Path: add plunge angle feature for PathProfile
With the new property "PlungeAngle", one can specify the inclination of a ramp into the material, instead of plunging straight down. The original behaviour and default is set to 90.0 degrees. A value of zero makes the tool descent exactly one layer depth down per turn.
This commit is contained in:
parent
d51422347c
commit
c862fc0065
|
@ -435,6 +435,16 @@
|
|||
<item row="2" column="1">
|
||||
<widget class="QDoubleSpinBox" name="rollRadius"/>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_13">
|
||||
<property name="text">
|
||||
<string>Plunge Angle</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QDoubleSpinBox" name="plungeAngle"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
|
@ -98,6 +98,7 @@ class ObjectProfile:
|
|||
obj.addProperty("App::PropertyDistance", "RollRadius", "Profile", "Radius at start and end")
|
||||
obj.addProperty("App::PropertyDistance", "OffsetExtra", "Profile", "Extra value to stay away from final profile- good for roughing toolpath")
|
||||
obj.addProperty("App::PropertyLength", "SegLen", "Profile", "Tesselation value for tool paths made from beziers, bsplines, and ellipses")
|
||||
obj.addProperty("App::PropertyAngle", "PlungeAngle", "Profile", "Plunge angle with which the tool enters the work piece. Straight down is 90 degrees, if set small enough or zero the tool will descent exactly one layer depth down per turn")
|
||||
|
||||
obj.addProperty("App::PropertyVectorList", "locs", "Tags", "List of holding tag locations")
|
||||
|
||||
|
@ -183,7 +184,7 @@ class ObjectProfile:
|
|||
wire, obj.Side, self.radius, clockwise,
|
||||
obj.ClearanceHeight.Value, obj.StepDown, obj.StartDepth.Value,
|
||||
obj.FinalDepth.Value, FirstEdge, PathClosed, obj.SegLen.Value,
|
||||
self.vertFeed, self.horizFeed)
|
||||
self.vertFeed, self.horizFeed, PlungeAngle=obj.PlungeAngle.Value)
|
||||
|
||||
return output
|
||||
|
||||
|
@ -466,6 +467,7 @@ class CommandPathProfile:
|
|||
FreeCADGui.doCommand('obj.OffsetExtra = 0.0')
|
||||
FreeCADGui.doCommand('obj.Direction = "CW"')
|
||||
FreeCADGui.doCommand('obj.UseComp = False')
|
||||
FreeCADGui.doCommand('obj.PlungeAngle = 90.0')
|
||||
FreeCADGui.doCommand('PathScripts.PathUtils.addToProject(obj)')
|
||||
|
||||
FreeCAD.ActiveDocument.commitTransaction()
|
||||
|
@ -509,6 +511,8 @@ class TaskPanel:
|
|||
self.obj.SegLen = self.form.segLen.value()
|
||||
if hasattr(self.obj, "RollRadius"):
|
||||
self.obj.RollRadius = self.form.rollRadius.value()
|
||||
if hasattr(self.obj, "PlungeAngle"):
|
||||
self.obj.PlungeAngle = str(self.form.plungeAngle.value())
|
||||
if hasattr(self.obj, "UseComp"):
|
||||
self.obj.UseComp = self.form.useCompensation.isChecked()
|
||||
if hasattr(self.obj, "UseStartPoint"):
|
||||
|
@ -523,7 +527,7 @@ class TaskPanel:
|
|||
self.obj.Direction = str(self.form.direction.currentText())
|
||||
self.obj.Proxy.execute(self.obj)
|
||||
|
||||
def setFields(self):
|
||||
def setFields(self):
|
||||
self.form.startDepth.setText(str(self.obj.StartDepth.Value))
|
||||
self.form.finalDepth.setText(str(self.obj.FinalDepth.Value))
|
||||
self.form.safeHeight.setText(str(self.obj.SafeHeight.Value))
|
||||
|
@ -532,6 +536,7 @@ class TaskPanel:
|
|||
self.form.extraOffset.setValue(self.obj.OffsetExtra.Value)
|
||||
self.form.segLen.setValue(self.obj.SegLen.Value)
|
||||
self.form.rollRadius.setValue(self.obj.RollRadius.Value)
|
||||
self.form.plungeAngle.setValue(self.obj.PlungeAngle.Value)
|
||||
self.form.useCompensation.setChecked(self.obj.UseComp)
|
||||
self.form.useStartPoint.setChecked(self.obj.UseStartPoint)
|
||||
self.form.useEndPoint.setChecked(self.obj.UseEndPoint)
|
||||
|
|
|
@ -184,18 +184,21 @@ def reverseEdge(e):
|
|||
return newedge
|
||||
|
||||
|
||||
def convert(toolpath, Side, radius, clockwise=False, Z=0.0, firstedge=None, vf=1.0, hf=2.0):
|
||||
def convert(toolpath, Side, radius, clockwise=False, Z=0.0, firstedge=None, vf=1.0, hf=2.0, PlungeAngle=90.0, Zprevious=None, StopLength=None):
|
||||
'''convert(toolpath,Side,radius,clockwise=False,Z=0.0,firstedge=None) Converts lines and arcs to G1,G2,G3 moves. Returns a string.'''
|
||||
last = None
|
||||
output = ""
|
||||
# create the path from the offset shape
|
||||
for edge in toolpath:
|
||||
if not last:
|
||||
# set the first point
|
||||
last = edge.Vertexes[0].Point
|
||||
# FreeCAD.Console.PrintMessage("last pt= " + str(last)+ "\n")
|
||||
output += "G1 X" + str(fmt(last.x)) + " Y" + str(fmt(last.y)) + \
|
||||
" Z" + str(fmt(Z)) + " F" + str(vf) + "\n"
|
||||
|
||||
if PlungeAngle != 90.0:
|
||||
if Zprevious is None:
|
||||
raise Exception("Cannot use PlungeAngle != 90.0 degrees without parameter Zprevious")
|
||||
tanA = math.tan(math.pi * PlungeAngle / 180.0)
|
||||
minA = (Zprevious - Z) / sum(edge.Length for edge in toolpath)
|
||||
if tanA < minA:
|
||||
tanA = minA
|
||||
#FreeCAD.Console.PrintMessage('Increasing ramp angle to {0} degrees, to be able to make a full round\n'.format(math.atan(tanA) * 180.0 / math.pi))
|
||||
else:
|
||||
Zprevious = Z
|
||||
|
||||
def edge_to_path(lastpt, edge, Z):
|
||||
if isinstance(edge.Curve, Part.Circle):
|
||||
# FreeCAD.Console.PrintMessage("arc\n")
|
||||
arcstartpt = edge.valueAt(edge.FirstParameter)
|
||||
|
@ -204,14 +207,14 @@ def convert(toolpath, Side, radius, clockwise=False, Z=0.0, firstedge=None, vf=1
|
|||
arcendpt = edge.valueAt(edge.LastParameter)
|
||||
# arcchkpt = edge.valueAt(edge.LastParameter * .99)
|
||||
|
||||
if DraftVecUtils.equals(last, arcstartpt):
|
||||
if DraftVecUtils.equals(lastpt, arcstartpt):
|
||||
startpt = arcstartpt
|
||||
endpt = arcendpt
|
||||
else:
|
||||
startpt = arcendpt
|
||||
endpt = arcstartpt
|
||||
center = edge.Curve.Center
|
||||
relcenter = center.sub(last)
|
||||
relcenter = center.sub(lastpt)
|
||||
# FreeCAD.Console.PrintMessage("arc startpt= " + str(startpt)+ "\n")
|
||||
# FreeCAD.Console.PrintMessage("arc midpt= " + str(midpt)+ "\n")
|
||||
# FreeCAD.Console.PrintMessage("arc endpt= " + str(endpt)+ "\n")
|
||||
|
@ -219,27 +222,82 @@ def convert(toolpath, Side, radius, clockwise=False, Z=0.0, firstedge=None, vf=1
|
|||
[(startpt.x, startpt.y), (midpt.x, midpt.y), (endpt.x, endpt.y)])
|
||||
# FreeCAD.Console.PrintMessage("arc_cw="+ str(arc_cw)+"\n")
|
||||
if arc_cw:
|
||||
output += "G2"
|
||||
output = "G2"
|
||||
else:
|
||||
output += "G3"
|
||||
output = "G3"
|
||||
output += " X" + str(fmt(endpt.x)) + " Y" + \
|
||||
str(fmt(endpt.y)) + " Z" + str(fmt(Z)) + " F" + str(hf)
|
||||
output += " I" + str(fmt(relcenter.x)) + " J" + \
|
||||
str(fmt(relcenter.y)) + " K" + str(fmt(relcenter.z))
|
||||
output += "\n"
|
||||
last = endpt
|
||||
# FreeCAD.Console.PrintMessage("last pt arc= " + str(last)+ "\n")
|
||||
lastpt = endpt
|
||||
# FreeCAD.Console.PrintMessage("last pt arc= " + str(lastpt)+ "\n")
|
||||
else:
|
||||
point = edge.Vertexes[-1].Point
|
||||
if DraftVecUtils.equals(point, last): # edges can come flipped
|
||||
if DraftVecUtils.equals(point, lastpt): # edges can come flipped
|
||||
point = edge.Vertexes[0].Point
|
||||
output += "G1 X" + str(fmt(point.x)) + " Y" + str(fmt(point.y)) + \
|
||||
output = "G1 X" + str(fmt(point.x)) + " Y" + str(fmt(point.y)) + \
|
||||
" Z" + str(fmt(Z)) + " F" + str(hf) + "\n"
|
||||
last = point
|
||||
lastpt = point
|
||||
# FreeCAD.Console.PrintMessage("line\n")
|
||||
# FreeCAD.Console.PrintMessage("last pt line= " + str(last)+ "\n")
|
||||
return output
|
||||
# FreeCAD.Console.PrintMessage("last pt line= " + str(lastpt)+ "\n")
|
||||
return lastpt, output
|
||||
|
||||
lastpt = None
|
||||
output = ""
|
||||
path_length = 0.0
|
||||
Z_cur = Zprevious
|
||||
|
||||
# create the path from the offset shape
|
||||
for edge in toolpath:
|
||||
if not lastpt:
|
||||
# set the first point
|
||||
lastpt = edge.Vertexes[0].Point
|
||||
# FreeCAD.Console.PrintMessage("last pt= " + str(lastpt)+ "\n")
|
||||
output += "G1 X" + str(fmt(lastpt.x)) + " Y" + str(fmt(lastpt.y)) + \
|
||||
" Z" + str(fmt(Z_cur)) + " F" + str(vf) + "\n"
|
||||
|
||||
if StopLength:
|
||||
if path_length + edge.Length > StopLength:
|
||||
# have to split current edge in two
|
||||
t0 = edge.FirstParameter
|
||||
t1 = edge.LastParameter
|
||||
dL = StopLength - path_length
|
||||
t = t0 + (t1 - t0) * dL / edge.Length
|
||||
assert(t0 < t < t1)
|
||||
edge = edge.split(t).Edges[0]
|
||||
path_length = StopLength
|
||||
else:
|
||||
path_length += edge.Length
|
||||
else:
|
||||
path_length += edge.Length
|
||||
|
||||
if Z_cur > Z:
|
||||
Z_next = Zprevious - path_length * tanA
|
||||
if Z_next < Z:
|
||||
# have to split current edge in two
|
||||
t0 = edge.FirstParameter
|
||||
t1 = edge.LastParameter
|
||||
dZ = Z_cur - Z
|
||||
t = t0 + (t1 - t0) * (dZ / tanA) / edge.Length
|
||||
assert(t0 < t < t1)
|
||||
subwire = edge.split(t)
|
||||
assert(len(subwire.Edges) == 2)
|
||||
Z_cur = Z
|
||||
lastpt, codes = edge_to_path(lastpt, subwire.Edges[0], Z_cur)
|
||||
output += codes
|
||||
edge = subwire.Edges[1]
|
||||
else:
|
||||
Z_cur = Z_next
|
||||
|
||||
lastpt, codes = edge_to_path(lastpt, edge, Z_cur)
|
||||
output += codes
|
||||
|
||||
if StopLength:
|
||||
if path_length >= StopLength:
|
||||
break
|
||||
|
||||
return output
|
||||
|
||||
def SortPath(wire, Side, radius, clockwise, firstedge=None, SegLen=0.5):
|
||||
'''SortPath(wire,Side,radius,clockwise,firstedge=None,SegLen =0.5) Sorts the wire and reverses it, if needed. Splits arcs over 180 degrees in two. Returns the reordered offset of the wire. '''
|
||||
|
@ -308,9 +366,9 @@ def SortPath(wire, Side, radius, clockwise, firstedge=None, SegLen=0.5):
|
|||
return offset
|
||||
|
||||
|
||||
def MakePath(wire, Side, radius, clockwise, ZClearance, StepDown, ZStart, ZFinalDepth, firstedge=None, PathClosed=True, SegLen=0.5, VertFeed=1.0, HorizFeed=2.0):
|
||||
def MakePath(wire, Side, radius, clockwise, ZClearance, StepDown, ZStart, ZFinalDepth, firstedge=None, PathClosed=True, SegLen=0.5, VertFeed=1.0, HorizFeed=2.0, PlungeAngle=90.0):
|
||||
''' makes the path - just a simple profile for now '''
|
||||
offset = SortPath(wire, Side, radius, clockwise, firstedge, SegLen=0.5)
|
||||
offset = SortPath(wire, Side, radius, clockwise, firstedge, SegLen=SegLen)
|
||||
if len(offset.Edges) == 0:
|
||||
return ""
|
||||
|
||||
|
@ -319,26 +377,40 @@ def MakePath(wire, Side, radius, clockwise, ZClearance, StepDown, ZStart, ZFinal
|
|||
paths += "G0 Z" + str(ZClearance) + "\n"
|
||||
first = toolpath[0].Vertexes[0].Point
|
||||
paths += "G0 X" + str(fmt(first.x)) + "Y" + str(fmt(first.y)) + "\n"
|
||||
Zprevious = ZStart
|
||||
ZCurrent = ZStart - StepDown
|
||||
if PathClosed:
|
||||
while ZCurrent > ZFinalDepth:
|
||||
paths += convert(toolpath, Side, radius, clockwise,
|
||||
ZCurrent, firstedge, VertFeed, HorizFeed)
|
||||
ZCurrent, firstedge, VertFeed, HorizFeed, PlungeAngle=PlungeAngle, Zprevious=Zprevious)
|
||||
Zprevious = ZCurrent
|
||||
ZCurrent = ZCurrent - abs(StepDown)
|
||||
paths += convert(toolpath, Side, radius, clockwise,
|
||||
ZFinalDepth, firstedge, VertFeed, HorizFeed)
|
||||
paths += "G0 Z" + str(ZClearance)
|
||||
ZFinalDepth, firstedge, VertFeed, HorizFeed, PlungeAngle=PlungeAngle, Zprevious=Zprevious)
|
||||
else:
|
||||
while ZCurrent > ZFinalDepth:
|
||||
paths += convert(toolpath, Side, radius, clockwise,
|
||||
ZCurrent, firstedge, VertFeed, HorizFeed)
|
||||
ZCurrent, firstedge, VertFeed, HorizFeed, PlungeAngle=PlungeAngle, Zprevious=Zprevious)
|
||||
paths += "G0 Z" + str(ZClearance)
|
||||
paths += "G0 X" + str(fmt(first.x)) + "Y" + \
|
||||
str(fmt(first.y)) + "\n"
|
||||
Zprevious = ZCurrent
|
||||
ZCurrent = ZCurrent - abs(StepDown)
|
||||
paths += convert(toolpath, Side, radius, clockwise,
|
||||
ZFinalDepth, firstedge, VertFeed, HorizFeed)
|
||||
paths += "G0 Z" + str(ZClearance)
|
||||
ZFinalDepth, firstedge, VertFeed, HorizFeed, PlungeAngle=PlungeAngle, Zprevious=Zprevious)
|
||||
|
||||
|
||||
# have to do one last pass to clear the remaining ramp
|
||||
if PlungeAngle != 90.0:
|
||||
tanA = math.tan(math.pi * PlungeAngle / 180.0)
|
||||
if tanA <= 0.0:
|
||||
StopLength=None
|
||||
else:
|
||||
StopLength=abs(StepDown/tanA)
|
||||
paths += convert(toolpath, Side, radius, clockwise,
|
||||
ZFinalDepth, firstedge, VertFeed, HorizFeed, StopLength=StopLength)
|
||||
|
||||
paths += "G0 Z" + str(ZClearance)
|
||||
return paths
|
||||
|
||||
# the next two functions are for automatically populating tool
|
||||
|
|
Loading…
Reference in New Issue
Block a user