The Blender Command Port - Blender Python Server and Client
The Blender Command Port patch makes it possible to start Blender in Python server mode. Clients can connect and send Python commands via the command port to the server.
- The Command Port allows to edit objects simultaneously using the Blender GUI and one or more clients connected to Blender via the command port.
- The patch also includes blash, a Blender Python shell client.
- blash can be used with the emacs python mode - the result is a formidable Blender Python development environment.
- Clients can be written in any language: lisp, Python, Perl, C, C++, Java etc.
- The patch was only tested on Debian sid until now but should run on any platform after a little customisation.
- If you have comments please send me an email.
Console, Blender Server, emacs client and shell client
Links
- The command port patch.
- The command port patch on the Blender patch tracker.
- Announcement with thread at the Bf-committers mailing list.
Example - a simple pyramid
The following example shows how the command port can be used to model a simple pyramid:
-
start the Blender server in some xterm
$ blender --geometry 800x600+10+10 --bcp 6789 &
- start blash in another xterm
$ blash --port 6789 This is Blash - the GNU BLender-Again SHell :) Connection to Blender Server established. (IP address: 127.0.0.1, port: 6789)
from Blender import * vertices = [[1, 1, 0], [-1, 1, 0], [-1, -1, 0], [1, -1, 0], [0, 0, 1.27]] faces = [[3, 2, 1, 0], [0, 1, 4], [1, 2, 4], [2, 3, 4], [3, 0, 4]] mesh = Mesh.New('mesh') mesh.verts.extend(vertices) mesh.faces.extend(faces) scene = Scene.GetCurrent() object = scene.objects.new(mesh, 'object') Redraw() bye
Installation
-
make a directory to build Blender
BCP=$HOME/bcp mkdir -p $BCP cd $BCP
-
download blender 2.44 sources
wget http://download.blender.org/source/blender-2.44.tar.gz wget http://download.blender.org/source/blender-2.44.tar.gz.md5sum md5sum -c blender-2.44.tar.gz.md5sum
-
download blender command port patch
wget http://formgames.org/blender/command-port/patches/blender-command-port-patch.2007-06-26.txt
-
unpack the blender sources
gunzip blender-2.44.tar.gz tar xvf blender-2.44.tar
-
patch them
cd blender-2.44 patch -p0 < ../blender-command-port-patch.2007-06-26.txt
-
build blash and blender
scons WITH_COMMAND_PORT=true blash
-
the (linux) binaries
$BCP/install/linux2/blender $BCP/install/linux2/blash
Blash Meta Commands
Some commands - the blash meta commands - are handeled directly by blash and not send to the Blender server:
help - Print a simple help text (not implemented yet). bye, quit, exit - Disconnect from the Blender server and exit blash. .shell, .single, .s - Switch to shell mode. .eval, .e - Switch to eval mode. .file, .f - Switch to file mode. . - All text until the next line consisting of a dot only are send to Blender for evaluating in file mode.
Blash Input Modes
- Shell Mode (prompt:
>>>
)Shell mode is the blash default mode and corresponds to the interactive Python shell. If the first line of an input is a complete Python statement, it is evaluated immediately and the result, stdout and stderr are printed; if the first line is not a complete Python statement, all following lines are appended to the input until an empty line is entered. The complete entered text is evaluated and the result, stdout and stderr are printed;
Shell mode is convenient when testing Blender Python code. It is inconvenient when there are empty lines inside of the entered code.
Shell mode is the default mode when blash is started. It is possible to switch from another blash input mode to shell mode by entering
.shell
or simply.s
.>>> print "This is a one-line command." This is a one-line command. >>> def foo(): ... print "This is a multi-line command." ... >>>
Note: The three dots (
...
) at the beginning of the lines of a multi line command are called continuation prompt and have the same meaning as in the Python shell: they signal that the current line is the 2nd, 3rd, ... line of a multi line input. - Eval Mode (prompt:
EM>
)In Eval mode only single expressions are allowed. Every expression has to be finished with a line consisting only of a single dot. The entered expression is evaluated and the result is printed.
It is possible to switch from another mode to eval mode by entering
.eval
or simply.e
. When blash is started with the command line option--eval-mode
or-e
, blash will start up in eval mode immediately - or better should start in eval mode immediately ... here comes my first bug report: For the moment, even when started with--eval-mode
or-e
, file mode is entered... How can I commit a patch to a patch correcting this?>>> .eval ...eval mode EM> 123 + 321 ... . 444 EM> "this evaluates to a string" ... . 'this evaluates to a string' EM> .shell ... . ...shell/single mode >>>
- File Mode (prompt:
FM>
)In file mode all lines until the next line consisting only of a single dot are send to the Blender server. The whole text, which can consist of any sequence of Python code, is evaluated by the Blender server. This is convenient when a long sequence of commands (which might also include empty lines) should be evaluated and the result of expressions included in the code is not of interest.
It is possible to switch from another blash input mode to file mode by entering
.file
or simply.f
. When blash is started with the command line option--file-mode
or-f
, blash will start up in file mode immediately.>>> .file ...file mode FM> ... ... from Blender import * ... ... vertices = [[1, 1, 0], [-1, 1, 0], [-1, -1, 0], [1, -1, 0], [0, 0, 1.27]] ... faces = [[3, 2, 1, 0], [0, 1, 4], [1, 2, 4], [2, 3, 4], [3, 0, 4]] ... ... mesh = Mesh.New('mesh') ... mesh.verts.extend(vertices) ... mesh.faces.extend(faces) ... ... scene = Scene.GetCurrent() ... object = scene.objects.new(mesh, 'object') ... Redraw() ... ... . None FM> .shell ... . ...shell/single mode >>>
From shell mode it is also possible to enter a sequence of lines in file mode by starting and terminating the input with a single dot line:
>>> . ...single text SF> from Blender import * ... ... vertices = [[1, 1, 0], [-1, 1, 0], [-1, -1, 0], [1, -1, 0], [0, 0, 1.27]] ... faces = [[3, 2, 1, 0], [0, 1, 4], [1, 2, 4], [2, 3, 4], [3, 0, 4]] ... mesh = Mesh.New('mesh') ... mesh.verts.extend(vertices) ... mesh.faces.extend(faces) ... scene = Scene.GetCurrent() ... object = scene.objects.new(mesh, 'object') ... Redraw() ... . None >>>
The BCP Protocol
For exchanging commands and evaluation results via the BCP socket a very simple protocol is used: The evaluation mode, the Python command input, the result of the evaluation, stdout and stderr, everything is sent forth and back packed into a simple c-string byte sequence via the command port socket.
The following lines explain the simple scheme used to pack meta commands, integers and strings into a c-string and the order used in command and result packages to pack the necessary information.
The encoded format can be seen when the Blender server is started
with the debug option --debug bcp:2
or - when a hex dump is wished also - --debug bcp:3
:
blender --debug bcp:2 --geometry 800x600+10+10 --bcp 6789 &
Here some examples of the simple BCP coding scheme:
A simple Blash read / evaluate / print loop protocol:
$ blash --port 7777 This is Blash - the GNU BLender-Again SHell :) Connection to Blender Server established. (IP address: 127.0.0.1, port: 7777) >>> 123 123 >>>
And (some of) the corresponding (for readability slightly edited) Blash server output:
$ blender --debug bcp:2 --geometry 800x600+10+10 --bcp 7777 [DEBUG] Turning debugging on for module `bcp' (level: 2). Starting the Blender command port - listening on port 7777. Handling client 127.0.0.1 ... >>> the packed command package: >>>#cS #s3 123 <<< ... >>> the packed result package: >>>#i0 #s0 #s4 123 #s0 <<< ...
And here the command and result packages analysed:
-
Blash input:
123
-
The command package send to the Blender server (everything between
>>>
and
<<<
):
>>>#cS #s3 123 <<<
Interpretation of the command package:
- The first line codes the evaluation mode to use:
#cS
stands for shell mode,#cE
for eval mode and#cS
for file mode. The prefix#c
stands for BCP meta command. - The rest of the package contains the blash command input:
-
#s3
- a string (#s
) of length 3 (3
) follows -
123
- the string itself
-
- The first line codes the evaluation mode to use:
-
The result package send back to blash:
>>>#i0 #s0 #s4 123 #s0 <<<
Interpretation of the result package:
- The first line contains the result code of the evaluation:
#i
signals that the rest of the line is an integer,0
is the integer itself: 0 for a successful evaluation.
The rest of the package consists in three strings coded in the same way already seen when looking at the command package. The order of the strings is as follows:
- result string - the result string is only used in evaluation mode. In shell mode the result is printed to stdout and therefore returned in the stdout string and not the result string. In our case the result string is empty and of length 0.
- stdout string - everything printed to stdout during the evaluation of the command. In shell mode also the result is printed to stdout and therefore returned in the stdout string - In our case the whole stdout string is the result: 123. It has the length 4 as it is terminated with a newline character.
- stderr string - the last string contains everything printed to stderr during the evaluation of the command. In our case nothing was printed to stdout.
In summary the result contained the following information:
- result code:
0
(success) - result string (only used in eval mode):
""
- stdout string (contains also the result when in shell mode):
"123\n"
- stderr string:
""
- The first line contains the result code of the evaluation:
-
Blash output:
123
See the next paragraph for a very simple Python BCP client.
The very simplest "hello world" Python client :)
Here comes a very very simple Python "hello world" client to answer to the posting of Chris Want - thank you :)
-
Start the Blender server in some xterm
$ blender --geometry 800x600+10+10 --bcp 7777
-
Start python in some other xterm
$ python
-
Enter the following commands into the Python shell:
import socket sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect(("127.0.0.1", 7777)) sock.send('#cS\n#s13\n"hello world"\n\0') sock.recv(1024) sock.close()
-
...and here comes the resulting protocol:
$ python Python 2.4.4 (#2, Apr 26 2007, 00:02:45) [GCC 4.1.2 20061115 (prerelease) (Debian 4.1.1-21)] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import socket >>> sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) >>> sock.connect(("127.0.0.1", 7777)) >>> sock.send('#cS\n#s13\n"hello world"\n\0') 24 >>> sock.recv(1024) "#i0\n#s0\n\n#s14\n'hello world'\n\n#s0\n\n\x00" >>> sock.close() >>>
Drawing a simple pyramid with a Python client
And here the simple pyramid example as simple python client:
-
The code:
import socket sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect(("127.0.0.1", 7777)) sock.send("""#cF #s323 from Blender import * vertices = [[1, 1, 0], [-1, 1, 0], [-1, -1, 0], [1, -1, 0], [0, 0, 1.27]] faces = [[3, 2, 1, 0], [0, 1, 4], [1, 2, 4], [2, 3, 4], [3, 0, 4]] mesh = Mesh.New('mesh') mesh.verts.extend(vertices) mesh.faces.extend(faces) scene = Scene.GetCurrent() object = scene.objects.new(mesh, 'object') Redraw() \0""") sock.recv(1024) sock.close()
-
The protocol:
$ python Python 2.4.4 (#2, Apr 26 2007, 00:02:45) [GCC 4.1.2 20061115 (prerelease) (Debian 4.1.1-21)] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import socket >>> sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) >>> sock.connect(("127.0.0.1", 7777)) >>> sock.send("""#cF ... #s323 ... from Blender import * ... vertices = [[1, 1, 0], [-1, 1, 0], [-1, -1, 0], [1, -1, 0], [0, 0, 1.27]] ... faces = [[3, 2, 1, 0], [0, 1, 4], [1, 2, 4], [2, 3, 4], [3, 0, 4]] ... mesh = Mesh.New('mesh') ... mesh.verts.extend(vertices) ... mesh.faces.extend(faces) ... scene = Scene.GetCurrent() ... object = scene.objects.new(mesh, 'object') ... Redraw() ... ... \0""") 335 >>> sock.recv(1024) '#i0\n#s4\nNone\n#s0\n\n#s0\n\n\x00' >>> sock.close() >>>
For other versions see here: