Smooth path with fillets.
This commit is contained in:
parent
0ba1030163
commit
23713a2c51
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
Loading…
Reference in New Issue
Block a user