From c5258a833feda41739c935d74955746e5e2b3199 Mon Sep 17 00:00:00 2001 From: Christophe Barlieb Date: Tue, 19 May 2026 22:07:50 +0200 Subject: [PATCH 1/3] post: bump HSC BSPLINE PATH_DEV + TRACK_DEV from 0.1 to 0.5 mm MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The KUKA CNC ISG #HSC BSPLINE smoothing tolerances were emitted at 0.1 mm for both path and track deviation, which keeps the toolpath very tight to the programmed points but allows little smoothing. Bumping both to 0.5 mm gives the controller more latitude to round corners during high-speed contouring — appropriate for clay extrusion where the bead naturally smooths over sub-millimetre path detail and the sharp corners of dense toolpath sampling can otherwise show as accel spikes. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../KUKA_CNC_2_1_ISG_CCL_3DP_WIP_MS_S_INT_HSC_WAIT_S_DELAY.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PostProcessor/KUKA_CNC_2_1_ISG_CCL_3DP_WIP_MS_S_INT_HSC_WAIT_S_DELAY.py b/PostProcessor/KUKA_CNC_2_1_ISG_CCL_3DP_WIP_MS_S_INT_HSC_WAIT_S_DELAY.py index 5ad1e79..b5920a2 100644 --- a/PostProcessor/KUKA_CNC_2_1_ISG_CCL_3DP_WIP_MS_S_INT_HSC_WAIT_S_DELAY.py +++ b/PostProcessor/KUKA_CNC_2_1_ISG_CCL_3DP_WIP_MS_S_INT_HSC_WAIT_S_DELAY.py @@ -272,7 +272,7 @@ def set_move_type(self, move_type): self.addline("#MCS ON") elif move_type == self.MOVE_TYPE_HSC: # BSPLINE smoothing only wraps the curve-follow section - self.addline("#HSC ON [BSPLINE PATH_DEV 0.1000 TRACK_DEV 0.1000]") + self.addline("#HSC ON [BSPLINE PATH_DEV 0.5000 TRACK_DEV 0.5000]") # PTP doesn't require an explicit ON self.MoveType = move_type # --------- Helpers for Extruder OFF lead compensation --------- From 540b73ba2a33c18e41175a2a8ef53c3867120cdb Mon Sep 17 00:00:00 2001 From: Christophe Barlieb Date: Tue, 19 May 2026 22:23:53 +0200 Subject: [PATCH 2/3] =?UTF-8?q?post:=20retract=20spindle=20lookahead=20?= =?UTF-8?q?=E2=80=94=20stop=20S=20before=20lift-off,=20resume=20after=20pl?= =?UTF-8?q?unge?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a MoveJ arrives while the extruder is still on, treat it as the lift-off of a retract: back the S1 command up by RETRACT_LOOKAHEAD_POINTS (default 10) waypoints so clay flow has stopped before the nozzle leaves the print, and stash the active S value. The next MoveL after the retract re-issues that S value so the extruder is flowing again by the time the plunge lands at the first print point. This lets source G-code leave the spindle on continuously with a single M3 S at program start — the post inserts the needed S1 / S transitions around each retract automatically. Tracks the active extruder S value in RunCode (whenever S>1 is emitted) so the resume value reflects the most recent setpoint rather than a hardcoded constant. Co-Authored-By: Claude Opus 4.7 (1M context) --- ...G_CCL_3DP_WIP_MS_S_INT_HSC_WAIT_S_DELAY.py | 62 ++++++++++++++++++- 1 file changed, 59 insertions(+), 3 deletions(-) diff --git a/PostProcessor/KUKA_CNC_2_1_ISG_CCL_3DP_WIP_MS_S_INT_HSC_WAIT_S_DELAY.py b/PostProcessor/KUKA_CNC_2_1_ISG_CCL_3DP_WIP_MS_S_INT_HSC_WAIT_S_DELAY.py index b5920a2..d66891f 100644 --- a/PostProcessor/KUKA_CNC_2_1_ISG_CCL_3DP_WIP_MS_S_INT_HSC_WAIT_S_DELAY.py +++ b/PostProcessor/KUKA_CNC_2_1_ISG_CCL_3DP_WIP_MS_S_INT_HSC_WAIT_S_DELAY.py @@ -34,6 +34,14 @@ # EXTRUDER_OFF_DELAY_S seconds so the nozzle actually stops at the # intended end-of-path position (clay keeps flowing under pressure # for ~0.25 s after the command is issued) +# - Retract spindle lookahead: when a MoveJ (rapid) arrives while the +# extruder is still on, the post backs the S1 up by +# RETRACT_LOOKAHEAD_POINTS MoveL waypoints (so flow has stopped +# before the nozzle lifts off the print) and stashes the active S +# value. The next MoveL after the retract re-issues that S value, +# so the extruder is flowing again by the time the plunge completes +# and the first print point fires. Source G-code can leave the +# spindle on continuously with one M3 S at program start. # - Spindle decimal stripping: S.0 -> S for clean # G-code output # - MoveJ rapids emitted as G01 (linear) at F3000 (50 mm/s) instead @@ -136,11 +144,20 @@ class RobotPost(object): # Tune this based on your system (clay systems often 0.05–0.40s, but can be higher). EXTRUDER_OFF_DELAY_S = 0.25 + # ------------------ Retract spindle lookahead ------------------ + # Number of MoveL waypoints to back the S1 command up when a retract + # (MoveJ while extruder is on) is detected. Clay flow tapers over + # the last several print points; backing the S1 up by ~10 points + # has the bead pressure-drop complete before the lift-off move. + RETRACT_LOOKAHEAD_POINTS = 10 + # Internal state for lead compensation _EXTRUDER_ON = False _LAST_XYZ = None # last cartesian XYZ in program coordinates _LAST_F_MM_MIN = 3000.0 # last feed in mm/min (used if no new F is provided) _EXTRUDE_SEGS = [] # buffered extrusion-on linear segments for backtracking + _LAST_EXTRUDER_S = 500.0 # last S value with S > 1 (= resume value after a retract) + _PENDING_RESUME_S = None # if set, next MoveL emits this S value to resume after retract MOVE_TYPE_NONE = -1 MOVE_NAMES = [] @@ -351,7 +368,26 @@ def _apply_extruder_off_lead(self): self._EXTRUDER_ON = False return True - + def _inject_extruder_off_n_points_back(self): + """Insert an S1 line into PROG at the position of the MoveL that's + RETRACT_LOOKAHEAD_POINTS waypoints back from the most recent one. + Falls back to emitting S1 inline at the current PROG end when the + buffered segment list is shorter than the lookahead (= we don't + have enough printed waypoints to back up by N).""" + N = self.RETRACT_LOOKAHEAD_POINTS + if not self._EXTRUDE_SEGS or N < 1: + self.addline('S1') + self._EXTRUDE_SEGS = [] + return + idx = max(0, len(self._EXTRUDE_SEGS) - N) + line_i = self._EXTRUDE_SEGS[idx]['line_index'] + if line_i < 0 or line_i >= len(self.PROG): + self.addline('S1') + self._EXTRUDE_SEGS = [] + return + self.PROG.insert(line_i, 'S1') + self._EXTRUDE_SEGS = [] + # Feed rate (mm/min) applied to rapid (approach/retract) moves. The # stock RoboDK post emits G00 rapids, but for clay extrusion on the # CCL-ALTAR-01 cell we need a controlled linear speed on approach and @@ -363,6 +399,16 @@ def _apply_extruder_off_lead(self): def MoveJ(self, pose, joints, conf_RLF=None): """Add a joint movement (emitted as G01 at RAPID_FEED_MM_MIN for controlled approach/retract — see RAPID_FEED_MM_MIN above).""" + # Retract lookahead: a MoveJ arriving while the extruder is on is + # the lift-off of a retract — back the S1 up by + # RETRACT_LOOKAHEAD_POINTS so flow has stopped before the nozzle + # leaves the print. Stash the active S value so the next MoveL + # (= first print point after the plunge) can resume it. + if self._EXTRUDER_ON and self._PENDING_RESUME_S is None: + self._inject_extruder_off_n_points_back() + self._PENDING_RESUME_S = self._LAST_EXTRUDER_S + self._EXTRUDER_ON = False + self.set_move_type(self.MOVE_TYPE_MCS) self.addline('G01 ' + joints_2_str(joints) + (' F%d' % self.RAPID_FEED_MM_MIN)) @@ -372,17 +418,26 @@ def MoveJ(self, pose, joints, conf_RLF=None): def MoveL(self, pose, joints, conf_RLF=None): """Add a linear movement""" + # Retract resume: first MoveL after a retract sequence. Re-issue + # the stashed S value before this print point fires so the + # extruder is flowing again when we land. _EXTRUDER_ON flips back + # on here too so subsequent MoveL calls buffer for lookahead. + if self._PENDING_RESUME_S is not None: + self.addline('S%d' % int(round(self._PENDING_RESUME_S))) + self._PENDING_RESUME_S = None + self._EXTRUDER_ON = True + #----------------------------------------------------- # Update the P value to sync two robots (if required) sync_p = '' if self.P_VALUE is not None: if self.LAST_POSE is not None: - self.P_VALUE += distance(self.LAST_POSE.Pos(), pose.Pos()) + self.P_VALUE += distance(self.LAST_POSE.Pos(), pose.Pos()) else: self.P_VALUE = 0 sync_p = ' P%.3f' % self.P_VALUE #----------------------------------------------------- - + self.set_move_type(self.MOVE_TYPE_HSC) # Track feed (mm/min): if a new F is provided it overrides the last one _f = self._parse_F_mm_min(self.SPEED_F) @@ -574,6 +629,7 @@ def _strip_spindle_decimal(s): self._EXTRUDER_ON = False else: self._EXTRUDER_ON = True + self._LAST_EXTRUDER_S = _sval # remember for retract resume if is_function_call: code = code.replace(' ','_') From 0ce5741d592cf2875a26fe1e2c51e017221fd45d Mon Sep 17 00:00:00 2001 From: Christophe Barlieb Date: Tue, 19 May 2026 22:31:39 +0200 Subject: [PATCH 3/3] post: detect retract by MoveL feed-rate jump, not MoveJ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RoboDK's CNC machining import collapses G0 rapids into MoveL with a faster feed rate — it never invokes MoveJ for inner retracts. The previous lookahead implementation triggered on MoveJ and so silently did nothing on real CNC-imported curves. New trigger: a MoveL whose speed is more than RETRACT_SPEED_RATIO (default 1.3) × the previous MoveL's speed marks a retract entry; a MoveL whose speed has dropped back below the previous MoveL's speed by the same ratio marks the first print point after the plunge. The MoveJ-based trigger is kept as a defensive fallback for stations that do emit MoveJ for retracts. The N-back fallback in _inject_extruder_off_n_points_back was also hardened: when the buffered print pass is shorter than the lookahead (short contour), emit S1 inline rather than at the very start of the pass — turning the extruder off for the entire short contour would lose more than it saves. Co-Authored-By: Claude Opus 4.7 (1M context) --- ...G_CCL_3DP_WIP_MS_S_INT_HSC_WAIT_S_DELAY.py | 62 ++++++++++++++----- 1 file changed, 47 insertions(+), 15 deletions(-) diff --git a/PostProcessor/KUKA_CNC_2_1_ISG_CCL_3DP_WIP_MS_S_INT_HSC_WAIT_S_DELAY.py b/PostProcessor/KUKA_CNC_2_1_ISG_CCL_3DP_WIP_MS_S_INT_HSC_WAIT_S_DELAY.py index d66891f..48bf779 100644 --- a/PostProcessor/KUKA_CNC_2_1_ISG_CCL_3DP_WIP_MS_S_INT_HSC_WAIT_S_DELAY.py +++ b/PostProcessor/KUKA_CNC_2_1_ISG_CCL_3DP_WIP_MS_S_INT_HSC_WAIT_S_DELAY.py @@ -146,11 +146,17 @@ class RobotPost(object): # ------------------ Retract spindle lookahead ------------------ # Number of MoveL waypoints to back the S1 command up when a retract - # (MoveJ while extruder is on) is detected. Clay flow tapers over - # the last several print points; backing the S1 up by ~10 points - # has the bead pressure-drop complete before the lift-off move. + # is detected. Clay flow tapers over the last several print points; + # backing the S1 up by ~10 points has the bead pressure-drop complete + # before the lift-off move. RETRACT_LOOKAHEAD_POINTS = 10 + # Speed ratio that classifies the upcoming MoveL as travel/retract. + # RoboDK's CNC import emits G0 rapids as fast MoveL (e.g. F6000) and + # G1 prints as slower MoveL (e.g. F3000); a jump above this ratio + # is the trigger to back up the S1. + RETRACT_SPEED_RATIO = 1.3 + # Internal state for lead compensation _EXTRUDER_ON = False _LAST_XYZ = None # last cartesian XYZ in program coordinates @@ -158,6 +164,7 @@ class RobotPost(object): _EXTRUDE_SEGS = [] # buffered extrusion-on linear segments for backtracking _LAST_EXTRUDER_S = 500.0 # last S value with S > 1 (= resume value after a retract) _PENDING_RESUME_S = None # if set, next MoveL emits this S value to resume after retract + _LAST_MOVEL_SPEED = None # speed (mm/min) of the previous MoveL — used for retract feed-jump detection MOVE_TYPE_NONE = -1 MOVE_NAMES = [] @@ -371,15 +378,17 @@ def _apply_extruder_off_lead(self): def _inject_extruder_off_n_points_back(self): """Insert an S1 line into PROG at the position of the MoveL that's RETRACT_LOOKAHEAD_POINTS waypoints back from the most recent one. - Falls back to emitting S1 inline at the current PROG end when the - buffered segment list is shorter than the lookahead (= we don't - have enough printed waypoints to back up by N).""" + If fewer than N waypoints are buffered (= the current print pass + is shorter than the desired lookahead), fall back to emitting S1 + inline rather than at the very start of the pass — turning the + extruder off for the entire short contour would lose more than + it saves.""" N = self.RETRACT_LOOKAHEAD_POINTS - if not self._EXTRUDE_SEGS or N < 1: + if N < 1 or not self._EXTRUDE_SEGS or len(self._EXTRUDE_SEGS) < N: self.addline('S1') self._EXTRUDE_SEGS = [] return - idx = max(0, len(self._EXTRUDE_SEGS) - N) + idx = len(self._EXTRUDE_SEGS) - N line_i = self._EXTRUDE_SEGS[idx]['line_index'] if line_i < 0 or line_i >= len(self.PROG): self.addline('S1') @@ -418,11 +427,33 @@ def MoveJ(self, pose, joints, conf_RLF=None): def MoveL(self, pose, joints, conf_RLF=None): """Add a linear movement""" - # Retract resume: first MoveL after a retract sequence. Re-issue - # the stashed S value before this print point fires so the - # extruder is flowing again when we land. _EXTRUDER_ON flips back - # on here too so subsequent MoveL calls buffer for lookahead. - if self._PENDING_RESUME_S is not None: + # ----- Retract spindle lookahead ----- + # RoboDK's CNC import sends all moves (including rapids converted + # from G0) as MoveL, distinguished only by feed rate — typically + # F3000 for prints and F6000 for travel/retract/plunge. Detect + # retract entry/exit by comparing the incoming MoveL's speed to + # the previous MoveL's speed. + current_speed = self._LAST_F_MM_MIN + last_speed = self._LAST_MOVEL_SPEED + + # Entry: speed jumped above RETRACT_SPEED_RATIO × last MoveL while + # extruding → back up the S1 by N points, stash the resume value. + if (self._EXTRUDER_ON + and self._PENDING_RESUME_S is None + and last_speed is not None + and last_speed > 0 + and current_speed > last_speed * self.RETRACT_SPEED_RATIO): + self._inject_extruder_off_n_points_back() + self._PENDING_RESUME_S = self._LAST_EXTRUDER_S + self._EXTRUDER_ON = False + + # Exit: a resume is pending and the speed has dropped back to a + # print-like value (= we're at the first MoveL after the plunge). + # Emit the stashed S before the MoveL line so the extruder is + # flowing again as the print point lands. + elif (self._PENDING_RESUME_S is not None + and last_speed is not None + and current_speed * self.RETRACT_SPEED_RATIO < last_speed): self.addline('S%d' % int(round(self._PENDING_RESUME_S))) self._PENDING_RESUME_S = None self._EXTRUDER_ON = True @@ -460,11 +491,12 @@ def MoveL(self, pose, joints, conf_RLF=None): self._EXTRUDE_SEGS.append({'p0': self._LAST_XYZ, 'p1': _xyz, 'len': L, 'line_index': len(self.PROG)-1}) self._LAST_XYZ = _xyz - + self._LAST_MOVEL_SPEED = current_speed + # Remember the last pose self.LAST_POSE = pose self.LAST_JOINTS = joints - self.LAST_CONFIG = conf_RLF + self.LAST_CONFIG = conf_RLF def MoveC(self, pose1, joints1, pose2, joints2, conf_RLF_1=None, conf_RLF_2=None): """Add a circular movement"""