Since I can't do any printing at the moment (printer is broken), I have put together a bit of a tutorial on "best practice" when creating conditional G code.
There will be many far more qualified people than I on the forum to do this, and I'm sure that there is better and more concise documentation coming, but some may find it useful.
It is far from a complete or even comprehensive treatise on the subject, nor is it meant to be.
Feel free to correct, disagree or ignore as you see fit.
Especially since the code therein is untested (printer broken remember)
Conditional G Code best practice.pdf
Best posts made by OwenD
-
Conditional G Code introduction "tutorial" (PDF)
-
RE: Baby Stepping.. can it, or can it not be permanent?
just run a macro. (assuming you're running RRF3)
You could either do it manually or put it in the stop gcode of your slicer;save_babystep.g ; Add babystep to Z offset and make "persistant" if move.axes[2].babystep !=0 echo {"Z trigger height altered by " ^ move.axes[2].babystep ^ "mm"} echo {"OLD: " ^ sensors.probes[0].triggerHeight ^ " new: " ^ sensors.probes[0].triggerHeight - move.axes[2].babystep} G31 Z{sensors.probes[0].triggerHeight - move.axes[2].babystep} M500 P10:31 ; save settings to config-overide.g - Must have M501 in config.g else echo "No babystepping set. Nothing to save"
EDIT: Original post incorrectly added baby steps to trigger height. It should subtract.
-
Pressure Advance Tuning file generator
Hi All,
In my quest to tune my various settings I decided I needed to be able to automatically generate a test file that would change the pressure advance at set intervals.
The only programming language I know is Delphi/Lazarus, so I created a windows application to carry out the task.Essentially that application creates a square box with two wall perimeters.
The inner wall prints at a constant speed and acts merely for support.
The outer wall alternates between a high and low speed with the transition point being mid way along the wall.
At the end of each layer a retraction is added.This allows you to see how the PA adjustments are affecting corners at the two speeds and how it is affecting the retraction settings.
A start and end G code section takes care of temps etc.
Here are some pics of tests starting a PA of zero and increasing by 0.05 every 5mm in height.
My printer runs about a 350mm bowden tube.Pic 1: PA range 0-0.35
Note the distinct thickness variations at the speed change point and the blobbing at the retraction/layer change point.
Pic2: PA Range 0.4-0.75
Transition line still faintly visible and some issues around retraction area
Pic3: PA range 0.8-1.15
Looks pretty good at about mid way
Pic4: PA range 1.2-1.55
Here we can see that pressure advance is starting to cause too much retraction.
I'd either have to reduce PA or try again with less retraction.
If anyone would like to try the application and provide feedback , I have put a link to my dropbox.
Download link:
https://www.dropbox.com/s/nolo5aca26e7fni/PaAdvanceTestInstall_v1-0.zip?dl=0BE WARNED!!
This should be considered very much beta software.
Use at your own risk.
I've only tested on Windows 10.I'm not sure my extrusion volume calculations are 100% accurate.
They seem close, but not identical to Cura's values.
At any rate it seems good enough for the test purposes. -
Macro for "automatic" calibration of BL Touch
This macro uses variables so needs RRF3.3b2 or later.
EDIT: Object model values for M558 are now stored in mm/min so no conversion necessary. - please used amended macro below which includes improvements and corrects some errors pointed out in this thread by other users.
It prompts you to jog the nozzle to the bed and then runs 10 probe offset tests (this number is configurable as a variable)
At the end, it discards the highest and lowest reading and averages the rest.
You can then choose to save the result in config-overide.g;Calibrate BL Touch ; Reprap firmware version 3.3b2 or later required! ; if two speed probing is configured in M558,we probably want to reduce the speed for this test var ProbeSpeedHigh = sensors.probes[0].speeds[0]*60 ; Speeds are saved in mm/sec in the object model but M558 uses mm/min var ProbeSpeedLow = sensors.probes[0].speeds[1]*60 M558 F60 ; reduce probe speed to 60mm/min for accuracy - adjust F parameter as required ;define some variables to store readings var NumTests=10 ; modify this value to define number of tests ; Do not change below this line var RunningTotal=0 var Average=0 var Lowest=0 var Highest=0 ; If the printer hasn't been homed, home it if !move.axes[0].homed || !move.axes[1].homed || !move.axes[2].homed G28 else G1 Z{sensors.probes[0].diveHeight} F360 ; if axes homed move to dive height M561 ; clear any bed transform M290 R0 S0 ; clear babystepping ; move nozzle to centre of bed G1 X{(move.axes[0].min + move.axes[0].max)/2} Y{(move.axes[1].min + move.axes[1].max)/2} M564 S0 H0 ; Allow movement beyond limits ;ensure you have room for the probe if move.axes[2].machinePosition < sensors.probes[0].diveHeight G1 Z{sensors.probes[0].diveHeight} M280 P0 S160 I1 ; reset BL Touch G4 S0.5 M98 P"0:/sys/retractprobe.g" ; Ensure probe is retracted & reset G4 S0.5 M561 ; clear any bed transform ; Jog head to position M291 P"Jog nozzle to touch bed" R"Set nozzle to zero" S3 Z1 G92 Z0 ; set Z position to zero M291 P"Press OK to begin" R"Ready?" S3; ; Move probe over top of same point that nozzle was when zero was set G1 Z{sensors.probes[0].diveHeight}; lift head G1 X{move.axes[0].machinePosition - sensors.probes[0].offsets[0]} Y{move.axes[1].machinePosition - sensors.probes[0].offsets[1]} F1800 echo "Current probe offset = " ^ sensors.probes[0].triggerHeight ^ "mm" ; carry out 10 probes (or what is set in NumTests variable) while iterations < var.NumTests G1 Z{sensors.probes[0].diveHeight} ; move to dive height if sensors.probes[0].value[0]=1000 ; if probe is in error state echo "Probe in error state- resetting" M280 P0 S160 I1 ; reset BL Touch G4 S0.5 M98 P"0:/sys/retractprobe.g" ; Ensure probe is retracted & reset G4 S0.5 G30 S-1 M118 P2 S{"Test # " ^ (iterations+1) ^ " Triggered @ " ^ move.axes[2].machinePosition ^ "mm"} ; send trigger height to Paneldue console M118 P3 S{"Test # " ^ (iterations+1) ^ " Triggered @ " ^ move.axes[2].machinePosition ^ "mm"} ; send trigger height to DWC console if iterations == 0 set var.Lowest={move.axes[2].machinePosition} ; set the new lowest reading to first probe height set var.Highest={move.axes[2].machinePosition} ; set the new highest reading to first probe height if move.axes[2].machinePosition < var.Lowest set var.Lowest={move.axes[2].machinePosition} ; set the new lowest reading ;M118 P3 S{"new low reading = " ^ move.axes[2].machinePosition} ; send trigger height to DWC console G4 S0.3 if move.axes[2].machinePosition > var.Highest set var.Highest={move.axes[2].machinePosition} ; set the new highest reading ;M118 P3 S{"new high reading = " ^ move.axes[2].machinePosition} ; send trigger height to DWC console G4 S0.3 set var.RunningTotal={var.RunningTotal + move.axes[2].machinePosition} ; set new running total ;M118 P3 S{"running total = " ^ var.RunningTotal} ; send running total to DWC console G4 S0.5 set var.Average = {(var.RunningTotal - var.Highest - var.Lowest) / (var.NumTests - 2)} ; calculate the average after discarding th ehigh & low reading ;M118 P3 S{"running total = " ^ var.RunningTotal} ; send running total to DWC console ;M118 P3 S{"low reading = " ^ var.Lowest} ; send low reading to DWC console ;M118 P3 S{"high reading = " ^ var.Highest} ; send high reading to DWC console M118 P2 S{"Average excluding high and low reading = " ^ var.Average} ; send average to PanelDue console M118 P3 S{"Average excluding high and low reading = " ^ var.Average} ; send average to DWC console G31 P500 Z{var.Average} ; set Z probe offset to the average reading M564 S0 H1 ; Reset limits M558 F{var.ProbeSpeedHigh}:{var.ProbeSpeedLow} ; reset probe speed to original G1 Z{sensors.probes[0].diveHeight} F360 ; move head back to dive height M291 P{"Trigger height set to : " ^ sensors.probes[0].triggerHeight ^ " OK to save to config-overide.g, cancel to use until next restart"} R"Finished" S3 M500 P31 ; optionally save result to config-overide.g
-
RE: oibject model "status" dcos.
@Tinchus
Political correctness has spread even to RRF.
In the spirit of inclusiveness , you must now use "processing" in deference to those machines that do not identify as "printers" -
Setting up video streaming using raspberry pi
Recently I had to set up a Raspberry Pi is camera to use as a monitoring device for my printer.
I found that pretty much all the online information was outdated and had no end trouble getting it to work on the current version of Pi OS (BullseyeBookworm).
The entire camera and video handling system has changed over the years.
So I decided to document what worked for me.
In the end it was relatively easy (once you knew the correct steps0I must first point out I know bugger all about Linux.
All this work is taken from bits and pieces I found on various sites and cobbled together, so my apologies for not crediting the original author(s)NOTE: These instructions were done using a Raspberry Pi 4 running
BullseyeBookworm 64 bit and an official Pi camera
Also if you're running RRF in SBC mode, you don't need to do this as I believe it has a built in camera setup.The first step is to install the OS using the Pi Imager
https://www.raspberrypi.com/software/When prompted, choose your user name, password and Wifi details.
NOTE: The current version no longer uses the default user pi and password raspberrywhen you get to this point, click on "edit settings"
Enable SSH so that you can connect to the Pi via PUTTY rather than always needing a monitor and keyboard.
If you installed a Pi OS that has a desktop you can use the inbuilt command line terminal for all the steps listed below.Then click on YES to apply the settings.
Once the image has been loaded onto the SD card, insert the card in your Pi and start it up.
Start Putty (or some other terminal) and SSH into the Pi
If you used the default settings, you should be able to go to
raspberrypi.local
You should see something like this
You may get any updates/upgrade by using these commands
sudo apt update
sudo apt upgrade
sudo apt install
Change directory
cd /usr/local/bin/
Open a text edior
sudo nano streamVideo.py
Paste in the following code
#!/usr/bin/python3 # This is the same as mjpeg_server.py, but uses the h/w MJPEG encoder. import io import logging import socketserver from http import server from threading import Condition from picamera2 import Picamera2 from picamera2.encoders import MJPEGEncoder from picamera2.outputs import FileOutput PAGE = """\ <html> <head> <title>3D Printer Camera</title> </head> <body> <img src="stream.mjpg" width="800" height="600" /> </body> </html> """ class StreamingOutput(io.BufferedIOBase): def __init__(self): self.frame = None self.condition = Condition() def write(self, buf): with self.condition: self.frame = buf self.condition.notify_all() class StreamingHandler(server.BaseHTTPRequestHandler): def do_GET(self): if self.path == '/': self.send_response(301) self.send_header('Location', '/index.html') self.end_headers() elif self.path == '/index.html': content = PAGE.encode('utf-8') self.send_response(200) self.send_header('Content-Type', 'text/html') self.send_header('Content-Length', len(content)) self.end_headers() self.wfile.write(content) elif self.path == '/stream.mjpg': self.send_response(200) self.send_header('Age', 0) self.send_header('Cache-Control', 'no-cache, private') self.send_header('Pragma', 'no-cache') self.send_header('Content-Type', 'multipart/x-mixed-replace; boundary=FRAME') self.end_headers() try: while True: with output.condition: output.condition.wait() frame = output.frame self.wfile.write(b'--FRAME\r\n') self.send_header('Content-Type', 'image/jpeg') self.send_header('Content-Length', len(frame)) self.end_headers() self.wfile.write(frame) self.wfile.write(b'\r\n') except Exception as e: logging.warning( 'Removed streaming client %s: %s', self.client_address, str(e)) else: self.send_error(404) self.end_headers() class StreamingServer(socketserver.ThreadingMixIn, server.HTTPServer): allow_reuse_address = True daemon_threads = True picam2 = Picamera2() picam2.configure(picam2.create_video_configuration(main={"size": (640, 480)})) output = StreamingOutput() picam2.start_recording(MJPEGEncoder(), FileOutput(output)) try: address = ('', 8888) server = StreamingServer(address, StreamingHandler) server.serve_forever() finally: picam2.stop_recording()
Press CTRL + X to exit and choose Y to save file
change directory
cd /etc/systemd/system
Open the text editor to create a system service file
sudo nano streamVideo.service
Paste in the following code
[Unit] Description=A script for straming video to http After=syslog.target network.target [Service] WorkingDirectory=/usr/local/bin/ ExecStart=/usr/bin/python3 /usr/local/bin/streamVideo.py Restart=always RestartSec=120 [Install] WantedBy=multi-user.target
PRess CTRL + X
press Y and enter to saveEnter the following to reload the daemon
sudo systemctl daemon-reload
Enable the service
sudo systemctl enable streamVideo.service
You should see something like
Created symlink /etc/systemd/system/multi-user.target.wants/streamVideo.service → /etc/systemd/system/streamVideo.service.
Restart the Pi
sudo shutdown -r now
After the Pi has rebooted, you should be able to access the stream by going to the following URL in your browser
http://raspberrypi.local:8888/index.html
In DWC you need to enter this URL on order to get the stream
http://raspberrypi.local:8888/stream.mjpg
Your video stream should now be visible in the Webcam tab
I hope this saves someone some frustration.
-
RE: Reprap G-Code syntax now part of RJ TextEd text editor
I have updated the syntax file for RJ TextEd to include more object model items.
I have also included a suggested colour format file.
You will need to update RJ TextEd to the latest version in order to take advantage of hints etc.Replace the existing files (reprap.syx, reprap.ini) in C:\Users"yourname"\AppData\Roaming\RJ TextEd\Syntax
Copy the file "Reprap Dark.xml" to C:\Users"yourname"\AppData\Roaming\RJ TextEd\Syntax color themes\To install.
1: Go to ENVIRONMENT > THEMES > CUSTOMISE
2: Select "Reprap G Code" from the drop down list of highlighters
3: Click the button, and select import colors from file
4: Select the "Reprap Dark.xml" file previously copied.
You can customize the colours to suit yourself.
Different G Code parameters such as X, Y, Z , E, F, G etc can be coloured to make individually.
Likewise, comments and other items can be individually coloured.
-
Forum categories for conditional Gcode & macros
Now that 3.1 is released, I think it would be beneficial to have a forum category dedicated to conditional gcode & macros.
This should probably be broken down into various kinematic types.
Furthermore it may be good to have some sort of “duet approved” marker like the “solved” marker, once the macros have been peer reviewed and improved where necessary.
I usually try to make macros “portable” by not hard coding anything, but the very fact that certain parts of the object model are not visible if you don’t run that type of configuration, probably makes putting them in the categories for which they were designed beneficial and will reduce angst and support time.
(Yes, I know we should be checking for null objects and kinematic types as a matter of course in every macro)
Which leads to my final point, which is once we have some documentation on the object model, perhaps someone could do a small tutorial on best practice when writing macros.
My programming background is not in C or its variants so I admit I’m constantly searching for proper syntax etc.
To a non-programmer it is just gobbledygook.
If there’s going to be dozens of people learning to do it, they may as well learn to do it in a way that ensure portability and reduces the chance of unexpected issues.
I’ve always found it usually takes 10 lines of code to do the job, the another 50 lines of code to stop people trying to do it out of order or in a way you never imagined -
RE: Setting up video streaming using raspberry pi
I found another application that works quite easily and has the benefit of being able to adjust all the settings from the web interface.
It's called picamera2-WebUI-Lite
There's a slight error on the instructions to install and it doesn't tell you how to set it up as a service, so I've listed thesteps.
To install.
SSH into your PI (or open a terminal if using a desktop)
Change directory
cd /usr/local/bin/
Clone the repositry
sudo git clone https://github.com/monkeymademe/picamera2-WebUI-Lite.git
Change directory
cd /usr/local/bin/picamera2-WebUI-Lite
Test that it's running
python3 app.py
Open your browser and go to
http://raspberrypi.local:8080/To see just the video feed go to
http://raspberrypi.local:8080/video_feedGo back to the terminal and hot CTRL + C to shut the app down
To set it up as a service
Change directorycd /etc/systemd/system
Open a text editor to create a system service file
sudo nano webui-lite.service
Paste in the following
[Unit] Description=Start script for WebUi-Lite as a service After=syslog.target network.target [Service] WorkingDirectory=/usr/local/bin/picamera2-WebUI-Lite/ ExecStart=/usr/bin/python3 /usr/local/bin//picamera2-WebUI-Lite/app.py Restart=always RestartSec=120 [Install] WantedBy=multi-user.target
Press CTRL + X to exit and Y to save
Reload the daemon
sudo systemctl daemon-reload
Enable the service
sudo systemctl enable webui-lite.service
NOTE that if you already have the other streamVideo service I listed above using Picamera you will have to disable it as you can't run both.
sudo systemctl disable streamVideo.service
Reboot the system to make sure the service starts
sudo shutdown -r now
Adjust your DWC settings as follows
This will allow you to easily get to the settings page by clicking on the webcam image in DWC
From there adjust your resolution and you can also zoom in a particular area using the scaler crop settings.Full view
Cropped view
-
RE: Conditional G Code - RJ TextEd Syntax file.
For those using RJ Text Editor, I have posted an updated syntax file that includes the new object model items and G Codes contained in RRF3.5.0 b2
It can be downloaded here
https://www.rj-texted.se/Forum/viewtopic.php?p=18659#p18659It includes mouse over hints for G Code commands and auto-complete
The program comes with the RRF syntax file as standard but you may need to update it as new firmware versions come out. -
Heater fault checking routine to be run in daemon.g
This is a routine I run in daemon.g to check for heater faults.
It is mainly aimed at covering the time when the printer is idle as RRF already checks when a print is running.
I have excluded heaters that are currently being PID auto tuned
It does not take action if the heater temp is < 45 degrees but will shut down if temp is < zero as that would indicate a thermistor fault.
If the temperature is over 45 degrees it will only fault if the current temp is > 15 degrees from the set active temp AND the temperature is rising by > 0.5 degrees over 3 seconds.
This is to avoid activating when the temp is deliberately lowered.
NOTE: I have used a dummy tool to store variables which allow me to have this routine run only at intervals greater than 10 seconds whilst allowing the rest of daemon.g to run at normal intervals. Without this (and until we get variables) you will need to modify the code and use G4 to set the timing
For the timing I have used state.upTime. which will probably roll over at some point if the printer is left on long enough (I'm guessing it's a 16 bit signed integer?), so I have a check for that.
I have tested it in both idle and printing states and it seems to work as expected but have not confirmed the upTime rollover works as expected.; 0:/sys/daemon.g ; runs continually in background at approximately 1Hz if not delayed internally ;HEATER CHECKS ; this section of daemon.g checks for heater faults ; RRF doesn't currently check for faults when idle but by default will shutdown during printing if temperature excursion is > 15 degrees. ; Note: temp excursion value and time may be manually set using M570 ; checking if temp is rising requires a variable. I have used a dummy tool offset (Tool2) for this and to do elapsed time since last check ; G4 could be used but would also delay anything else in daemon.g ; this way allows other checks to run more frequently if needed however the G4 delays inside the loop will affect the frequency of daemon.g ; will be updated when variables are available in RRF. while iterations < #heat.heaters ; loop through all configured heaters if ((tools[2].offsets[1]+10) > state.upTime) ; if checked in last 10 seconds escape loop and go to rest of daemon.g if present. offset will be zero at startup via config.g ;echo "skipping loop " ^ " " ^ state.upTime ^ " " ^ tools[2].offsets[1]+10 if tools[2].offsets[1] - state.upTime > 60 ; uptime must have rolled over so reset off set to zero G10 P2 Y0 echo "upTime has rolled over. Heater checking reset" break ;echo "checking heater " ^ iterations ^ " " ^ state.upTime ^ " " ^ tools[2].offsets[1]+10 if heat.heaters[iterations].state="tuning" ;echo "heater " ^ iterations ^ " is tuning - no check carried out" continue ; don't check this heater as it is PID auto tuning if (heat.heaters[iterations].current) > (heat.heaters[iterations].max) ; temp is over max so emergency shutdown required ;M41 P5 S1 ; activate output connected to externally powered latching relay here to sound alarm echo "heater over max temp fault detected in daemon.g. - shutting down" M112; emergency shutdown M81 S1 ; turn off power when fans have turned off if (heat.heaters[iterations].current > 45) ; no real danger at below this temp as ambient may be close to this ;echo "heater " ^ iterations ^ " is above 45 degrees" if (heat.heaters[iterations].state!="off") && (heat.heaters[iterations].current > heat.heaters[iterations].active + 15) ; temp is > 15 degrees above target. ;echo "heater " ^ iterations ^ " is on or in standby - checking if temp is rising" G10 P2 X{heat.heaters[iterations].current} ; set dummy tool X offset to current temp of heater G4 S3 ; wait 3 seconds if (heat.heaters[iterations].current > tools[2].offsets[0] + 0.5) ; heat is rising by more than 0.5 degrees in 3 seconds echo "heater runaway fault detected in daemon.g. - shutting down" if (state.status=="processing") M25 ; pause print so you might be able to save it using M119 ;M41 P5 S1 ; activate output connected to externally powered latching relay here to sound alarm M0 ; unconditional stop. If axes are homed and a print is being canceled will run cancel.g otherwise will run stop.g M81 S1 ; turn off power when fans have turned off else ;echo "heater is on or standby but temp is falling on heater " ^ iterations ^ " - no action needed" elif (heat.heaters[iterations].state="off") && (heat.heaters[iterations].current >= 45) ; if heater is off and temp is greater than 45 there could be an issue ;echo "heater " ^ iterations ^ " is off but checking if temp is rising" G10 P2 X{heat.heaters[iterations].current} ; set dummy tool X offset to current temp of heater G4 S3 ; wait 3 seconds if (heat.heaters[iterations].current > tools[2].offsets[0] + 0.5) ; heat is rising by more than 0.5 degrees in 3 seconds ;echo "heater is off but temp is rising on heater " ^ iterations ^ "emergency shutdown" ;M41 P5 S1 ; activate output connected to externally powered latching relay here to sound alarm echo "heater runaway fault detected in daemon.g. - shutting down" M112; emergency shutdown M81 S1 ; turn off power when fans have turned off else ;echo "heater is off & temp is falling or stable on heater " ^ iterations ^ " - no action needed" else ;heater is below 45 degrees so only other fault may be an open circuit thermistor which should show -275 degrees if heat.heaters[iterations].current < -0 ; we probably have a thermistor fault if heater is less than 0 degrees M112 ; emergency shutdown M81 S1 ; turn off power when fans have turned off if iterations == #heat.heaters-1 ; all heaters have been checked G10 P2 Y{state.upTime} ; set the new time to check again ; END HEATER CHECKS ; run other checks ;G4 S10 ; pause daemon.g for 10 seconds before next run - required if not using variables
-
RE: How can I create a toggle macro?
@paulhew said in How can I create a toggle macro?:
@dc42 said in How can I create a toggle macro?:
state.gpout
Appreciate the input you have given, maybe I need to start at the beginning.
This is my config for my Lights;Lights M950 F10 C"out1" M106 P10 S0 H-1 C"Front Lights" M106 P10 S1
It looks correct, it works. Personally it would be better if it was an on/off and not PWM'd, Do not think I require mood lighting on my chamber lights
So after enabling Object Model Browser, I found F10 under Fans and it shows the correct info as in my config.
I change the lights on and off and it changes 'actualValue'Not being as clever as you guys but willing to learn, how do I query 'Fan.10' actualValue'?
I sort of understand the 'if' part
if 'actualValue' = 1
then set to 0
Elif 'actualValue' = 0
set to 1Personally, it would be handy if there were some basic 'Meta Guides' to help us understand how to write queries and some simple ones we could implement to advance our learning.
try here
https://duet3d.dozuki.com/Wiki/GCode_Meta_CommandsLike if the printer is not homed, then home it and how to turn on /off lights with a one button macro!
If you look through the meta commands topic section of the forum there are many examples which should help you understand.
https://forum.duet3d.com/category/34/gcode-meta-commandsMuch like you have done with the RRF configurator, it explains the function which has helped me learn a lot.
I did search for the "state.gpout[n].pwm" and variations in the dozuki and could not find any reference to it.
Again, Thanks in advance.
You have to use the object model to reference the proper name & syntax for the GPIO output, fan or whatever.
In our case, you're using a fan output, not a GPIO pin.
So you need to look in the fans section of the object browser.
Fan outputs are PWM capable, so that you can set a fan to any speed.
In your case, you can just set it to 0 or 1 using M106.
A GPIO output would be set by M42in terms of code you could have something like
if fans[10].actualValue = 1 M106 P10 S0 else M106 P10 S1
This example is only meant for an LED connected to a fan ouput that is only ever set full on, or full off.
-
RE: A couple of questions regarding global variables
As has been pointed out, you can declare the globals wherever you like, but of course you must ensure that you do so before you try to use them anywhere.
I always check if the global has already been created as I don't like seeing the errors if I run M98 P"0:/sys/config.g" for example.
It also ensures the global is reset in case I changed it in code somewhere.
In some cases I check for a null value, but in most cases that's probably not necessary.if !exists(global.InputStart) || global.InputStart=null global InputStart=0 else set global.InputStart=0 if !exists(global.InputEnd) || global.InputEnd=null global InputEnd=1 else set global.InputEnd=1
With regards to sending parameters using M98, it's pretty straight forward.
For example you might use it when there is a value you want to use from your slicer.
Here I pass an "S" and a "D" parameter taken from the slicerM98 P"0:/macros/print/doPrimeLine" S[extrusion_multiplier] D[nozzle_diameter] F[filament_diameter] ; does prime line with calculated extrusion amount
In my macro I use the values passed thus
This macro also uses local variables which are freed once the macro has run.
It's a fair bit of overkill to do a prime line, but it was mainly a test bed for various methods; 0:/macros/print/doPrimeLine if state.currentTool = -1 abort "No tool selected" var ThisHeater = tools[state.currentTool].heaters[0] ; create local variable if {heat.heaters[var.ThisHeater].state != "active"} abort "Heater " ^ var.ThisHeater ^ " on " ^ tools[state.currentTool].name ^ " not active" if {heat.heaters[var.ThisHeater].active < heat.coldExtrudeTemperature} abort "Heater " ^ var.ThisHeater ^ " on " ^ tools[state.currentTool].name ^ " set below min extrude temp" if !exists(param.S) abort "no ""S"" (extrusion factor) parameter sent to macro in M98" if !exists(param.D) abort "no ""D"" (nozzle diameter) parameter sent to macro in M98" if !exists(param.F) abort "no ""F"" (filament diameter) parameter sent to macro in M98" M116 ; wait for heaters to settle var height = 0.2 ; create a variable then set it depending on slicer commanded position prior to calling this macro if move.axes[2].machinePosition <= 0.4 set var.height = move.axes[2].machinePosition else set var.height = 0.3 var SF = 100 if param.S <=1 ; check if slicer sends percentage of 1 or 100 set var.SF = param.S ; extrusion multiplier as a percentage of 1 else set var.SF = param.S / 100 var FD = 1.75 ; set default filament diameter set var.FD = param.F ; but over write with slicer setting ; set start and end of move in Y direction - X position will be set manually var start = {move.axes[1].max -10 } ; extrude line start position var end = {move.axes[1].min +10} ; extrude line end position var l = var.start - var.end ; calculated line length var d = param.D ; nozzle diameter that is passed to macro as paramater var amount = ((var.d*var.height*var.l) + (pi*((var.height/2)*(var.height/2))*var.l)) / (pi*((var.FD/2)*(var.FD/2))) * var.SF ; calculate how many mm of filament to extrude - Owen's calculation echo "flow rate value " ^ var.SF ^ " with " ^ var.d ^ "mm nozzle, " ^ var.FD ^ "mm filament dia over " ^ var.l ^ "mm length = E" ^ var.amount ^ "mm" M83 ; set relative extrusion G1 X0 Y{var.start} Z{var.height} F1200 ; move to edge to wipe any oozed filament G1 X0 Y{var.end} F600 E{var.amount}; move and extrude G1 X{var.d} ; move by one nozle width G1 Y{var.start} F600 E{var.amount}; move and extrude G10 ; retract
-
Macro to start job after a delay or at specific time
Sometimes I'm reticent to leave certain jobs printing over night.
I did this macro to allow me to work on models at night and have them start the print process before I get up. (download link at bottom)
This allows all the heating, bed leveling etc to have taken place by the time I'm up.
It should be commented well enough to allow you to figure out how to use it.
Naturally you have to be confident nothing will go wrong before you get to the printer (we all get perfect first layers every time right? ).I highly recommend that it is called BEFORE any heaters are activated.
If your intention is to preheat beds, chambers etc but wait until you command a print, then I suggest starting the heating AFTER the macro exits then putting a pause command in your start G-code after the heating if you don't want printing to start before you get there.The countdown displays continually and is updated at various intervals depending on how long is left till blast off.
There is no option to cancel once started.EDIT: I've modified the code to create a global variable which can be set to cancel the operation. Read the comments.
To test from DWC without running a print, the X parameter must be used.
This will escape the macro after X loops. The loops vary in time depending on the time left, so for example to get the last minute then X must be greater than 22.
Naturally it should not be used when printing.There will be beeps at 5 second intervals in the last minute and 1 second intervals in the last 10 seconds.
; start_after_delay.g ; should be called from slicer start gcode or start.g BEFORE any heating actions ; Accepts four parameters ; X = a testing parameter to allow early exit after X loops ; S = number of minutes to delay ; H = number of hours to delay ; H & S times are added to achieve total delay ; R = start time. Must have the format R"yyyy-mm-ddThh:mm:ss" e.g R"2022-01-04T13:30:00" ; NOTE: the use of curly braces surrounding time works in DWC but fails in SuperSlicer ; R{"2022-01-04T16:47:00"} will fail if used in SuperSlicer ; if R parameter (start time) is passed, H & S are ignored ; at least one time parameter must be passed ; e.g ; M98 P"start_after_delay.g" H4 S20 ; (delay the start of the print for 4 hours 20 minutes) from now ; M98 P"start_after_delay.g" H1 ; (delay the start of the print for 1 hour) from now ; M98 P"start_after_delay.g" S10 ; (delay the start of the print for 10 minutes) from now ; M98 P"start_after_delay.g" R"2022-04-01T06:00:00" ; start the print on 1st April 2022 at 06:00AM var LongDelay = 60 ; Delay between message updates if time left is greater than 1 hour var MediumDelay = 20 ; Delay between message updates if time left is less than 1 hour but greater than 10 minutes var ShortDelay = 10 ; Delay between message updates if time left less than 10 minutes ; at less than one minute updates will happen every 5 seconds ; at less than 10 seconds, updates will happen every second var BeepFrequencyLow = 1000 ; frequency (low pitch) of beep play every var.ShortDelay when one minute left var BeepFrequencyHigh = 3000 ; frequency (high pitch) of beep play every var.ShortDelay when ten seconds left var BeepDuration = 200 ; duration of beep in milliseconds (must be less than one second) - note 1 second will cause constant beep for last 10 seconds ; ************** Don't change below this line ***************** ; Create a global variable to allow the process to be cancelled. if !exists(global.Cancelled) global Cancelled = false ; NOTE: There will be a delay between setting this global to true and the macro/print cancelling ; as it may be called during a G4 wait commmand and can't be activated on until that finishes. ; To use, either send "set global.Cancelled = true" from the command line or a macro, or set up a button with an associated trigger. ; A separate macro may be hard to execute when the loop timer gets down to 5 or 1 seconds. var FileName = "No file selected" if !exists(param.X) if (job.file.fileName = "") || (job.file.fileName=null) abort "No print in progress" else set var.FileName=job.file.fileName else if (job.file.fileName!=null) set var.FileName=job.file.fileName if var.BeepDuration > 1000 echo "Invalid beep duration - reset to 1/2 second) set var.BeepDuration = 1000 if !exists(param.R) if !exists(param.H) if !exists(param.S) echo "No parameters passed - exiting macro" M99 var Hours = 0 ; variable for number of whole hours from start time until run time var Minutes = 0 ; variable for number of whole minutes from start time until run time var Delay = 10 ; variable for delay between displaying messages var HoursLeft= 0 ; variable for number of whole hours from current time until run time var MinutesLeft = 0 ; variable for number of whole minutes from current time until run time var SecondsLeft = 0 ; variable for number of whole seconds from current time until run time var StartTime = datetime(state.time) ; variable to hold time when macro first called var RunTime = datetime(state.time) ; variable to hold time when macro will end and print will run if exists(param.R) set var.StartTime = datetime(param.R) set var.StartTime = var.StartTime - state.time set var.RunTime = state.time + var.StartTime set var.Hours = floor(var.StartTime / 3600) ; calculate number of whole hours till start time set var.Minutes = floor(var.StartTime/60)-(var.Hours*60) ; calculate number of whole minutes till start time else if exists(param.H) set var.Hours = param.H if exists(param.S) set var.Minutes = param.S set var.StartTime = state.time + var.Hours*60*60 + var.Minutes*60 set var.RunTime = var.StartTime var Loops = 0 ; used if a testing parameter X is passed in order to exit after number of loops has expired echo "Print start time is " ^ var.RunTime while state.time < var.RunTime if exists(global.Cancelled) if global.Cancelled = true M291 P"Operation has been cancelled" S0 T3 G4 S3 abort "Deferred print cancelled." if exists(param.X) set var.Loops = var.Loops + 1 set var.HoursLeft = floor((var.RunTime - state.time )/60/60) set var.MinutesLeft = floor((var.RunTime - state.time)/60)-(var.HoursLeft*60) set var.SecondsLeft = mod((var.RunTime - state.time),3600)-(var.MinutesLeft*60) if var.RunTime - state.time > 3600 set var.Delay = var.LongDelay else set var.Delay = var.MediumDelay if (var.RunTime - state.time) > 600 M291 R{var.FileName} T{var.Delay} S1 P{"Print start deferred for " ^ var.HoursLeft ^ " hrs : " ^ var.MinutesLeft ^ " mins - " ^ var.RunTime} G4 S{var.Delay} elif (var.RunTime - state.time) > 60 set var.Delay = var.ShortDelay M291 R{var.FileName} T{var.Delay} S1 P{"Print start deferred for " ^ var.HoursLeft ^ " hrs : " ^ var.MinutesLeft ^ " mins : " ^ var.SecondsLeft ^ " secs - " ^ var.RunTime} G4 S{var.Delay} elif (var.RunTime - state.time) > 10 set var.Delay = 5 M291 R{var.FileName} T{var.Delay} S1 P{"Print starting in " ^ floor((var.RunTime - state.time)) ^ " seconds"} M300 S{var.BeepFrequencyLow} P{var.BeepDuration} G4 S{var.Delay} else set var.Delay = 1 M291 R{var.FileName} T{var.Delay} S0 P{"Print starting in " ^ floor((var.RunTime - state.time)) ^ " seconds"} M300 S{var.BeepFrequencyHigh} P{var.BeepDuration} G4 S{var.Delay} if exists(param.X) && (var.Loops = param.X) break M118 S"Starting Deferred Print" M300 S{floor(var.BeepFrequencyHigh * 1.2)} P1000 G4 S2
-
RE: Paneldue access to Filament Config load/unload options
@nirin
There's probably not going to be a similar functionality to DWC because PanelDue doesn't use a browser.
If you run RRF 3.5 there is a better option to creating a macro button for each filament.
This macro takes advantage of M291 and will allow you to put your filaments into a list and cycle through the pages of them until you find the one you want.
You could simply use M291 S7 to prompt for the name of the filament you want to change to, which would be a simpler macro, but would require you to type it in exactly as it is in DWC
This way you can cut and past your filament names from DWC into the macro.
The best way to get the list is sendM20 S0 P{directories.filaments}
and copy the result from the consoleYou can adjust the number of buttons displayed on each page to suit yourself.
I found five buttons per page fitted on one line (7i)
If you use six, the NEXT button is on another line.
On the last page not all buttons will have a name and a cancel button appears.
EDIT: 7i PD can only display a max of 10 buttons, so I've limited it too that.Download link at bottom of post.
; ChangeFilament.g ; requires RRF 3.5 or later! ; list of filaments must follow rules for array ; https://docs.duet3d.com/User_manual/Reference/Gcode_meta_commands#array-expressions var filaments = {"ABS","ASA","EDGE","eFlex","eLastic","FLEX","HIPS","NGEN","NYLON","PA-CF","PC","PCABS","PDVF","PEEK","PEI","PEKK","PET","PETG","PLA","POM","PP","PSU","PVA","SCAFF","TPE","TPU",} ; list your filaments here var maxBtns = 10; Max number of buttons per page on PanelDue. Adjust as required. 5 works OK on 7"paneldue - 9 is max! ; don't change below here var thisTool = state.currentTool if var.thisTool = -1 abort "No tool selected" var thisFilament = move.extruders[tools[var.thisTool].extruders[0]].filament var newFilament = null if var.maxBtns > 10 set var.maxBtns = 10 echo "Paneldue can only display 10 buttons in total" echo "Max buttons has been reset" var thisPage = vector(var.maxBtns,"") var numPages = floor(#var.filaments / (var.maxBtns - 1)) if mod(#var.filaments , var.maxBtns - 1) > 0 set var.numPages = var.numPages + 1 var pagesDone = 0; var btnsDone = 0 var nextFilament = "" var nextItem = 0 while var.pagesDone < var.numPages set var.thisPage = vector(var.maxBtns,"") set var.btnsDone = 0 while var.btnsDone < var.maxBtns-1 set var.nextItem = iterations + (var.pagesDone * (var.maxBtns-1)) if var.nextItem = #var.filaments break set var.thisPage[var.btnsDone] = var.filaments[var.nextItem] set var.nextFilament = var.filaments[var.nextItem] set var.btnsDone = var.btnsDone + 1 if var.pagesDone = var.numPages - 1 set var.thisPage[{var.maxBtns-1}] = "Cancel" else set var.thisPage[{var.maxBtns-1}] = "Next" set var.pagesDone = var.pagesDone + 1 M291 P"Select filament" S4 K{var.thisPage} if input = var.maxBtns-1 continue else set var.newFilament = var.thisPage[input] break if (var.newFilament = null) || (var.newFilament = "") abort "No filaments chosen" else echo "Filament chosen : ", var.newFilament, " : commence change" if var.newFilament = "noFilament" M701 S{var.newFilament} if result != 0 abort "Error during loading" M703 else if (var.thisFilament != "noFilament") && (var.thisFilament != null) M702 M701 S{var.newFilament} if result != 0 abort "Error during loading" M703
-
RE: System files check
If that type of checking is important to you then I suggest creating a macro which uses M38 to check for the existence of whatever file(s) you're worried about and call it from config.g
Which files are critical may well depend on what the board is being used for and which board it is.
Hard coding such checks would likely have negative effects on many users for a use case that is arguably of limited value.
The firmware checks at the time of usage because that's the logical assurance that the file in question is actually going to be used. -
[3.4.0b7+7] Heater overshoot if second K value in M307 is used
As reported by several users there seems to be some issues with the heating algorithm if the heater is tuned as a tool and a second K value is used.
I have noticed an increase in initial heater overshoot as well compared with earlier firmware versions.Duet 2 Wifi standalone.
Titan Aqua (water cooled) extruder
E3D V6 running 24v 40W cartridge (measured at 15ohms resistance)
Silicon sock fitted
Nominal voltage is 24.3If you set up a print that has rapid (short period) fan speed changes, such as in bridging, t appears that there is a boost of power to the heater each time the fan comes on even if the current temp is well above the set value.
In some cases this has triggered a heater fault for me.In this print (using TPE) I have a default fan speed of zero
External perimeters are set to 35% fan speed and bridging at 80%Before running it, I did a fresh PID autotune using this command.
M303 T0 P1 S240 F1
and got a result of
M307 H1 R2.848 K0.325:0.197 D10.51 E1.35 S1.00 B0 V24.3
I didn't need to get to the bridging to demonstrate the issue.
Initial heatup seems to give an overshoot of around 10 degrees which is increased from earlier versions.
As soon as the print got to the second layer and began turning the fan on & off as it moved from leg to leg, then this graph resulted.
I then sent this command
M307 H1 R2.848 K0.325:0 D10.51 E1.35 S1.00 B0 V24.3
After which this graph resulted. Note temp has dropped back to target value whereas before it was oscillating about 10 degrees over
I had turned on logging at the start using
M929 P"0:/sys/heater-log.txt" S3
But all I got back when I stopped the print was in the attached file.
I had expected heater values?
heater-log.txt -
RE: What if I want to run daemon.g at less than 10 second intervals?
Lazy sunday, so I decided to answer some of this myself
@owend said in daemon.g usage "rules" clarification:
What hasn't been covered (to my knowledge) is the implications of creating a loop which lasts longer than 10 seconds.
I presume RRF checks if the daemon process/thread is running before attempting to start a new one?
I.e "there can only be one"This seems to be the case. The daemon does not try to re-open if an instance already exists in memory.
If an infinite loop were created, would it still be possible to rename daemon.g in order to modify?
Yes, you can rename it in order to edit the contents. However your revision won't take effect until the runnning instance of daemon.g is stopped (which will allow the new file to be loaded from disk)
Or would we need to do something like set a variable (which we can modify from the console) which we check before the loop for cases where we need to modify the daemon ?
I chose to do this as a fail safe. Restarting would load the new file, but I wanted a way to ensure I wasn't stuck in a loop.
So I made a couple of global variable in config.gAre there any other implications of running the daemon in an infinite loop?
This question I haven't fully answered. More variables = more memory taken from the stack.
I guess it'll depend on what you do in daemon.g as to whether it could have effect on printing.For those interested here's what I did.
In config.g I added
if !exists(global.RunDaemon) global RunDaemon=true else set global.RunDaemon=true ; Global Variables for heater checking routine in daemon.g if !exists(global.HeaterCheckInterval) global HeaterCheckInterval=10 ; variable for use in daemon.g sets interval of heater checks else set global.HeaterCheckInterval=10 ; variable for use in daemon.g sets interval of heater checks
I then created two macros for "starting" and "stopping" the daemon
StartDaemon.g
;0:/macros/Daemon control/StartDaemon.g set global.RunDaemon = true M38 "0:/sys/daemon.g" ; check if dameon.g exists by trying to calculate hash if result = 2 ; file not found M38 "0:/sys/daemon.g.bak" ; check if dameon.g.bak exists by trying to calculate hash if result = 0 M291 P"No daemon.g found" R"Daemon.g not found but daemon.g.bak found. Rename now?" S3 M471 S"0:/sys/daemon.g.bak" T"0:/sys/daemon.g" ; rename dameon.g.bak to daemon.g if result = 0 echo "Daemon successfully renamed. Hit F5 on browser to refresh" else M291 P"Error" R"Unable to rename file" S2 else M291 P"No daemon found" R"Daemon.g not found." S2
StopDaemon.g
set global.RunDaemon = false M471 S"0:/sys/daemon.g" T"0:/sys/daemon.g.bak" ; rename dameon.g if result = 0 M291 R"Daemon.g stopped" P"Daemon.g has been stopped and renamed. Hit F5 to refresh browser" S2 else M291 R"Error" P"Could not rename daemon.g" S2
My daemon.g was then modified so that I had an infinite loop
I could use G4 to set the timing of this, however I have broken down sections which I can run at selected intervals
As it turns out, I wanted to check my heaters at 10 second intervals (which I was doing when daemon.g ran at 1Hz)Note that anything not contained inside the infinite loop would only ever get run at the first loading of daemon.g and only then if the code was placed BEFORE the infinite loop.
daemon.g
; 0:/sys/daemon.g ; runs continually in background at approximately 10 second intervals ; We have initiated an infinite loop at the start so that we can do things at intervals less than 10 seconds ; daemon.g won't be opened again if it it still running ; everything must be indented by 2 x tabs becasue of the infinite loop at the start to allow checks at intervals less than 10 seconds ; We have created a global variable in config.g called RunDaemon ; If RunDaemon is set to "false" nothing inside the infinite loop will run, however daemon.g will still be run every 10 seconds if it exists. ; Any code outside the infinite loop would run at 10 second intervals provided it came before the loop while true if global.RunDaemon == false M99 ; exit the macro else ;add code that must be run at less than 10 second intervals here ;HEATER CHECKS ; this section of daemon.g checks for heater faults ; RRF doesn't currently check for faults when idle but by default will shut down during printing if temperature excursion is > 15 degrees. ; Note: temp excursion value and time may be manually set using M570 ; checking if temp is rising requires a variable. ; G4 could be used but would also delay anything else in daemon.g ; this way allows other checks to run more frequently if needed however the G4 delays inside the loop will affect the frequency of daemon.g ; will be updated when variables are available in RRF. ; everything must be indented by 2 x tabs becasue of the infinite loop at the start to allow checks at intervals less than 10 seconds while iterations < #heat.heaters ; loop through all configured heaters set global.LastTemp=heat.heaters[1].current ; Set variable to current extruder temp. if state.upTime < 60 break; If uptime is < 60 seconds, break out so all fans etc have time to stabilise. if ((global.LastCheckTime + global.HeaterCheckInterval) > state.upTime) ; if checked in last 10 seconds escape loop and go to rest of daemon.g if present. offset will be zero at startup via config.g ;echo "skipping loop " ^ " " ^ state.upTime ^ " " ^ global.LastCheckTime+10 if global.LastCheckTime-state.upTime > 60 ; uptime must have rolled over so reset off set to zero G10 P2 Y0 echo "upTime has rolled over. Heater checking reset" break else ;echo "checking heater " ^ iterations ^ " " ^ state.upTime ^ " " ^ global.LastCheckTime+10 if heat.heaters[iterations].state="tuning" ;echo "heater " ^ iterations ^ " is tuning - no check carried out" continue ; don't check this heater as it is PID auto tuning if (heat.heaters[iterations].current) > (heat.heaters[iterations].max) ; temp is over max so emergency shutdown required ;M41 P5 S1 ; activate output connected to externally powered latching relay here to sound alarm M118 P0 S"heater over max temp fault detected in daemon.g. - shutting down" L1 M112; emergency shutdown M81 S1 ; turn off power when fans have turned off if (heat.heaters[iterations].current > 45) && (heat.heaters[iterations].active > 45); no real danger at below this temp as ambient may be close to this ;echo "heater " ^ iterations ^ " is above 45 degrees" if (heat.heaters[iterations].state!="off") && (heat.heaters[iterations].current > heat.heaters[iterations].active + 15) ; temp is > 15 degrees above target. ;echo "heater " ^ iterations ^ " is on or in standby - checking if temp is rising" set global.LastTemp=heat.heaters[iterations].current ; set the last check temp ;echo "heater " ^ iterations ^ " temp: " ^ heat.heaters[iterations].current G4 S3 ; wait 3 seconds if (heat.heaters[iterations].current > global.LastTemp + 0.5) ; heat is rising by more than 0.5 degrees in 3 seconds echo "heater runaway fault detected in daemon.g. - shutting down" if (state.status=="processing") M25 ; pause print so you might be able to save it using M119 ;M41 P5 S1 ; activate output connected to externally powered latching relay here to sound alarm M0 ; unconditional stop. If axes are homed and a print is being canceled will run cancel.g otherwise will run stop.g M81 S1 ; turn off power when fans have turned off else ;echo "heater is on or standby but temp is falling on heater " ^ iterations ^ " - no action needed" elif (heat.heaters[iterations].state="off") && ((heat.heaters[iterations].current) >= (fans[1].thermostatic.lowTemperature+0)) ; if heater is off and temp is greater than 50 there could be an issue set global.LastTemp=heat.heaters[iterations].current; ;echo "heater " ^ iterations ^ " is off but checking if temp is rising" G4 S3 ; wait 3 seconds if (heat.heaters[iterations].current > global.LastTemp + 0.5) ; heat is rising by more than 0.5 degrees in 3 seconds echo "heater is off but temp is rising on heater " ^ iterations ^ "emergency shutdown" ;M41 P5 S1 ; activate output connected to externally powered latching relay here to sound alarm echo "heater runaway fault detected in daemon.g. - shutting down" M112; emergency shutdown M81 S1 ; turn off power when fans have turned off else ;echo "heater " ^ iterations ^ " is off & temp is falling or stable on heater " ^ iterations ^ " - no action needed" else ;echo "heater " ^ iterations ^ " is below 45 degrees so check thermistor" ;"heater is below 45 degrees so only other fault may be an open circuit thermistor which should show -275 degrees" if heat.heaters[iterations].current < 0 ; we probably have a thermistor fault if heater is less than 0 degrees M112 ; emergency shutdown M81 S1 ; turn off power when fans have turned off ;Check if water pump is running correctly if (iterations=1) && ((heat.heaters[1].current) > (fans[1].thermostatic.lowTemperature+0)) if fans[1].rpm <= 500 ; Coolant pump RPM off or low G4 S3 ; check again in 3 seconds in case it's just spinning up if fans[1].rpm <= 500 echo "Water pump fault - shutting down heaters - RPM : " ^ fans[1].rpm M25 ; pause print so you might be able to save it using M119 M0 ; unconditional stop. If axes are homed and a print is being canceled will run cancel.g otherwise will run stop.g M81 S1 ; turn off power when fans have turned off elif (fans[1].rpm > 500) && (fans[1].rpm < 1400) G4 S3 ; check again in 3 seconds in case it's just spinning up if (fans[1].rpm > 500) && (fans[1].rpm < 1400) echo "WARNING: Water pump RPM low - RPM : " ^ fans[1].rpm if (state.status=="processing") M25 ; pause print so you might be able to save it using M119 ;M41 P5 S1 ; activate output connected to externally powered latching relay here to sound alarm M0 ; unconditional stop. If axes are homed and a print is being canceled will run cancel.g otherwise will run stop.g M81 S1 ; turn off power when fans have turned off else ;echo "Coolant OK - RPM : " ^ fans[1].rpm if fans[2].rpm <=1000 echo "WARNING: Water pump FAN RPM low - RPM : " ^ fans[2].rpm if (state.status=="processing") M25 ; pause print so you might be able to save it using M119 ;M41 P5 S1 ; activate output connected to externally powered latching relay here to sound alarm M0 ; unconditional stop. If axes are homed and a print is being canceled will run cancel.g otherwise will run stop.g M81 S1 ; turn off power when fans have turned off if iterations == #heat.heaters-1 ; all heaters have been checked set global.LastCheckTime=state.upTime ; set the new time to check ; END HEATER CHECKS ; BEGIN OTHER CHECKS AT INTERVALS LESS THAN 10 SECONDS ; run other checks - don't forget to add 2 x tabs ;echo state.upTime ^ ":" ^ state.msUpTime ; should run very fast ;G4 P500
-
RE: Slicer-adjustment, script or something else?
You can just select laser mode using M452
https://duet3d.dozuki.com/Wiki/Gcode#Section_M452_Select_Laser_DeviceModeUse the pin defined in the P parameter to control a relay which in turn can be wired to your torch trigger.
Your image appears to depict tig or plasma cold wire.
this is a poor choice for WAAM using a printer base as you have no way of keeping the wire in the correct orientation with regards to the arc and direction of travel.The best process is CMT (Cold Metal Transfer)
This is being used by several commercial WAAM systems.