From BlenderWiki

Jump to: navigation, search

Adding command line option for python scripts

by Kent Mein

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
</source
-p and -y are already taken so I think I'd like to go with:
<source lang="bash">
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:

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


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 :

grep PKEY ./source/blender/src/*


I get:

  • drawtext.c
  • drawview.c
  • editimasel.c
  • editscreen.c

and a bunch of other stuff.


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

case PKEY:
                        if (G.qual & LR_ALTKEY) {
                                if (!BPY_txt_do_python(st)) {

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:

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...
   }
}


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

ls -ld */*python*

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

BPY_txt_do_python

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:

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;
   }
}

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:

text = add_text(infile);

Compiling

Time to see if our stuff compiles...

The first Error I get is:

../../../blender/source/creator/creator.c:176: `PyObject' undeclared 
(first use in this function)

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:

grep python `find . -name Makefile -print`


Ok our next error is this one:

../../../blender/source/creator/creator.c: In function `call_python_script':
../../../blender/source/creator/creator.c:178: `Text' undeclared 
(first use in this function)

Looks like I need some more include paths and header files.. Using cscope to find Text's definition I came up with:

#include "DNA_text_types.h"


Now were down to a couple of warnings:

../../../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

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:

case 'r':
   a++;
   call_python_script(argv[a]);
   break;

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:

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'

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.

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)

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:

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

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:

 cd /usr/lib
 script /tmp/mein.txt
 foreach i (`find . -type f -print`)
    echo "Looking at $i"
    nm $i |grep floor
    end
 exit

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.