Introduction

This is an incomplete shot at adding a command line option to blender to run a

python script. This is a hands on sort of thing for going about attacking a specific

task, mostly focusing on tools talked about in the intro to the source. I'll also

add a couple more tools to your bag of tricks at the end of the document.

 

This is based off of the blender-2.28 release to check it out you will want to do

the following in a directory that does not have a blender subdir: cvs checkout -r

blender-2-28-release blender (This is assuming you have already logged in to

the cvs server) While its not the latest and greatest, having a version you can

checkout and use tools like cvs diff with will be helpful, and this way all of the

code will match up. Durring the writing of this document the python API was being

replaced. (You'll see a hint to some of the problems with this later on)

 

Before you get started with this tutorial you should really try to build blender from

the 2-28-release and get that working first. It will make your life a lot easier.

 

Now on with the show...

Planning

Its always a good idea to talk to other people whenever you have questions. Just

talking to someone else about it will probably give you a better understanding of

the subject and help you flesh out your ideas. Don't be afraid to talk about things

with others, you may even answer your own questions while trying to explain your

ideas to someone else.

 

The first step is seeing what command line options are already there. So we fire up: blender -h

-p and -y are already taken so I think I'd like to go with:

blender -r myscript.py

 

So where do we start? We basically have the following tasks:

  • find where the command line stuff takes place.
  • add the help to the command line help function.
  • find out how to execute a python script.
  • find out where to execute our python script.

Searching The Code For Command Line Options

Ok lets hunt down the command line stuff since thats easy to find: cd

blender; cscope -R (cscope is a tool I mentioned in the last doc, cscope is

your friend) I'm going to use cscope to search for something in the -h output.

So lets search for "Game Engine specific options:" I got line 174 of creator.c

So lets see if we can find creator.c type: find . -name creator.c -print

It returns: ./source/creator/creator.c If we didn't know the exact name and or

capitalization, we could use -iname (case insensitive) and wild cards. so we

could do something like find . -iname "*tor.c" -print All of the

following examples would match: FUNtor.c unTor.c creator.c BLAHBlahblahtor.c

 

Looking through the options in creator.c, I think I'll put the info under Misc

options: Shoot there is a -R under windows... however looking up further we

notice there is a -S and a -s so we can use -r and -R were still all right.

 

I'm adding the following:

printf (" -r \t\tCall a python script from the command line\n");

 

The next thing we do is look at the name of this help function and its

print_help If we look at the code and find where print_help is called, were

probably pretty close to where we want to add the function to parse our python

script. So now we have a clue as to where to look to add our functions to

blender.

 

Firing up cscope again, this time I'm going to search for where print_help gets

called: We see its again creator.c line 261 and line 306. If you look at the two

lines shown by cscope we find the part where we handle Unix and windows style

help requests. While this may not be quite right, its at least where we will want

to parse the command line. (We may need to store the location of the python

script here for later, depending on what has already happened in the code, if

blender hasn't fully started up yet it might not make sense to call a python

script if you can't really do anything yet with it, python may not be started yet)

Writing Functions To Do Our Stuff

Now we need to know what to actually do, this is going to take some hunting:

We could fire up cscope and do some searches for python, Look at the basic

directory structure, could ask on #blendercoders on IRC, pray or a

bunch of other things. Again you can find more info on the directory structure

in the previous document and or the online resources.

 

There are all kinds of tricks we can use and the more we get to know the code

the more tricks we'll have to use. The truth is though this part takes some

work. The first thing to do is to collect all of the data you think you know: That

could be key words, gui elements, keystrokes/shortcuts, or even tool tips

comments.

 

I know current python scripts are in a text window, and they get called with the

alt-p command. We also know we could use works like python, script,

text, execute, evaluate, etc...

 

Because I've played with the source a bit I'm going to let you in on a little

secret that may or may not be helpful in this case. A great place to start

looking is in the following files:

<ccode>

blender/source/blender/src/space.c

blender/source/blender/src/toets.c (keys.c in dutch)

blender/source/blender/src/toolbox.c

</ccode>

looking at those files I easily figure out PKEY is used in the code to denote

pressing the p key. There is also a file keyval.c that has this info. doing :

<ccode>grep PKEY ./source/blender/src/*</ccode>

 

I get:<ccode>

drawtext.c

drawview.c

editimasel.c

editscreen.c

and a bunch of other stuff.

</ccode>

Looking at drawtext.c I think we have a winner... Looking at the PKEY we have:

<ccode>

case PKEY:

if (G.qual & LR_ALTKEY) {

if (!BPY_txt_do_python(st)) {

</ccode>

I think we want everything in that if statement looking at it.

 

We probably want to make our own function like print_help in creator.c that

does this stuff, or the equivalent. If this is the wrong place for it we can move

it later.

 

So there is quite a bit of stuff in this bit. Looking at it though we have this call

to BPY_txt_do_python and a few more things of interest then we have

this if lineno >=0 which looks like its jumping to the error in the text

window since we don't have that we can ignore that bit.

 

So lets start with something like this:

<ccode>

int call_python_script(char *infile) {

int lineno;

st; /* Need to figure out what we do here were not using a window */

char *py_filename;

 

if (!BPY_txt_do_python(st)) {

lineno = BPY_Err_getLinenumber();

py_filename = (char*) BPY_Err_getFilename();

if (!strcmp(py_filename, st->text->id.name+2))

 

We may or may not need to add a print statement for the error

message here, we need to do more research

who knows we may even be able to get rid of some of the stuff

above like the filename and lineno bit...

}

}

</ccode>

It still needs some work but were getting somewhere.

Take 2

Before we continue, your probably thinking wow I could never do that,

this guys not really a beginner he's cheating. So lets try this all again

from another route. Ok lets start with the directory's Our first choice is

blender/intern or blender/source The intern dir is mostly for higher level

little libraries that the blender code uses. Looking at blender/intern/ we

have a python directory could be promising... Looking at blender/source

we have to make a couple of more choices: We have to look at a bunch of

dirs but

 

<ccode>ls -ld */*python*</ccode>

gives us: blender/source/python and blender/source/bpython two more

promising dirs... We could use grep or cscope in there as well, and just

search for python. If you do after a little hunting you will also come up

with BPY_txt_do_python, or as we see further down you might even

skip over this step so lets go back to where we were.

 

We need to hunt down <ccode>BPY_txt_do_python</ccode> using cscope we get:

  • BPY_extern.h
  • BPY_main.c
  • BPY_interface.c
  • drawtext.c

Looking at BPY_main.c we could probably get by with setting up a

dummy SpaceText st and just calling the same function.

If we look through the function though what we really want is to setup our

python env and then make a call to BPY_runPython(text, d);

 

So lets change our function to the following:

<ccode>

int call_python_script(char *infile) {

PyObject* d = NULL;

PyObject* ret;

Text *text;

 

text =

d = newGlobalDictionary();

ret = BPY_runPython(text, d);

if (!ret) {

releaseGlobalDictionary(d);

BPY_Err_Handle(text);

Py_Finalize();

initBPythonInterpreter();

return 0;

} else {

releaseGlobalDictionary(d);

Py_DECREF(ret);

return 1;

}

}

</ccode>

Now all we need to do is figure out how to load our text into a Text object.

Doing some more hunting with cscope (for "Text ") I came up with:

<ccode>text = add_text(infile);</ccode>

Compiling

Time to see if our stuff compiles...

The first Error I get is:

<ccode>

../../../blender/source/creator/creator.c:176: `PyObject' undeclared

(first use in this function)</ccode>

After a little digging and talking to a python blender developer I learned

that I need to #include I also need to add a -I to

blender/source/creator/Makefile.am and blender/source/creator/Makefile

that includes the system python headers. Of course after editing the

Makefile.am if your using autotools you need to rerun bootstrap and

configure.

 

How do we know this, and what do we include? Well we would know because

its unable to find Python.h and to find what to include we can look at other

Makefiles. Cscope doesn't look at the Makefiles so we need to use

another trick. We could use cscope to find other files that use Python.h and

look at their makefile(s). Or we could use the following from the root dir:

<ccode>grep python `find . -name Makefile -print`</ccode>

 

Ok our next error is this one:

<ccode>

../../../blender/source/creator/creator.c: In function `call_python_script':

../../../blender/source/creator/creator.c:178: `Text' undeclared

(first use in this function)

</ccode>

Looks like I need some more include paths and header files.. Using

cscope to find Text's definition I came up with:

<ccode>#include "DNA_text_types.h"</ccode>

 

Now were down to a couple of warnings:

<ccode>

../../../blender/source/creator/creator.c:180: warning: assignment makes

pointer from integer without a cast

 

../../../blender/source/creator/creator.c:182: warning: assignment makes

pointer from integer without a cast

 

../../../blender/source/creator/creator.c:183: warning: assignment makes

pointer from integer without a cast</ccode>

A couple of casts and those are easly taken care of.

 

Now we need to add the part that calls our new function. Lets first look at the

blender -h output again. We need to model our function call after

another help function that has a parameter. Lets use : the -p option so

search for case 'p' we only have one arg So lets add the following after the

'p' case:

<ccode>

case 'r':

a++;

call_python_script(argv[a]);

break;

</ccode>

This probably is not the place to put it because we may not want to execute

python scripts just yet but its a good place for now until we have everything

compiling. We really need to look at this code and find the proper place to

do it.

 

Now after another try and compiling we get the following:

<ccode>

source/.libs/libblender_source.al(creator.lo): In function `call_python_script':

 

../../../blender/source/creator/creator.c:182: undefined reference to

`newGlobalDictionary'

 

../../../blender/source/creator/creator.c:183: undefined reference to

`BPY_runPython'

 

../../../blender/source/creator/creator.c:192: undefined reference to

`releaseGlobalDictionary'

 

../../../blender/source/creator/creator.c:186: undefined reference to

`releaseGlobalDictionary'

 

../../../blender/source/creator/creator.c:189: undefined reference to

`initBPythonInterpreter'

 

</ccode>

Looks like we need to fix some of the linking. In general this could

mean were not including the library that has this stuff defined in it, and

or its in the wrong spot for the linker.

 

Its taken me way to long to get this document up on the blender site,

so I'm going to cut it off here and hopefully there will be a followup down

the road...

 

Too Be continued.....

New Tools

Ok I promised I'd introduce a couple of new tools. The first is ldd.

ldd is used to determine what library's a program is linked against.

For example Here is some output of running ldd on blender on my system.

<ccode>

ldd blender

libblender_source.so.0 => /usr/local/blender/lib/libblender_source.so.0

(0x40016000)

 

libm.so.6 => /lib/libm.so.6 (0x40401000)

libpthread.so.0 => /lib/libpthread.so.0 (0x40425000)

libgcc_s.so.1 => /usr/lib/libgcc_s.so.1 (0x4043a000)

libc.so.6 => /lib/libc.so.6 (0x40442000)

libblender_intern.so.0 => /usr/local/blender/lib/libblender_intern.so.0

(0x40567000)

 

libGLU.so.1 => /usr/X11R6/lib/libGLU.so.1 (0x40601000)

libGL.so.1 => /usr/lib/libGL.so.1 (0x4067e000)

libopenal.so.0 => /usr/local/lib/libopenal.so.0 (0x406e5000)

(there is more thats deleted)

</ccode>

Why is this useful?

For starters you can make sure its linking against the correct version

of libGL. If you have multiple versions on your system you may be

wondering which version its using. The next tool is nm. Nm is used to

show the symbols defined in a library or other object file. For example

here is some of the output on my machine of:

<ccode>

nm /usr/lib/libc.a

 

init-first.o:

0000019f t Letext

U __close

U __environ

U __fpu_control

00000004 C __libc_argc

00000004 C __libc_argv

U __libc_fatal

U __libc_init

00000178 T __libc_init_first

U __libc_init_secure

00000000 D __libc_multiple_libcs

</ccode>

What is all of this garbage, and why do I care?

each line is a value, type, and symbol. The type and symbol are

mostly what were interested in. Notice in the above U __libc_init_

secure means that __libc_init_secure is undefined. and 00000178

T __libc_init_first means that this library has __libc_init_first defined in

it, if were using this variable or function this is the library we need to

link against. Read the man page on nm for information on the various types.

 

So whenever your getting problems where you have undefined symbol

BLAH.... you can start going through your library's looking for where

BLAH is defined. So for example lets say we were having problems finding

the floor function. well a quick nm /usr/lib/libm.a |grep floor shows us

thats the library we need to add to our linking line.

 

So lets say your really lost and have no clue where floor might be found.

You could always do the following:

<ccode>

cd /usr/lib

script /tmp/mein.txt

foreach i (`find . -type f -print`)

echo "Looking at $i"

nm $i |grep floor

end

exit

</ccode>

Then look through /tmp/mein.txt for floor, once you found it scroll up

to the Looking at line and you've found your library.