Smooth path with fillets.

This commit is contained in:
ml 2016-10-16 23:46:00 -07:00 committed by Markus Lampert
parent 0ba1030163
commit 23713a2c51
3 changed files with 244 additions and 181 deletions

View File

@ -1717,7 +1717,15 @@ def fillet(lEdges,r,chamfer=False):
rndEdges[1] = Part.Edge(Part.Line(arcPt1,arcPt3)) rndEdges[1] = Part.Edge(Part.Line(arcPt1,arcPt3))
else: else:
rndEdges[1] = Part.Edge(Part.Arc(arcPt1,arcPt2,arcPt3)) rndEdges[1] = Part.Edge(Part.Arc(arcPt1,arcPt2,arcPt3))
if lVertexes[0].Point == arcPt1:
# fillet consumes entire first edge
rndEdges.pop(0)
else:
rndEdges[0] = Part.Edge(Part.Line(lVertexes[0].Point,arcPt1)) rndEdges[0] = Part.Edge(Part.Line(lVertexes[0].Point,arcPt1))
if lVertexes[2].Point != arcPt3:
# fillet does not consume entire second edge
rndEdges += [Part.Edge(Part.Line(arcPt3,lVertexes[2].Point))] rndEdges += [Part.Edge(Part.Line(arcPt3,lVertexes[2].Point))]
return rndEdges return rndEdges

View File

@ -27,6 +27,8 @@ import Path
from PathScripts import PathUtils from PathScripts import PathUtils
from PySide import QtCore, QtGui from PySide import QtCore, QtGui
import math import math
import Part
import DraftGeomUtils
"""Dogbone Dressup object and FreeCAD command""" """Dogbone Dressup object and FreeCAD command"""
@ -45,6 +47,20 @@ except AttributeError:
movecommands = ['G0', 'G00', 'G1', 'G01', 'G2', 'G02', 'G3', 'G03'] movecommands = ['G0', 'G00', 'G1', 'G01', 'G2', 'G02', 'G3', 'G03']
movestraight = ['G1', 'G01'] movestraight = ['G1', 'G01']
def debugMarker(vector, label):
obj = FreeCAD.ActiveDocument.addObject("Part::Sphere", label)
obj.Label = label
obj.Radius = 0.5
obj.Placement = FreeCAD.Placement(vector, FreeCAD.Rotation(FreeCAD.Vector(0,0,1), 0))
def debugCircle(vector, r, label):
obj = FreeCAD.ActiveDocument.addObject("Part::Cylinder", label)
obj.Label = label
obj.Radius = r
obj.Height = 1
obj.Placement = FreeCAD.Placement(vector, FreeCAD.Rotation(FreeCAD.Vector(0,0,1), 0))
obj.ViewObject.Transparency = 95
class Style: class Style:
Dogbone = 'Dogbone' Dogbone = 'Dogbone'
Tbone_H = 'T-bone horizontal' Tbone_H = 'T-bone horizontal'
@ -155,6 +171,9 @@ class Chord (object):
def g1Command(self): def g1Command(self):
return Path.Command("G1", {"X": self.End.x, "Y": self.End.y}) return Path.Command("G1", {"X": self.End.x, "Y": self.End.y})
def arcCommand(self, orientation):
return self.g1Command()
def isAPlungeMove(self): def isAPlungeMove(self):
return self.End.z != self.Start.z return self.End.z != self.Start.z
@ -195,7 +214,14 @@ class Chord (object):
else: else:
print("Now this really sucks") print("Now this really sucks")
return (x, y) return FreeCAD.Vector(x, y, self.End.z)
def perpendicular(self):
v = self.asVector()
return FreeCAD.Vector(-v.y, v.x, 0)
def footOfPerpendicularFrom(self, vector):
return self.intersection(Chord(vector, vector + self.perpendicular()))
class ObjectDressup: class ObjectDressup:
@ -235,21 +261,11 @@ class ObjectDressup:
def shouldInsertDogbone(self, obj, inChord, outChord): def shouldInsertDogbone(self, obj, inChord, outChord):
return outChord.foldsBackOrTurns(inChord, self.theOtherSideOf(obj.Side)) return outChord.foldsBackOrTurns(inChord, self.theOtherSideOf(obj.Side))
# draw circles where dogbones go, easier to spot during testing
def debugCircleBone(self, obj,inChord, outChord):
di = 0.
dj = 0.5
if inChord.Start.x < outChord.End.x:
dj = -dj
circle = Path.Command("G1", {"I": di, "J": dj}).Parameters
circle.update({"X": inChord.End.x, "Y": inChord.End.y})
return [ Path.Command("G3", circle) ]
def adaptiveBoneLength(self, obj, inChord, outChord, angle): def adaptiveBoneLength(self, obj, inChord, outChord, angle):
iChord = inChord.offsetBy(self.toolRadius) iChord = inChord.offsetBy(self.toolRadius)
oChord = outChord.offsetBy(self.toolRadius) oChord = outChord.offsetBy(self.toolRadius)
x,y = iChord.intersection(oChord, self.toolRadius) v = iChord.intersection(oChord, self.toolRadius)
dest = inChord.moveTo(FreeCAD.Vector(x, y, inChord.End.z)) dest = inChord.moveTo(FreeCAD.Vector(v.x, v.y, v.z))
destAngle = dest.getAngleXY() destAngle = dest.getAngleXY()
distance = dest.getLength() - self.toolRadius * math.fabs(math.cos(destAngle - angle)) distance = dest.getLength() - self.toolRadius * math.fabs(math.cos(destAngle - angle))
#print("adapt") #print("adapt")
@ -258,13 +274,43 @@ class ObjectDressup:
#print(" = (%.2f, %.2f) -> %.2f (%.2f %.2f) -> %.2f" % (x, y, dest.getLength(), destAngle/math.pi, angle/math.pi, distance)) #print(" = (%.2f, %.2f) -> %.2f (%.2f %.2f) -> %.2f" % (x, y, dest.getLength(), destAngle/math.pi, angle/math.pi, distance))
return distance return distance
def smoothChordCommands(self, inChord, outChord, smooth): def smoothChordCommands(self, inChord, outChord, side, smooth):
if smooth == 0: if smooth == 0:
return [ inChord.g1Command(), outChord.g1Command() ] return [ inChord.g1Command(), outChord.g1Command() ]
print("(%.2f, %.2f) -> (%.2f, %.2f) -> (%.2f, %.2f)" % (inChord.Start.x, inChord.Start.y, inChord.End.x, inChord.End.y, outChord.End.x, outChord.End.y))
inAngle = inChord.getAngleXY() inAngle = inChord.getAngleXY()
outAngle = outChord.getAngleXY() outAngle = outChord.getAngleXY()
if inAngle == outAngle: # straight line, combine g1 print(" inAngle = %.2f outAngle = %.2f" % (inAngle/math.pi, outAngle/math.pi))
return [ Chord(inChord.Start, outChord.End).g1Command() ] if inAngle == outAngle: # straight line, outChord includes inChord
print(" ---> (%.2f, %.2f)" %(outChord.End.x, outChord.End.y))
return [ outChord.g1Command() ]
print("%s :: %s" % (inChord, outChord))
inEdge = DraftGeomUtils.edg(inChord.Start, inChord.End)
outEdge = DraftGeomUtils.edg(outChord.Start, outChord.End)
#wire = Part.Wire([inEdge, outEdge])
#print(" => %s" % wire)
#wire = wire.makeOffset2D(self.toolRadius)
#print(" ==> %s" % wire)
#wire = wire.makeOffset2D(-self.toolRadius)
#print(" ===> %s" % wire)
radius = self.toolRadius
while radius > 0:
lastpt = None
commands = ""
edges = DraftGeomUtils.fillet([inEdge, outEdge], radius)
if DraftGeomUtils.isSameLine(edges[0], inEdge) or DraftGeomUtils.isSameLine(edges[1], inEdge):
print("Oh, we got a problem, try smaller radius")
radius = radius - 0.1 * self.toolRadius
continue
print("we're good")
#for edge in wire.Edges[:-1]: # makeOffset2D closes the wire
for edge in edges:
if not lastpt:
lastpt = edge.Vertexes[0].Point
lastpt, cmds = PathUtils.edge_to_path(lastpt, edge, 0)
commands += cmds
path = Path.Path(commands)
return path.Commands
return [ inChord.g1Command(), outChord.g1Command() ] return [ inChord.g1Command(), outChord.g1Command() ]
def inOutBoneCommands(self, obj, inChord, outChord, angle, fixedLength, smooth): def inOutBoneCommands(self, obj, inChord, outChord, angle, fixedLength, smooth):
@ -279,9 +325,12 @@ class ObjectDressup:
boneInChord = inChord.moveBy(x, y, 0) boneInChord = inChord.moveBy(x, y, 0)
boneOutChord = boneInChord.moveTo(outChord.Start) boneOutChord = boneInChord.moveTo(outChord.Start)
debugCircle(boneInChord.Start, self.toolRadius, 'boneStart')
debugCircle(boneInChord.End, self.toolRadius, 'boneEnd')
bones = [] bones = []
bones.extend(self.smoothChordCommands(inChord, boneInChord, smooth & Smooth.In)) bones.extend(self.smoothChordCommands(inChord, boneInChord, obj.Side, smooth & Smooth.In))
bones.extend(self.smoothChordCommands(boneOutChord, outChord, smooth & Smooth.Out)) bones.extend(self.smoothChordCommands(boneOutChord, outChord, obj.Side, smooth & Smooth.Out))
return bones return bones
def dogboneAngle(self, obj, inChord, outChord): def dogboneAngle(self, obj, inChord, outChord):
@ -349,12 +398,7 @@ class ObjectDressup:
return (blacklisted, parentConsumed) return (blacklisted, parentConsumed)
# Generate commands necessary to execute the dogbone # Generate commands necessary to execute the dogbone
def boneCommands(self, obj, boneId, inChord, outChord, smooth): def boneCommands(self, obj, enabled, inChord, outChord, smooth):
loc = (inChord.End.x, inChord.End.y)
blacklisted, inaccessible = self.boneIsBlacklisted(obj, boneId, loc)
enabled = not blacklisted
self.bones.append((boneId, loc, enabled, inaccessible))
if enabled: if enabled:
if obj.Style == Style.Dogbone: if obj.Style == Style.Dogbone:
return self.dogbone(obj, inChord, outChord, smooth) return self.dogbone(obj, inChord, outChord, smooth)
@ -366,13 +410,22 @@ class ObjectDressup:
return self.tboneLongEdge(obj, inChord, outChord, smooth) return self.tboneLongEdge(obj, inChord, outChord, smooth)
if obj.Style == Style.Tbone_S: if obj.Style == Style.Tbone_S:
return self.tboneShortEdge(obj, inChord, outChord, smooth) return self.tboneShortEdge(obj, inChord, outChord, smooth)
return self.debugCircleBone(obj, inChord, outChord)
else: else:
return [] return [ inChord.g1Command(), outChord.g1Command() ]
def insertBone(self, boneId, obj, inChord, outChord, commands, smooth): def insertBone(self, boneId, obj, inChord, outChord, commands, smooth):
bones = self.boneCommands(obj, boneId, inChord, outChord, smooth) print(">----------------------------------- %d --------------------------------------" % boneId)
loc = (inChord.End.x, inChord.End.y)
blacklisted, inaccessible = self.boneIsBlacklisted(obj, boneId, loc)
enabled = not blacklisted
self.bones.append((boneId, loc, enabled, inaccessible))
if boneId < 9:
bones = self.boneCommands(obj, enabled, inChord, outChord, smooth)
else:
bones = self.boneCommands(obj, False, inChord, outChord, smooth)
commands.extend(bones[:-1]) commands.extend(bones[:-1])
print("<----------------------------------- %d --------------------------------------" % boneId)
return boneId + 1, bones[-1] return boneId + 1, bones[-1]
def execute(self, obj): def execute(self, obj):
@ -424,6 +477,8 @@ class ObjectDressup:
commands.append(lastCommand) commands.append(lastCommand)
lastCommand = None lastCommand = None
commands.append(thisCmd) commands.append(thisCmd)
#for cmd in commands:
# print("cmd = '%s'" % cmd)
path = Path.Path(commands) path = Path.Path(commands)
obj.Path = path obj.Path = path

View File

@ -233,22 +233,7 @@ def reverseEdge(e):
return newedge return newedge
def edge_to_path(lastpt, edge, Z, hf=2.0):
def convert(toolpath, Z=0.0, PlungeAngle=90.0, Zprevious=None, StopLength=None, vf=1.0, hf=2.0) :
'''convert(toolpath,Z=0.0,vf=1.0,hf=2.0,PlungeAngle=90.0,Zprevious=None,StopLength=None) Converts lines and arcs to G1,G2,G3 moves. Returns a string.'''
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): if isinstance(edge.Curve, Part.Circle):
# FreeCAD.Console.PrintMessage("arc\n") # FreeCAD.Console.PrintMessage("arc\n")
arcstartpt = edge.valueAt(edge.FirstParameter) arcstartpt = edge.valueAt(edge.FirstParameter)
@ -293,6 +278,21 @@ def convert(toolpath, Z=0.0, PlungeAngle=90.0, Zprevious=None, StopLength=None,
# FreeCAD.Console.PrintMessage("last pt line= " + str(lastpt)+ "\n") # FreeCAD.Console.PrintMessage("last pt line= " + str(lastpt)+ "\n")
return lastpt, output return lastpt, output
def convert(toolpath, Z=0.0, PlungeAngle=90.0, Zprevious=None, StopLength=None, vf=1.0, hf=2.0) :
'''convert(toolpath,Z=0.0,vf=1.0,hf=2.0,PlungeAngle=90.0,Zprevious=None,StopLength=None) Converts lines and arcs to G1,G2,G3 moves. Returns a string.'''
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
lastpt = None lastpt = None
output = "" output = ""
path_length = 0.0 path_length = 0.0
@ -334,13 +334,13 @@ def convert(toolpath, Z=0.0, PlungeAngle=90.0, Zprevious=None, StopLength=None,
subwire = edge.split(t) subwire = edge.split(t)
assert(len(subwire.Edges) == 2) assert(len(subwire.Edges) == 2)
Z_cur = Z Z_cur = Z
lastpt, codes = edge_to_path(lastpt, subwire.Edges[0], Z_cur) lastpt, codes = edge_to_path(lastpt, subwire.Edges[0], Z_cur, hf)
output += codes output += codes
edge = subwire.Edges[1] edge = subwire.Edges[1]
else: else:
Z_cur = Z_next Z_cur = Z_next
lastpt, codes = edge_to_path(lastpt, edge, Z_cur) lastpt, codes = edge_to_path(lastpt, edge, Z_cur, hf)
output += codes output += codes
if StopLength: if StopLength: