Jump to content
Ultimaker Community of 3D Printing Experts
Kin

How to make a plugin / anyone have a plugin to do X action every layer (like a nozzle wipe)?

Recommended Posts

Hi!

I have the code I would like to introduce between every layer change on my gcode. It's a nozzle wipe, ideally I'd have a way to do every "X" layers, or something like that, but every layer is great start.

My understanding is that Cura doesn't have this functionality built in to the user-side (Simplify3D does, and Slic3r does), but I would suspect it's a semi-common need.

Would anyone have such a plugin already, or something they'd recommend I try repurposing? I can write some python, though not an expert, but I don't really have any idea of the format for Cura plugins (especially as I think it is slightly different than 15.X plugins.)

Thanks for any help or pointers!

Share this post


Link to post
Share on other sites

I got my feet wet writing Cura Post-Processing Plugins (that's what you'd want) just by examining the code and hacking at it until I got what I wanted.

All the Post Processing Scripts for Cura 2.3 can be found here:

C:\Program Files\Cura 2.3\plugins\PostProcessingPlugin\scripts

There's not a lot to them:

A Class Name that matches the Filename that must contain:

 

  • def __init__(self): init the class
  • def getSettingDataString(self): Get the data from the dialogbox
  • def execute(self, data): do the modifications to the g-code pass in the data structure

 

The python file must also import Script at the top:

 

from ..Script import Script

 

That's the basics, its just wack and hack from there! :)

Share this post


Link to post
Share on other sites

Thanks DaHai8!

So it sounds like there isn't any particular functionality built into cura for the plugins, and I'll just be parsing the gcode with standard python string parsing tools?  I think I will take a closer look at the example scripts, thank you.

This should be doable I suppose, even if my solution isn't very robust.

Thanks!

 

I got my feet wet writing Cura Post-Processing Plugins (that's what you'd want) just by examining the code and hacking at it until I got what I wanted.

All the Post Processing Scripts for Cura 2.3 can be found here:

C:\Program Files\Cura 2.3\plugins\PostProcessingPlugin\scripts

There's not a lot to them:

A Class Name that matches the Filename that must contain:

 

  • def __init__(self): init the class
  • def getSettingDataString(self): Get the data from the dialogbox
  • def execute(self, data): do the modifications to the g-code pass in the data structure

 

The python file must also import Script at the top:

 

from ..Script import Script

 

That's the basics, its just wack and hack from there! :)

 

Share this post


Link to post
Share on other sites

So I started working on this again this weekend, and I do find the basics to be approachable.

I am a little confused about how to "load" the plugin. I thought if the .py was saved in the plugins folder, it would be automatically loaded. But I don't see my script when I open cura. I assume this means I have an error in my script format, but it seems at first few glances correct, although I am an amateur. Is there a better way to understand how to load a custom plugin, or where I might find an error log of that process?

Thanks!

Updates: So far I have found I can tweak an existing working script to change names and become a "new" script. This autoloads at start of cura. I can break the script with a while(1) loop, which then means the script will fail to load next time I open cura (i think.) So I am guessing this means I need to scour my code and see what I did wrong, assuming that it is not autoloading because of a functional error in the script. I'm not really a programmer so I find myself uncertain if I don't know the basics of how to do this right because I simply lack knowledge of convention, or if I am missing out the ways/location that these basics would be detailed (or if they are simply not yet clear for this plugin.)

Edited by Guest

Share this post


Link to post
Share on other sites

Heyo, six hours in, kind of certain my error is basic in nature. Not really sure what it is though. I can't get the script to show up, so I think I'm doing something wrong.

Been trying to do things like "work from base case up" in terms of modifying the existing scripts into doing what I want them to. But besides the fact cura takes 20ish seconds to startup on my computer, it's been slow and not very effective to understand what kind of issue I am seeing. I'm hoping it won't take too long for someone to be able to redirect me where I'm probably doing something obviously wrong.

Sidenote: I noticed when I edit in notepad++ the formatting isn't clean if I open in IDLE. So I go back to IDLE to format indents nicely, but I presumably notepad++ should handle things correctly when in the python setting.

I made a copy of my code here:

https://dl.dropboxusercontent.com/u/3619097/Wipe_ScriptZZZ.py

Share this post


Link to post
Share on other sites

Kin,

It's all about the indents. If they are not perfectly aligned, the Python interpreter just tosses out the whole script.

The last return at the bottom of you script is not perfectly indented from the def execute subroutine declaration.

If NotePad++ has an 'Untabify' function (remove tabs and replace with spaces), use that after aligning the 'return' statement.

Otherwise, might I suggest Kimodo Edit from ActiveState. It's free, easy to use and understands a lot of different programming languages (including Python). And it has a function under the 'Code' menu to 'Untabify Region' (which basically removed tabs and replaces with spaces all the lines you have highlighted).

That's is how I got you script to load and show up in Cura.

Hope this helps.

Share this post


Link to post
Share on other sites

So Dahai, thanks again for all this help. I definitively have been struggling with this more than most simple projects, probably because I can't see my errors well. I think in your last post, you made me realize python is supposed to use four spaces over the tab notation, per some convention. I used the "untabify" type function of notepad++ to do that, and ran through everything. "return data" is supposed to be four spaces indented of "def execute" I think... Am I missing something there? When I do that, I still can't get cura 2.3 to load the script. UPDATE: Just kidding! Untabify, looking more closely, made me realize all my interior statements were totally off on their tabbing. Not sure how I messed that up so bad. Now off to actually implement my script.

By the way,

I actually did install Komodo edit, but I can't seem to troubleshoot why it won't work on my windows 10 PC. It seems to simply not load! Kind of unusual, so I uninstalled and will try to install again.

Share this post


Link to post
Share on other sites

Heh, I am still chugging away at this. In the past I would have used raw file manipulation, but here I'm trying to build this properly as a cura gcode script. That said, I am thinking I basically don't know where the functionality is even coming from. Like, I am not clear on how cura expects the data input to be formatted (I can mimic the basic integer and yes/no inputs from example scripts) and even am not 100% certain how functionality like "for layer in data" work. I mean, I get that I am cutting the dataset apart(by layers? using the "LAYER:" key I assume?), but I don't even know what structure the gcode data is coming in as, or where I would find that out. Then something like "lines =layer.split("\n") cuts presumably the layer into strings by line. But I am guessing that from structure and it's taken ages to piece it together.

I finally think I understand why the basic structure of my previous code didn't work -namely that it acted to look for a "LAYER:" key, but was utilizing the "layer in data" structure that seems to abstracted away the need to look for where the layers start and end. Is that remotely correct that it does that? My impression is that in "for layer in data" I am cutting my data of a set of large text into sets of largish texts of the lines exluding the "LAYER:#" line. Then "line = layer.split(\n") further cuts it into an array of strings that are each carriage return line of the set of layer gcode.

So to take a step back - I am a mech-E with decent C++ microcontroller experience and random small python scripting stuff (pandas manipulation, CVS generation, array manipulation & image types). I don't have a lot of experience but I am used to normally scouring documentation to figure out how structures and things work. Here, it seems I'm grasping at straws while slowly testing small changes to my script by loading them into cura and slicing code, but finding that my script doesn't cause the effects I want. In any case, surely, there must be a better way to develop my script and see what is going on in the script. Unless, this kind of scripting is meant for more familiar programmers who might not make errors in the drafting of such a script, and are used to the basic conventions without need for commenting explaining how the code works.

very very grateful! Thanks again for all the insight so far. And I promise I'm trying to use four spaces not tabs now.

Share this post


Link to post
Share on other sites

You're on the right track.

 

 for layer in data: 

 

The data part is the whole gCode of your object.

The layer part splits that gCode into sections by Layer:

 

;LAYER:0M117 0/124L, 0.00%.M107M204 S500M205 X5G1 F2100 E-2G0 F1200 X70.225 Y72.273 Z.2;TYPE:SKIRTG1 F2100 E0G1 F1200 X70.676 Y71.863 E0.02027G1 X71.099 Y71.533 E0.03812G1 X71.619 Y71.245 E0.05789G1 X72.045 Y71.053 E0.07343G1 X72.549 Y70.87 E0.09126G1 X73.241 Y70.746 E0.11464...

 

Then:

 

lines = layer.split("\n")for line in lines:

 

Splits the Layer data into lines terminated with the '\n' into an array called 'lines'. Then the 'for' loop just loops over each line in the 'lines' array.

Since you're only wanting to make changes at the start of every layer, you may not need the 'for lines in lines' loop at all.

Just:

 

index = data.index(layer)layer = wipe_gcode + layerdata[index] = layer

 

on each 'layer' of the data. And you should not need the 'break' either, since you're only working at the layer resolution.

Hope this helps

Share this post


Link to post
Share on other sites

P.S.

On Python, the indentations size does not really matter, as long as its consistent (2, 4, or 8 spaces:

 

.def (new function) commands in def loop     commands within loop commands outside of loop return def (new function) ...

 

Python is a position oriented language on its functions/subroutines and code blocks - unlike C or Perl that used braces { }.

So a code block is an indent, a sub-block is an additional indent. To end a code block you place your commands a the previous indent.

Edited by Guest

Share this post


Link to post
Share on other sites

So I think I finally have a reasonable proto that works-almost. It seems reliably to add the gcode and resume the z height, for every layer except layers 0 and 1. In those two layers the gcode first line is not "G1" or "G0" but rather M106 or M106 and M107. I can't confirm that is the problem but it's the only thing I can notice different about those layers (and that my code doesn't start in earnest until after it passes "layer:0".) In my code I believe I should be resolving those cases of M106 and M107 by iterating linewise until I find a G1 or G0 command, but the behavior doesn't seem to follow that.

Don't know if you have interest or time to take a look at what I've written, but I've been pouring up and down it for ages trying to execute the code as I think it should be executing, but it doesn't.

https://dl.dropboxusercontent.com/u/3619097/Wipe_ScriptZZZ.py

Share this post


Link to post
Share on other sites

Sometimes the hardest bugs to find are the ones that Are NOT in your code! :O

Your script was not failing if the Layer didn't start with a G code, it was failing if the Z height was less that 1.

 

;LAYER:1M106 S255M204 S750M205 X10G0 F5100 X116.836 Y75.235 Z.4

 

The getValue def from the Script.py file in Cura assumes all numerical values have at least one digit to the left of the decimal point:

 

m = re.search('^[0-9]+\.?[0-9]*', sub_part)

 

Clearly this is not the case for layers less than 1mm ...

My solution was to replace their getValue def with my own:

 

#   def getMyValue(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

 

It also handles keys with lengths greater than 1.

You will also need to add this to the second line of the script:

 

import re

 

That should get you going again - it worked for me.

Cheers.

Edited by Guest

Share this post


Link to post
Share on other sites

Seriously, you're amazing. I am grinning, as this has been a lonnnggg struggle for some simple functionality, but totally worth it (selective memory?)

I admit I looked into getValue to even confirm how it was working, but balked at trying to understand the .search functionality (embarrassed to say.) I will now make sure I fully understand that code and your version.

Share this post


Link to post
Share on other sites

Thanks again for finding the issue I had!

I imagine the criteria in .search are more familiar for traditional programmers. It sounds like regular expressions section of the python documentation I need to get more background on. I had quite a hard time understanding how to parse the difference between your search criteria and the default criteria. That is, between

re.search('^[0-9]+\.?[0-9]*', sub_part) and

re.search('^[-+]?[0-9]*\.?[0-9]*', sub_part)

In fact, at first I was sure you had a typo with * vs +, but I understand a bit better that the characters are shorthand for a number of categories and parameters. I wish I had an easier time parsing what is being said, so I'll make sure to checkup again the documentation until I do get it. For sure, yours did work, and I was also able to cut the extraneous couple checks I implemented while chasing the incorrectly perceived cause of my script bug.

Share this post


Link to post
Share on other sites

[] are sets

? is match 0 or 1 times

* is match 0 or more times

Basically, its says:

Start at the beginning of the string (sub_part) : ^

Look for 0 or 1 '-' or '+' signs: [-+]?

Look for 0 or more digits: [0-9]*

Look for 0 or 1 decimal point: \.?

Look for 0 or more digits: [0-9]*

If it finds a match to that pattern, that sub-string is returned.

P.S. decimal points (periods) are escaped (\.) as they can have special meanings in Regular Expressions. And I added the signs (-+) for relative moves, which can be negative - even through that's hardly ever used.

Share this post


Link to post
Share on other sites
from ..Script import Script
import re
class share_Wipe(Script):
    version = "3.2"
    def __init__(self):
        super().__init__()

    def getSettingDataString(self):
        return """{
            "name":"share Wipe """ + self.version + """",
            "key":"share_Wipe",
            "metadata": {},
            "version": 2,
            "settings":
            {
                "machine_type":
                {
                    "label": "Machine Type",
                    "description": "Select which machine type you are using. CRITICAL!",
                    "type": "enum",
                    "options": {
                    "printer0":"printer0 Type",
                    "printer1":"printer1 Type",
                    "printer2":"printer2 W/Varipower Bed",
                    "printer3":"printer3 W/Original Bed",
                    "printer4":"printer4 NOT IMPLEMENTED"
                    },
                    "default_value": "printer2"
                },
                "wipe_layer":
                {
                    "label": "Wipe Frequency",
                    "description": "Wipe every N layers",
                    "unit": "Layers",
                    "type": "int",
                    "default_value": 1
                },
                "wipe_volume":
                {
                    "label": "Wipe Volume",
                    "description": "Wipe every N layers and volume - not implemented",
                    "unit": "mm",
                    "type": "float",
                    "default_value": 0
                },
                "Xcenter":
                {
                    "label": "Brush X Center",
                    "description": "center X point of brush movement",
                    "unit": "mm",
                    "type": "float",
                    "default_value": 99,
                    "value": "20 if machine_type == 'printer0' else (100 if machine_type == 'printer2' else (100 if machine_type == 'printer3' else (20 if machine_type == 'printer1' else 0)))"
                },
                "Ycenter":
                {
                    "label": "Brush Y Center",
                    "description": "center Y point of the brush movement",
                    "unit": "mm",
                    "type": "float",
                    "default_value": 99,
                    "value": "80 if machine_type == 'printer0' else (272 if machine_type == 'printer2' else (272 if machine_type == 'printer3' else (80 if machine_type == 'printer1' else 0)))"
                },
				"Yapproach":
                {
                    "label": "Y Approach Offset",
                    "description": "how far away from brush Ycenter to start moving Z if below Zbrush",
                    "unit": "mm",
                    "type": "float",
                    "default_value": 99,
                    "value": "0 if machine_type == 'printer0' else (40 if machine_type == 'printer2' else (40 if machine_type == 'printer3' else (0 if machine_type == 'printer1' else 0)))"
                },
                "Xmove":
                {
                    "label": "X move",
                    "description": "How far from Xcenter that the brush script moves",
                    "unit": "mm",
                    "type": "float",
                    "default_value": 99,
                    "value": "5 if machine_type == 'printer0' else (30 if machine_type == 'printer2' else (30 if machine_type == 'printer3' else (5 if machine_type == 'printer1' else 0)))"
                },
                "Ymove":
                {
                    "label": "Y move",
                    "description": "How far from Ycenter that the brush script moves",
                    "unit": "mm",
                    "type": "float",
                    "default_value": 99,
                    "value": "40 if machine_type == 'printer0' else (5 if machine_type == 'printer2' else (5 if machine_type == 'printer3' else (40 if machine_type == 'printer1' else 0)))"
                },
                "travelSpeed":
                {
                    "label": "Travel Speed",
                    "description": "Travel speed to use during the brush script",
                    "unit": "mm/min",
                    "type": "float",
                    "default_value": 99,
                    "value": "2900 if machine_type == 'printer0' else (4800 if machine_type == 'printer2' else (4800 if machine_type == 'printer3' else (5200 if machine_type == 'printer1' else 2500)))"
                },
                "Z_brushHeight":
                {
                    "label": "brush movement Z height",
                    "description": "If brush movement has a Z height component, otherwise ignore",
                    "unit": "mm",
                    "type": "float",
                    "default_value": 9999,
                    "value": "5 if machine_type == 'printer2' else (27 if machine_type == 'printer3' else 90)",
                    "enabled": "True if machine_type == 'printer2' else (True if machine_type == 'printer3' else False)"
                }
            }
        }"""
#getMyValue is like the script default getValue, but can find <1 values. 
    def getMyValue(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 getMyCommentVal(self, line, key, default = None): #less safe version that doesn't ignore comments.
        if not key in line:
            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 buildGCode(self,Xcenter_local,Ycenter_local,Yapproach_local,Z_truth_local,X_resume,Y_resume,Z_resume,Z_brushHeight_local,Xwidth_local,Ywidth_local,travelSpeed_local,resumeSpeed_local,brush_type_local):
        #Z_brusHeight default 27 for printer3
        wipeGcode = "; TEST WIPE:" + str(wipe_count) + " \n" #TODO remove this
        wipeGcode += "G1 F" + str(travelSpeed_local) + "\n"
        if Z_truth_local == 1: #Z_truth = 1 is also for Taz-type brushes. How one "approaches" the brush is very specific for taz-type brushes (to avoid collisions)
            if (Z_resume<(Z_brushHeight_local+5)):
                wipeGcode+="G1 Y" + str((Ycenter_local-Yapproach_local)) + ";move close to wiper but not too close when below brushheight \n"
                wipeGcode+="G1 Z" + str(Z_brushHeight_local+5) +"; Move to above the wipe \n"
                wipeGcode+="G1 Y" + str(Ycenter_local) + " X" + str(Xcenter_local) +";get back to the brush center \n"
            else:
                wipeGcode += "G1 Y" + str(Ycenter_local)  + ";Move close to wiper without changing Z height \n"
                wipeGcode += "G1 Z" + str(Z_brushHeight_local+5) + "; Move to above the wiper \n"
                wipeGcode += "G1 X" + str(Xcenter_local) + " Y" + str(Ycenter_local) + "\n"
            wipeGcode += "G1 Z" + str(Z_brushHeight_local) + ";move down to the Z_brush height \n"
        else:
            wipeGcode += "G1 X" + str(Xcenter_local) + " Y" + str(Ycenter_local) + ";Move to center of brush w/o changing Z \n"
        wipeGcode += "G1 X" + str(Xcenter_local+Xwidth_local) + " Y" + str(Ycenter_local+Ywidth_local) + "; move to positive max of brush \n"
        wipeGcode += "G1 X" + str(Xcenter_local-Xwidth_local) + " Y" + str(Ycenter_local-Ywidth_local) + "; move to negative max of brush \n"
        if Z_truth_local == 1:
            wipeGcode+= "G1 Z" + str(Z_brushHeight_local+5) + "; move away from brush before return movement \n"
            wipeGcode+= "G1 Y" + str(Ycenter_local-30) + "\n"
            wipeGcode+= "G1 Z" + str(Z_resume) + "\n"
        wipeGcode+= "G1 X" +str(X_resume) + " Y" + str(Y_resume) + "\n"
        if resumeSpeed_local != 3030303030:  #don't add a speed value if you don't have an old speed value
            wipeGcode+="G1 F" + str(resumeSpeed_local) + "; resumeSpeed based on last XY speed of previous layer \n"
        else:
            wipeGcode+="G1 F" + str(1807) + "; resume speed based on default exception \n"
        return wipeGcode
                               
    def execute(self, data):
        #initialize values for different brushes
        Xcenter = self.getSettingValueByKey("Xcenter")
        Ycenter = self.getSettingValueByKey("Ycenter")
        Xwidth = self.getSettingValueByKey("Xmove") #note Xmove is the JSON move for width. Width is the name in code but poor choice due to the fact it really represents 1/2 of the width of the movement and isn't very clear.
        Ywidth = self.getSettingValueByKey("Ymove")
        travelSpeed = self.getSettingValueByKey("travelSpeed")
        Z_brushHeight = self.getSettingValueByKey("Z_brushHeight")
        Yapproach = self.getSettingValueByKey("Yapproach")
        brush_type = 0
        Z_truth = 0 #default assumption that there's no Z movement [safest]
        wipeEveryN = self.getSettingValueByKey("wipe_layer")
        wipeEveryLayerCounter = 0

        if self.getSettingValueByKey("machine_type") == "printer0":
            Z_truth = 0
        if self.getSettingValueByKey("machine_type") == "printer2":
            Z_truth = 1
        if self.getSettingValueByKey("machine_type") == "printer3":
            Z_truth = 1
        if self.getSettingValueByKey("machine_type") == "printer4":
            Z_truth = 0

        layers_started = False
        last_layer = False
        last_speed = 3030303030 #wipe script is added at the start of indicated layers. So throughout the previous layer, I should look for Fcommands, and keep the most recent one.
        resume_speed = 3030303030
        extruded_count = 0 #  TODO - volumetric wipe functionality
        extruding_type = None # absolute vs relative, lets say absolute = 0, relative = 1  TODO
        global wipe_count
        wipe_count = 0
        total_layers = -555555555555#wary as last_layer counting system relies on wipe_count and I don't want cura to break the script by devising "negative layers" or something like that
        #skip layers = self.getSettingValueByKey("every_layer") #future functionality
        for layer in data: #data is already split by layers as given by cura (see tweakZ for same format)
            lines = layer.splitlines() #'Ive used both layer.split("\n") and this, but find same behavior. I believe all gcode from cura should split on \n, but a stackexchange answer said it can be platform dependant as a general convention so I chose to use splitlines to circumvent that. 
            first_Z = None
            first_X = None
            first_Y = None
            wipe_gcode = None #reset the wipe gcode per layer
            for line in lines:
                if ";LAYER_COUNT:" in line:
                    total_layers = self.getMyCommentVal(line,';LAYER_COUNT:')
                    total_layers = int(round(total_layers,0))
                if ";LAYER:1" in line: #should run once when passing LAYER:N
                    layers_started = True
                    #wipe_gcode = "Total Layer No. = " + str(total_layers) + "\n" #TODO REMOVE THIS DEBUG
                if not layers_started: #should run once after passed LAYER:N
                    continue
                if (";LAYER:"+str(total_layers-1)) in line:
                    last_layer = True
                #Next, searching for the first Z, first X, first Y, and last F. 
                if self.getMyValue(line,'G') == 1 or self.getMyValue(line,'G') == 0:
                    if first_Z == None: #only act if we haven't found Z yet in this layer
                        first_Z = self.getMyValue(line,'Z') #should return None if doesn't find anything in this line
                    if first_X == None: #TODO consider case of X1 command then a Y1 command later (I.e, first X command isn't in same line as first Y command)
                        first_X = self.getMyValue(line,'X') #should return None if doesn't find anything
                    if first_Y == None:
                        first_Y = self.getMyValue(line,'Y') #should return None if doesn't find anything
                    if self.getMyValue(line,'F') != None:
                        if (self.getMyValue(line,'X') != None) or (self.getMyValue(line,'Y')!=None): #only count F commands that include some XY motion to ignore slow extruder or Z speeds. TODO: Consider that this 'safety' should not be required, because even if you set the speed to that slower speed, if it truly was the last F command in a layer, the next layer should fix itself (or else it would fail without the wipe code)
                            last_speed = self.getMyValue(line,'F')
                            
            if not layers_started: #should run once after passed LAYER:N
                continue
            
            wipeEveryLayerCounter+=1 #this counter should reset each time you build and store a wipe code
            wipe_count += 1 #this is a more general count of what potential wipe layer you're on, but mostly defunct code (cleanup?)
            if wipeEveryLayerCounter<wipeEveryN: #e.g, wipe every 2 layers. counter =1, N=2. Second layer should wipe and reset counter to 0.  
                if last_speed != None:  #See this same line later, after buildGcode. Even if you skip buildGcode you still want to track your previous layer speed, for the next buildGcode to have a speed ready.  
                    resume_speed = last_speed  
                if last_layer == True:  #There are two last layer checks, they could probably be consolidated to (1) in front of both of them, that checks for N-1 of the current check [checking within this loop was chosen as an extension of the original test before wiping every N layers was implemented [original test was after the last gcode one adds rather than before building]]
                    return data 
                continue #skips the wipe gcode generation and inclusion (below)
            else:
                wipeEveryLayerCounter=0 #resets the wipe skipping counter. Assume default wipeEveryN = 1, should turn 0 here, then turn to 1 again by the +=1 val. 

            wipe_gcode = self.buildGCode(Xcenter,Ycenter,Yapproach,Z_truth,first_X,first_Y,first_Z,Z_brushHeight,Xwidth,Ywidth,travelSpeed,resume_speed,brush_type)#travelSpeed,resumeSpeed,brush_type)
            if last_speed != None:
                resume_speed = last_speed   
            #add the wipe code adding -format pulled from BQ Pause@height code
            index = data.index(layer) #find our current layer
            layer = wipe_gcode + layer #add wipe code then (is there order of operations to string addition? Presumably)
            data[index] = layer
            if last_layer == True:
                return data
        return data

I don't know if this will be useful for you, I have not used it in over six months. But last I used it I believe it was working :-) 

Share this post


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

Announcements

  • Our picks

    • Architect Contest | Entourage 01.
      Presenting an idea, an architectural design or something as big as an urban project isn't easy. A scaled models can really help to get your idea across. But besides your main model, there is more needed to get your idea across vividly! An architect regularly uses various entourage sets to help bring these ideas and models to live. 
        • Like
      • 9 replies
    • What The DfAM?
      I'm Steve Cox, an experienced engineer familiar with 3D printing. I wanted to share some DfAM guidelines with this community to help and make stronger parts.
      I'm also an Autodesk Certified Instructor for Fusion 360, so many of the images in ...
        • Thanks
        • Like
      • 17 replies
×

Important Information

Welcome to the Ultimaker Community of 3D printing experts. Visit the following links to read more about our Terms of Use or our Privacy Policy. Thank you!