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...
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:
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)
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.
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:
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>
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.....
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.