diff --git a/src/Mod/CAM/Gui/Resources/panels/DressUpLeadInOutEdit.ui b/src/Mod/CAM/Gui/Resources/panels/DressUpLeadInOutEdit.ui
index d256ef50c2..baab2f8dab 100644
--- a/src/Mod/CAM/Gui/Resources/panels/DressUpLeadInOutEdit.ui
+++ b/src/Mod/CAM/Gui/Resources/panels/DressUpLeadInOutEdit.ui
@@ -7,7 +7,7 @@
0
0
359
- 534
+ 555
@@ -21,152 +21,240 @@
-
-
-
-
-
-
- Enable lead-in move
-
-
- Enable lead-in
-
-
-
- -
-
-
- Style
-
-
-
- -
-
-
-
- 80
- 0
-
-
-
-
- 16777215
- 16777215
-
-
-
-
- -
-
-
- Length/radius
-
-
-
- -
-
-
- Length or radius of the lead-in
-
-
- 0.100000000000000
-
-
-
-
-
-
- -
-
-
- Extend
-
-
-
- -
-
-
- Extends the lead-in distance
-
-
-
-
-
-
-
+
+
+ Lead In
+
+
+ true
+
+
+ -
+
+
-
+
+
+ Style
+
+
+
+ -
+
+
+
+ 80
+ 0
+
+
+
+
+ 16777215
+ 16777215
+
+
+
+
+ -
+
+
+ Radius/length (% tool radius)
+
+
+
+ -
+
+
+ Length of the Lead-in, as a percentage of tool radius
+
+
+ 0
+
+
+ 1.000000000000000
+
+
+ 999999.000000000000000
+
+
+ 10.000000000000000
+
+
+
+
+
+
+ -
+
+
+ Angle
+
+
+
+ -
+
+
+ Angular extent of the lead in arc (degrees)
+
+
+ 180.000000000000000
+
+
+ 10.000000000000000
+
+
+
+
+
+
+ -
+
+
+ Offset Entrance Location
+
+
+
+ -
+
+
+ -999999.000000000000000
+
+
+ 999999.000000000000000
+
+
+
+
+
+
+ -
+
+
+ Invert Direction
+
+
+
+
+
+
+
-
-
-
-
-
-
- Enable lead-out move
-
-
- Enable lead out
-
-
-
- -
-
-
- Style
-
-
-
- -
-
-
-
- 80
- 0
-
-
-
-
- 16777215
- 16777215
-
-
-
-
- -
-
-
- Length/radius
-
-
-
- -
-
-
- Length or radius of the lead-out
-
-
- 0.100000000000000
-
-
-
-
-
-
- -
-
-
- Extend
-
-
-
- -
-
-
- Extends the lead-out distance
-
-
-
-
-
-
-
+
+
+ Lead Out
+
+
+ true
+
+
+ -
+
+
-
+
+
+ Style
+
+
+
+ -
+
+
+
+ 80
+ 0
+
+
+
+
+ 16777215
+ 16777215
+
+
+
+
+ -
+
+
+ Radius/length (% tool radius)
+
+
+
+ -
+
+
+ Length of the Lead-out, as a percentage of tool radius
+
+
+ 0
+
+
+ 1.000000000000000
+
+
+ 999999.000000000000000
+
+
+ 10.000000000000000
+
+
+
+
+
+
+ -
+
+
+ Angle
+
+
+
+ -
+
+
+ Angular extent of the lead out arc (degrees)
+
+
+ 180.000000000000000
+
+
+ 10.000000000000000
+
+
+
+
+
+
+ -
+
+
+ Offset Exit Location
+
+
+
+ -
+
+
+ -999999.000000000000000
+
+
+ 999999.000000000000000
+
+
+
+
+
+
+ -
+
+
+ Invert Direction
+
+
+
+
+
+
+
-
@@ -197,12 +285,19 @@
-
-
-
- Keep the tool down in the path
-
+
- Keep tool down
+ Retract Threshold
+
+
+
+ -
+
+
+ 999999.000000000000000
+
+
+
@@ -223,13 +318,6 @@
-
-
- Gui::InputField
- QLineEdit
-
-
-
diff --git a/src/Mod/CAM/Path/Base/Language.py b/src/Mod/CAM/Path/Base/Language.py
index d32e8723a6..225c19e708 100644
--- a/src/Mod/CAM/Path/Base/Language.py
+++ b/src/Mod/CAM/Path/Base/Language.py
@@ -37,7 +37,7 @@ class Instruction(object):
def __init__(self, begin, cmd, param=None):
self.begin = begin
- if type(cmd) == Path.Command:
+ if isinstance(cmd, Path.Command):
self.cmd = Path.Name
self.param = Path.Parameters
else:
@@ -72,8 +72,15 @@ class Instruction(object):
return False
def isPlunge(self):
- """isPlunge() ... return true if this moves up or down"""
- return self.isMove() and not Path.Geom.isRoughly(self.begin.z, self.z(self.begin.z))
+ """isPlunge() ... return true if this moves is vertical"""
+ if self.isMove():
+ if (
+ Path.Geom.isRoughly(self.begin.x, self.x(self.begin.x))
+ and Path.Geom.isRoughly(self.begin.y, self.y(self.begin.y))
+ and not Path.Geom.isRoughly(self.begin.z, self.z(self.begin.z))
+ ):
+ return True
+ return False
def leadsInto(self, instr):
"""leadsInto(instr) ... return true if instr is a continuation of self"""
@@ -130,6 +137,12 @@ class MoveStraight(Instruction):
def isMove(self):
return True
+ def isStraight(self):
+ return True
+
+ def isArc(self):
+ return False
+
def isRapid(self):
return self.cmd in Path.Geom.CmdMoveRapid
@@ -159,6 +172,9 @@ class MoveArc(Instruction):
def isArc(self):
return True
+ def isStraight(self):
+ return False
+
def isCW(self):
return self.arcDirection() < 0
diff --git a/src/Mod/CAM/Path/Dressup/Gui/LeadInOut.py b/src/Mod/CAM/Path/Dressup/Gui/LeadInOut.py
index 32ca7f09cc..7eca7130d8 100644
--- a/src/Mod/CAM/Path/Dressup/Gui/LeadInOut.py
+++ b/src/Mod/CAM/Path/Dressup/Gui/LeadInOut.py
@@ -28,6 +28,7 @@ import Path
import Path.Base.Language as PathLanguage
import Path.Dressup.Utils as PathDressup
import PathScripts.PathUtils as PathUtils
+import copy
import math
__doc__ = """LeadInOut Dressup USE ROLL-ON ROLL-OFF to profile"""
@@ -44,14 +45,23 @@ if False:
else:
Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule())
+lead_styles = [
+ # common options first
+ QT_TRANSLATE_NOOP("CAM_DressupLeadInOut", "Arc"),
+ QT_TRANSLATE_NOOP("CAM_DressupLeadInOut", "Line"),
+ # additional options, alphabetical order
+ QT_TRANSLATE_NOOP("CAM_DressupLeadInOut", "Arc3d"),
+ QT_TRANSLATE_NOOP("CAM_DressupLeadInOut", "ArcZ"),
+ QT_TRANSLATE_NOOP("CAM_DressupLeadInOut", "Helix"),
+ QT_TRANSLATE_NOOP("CAM_DressupLeadInOut", "Line3d"),
+ QT_TRANSLATE_NOOP("CAM_DressupLeadInOut", "LineZ"),
+ QT_TRANSLATE_NOOP("CAM_DressupLeadInOut", "No Retract"),
+ QT_TRANSLATE_NOOP("CAM_DressupLeadInOut", "Vertical"),
+]
+
class ObjectDressup:
def __init__(self, obj):
- lead_styles = [
- QT_TRANSLATE_NOOP("CAM_DressupLeadInOut", "Arc"),
- QT_TRANSLATE_NOOP("CAM_DressupLeadInOut", "Tangent"),
- QT_TRANSLATE_NOOP("CAM_DressupLeadInOut", "Perpendicular"),
- ]
self.obj = obj
obj.addProperty(
"App::PropertyLink",
@@ -63,31 +73,21 @@ class ObjectDressup:
"App::PropertyBool",
"LeadIn",
"Path",
- QT_TRANSLATE_NOOP("App::Property", "Calculate roll-on to toolpath"),
+ QT_TRANSLATE_NOOP("App::Property", "Modify lead in to toolpath"),
)
obj.addProperty(
"App::PropertyBool",
"LeadOut",
"Path",
- QT_TRANSLATE_NOOP("App::Property", "Calculate roll-off from toolpath"),
+ QT_TRANSLATE_NOOP("App::Property", "Modify lead out from toolpath"),
)
obj.addProperty(
- "App::PropertyBool",
- "KeepToolDown",
+ "App::PropertyLength",
+ "RetractThreshold",
"Path",
- QT_TRANSLATE_NOOP("App::Property", "Keep the tool down in toolpath"),
- )
- obj.addProperty(
- "App::PropertyDistance",
- "LengthIn",
- "Path",
- QT_TRANSLATE_NOOP("App::Property", "Length or radius of the approach"),
- )
- obj.addProperty(
- "App::PropertyDistance",
- "LengthOut",
- "Path",
- QT_TRANSLATE_NOOP("App::Property", "Length or radius of the exit"),
+ QT_TRANSLATE_NOOP(
+ "App::Property", "Set distance which will attempts to avoid unnecessary retractions"
+ ),
)
obj.addProperty(
"App::PropertyEnumeration",
@@ -103,29 +103,59 @@ class ObjectDressup:
QT_TRANSLATE_NOOP("App::Property", "The style of motion out of the toolpath"),
)
obj.StyleOut = lead_styles
- obj.addProperty(
- "App::PropertyDistance",
- "ExtendLeadIn",
- "Path",
- QT_TRANSLATE_NOOP("App::Property", "Extends lead in distance"),
- )
- obj.addProperty(
- "App::PropertyDistance",
- "ExtendLeadOut",
- "Path",
- QT_TRANSLATE_NOOP("App::Property", "Extends lead out distance"),
- )
obj.addProperty(
"App::PropertyBool",
"RapidPlunge",
"Path",
QT_TRANSLATE_NOOP("App::Property", "Perform plunges with G0"),
)
+ obj.addProperty(
+ "App::PropertyAngle",
+ "AngleIn",
+ "Path Lead-in",
+ QT_TRANSLATE_NOOP("App::Property", "Angle of the Lead-In (1..90)"),
+ )
+ obj.addProperty(
+ "App::PropertyAngle",
+ "AngleOut",
+ "Path Lead-out",
+ QT_TRANSLATE_NOOP("App::Property", "Angle of the Lead-Out (1..90)"),
+ )
+ obj.addProperty(
+ "App::PropertyInteger",
+ "PercentageRadiusIn",
+ "Path Lead-in",
+ QT_TRANSLATE_NOOP("App::Property", "Determine length of the Lead-In"),
+ )
+ obj.addProperty(
+ "App::PropertyInteger",
+ "PercentageRadiusOut",
+ "Path Lead-out",
+ QT_TRANSLATE_NOOP("App::Property", "Determine length of the Lead-Out"),
+ )
obj.addProperty(
"App::PropertyBool",
- "IncludeLayers",
- "Path",
- QT_TRANSLATE_NOOP("App::Property", "Apply Lead in/out to layers within an operation"),
+ "InvertIn",
+ "Path Lead-in",
+ QT_TRANSLATE_NOOP("App::Property", "Invert Lead-In direction"),
+ )
+ obj.addProperty(
+ "App::PropertyBool",
+ "InvertOut",
+ "Path Lead-out",
+ QT_TRANSLATE_NOOP("App::Property", "Invert Lead-Out direction"),
+ )
+ obj.addProperty(
+ "App::PropertyDistance",
+ "OffsetIn",
+ "Path Lead-in",
+ QT_TRANSLATE_NOOP("App::Property", "Move start point"),
+ )
+ obj.addProperty(
+ "App::PropertyDistance",
+ "OffsetOut",
+ "Path Lead-out",
+ QT_TRANSLATE_NOOP("App::Property", "Move end point"),
)
obj.Proxy = self
@@ -136,49 +166,64 @@ class ObjectDressup:
return None
def setup(self, obj):
- obj.LengthIn = PathDressup.toolController(obj.Base).Tool.Diameter * 0.75
- obj.LengthOut = PathDressup.toolController(obj.Base).Tool.Diameter * 0.75
obj.LeadIn = True
obj.LeadOut = True
- obj.KeepToolDown = False
+ obj.AngleIn = 45
+ obj.AngleOut = 45
+ obj.InvertIn = False
+ obj.InvertOut = False
+ obj.PercentageRadiusIn = 150
+ obj.PercentageRadiusOut = 150
+ obj.RapidPlunge = False
obj.StyleIn = "Arc"
obj.StyleOut = "Arc"
- obj.ExtendLeadIn = 0
- obj.ExtendLeadOut = 0
- obj.RapidPlunge = False
- obj.IncludeLayers = True
def execute(self, obj):
if not obj.Base:
+ obj.Path = Path.Path()
return
if not obj.Base.isDerivedFrom("Path::Feature"):
+ obj.Path = Path.Path()
return
if not obj.Base.Path:
+ obj.Path = Path.Path()
return
- if obj.LengthIn <= 0:
- Path.Log.error(
- translate("CAM_DressupLeadInOut", "Length/radius positive not Null") + "\n"
- )
- obj.LengthIn = 0.1
+ if obj.PercentageRadiusIn < 1:
+ obj.PercentageRadiusIn = 1
+ if obj.PercentageRadiusOut < 1:
+ obj.PercentageRadiusOut = 1
- if obj.LengthOut <= 0:
- Path.Log.error(
- translate("CAM_DressupLeadInOut", "Length/radius positive not Null") + "\n"
- )
- obj.LengthOut = 0.1
+ limit_angle_in = 1 if "Arc" in obj.StyleIn or "Helix" == obj.StyleIn else 0
+ limit_angle_out = 1 if "Arc" in obj.StyleOut or "Helix" == obj.StyleOut else 0
+ if obj.AngleIn > 180:
+ obj.AngleIn = 180
+ if obj.AngleIn < limit_angle_in:
+ obj.AngleIn = limit_angle_in
+
+ if obj.AngleOut > 180:
+ obj.AngleOut = 180
+ if obj.AngleOut < limit_angle_out:
+ obj.AngleOut = limit_angle_out
+
+ hideModes = {
+ "Angle": ["No Retract", "Vertical"],
+ "Invert": ["No Retract", "ArcZ", "LineZ", "Vertical"],
+ "Offset": ["No Retract"],
+ "PercentageRadius": ["No Retract", "Vertical"],
+ }
+ for k, v in hideModes.items():
+ obj.setEditorMode(k + "In", 2 if obj.StyleIn in v else 0)
+ obj.setEditorMode(k + "Out", 2 if obj.StyleOut in v else 0)
obj.Path = self.generateLeadInOutCurve(obj)
def onDocumentRestored(self, obj):
- """onDocumentRestored(obj)… Called automatically when document is restored."""
- lead_styles = [
- QT_TRANSLATE_NOOP("CAM_DressupLeadInOut", "Arc"),
- QT_TRANSLATE_NOOP("CAM_DressupLeadInOut", "Tangent"),
- QT_TRANSLATE_NOOP("CAM_DressupLeadInOut", "Perpendicular"),
- ]
+ """onDocumentRestored(obj) ... Called automatically when document is restored."""
+ styleOn = styleOff = None
if hasattr(obj, "StyleOn"):
# Replace StyleOn by StyleIn
+ styleOn = obj.StyleOn
obj.addProperty(
"App::PropertyEnumeration",
"StyleIn",
@@ -186,10 +231,11 @@ class ObjectDressup:
QT_TRANSLATE_NOOP("App::Property", "The style of motion into the toolpath"),
)
obj.StyleIn = lead_styles
- obj.StyleIn = obj.StyleOn
obj.removeProperty("StyleOn")
+ obj.StyleIn = "Arc"
if hasattr(obj, "StyleOff"):
# Replace StyleOff by StyleOut
+ styleOff = obj.StyleOff
obj.addProperty(
"App::PropertyEnumeration",
"StyleOut",
@@ -197,95 +243,332 @@ class ObjectDressup:
QT_TRANSLATE_NOOP("App::Property", "The style of motion out of the toolpath"),
)
obj.StyleOut = lead_styles
- obj.StyleOut = obj.StyleOff
obj.removeProperty("StyleOff")
+ obj.StyleOut = "Arc"
+
+ if not hasattr(obj, "AngleIn"):
+ obj.addProperty(
+ "App::PropertyAngle",
+ "AngleIn",
+ "Path Lead-in",
+ QT_TRANSLATE_NOOP("App::Property", "Angle of the Lead-In (1..90)"),
+ )
+ obj.AngleIn = 45
+ if not hasattr(obj, "AngleOut"):
+ obj.addProperty(
+ "App::PropertyAngle",
+ "AngleOut",
+ "Path Lead-out",
+ QT_TRANSLATE_NOOP("App::Property", "Angle of the Lead-Out (1..90)"),
+ )
+ obj.AngleOut = 45
+
+ if styleOn:
+ if styleOn == "Arc":
+ obj.StyleIn = "Arc"
+ obj.AngleIn = 90
+ elif styleOn in ["Perpendicular", "Tangent"]:
+ obj.StyleIn = "Line"
+ obj.AngleIn = 90 if styleOn == "Perpendicular" else 0
+
+ if styleOff:
+ if styleOff == "Arc":
+ obj.StyleOut = "Arc"
+ obj.AngleOut = 90
+ elif styleOff in ["Perpendicular", "Tangent"]:
+ obj.StyleOut = "Line"
+ obj.AngleOut = 90 if styleOff == "Perpendicular" else 0
+
+ toolRadius = PathDressup.toolController(obj.Base).Tool.Diameter.Value / 2
if hasattr(obj, "Length"):
- # Replace Length by LengthIn
+ # Replace Length by PercentageRadiusIn
+ obj.addProperty(
+ "App::PropertyInteger",
+ "PercentageRadiusIn",
+ "Path Lead-in",
+ QT_TRANSLATE_NOOP("App::Property", "Determine length of the Lead-In"),
+ )
+ obj.PercentageRadiusIn = int(obj.Length / toolRadius * 100)
+ obj.removeProperty("Length")
+ if hasattr(obj, "LengthOut"):
+ # Replace LengthOut by PercentageRadiusOut
+ obj.addProperty(
+ "App::PropertyInteger",
+ "PercentageRadiusOut",
+ "Path Lead-out",
+ QT_TRANSLATE_NOOP("App::Property", "Determine length of the Lead-Out"),
+ )
+ obj.PercentageRadiusOut = int(obj.LengthOut / toolRadius * 100)
+ obj.removeProperty("LengthOut")
+
+ # The new features do not have a good analog for ExtendLeadIn/Out, so these old values will be ignored
+ if hasattr(obj, "ExtendLeadIn"):
+ # Remove ExtendLeadIn property
+ obj.removeProperty("ExtendLeadIn")
+ if hasattr(obj, "ExtendLeadOut"):
+ # Remove ExtendLeadOut property
+ obj.removeProperty("ExtendLeadOut")
+ if hasattr(obj, "IncludeLayers"):
+ obj.removeProperty("IncludeLayers")
+
+ if not hasattr(obj, "InvertIn"):
+ obj.addProperty(
+ "App::PropertyBool",
+ "InvertIn",
+ "Path Lead-in",
+ QT_TRANSLATE_NOOP("App::Property", "Invert Lead-In direction"),
+ )
+ if not hasattr(obj, "InvertOut"):
+ obj.addProperty(
+ "App::PropertyBool",
+ "InvertOut",
+ "Path Lead-out",
+ QT_TRANSLATE_NOOP("App::Property", "Invert Lead-Out direction"),
+ )
+ if not hasattr(obj, "OffsetIn"):
obj.addProperty(
"App::PropertyDistance",
- "LengthIn",
- "Path",
- QT_TRANSLATE_NOOP("App::Property", "Length or radius of the approach"),
+ "OffsetIn",
+ "Path Lead-in",
+ QT_TRANSLATE_NOOP("App::Property", "Move start point"),
)
- obj.LengthIn = obj.Length
- obj.removeProperty("Length")
+ if not hasattr(obj, "OffsetOut"):
+ obj.addProperty(
+ "App::PropertyDistance",
+ "OffsetOut",
+ "Path Lead-out",
+ QT_TRANSLATE_NOOP("App::Property", "Move end point"),
+ )
+ if not hasattr(obj, "RetractThreshold"):
+ obj.addProperty(
+ "App::PropertyLength",
+ "RetractThreshold",
+ "Path",
+ QT_TRANSLATE_NOOP(
+ "App::Property",
+ "Set distance which will attempts to avoid unnecessary retractions",
+ ),
+ )
+ if hasattr(obj, "KeepToolDown"):
+ if obj.KeepToolDown:
+ obj.RetractThreshold = 999999
+ obj.removeProperty("KeepToolDown")
- def getDirectionOfPath(self, obj):
+ # Get direction for lead-in/lead-out in XY plane
+ def getLeadDir(self, obj, invert=False):
+ output = math.pi / 2
op = PathDressup.baseOp(obj.Base)
side = op.Side if hasattr(op, "Side") else "Inside"
direction = op.Direction if hasattr(op, "Direction") else "CCW"
+ if (side == "Inside" and direction == "CW") or (side == "Outside" and direction == "CCW"):
+ output = -output
+ if invert:
+ output = -output
- if side == "Outside":
- return "left" if direction == "CW" else "right"
- else:
- return "right" if direction == "CW" else "left"
+ return output
- def getArcDirection(self, obj):
- direction = self.getDirectionOfPath(obj)
- return math.pi / 2 if direction == "left" else -math.pi / 2
+ # Get direction of original path
+ def getPathDir(self, obj):
+ # only CW or CCW is matter
+ op = PathDressup.baseOp(obj.Base)
+ direction = op.Direction if hasattr(op, "Direction") else "CCW"
+ output = math.pi / 2
+ if direction == "CW":
+ output = -output
- def getTravelStart(self, obj, pos, first):
+ return output
+
+ # Create safety movements to start point
+ def getTravelStart(self, obj, pos, first, inInstrPrev, outInstrPrev):
op = PathDressup.baseOp(obj.Base)
vertfeed = PathDressup.toolController(obj.Base).VertFeed.Value
- travel = []
+ commands = []
+ posPrev = outInstrPrev.positionEnd() if outInstrPrev else App.Vector()
+ posPrevXY = App.Vector(posPrev.x, posPrev.y, 0)
+ posXY = App.Vector(pos.x, pos.y, 0)
+ distance = posPrevXY.distanceToPoint(posXY)
- # begin positions for travel and plunge moves are not used anywhere,
- # skipping them makes our life a lot easier
+ if first or (distance > obj.RetractThreshold):
+ # move to clearance height
+ commands.append(PathLanguage.MoveStraight(None, "G00", {"Z": op.ClearanceHeight.Value}))
- # move to clearance height
- if first:
- travel.append(PathLanguage.MoveStraight(None, "G0", {"Z": op.ClearanceHeight.Value}))
+ # move to mill X/Y-position (without move Z)
+ commands.append(PathLanguage.MoveStraight(None, "G00", {"X": pos.x, "Y": pos.y}))
- # move to correct xy-position
- travel.append(PathLanguage.MoveStraight(None, "G0", {"X": pos.x, "Y": pos.y}))
+ if distance > obj.RetractThreshold:
+ # move vertical down to mill position
+ if obj.RapidPlunge:
+ # move to mill position rapidly
+ commands.append(PathLanguage.MoveStraight(None, "G00", {"Z": pos.z}))
+ else:
+ # move to mill position in one or two steps
+ if first:
+ # move down to SafeHeight
+ commands.append(
+ PathLanguage.MoveStraight(None, "G00", {"Z": op.SafeHeight.Value})
+ )
+ commands.append(PathLanguage.MoveStraight(None, "G01", {"Z": pos.z, "F": vertfeed}))
+
+ elif obj.StyleOut == "Helix":
+ # move by helix to next mill position
+ if obj.StyleIn == "Helix":
+ halfStepZ = (posPrev.z - pos.z) / 2
+ stepOutZ = halfStepZ * outInstrPrev.arcAngle() / math.pi
+ lastZMove = stepOutZ
+ else:
+ stepOutZ = posPrev.z - pos.z
+ lastZMove = 0
+ outInstrPrev.param["Z"] = posPrev.z - stepOutZ
+ if not Path.Geom.pointsCoincide(posPrevXY, posXY):
+ if obj.RapidPlunge:
+ commands.append(
+ PathLanguage.MoveStraight(
+ outInstrPrev.positionEnd(),
+ "G00",
+ {"X": pos.x, "Y": pos.y, "Z": pos.z + lastZMove},
+ )
+ )
+ else:
+ commands.append(
+ PathLanguage.MoveStraight(
+ outInstrPrev.positionEnd(),
+ "G01",
+ {"X": pos.x, "Y": pos.y, "Z": pos.z + lastZMove, "F": vertfeed},
+ )
+ )
- # move to correct z-position (either rapidly or in two steps)
- if obj.RapidPlunge:
- travel.append(PathLanguage.MoveStraight(None, "G0", {"Z": pos.z}))
else:
- if first or not obj.KeepToolDown:
- travel.append(PathLanguage.MoveStraight(None, "G0", {"Z": op.SafeHeight.Value}))
- travel.append(PathLanguage.MoveStraight(None, "G1", {"Z": pos.z, "F": vertfeed}))
+ # move to next mill position by short path
+ if obj.RapidPlunge:
+ commands.append(
+ PathLanguage.MoveStraight(None, "G00", {"X": pos.x, "Y": pos.y, "Z": pos.z})
+ )
+ else:
+ commands.append(
+ PathLanguage.MoveStraight(
+ None, "G01", {"X": pos.x, "Y": pos.y, "Z": pos.z, "F": vertfeed}
+ )
+ )
- return travel
+ return commands
- def getTravelEnd(self, obj, pos, last):
+ # Create commands with movements to clearance height
+ def getTravelEnd(self, obj):
+ commands = []
op = PathDressup.baseOp(obj.Base)
- travel = []
+ z = op.ClearanceHeight.Value
+ commands.append(PathLanguage.MoveStraight(None, "G00", {"Z": z}))
- # move to clearance height
- if last or not obj.KeepToolDown:
- travel.append(PathLanguage.MoveStraight(None, "G0", {"Z": op.ClearanceHeight.Value}))
-
- return travel
+ return commands
+ # Create vector object from angle
def angleToVector(self, angle):
return App.Vector(math.cos(angle), math.sin(angle), 0)
- def createArcMove(self, obj, begin, end, c):
+ # Create arc in XY plane with automatic detection G2|G3
+ def createArcMove(self, obj, begin, end, offset, invert=False):
horizfeed = PathDressup.toolController(obj.Base).HorizFeed.Value
-
- param = {"X": end.x, "Y": end.y, "I": c.x, "J": c.y, "F": horizfeed}
- if self.getArcDirection(obj) > 0:
- return PathLanguage.MoveArcCCW(begin, "G3", param)
+ param = {"X": end.x, "Y": end.y, "Z": end.z, "I": offset.x, "J": offset.y, "F": horizfeed}
+ if self.getLeadDir(obj, invert) > 0:
+ command = PathLanguage.MoveArcCCW(begin, "G3", param)
else:
- return PathLanguage.MoveArcCW(begin, "G2", param)
+ command = PathLanguage.MoveArcCW(begin, "G2", param)
+ return command
+
+ # Create arc in XY plane with manually set G2|G3
+ def createArcMoveN(self, obj, begin, end, offset, cmdName):
+ horizfeed = PathDressup.toolController(obj.Base).HorizFeed.Value
+ param = {"X": end.x, "Y": end.y, "I": offset.x, "J": offset.y, "F": horizfeed}
+ if cmdName == "G2":
+ command = PathLanguage.MoveArcCW(begin, cmdName, param)
+ else:
+ command = PathLanguage.MoveArcCCW(begin, cmdName, param)
+
+ return command
+
+ # Create line movement G1
def createStraightMove(self, obj, begin, end):
horizfeed = PathDressup.toolController(obj.Base).HorizFeed.Value
+ param = {"X": end.x, "Y": end.y, "Z": end.z, "F": horizfeed}
+ command = PathLanguage.MoveStraight(begin, "G1", param)
- param = {"X": end.x, "Y": end.y, "F": horizfeed}
- return PathLanguage.MoveStraight(begin, "G1", param)
+ return command
- def getLeadStart(self, obj, move, first):
- lead = []
- begin = move.positionBegin()
+ # Get optimal step angle for iteration ArcZ
+ def getStepAngleArcZ(self, obj, radius):
+ job = PathUtils.findParentJob(obj)
+ minArcLength = job.GeometryTolerance.Value * 2
+ maxArcLength = 1
+ stepAngle = math.pi / 60
+ stepArcLength = stepAngle * radius
+ if stepArcLength > maxArcLength:
+ # limit max arc length by 1 mm
+ stepAngle = maxArcLength / radius
+ elif stepArcLength < minArcLength:
+ # limit min arc length by geometry tolerance
+ stepAngle = minArcLength / radius
- def prepend(instr):
- nonlocal lead
- nonlocal begin
- lead.insert(0, instr)
- begin = lead[0].positionBegin()
+ return stepAngle
+
+ # Create vertical arc with move Down by line segments
+ def createArcZMoveDown(self, obj, begin, end, radius):
+ commands = []
+ horizfeed = PathDressup.toolController(obj.Base).HorizFeed.Value
+ angle = math.acos((radius - begin.z + end.z) / radius) # start angle
+ stepAngle = self.getStepAngleArcZ(obj, radius)
+ iters = math.ceil(angle / stepAngle)
+ iterBegin = copy.copy(begin) # start point of short segment
+ iter = 1
+ v = end - begin
+ n = math.hypot(v.x, v.y)
+ u = v / n
+ while iter <= iters:
+ if iter < iters:
+ angle -= stepAngle
+ distance = n - radius * math.sin(angle)
+ iterEnd = begin + u * distance
+ iterEnd.z = end.z + radius * (1 - math.cos(angle))
+ else:
+ # exclude error of calculations for the last iteration
+ iterEnd = copy.copy(end)
+ param = {"X": iterEnd.x, "Y": iterEnd.y, "Z": iterEnd.z, "F": horizfeed}
+ commands.append(PathLanguage.MoveStraight(iterBegin, "G1", param))
+ iterBegin = copy.copy(iterEnd)
+ iter += 1
+
+ return commands
+
+ # Create vertical arc with move Up by line segments
+ def createArcZMoveUp(self, obj, begin, end, radius):
+ commands = []
+ horizfeed = PathDressup.toolController(obj.Base).HorizFeed.Value
+ angleMax = math.acos((radius - end.z + begin.z) / radius) # finish angle
+ stepAngle = self.getStepAngleArcZ(obj, radius)
+ iters = math.ceil(angleMax / stepAngle)
+ iterBegin = copy.copy(begin) # start point of short segment
+ iter = 1
+ v = end - begin
+ n = math.hypot(v.x, v.y)
+ u = v / n
+ angle = 0 # start angle
+ while iter <= iters:
+ if iter < iters:
+ angle += stepAngle
+ distance = radius * math.sin(angle)
+ iterEnd = begin + u * distance
+ iterEnd.z = begin.z + radius * (1 - math.cos(angle))
+ 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": horizfeed}
+ commands.append(PathLanguage.MoveStraight(iterBegin, "G1", param))
+ iterBegin = copy.copy(iterEnd)
+ iter += 1
+
+ return commands
+
+ def getLeadStart(self, obj, move, first, inInstrPrev, outInstrPrev):
# tangent begin move
# <----_-----x-------------------x
@@ -294,41 +577,114 @@ class ObjectDressup:
# | |
# x v
- if obj.LeadIn:
- length = obj.LengthIn.Value
- angle = move.anglesOfTangents()[0]
- tangent = -self.angleToVector(angle) * length
- normal = self.angleToVector(angle + self.getArcDirection(obj)) * length
+ lead = []
+ begin = move.positionBegin()
- # prepend the selected lead-in
- if obj.StyleIn == "Arc":
- arcbegin = begin + tangent + normal
- prepend(self.createArcMove(obj, arcbegin, begin, -tangent))
- elif obj.StyleIn == "Tangent":
- prepend(self.createStraightMove(obj, begin + tangent, begin))
- else: # obj.StyleIn == "Perpendicular"
- prepend(self.createStraightMove(obj, begin + normal, begin))
+ if obj.StyleIn not in ["No Retract", "Vertical"]:
+ toolRadius = PathDressup.toolController(obj.Base).Tool.Diameter.Value / 2
+ angleIn = math.radians(obj.AngleIn.Value)
+ length = obj.PercentageRadiusIn * toolRadius / 100
+ angleTangent = move.anglesOfTangents()[0]
+ normalMax = (
+ self.angleToVector(angleTangent + self.getLeadDir(obj, obj.InvertIn)) * length
+ )
- extend = obj.ExtendLeadIn.Value
- if extend != 0:
- # prepend extension
- extendbegin = begin + normal / length * extend
- prepend(self.createStraightMove(obj, extendbegin, begin))
+ # Here you can find description of the calculations
+ # https://forum.freecad.org/viewtopic.php?t=97641
- # prepend travel moves
- lead = self.getTravelStart(obj, begin, first) + lead
+ # prepend "Arc" style lead-in - arc in XY
+ # Arc3d the same as Arc, but increased Z start point
+ if obj.StyleIn in ["Arc", "Arc3d", "Helix"]:
+ # tangent and normal vectors in XY plane
+ arcRadius = length
+ tangentLength = math.sin(angleIn) * arcRadius
+ normalLength = arcRadius * (1 - math.cos(angleIn))
+ tangent = -self.angleToVector(angleTangent) * tangentLength
+ normal = (
+ self.angleToVector(angleTangent + self.getLeadDir(obj, obj.InvertIn))
+ * normalLength
+ )
+ arcBegin = begin + tangent + normal
+ arcCenter = begin + normalMax
+ arcOffset = arcCenter - arcBegin
+ lead.append(self.createArcMove(obj, arcBegin, begin, arcOffset, obj.InvertIn))
+
+ # prepend "Line" style lead-in - line in XY
+ # Line3d the same as Line, but increased Z start point
+ elif obj.StyleIn in ["Line", "Line3d"]:
+ # tangent and normal vectors in XY plane
+ tangentLength = math.cos(angleIn) * length
+ normalLength = math.sin(angleIn) * length
+ tangent = -self.angleToVector(angleTangent) * tangentLength
+ normal = (
+ self.angleToVector(angleTangent + self.getLeadDir(obj, obj.InvertIn))
+ * normalLength
+ )
+ lineBegin = begin + tangent + normal
+ lead.append(self.createStraightMove(obj, lineBegin, begin))
+
+ # prepend "LineZ" style lead-in - vertical inclined line
+ # Should be apply only on straight Path segment
+ elif obj.StyleIn == "LineZ":
+ # tangent vector in XY plane
+ # normal vector is vertical
+ op = PathDressup.baseOp(obj.Base)
+ normalLengthMax = op.SafeHeight.Value - begin.z
+ normalLength = math.sin(angleIn) * length
+ # do not exceed Normal vector max length
+ normalLength = min(normalLength, normalLengthMax)
+ tangentLength = normalLength / math.tan(angleIn)
+ tangent = -self.angleToVector(angleTangent) * tangentLength
+ normal = App.Vector(0, 0, normalLength)
+ lineBegin = begin + tangent + normal
+ lead.append(self.createStraightMove(obj, lineBegin, begin))
+
+ # prepend "ArcZ" style lead-in - vertical Arc
+ # Should be apply only on straight Path segment
+ elif obj.StyleIn == "ArcZ":
+ # tangent vector in XY plane
+ # normal vector is vertical
+ op = PathDressup.baseOp(obj.Base)
+ arcRadius = length
+ normalLengthMax = op.SafeHeight.Value - begin.z
+ normalLength = arcRadius * (1 - math.cos(angleIn))
+ if normalLength > normalLengthMax:
+ # do not exceed Normal vector max length
+ normalLength = normalLengthMax
+ # recalculate angle for limited normal length
+ angleIn = math.acos((arcRadius - normalLength) / arcRadius)
+ tangentLength = arcRadius * math.sin(angleIn)
+ tangent = -self.angleToVector(angleTangent) * tangentLength
+ normal = App.Vector(0, 0, normalLength)
+ arcBegin = begin + tangent + normal
+ lead.extend(self.createArcZMoveDown(obj, arcBegin, begin, arcRadius))
+
+ # replace 'begin' position by first lead-in command
+ begin = lead[0].positionBegin()
+
+ if obj.StyleIn in ["Arc3d", "Line3d"]:
+ # up Z start point for Arc3d and Line3d
+ op = PathDressup.baseOp(obj.Base)
+ if inInstrPrev and inInstrPrev.z() > begin.z:
+ begin.z = inInstrPrev.z()
+ else:
+ begin.z = op.StartDepth.Value
+ lead[0].setPositionBegin(begin)
+
+ # get complete start travel moves
+ if obj.StyleIn != "No Retract":
+ travelToStart = self.getTravelStart(obj, begin, first, inInstrPrev, outInstrPrev)
+ else:
+ # exclude any lead-in commands
+ horizfeed = PathDressup.toolController(obj.Base).HorizFeed.Value
+ param = {"X": begin.x, "Y": begin.y, "Z": begin.z, "F": horizfeed}
+ travelToStart = [PathLanguage.MoveStraight(None, "G01", param)]
+
+ lead = travelToStart + lead
return lead
- def getLeadEnd(self, obj, move, last):
- lead = []
- end = move.positionEnd()
-
- def append(instr):
- nonlocal lead
- nonlocal end
- lead.append(instr)
- end = lead[-1].positionEnd()
+ def getLeadEnd(self, obj, move, last, inInstrPrev, outInstrPrev):
# move end tangent
# x-------------------x-----_---->
@@ -337,43 +693,290 @@ class ObjectDressup:
# | |
# v x
- if obj.LeadOut:
- length = obj.LengthOut.Value
- angle = move.anglesOfTangents()[1]
- tangent = self.angleToVector(angle) * length
- normal = self.angleToVector(angle + self.getArcDirection(obj)) * length
+ lead = []
+ end = move.positionEnd()
- # append the selected lead-out
- if obj.StyleOut == "Arc":
- arcend = end + tangent + normal
- append(self.createArcMove(obj, end, arcend, normal))
- elif obj.StyleOut == "Tangent":
- append(self.createStraightMove(obj, end, end + tangent))
- else: # obj.StyleOut == "Perpendicular"
- append(self.createStraightMove(obj, end, end + normal))
+ if obj.StyleOut not in ["No Retract", "Vertical"]:
+ toolRadius = PathDressup.toolController(obj.Base).Tool.Diameter.Value / 2
+ angleOut = math.radians(obj.AngleOut.Value)
+ length = obj.PercentageRadiusOut * toolRadius / 100
+ angleTangent = move.anglesOfTangents()[1]
+ normalMax = (
+ self.angleToVector(angleTangent + self.getLeadDir(obj, obj.InvertOut)) * length
+ )
- extend = obj.ExtendLeadOut.Value
- if extend != 0:
- # append extension
- extendend = end + normal / length * extend
- append(self.createStraightMove(obj, end, extendend))
+ # Here you can find description of the calculations
+ # https://forum.freecad.org/viewtopic.php?t=97641
- # append travel moves
- lead += self.getTravelEnd(obj, end, last)
+ # append "Arc" style lead-out - arc in XY
+ # Arc3d the same as Arc, but increased Z start point
+ if obj.StyleOut in ["Arc", "Arc3d", "Helix"]:
+ # tangent and normal vectors in XY plane
+ arcRadius = length
+ tangentLength = math.sin(angleOut) * arcRadius
+ normalLength = arcRadius * (1 - math.cos(angleOut))
+ tangent = self.angleToVector(angleTangent) * tangentLength
+ normal = (
+ self.angleToVector(angleTangent + self.getLeadDir(obj, obj.InvertOut))
+ * normalLength
+ )
+ arcEnd = end + tangent + normal
+ lead.append(self.createArcMove(obj, end, arcEnd, normalMax, obj.InvertOut))
+
+ # append "Line" style lead-out
+ # Line3d the same as Line, but increased Z start point
+ elif obj.StyleOut in ["Line", "Line3d"]:
+ # tangent and normal vectors in XY plane
+ tangentLength = math.cos(angleOut) * length
+ normalLength = math.sin(angleOut) * length
+ tangent = self.angleToVector(angleTangent) * tangentLength
+ normal = (
+ self.angleToVector(angleTangent + self.getLeadDir(obj, obj.InvertOut))
+ * normalLength
+ )
+ lineEnd = end + tangent + normal
+ lead.append(self.createStraightMove(obj, end, lineEnd))
+
+ # append "LineZ" style lead-out - vertical inclined line
+ # Should be apply only on straight Path segment
+ elif obj.StyleOut == "LineZ":
+ # tangent vector in XY plane
+ # normal vector is vertical
+ op = PathDressup.baseOp(obj.Base)
+ normalLengthMax = op.StartDepth.Value - end.z
+ normalLength = math.sin(angleOut) * length
+ # do not exceed Normal vector max length
+ normalLength = min(normalLength, normalLengthMax)
+ tangentLength = normalLength / math.tan(angleOut)
+ tangent = self.angleToVector(angleTangent) * tangentLength
+ normal = App.Vector(0, 0, normalLength)
+ lineEnd = end + tangent + normal
+ lead.append(self.createStraightMove(obj, end, lineEnd))
+
+ # prepend "ArcZ" style lead-out - vertical Arc
+ # Should be apply only on straight Path segment
+ elif obj.StyleOut == "ArcZ":
+ # tangent vector in XY plane
+ # normal vector is vertical
+ op = PathDressup.baseOp(obj.Base)
+ arcRadius = length
+ normalLengthMax = op.SafeHeight.Value - end.z
+ normalLength = arcRadius * (1 - math.cos(angleOut))
+ if normalLength > normalLengthMax:
+ # do not exceed Normal vector max length
+ normalLength = normalLengthMax
+ # recalculate angle for limited normal length
+ angleOut = math.acos((arcRadius - normalLength) / arcRadius)
+ tangentLength = arcRadius * math.sin(angleOut)
+ tangent = self.angleToVector(angleTangent) * tangentLength
+ normal = App.Vector(0, 0, normalLength)
+ arcEnd = end + tangent + normal
+ lead.extend(self.createArcZMoveUp(obj, end, arcEnd, arcRadius))
+
+ if obj.StyleOut in ["Arc3d", "Line3d"]:
+ # Up Z end point for Arc3d and Line3d
+ op = PathDressup.baseOp(obj.Base)
+ if outInstrPrev and outInstrPrev.positionBegin().z > end.z:
+ lead[-1].param["Z"] = outInstrPrev.positionBegin().z
+ else:
+ lead[-1].param["Z"] = op.StartDepth.Value
+
+ # append travel moves to cleareance height after finish all profiles
+ if last and obj.StyleOut != "No Retract":
+ lead += self.getTravelEnd(obj)
return lead
- def isCuttingMove(self, obj, instr):
- return (
- instr.isMove()
- and not instr.isRapid()
- and (not obj.IncludeLayers or not instr.isPlunge())
- )
+ # Check command
+ def isCuttingMove(self, instr):
+ result = instr.isMove() and not instr.isRapid() and not instr.isPlunge()
+ return result
- def findLastCuttingMoveIndex(self, obj, source):
+ # Get direction of non cut movements
+ def getMoveDir(self, instr):
+ if instr.positionBegin().z > instr.positionEnd().z:
+ return "Down"
+ elif instr.positionBegin().z < instr.positionEnd().z:
+ return "Up"
+ elif instr.pathLength() != 0:
+ return "Hor"
+ else:
+ # move command without change position
+ return None
+
+ # Get last index of mill command in whole Path
+ def findLastCuttingMoveIndex(self, source):
for i in range(len(source) - 1, -1, -1):
- if self.isCuttingMove(obj, source[i]):
+ if self.isCuttingMove(source[i]):
return i
+
+ return None
+
+ # Get finish index of mill command for one profile
+ def findLastCutMultiProfileIndex(self, source, startIndex):
+ for i in range(startIndex, len(source), +1):
+ if not self.isCuttingMove(source[i]):
+ return i - 1
+
+ return i
+
+ # Increase travel length from begin
+ def getOvertravelIn(self, obj, source, length, start, end):
+ startPoint = source[start].positionBegin()
+ endPoint = source[end].positionEnd()
+
+ if Path.Geom.pointsCoincide(startPoint, endPoint):
+ # closed profile
+ # get extra commands from end of the closed profile
+ measuredLength = 0
+ for i, instr in enumerate(reversed(source[start : end + 1])):
+ instrLength = instr.pathLength()
+
+ if Path.Geom.isRoughly(measuredLength + instrLength, length, 1):
+ # get needed length and not need to cut last command
+ commands = source[end - i : end + 1]
+ return commands
+
+ elif measuredLength + instrLength > length:
+ # measured length exceed needed length and need cut command
+ commands = source[end - i + 1 : end + 1]
+ newLength = length - measuredLength
+ newInstr = self.cutInstrBegin(obj, instr, newLength)
+ commands.insert(0, newInstr)
+ return commands
+
+ measuredLength += instrLength
+
+ else:
+ # open profile
+ # extend first move
+ instr = source[start]
+ newLength = length + instr.pathLength()
+ newInstr = self.cutInstrBegin(obj, instr, newLength)
+ return [newInstr]
+
+ return None
+
+ # Increse travel length from end
+ def getOvertravelOut(self, obj, source, length, start, end):
+ startPoint = source[start].positionBegin()
+ endPoint = source[end].positionEnd()
+
+ if Path.Geom.pointsCoincide(startPoint, endPoint):
+ # closed profile
+ # get extra commands from begin of the closed profile
+ measuredLength = 0
+ for i, instr in enumerate(source[start:end]):
+ instrLength = instr.pathLength()
+
+ if Path.Geom.isRoughly(measuredLength + instrLength, length, 1):
+ # get needed length and not need to cut last command
+ commands = source[start : start + i + 1]
+ return commands
+
+ elif measuredLength + instrLength > length:
+ # measured length exceed needed length and need cut command
+ commands = source[start : start + i]
+ newLength = length - measuredLength
+ newInstr = self.cutInstrEnd(obj, instr, newLength)
+ commands.append(newInstr)
+ return commands
+
+ measuredLength += instrLength
+
+ else:
+ # open profile
+ # extend last move
+ instr = source[end]
+ newLength = length + instr.pathLength()
+ newInstr = self.cutInstrEnd(obj, instr, newLength)
+ return [newInstr]
+
+ return None
+
+ # Cut travel end by distance (negative overtravel out)
+ def cutTravelEnd(self, obj, source, cutLength):
+ measuredLength = 0
+
+ for i, instr in enumerate(reversed(source)):
+ instrLength = instr.pathLength()
+ measuredLength += instrLength
+
+ if Path.Geom.isRoughly(measuredLength, cutLength):
+ # get needed length and not need to cut any command
+ commands = source[: -i - 1]
+ return commands
+
+ elif measuredLength > cutLength:
+ # measured length exceed needed cut length and need cut command
+ commands = source[: -i - 1]
+ newLength = measuredLength - cutLength
+ newInstr = self.cutInstrEnd(obj, instr, newLength)
+ commands.append(newInstr)
+ return commands
+
+ return None
+
+ # Change end point of instruction
+ def cutInstrEnd(self, obj, instr, newLength):
+ command = None
+ # Cut straight move from begin
+ if instr.isStraight():
+ begin = instr.positionBegin()
+ end = instr.positionEnd()
+ v = end - begin
+ n = math.hypot(v.x, v.y)
+ u = v / n
+ cutEnd = begin + u * newLength
+ command = self.createStraightMove(obj, begin, cutEnd)
+
+ # Cut arc move from begin
+ elif instr.isArc():
+ angleTangent = instr.anglesOfTangents()[0]
+ arcBegin = instr.positionBegin()
+ arcOffset = App.Vector(instr.i(), instr.j(), instr.k())
+ arcRadius = instr.arcRadius()
+ arcAngle = newLength / arcRadius
+ tangentLength = math.sin(arcAngle) * arcRadius
+ normalLength = arcRadius * (1 - math.cos(arcAngle))
+ tangent = self.angleToVector(angleTangent) * tangentLength
+ 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)
+
+ return command
+
+ # Change start point of instruction
+ def cutInstrBegin(self, obj, instr, newLength):
+ # Cut straignt move from begin
+ if instr.isStraight():
+ begin = instr.positionBegin()
+ end = instr.positionEnd()
+ v = end - begin
+ n = math.hypot(v.x, v.y)
+ u = v / n
+ newBegin = end - u * newLength
+ command = self.createStraightMove(obj, newBegin, end)
+ return command
+
+ # Cut arc move from begin
+ elif instr.isArc():
+ angleTangent = instr.anglesOfTangents()[1]
+ arcEnd = instr.positionEnd()
+ arcCenter = instr.xyCenter()
+ arcRadius = instr.arcRadius()
+ arcAngle = newLength / arcRadius
+ tangentLength = math.sin(arcAngle) * arcRadius
+ normalLength = arcRadius * (1 - math.cos(arcAngle))
+ tangent = -self.angleToVector(angleTangent) * tangentLength
+ normal = self.angleToVector(angleTangent + self.getPathDir(obj)) * normalLength
+ arcBegin = arcEnd + tangent + normal
+ arcOffset = arcCenter - arcBegin
+ cmdName = "G2" if instr.isCW() else "G3"
+ command = self.createArcMoveN(obj, arcBegin, arcEnd, arcOffset, cmdName)
+ return command
+
return None
def generateLeadInOutCurve(self, obj):
@@ -383,32 +986,120 @@ class ObjectDressup:
# Knowing weather a given instruction is the first cutting move is easy,
# we just use a flag and set it to false afterwards. To find the last
# cutting move we need to search the list in reverse order.
- first = True
- lastCuttingMoveIndex = self.findLastCuttingMoveIndex(obj, source)
+ first = True # prepare first move at cleareance height
+ firstMillIndex = None # Index start mill instruction for one profile
+ lastCuttingMoveIndex = self.findLastCuttingMoveIndex(source)
+ inInstrPrev = None
+ outInstrPrev = None
+ measuredLength = 0 # for negative OffsetIn
+ skipCounter = 0 # for negative OffsetIn
+ commands = []
+ moveDir = None
+
+ # Process all instructions
for i, instr in enumerate(source):
- if not self.isCuttingMove(obj, instr):
- # non-move instructions get added verbatim
- if not instr.isMove():
- maneuver.addInstruction(instr)
- # skip travel and plunge moves, travel moves will be added in
- # getLeadStart and getLeadEnd
+ # Process not mill instruction
+ if not self.isCuttingMove(instr):
+ if not instr.isMove():
+ # non-move instruction get added verbatim
+ commands.append(instr)
+ else:
+ moveDir = self.getMoveDir(instr)
+ if not obj.LeadIn and (moveDir in ["Down", "Hor"] or first):
+ # keep original Lead-in movements
+ commands.append(instr)
+ elif not obj.LeadOut and moveDir in ["Up"] and not first:
+ # keep original Lead-out movements
+ commands.append(instr)
+ # skip travel and plunge moves if LeadInOut will be process
+ # travel moves will be added in getLeadStart and getLeadEnd
continue
- if first or not self.isCuttingMove(obj, source[i - 1]):
- # add lead start and travel moves
- maneuver.addInstructions(self.getLeadStart(obj, instr, first))
+ # measuring length for one profile
+ if self.isCuttingMove(instr):
+ measuredLength += instr.pathLength()
+
+ # Process Lead-In
+ if first or not self.isCuttingMove(source[i - 1 - skipCounter]):
+ if obj.LeadIn:
+ # Process negative Offset Lead-In (cut travel from begin)
+ if obj.OffsetIn.Value < 0 and obj.StyleIn != "No Retract":
+ if measuredLength <= abs(obj.OffsetIn.Value):
+ # skip mill instruction
+ skipCounter += 1 # count skipped instructions
+ continue
+ else:
+ skipCounter = 0
+ # cut mill instruction
+ newLength = measuredLength - abs(obj.OffsetIn.Value)
+ instr = self.cutInstrBegin(obj, instr, newLength)
+
+ # Process positive offset Lead-In (overtravel)
+ firstMillIndex = i
+ lastMillIndex = self.findLastCutMultiProfileIndex(source, i + 1)
+ overtravelIn = None
+ if obj.OffsetIn.Value > 0 and obj.StyleIn != "No Retract":
+ overtravelIn = self.getOvertravelIn(
+ obj,
+ source,
+ obj.OffsetIn.Value,
+ firstMillIndex,
+ lastMillIndex,
+ )
+ if overtravelIn:
+ commands.extend(
+ self.getLeadStart(
+ obj, overtravelIn[0], first, inInstrPrev, outInstrPrev
+ )
+ )
+ commands.extend(overtravelIn)
+ else:
+ commands.extend(
+ self.getLeadStart(obj, instr, first, inInstrPrev, outInstrPrev)
+ )
+ firstMillIndex = i if not firstMillIndex else firstMillIndex
+ inInstrPrev = commands[-1]
first = False
- # add current move
- maneuver.addInstruction(instr)
+ # Add mill instruction
+ commands.append(instr)
+
+ # Process Lead-Out
+ last = bool(i == lastCuttingMoveIndex)
+ if (last or not self.isCuttingMove(source[i + 1])) and obj.LeadOut:
+ measuredLength = 0 # reset measured length for last profile
+ lastMillIndex = i # index last mill instruction for last profile
+
+ # Process negative Offset Lead-Out (cut travel from end)
+ if obj.OffsetOut.Value < 0 and obj.StyleOut != "No Retract":
+ commands = self.cutTravelEnd(obj, commands, abs(obj.OffsetOut.Value))
+
+ # Process positive Offset Lead-Out (overtravel)
+ if obj.OffsetOut.Value > 0 and obj.StyleOut != "No Retract":
+ overtravelOut = self.getOvertravelOut(
+ obj,
+ source,
+ obj.OffsetOut.Value,
+ firstMillIndex,
+ lastMillIndex,
+ )
+ firstMillIndex = None
+ if overtravelOut:
+ commands.extend(overtravelOut)
- last = i == lastCuttingMoveIndex
- if last or not self.isCuttingMove(obj, source[i + 1]):
# add lead end and travel moves
- maneuver.addInstructions(self.getLeadEnd(obj, instr, last))
+ leadEndInstr = self.getLeadEnd(obj, commands[-1], last, inInstrPrev, outInstrPrev)
+ commands.extend(leadEndInstr)
+ # Last mill position to check RetractThreshold
+ if leadEndInstr:
+ outInstrPrev = leadEndInstr[-1]
+ else:
+ outInstrPrev = instr
+
+ maneuver.addInstructions(commands)
return maneuver.toPath()
@@ -417,19 +1108,30 @@ class TaskDressupLeadInOut(SimpleEditPanel):
_ui_file = ":/panels/DressUpLeadInOutEdit.ui"
def setupUi(self):
- self.connectWidget("LeadIn", self.form.chkLeadIn)
- self.connectWidget("LeadOut", self.form.chkLeadOut)
- self.connectWidget("LengthIn", self.form.dspLenIn)
- self.connectWidget("LengthOut", self.form.dspLenOut)
- self.connectWidget("ExtendLeadIn", self.form.dspExtendIn)
- self.connectWidget("ExtendLeadOut", self.form.dspExtendOut)
+ self.connectWidget("InvertIn", self.form.chkInvertDirectionIn)
+ self.connectWidget("InvertOut", self.form.chkInvertDirectionOut)
+ 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.connectWidget("AngleIn", self.form.dspAngleIn)
+ self.connectWidget("AngleOut", self.form.dspAngleOut)
+ self.connectWidget("OffsetIn", self.form.dspOffsetIn)
+ self.connectWidget("OffsetOut", self.form.dspOffsetOut)
self.connectWidget("RapidPlunge", self.form.chkRapidPlunge)
- self.connectWidget("IncludeLayers", self.form.chkLayers)
- self.connectWidget("KeepToolDown", self.form.chkKeepToolDown)
self.setFields()
+ styleEnum = self.obj.getEnumerationsOfProperty("StyleIn")
+
+ def handleGroupBoxCheck():
+ self.obj.LeadIn = self.form.groupBoxIn.isChecked()
+ self.obj.LeadOut = self.form.groupBoxOut.isChecked()
+
+ self.form.groupBoxIn.setChecked(self.obj.LeadIn)
+ self.form.groupBoxOut.setChecked(self.obj.LeadOut)
+ self.form.groupBoxIn.clicked.connect(handleGroupBoxCheck)
+ self.form.groupBoxOut.clicked.connect(handleGroupBoxCheck)
+
class ViewProviderDressup:
def __init__(self, vobj):
@@ -489,7 +1191,7 @@ class CommandPathDressupLeadInOut:
"MenuText": QT_TRANSLATE_NOOP("CAM_DressupLeadInOut", "Lead In/Out"),
"ToolTip": QT_TRANSLATE_NOOP(
"CAM_DressupLeadInOut",
- "Creates a cutter radius compensation G41/G42 entry dressup object from a selected path",
+ "Creates entry and exit motions for a selected path",
),
}
diff --git a/src/Mod/CAM/PathPythonGui/simple_edit_panel.py b/src/Mod/CAM/PathPythonGui/simple_edit_panel.py
index a581b85451..0c03ba86f5 100644
--- a/src/Mod/CAM/PathPythonGui/simple_edit_panel.py
+++ b/src/Mod/CAM/PathPythonGui/simple_edit_panel.py
@@ -5,7 +5,11 @@ from PySide import QtGui
translate = FreeCAD.Qt.translate
PROP_TYPE_QTYES = ["App::PropertyDistance", "App::PropertyAngle"]
-PROP_TYPE_NUMERIC = PROP_TYPE_QTYES + ["App::PropertyPercent", "App:PropertyFloat"]
+PROP_TYPE_NUMERIC = PROP_TYPE_QTYES + [
+ "App::PropertyPercent",
+ "App::PropertyInteger",
+ "App:PropertyFloat",
+]
class SimpleEditPanel:
@@ -30,7 +34,10 @@ class SimpleEditPanel:
def getFields(self):
for prop_name, (get_field, set_field) in self._fc.items():
- setattr(self.obj, prop_name, get_field())
+ val = get_field()
+ if isinstance(getattr(self.obj, prop_name), int):
+ val = int(val)
+ setattr(self.obj, prop_name, val)
def setFields(self):
for prop_name, (get_field, set_field) in self._fc.items():