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:
Lorenz Hüdepohl 2016-06-12 15:23:11 +02:00 committed by wmayer
parent d51422347c
commit c862fc0065
3 changed files with 119 additions and 32 deletions

View File

@ -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>

View File

@ -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)

View File

@ -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