banner perso

Some links about me. Many of my 3D designs are free. I also post on Google+ and in another blog, oz4.us
review (32) general (26) issue (16) mechanical (16) replacement (14) software (13) addon (12) bowden tube (10) business (10) consumables (10) heat (10) feeder (8) hot end (8) weird (7) motor (6) off-topic (6) trick (6) electric (4) bed (3) cnc (2)

Tuesday, February 12, 2013

How to run a (Cura) Python plugin on STL files without Cura?

Here is a geekier post about how to tweak Cura plugins and convert them to standalone Python scripts that post-process your g-code without Cura nor Skeinforge.

After I designed and used my wood gradient plugin, people naturally started to ask for the source code. I was a bit lazy first, because it was made for Cura 12.08: the patch I released was not very convenient, and Cura had evolved with a better and simpler plugin system, and included my own plugin.

Then, I got the time to fix it. Here is how I converted it to a standalone Python script that runs on its own, asking nothing to nobody.

The way I did it may be of interest to a (very) few people out there, and it is the subject of this post.

What is the use for a standalone g-code post processor?

Indeed, imagine you see an interesting plugin for Cura that you would like to run from another slicer, or to automate within a special process (eg. with a matlab script that generates gcode directly). The most portable way is then to run your gcode through a console-based plugin, without the interface and need for other software around it.

Actually, my original wood plugin for Cura & Skeinforge was a bit tricky to write, as it did require multiple and somehow ugly required code patches in both programs. With Cura 12.10, Daid finally designed a much simpler post-processor plugin system, and he ported my plugin to it (it even was the first third-party plugin, in Cura 12.11). He did well, since I was too lazy to go back and check if it would rune as a pure Skeinforge plugin to fulfill some of my requests.

Since the work was almost done, here is how I took it over again and made it a pure Python script. What may be more interesting even is that, as of today (2013-02-11, Cura v12.11 and above), the same approach would probably make any other Cura post processing plugin a standalone Python script as well.

From a Cura post processor to a standalone script

When you open the wood plugin from the "plugin" directory of Cura installation folder, you'll get something like the following. Writing a new plugin for Cura is outside the scope of this post, and is already well documented on the official website.

So back to the beginning of the wood.py plugin:

#Name: Wood
#Info: Vary the print temperature troughout the print to create wood rings with the LayWood printing material
#Depend: GCode
#Type: postprocess
#Param: minTemp(float:180) Min print temperature (c)
#Param: maxTemp(float:230) Max print temperature (c)
#Param: grainSize(float:3.0) Average Grain Size (mm)

import re
import random
As you can see, the plugin parameters are clearly listed after the Param: prefix. In a standalone script, we want them to come from parameters on the command line, not from the Cura context. The other missing information in the script is the g-code filename which is obviously needed.

Here is the piece of code you would like to copy/paste just after the __license__ information in the top of the existing plugin (you can download the whole file also from thingiverse):

# This part is an "adapter" to Daid's version of my original Cura/Skeinforge plugin that
# he upgraded to the lastest & simpler Cura plugin system. It enables commmand-line
# postprocessing of a gcode file, so as to insert the temperature commands at each layer.
# Also it is still viewed by Cura as a regular and valid plugin!
# To run it you need Python, then simply run it like
#   wood_standalone.py --min minTemp --max maxTemp --grain grainSize --file gcodeFile
# It will "patch" your gcode file with the appropriate M104 temperature change.
import inspect
import sys
import getopt

def plugin_standalone_usage(myName):
print "Usage:"
print "  "+myName+" --min minTemp --max maxTemp --grain grainSize --file gcodeFile"
print "  "+myName+" -i minTemp -a maxTemp -g grainSize -f gcodeFile"
print "Licensed under CC-BY-NC from Jeremie.Francois@gmail.com (betterprinter.blogspot.com)"

except NameError:
# Then we are called from the command line (not from cura)
# trying len(inspect.stack()) > 2 would be less secure btw
opts, extraparams = getopt.getopt(sys.argv[1:],'i:a:g:f:h',['min=','max=','grain=','file=']) 
for o,p in opts:
if o in ['-i','--min']:
minTemp = float(p)
elif o in ['-a','--max']:
maxTemp = float(p)
elif o in ['-g','--grain']:
grainSize = float(p)
elif o in ['-f','--file']:
filename = p
if not filename:
Note: don't blame me, as this is the third program I ever wrote in Python ;)

Obviously, this piece of code is designed for the wood plugin. Now, you will need to add/remove/change the parameters for you own selected plugin accordingly but is is quite easy based on this working sample. May be in another life I would write the necessary code (with Python reflection capabilities) to find out all this from the plugin source code itself, but is it really worth the time? ;)

As you probably understood, the plugin_standalone_usage()function is optional. It simply prints some usage information in the console when the caller provides no parameters (or when he only use the -h or --help option).

There are two pieces of codes that are important and would have to be changed for other plugins:

As a side not, the way I wrote it makes the plugin compatible with Cura. IMHO such double usage would be something useful for other Cura plugins.

The parameters

The second parameter given to the getopt() function is important: the weird character string i:a:g:f:h means that the script understand all of these single-letter parameter. When a letter is followed by a colon, it means that a value is also expected after the option. Else it is a just a flag with no value (such as the -h option).

The third parameter to getopt() is the parameter long-name variants, with a similar syntax.

One compulsory and common parameter is of course the filename, which is the g-code file to process.

Finally, you will need to write what to do with the parameter, in the for o,p in opts: loop. Do not forget to use float(p) to convert the argument (a string) into a number when it is needed!

And the default options

You will have to provide default values for your options, else you probably want to add more tests in the end of the block. Here we only require that filename is explicitly provided.

Look for Python syntax on the web if you need more conditionals, the language is easy to learn and very well documented.  Don't forget the colon after a conditional statement and respect the strict indentation and you are done.

Finally, how to run the Python script?

To run it you still need Python of course. Then simply call it from a console like this:
python wood_standalone.py --min 180 --max 250 --grain 3 --file frog.g
Note: if you are running windows and you are getting issues with the command line, you will find answers in the python FAQ.

Running the script on your frog will "patch" the g-code file in place, with the appropriate M104 temperature change. Keep a backup of the original file, and try not to run the script several time on the same file as it would clobber the code with many useless M104 (no big harm though).

The script itself is published on thingiverse.


  1. I'm getting an error in the script when run in windows command prompt:

    C:\Users\James>python wood_standalone.py -i 185 -a 240 -g 3 -f c:\Users\James\ba
    Traceback (most recent call last):
    File "wood_standalone.py", line 173, in
    noise= banding * perlin.fractal(octaves, persistence, 0,0,z/(grainSize*2));
    TypeError: unsupported operand type(s) for /: 'float' and 'str'

    1. You are quick and you are right... Sorry for this :)
      Check the update I just uploaded to thingiverse http://www.thingiverse.com/thing:49276
      It also show the temperature graph in a better way.

  2. Hi Jeremie,
    The comment from March 16 is spam. You may want to remove it so you remain visible in Google :)

    +1 for blog posts with "interest to a (very) few people"!

  3. Thanks Jensa, I did not know it could be harmful, but it makes sense indeed... Will react faster next time.
    And I will keep on posting geeky stuff if a few but worthy people like it ;)

  4. This is cool... Seems to not work when model is less than 15mm... Been looking at where I can tweak that on the script... for now, I just add "G0 Z16" before the end of the gcode and it works...