From 9ad22dcc0e4c2d7babd17523fde83931e974ebfb Mon Sep 17 00:00:00 2001 From: David Kaufman Date: Thu, 21 Aug 2025 09:34:46 -0400 Subject: [PATCH 01/12] [CAM] Add feed rate percent to ramp dressup --- src/Mod/CAM/Path/Dressup/Gui/RampEntry.py | 30 ++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/src/Mod/CAM/Path/Dressup/Gui/RampEntry.py b/src/Mod/CAM/Path/Dressup/Gui/RampEntry.py index 02c19c7be0..044fb53684 100644 --- a/src/Mod/CAM/Path/Dressup/Gui/RampEntry.py +++ b/src/Mod/CAM/Path/Dressup/Gui/RampEntry.py @@ -187,6 +187,14 @@ class ObjectDressup: "FeedRate", QT_TRANSLATE_NOOP("App::Property", "Which feed rate to use for ramping"), ) + obj.addProperty( + "App::PropertyPercent", + "FeedRatePercent", + "FeedRate", + QT_TRANSLATE_NOOP( + "App::Property", "Percentage modifier to apply to feed rate while ramping" + ), + ) obj.addProperty( "App::PropertySpeed", "CustomFeedRate", @@ -298,15 +306,31 @@ class ObjectDressup: if obj.RampFeedRate == "Custom": obj.setEditorMode("CustomFeedRate", 0) + if hasattr(obj, "FeedRatePercent"): + obj.setEditorMode("FeedRatePercent", 2) else: obj.setEditorMode("CustomFeedRate", 2) + if hasattr(obj, "FeedRatePercent"): + obj.setEditorMode("FeedRatePercent", 0) def onDocumentRestored(self, obj): + if not hasattr(obj, "FeedRatePercent"): + obj.addProperty( + "App::PropertyPercent", + "FeedRatePercent", + "FeedRate", + QT_TRANSLATE_NOOP( + "App::Property", "Percentage modifier to apply to feed rate while ramping" + ), + ) + obj.FeedRatePercent = 100 + self.setEditorProperties(obj) def setup(self, obj): obj.Angle = 60 obj.Method = 2 + obj.FeedRatePercent = 100 if PathDressup.baseOp(obj.Base).StartDepth is not None: obj.DressupStartDepth = PathDressup.baseOp(obj.Base).StartDepth @@ -685,11 +709,11 @@ class ObjectDressup: vertRapid = tc.VertRapid.Value if obj.RampFeedRate == "Horizontal Feed Rate": - rampFeed = horizFeed + rampFeed = horizFeed * obj.FeedRatePercent / 100 elif obj.RampFeedRate == "Vertical Feed Rate": - rampFeed = vertFeed + rampFeed = vertFeed * obj.FeedRatePercent / 100 elif obj.RampFeedRate == "Ramp Feed Rate": - rampFeed = math.sqrt(pow(vertFeed, 2) + pow(horizFeed, 2)) + rampFeed = (math.sqrt(pow(vertFeed, 2) + pow(horizFeed, 2))) * obj.FeedRatePercent / 100 else: rampFeed = obj.CustomFeedRate.Value From 9c5e8f834044d08e5365644bcf7051239469f025 Mon Sep 17 00:00:00 2001 From: David Kaufman Date: Thu, 21 Aug 2025 17:20:59 -0400 Subject: [PATCH 02/12] [CAM] Add feed rate percent to lead in/out dressup --- .../Resources/panels/DressUpLeadInOutEdit.ui | 29 +++++++++++++++++++ src/Mod/CAM/Path/Dressup/Gui/LeadInOut.py | 27 ++++++++++++++++- src/Mod/CAM/Path/Dressup/Gui/RampEntry.py | 4 +-- 3 files changed, 57 insertions(+), 3 deletions(-) diff --git a/src/Mod/CAM/Gui/Resources/panels/DressUpLeadInOutEdit.ui b/src/Mod/CAM/Gui/Resources/panels/DressUpLeadInOutEdit.ui index 26e44139c1..f6e956cfd3 100644 --- a/src/Mod/CAM/Gui/Resources/panels/DressUpLeadInOutEdit.ui +++ b/src/Mod/CAM/Gui/Resources/panels/DressUpLeadInOutEdit.ui @@ -175,6 +175,35 @@ + + + Feed Rate Percent + + + + + + + Horizontal feed rate during entrances and exits, as a percentage of the normal feed rate. + + + 0 + + + 1.000000000000000 + + + 999999.000000000000000 + + + 10.000000000000000 + + + + + + + diff --git a/src/Mod/CAM/Path/Dressup/Gui/LeadInOut.py b/src/Mod/CAM/Path/Dressup/Gui/LeadInOut.py index 3f301d8899..3cee50e06f 100644 --- a/src/Mod/CAM/Path/Dressup/Gui/LeadInOut.py +++ b/src/Mod/CAM/Path/Dressup/Gui/LeadInOut.py @@ -162,6 +162,15 @@ class ObjectDressup: "Path Lead-out", QT_TRANSLATE_NOOP("App::Property", "Move end point"), ) + obj.addProperty( + "App::PropertyInteger", + "FeedRatePercent", + "Path", + QT_TRANSLATE_NOOP( + "App::Property", + "Percentage modifier to apply to feed rate while entering and exiting", + ), + ) obj.Proxy = self def dumps(self): @@ -179,6 +188,7 @@ class ObjectDressup: obj.LeadOut = True obj.AngleIn = 90 obj.AngleOut = 90 + obj.FeedRatePercent = 100 obj.InvertIn = False obj.InvertOut = False obj.RapidPlunge = False @@ -251,7 +261,7 @@ class ObjectDressup: ) return - self.horizFeed = self.toolController.HorizFeed.Value + self.horizFeed = self.toolController.HorizFeed.Value * obj.FeedRatePercent / 100 self.vertFeed = self.toolController.VertFeed.Value obj.Path = self.generateLeadInOutCurve(obj) @@ -419,6 +429,18 @@ class ObjectDressup: obj.setEditorMode(k + "In", 2 if obj.StyleIn in v else 0) obj.setEditorMode(k + "Out", 2 if obj.StyleOut in v else 0) + if not hasattr(obj, "FeedRatePercent"): + obj.addProperty( + "App::PropertyInteger", + "FeedRatePercent", + "Path", + QT_TRANSLATE_NOOP( + "App::Property", + "Percentage modifier to apply to feed rate while entering and exiting", + ), + ) + obj.FeedRatePercent = 100 + # Get direction for lead-in/lead-out in XY plane def getLeadDir(self, obj, invert=False): output = math.pi / 2 @@ -1188,6 +1210,9 @@ class TaskDressupLeadInOut(SimpleEditPanel): def setupSpinBoxes(self): self.connectWidget("InvertIn", self.form.chkInvertDirectionIn) self.connectWidget("InvertOut", self.form.chkInvertDirectionOut) + self.connectWidget("FeedRatePercent", self.form.dspFeedRatePercent) + self.connectWidget("PercentageRadiusIn", self.form.dspPercentageRadiusIn) + self.connectWidget("PercentageRadiusOut", self.form.dspPercentageRadiusOut) self.connectWidget("StyleIn", self.form.cboStyleIn) self.connectWidget("StyleOut", self.form.cboStyleOut) self.radiusIn = PathGuiUtil.QuantitySpinBox(self.form.dspRadiusIn, self.obj, "RadiusIn") diff --git a/src/Mod/CAM/Path/Dressup/Gui/RampEntry.py b/src/Mod/CAM/Path/Dressup/Gui/RampEntry.py index 044fb53684..e13f76f56c 100644 --- a/src/Mod/CAM/Path/Dressup/Gui/RampEntry.py +++ b/src/Mod/CAM/Path/Dressup/Gui/RampEntry.py @@ -188,7 +188,7 @@ class ObjectDressup: QT_TRANSLATE_NOOP("App::Property", "Which feed rate to use for ramping"), ) obj.addProperty( - "App::PropertyPercent", + "App::PropertyInteger", "FeedRatePercent", "FeedRate", QT_TRANSLATE_NOOP( @@ -316,7 +316,7 @@ class ObjectDressup: def onDocumentRestored(self, obj): if not hasattr(obj, "FeedRatePercent"): obj.addProperty( - "App::PropertyPercent", + "App::PropertyInteger", "FeedRatePercent", "FeedRate", QT_TRANSLATE_NOOP( From 093152b1dcf7d0de1c2c51f33db597405d31e05b Mon Sep 17 00:00:00 2001 From: David Kaufman Date: Fri, 22 Aug 2025 14:25:56 -0400 Subject: [PATCH 03/12] [CAM] separate controls for feed rate percent entrance vs exit --- .../Resources/panels/DressUpLeadInOutEdit.ui | 37 ++++++- src/Mod/CAM/Path/Dressup/Gui/LeadInOut.py | 100 ++++++++++++------ 2 files changed, 101 insertions(+), 36 deletions(-) diff --git a/src/Mod/CAM/Gui/Resources/panels/DressUpLeadInOutEdit.ui b/src/Mod/CAM/Gui/Resources/panels/DressUpLeadInOutEdit.ui index f6e956cfd3..39211d2045 100644 --- a/src/Mod/CAM/Gui/Resources/panels/DressUpLeadInOutEdit.ui +++ b/src/Mod/CAM/Gui/Resources/panels/DressUpLeadInOutEdit.ui @@ -177,14 +177,14 @@ - Feed Rate Percent + Entrance Feed Rate Percent - + - Horizontal feed rate during entrances and exits, as a percentage of the normal feed rate. + Feed rate to use when entering a cut, as a percentage of the normal feed rate. 0 @@ -196,7 +196,7 @@ 999999.000000000000000 - 10.000000000000000 + 5.000000000000000 @@ -204,6 +204,35 @@ + + + Exit Feed Rate Percent + + + + + + + Feed rate to use when exiting a cut, as a percentage of the normal feed rate. + + + 0 + + + 1.000000000000000 + + + 999999.000000000000000 + + + 5.000000000000000 + + + + + + + diff --git a/src/Mod/CAM/Path/Dressup/Gui/LeadInOut.py b/src/Mod/CAM/Path/Dressup/Gui/LeadInOut.py index 3cee50e06f..0f8df380e3 100644 --- a/src/Mod/CAM/Path/Dressup/Gui/LeadInOut.py +++ b/src/Mod/CAM/Path/Dressup/Gui/LeadInOut.py @@ -164,11 +164,20 @@ class ObjectDressup: ) obj.addProperty( "App::PropertyInteger", - "FeedRatePercent", + "FeedRatePercentIn", "Path", QT_TRANSLATE_NOOP( "App::Property", - "Percentage modifier to apply to feed rate while entering and exiting", + "Percentage modifier to apply to feed rate while entering the cut", + ), + ) + obj.addProperty( + "App::PropertyInteger", + "FeedRatePercentOut", + "Path", + QT_TRANSLATE_NOOP( + "App::Property", + "Percentage modifier to apply to feed rate while exiting the cut", ), ) obj.Proxy = self @@ -188,7 +197,8 @@ class ObjectDressup: obj.LeadOut = True obj.AngleIn = 90 obj.AngleOut = 90 - obj.FeedRatePercent = 100 + obj.FeedRatePercentIn = 100 + obj.FeedRatePercentOut = 100 obj.InvertIn = False obj.InvertOut = False obj.RapidPlunge = False @@ -261,8 +271,10 @@ class ObjectDressup: ) return - self.horizFeed = self.toolController.HorizFeed.Value * obj.FeedRatePercent / 100 + self.horizFeed = self.toolController.HorizFeed.Value self.vertFeed = self.toolController.VertFeed.Value + self.entranceFeed = self.horizFeed * obj.FeedRatePercentIn / 100 + self.exitFeed = self.horizFeed * obj.FeedRatePercentOut / 100 obj.Path = self.generateLeadInOutCurve(obj) @@ -429,17 +441,28 @@ class ObjectDressup: obj.setEditorMode(k + "In", 2 if obj.StyleIn in v else 0) obj.setEditorMode(k + "Out", 2 if obj.StyleOut in v else 0) - if not hasattr(obj, "FeedRatePercent"): + if not hasattr(obj, "FeedRatePercentIn"): obj.addProperty( "App::PropertyInteger", - "FeedRatePercent", + "FeedRatePercentIn", "Path", QT_TRANSLATE_NOOP( "App::Property", - "Percentage modifier to apply to feed rate while entering and exiting", + "Percentage modifier to apply to feed rate while entering the cut", ), ) - obj.FeedRatePercent = 100 + obj.FeedRatePercentIn = 100 + if not hasattr(obj, "FeedRatePercentOut"): + obj.addProperty( + "App::PropertyInteger", + "FeedRatePercentOut", + "Path", + QT_TRANSLATE_NOOP( + "App::Property", + "Percentage modifier to apply to feed rate while exiting the cut", + ), + ) + obj.FeedRatePercentOut = 100 # Get direction for lead-in/lead-out in XY plane def getLeadDir(self, obj, invert=False): @@ -521,14 +544,14 @@ class ObjectDressup: return App.Vector(math.cos(angle), math.sin(angle), 0) # Create arc in XY plane with automatic detection G2|G3 - def createArcMove(self, obj, begin, end, offset, invert=False): + def createArcMove(self, obj, begin, end, offset, invert, feedRate): param = { "X": end.x, "Y": end.y, "Z": end.z, "I": offset.x, "J": offset.y, - "F": self.horizFeed, + "F": feedRate, } if self.getLeadDir(obj, invert) > 0: command = PathLanguage.MoveArcCCW(begin, "G3", param) @@ -538,8 +561,8 @@ class ObjectDressup: return command # Create arc in XY plane with manually set G2|G3 - def createArcMoveN(self, obj, begin, end, offset, cmdName): - param = {"X": end.x, "Y": end.y, "I": offset.x, "J": offset.y, "F": self.horizFeed} + def createArcMoveN(self, obj, begin, end, offset, cmdName, feedRate): + param = {"X": end.x, "Y": end.y, "I": offset.x, "J": offset.y, "F": feedRate} if cmdName == "G2": command = PathLanguage.MoveArcCW(begin, cmdName, param) else: @@ -548,8 +571,8 @@ class ObjectDressup: return command # Create line movement G1 - def createStraightMove(self, obj, begin, end): - param = {"X": end.x, "Y": end.y, "Z": end.z, "F": self.horizFeed} + def createStraightMove(self, obj, begin, end, feedRate): + param = {"X": end.x, "Y": end.y, "Z": end.z, "F": feedRate} command = PathLanguage.MoveStraight(begin, "G1", param) return command @@ -571,7 +594,7 @@ class ObjectDressup: return stepAngle # Create vertical arc with move Down by line segments - def createArcZMoveDown(self, obj, begin, end, radius): + def createArcZMoveDown(self, obj, begin, end, radius, feedRate): commands = [] angle = math.acos((radius - begin.z + end.z) / radius) # start angle stepAngle = self.getStepAngleArcZ(obj, radius) @@ -590,7 +613,7 @@ class ObjectDressup: else: # exclude error of calculations for the last iteration iterEnd = copy.copy(end) - param = {"X": iterEnd.x, "Y": iterEnd.y, "Z": iterEnd.z, "F": self.horizFeed} + param = {"X": iterEnd.x, "Y": iterEnd.y, "Z": iterEnd.z, "F": feedRate} commands.append(PathLanguage.MoveStraight(iterBegin, "G1", param)) iterBegin = copy.copy(iterEnd) iter += 1 @@ -598,7 +621,7 @@ class ObjectDressup: return commands # Create vertical arc with move Up by line segments - def createArcZMoveUp(self, obj, begin, end, radius): + def createArcZMoveUp(self, obj, begin, end, radius, feedRate): commands = [] angleMax = math.acos((radius - end.z + begin.z) / radius) # finish angle stepAngle = self.getStepAngleArcZ(obj, radius) @@ -618,7 +641,7 @@ class ObjectDressup: else: # exclude the error of calculations of the last point iterEnd = copy.copy(end) - param = {"X": iterEnd.x, "Y": iterEnd.y, "Z": iterEnd.z, "F": self.horizFeed} + param = {"X": iterEnd.x, "Y": iterEnd.y, "Z": iterEnd.z, "F": feedRate} commands.append(PathLanguage.MoveStraight(iterBegin, "G1", param)) iterBegin = copy.copy(iterEnd) iter += 1 @@ -676,7 +699,11 @@ class ObjectDressup: arcBegin = begin + tangent + normal arcCenter = begin + normalMax arcOffset = arcCenter - arcBegin - lead.append(self.createArcMove(obj, arcBegin, begin, arcOffset, obj.InvertIn)) + lead.append( + self.createArcMove( + obj, arcBegin, begin, arcOffset, obj.InvertIn, self.entranceFeed + ) + ) # prepend "Line" style lead-in - line in XY # Line3d the same as Line, but increased Z start point @@ -690,7 +717,7 @@ class ObjectDressup: * normalLength ) lineBegin = begin + tangent + normal - lead.append(self.createStraightMove(obj, lineBegin, begin)) + lead.append(self.createStraightMove(obj, lineBegin, begin, self.entranceFeed)) # prepend "LineZ" style lead-in - vertical inclined line # Should be apply only on straight Path segment @@ -705,7 +732,7 @@ class ObjectDressup: tangent = -self.angleToVector(angleTangent) * tangentLength normal = App.Vector(0, 0, normalLength) lineBegin = begin + tangent + normal - lead.append(self.createStraightMove(obj, lineBegin, begin)) + lead.append(self.createStraightMove(obj, lineBegin, begin, self.entranceFeed)) # prepend "ArcZ" style lead-in - vertical Arc # Should be apply only on straight Path segment @@ -724,7 +751,9 @@ class ObjectDressup: tangent = -self.angleToVector(angleTangent) * tangentLength normal = App.Vector(0, 0, normalLength) arcBegin = begin + tangent + normal - lead.extend(self.createArcZMoveDown(obj, arcBegin, begin, arcRadius)) + lead.extend( + self.createArcZMoveDown(obj, arcBegin, begin, arcRadius, self.entranceFeed) + ) # replace 'begin' position by first lead-in command begin = lead[0].positionBegin() @@ -764,7 +793,7 @@ class ObjectDressup: travelToStart = self.getTravelStart(obj, begin, first, outInstrPrev) else: # exclude any lead-in commands - param = {"X": begin.x, "Y": begin.y, "Z": begin.z, "F": self.horizFeed} + param = {"X": begin.x, "Y": begin.y, "Z": begin.z, "F": self.entranceFeed} travelToStart = [PathLanguage.MoveStraight(None, "G01", param)] lead = travelToStart + lead @@ -812,7 +841,11 @@ class ObjectDressup: * normalLength ) arcEnd = end + tangent + normal - lead.append(self.createArcMove(obj, end, arcEnd, normalMax, obj.InvertOut)) + lead.append( + self.createArcMove( + obj, end, arcEnd, normalMax, obj.InvertOut, self.exitFeed + ) + ) # append "Line" style lead-out # Line3d the same as Line, but increased Z start point @@ -826,7 +859,7 @@ class ObjectDressup: * normalLength ) lineEnd = end + tangent + normal - lead.append(self.createStraightMove(obj, end, lineEnd)) + lead.append(self.createStraightMove(obj, end, lineEnd, self.exitFeed)) # append "LineZ" style lead-out - vertical inclined line # Should be apply only on straight Path segment @@ -841,7 +874,7 @@ class ObjectDressup: tangent = self.angleToVector(angleTangent) * tangentLength normal = App.Vector(0, 0, normalLength) lineEnd = end + tangent + normal - lead.append(self.createStraightMove(obj, end, lineEnd)) + lead.append(self.createStraightMove(obj, end, lineEnd, self.exitFeed)) # prepend "ArcZ" style lead-out - vertical Arc # Should be apply only on straight Path segment @@ -860,7 +893,9 @@ class ObjectDressup: tangent = self.angleToVector(angleTangent) * tangentLength normal = App.Vector(0, 0, normalLength) arcEnd = end + tangent + normal - lead.extend(self.createArcZMoveUp(obj, end, arcEnd, arcRadius)) + lead.extend( + self.createArcZMoveUp(obj, end, arcEnd, arcRadius, self.exitFeed) + ) if obj.StyleOut in ("Arc3d", "Line3d"): # Up Z end point for Arc3d and Line3d @@ -1018,7 +1053,7 @@ class ObjectDressup: n = math.hypot(v.x, v.y) u = v / n cutEnd = begin + u * newLength - command = self.createStraightMove(obj, begin, cutEnd) + command = self.createStraightMove(obj, begin, cutEnd, self.horizFeed) # Cut arc move from begin elif instr.isArc(): @@ -1033,7 +1068,7 @@ class ObjectDressup: normal = self.angleToVector(angleTangent + self.getPathDir(obj)) * normalLength arcEnd = arcBegin + tangent + normal cmdName = "G2" if instr.isCW() else "G3" - command = self.createArcMoveN(obj, arcBegin, arcEnd, arcOffset, cmdName) + command = self.createArcMoveN(obj, arcBegin, arcEnd, arcOffset, cmdName, self.horizFeed) return command @@ -1047,7 +1082,7 @@ class ObjectDressup: n = math.hypot(v.x, v.y) u = v / n newBegin = end - u * newLength - command = self.createStraightMove(obj, newBegin, end) + command = self.createStraightMove(obj, newBegin, end, self.horizFeed) return command # Cut arc move from begin @@ -1064,7 +1099,7 @@ class ObjectDressup: arcBegin = arcEnd + tangent + normal arcOffset = arcCenter - arcBegin cmdName = "G2" if instr.isCW() else "G3" - command = self.createArcMoveN(obj, arcBegin, arcEnd, arcOffset, cmdName) + command = self.createArcMoveN(obj, arcBegin, arcEnd, arcOffset, cmdName, self.horizFeed) return command return None @@ -1210,7 +1245,8 @@ class TaskDressupLeadInOut(SimpleEditPanel): def setupSpinBoxes(self): self.connectWidget("InvertIn", self.form.chkInvertDirectionIn) self.connectWidget("InvertOut", self.form.chkInvertDirectionOut) - self.connectWidget("FeedRatePercent", self.form.dspFeedRatePercent) + self.connectWidget("FeedRatePercentIn", self.form.dspFeedRatePercentIn) + self.connectWidget("FeedRatePercentOut", self.form.dspFeedRatePercentOut) self.connectWidget("PercentageRadiusIn", self.form.dspPercentageRadiusIn) self.connectWidget("PercentageRadiusOut", self.form.dspPercentageRadiusOut) self.connectWidget("StyleIn", self.form.cboStyleIn) From f7dc85ef29ce767f09685dc8a14336fb6b4c8bae Mon Sep 17 00:00:00 2001 From: David Kaufman Date: Wed, 3 Sep 2025 12:42:45 -0400 Subject: [PATCH 04/12] update lead in/out to use feed rate from tool controller --- .../Resources/panels/DressUpLeadInOutEdit.ui | 58 ------------------ src/Mod/CAM/Path/Dressup/Gui/LeadInOut.py | 49 +-------------- src/Mod/CAM/Path/Tool/Controller.py | 59 +++++++++++++++++++ 3 files changed, 61 insertions(+), 105 deletions(-) diff --git a/src/Mod/CAM/Gui/Resources/panels/DressUpLeadInOutEdit.ui b/src/Mod/CAM/Gui/Resources/panels/DressUpLeadInOutEdit.ui index 39211d2045..26e44139c1 100644 --- a/src/Mod/CAM/Gui/Resources/panels/DressUpLeadInOutEdit.ui +++ b/src/Mod/CAM/Gui/Resources/panels/DressUpLeadInOutEdit.ui @@ -175,64 +175,6 @@ - - - Entrance Feed Rate Percent - - - - - - - Feed rate to use when entering a cut, as a percentage of the normal feed rate. - - - 0 - - - 1.000000000000000 - - - 999999.000000000000000 - - - 5.000000000000000 - - - - - - - - - - Exit Feed Rate Percent - - - - - - - Feed rate to use when exiting a cut, as a percentage of the normal feed rate. - - - 0 - - - 1.000000000000000 - - - 999999.000000000000000 - - - 5.000000000000000 - - - - - - - diff --git a/src/Mod/CAM/Path/Dressup/Gui/LeadInOut.py b/src/Mod/CAM/Path/Dressup/Gui/LeadInOut.py index 0f8df380e3..3520545e8b 100644 --- a/src/Mod/CAM/Path/Dressup/Gui/LeadInOut.py +++ b/src/Mod/CAM/Path/Dressup/Gui/LeadInOut.py @@ -162,24 +162,6 @@ class ObjectDressup: "Path Lead-out", QT_TRANSLATE_NOOP("App::Property", "Move end point"), ) - obj.addProperty( - "App::PropertyInteger", - "FeedRatePercentIn", - "Path", - QT_TRANSLATE_NOOP( - "App::Property", - "Percentage modifier to apply to feed rate while entering the cut", - ), - ) - obj.addProperty( - "App::PropertyInteger", - "FeedRatePercentOut", - "Path", - QT_TRANSLATE_NOOP( - "App::Property", - "Percentage modifier to apply to feed rate while exiting the cut", - ), - ) obj.Proxy = self def dumps(self): @@ -197,8 +179,6 @@ class ObjectDressup: obj.LeadOut = True obj.AngleIn = 90 obj.AngleOut = 90 - obj.FeedRatePercentIn = 100 - obj.FeedRatePercentOut = 100 obj.InvertIn = False obj.InvertOut = False obj.RapidPlunge = False @@ -273,8 +253,8 @@ class ObjectDressup: self.horizFeed = self.toolController.HorizFeed.Value self.vertFeed = self.toolController.VertFeed.Value - self.entranceFeed = self.horizFeed * obj.FeedRatePercentIn / 100 - self.exitFeed = self.horizFeed * obj.FeedRatePercentOut / 100 + self.entranceFeed = self.toolController.LeadInFeed.Value + self.exitFeed = self.toolController.LeadOutFeed.Value obj.Path = self.generateLeadInOutCurve(obj) @@ -441,29 +421,6 @@ class ObjectDressup: obj.setEditorMode(k + "In", 2 if obj.StyleIn in v else 0) obj.setEditorMode(k + "Out", 2 if obj.StyleOut in v else 0) - if not hasattr(obj, "FeedRatePercentIn"): - obj.addProperty( - "App::PropertyInteger", - "FeedRatePercentIn", - "Path", - QT_TRANSLATE_NOOP( - "App::Property", - "Percentage modifier to apply to feed rate while entering the cut", - ), - ) - obj.FeedRatePercentIn = 100 - if not hasattr(obj, "FeedRatePercentOut"): - obj.addProperty( - "App::PropertyInteger", - "FeedRatePercentOut", - "Path", - QT_TRANSLATE_NOOP( - "App::Property", - "Percentage modifier to apply to feed rate while exiting the cut", - ), - ) - obj.FeedRatePercentOut = 100 - # Get direction for lead-in/lead-out in XY plane def getLeadDir(self, obj, invert=False): output = math.pi / 2 @@ -1245,8 +1202,6 @@ class TaskDressupLeadInOut(SimpleEditPanel): def setupSpinBoxes(self): self.connectWidget("InvertIn", self.form.chkInvertDirectionIn) self.connectWidget("InvertOut", self.form.chkInvertDirectionOut) - self.connectWidget("FeedRatePercentIn", self.form.dspFeedRatePercentIn) - self.connectWidget("FeedRatePercentOut", self.form.dspFeedRatePercentOut) self.connectWidget("PercentageRadiusIn", self.form.dspPercentageRadiusIn) self.connectWidget("PercentageRadiusOut", self.form.dspPercentageRadiusOut) self.connectWidget("StyleIn", self.form.cboStyleIn) diff --git a/src/Mod/CAM/Path/Tool/Controller.py b/src/Mod/CAM/Path/Tool/Controller.py index d76b8f9c07..133a7582eb 100644 --- a/src/Mod/CAM/Path/Tool/Controller.py +++ b/src/Mod/CAM/Path/Tool/Controller.py @@ -106,6 +106,31 @@ class ToolController: "Rapid", QT_TRANSLATE_NOOP("App::Property", "Rapid rate for horizontal moves"), ) + + obj.addProperty( + "App::PropertySpeed", + "RampFeed", + "Feed", + QT_TRANSLATE_NOOP("App::Property", "Feed rate for ramp moves"), + ) + obj.setExpression("RampFeed", "HorizFeed") + + obj.addProperty( + "App::PropertySpeed", + "LeadInFeed", + "Feed", + QT_TRANSLATE_NOOP("App::Property", "Feed rate for lead-in moves"), + ) + obj.setExpression("LeadInFeed", "HorizFeed") + + obj.addProperty( + "App::PropertySpeed", + "LeadOutFeed", + "Feed", + QT_TRANSLATE_NOOP("App::Property", "Feed rate for lead-out moves"), + ) + obj.setExpression("LeadOutFeed", "HorizFeed") + obj.setEditorMode("Placement", 2) for n in self.propertyEnumerations(): @@ -166,6 +191,40 @@ class ToolController: obj.setEditorMode("Placement", 2) + needsRecompute = False + if not hasattr(obj, "RampFeed"): + obj.addProperty( + "App::PropertySpeed", + "RampFeed", + "Feed", + QT_TRANSLATE_NOOP("App::Property", "Feed rate for ramp moves"), + ) + obj.setExpression("RampFeed", "HorizFeed") + needsRecompute = True + + if not hasattr(obj, "LeadInFeed"): + obj.addProperty( + "App::PropertySpeed", + "LeadInFeed", + "Feed", + QT_TRANSLATE_NOOP("App::Property", "Feed rate for lead-in moves"), + ) + obj.setExpression("LeadInFeed", "HorizFeed") + needsRecompute = True + + if not hasattr(obj, "LeadOutFeed"): + obj.addProperty( + "App::PropertySpeed", + "LeadOutFeed", + "Feed", + QT_TRANSLATE_NOOP("App::Property", "Feed rate for lead-out moves"), + ) + obj.setExpression("LeadOutFeed", "HorizFeed") + needsRecompute = True + + if needsRecompute: + obj.recompute() + def onDelete(self, obj, arg2=None): if hasattr(obj.Tool, "InList") and len(obj.Tool.InList) == 1: if hasattr(obj.Tool.Proxy, "onDelete"): From d1d6846af02ee7770fdde479450b0d10e819f38c Mon Sep 17 00:00:00 2001 From: David Kaufman Date: Thu, 4 Sep 2025 17:54:07 -0400 Subject: [PATCH 05/12] [CAM] migrate ramp and lead in/out feed rates to the tool controller --- src/Mod/CAM/Path/Dressup/Gui/RampEntry.py | 85 +++++---------------- src/Mod/CAM/Path/Main/Job.py | 4 +- src/Mod/CAM/Path/Op/Gui/Base.py | 16 +--- src/Mod/CAM/Path/Tool/Controller.py | 93 ++++++++++++++++++++++- 4 files changed, 114 insertions(+), 84 deletions(-) diff --git a/src/Mod/CAM/Path/Dressup/Gui/RampEntry.py b/src/Mod/CAM/Path/Dressup/Gui/RampEntry.py index e13f76f56c..6abc8f7f07 100644 --- a/src/Mod/CAM/Path/Dressup/Gui/RampEntry.py +++ b/src/Mod/CAM/Path/Dressup/Gui/RampEntry.py @@ -181,26 +181,6 @@ class ObjectDressup: "Path", QT_TRANSLATE_NOOP("App::Property", "Ramping Method"), ) - obj.addProperty( - "App::PropertyEnumeration", - "RampFeedRate", - "FeedRate", - QT_TRANSLATE_NOOP("App::Property", "Which feed rate to use for ramping"), - ) - obj.addProperty( - "App::PropertyInteger", - "FeedRatePercent", - "FeedRate", - QT_TRANSLATE_NOOP( - "App::Property", "Percentage modifier to apply to feed rate while ramping" - ), - ) - obj.addProperty( - "App::PropertySpeed", - "CustomFeedRate", - "FeedRate", - QT_TRANSLATE_NOOP("App::Property", "Custom feed rate"), - ) obj.addProperty( "App::PropertyBool", "UseStartDepth", @@ -254,21 +234,6 @@ class ObjectDressup: (translate("CAM_DressupRampEntry", "RampMethod3"), "RampMethod3"), (translate("CAM_DressupRampEntry", "Helix"), "Helix"), ], - "RampFeedRate": [ - ( - translate("CAM_DressupRampEntry", "Horizontal Feed Rate"), - "Horizontal Feed Rate", - ), - ( - translate("CAM_DressupRampEntry", "Vertical Feed Rate"), - "Vertical Feed Rate", - ), - ( - translate("CAM_DressupRampEntry", "Ramp Feed Rate"), - "Ramp Feed Rate", - ), - (translate("CAM_DressupRampEntry", "Custom"), "Custom"), - ], } if dataType == "raw": @@ -292,7 +257,7 @@ class ObjectDressup: return None def onChanged(self, obj, prop): - if prop in ["RampFeedRate", "UseStartDepth"]: + if prop in ["UseStartDepth"]: self.setEditorProperties(obj) if prop == "Path" and obj.ViewObject: obj.ViewObject.signalChangeIcon() @@ -304,33 +269,29 @@ class ObjectDressup: else: obj.setEditorMode("DressupStartDepth", 2) - if obj.RampFeedRate == "Custom": - obj.setEditorMode("CustomFeedRate", 0) - if hasattr(obj, "FeedRatePercent"): - obj.setEditorMode("FeedRatePercent", 2) - else: - obj.setEditorMode("CustomFeedRate", 2) - if hasattr(obj, "FeedRatePercent"): - obj.setEditorMode("FeedRatePercent", 0) - def onDocumentRestored(self, obj): - if not hasattr(obj, "FeedRatePercent"): - obj.addProperty( - "App::PropertyInteger", - "FeedRatePercent", - "FeedRate", - QT_TRANSLATE_NOOP( - "App::Property", "Percentage modifier to apply to feed rate while ramping" - ), - ) - obj.FeedRatePercent = 100 - self.setEditorProperties(obj) + # Remove RampFeedRate + CustomFeedRate properties, but keep the values around temporarily + # This is required for tool controller migration: if a TC migrates with onDocumentRestored + # called after this, the prior ramp feed rate still needs to be accessible. + if hasattr(obj, "RampFeedRate"): + obj.Proxy.RampFeedRate = obj.RampFeedRate + obj.removeProperty("RampFeedRate") + print("delete RampFeedRate", obj.Label) + + if hasattr(obj, "CustomFeedRate"): + tmp = obj.CustomFeedRate.Value + for prop, exp in obj.ExpressionEngine: + if prop == "CustomFeedRate": + tmp = exp + obj.Proxy.CustomFeedRate = tmp + obj.removeProperty("CustomFeedRate") + print("delete CustomFeedRate", obj.Label) + def setup(self, obj): obj.Angle = 60 obj.Method = 2 - obj.FeedRatePercent = 100 if PathDressup.baseOp(obj.Base).StartDepth is not None: obj.DressupStartDepth = PathDressup.baseOp(obj.Base).StartDepth @@ -707,15 +668,7 @@ class ObjectDressup: vertFeed = tc.VertFeed.Value horizRapid = tc.HorizRapid.Value vertRapid = tc.VertRapid.Value - - if obj.RampFeedRate == "Horizontal Feed Rate": - rampFeed = horizFeed * obj.FeedRatePercent / 100 - elif obj.RampFeedRate == "Vertical Feed Rate": - rampFeed = vertFeed * obj.FeedRatePercent / 100 - elif obj.RampFeedRate == "Ramp Feed Rate": - rampFeed = (math.sqrt(pow(vertFeed, 2) + pow(horizFeed, 2))) * obj.FeedRatePercent / 100 - else: - rampFeed = obj.CustomFeedRate.Value + rampFeed = tc.RampFeed.Value lastX = lastY = lastZ = 0 for cmd in commands: diff --git a/src/Mod/CAM/Path/Main/Job.py b/src/Mod/CAM/Path/Main/Job.py index 03afd88d84..8e5eb8837e 100644 --- a/src/Mod/CAM/Path/Main/Job.py +++ b/src/Mod/CAM/Path/Main/Job.py @@ -705,7 +705,7 @@ class ObjectJob: "VertRapid", "%s.%s" % ( - self.setupSheet.expressionReference(), + self.obj.SetupSheet.Proxy.expressionReference(), PathSetupSheet.Template.VertRapid, ), ) @@ -713,7 +713,7 @@ class ObjectJob: "HorizRapid", "%s.%s" % ( - self.setupSheet.expressionReference(), + self.obj.SetupSheet.Proxy.expressionReference(), PathSetupSheet.Template.HorizRapid, ), ) diff --git a/src/Mod/CAM/Path/Op/Gui/Base.py b/src/Mod/CAM/Path/Op/Gui/Base.py index da8cb52ac5..25e4668551 100644 --- a/src/Mod/CAM/Path/Op/Gui/Base.py +++ b/src/Mod/CAM/Path/Op/Gui/Base.py @@ -429,22 +429,8 @@ class TaskPanelPage(object): def copyToolController(self): oldTc = self.tcEditor.obj self.tcEditor.updateToolController() - tc = PathToolController.Create( - name=oldTc.Label, tool=oldTc.Tool, toolNumber=oldTc.ToolNumber - ) job = self.obj.Proxy.getJob(self.obj) - job.Proxy.addToolController(tc) - - tc.HorizFeed = oldTc.HorizFeed - tc.VertFeed = oldTc.VertFeed - tc.HorizRapid = oldTc.HorizRapid - tc.VertRapid = oldTc.VertRapid - tc.SpindleSpeed = oldTc.SpindleSpeed - tc.SpindleDir = oldTc.SpindleDir - for attr, expr in oldTc.ExpressionEngine: - tc.setExpression(attr, expr) - - self.obj.ToolController = tc + self.obj.ToolController = PathToolController.copyTC(oldTc, job) self.setupToolController() def tcEditorChanged(self): diff --git a/src/Mod/CAM/Path/Tool/Controller.py b/src/Mod/CAM/Path/Tool/Controller.py index 133a7582eb..6d33c6635c 100644 --- a/src/Mod/CAM/Path/Tool/Controller.py +++ b/src/Mod/CAM/Path/Tool/Controller.py @@ -59,6 +59,80 @@ class ToolControllerTemplate: VertRapid = "vrapid" +def _migrateRampDressups(tc): + # Enumerate ramp dressups using this TC and their feed rates + ramps = set() + job_ramp_feeds = [] + print("search tc", tc.Name) + for job in tc.Document.Objects: + if hasattr(job, "Operations") and hasattr(job.Operations, "Group"): + print("job", job.Name) + for op in job.Operations.Group: + for ramp in [op] + op.OutListRecursive: + try: + if ramp not in ramps and ( + hasattr(ramp, "RampFeedRate") or hasattr(ramp.Proxy, "RampFeedRate") + ): + rampFeedRate = ( + ramp.RampFeedRate + if hasattr(ramp, "RampFeedRate") + else ramp.Proxy.RampFeedRate + ) + if hasattr(ramp, "CustomFeedRate"): + customFeedRate = ramp.CustomFeedRate.Value + for prop, exp in ramp.ExpressionEngine: + if prop == "CustomFeedRate": + customFeedRate = exp + else: + ramp.Proxy.CustomFeedRate + + if op.Base.ToolController == tc: + ramps.add(ramp) + feed = "HorizFeed" + if rampFeedRate == "Horizontal Feed Rate": + feed = "HorizFeed" + elif rampFeedRate == "Vertical Feed Rate": + feed = "VertFeed" + elif rampFeedRate == "Ramp Feed Rate": + feed = "sqrt(HorizFeed * HorizFeed + VertFeed * VertFeed)" + else: + feed = customFeedRate + job_ramp_feeds.append((job, ramp, feed)) + print("add", ramp.Label) + except: + pass + + # Ensure there is a TC for each required feed, starting with this one + print("job_ramp_feeds", job_ramp_feeds) + feed_to_tc = {} + for i, (job, ramp, feed) in enumerate(job_ramp_feeds): + if feed in feed_to_tc: + continue + + if len(feed_to_tc) == 0: + opTc = tc + else: + opTc = copyTC(tc, job) + # Note: C++ doesn't make an effort to deduplicate the Labels of + # objects created during document restore, so here we will reuse the + # (deduplicated) name as the label + opTc.Label = opTc.Name + + feed_to_tc[feed] = opTc + + if isinstance(feed, str): + opTc.setExpression("RampFeed", feed) + else: + opTc.setExpression("RampFeed", None) + opTc.RampFeed = feed + opTc.recompute() + + # Loop over ramps and assign each one the appropriate TC + for _, ramp, feed in job_ramp_feeds: + print("assign", ramp.Name, feed_to_tc[feed].Name) + ramp.Base.ToolController = feed_to_tc[feed] + + class ToolController: def __init__(self, obj, createTool=True): Path.Log.track("tool: ") @@ -199,7 +273,7 @@ class ToolController: "Feed", QT_TRANSLATE_NOOP("App::Property", "Feed rate for ramp moves"), ) - obj.setExpression("RampFeed", "HorizFeed") + _migrateRampDressups(obj) needsRecompute = True if not hasattr(obj, "LeadInFeed"): @@ -386,6 +460,23 @@ def Create( return obj +def copyTC(tc, job): + newtc = Create(name=tc.Label, tool=tc.Tool, toolNumber=tc.ToolNumber) + job.Proxy.addToolController(newtc) + + for prop in tc.PropertiesList: + try: + if prop not in ["Label", "Label2"]: + print("set prop", prop, getattr(tc, prop)) + setattr(newtc, prop, getattr(tc, prop)) + except: + pass + for attr, expr in tc.ExpressionEngine: + newtc.setExpression(attr, expr) + + return newtc + + def FromTemplate(template, assignViewProvider=True): Path.Log.track() From d699d080e0a3fd4785241d35152344f0ba300bc3 Mon Sep 17 00:00:00 2001 From: David Kaufman Date: Thu, 4 Sep 2025 18:07:49 -0400 Subject: [PATCH 06/12] Fix warning about new Profile properties NumPasses and StepOver being created automatically --- src/Mod/CAM/Path/Op/Profile.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Mod/CAM/Path/Op/Profile.py b/src/Mod/CAM/Path/Op/Profile.py index ca8ebbb333..a045372f52 100644 --- a/src/Mod/CAM/Path/Op/Profile.py +++ b/src/Mod/CAM/Path/Op/Profile.py @@ -313,9 +313,6 @@ class ObjectProfile(PathAreaOp.ObjectOp): def areaOpOnDocumentRestored(self, obj): self.propertiesReady = False - self.initAreaOpProperties(obj, warn=True) - self.areaOpSetDefaultValues(obj, PathUtils.findParentJob(obj)) - self.setOpEditorProperties(obj) if not hasattr(obj, "NumPasses"): obj.addProperty( "App::PropertyInteger", @@ -326,6 +323,7 @@ class ObjectProfile(PathAreaOp.ObjectOp): "The number of passes to do. Requires a non-zero value for Stepover", ), ) + if not hasattr(obj, "Stepover"): obj.addProperty( "App::PropertyDistance", @@ -337,6 +335,10 @@ class ObjectProfile(PathAreaOp.ObjectOp): ), ) + self.initAreaOpProperties(obj, warn=True) + self.areaOpSetDefaultValues(obj, PathUtils.findParentJob(obj)) + self.setOpEditorProperties(obj) + def areaOpOnChanged(self, obj, prop): """areaOpOnChanged(obj, prop) ... updates certain property visibilities depending on changed properties.""" if prop in ["UseComp", "JoinType", "Base"]: From e464cdb73d98e84d1c03238e2fd77cfffcbe893f Mon Sep 17 00:00:00 2001 From: David Kaufman Date: Thu, 4 Sep 2025 22:06:18 -0400 Subject: [PATCH 07/12] WIP update tool controller UI --- .../Resources/panels/ToolControllerEdit.ui | 95 ++++++++++++++++++- src/Mod/CAM/Path/Tool/Controller.py | 16 ++++ src/Mod/CAM/Path/Tool/Gui/Controller.py | 17 ++++ 3 files changed, 124 insertions(+), 4 deletions(-) diff --git a/src/Mod/CAM/Gui/Resources/panels/ToolControllerEdit.ui b/src/Mod/CAM/Gui/Resources/panels/ToolControllerEdit.ui index bbb94b24b2..9ef15f2c56 100644 --- a/src/Mod/CAM/Gui/Resources/panels/ToolControllerEdit.ui +++ b/src/Mod/CAM/Gui/Resources/panels/ToolControllerEdit.ui @@ -117,14 +117,14 @@ - + - Horizontal rapid + Lead-in feed - + 0 @@ -146,13 +146,100 @@ + + + Lead-out feed + + + + + + + + 0 + 0 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 0.000000000000000 + + + 9999999.000000000000000 + + + mm/s + + + + + + + Ramp feed + + + + + + + + 0 + 0 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 0.000000000000000 + + + 9999999.000000000000000 + + + mm/s + + + + + + + Horizontal rapid + + + + + + + + 0 + 0 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 0.000000000000000 + + + 9999999.000000000000000 + + + mm/s + + + + Vertical rapid - + diff --git a/src/Mod/CAM/Path/Tool/Controller.py b/src/Mod/CAM/Path/Tool/Controller.py index 6d33c6635c..2e92112ee4 100644 --- a/src/Mod/CAM/Path/Tool/Controller.py +++ b/src/Mod/CAM/Path/Tool/Controller.py @@ -49,7 +49,10 @@ class ToolControllerTemplate: HorizFeed = "hfeed" HorizRapid = "hrapid" Label = "label" + LeadInFeed = "leadinfeed" + LeadOutFeed = "leadoutfeed" Name = "name" + RampFeed = "rampfeed" SpindleDir = "dir" SpindleSpeed = "speed" ToolNumber = "nr" @@ -314,12 +317,22 @@ class ToolController: if template.get(ToolControllerTemplate.Version): version = int(template.get(ToolControllerTemplate.Version)) if version == 1 or version == 2: + # TODO figure out the meaning of this, and how to handle ramp/leadin/leadout feed rates + # or what else must be added to templates if template.get(ToolControllerTemplate.Label): obj.Label = template.get(ToolControllerTemplate.Label) if template.get(ToolControllerTemplate.VertFeed): obj.VertFeed = template.get(ToolControllerTemplate.VertFeed) if template.get(ToolControllerTemplate.HorizFeed): obj.HorizFeed = template.get(ToolControllerTemplate.HorizFeed) + if template.get(ToolControllerTemplate.LeadInFeed): + obj.LeadInFeed = template.get(ToolControllerTemplate.LeadInFeed, obj.LeadInFeed) + if template.get(ToolControllerTemplate.LeadOutFeed): + obj.LeadOutFeed = template.get( + ToolControllerTemplate.LeadOutFeed, obj.LeadOutFeed + ) + if template.get(ToolControllerTemplate.RampFeed): + obj.RampFeed = template.get(ToolControllerTemplate.RampFeed, obj.RampFeed) if template.get(ToolControllerTemplate.VertRapid): obj.VertRapid = template.get(ToolControllerTemplate.VertRapid) if template.get(ToolControllerTemplate.HorizRapid): @@ -374,6 +387,9 @@ class ToolController: attrs[ToolControllerTemplate.ToolNumber] = obj.ToolNumber attrs[ToolControllerTemplate.VertFeed] = "%s" % (obj.VertFeed) attrs[ToolControllerTemplate.HorizFeed] = "%s" % (obj.HorizFeed) + attrs[ToolControllerTemplate.LeadInFeed] = "%s" % (obj.LeadInFeed) + attrs[ToolControllerTemplate.LeadOutFeed] = "%s" % (obj.LeadOutFeed) + attrs[ToolControllerTemplate.RampFeed] = "%s" % (obj.RampFeed) attrs[ToolControllerTemplate.VertRapid] = "%s" % (obj.VertRapid) attrs[ToolControllerTemplate.HorizRapid] = "%s" % (obj.HorizRapid) attrs[ToolControllerTemplate.SpindleSpeed] = obj.SpindleSpeed diff --git a/src/Mod/CAM/Path/Tool/Gui/Controller.py b/src/Mod/CAM/Path/Tool/Gui/Controller.py index 146897e22a..4b2ab0b59a 100644 --- a/src/Mod/CAM/Path/Tool/Gui/Controller.py +++ b/src/Mod/CAM/Path/Tool/Gui/Controller.py @@ -218,6 +218,11 @@ class ToolControllerEditor(object): PathGuiUtil.populateCombobox(self.controller, enumTups, comboToPropertyMap) self.vertFeed = PathGuiUtil.QuantitySpinBox(self.controller.vertFeed, obj, "VertFeed") self.horizFeed = PathGuiUtil.QuantitySpinBox(self.controller.horizFeed, obj, "HorizFeed") + self.leadInFeed = PathGuiUtil.QuantitySpinBox(self.controller.horizFeed, obj, "LeadInFeed") + self.leadOutFeed = PathGuiUtil.QuantitySpinBox( + self.controller.horizFeed, obj, "LeadOutFeed" + ) + self.rampFeed = PathGuiUtil.QuantitySpinBox(self.controller.horizFeed, obj, "RampFeed") self.vertRapid = PathGuiUtil.QuantitySpinBox(self.controller.vertRapid, obj, "VertRapid") self.horizRapid = PathGuiUtil.QuantitySpinBox(self.controller.horizRapid, obj, "HorizRapid") @@ -260,6 +265,9 @@ class ToolControllerEditor(object): self.controller.tcNumber, self.horizFeed.widget, self.horizRapid.widget, + self.leadInFeed.widget, + self.leadOutFeed.widget, + self.rampFeed.widget, self.vertFeed.widget, self.vertRapid.widget, self.controller.spindleSpeed, @@ -273,6 +281,9 @@ class ToolControllerEditor(object): self.controller.tcNumber.setValue(tc.ToolNumber) self.horizFeed.updateWidget() self.horizRapid.updateWidget() + self.leadInFeed.updateWidget() + self.leadOutFeed.updateWidget() + self.rampFeed.updateWidget() self.vertFeed.updateWidget() self.vertRapid.updateWidget() self.controller.spindleSpeed.setValue(tc.SpindleSpeed) @@ -292,6 +303,9 @@ class ToolControllerEditor(object): tc.ToolNumber = self.controller.tcNumber.value() self.horizFeed.updateProperty() self.vertFeed.updateProperty() + self.leadInFeed.updateProperty() + self.leadOutFeed.updateProperty() + self.rampFeed.updateProperty() self.horizRapid.updateProperty() self.vertRapid.updateProperty() tc.SpindleSpeed = self.controller.spindleSpeed.value() @@ -323,6 +337,9 @@ class ToolControllerEditor(object): self.controller.tcNumber.editingFinished.connect(self.changed) self.vertFeed.widget.textChanged.connect(self.changed) self.horizFeed.widget.textChanged.connect(self.changed) + self.leadInFeed.widget.textChanged.connect(self.changed) + self.leadOutFeed.widget.textChanged.connect(self.changed) + self.rampFeed.widget.textChanged.connect(self.changed) self.vertRapid.widget.textChanged.connect(self.changed) self.horizRapid.widget.textChanged.connect(self.changed) self.controller.spindleSpeed.editingFinished.connect(self.changed) From 6f62d69b9fb3a18a8e88c310c3f0a17c3dcca728 Mon Sep 17 00:00:00 2001 From: David Kaufman Date: Wed, 10 Sep 2025 14:32:15 -0400 Subject: [PATCH 08/12] fix bugs, clean up --- .../Gui/Resources/panels/ToolControllerEdit.ui | 6 +++--- src/Mod/CAM/Path/Dressup/Gui/RampEntry.py | 2 -- src/Mod/CAM/Path/Tool/Controller.py | 17 ++++++----------- src/Mod/CAM/Path/Tool/Gui/Controller.py | 6 +++--- 4 files changed, 12 insertions(+), 19 deletions(-) diff --git a/src/Mod/CAM/Gui/Resources/panels/ToolControllerEdit.ui b/src/Mod/CAM/Gui/Resources/panels/ToolControllerEdit.ui index 9ef15f2c56..44933258dd 100644 --- a/src/Mod/CAM/Gui/Resources/panels/ToolControllerEdit.ui +++ b/src/Mod/CAM/Gui/Resources/panels/ToolControllerEdit.ui @@ -124,7 +124,7 @@ - + 0 @@ -146,14 +146,14 @@ - + Lead-out feed - + 0 diff --git a/src/Mod/CAM/Path/Dressup/Gui/RampEntry.py b/src/Mod/CAM/Path/Dressup/Gui/RampEntry.py index 6abc8f7f07..47def949bf 100644 --- a/src/Mod/CAM/Path/Dressup/Gui/RampEntry.py +++ b/src/Mod/CAM/Path/Dressup/Gui/RampEntry.py @@ -278,7 +278,6 @@ class ObjectDressup: if hasattr(obj, "RampFeedRate"): obj.Proxy.RampFeedRate = obj.RampFeedRate obj.removeProperty("RampFeedRate") - print("delete RampFeedRate", obj.Label) if hasattr(obj, "CustomFeedRate"): tmp = obj.CustomFeedRate.Value @@ -287,7 +286,6 @@ class ObjectDressup: tmp = exp obj.Proxy.CustomFeedRate = tmp obj.removeProperty("CustomFeedRate") - print("delete CustomFeedRate", obj.Label) def setup(self, obj): obj.Angle = 60 diff --git a/src/Mod/CAM/Path/Tool/Controller.py b/src/Mod/CAM/Path/Tool/Controller.py index 2e92112ee4..342fe9b52e 100644 --- a/src/Mod/CAM/Path/Tool/Controller.py +++ b/src/Mod/CAM/Path/Tool/Controller.py @@ -66,10 +66,8 @@ def _migrateRampDressups(tc): # Enumerate ramp dressups using this TC and their feed rates ramps = set() job_ramp_feeds = [] - print("search tc", tc.Name) for job in tc.Document.Objects: if hasattr(job, "Operations") and hasattr(job.Operations, "Group"): - print("job", job.Name) for op in job.Operations.Group: for ramp in [op] + op.OutListRecursive: try: @@ -87,7 +85,7 @@ def _migrateRampDressups(tc): if prop == "CustomFeedRate": customFeedRate = exp else: - ramp.Proxy.CustomFeedRate + customFeedRate = ramp.Proxy.CustomFeedRate if op.Base.ToolController == tc: ramps.add(ramp) @@ -101,12 +99,10 @@ def _migrateRampDressups(tc): else: feed = customFeedRate job_ramp_feeds.append((job, ramp, feed)) - print("add", ramp.Label) except: pass # Ensure there is a TC for each required feed, starting with this one - print("job_ramp_feeds", job_ramp_feeds) feed_to_tc = {} for i, (job, ramp, feed) in enumerate(job_ramp_feeds): if feed in feed_to_tc: @@ -116,9 +112,9 @@ def _migrateRampDressups(tc): opTc = tc else: opTc = copyTC(tc, job) - # Note: C++ doesn't make an effort to deduplicate the Labels of - # objects created during document restore, so here we will reuse the - # (deduplicated) name as the label + # Note: C++ doesn't try to deduplicate the Labels of objects created + # during document restore, so here we will reuse the (deduplicated) + # name as the label opTc.Label = opTc.Name feed_to_tc[feed] = opTc @@ -128,11 +124,11 @@ def _migrateRampDressups(tc): else: opTc.setExpression("RampFeed", None) opTc.RampFeed = feed - opTc.recompute() + if opTc is not tc: + opTc.recompute() # Loop over ramps and assign each one the appropriate TC for _, ramp, feed in job_ramp_feeds: - print("assign", ramp.Name, feed_to_tc[feed].Name) ramp.Base.ToolController = feed_to_tc[feed] @@ -483,7 +479,6 @@ def copyTC(tc, job): for prop in tc.PropertiesList: try: if prop not in ["Label", "Label2"]: - print("set prop", prop, getattr(tc, prop)) setattr(newtc, prop, getattr(tc, prop)) except: pass diff --git a/src/Mod/CAM/Path/Tool/Gui/Controller.py b/src/Mod/CAM/Path/Tool/Gui/Controller.py index 4b2ab0b59a..332706856c 100644 --- a/src/Mod/CAM/Path/Tool/Gui/Controller.py +++ b/src/Mod/CAM/Path/Tool/Gui/Controller.py @@ -218,11 +218,11 @@ class ToolControllerEditor(object): PathGuiUtil.populateCombobox(self.controller, enumTups, comboToPropertyMap) self.vertFeed = PathGuiUtil.QuantitySpinBox(self.controller.vertFeed, obj, "VertFeed") self.horizFeed = PathGuiUtil.QuantitySpinBox(self.controller.horizFeed, obj, "HorizFeed") - self.leadInFeed = PathGuiUtil.QuantitySpinBox(self.controller.horizFeed, obj, "LeadInFeed") + self.leadInFeed = PathGuiUtil.QuantitySpinBox(self.controller.leadInFeed, obj, "LeadInFeed") self.leadOutFeed = PathGuiUtil.QuantitySpinBox( - self.controller.horizFeed, obj, "LeadOutFeed" + self.controller.leadOutFeed, obj, "LeadOutFeed" ) - self.rampFeed = PathGuiUtil.QuantitySpinBox(self.controller.horizFeed, obj, "RampFeed") + self.rampFeed = PathGuiUtil.QuantitySpinBox(self.controller.rampFeed, obj, "RampFeed") self.vertRapid = PathGuiUtil.QuantitySpinBox(self.controller.vertRapid, obj, "VertRapid") self.horizRapid = PathGuiUtil.QuantitySpinBox(self.controller.horizRapid, obj, "HorizRapid") From 114b679f644b695e9b72c8dffa428e05500e573c Mon Sep 17 00:00:00 2001 From: David Kaufman Date: Wed, 10 Sep 2025 16:04:09 -0400 Subject: [PATCH 09/12] fix lint warnings --- src/Mod/CAM/Path/Tool/Controller.py | 59 +++++++++++++++-------------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/src/Mod/CAM/Path/Tool/Controller.py b/src/Mod/CAM/Path/Tool/Controller.py index 342fe9b52e..368c14318c 100644 --- a/src/Mod/CAM/Path/Tool/Controller.py +++ b/src/Mod/CAM/Path/Tool/Controller.py @@ -70,37 +70,38 @@ def _migrateRampDressups(tc): if hasattr(job, "Operations") and hasattr(job.Operations, "Group"): for op in job.Operations.Group: for ramp in [op] + op.OutListRecursive: - try: - if ramp not in ramps and ( - hasattr(ramp, "RampFeedRate") or hasattr(ramp.Proxy, "RampFeedRate") - ): - rampFeedRate = ( - ramp.RampFeedRate - if hasattr(ramp, "RampFeedRate") - else ramp.Proxy.RampFeedRate + if ramp not in ramps and ( + hasattr(ramp, "RampFeedRate") + or (hasattr(ramp, "Proxy") and hasattr(ramp.Proxy, "RampFeedRate")) + ): + rampFeedRate = ( + ramp.RampFeedRate + if hasattr(ramp, "RampFeedRate") + else ramp.Proxy.RampFeedRate + ) + if hasattr(ramp, "CustomFeedRate"): + customFeedRate = ramp.CustomFeedRate.Value + for prop, exp in ramp.ExpressionEngine: + if prop == "CustomFeedRate": + customFeedRate = exp + else: + customFeedRate = ( + ramp.Proxy.CustomFeedRate + if hasattr(ramp.Proxy, "CustomFeedRate") + else "HorizFeed" ) - if hasattr(ramp, "CustomFeedRate"): - customFeedRate = ramp.CustomFeedRate.Value - for prop, exp in ramp.ExpressionEngine: - if prop == "CustomFeedRate": - customFeedRate = exp - else: - customFeedRate = ramp.Proxy.CustomFeedRate - if op.Base.ToolController == tc: - ramps.add(ramp) + if op.Base.ToolController == tc: + ramps.add(ramp) + if rampFeedRate == "Horizontal Feed Rate": feed = "HorizFeed" - if rampFeedRate == "Horizontal Feed Rate": - feed = "HorizFeed" - elif rampFeedRate == "Vertical Feed Rate": - feed = "VertFeed" - elif rampFeedRate == "Ramp Feed Rate": - feed = "sqrt(HorizFeed * HorizFeed + VertFeed * VertFeed)" - else: - feed = customFeedRate - job_ramp_feeds.append((job, ramp, feed)) - except: - pass + elif rampFeedRate == "Vertical Feed Rate": + feed = "VertFeed" + elif rampFeedRate == "Ramp Feed Rate": + feed = "sqrt(HorizFeed * HorizFeed + VertFeed * VertFeed)" + else: + feed = customFeedRate + job_ramp_feeds.append((job, ramp, feed)) # Ensure there is a TC for each required feed, starting with this one feed_to_tc = {} @@ -481,6 +482,8 @@ def copyTC(tc, job): if prop not in ["Label", "Label2"]: setattr(newtc, prop, getattr(tc, prop)) except: + # ignore failure; it will succeed for all attributes that are meant + # to be assigned pass for attr, expr in tc.ExpressionEngine: newtc.setExpression(attr, expr) From e7643dc9298dc323569dc41c019ac37c7758fc17 Mon Sep 17 00:00:00 2001 From: David Kaufman Date: Fri, 19 Sep 2025 13:33:14 -0400 Subject: [PATCH 10/12] fix codeql warning --- src/Mod/CAM/Path/Tool/Controller.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Mod/CAM/Path/Tool/Controller.py b/src/Mod/CAM/Path/Tool/Controller.py index 368c14318c..ec62888287 100644 --- a/src/Mod/CAM/Path/Tool/Controller.py +++ b/src/Mod/CAM/Path/Tool/Controller.py @@ -481,9 +481,8 @@ def copyTC(tc, job): try: if prop not in ["Label", "Label2"]: setattr(newtc, prop, getattr(tc, prop)) - except: - # ignore failure; it will succeed for all attributes that are meant - # to be assigned + except RuntimeError: + # Ignore errors for read-only properties pass for attr, expr in tc.ExpressionEngine: newtc.setExpression(attr, expr) From 0976fdb0fa7ed600d52b90aa2d8d3f40a2165bed Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 5 Nov 2025 23:03:17 +0000 Subject: [PATCH 11/12] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/Mod/CAM/Path/Dressup/Gui/LeadInOut.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Mod/CAM/Path/Dressup/Gui/LeadInOut.py b/src/Mod/CAM/Path/Dressup/Gui/LeadInOut.py index 3520545e8b..9b8202af7c 100644 --- a/src/Mod/CAM/Path/Dressup/Gui/LeadInOut.py +++ b/src/Mod/CAM/Path/Dressup/Gui/LeadInOut.py @@ -799,9 +799,7 @@ class ObjectDressup: ) arcEnd = end + tangent + normal lead.append( - self.createArcMove( - obj, end, arcEnd, normalMax, obj.InvertOut, self.exitFeed - ) + self.createArcMove(obj, end, arcEnd, normalMax, obj.InvertOut, self.exitFeed) ) # append "Line" style lead-out @@ -850,9 +848,7 @@ class ObjectDressup: tangent = self.angleToVector(angleTangent) * tangentLength normal = App.Vector(0, 0, normalLength) arcEnd = end + tangent + normal - lead.extend( - self.createArcZMoveUp(obj, end, arcEnd, arcRadius, self.exitFeed) - ) + lead.extend(self.createArcZMoveUp(obj, end, arcEnd, arcRadius, self.exitFeed)) if obj.StyleOut in ("Arc3d", "Line3d"): # Up Z end point for Arc3d and Line3d From 7fb93c5a505c33f2341d40302955cda54bbd2928 Mon Sep 17 00:00:00 2001 From: David Kaufman Date: Thu, 6 Nov 2025 13:59:04 -0500 Subject: [PATCH 12/12] retrigger tests