Jump to content

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


Kin

Recommended Posts

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

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!

  • Link to post
    Share on other sites

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

    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! :)

  • Link to post
    Share on other sites

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

    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! :)

     

  • Link to post
    Share on other sites

    • 2 weeks later...
    Posted (edited) · How to make a plugin / anyone have a plugin to do X action every layer (like a nozzle wipe)?

    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
  • Link to post
    Share on other sites

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

    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

  • Link to post
    Share on other sites

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

    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.

    • Like 1
    Link to post
    Share on other sites

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

    You are a wonderful saint. Can't tell you how frustrated I was getting and confused! It's just that I feel I couldn't understand where I was going wrong, since this was such a simple protoscript.

    Now I look forward to really fleshing this out into a useful script. Thank you!

  • Link to post
    Share on other sites

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

    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.

  • Link to post
    Share on other sites

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

    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.

  • Link to post
    Share on other sites

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

    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

  • Link to post
    Share on other sites

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

    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
  • Link to post
    Share on other sites

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

    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

  • Link to post
    Share on other sites

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

    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
  • Link to post
    Share on other sites

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

    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.

  • Link to post
    Share on other sites

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

    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.

  • Link to post
    Share on other sites

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

    [] 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.

  • Link to post
    Share on other sites

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

    Hey,

    I'am new here. And I found this conversation because I want to do almost the same. I want to home the printhead every second layer. If you don't mind could you give me your code if you still have it, so I can modify it for my application? 
     

  • Link to post
    Share on other sites

    Posted · How to make a plugin / anyone have a plugin to do X action every layer (like a nozzle wipe)?
    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 :-) 

  • Link to post
    Share on other sites

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

    I'm working with the newest version of cura, but it seems to run the way it should be. Thanks a lot ? 

  • 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
    • Our picks

      • UltiMaker Cura 5.7 stable released
        Cura 5.7 is here and it brings a handy new workflow improvement when using Thingiverse and Cura together, as well as additional capabilities for Method series printers, and a powerful way of sharing print settings using new printer-agnostic project files! Read on to find out about all of these improvements and more. 
         
          • Like
        • 13 replies
      • S-Line Firmware 8.3.0 was released Nov. 20th on the "Latest" firmware branch.
        (Sorry, was out of office when this released)

        This update is for...
        All UltiMaker S series  
        New features
         
        Temperature status. During print preparation, the temperatures of the print cores and build plate will be shown on the display. This gives a better indication of the progress and remaining wait time. Save log files in paused state. It is now possible to save the printer's log files to USB if the currently active print job is paused. Previously, the Dump logs to USB option was only enabled if the printer was in idle state. Confirm print removal via Digital Factory. If the printer is connected to the Digital Factory, it is now possible to confirm the removal of a previous print job via the Digital Factory interface. This is useful in situations where the build plate is clear, but the operator forgot to select Confirm removal on the printer’s display. Visit this page for more information about this feature.
          • Like
        • 0 replies
    ×
    ×
    • Create New...