Jump to content
Ultimaker Community of 3D Printing Experts

Plugin for woodFill or similar filament


Recommended Posts

Posted · Plugin for woodFill or similar filament

Does anyone know if there is a plugin for Cura (I'm currently using 2.1.2) that can automate a random heat change pattern, resulting in a colour change when using woodFill or a similar filament? I know there is the TweakAtZ 5.0.1 script, but this can be a bit laborious when you are printing a tall object and you want the colour to change every millimetre or so.

  • Link to post
    Share on other sites
    Posted · Plugin for woodFill or similar filament

    Check out this item on Thingiverse. The author has a standalone python script that it looks like you just feed it your .gcode file

    Generate wood patterns with temperature changes

    This might work as-is in Cura 2.x, I dunno

     

    Thanks for that DaHai8,

    I had already found that, but can't work out how to plug it in to the Cura software. So if anyone can help with that I'd appreciate it.

  • Link to post
    Share on other sites
    Posted (edited) · Plugin for woodFill or similar filament

    Any hint´s what to change in the plugins - or do they have to be rewritten completely?

    Edited by Guest
  • Link to post
    Share on other sites
    Posted · Plugin for woodFill or similar filament

    This one had to be rehashed from top/bottom because it tried to be a 15.x plugin and a stand-alone python script at the same time.

    There are new defs in Cura 2.x like "getSettingDataString(self)" to get the user's input, as well as 15.x handled the .gcode file directly whereas in 2.x, the plugin is handed a structure containing the gcode, so no file reads/writes.

    There are also some Python2 v. new Python3 differences that caught me a few times.

    However, as for Cura 2.1.x v 2.3 the changes are minor (which is why I kept missing them!):

    1. getSettingData appears to be deprecated and replaced with getSettingDataString

    2. "default" is now "default_value" in the settings

    3. "visible" is deprecated and cannot be used

    4. "name", "key", "metadata", "version" in the getSettingDataString appear to be required

    5. "settings" is also required, but it can be empty brackets

    6. You can return the Setting Data as a big string (or not - not sure which way is preferred):

     

    def getSettingDataString(self):       return """{           "name":"Layer Numbers",           "key": "LayerNumbers",           "metadata":{},           "version": 2,           "settings":           {           }       }"""

     

    Everything else seems to work without modifications. So its all apparently confined to the getSettingDataString function/def/thingy

  • Link to post
    Share on other sites
    Posted · Plugin for woodFill or similar filament

    Wood Grain Post Processing Plugin for Cura 2.3 (Beta) is done!

    I'm don't think it will work in 2.1.x...

    Here's the code WoodGrain.py (no warranty whatsoever, use at your own risk, etc., etc., blah, blah, blah...)

     

    #Name: Wood Grain#Info: Vary the print temperature troughout the print to create wood rings with some printing material such as the LayWoo. The higher the temperature, the darker the print.#Depend: GCode#Type: postprocess#Param: minTemp(float:180) Mininmum print temperature (degree C)#Param: maxTemp(float:230) Maximun print temperature (degree C)#Param: grainSize(float:3.0) Average "wood grain" size (mm)#Param: firstTemp(float:0) Starting temperature (degree C, zero to disable)#Param: spikinessPower(float:1.0) Relative thickness of light bands (power, >1 to make dark bands sparser)#Param: maxUpward(float:0) Instant temperature increase limit, as required by some firmwares ©#Param: zOffset(float:0) Vertical shift of the variations, as shown at the end of the gcode file (mm)## Original Author: Jeremie Francois (jeremie.francois@gmail.com)# License: GNU Affero General Public License http://www.gnu.org/licenses/agpl.html# Heavily Modified by: Zhu Da Hai. Sept 2, 2016 (appologies to Jeremie)# Compatible with: Cura 2.3 Beta#from ..Script import Scriptimport reimport randomimport mathimport timeimport datetimeeol = "\n"import inspectimport sysimport getoptclass WoodGrain(Script):   def __init__(self):       super().__init__()   def getSettingDataString(self):       return {           "name":"Wood Grain",           "key": "WoodGrain",           "metadata":{},           "version": 2,           "settings":           {               "a_minTemp":                {                   "label": "Min Temp",                   "description": "Mininmum print temperature (degree C)",                   "unit": "c",                   "type": "float",                   "default_value": 180               },               "b_maxTemp":                {                   "label": "Max Temp",                   "description": "Maximun print temperature (degree C)",                   "unit": "c",                   "type": "float",                   "default_value": 230               },               "c_firstTemp":                {                   "label": "First Temp",                   "description": "Starting temperature (degree C, zero to disable)",                   "unit": "c",                   "type": "float",                   "default_value": 0               },               "d_grainSize":                {                   "label": "Grain Size",                   "description": "Average 'wood grain' size (mm)",                   "unit": "mm",                   "type": "float",                   "default_value": 3.0               },               "e_maxUpward":                {                   "label": "Max Upward",                   "description": "Instant temperature increase limit, as required by some firmwares ©",                   "unit": "c",                   "type": "float",                   "default_value": 0               },               "f_zOffset":                {                   "label": "Z-Offset",                   "description": "Vertical shift of the variations, as shown at the end of the gcode file (mm)",                   "unit": "mm",                   "type": "float",                   "default_value": 1.0               },               "g_randomSeed":                {                   "label": "Random Seed",                   "description": "Seed for Random Number Generator (0 = no seed - randomize)",                   "unit": "",                   "type": "int",                   "default_value": 0               },               "h_spikinessPower":                {                   "label": "Spikiness Power",                   "description": "Relative thickness of light bands (power, >1 to make dark bands sparser)",                   "unit": "",                   "type": "float",                   "default_value": 1.0               }           }       }   def getValue(self, line, key, default = None):       if not key in line or (';' in line and line.find(key) > line.find(';')):           return default       sub_part = line[line.find(key) + len(key):]       m = re.search('^[-+]?[0-9]+\.?[0-9]*', sub_part)       if m is None:           return default       try:           return float(m.group(0))       except:           return default   def getZ(self, line, default = None):       # new 20130727: now support G0 in addition to G1       if self.getValue(line, 'G') == 0 or self.getValue(line, 'G') == 1:           return self.getValue(line, 'Z', default)       else:           return default   try:       xrange   except NameError:       xrange = range   def perlinToNormalizedWood(self, z, zOffset, grainSize, spikinessPower, perlin):       banding = 3       octaves = 2       persistence = 0.7       noise = banding * perlin.fractal(octaves, persistence, 0,0, (z+zOffset)/(grainSize*2));       noise = (noise - math.floor(noise)) # normalized to [0,1]       noise= math.pow(noise, spikinessPower)       return noise   def noiseToTemp(self, noise, maxTemp, minTemp):       return minTemp + noise * (maxTemp - minTemp)   class Perlin:       # Perlin noise: http://mrl.nyu.edu/~perlin/noise/       def __init__(self, tiledim=256):           self.tiledim= tiledim           self.perm = [None]*2*tiledim           permutation = []           # xrange changed to range for Python3           for value in range(tiledim):               permutation.append(value)           random.shuffle(permutation)           # xrange changed to range for Python3           for i in range(tiledim):               self.perm[i] = permutation[i]               self.perm[tiledim+i] = self.perm[i]       def fade(self, t):           return t * t * t * (t * (t * 6 - 15) + 10)       def lerp(self, t, a, b):           return a + t * (b - a)       def grad(self, hash, x, y, z):           #CONVERT LO 4 BITS OF HASH CODE INTO 12 GRADIENT DIRECTIONS.           h = hash & 15           if h < 8: u = x           else:     u = y           if h < 4: v = y           else:               if h == 12 or h == 14: v = x               else:                  v = z           if h&1 == 0: first = u           else:        first = -u           if h&2 == 0: second = v           else:        second = -v           return first + second       def noise(self, x,y,z):           #FIND UNIT CUBE THAT CONTAINS POINT.           X = int(x)&(self.tiledim-1)           Y = int(y)&(self.tiledim-1)           Z = int(z)&(self.tiledim-1)           #FIND RELATIVE X,Y,Z OF POINT IN CUBE.           x -= int(x)           y -= int(y)           z -= int(z)           #COMPUTE FADE CURVES FOR EACH OF X,Y,Z.           u = self.fade(x)           v = self.fade(y)           w = self.fade(z)           #HASH COORDINATES OF THE 8 CUBE CORNERS           A = self.perm[X  ]+Y; AA = self.perm[A]+Z; AB = self.perm[A+1]+Z           B = self.perm[X+1]+Y; BA = self.perm[b]+Z; BB = self.perm[b+1]+Z           #AND ADD BLENDED RESULTS FROM 8 CORNERS OF CUBE           return self.lerp(w,self.lerp(v,                   self.lerp(u,self.grad(self.perm[AA  ],x  ,y  ,z  ), self.grad(self.perm[bA  ],x-1,y  ,z  )),                   self.lerp(u,self.grad(self.perm[AB  ],x  ,y-1,z  ), self.grad(self.perm[bB  ],x-1,y-1,z  ))),               self.lerp(v,                   self.lerp(u,self.grad(self.perm[AA+1],x  ,y  ,z-1), self.grad(self.perm[bA+1],x-1,y  ,z-1)),                   self.lerp(u,self.grad(self.perm[AB+1],x  ,y-1,z-1), self.grad(self.perm[bB+1],x-1,y-1,z-1))))       def fractal(self, octaves, persistence, x,y,z, frequency=1):           value = 0.0           amplitude = 1.0           totalAmplitude= 0.0           # xrange changed to range for Python3           for octave in range(octaves):               n= self.noise(x*frequency,y*frequency,z*frequency)               value += amplitude * n               totalAmplitude += amplitude               amplitude *= persistence               frequency *= 2           return value / totalAmplitude   def execute(self, data):       minTemp = float(self.getSettingValueByKey("a_minTemp"))       maxTemp = float(self.getSettingValueByKey("b_maxTemp"))       firstTemp = float(self.getSettingValueByKey("c_firstTemp"))       grainSize = float(self.getSettingValueByKey("d_grainSize"))       maxUpward = float(self.getSettingValueByKey("e_maxUpward"))       zOffset = float(self.getSettingValueByKey("f_zOffset"))       randomSeed = int(self.getSettingValueByKey("g_randomSeed"))       spikinessPower = float(self.getSettingValueByKey("h_spikinessPower"))       myStr = ""       if randomSeed != 0:           random.seed( randomSeed )       # new 20130727: limit the number of changes for helicoidal/Joris slicing method       minimumChangeZ=0.1       perlin = WoodGrain.Perlin()       # Generate normalized noises, and then temperatures (will be indexed by Z value)       noises = {}       # first value is hard encoded since some slicers do not write a Z0 at the first layer!       # TODO: keep only Z changes that are followed by real extrusion (ie. discard non-printing head movements!)       noises[0] = self.perlinToNormalizedWood(0, zOffset, grainSize, spikinessPower, perlin)       pendingNoise = None       formerZ = -1       for layer in data:           lines = layer.split("\n")           for line in lines:               thisZ = self.getZ(line, formerZ)               if thisZ > 2 + formerZ:                   formerZ = thisZ                   #noises = {} # some damn slicers include a big negative Z shift at the beginning, which impacts the min/max range               elif abs ( thisZ - formerZ ) > minimumChangeZ:                   formerZ = thisZ                   noises[thisZ] = self.perlinToNormalizedWood(thisZ, zOffset, grainSize, spikinessPower, perlin);       lastPatchZ = thisZ; # record when to stop patching M104, to leave the last one switch the temperature off       # normalize built noises       noisesMax = noises[max(noises, key = noises.get )]       noisesMin = noises[min(noises, key = noises.get )]       for z,v in noises.items():           noises[z]= (noises[z]-noisesMin)/(noisesMax-noisesMin)       #       # new 20130727: header and first (blocking) temperature change       #       warmingTempCommands="M230 S0" + eol # enable wait for temp on the first change       if firstTemp == 0:           warmingTempCommands+= ("M104 S%i" + eol) % self.noiseToTemp(0, maxTemp, minTemp)       else:           warmingTempCommands+= ("M104 S%i" + eol) % firstTemp       # The two following commands depends on the firmware:       warmingTempCommands+= "M230 S1" + eol # now disable wait for temp on the first change       warmingTempCommands+= "M116" + eol # wait for the temperature to reach the setting (M109 is obsolete)       # Prepare a transposed temperature graph for the end of the file       graphStr=";WoodGraph: Wood temperature graph (from "+str(minTemp)+"C to "+str(maxTemp)+"C, grain size "+str(grainSize)+"mm, z-offset "+str(zOffset)+")"       if maxUpward:           graphStr+=", temperature increases capped at "+str(maxUpward)       graphStr+=":"       graphStr+=eol       thisZ = -1       formerZ = -1       warned = 0       header = 1       savelayer = 0       postponedTempDelta=0 # only when maxUpward is used       postponedTempLast=None # only when maxUpward is used       skiplines=0       # Now Modify the gCode       for layer in data:           if header == 1:               index = data.index(layer)                layer = warmingTempCommands + layer               data[index] = layer #Override the data of this layer with the modified data               header = 0           lines = layer.split("\n")           for line in lines:               if "; set extruder " in line.lower(): # special fix for BFB                   index = data.index(layer)                    layer = layer.replace(line,line + "\n" + warmingTempCommands)                   warmingTempCommands=""                   savelayer = 1               elif skiplines > 0:                   skiplines= skiplines-1;               elif ";woodified" in line.lower():                   skiplines=4 # skip 4 more lines after our comment               elif not ";woodgraph" in line.lower(): # forget optional former temp graph lines in the file                   if thisZ == lastPatchZ:                       line = line # dummy                   elif not "m104" in line.lower(): # forget any previous temp in the file                       thisZ = self.getZ(line, formerZ)                       if thisZ != formerZ and thisZ in noises:                           if firstTemp != 0 and thisZ<=0.5: # if specifed, keep the first temp for the first 0.5mm                               temp= firstTemp                           else:                               temp= self.noiseToTemp(noises[thisZ], maxTemp, minTemp)                               # possibly cap temperature change upward                               temp += postponedTempDelta;                               #print("ppdelta=%f\n" % postponedTempDelta)                               postponedTempDelta = 0                               if postponedTempLast!= None and maxUpward > 0 and temp > postponedTempLast + maxUpward:                                   postponedTempDelta = temp - (postponedTempLast + maxUpward)                                   temp= postponedTempLast + maxUpward                               if temp > maxTemp:                                   postponedTempDelta= 0                                   temp= maxTemp                               postponedTempLast = temp                               index = data.index(layer)                                layer = layer.replace(line,line + "\n" + ("M104 S%i ; Wood Grain" + eol) % temp)                               savelayer = 1                           formerZ = thisZ                           # Build the corresponding graph line                           #t = int(19 * noises[thisZ])                           t= int(19 * (temp-minTemp)/(maxTemp-minTemp))                           myStr = ";WoodGraph: Z %03f " % thisZ                           myStr += "@%3iC | " % temp                           # xrange changed to range for Python3                           for i in range(0,t):                               myStr += "#"                           # xrange changed to range for Python3                           for i in range(t+1,20):                               myStr += "."                           graphStr += myStr + eol           if savelayer == 1:               data[index] = layer               savelayer = 0       index = data.index(layer)       layer = layer + graphStr + eol       data[index] = layer       return data    

     

    • Like 2
    Link to post
    Share on other sites
    Posted · Plugin for woodFill or similar filament

    Wood Grain Post Processing Plugin for Cura 2.3 (Beta) is done!

     

    Great, thanks!

    I just imported it an checked - everythings working :-)

  • Link to post
    Share on other sites
    Posted · Plugin for woodFill or similar filament

    Hey Guys, I'm using Cura 2.4 on a mac. I dropped the new code that DaHai8 made into my Cura plugins folder and nothing. I can't get this to work. Any suggestions?

  • Link to post
    Share on other sites
    Posted · Plugin for woodFill or similar filament

    It should go in the plugins/PostProcessingPlugin/scripts folder.

    Does it not show up in the Extensions -> Post Processing -> Modify G-Code window?

    I'm not very familiar with macs, do you need to mark it as 'executable' like on Unix systems?

  • Link to post
    Share on other sites
    Posted · Plugin for woodFill or similar filament

    sorry but i tried to follow your instructions but i probably miss something.

    Is it possible to have the .py file?

    Thank you

  • Link to post
    Share on other sites
    Posted · Plugin for woodFill or similar filament

    You can download it here: http://bitman.org/dahai/WoodGrain.zip

  • Link to post
    Share on other sites
    Posted · Plugin for woodFill or similar filament

    To use DaHai8's plugin on a mac:

    Step 1) Download the plugin here: http://bitman.org/dahai/WoodGrain.zip

    Step 2) Unzip and place WoodGrain.py in the following dir:

    /Applications/Cura.app/Contents/Resources/plugins/plugins/PostProcessingPlugin/scripts

    Step 3) Re-Launch Cura and select:

    Extensions --> Post Processing --> Modify G-Code

    Step 4) Click "Add a Script" --> "Wood Grain"

    Step 5) ¯\_(ツ)_/¯

    Step 6) Profit

  • Link to post
    Share on other sites
    Posted · Plugin for woodFill or similar filament

    Hello,

    I just tried to use this script and had some troubles.

    I am on Windows with Cura 2.6.2 and able to use the plugin, but only the 2 first layers (layer 0 and layer 1) are affected by the temp changes after postprocessing.

    After layer 1, all the rest of code stays at the same temp and I don't understand why.

    I tried different wood grain paramaters, but it is still the same.

    Thanks for helping

  • Link to post
    Share on other sites
    Posted · Plugin for woodFill or similar filament

    It's the +0.5 relative Z hop in the End Gcode of the printer profile that's confusing the script. It's written to only know about absolute moves.

    I'm working on a fix and should have it posted soon.

    Cheers

  • Link to post
    Share on other sites
    Posted · Plugin for woodFill or similar filament

    I have that bug fixed and its posted on my site for anyone to pick up

    Cura Post Processing Plugins

    I did uncover another bug that I haven't had time to fix: sometimes (usually on .2mm layer heights) it will not do any Wood Grain temperature mod on the last few layers, no will it save the Wood Grain Graph at the end of the gcode file.

    I'll try to get those fixed soon, but I don't consider them critical at the moment.

  • Link to post
    Share on other sites
    Posted · Plugin for woodFill or similar filament

    Hey @DaHai8! I have been using your WoodGrain.py for all my wood prints and its been awesome! Thank you so much! But now with Cura 3.6.0, I can't get it to work. Although it still works with Cura 3.5.1 just fine. 

     

    Do you know how I can edit the code to work with Cura 3.6.0? Or do you have an updated version you can share? Thanks again for your awesome contributions!

  • Link to post
    Share on other sites
    Posted · Plugin for woodFill or similar filament

    Any chance you could update this for Cura 4.4.1. I loaded it into the scripts folder but when I go into Cura to use it per the above instructions, it doesn't show in the list?

  • Link to post
    Share on other sites

    Create an account or sign in to comment

    You need to be a member in order to leave a comment

    Create an account

    Sign up for a new account in our community. It's easy!

    Register a new account

    Sign in

    Already have an account? Sign in here.

    Sign In Now
    ×
    ×
    • Create New...