Scripting in Structure Synth

I’ve added a JavaScript interface to Structure Synth. This makes it possible to automate and script animations from within Structure Synth. Here is an example:

The JavaScript interface will be part of the next version of Structure Synth (which is almost complete), but it is possible to try out the new features right now, by checking out the sources from the Subversion repository.

The rest of this post shows how to use the JavaScript interface.

A very simple example

This snippet shows how to raytrace an EisenScript system to an external file:
 #javascript Builder.load("NouveauMovie.es"); Builder.setSize(0,100); Builder.define("myRotation",45); Builder.build(); Builder.raytraceToFile("output.png",true); 

The script above can be executed directly from within Structure Synth, the same way EisenScript code is run. Just paste it into a window and press the ‘play’ button.

The first line (“#javascript”) tells Structure Synth that the code should be invoked using the internal JavaScript interpreter, instead of being parsed as EisenScript. This tag must appear as the first code line without any spaces.

The ‘load’ command simply reads the given file into a buffer. Notice, that files are located relative to the path the current code is saved in (so save your script before running it).

The ‘setSize’ determines the output size. In the example above the ‘width’ argument is zero: this means the width will be calculated from the height using the aspect ratio from the OpenGL workspace window. It is of course possible to specify both dimensions, but be careful not to distort the aspect ratio. The setSize-command will not affect the OpenGL windows size – it only controls the internal raytracer and the template export dimensions.

Next, the ‘define’ makes it possible to overwrite preprocessor variables in the EisenScript file. The example above assumes that there is a preprocessor variable defined such as:
 #define myRotation 180 

Thus, by calling ‘Builder.define’, variables in the EisenScript code can be controlled, which as the next example shows, makes it possible to do scripted animations.

The ‘build’ command executes the EisenScript system in the buffer. The results will be visible in the OpenGL window.

The final command ‘raytraceToFile’ invokes the internal raytracer and renders an image to “output.png” (the ‘true’ flag here just tells Structure Synth to overwrite files without warning).

But of course the real power of JavaScript is that you can write real programs, with functions and conditionals. Structure Synth uses QT’s QScriptEngine, which makes it possible to use standard JavaScript language constructs. The following example shows how this works.

An animation example

The example shows how to construct a 1000 frame animation using the internal raytracer.

 #javascript   function pad(number) {    number = number + ''; // convert to string    while (number.length < 4) { number = "0" + number; }    return number; }   Builder.load("NouveauMovie.es"); max = 1000; for (i = 0; i <= max; i+=1) {    c = i/max;    Builder.reset();    Builder.setSize(0,600);    Builder.define("_rz",c*360);    Builder.define("_md",parseInt(20+c*1000));    Builder.define("_dofa",0.2+ 0.1*Math.sin(c*3.1415*2));    Builder.build();    Builder.raytraceToFile("Image" + pad(i) + ".png",true); } 

The 'reset' command is necessary when building animations. This is because of the way 'define' variables are set: First, the Builder loads the EisenScript into a buffer, then it sets the 'define' variables by simply substituting text strings in the buffer with the actual values. Since this can only be done once, it is necessary to reset the buffer to the original state before each new frame - this is the done by calling the 'reset' command.

The JavaScript 'pad' function simply makes sure the files get properly named: Image0000.png, Image0001.png, Image0002.png, and so on. This makes it easier to encode them subsequently.

A few hints: fix the camera position and random seed in the EisenScript file. The camera settings can be included in the EisenScript by using the context menu on the OpenGL window, and choosing "Copy Camera Settings to EisenScript Window". Instead of changing the camera position, it is much easier to apply some zoom and rotational operations on the start rule in the EisenScript code.

Converting images to a movie

Video encoding can be extremely tricky, and there are many ways to do it. I used the mencoder software (Windows binaries here) to convert the series of images into a MPEG4-encoded avi file. This was done by the following command:

 c:\PathToMencoder\mencoder.exe "mf://*.png" -mf fps=25:type=png -ovc lavc -lavcopts vcodec=mpeg4:vbitrate=2642822:mbd=2:keyint=132:vqblur=1.0:cmp=2:subcmp=2:dia=2:mv0:last_pred=3 -oac copy -o output.avi 

Notice, that Mencoder can only recognize the correct image order, if they are alfabetically ordered (Image1.png, ..., Image10.png, ... will not work), which is why the example above used the 'pad' function.

Besides the YouTube version shown at the top of this post, I tried a few other video sharing services as well: Flickr and Vimeo. I was a bit disappointed by both - Vimeo has a large and interesting community, but the free 'basic' subscribtion seems a bit limited. For comparison the original version can be downloaded here (15MB AVI).

Calling external raytracers

It is also possible to call external raytracers using the JavaScript interface.

The following code shows how the loop above can be modified to use Sunflow as external raytracer.

 /// ... load system .... for (i = 0; i <= max; i+=1) {    Builder.reset();    /// ... set 'define' variables here    Builder.build();      // ---- Sunflow raytrace -----    name = "f:/Test/out" + pad(i);    Builder.templateRenderToFile("Sunflow-Colored.rendertemplate", name + ".sc",true);    Builder.execute('"C:/Program Files/Java/jdk1.6.0_21/bin/java"', '-Xmx1G -server -jar "%SUNFLOW%/sunflow.jar" ' + name + ".sc -nogui -o " + name + ".png", true); } 

The 'templateRenderToFile' takes three arguments: the first is the name of a rendertemplate (the default location is the 'Misc' folder), the second the desired output filename, and the third determines whether to overwrite without warnings.

The 'execute' command spawns an external process. The first parameter is the name of the executable, the second a string with command line arguments, and the third controls if the JavaScript interpreter should wait for the process to finish.

Getting the syntax right for executing external commands, can be tricky: for instance, normally Sunflow is invoked by calling a shell-script, but the 'Builder.execute' command cannot parse these. Instead it calls the Java spawner directly.

The 'Builder.execute' command is able to substitute environment commands (e.g. the '%SUNFLOW%' variable).

Also be careful with spaces: all command line arguments which contain spaces must be quoted. In JavaScript the easiest way to accomplish this, is by using single quotes for the arguments to 'Builder.execute', and use double quotes inside these strings for command line arguments which could contain spaces. (In the example above "%SUNFLOW%/sunflow.jar" is quoted even though it does not contain spaces - this is because %SUNFLOW% may be substituted for a path which contains spaces.)

Finally, it is also possible just to use 'Builder.renderToFile' to save the current OpenGL view as an image (without raytracing). This is useful for creating fast previews.

Reference

The Structure Synth JavaScript API only exposes a single, global object - the Builder. The following methods are available:

Loads an EisenScript file into the working buffer.

Builder.define(string input, string value)
Performs text substitutions on the buffer (but ignores preprocessor lines!)

Builder.prepend(string prescript);
Prepends the buffer script with 'prescript'

Builder.append(string postscript);
Appends the buffer script with 'postscript'

Builder.build();
Builds the system. The output is visible in the OpenGL workspace window.

Builder.renderToFile(string fileName, bool overwrite);
Saves the current OpenGL view to a file.
'overwrite' toggles if it is okay to overwrite existing files without warning.

Builder.raytraceToFile(string fileName, bool overwrite);
Raytrace image with internal raytracer to file.
'overwrite' toggles if it is okay to overwrite existing files without warning.

Builder.templateRenderToFile(string templateName, string fileName, bool overwrite);
Raytrace image with same dimensions as viewport to file.
'overwrite' toggles if it is okay to overwrite existing files without warning.

Builder.execute(string fileName, string args, bool waitForFinish);
Executes an external process. Meant to be used after having called 'templateRenderToFile'.
If 'waitForFinish' is true, the JavaScript interpreter will block until the process has completed.

Builder.reset();
Restores the original content of the buffer (useful if substitutions have been made.)

Builder.setSize(int width, int height);
Sets the dimensions for the output (internal raytracer and templates).
If both width and height is zero, the screen viewport size is used (default).
If either width or height is zero, the other dimension is calculated using the aspect ratio of the viewport.

5 thoughts on “Scripting in Structure Synth”

1. Aspect Stalence says:

Hello, i wanted to ask a question. When calling sunflow as an external raytracer, the templates are exported without any problems but i don’t get any png files. Is this the way it is supposed to work?I was expecting sunflow to automatically run–>save the templates as png files.No errors while rendering either.
This is the exact “execute” line:
Builder.execute(‘”C:/Program Files/Java/jdk1.7.0_07/bin/java”‘, ‘-Xmx1G -server -jar “C:/Users/Aspect/Desktop/Desktop/Ssynth Sunflow Exports/sunflow/sunflow.jar” ‘ + name + “.sc -nogui -o ” + name + “.png”, true);
Could it be that the newer version of jdk causes some sort of conflict? Thanks in advance

2. Aspect Stalence says:

Oh, sorry for doubleposting but i forgot something probably important.
On the render status below it says “Executed %javapath%/java in 0 seconds…”

3. Nicasi says:

@Aspect

I guess it’s a bit late for you but it might help someone else. If you don’t want to change env variables or don’t know what they are, you may try this, changing the location of sunflow and java to where you dropped them:

Builder.execute(‘”C:/Program Files/Java/jdk1.7.0_40/bin/java”‘, ‘-Xmx1G -server -jar “C:/Program Files (x86)/sunflow/sunflow.jar” “‘ + name + ‘.sc” -nogui -o “‘ + name + ‘.png” ‘, true);

You should get a window saying “Executing Java” and “Running for .. seconds” and afterwards:

Executed %javapath%/java in 15 seconds

or something similar

4. Bert says:

StructureSynth 1.5.0, on OS X, crashing every time after a random number of frames using the movie – javascript example file provided with the package, unaltered.
I had a lot of hope for this and it’s not working. Very disappointed. See other people able to do this successfully online, even posting successful videos, and I am not, even with the example files unchanged.
Any ideas?
It “works” for a short time, outputs PNGs, I can see the progress on screen, and then it finally crashes out and leaves me with the classic “Structure Synth has unexpectedly quit” message.
Never gotten it past frame 35.

5. Rick says:

Can’t seem to get this to work for me. Any chance there is an explanation for total dummies out there somewhere? I love the objects I can create, but I want to see it move and grow. Help!