[CAM] Fix ramp dressup performance (#21944)
* [CAM] fix biggest performance problems in ramp dressup key items: - finding the index of the current edge with edeges.index(edge) was very expensive; fixed by tracking the index while looping - checking which edges were rapids with edge equality was also expensive; fixed by keeping a list of indexes of rapid input edges, and tagging output edges with whether or not they are rapids * [CAM] comment out Path.Log.debug in hot segments of ramping code Even when low level logs are supposed to be suppressed, Path.Log.debug takes take invoking traceback.extract_stack. This time adds up if logs are invoked in frequently run loops. * Fix CAM test * [CAM] reimplment ramp method 1 with faster code * [CAM] reimplement ramp methods 2, 3, and helix * [CAM] patch to make output match original * [CAM] ramping full performance + functionality fix
This commit is contained in:
@@ -557,7 +557,7 @@ class TestPathGeom(PathTestBase):
|
||||
commands.append(Path.Command("G0", {"X": 0}))
|
||||
commands.append(Path.Command("G1", {"Y": 0}))
|
||||
|
||||
wire, rapid = Path.Geom.wireForPath(Path.Path(commands))
|
||||
wire, rapid, rapid_indexes = Path.Geom.wireForPath(Path.Path(commands))
|
||||
self.assertEqual(len(wire.Edges), 4)
|
||||
self.assertLine(wire.Edges[0], Vector(0, 0, 0), Vector(1, 0, 0))
|
||||
self.assertLine(wire.Edges[1], Vector(1, 0, 0), Vector(1, 1, 0))
|
||||
|
||||
@@ -28,7 +28,6 @@ import Path
|
||||
import Path.Base.Language as PathLanguage
|
||||
import Path.Dressup.Utils as PathDressup
|
||||
import PathScripts.PathUtils as PathUtils
|
||||
from Path.Geom import wireForPath
|
||||
import math
|
||||
|
||||
__doc__ = """LeadInOut Dressup USE ROLL-ON ROLL-OFF to profile"""
|
||||
@@ -130,9 +129,6 @@ class ObjectDressup:
|
||||
)
|
||||
obj.Proxy = self
|
||||
|
||||
self.wire = None
|
||||
self.rapids = None
|
||||
|
||||
def dumps(self):
|
||||
return None
|
||||
|
||||
@@ -172,7 +168,6 @@ class ObjectDressup:
|
||||
)
|
||||
obj.LengthOut = 0.1
|
||||
|
||||
self.wire, self.rapids = wireForPath(PathUtils.getPathWithPlacement(obj.Base))
|
||||
obj.Path = self.generateLeadInOutCurve(obj)
|
||||
|
||||
def onDocumentRestored(self, obj):
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
from PathScripts import PathUtils
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
import copy
|
||||
import FreeCAD
|
||||
import Path
|
||||
import Path.Dressup.Utils as PathDressup
|
||||
@@ -47,6 +48,117 @@ else:
|
||||
Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule())
|
||||
|
||||
|
||||
class AnnotatedGCode:
|
||||
def __init__(self, command, start_point):
|
||||
self.start_point = start_point
|
||||
self.command = command
|
||||
self.end_point = (
|
||||
command.Parameters.get("X", start_point[0]),
|
||||
command.Parameters.get("Y", start_point[1]),
|
||||
command.Parameters.get("Z", start_point[2]),
|
||||
)
|
||||
self.is_line = command.Name in Path.Geom.CmdMoveStraight
|
||||
self.is_arc = command.Name in Path.Geom.CmdMoveArc
|
||||
self.xy_length = None
|
||||
if self.is_line:
|
||||
self.xy_length = (
|
||||
(start_point[0] - self.end_point[0]) ** 2
|
||||
+ (start_point[1] - self.end_point[1]) ** 2
|
||||
) ** 0.5
|
||||
elif self.is_arc:
|
||||
self.center_xy = (
|
||||
start_point[0] + command.Parameters.get("I", 0),
|
||||
start_point[1] + command.Parameters.get("J", 0),
|
||||
)
|
||||
self.start_angle = math.atan2(
|
||||
start_point[1] - self.center_xy[1],
|
||||
start_point[0] - self.center_xy[0],
|
||||
)
|
||||
self.end_angle = math.atan2(
|
||||
self.end_point[1] - self.center_xy[1],
|
||||
self.end_point[0] - self.center_xy[0],
|
||||
)
|
||||
if self.command.Name in Path.Geom.CmdMoveCCW and self.end_angle < self.start_angle:
|
||||
self.end_angle += 2 * math.pi
|
||||
if self.command.Name in Path.Geom.CmdMoveCW and self.end_angle > self.start_angle:
|
||||
self.end_angle -= 2 * math.pi
|
||||
self.radius = (
|
||||
(start_point[0] - self.center_xy[0]) ** 2
|
||||
+ (start_point[1] - self.center_xy[1]) ** 2
|
||||
) ** 0.5
|
||||
self.xy_length = self.radius * abs(self.end_angle - self.start_angle)
|
||||
|
||||
"""Makes a copy of this annotated gcode at the given z height"""
|
||||
|
||||
def clone(self, z_start=None, z_end=None, reverse=False):
|
||||
z_start = z_start if z_start is not None else self.start_point[2]
|
||||
z_end = z_end if z_end is not None else self.end_point[2]
|
||||
|
||||
other = copy.copy(self)
|
||||
otherParams = copy.copy(self.command.Parameters)
|
||||
otherCommandName = self.command.Name
|
||||
other.start_point = (self.start_point[0], self.start_point[1], z_start)
|
||||
other.end_point = (self.end_point[0], self.end_point[1], z_end)
|
||||
otherParams.update({"Z": z_end})
|
||||
if reverse:
|
||||
other.start_point, other.end_point = other.end_point, other.start_point
|
||||
otherParams.update(
|
||||
{"X": other.end_point[0], "Y": other.end_point[1], "Z": other.end_point[2]}
|
||||
)
|
||||
if other.is_arc:
|
||||
other.start_angle, other.end_angle = other.end_angle, other.start_angle
|
||||
otherCommandName = (
|
||||
Path.Geom.CmdMoveCW[0]
|
||||
if self.command.Name in Path.Geom.CmdMoveCCW
|
||||
else Path.Geom.CmdMoveCCW[0]
|
||||
)
|
||||
otherParams.update(
|
||||
{
|
||||
"I": other.center_xy[0] - other.start_point[0],
|
||||
"J": other.center_xy[1] - other.start_point[1],
|
||||
}
|
||||
)
|
||||
other.command = Path.Command(otherCommandName, otherParams)
|
||||
return other
|
||||
|
||||
"""Splits the edge into two parts, the first split_length (if less than xy_length) long. Only supported for lines and arcs (no rapids)"""
|
||||
|
||||
def split(self, split_length):
|
||||
split_length = min(split_length, self.xy_length)
|
||||
p = split_length / self.xy_length
|
||||
firstParams = copy.copy(self.command.Parameters)
|
||||
secondParams = copy.copy(self.command.Parameters)
|
||||
split_point = None
|
||||
if self.is_line:
|
||||
split_point = (
|
||||
self.start_point[0] * (1 - p) + self.end_point[0] * p,
|
||||
self.start_point[1] * (1 - p) + self.end_point[1] * p,
|
||||
self.start_point[2] * (1 - p) + self.end_point[2] * p,
|
||||
)
|
||||
elif self.is_arc:
|
||||
angle = self.start_angle * (1 - p) + self.end_angle * p
|
||||
split_point = (
|
||||
self.center_xy[0] + self.radius * math.cos(angle),
|
||||
self.center_xy[1] + self.radius * math.sin(angle),
|
||||
self.start_point[2] * (1 - p) + self.end_point[2] * p,
|
||||
)
|
||||
secondParams.update(
|
||||
{
|
||||
"I": self.center_xy[0] - split_point[0],
|
||||
"J": self.center_xy[1] - split_point[1],
|
||||
}
|
||||
)
|
||||
else:
|
||||
raise Exception("Invalid type, can only split (non-rapid) lines and arcs")
|
||||
|
||||
firstParams.update({"X": split_point[0], "Y": split_point[1], "Z": split_point[2]})
|
||||
first_command = Path.Command(self.command.Name, firstParams)
|
||||
second_command = Path.Command(self.command.Name, secondParams)
|
||||
return AnnotatedGCode(first_command, self.start_point), AnnotatedGCode(
|
||||
second_command, split_point
|
||||
)
|
||||
|
||||
|
||||
class ObjectDressup:
|
||||
def __init__(self, obj):
|
||||
self.obj = obj
|
||||
@@ -222,45 +334,69 @@ class ObjectDressup:
|
||||
|
||||
self.angle = obj.Angle
|
||||
self.method = obj.Method
|
||||
self.wire, self.rapids = Path.Geom.wireForPath(PathUtils.getPathWithPlacement(obj.Base))
|
||||
positioned_path = PathUtils.getPathWithPlacement(obj.Base)
|
||||
cmds = positioned_path.Commands if hasattr(positioned_path, "Commands") else []
|
||||
self.edges = []
|
||||
start_point = (0, 0, 0)
|
||||
last_params = {}
|
||||
for cmd in cmds:
|
||||
# Skip repeat move commands
|
||||
params = cmd.Parameters
|
||||
if (
|
||||
cmd.Name in Path.Geom.CmdMoveAll
|
||||
and len(self.edges) > 0
|
||||
and cmd.Name == self.edges[-1].command.Name
|
||||
):
|
||||
found_diff = False
|
||||
for k, v in params.items():
|
||||
if last_params.get(k, None) != v:
|
||||
found_diff = True
|
||||
break
|
||||
if not found_diff:
|
||||
continue
|
||||
|
||||
last_params.update(params)
|
||||
|
||||
if cmd.Name in Path.Geom.CmdMoveAll and (
|
||||
not "X" in params or not "Y" in params or not "Z" in params
|
||||
):
|
||||
params["X"] = params.get("X", start_point[0])
|
||||
params["Y"] = params.get("Y", start_point[1])
|
||||
params["Z"] = params.get("Z", start_point[2])
|
||||
cmd = Path.Command(cmd.Name, params)
|
||||
annotated = AnnotatedGCode(cmd, start_point)
|
||||
self.edges.append(annotated)
|
||||
start_point = annotated.end_point
|
||||
if self.method in ["RampMethod1", "RampMethod2", "RampMethod3"]:
|
||||
self.outedges = self.generateRamps()
|
||||
else:
|
||||
self.outedges = self.generateHelix()
|
||||
obj.Path = self.createCommands(obj, self.outedges)
|
||||
|
||||
def generateRamps(self, allowBounce=True):
|
||||
edges = self.wire.Edges
|
||||
def generateRamps(self):
|
||||
edges = self.edges
|
||||
outedges = []
|
||||
for edge in edges:
|
||||
israpid = False
|
||||
for redge in self.rapids:
|
||||
if Path.Geom.edgesMatch(edge, redge):
|
||||
israpid = True
|
||||
if not israpid:
|
||||
bb = edge.BoundBox
|
||||
p0 = edge.Vertexes[0].Point
|
||||
p1 = edge.Vertexes[1].Point
|
||||
for edgei, edge in enumerate(edges):
|
||||
if edge.is_line or edge.is_arc:
|
||||
rampangle = self.angle
|
||||
if bb.XLength < 1e-6 and bb.YLength < 1e-6 and bb.ZLength > 0 and p0.z > p1.z:
|
||||
|
||||
# check for plunge
|
||||
if edge.xy_length < 1e-6 and edge.end_point[2] < edge.start_point[2]:
|
||||
# check if above ignoreAbove parameter - do not generate ramp if it is
|
||||
newEdge, cont = self.checkIgnoreAbove(edge)
|
||||
if newEdge is not None:
|
||||
outedges.append(newEdge)
|
||||
p0.z = self.ignoreAbove
|
||||
if cont:
|
||||
noramp_edge, edge = self.processIgnoreAbove(edge)
|
||||
if noramp_edge is not None:
|
||||
outedges.append(noramp_edge)
|
||||
if edge is None:
|
||||
continue
|
||||
|
||||
plungelen = abs(p0.z - p1.z)
|
||||
plungelen = abs(edge.start_point[2] - edge.end_point[2])
|
||||
projectionlen = plungelen * math.tan(
|
||||
math.radians(rampangle)
|
||||
) # length of the forthcoming ramp projected to XY plane
|
||||
Path.Log.debug(
|
||||
"Found plunge move at X:{} Y:{} From Z:{} to Z{}, length of ramp: {}".format(
|
||||
p0.x, p0.y, p0.z, p1.z, projectionlen
|
||||
)
|
||||
)
|
||||
# Path.Log.debug(
|
||||
# "Found plunge move at X:{} Y:{} From Z:{} to Z{}, length of ramp: {}".format(
|
||||
# p0.x, p0.y, p0.z, p1.z, projectionlen
|
||||
# )
|
||||
# )
|
||||
if self.method == "RampMethod3":
|
||||
projectionlen = projectionlen / 2
|
||||
|
||||
@@ -269,63 +405,54 @@ class ObjectDressup:
|
||||
covered = False
|
||||
coveredlen = 0
|
||||
rampedges = []
|
||||
i = edges.index(edge) + 1
|
||||
while not covered:
|
||||
i = edgei + 1
|
||||
while not covered and i < len(edges):
|
||||
candidate = edges[i]
|
||||
cp0 = candidate.Vertexes[0].Point
|
||||
cp1 = candidate.Vertexes[1].Point
|
||||
if abs(cp0.z - cp1.z) > 1e-6:
|
||||
# this edge is not parallel to XY plane, not qualified for ramping.
|
||||
if abs(candidate.start_point[2] - candidate.end_point[2]) > 1e-6 or (
|
||||
not candidate.is_line and not candidate.is_arc
|
||||
):
|
||||
# this edge is not an edge/arc in the XY plane; not qualified for ramping
|
||||
break
|
||||
# Path.Log.debug("Next edge length {}".format(candidate.Length))
|
||||
rampedges.append(candidate)
|
||||
coveredlen = coveredlen + candidate.Length
|
||||
coveredlen = coveredlen + candidate.xy_length
|
||||
|
||||
if coveredlen > projectionlen:
|
||||
covered = True
|
||||
i = i + 1
|
||||
if i >= len(edges):
|
||||
break
|
||||
if len(rampedges) == 0:
|
||||
Path.Log.debug("No suitable edges for ramping, plunge will remain as such")
|
||||
Path.Log.warn("No suitable edges for ramping, plunge will remain as such")
|
||||
outedges.append(edge)
|
||||
else:
|
||||
if not covered:
|
||||
if (not allowBounce) or self.method == "RampMethod2":
|
||||
l = 0
|
||||
for redge in rampedges:
|
||||
l = l + redge.Length
|
||||
if self.method == "RampMethod3":
|
||||
rampangle = math.degrees(math.atan(l / (plungelen / 2)))
|
||||
else:
|
||||
rampangle = math.degrees(math.atan(l / plungelen))
|
||||
Path.Log.warning(
|
||||
"Cannot cover with desired angle, tightening angle to: {}".format(
|
||||
rampangle
|
||||
)
|
||||
)
|
||||
|
||||
# Path.Log.debug("Doing ramp to edges: {}".format(rampedges))
|
||||
if self.method == "RampMethod1":
|
||||
outedges.extend(
|
||||
self.createRampMethod1(rampedges, p0, projectionlen, rampangle)
|
||||
self.createRampMethod1(
|
||||
rampedges, edge.start_point, projectionlen, rampangle
|
||||
)
|
||||
)
|
||||
elif self.method == "RampMethod2":
|
||||
outedges.extend(
|
||||
self.createRampMethod2(rampedges, p0, projectionlen, rampangle)
|
||||
self.createRampMethod2(
|
||||
rampedges, edge.start_point, projectionlen, rampangle
|
||||
)
|
||||
)
|
||||
else:
|
||||
# if the ramp cannot be covered with Method3, revert to Method1
|
||||
# because Method1 support going back-and-forth and thus results in same path as Method3 when
|
||||
# length of the ramp is smaller than needed for single ramp.
|
||||
if (not covered) and allowBounce:
|
||||
if not covered:
|
||||
projectionlen = projectionlen * 2
|
||||
outedges.extend(
|
||||
self.createRampMethod1(rampedges, p0, projectionlen, rampangle)
|
||||
self.createRampMethod1(
|
||||
rampedges, edge.start_point, projectionlen, rampangle
|
||||
)
|
||||
)
|
||||
else:
|
||||
outedges.extend(
|
||||
self.createRampMethod3(rampedges, p0, projectionlen, rampangle)
|
||||
self.createRampMethod3(
|
||||
rampedges, edge.start_point, projectionlen, rampangle
|
||||
)
|
||||
)
|
||||
else:
|
||||
outedges.append(edge)
|
||||
@@ -334,66 +461,49 @@ class ObjectDressup:
|
||||
return outedges
|
||||
|
||||
def generateHelix(self):
|
||||
edges = self.wire.Edges
|
||||
edges = self.edges
|
||||
minZ = self.findMinZ(edges)
|
||||
Path.Log.debug("Minimum Z in this path is {}".format(minZ))
|
||||
outedges = []
|
||||
i = 0
|
||||
while i < len(edges):
|
||||
edge = edges[i]
|
||||
israpid = False
|
||||
for redge in self.rapids:
|
||||
if Path.Geom.edgesMatch(edge, redge):
|
||||
israpid = True
|
||||
if not israpid:
|
||||
bb = edge.BoundBox
|
||||
p0 = edge.Vertexes[0].Point
|
||||
p1 = edge.Vertexes[1].Point
|
||||
if bb.XLength < 1e-6 and bb.YLength < 1e-6 and bb.ZLength > 0 and p0.z > p1.z:
|
||||
# plungelen = abs(p0.z-p1.z)
|
||||
Path.Log.debug(
|
||||
"Found plunge move at X:{} Y:{} From Z:{} to Z{}, Searching for closed loop".format(
|
||||
p0.x, p0.y, p0.z, p1.z
|
||||
)
|
||||
)
|
||||
# check if above ignoreAbove parameter - do not generate helix if it is
|
||||
newEdge, cont = self.checkIgnoreAbove(edge)
|
||||
if newEdge is not None:
|
||||
outedges.append(newEdge)
|
||||
p0.z = self.ignoreAbove
|
||||
if cont:
|
||||
if edge.is_line or edge.is_arc:
|
||||
if edge.xy_length < 1e-6 and edge.end_point[2] < edge.start_point[2]:
|
||||
noramp_edge, edge = self.processIgnoreAbove(edge)
|
||||
if noramp_edge is not None:
|
||||
outedges.append(noramp_edge)
|
||||
if edge is None:
|
||||
i = i + 1
|
||||
continue
|
||||
# next need to determine how many edges in the path after plunge are needed to cover the length:
|
||||
|
||||
# next need to find a loop
|
||||
loopFound = False
|
||||
rampedges = []
|
||||
j = i + 1
|
||||
while not loopFound:
|
||||
while j < len(edges) and not loopFound:
|
||||
candidate = edges[j]
|
||||
cp0 = candidate.Vertexes[0].Point
|
||||
cp1 = candidate.Vertexes[1].Point
|
||||
if Path.Geom.pointsCoincide(p1, cp1):
|
||||
# found closed loop
|
||||
loopFound = True
|
||||
rampedges.append(candidate)
|
||||
break
|
||||
if abs(cp0.z - cp1.z) > 1e-6:
|
||||
if abs(candidate.start_point[2] - candidate.end_point[2]) > 1e-6:
|
||||
# this edge is not parallel to XY plane, not qualified for ramping.
|
||||
# exit early, no loop found
|
||||
break
|
||||
# Path.Log.debug("Next edge length {}".format(candidate.Length))
|
||||
if (
|
||||
Path.Geom.isRoughly(edge.end_point[0], candidate.end_point[0])
|
||||
and Path.Geom.isRoughly(edge.end_point[1], candidate.end_point[1])
|
||||
and Path.Geom.isRoughly(edge.end_point[2], candidate.end_point[2])
|
||||
):
|
||||
loopFound = True
|
||||
rampedges.append(candidate)
|
||||
j = j + 1
|
||||
if j >= len(edges):
|
||||
break
|
||||
if len(rampedges) == 0 or not loopFound:
|
||||
Path.Log.debug("No suitable helix found")
|
||||
if not loopFound:
|
||||
Path.Log.warn("No suitable helix found, leaving as a plunge")
|
||||
outedges.append(edge)
|
||||
else:
|
||||
outedges.extend(self.createHelix(rampedges, p0, p1))
|
||||
if not Path.Geom.isRoughly(p1.z, minZ):
|
||||
outedges.extend(self.createHelix(rampedges, edge.start_point[2]))
|
||||
if not Path.Geom.isRoughly(edge.end_point[2], minZ):
|
||||
# the edges covered by the helix not handled again,
|
||||
# unless reached the bottom height
|
||||
i = j
|
||||
i = j - 1
|
||||
|
||||
else:
|
||||
outedges.append(edge)
|
||||
@@ -402,176 +512,122 @@ class ObjectDressup:
|
||||
i = i + 1
|
||||
return outedges
|
||||
|
||||
def checkIgnoreAbove(self, edge):
|
||||
if self.ignoreAboveEnabled:
|
||||
p0 = edge.Vertexes[0].Point
|
||||
p1 = edge.Vertexes[1].Point
|
||||
if p0.z > self.ignoreAbove and (
|
||||
p1.z > self.ignoreAbove or Path.Geom.isRoughly(p1.z, self.ignoreAbove.Value)
|
||||
):
|
||||
Path.Log.debug("Whole plunge move above 'ignoreAbove', ignoring")
|
||||
return (edge, True)
|
||||
elif p0.z > self.ignoreAbove and not Path.Geom.isRoughly(p0.z, self.ignoreAbove.Value):
|
||||
Path.Log.debug("Plunge move partially above 'ignoreAbove', splitting into two")
|
||||
newPoint = FreeCAD.Base.Vector(p0.x, p0.y, self.ignoreAbove)
|
||||
return (Part.makeLine(p0, newPoint), False)
|
||||
else:
|
||||
return None, False
|
||||
else:
|
||||
return None, False
|
||||
"""
|
||||
Edges, or parts of edges, above self.ignoreAbove should not be ramped.
|
||||
This method is a helper for splitting edges into a portion that should be
|
||||
ramped and a portion that should not be ramped.
|
||||
|
||||
def createHelix(self, rampedges, startPoint, endPoint):
|
||||
Returns (noramp_edge, ramp_edge). Either of these variables may be None
|
||||
"""
|
||||
|
||||
def processIgnoreAbove(self, edge):
|
||||
if not self.ignoreAboveEnabled:
|
||||
return None, edge
|
||||
z0, z1 = edge.start_point[2], edge.end_point[2]
|
||||
if z0 > self.ignoreAbove.Value:
|
||||
if z1 > self.ignoreAbove.Value or Path.Geom.isRoughly(z1, self.ignoreAbove.Value):
|
||||
# Entire plunge is above ignoreAbove
|
||||
return edge, None
|
||||
elif not Path.Geom.isRoughly(z0, self.ignoreAbove.Value):
|
||||
# Split the edge into regions above and below
|
||||
return (
|
||||
edge.clone(z0, self.ignoreAbove.Value),
|
||||
edge.clone(self.ignoreAbove.Value, z1),
|
||||
)
|
||||
# Entire plunge is below ignoreAbove
|
||||
return None, edge
|
||||
|
||||
def createHelix(self, rampedges, startZ):
|
||||
outedges = []
|
||||
ramplen = 0
|
||||
for redge in rampedges:
|
||||
ramplen = ramplen + redge.Length
|
||||
rampheight = abs(endPoint.z - startPoint.z)
|
||||
ramplen = ramplen + redge.xy_length
|
||||
rampheight = abs(startZ - rampedges[-1].end_point[2])
|
||||
|
||||
max_rise_over_run = 1 / math.tan(math.radians(self.angle))
|
||||
num_loops = math.ceil(rampheight / ramplen / max_rise_over_run)
|
||||
rampedges *= num_loops
|
||||
ramplen *= num_loops
|
||||
|
||||
rampangle_rad = math.atan(ramplen / rampheight)
|
||||
curPoint = startPoint
|
||||
curZ = startZ
|
||||
for i, redge in enumerate(rampedges):
|
||||
if i < len(rampedges) - 1:
|
||||
deltaZ = redge.Length / math.tan(rampangle_rad)
|
||||
newPoint = FreeCAD.Base.Vector(
|
||||
redge.valueAt(redge.LastParameter).x,
|
||||
redge.valueAt(redge.LastParameter).y,
|
||||
curPoint.z - deltaZ,
|
||||
)
|
||||
outedges.append(self.createRampEdge(redge, curPoint, newPoint))
|
||||
curPoint = newPoint
|
||||
else:
|
||||
# on the last edge, force it to end to the endPoint
|
||||
# this should happen automatically, but this avoids any rounding error
|
||||
outedges.append(self.createRampEdge(redge, curPoint, endPoint))
|
||||
return outedges
|
||||
|
||||
def createRampEdge(self, originalEdge, startPoint, endPoint):
|
||||
# Path.Log.debug("Create edge from [{},{},{}] to [{},{},{}]".format(startPoint.x,startPoint.y, startPoint.z, endPoint.x, endPoint.y, endPoint.z))
|
||||
if type(originalEdge.Curve) == Part.Line or type(originalEdge.Curve) == Part.LineSegment:
|
||||
return Part.makeLine(startPoint, endPoint)
|
||||
elif type(originalEdge.Curve) == Part.Circle:
|
||||
firstParameter = originalEdge.Curve.parameter(startPoint)
|
||||
lastParameter = originalEdge.Curve.parameter(endPoint)
|
||||
arcMid = originalEdge.valueAt((firstParameter + lastParameter) / 2)
|
||||
arcMid.z = (startPoint.z + endPoint.z) / 2
|
||||
return Part.Arc(startPoint, arcMid, endPoint).toShape()
|
||||
else:
|
||||
Path.Log.error("Edge should not be helix")
|
||||
|
||||
def getreversed(self, edges):
|
||||
"""
|
||||
Reverses the edge array and the direction of each edge
|
||||
"""
|
||||
outedges = []
|
||||
for edge in reversed(edges):
|
||||
# reverse the start and end points
|
||||
startPoint = edge.valueAt(edge.LastParameter)
|
||||
endPoint = edge.valueAt(edge.FirstParameter)
|
||||
if type(edge.Curve) == Part.Line or type(edge.Curve) == Part.LineSegment:
|
||||
outedges.append(Part.makeLine(startPoint, endPoint))
|
||||
elif type(edge.Curve) == Part.Circle:
|
||||
arcMid = edge.valueAt((edge.FirstParameter + edge.LastParameter) / 2)
|
||||
outedges.append(Part.Arc(startPoint, arcMid, endPoint).toShape())
|
||||
else:
|
||||
Path.Log.error("Edge should not be helix")
|
||||
deltaZ = redge.xy_length / math.tan(rampangle_rad)
|
||||
# compute new z, or clamp to end segment to avoid rounding error
|
||||
newZ = curZ - deltaZ if i < len(rampedges) - 1 else rampedges[-1].end_point[2]
|
||||
outedges.append(redge.clone(curZ, newZ))
|
||||
curZ = newZ
|
||||
return outedges
|
||||
|
||||
def findMinZ(self, edges):
|
||||
minZ = 99999999999
|
||||
for edge in edges[1:]:
|
||||
for v in edge.Vertexes:
|
||||
if v.Point.z < minZ:
|
||||
minZ = v.Point.z
|
||||
for edge in edges:
|
||||
if edge.end_point[2] < minZ:
|
||||
minZ = edge.end_point[2]
|
||||
return minZ
|
||||
|
||||
def getSplitPoint(self, edge, remaining):
|
||||
if type(edge.Curve) == Part.Line or type(edge.Curve) == Part.LineSegment:
|
||||
return edge.valueAt(remaining)
|
||||
elif type(edge.Curve) == Part.Circle:
|
||||
param = remaining / edge.Curve.Radius
|
||||
return edge.valueAt(param)
|
||||
|
||||
def createRampMethod1(self, rampedges, p0, projectionlen, rampangle):
|
||||
"""
|
||||
This method generates ramp with following pattern:
|
||||
1. Start from the original startpoint of the plunge
|
||||
2. Ramp down along the path that comes after the plunge
|
||||
3. When reaching the Z level of the original plunge, return back to the beginning
|
||||
by going the path backwards until the original plunge end point is reached
|
||||
by going the path backwards until the original plunge end point is reached
|
||||
4. Continue with the original path
|
||||
|
||||
This method causes many unnecessary moves with tool down.
|
||||
"""
|
||||
outedges = []
|
||||
ramp, reset = self._createRampMethod1(rampedges, p0, projectionlen, rampangle)
|
||||
return ramp + reset
|
||||
|
||||
def _createRampMethod1(self, rampedges, p0, projectionlen, rampangle):
|
||||
"""
|
||||
Helper method for generating ramps. Computes ramp method 1, but returns the result in pieces to allow for implementing the other ramp methods.
|
||||
Returns (ramp, reset)
|
||||
- ramp: array of commands ramping down
|
||||
- reset: array of commands returning from the bottom of the ramp to the bottom of the original plunge
|
||||
"""
|
||||
ramp = []
|
||||
reset = []
|
||||
reversed_edges = [redge.clone(reverse=True) for redge in rampedges]
|
||||
rampremaining = projectionlen
|
||||
curPoint = p0 # start from the upper point of plunge
|
||||
done = False
|
||||
z = p0[2] # start from the upper point of plunge
|
||||
goingForward = True
|
||||
i = 0
|
||||
while not done:
|
||||
for i, redge in enumerate(rampedges):
|
||||
if redge.Length >= rampremaining:
|
||||
# will reach end of ramp within this edge, needs to be split
|
||||
p1 = self.getSplitPoint(redge, rampremaining)
|
||||
splitEdge = Path.Geom.splitEdgeAt(redge, p1)
|
||||
Path.Log.debug("Ramp remaining: {}".format(rampremaining))
|
||||
Path.Log.debug(
|
||||
"Got split edge (index: {}) (total len: {}) with lengths: {}, {}".format(
|
||||
i, redge.Length, splitEdge[0].Length, splitEdge[1].Length
|
||||
)
|
||||
)
|
||||
# ramp ends to the last point of first edge
|
||||
p1 = splitEdge[0].valueAt(splitEdge[0].LastParameter)
|
||||
outedges.append(self.createRampEdge(splitEdge[0], curPoint, p1))
|
||||
# now we have reached the end of the ramp. Go back to plunge position with constant Z
|
||||
# start that by going to the beginning of this splitEdge
|
||||
if goingForward:
|
||||
outedges.append(
|
||||
self.createRampEdge(
|
||||
splitEdge[0], p1, redge.valueAt(redge.FirstParameter)
|
||||
)
|
||||
)
|
||||
else:
|
||||
# if we were reversing, we continue to the same direction as the ramp
|
||||
outedges.append(
|
||||
self.createRampEdge(
|
||||
splitEdge[0], p1, redge.valueAt(redge.LastParameter)
|
||||
)
|
||||
)
|
||||
done = True
|
||||
break
|
||||
else:
|
||||
deltaZ = redge.Length / math.tan(math.radians(rampangle))
|
||||
newPoint = FreeCAD.Base.Vector(
|
||||
redge.valueAt(redge.LastParameter).x,
|
||||
redge.valueAt(redge.LastParameter).y,
|
||||
curPoint.z - deltaZ,
|
||||
)
|
||||
outedges.append(self.createRampEdge(redge, curPoint, newPoint))
|
||||
curPoint = newPoint
|
||||
rampremaining = rampremaining - redge.Length
|
||||
i = 0 # current position = start of this edge. May be len(rampremaining) if going backwards
|
||||
while rampremaining > 0:
|
||||
redge = rampedges[i] if goingForward else reversed_edges[i - 1]
|
||||
|
||||
if not done:
|
||||
# we did not reach the end of the ramp going this direction, lets reverse.
|
||||
rampedges = self.getreversed(rampedges)
|
||||
Path.Log.debug("Reversing")
|
||||
# for i, redge in enumerate(rampedges):
|
||||
if redge.xy_length > rampremaining:
|
||||
# will reach end of ramp within this edge, needs to be split
|
||||
split_first, split_remaining = redge.split(rampremaining)
|
||||
ramp.append(split_first.clone(z_start=z))
|
||||
# now we have reached the end of the ramp. Go back to plunge position with constant Z
|
||||
# start that by going to the beginning of this splitEdge
|
||||
if goingForward:
|
||||
goingForward = False
|
||||
reset.append(split_first.clone(reverse=True))
|
||||
else:
|
||||
# if we were reversing, we continue to the same direction as the ramp
|
||||
reset.append(split_remaining)
|
||||
i = i - 1
|
||||
rampremaining = 0
|
||||
break
|
||||
else:
|
||||
deltaZ = redge.xy_length / math.tan(math.radians(rampangle))
|
||||
new_z = z - deltaZ
|
||||
ramp.append(redge.clone(z, new_z))
|
||||
z = new_z
|
||||
rampremaining = rampremaining - redge.xy_length
|
||||
i = i + 1 if goingForward else i - 1
|
||||
if i == 0:
|
||||
goingForward = True
|
||||
if i == len(rampedges):
|
||||
goingForward = False
|
||||
|
||||
# now we need to return to original position.
|
||||
if goingForward:
|
||||
# if the ramp was going forward, the return edges are the edges we already covered in ramping,
|
||||
# except the last one, which was already covered inside for loop. Direction needs to be reversed also
|
||||
returnedges = self.getreversed(rampedges[:i])
|
||||
else:
|
||||
# if the ramp was already reversing, the edges needed for return are the ones
|
||||
# which were not covered in ramp
|
||||
returnedges = rampedges[(i + 1) :]
|
||||
while i >= 1:
|
||||
reset.append(reversed_edges[i - 1])
|
||||
i = i - 1
|
||||
|
||||
# add the return edges:
|
||||
outedges.extend(returnedges)
|
||||
|
||||
return outedges
|
||||
return ramp, reset
|
||||
|
||||
def createRampMethod3(self, rampedges, p0, projectionlen, rampangle):
|
||||
"""
|
||||
@@ -582,237 +638,83 @@ class ObjectDressup:
|
||||
3. Change direction and ramp backwards to the original plunge end point
|
||||
4. Continue with the original path
|
||||
|
||||
This method causes many unnecessary moves with tool down.
|
||||
This path is computed using ramp method 1.
|
||||
"""
|
||||
outedges = []
|
||||
rampremaining = projectionlen
|
||||
curPoint = p0 # start from the upper point of plunge
|
||||
done = False
|
||||
|
||||
i = 0
|
||||
while not done:
|
||||
for i, redge in enumerate(rampedges):
|
||||
if redge.Length >= rampremaining:
|
||||
# will reach end of ramp within this edge, needs to be split
|
||||
p1 = self.getSplitPoint(redge, rampremaining)
|
||||
splitEdge = Path.Geom.splitEdgeAt(redge, p1)
|
||||
Path.Log.debug(
|
||||
"Got split edge (index: {}) with lengths: {}, {}".format(
|
||||
i, splitEdge[0].Length, splitEdge[1].Length
|
||||
)
|
||||
)
|
||||
# ramp ends to the last point of first edge
|
||||
p1 = splitEdge[0].valueAt(splitEdge[0].LastParameter)
|
||||
deltaZ = splitEdge[0].Length / math.tan(math.radians(rampangle))
|
||||
p1.z = curPoint.z - deltaZ
|
||||
outedges.append(self.createRampEdge(splitEdge[0], curPoint, p1))
|
||||
curPoint.z = p1.z - deltaZ
|
||||
# now we have reached the end of the ramp. Reverse direction of ramp
|
||||
# start that by going back to the beginning of this splitEdge
|
||||
outedges.append(self.createRampEdge(splitEdge[0], p1, curPoint))
|
||||
|
||||
done = True
|
||||
break
|
||||
elif i == len(rampedges) - 1:
|
||||
# last ramp element but still did not reach the full length?
|
||||
# Probably a rounding issue on floats.
|
||||
p1 = redge.valueAt(redge.LastParameter)
|
||||
deltaZ = redge.Length / math.tan(math.radians(rampangle))
|
||||
p1.z = curPoint.z - deltaZ
|
||||
outedges.append(self.createRampEdge(redge, curPoint, p1))
|
||||
# and go back that edge
|
||||
newPoint = FreeCAD.Base.Vector(
|
||||
redge.valueAt(redge.FirstParameter).x,
|
||||
redge.valueAt(redge.FirstParameter).y,
|
||||
p1.z - deltaZ,
|
||||
)
|
||||
outedges.append(self.createRampEdge(redge, p1, newPoint))
|
||||
curPoint = newPoint
|
||||
done = True
|
||||
else:
|
||||
deltaZ = redge.Length / math.tan(math.radians(rampangle))
|
||||
newPoint = FreeCAD.Base.Vector(
|
||||
redge.valueAt(redge.LastParameter).x,
|
||||
redge.valueAt(redge.LastParameter).y,
|
||||
curPoint.z - deltaZ,
|
||||
)
|
||||
outedges.append(self.createRampEdge(redge, curPoint, newPoint))
|
||||
curPoint = newPoint
|
||||
rampremaining = rampremaining - redge.Length
|
||||
|
||||
returnedges = self.getreversed(rampedges[:i])
|
||||
|
||||
# ramp backwards to the plunge position
|
||||
for i, redge in enumerate(returnedges):
|
||||
deltaZ = redge.Length / math.tan(math.radians(rampangle))
|
||||
newPoint = FreeCAD.Base.Vector(
|
||||
redge.valueAt(redge.LastParameter).x,
|
||||
redge.valueAt(redge.LastParameter).y,
|
||||
curPoint.z - deltaZ,
|
||||
z_half = (p0[2] + rampedges[0].start_point[2]) / 2
|
||||
r1_rampedges = [redge.clone(z_half, z_half) for redge in rampedges]
|
||||
ramp, _ = self._createRampMethod1(r1_rampedges, p0, projectionlen, rampangle)
|
||||
ramp_back = [
|
||||
redge.clone(
|
||||
2 * z_half - redge.start_point[2], 2 * z_half - redge.end_point[2], reverse=True
|
||||
)
|
||||
if i == len(rampedges) - 1:
|
||||
# make sure that the last point of the ramps ends to the original position
|
||||
newPoint = redge.valueAt(redge.LastParameter)
|
||||
outedges.append(self.createRampEdge(redge, curPoint, newPoint))
|
||||
curPoint = newPoint
|
||||
|
||||
return outedges
|
||||
for redge in ramp[::-1]
|
||||
]
|
||||
return ramp + ramp_back
|
||||
|
||||
def createRampMethod2(self, rampedges, p0, projectionlen, rampangle):
|
||||
"""
|
||||
This method generates ramp with following pattern:
|
||||
1. Start from the original startpoint of the plunge
|
||||
2. Calculate the distance on the path which is needed to implement the ramp
|
||||
and travel that distance while maintaining start depth
|
||||
3. Start ramping while traveling the original path backwards until reaching the
|
||||
original plunge end point
|
||||
2. Travel at start depth along the path, for a distance required for step 3
|
||||
3. Ramp backwards along the path at rampangle, arriving exactly at the bottom of the plunge
|
||||
4. Continue with the original path
|
||||
|
||||
This path is computed using ramp method 1:
|
||||
1. Move all edges up to the start height
|
||||
2. Perform ramp method 1 from the bottom of the plunge *up* to the relocated path
|
||||
3. Reverse the resulting path (both edge order and direction)
|
||||
"""
|
||||
outedges = []
|
||||
rampremaining = projectionlen
|
||||
curPoint = p0 # start from the upper point of plunge
|
||||
if Path.Geom.pointsCoincide(
|
||||
Path.Geom.xy(p0),
|
||||
Path.Geom.xy(rampedges[-1].valueAt(rampedges[-1].LastParameter)),
|
||||
):
|
||||
Path.Log.debug("The ramp forms a closed wire, needless to move on original Z height")
|
||||
else:
|
||||
for i, redge in enumerate(rampedges):
|
||||
if redge.Length >= rampremaining:
|
||||
# this edge needs to be split
|
||||
p1 = self.getSplitPoint(redge, rampremaining)
|
||||
splitEdge = Path.Geom.splitEdgeAt(redge, p1)
|
||||
Path.Log.debug(
|
||||
"Got split edges with lengths: {}, {}".format(
|
||||
splitEdge[0].Length, splitEdge[1].Length
|
||||
)
|
||||
)
|
||||
# ramp starts at the last point of first edge
|
||||
p1 = splitEdge[0].valueAt(splitEdge[0].LastParameter)
|
||||
p1.z = p0.z
|
||||
outedges.append(self.createRampEdge(splitEdge[0], curPoint, p1))
|
||||
# now we have reached the beginning of the ramp.
|
||||
# start that by going to the beginning of this splitEdge
|
||||
deltaZ = splitEdge[0].Length / math.tan(math.radians(rampangle))
|
||||
newPoint = FreeCAD.Base.Vector(
|
||||
splitEdge[0].valueAt(splitEdge[0].FirstParameter).x,
|
||||
splitEdge[0].valueAt(splitEdge[0].FirstParameter).y,
|
||||
p1.z - deltaZ,
|
||||
)
|
||||
outedges.append(self.createRampEdge(splitEdge[0], p1, newPoint))
|
||||
curPoint = newPoint
|
||||
elif i == len(rampedges) - 1:
|
||||
# last ramp element but still did not reach the full length?
|
||||
# Probably a rounding issue on floats.
|
||||
# Lets start the ramp anyway
|
||||
p1 = redge.valueAt(redge.LastParameter)
|
||||
p1.z = p0.z
|
||||
outedges.append(self.createRampEdge(redge, curPoint, p1))
|
||||
# and go back that edge
|
||||
deltaZ = redge.Length / math.tan(math.radians(rampangle))
|
||||
newPoint = FreeCAD.Base.Vector(
|
||||
redge.valueAt(redge.FirstParameter).x,
|
||||
redge.valueAt(redge.FirstParameter).y,
|
||||
p1.z - deltaZ,
|
||||
)
|
||||
outedges.append(self.createRampEdge(redge, p1, newPoint))
|
||||
curPoint = newPoint
|
||||
|
||||
else:
|
||||
# we are traveling on start depth
|
||||
newPoint = FreeCAD.Base.Vector(
|
||||
redge.valueAt(redge.LastParameter).x,
|
||||
redge.valueAt(redge.LastParameter).y,
|
||||
p0.z,
|
||||
)
|
||||
outedges.append(self.createRampEdge(redge, curPoint, newPoint))
|
||||
curPoint = newPoint
|
||||
rampremaining = rampremaining - redge.Length
|
||||
|
||||
# the last edge got handled previously
|
||||
rampedges.pop()
|
||||
# ramp backwards to the plunge position
|
||||
for i, redge in enumerate(reversed(rampedges)):
|
||||
deltaZ = redge.Length / math.tan(math.radians(rampangle))
|
||||
newPoint = FreeCAD.Base.Vector(
|
||||
redge.valueAt(redge.FirstParameter).x,
|
||||
redge.valueAt(redge.FirstParameter).y,
|
||||
curPoint.z - deltaZ,
|
||||
)
|
||||
if i == len(rampedges) - 1:
|
||||
# make sure that the last point of the ramps ends to the original position
|
||||
newPoint = redge.valueAt(redge.FirstParameter)
|
||||
outedges.append(self.createRampEdge(redge, curPoint, newPoint))
|
||||
curPoint = newPoint
|
||||
|
||||
r1_rampedges = [redge.clone(p0[2], p0[2]) for redge in rampedges]
|
||||
r1_p0 = rampedges[0].start_point
|
||||
r1_rampangle = -rampangle
|
||||
r1_result = self.createRampMethod1(r1_rampedges, r1_p0, projectionlen, r1_rampangle)
|
||||
outedges = [redge.clone(reverse=True) for redge in r1_result[::-1]]
|
||||
return outedges
|
||||
|
||||
def createCommands(self, obj, edges):
|
||||
commands = []
|
||||
for edge in edges:
|
||||
israpid = False
|
||||
for redge in self.rapids:
|
||||
if Path.Geom.edgesMatch(edge, redge):
|
||||
israpid = True
|
||||
if israpid:
|
||||
v = edge.valueAt(edge.LastParameter)
|
||||
commands.append(Path.Command("G0", {"X": v.x, "Y": v.y, "Z": v.z}))
|
||||
else:
|
||||
commands.extend(Path.Geom.cmdsForEdge(edge))
|
||||
|
||||
lastCmd = Path.Command("G0", {"X": 0.0, "Y": 0.0, "Z": 0.0})
|
||||
|
||||
commands = [edge.command for edge in edges]
|
||||
outCommands = []
|
||||
|
||||
tc = PathDressup.toolController(obj.Base)
|
||||
|
||||
horizFeed = tc.HorizFeed.Value
|
||||
vertFeed = tc.VertFeed.Value
|
||||
|
||||
if obj.RampFeedRate == "Horizontal Feed Rate":
|
||||
rampFeed = tc.HorizFeed.Value
|
||||
elif obj.RampFeedRate == "Vertical Feed Rate":
|
||||
rampFeed = tc.VertFeed.Value
|
||||
elif obj.RampFeedRate == "Ramp Feed Rate":
|
||||
rampFeed = math.sqrt(pow(tc.VertFeed.Value, 2) + pow(tc.HorizFeed.Value, 2))
|
||||
else:
|
||||
rampFeed = obj.CustomFeedRate.Value
|
||||
|
||||
horizRapid = tc.HorizRapid.Value
|
||||
vertRapid = tc.VertRapid.Value
|
||||
|
||||
if obj.RampFeedRate == "Horizontal Feed Rate":
|
||||
rampFeed = horizFeed
|
||||
elif obj.RampFeedRate == "Vertical Feed Rate":
|
||||
rampFeed = vertFeed
|
||||
elif obj.RampFeedRate == "Ramp Feed Rate":
|
||||
rampFeed = math.sqrt(pow(vertFeed, 2) + pow(horizFeed, 2))
|
||||
else:
|
||||
rampFeed = obj.CustomFeedRate.Value
|
||||
|
||||
lastX = lastY = lastZ = 0
|
||||
for cmd in commands:
|
||||
params = cmd.Parameters
|
||||
zVal = params.get("Z", None)
|
||||
zVal2 = lastCmd.Parameters.get("Z", None)
|
||||
x = params.get("X", lastX)
|
||||
y = params.get("Y", lastY)
|
||||
z = params.get("Z", lastZ)
|
||||
|
||||
xVal = params.get("X", None)
|
||||
xVal2 = lastCmd.Parameters.get("X", None)
|
||||
|
||||
yVal2 = lastCmd.Parameters.get("Y", None)
|
||||
yVal = params.get("Y", None)
|
||||
|
||||
zVal = zVal and round(zVal, 8)
|
||||
zVal2 = zVal2 and round(zVal2, 8)
|
||||
z = z and round(z, 8)
|
||||
|
||||
if cmd.Name in ["G1", "G2", "G3", "G01", "G02", "G03"]:
|
||||
if zVal is not None and zVal2 != zVal:
|
||||
if Path.Geom.isRoughly(xVal, xVal2) and Path.Geom.isRoughly(yVal, yVal2):
|
||||
# this is a straight plunge
|
||||
if lastZ != z:
|
||||
if Path.Geom.isRoughly(x, lastX) and Path.Geom.isRoughly(y, lastY):
|
||||
params["F"] = vertFeed
|
||||
else:
|
||||
# this is a ramp
|
||||
params["F"] = rampFeed
|
||||
else:
|
||||
params["F"] = horizFeed
|
||||
lastCmd = cmd
|
||||
|
||||
elif cmd.Name in ["G0", "G00"]:
|
||||
if zVal is not None and zVal2 != zVal:
|
||||
if lastZ != z:
|
||||
params["F"] = vertRapid
|
||||
else:
|
||||
params["F"] = horizRapid
|
||||
lastCmd = cmd
|
||||
|
||||
lastX, lastY, lastZ = x, y, z
|
||||
|
||||
outCommands.append(Path.Command(cmd.Name, params))
|
||||
|
||||
|
||||
@@ -642,7 +642,7 @@ class PathData:
|
||||
Path.Log.track(obj.Base.Name)
|
||||
self.obj = obj
|
||||
path = PathUtils.getPathWithPlacement(obj.Base)
|
||||
self.wire, rapid = Path.Geom.wireForPath(path)
|
||||
self.wire, rapid, rapid_indexes = Path.Geom.wireForPath(path)
|
||||
self.rapid = _RapidEdges(rapid)
|
||||
if self.wire:
|
||||
self.edges = self.wire.Edges
|
||||
|
||||
@@ -320,24 +320,24 @@ def cmdsForEdge(edge, flip=False, useHelixForBSpline=True, segm=50, hSpeed=0, vS
|
||||
offset = edge.Curve.Center - pt
|
||||
else:
|
||||
pd = Part.Circle(xy(p1), xy(p2), xy(p3)).Center
|
||||
Path.Log.debug(
|
||||
"**** %s.%d: (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f) -> center=(%.2f, %.2f)"
|
||||
% (
|
||||
cmd,
|
||||
flip,
|
||||
p1.x,
|
||||
p1.y,
|
||||
p1.z,
|
||||
p2.x,
|
||||
p2.y,
|
||||
p2.z,
|
||||
p3.x,
|
||||
p3.y,
|
||||
p3.z,
|
||||
pd.x,
|
||||
pd.y,
|
||||
)
|
||||
)
|
||||
# Path.Log.debug(
|
||||
# "**** %s.%d: (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f) -> center=(%.2f, %.2f)"
|
||||
# % (
|
||||
# cmd,
|
||||
# flip,
|
||||
# p1.x,
|
||||
# p1.y,
|
||||
# p1.z,
|
||||
# p2.x,
|
||||
# p2.y,
|
||||
# p2.z,
|
||||
# p3.x,
|
||||
# p3.y,
|
||||
# p3.z,
|
||||
# pd.x,
|
||||
# pd.y,
|
||||
# )
|
||||
# )
|
||||
|
||||
# Have to calculate the center in the XY plane, using pd leads to an error if this is a helix
|
||||
pa = xy(p1)
|
||||
@@ -345,15 +345,15 @@ def cmdsForEdge(edge, flip=False, useHelixForBSpline=True, segm=50, hSpeed=0, vS
|
||||
pc = xy(p3)
|
||||
offset = Part.Circle(pa, pb, pc).Center - pa
|
||||
|
||||
Path.Log.debug(
|
||||
"**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)"
|
||||
% (pa.x, pa.y, pa.z, pc.x, pc.y, pc.z)
|
||||
)
|
||||
Path.Log.debug(
|
||||
"**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)"
|
||||
% (pb.x, pb.y, pb.z, pd.x, pd.y, pd.z)
|
||||
)
|
||||
Path.Log.debug("**** (%.2f, %.2f, %.2f)" % (offset.x, offset.y, offset.z))
|
||||
# Path.Log.debug(
|
||||
# "**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)"
|
||||
# % (pa.x, pa.y, pa.z, pc.x, pc.y, pc.z)
|
||||
# )
|
||||
# Path.Log.debug(
|
||||
# "**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)"
|
||||
# % (pb.x, pb.y, pb.z, pd.x, pd.y, pd.z)
|
||||
# )
|
||||
# Path.Log.debug("**** (%.2f, %.2f, %.2f)" % (offset.x, offset.y, offset.z))
|
||||
|
||||
params.update({"I": offset.x, "J": offset.y, "K": (p3.z - p1.z) / 2})
|
||||
# G2/G3 commands are always performed at hSpeed
|
||||
@@ -387,8 +387,8 @@ def edgeForCmd(cmd, startPoint):
|
||||
"""edgeForCmd(cmd, startPoint).
|
||||
Returns an Edge representing the given command, assuming a given startPoint."""
|
||||
|
||||
Path.Log.debug("cmd: {}".format(cmd))
|
||||
Path.Log.debug("startpoint {}".format(startPoint))
|
||||
# Path.Log.debug("cmd: {}".format(cmd))
|
||||
# Path.Log.debug("startpoint {}".format(startPoint))
|
||||
|
||||
endPoint = commandEndPoint(cmd, startPoint)
|
||||
if (cmd.Name in CmdMoveStraight) or (cmd.Name in CmdMoveRapid) or (cmd.Name in CmdMoveDrill):
|
||||
@@ -403,9 +403,9 @@ def edgeForCmd(cmd, startPoint):
|
||||
d = -B.x * A.y + B.y * A.x
|
||||
|
||||
if isRoughly(d, 0, 0.005):
|
||||
Path.Log.debug(
|
||||
"Half circle arc at: (%.2f, %.2f, %.2f)" % (center.x, center.y, center.z)
|
||||
)
|
||||
# Path.Log.debug(
|
||||
# "Half circle arc at: (%.2f, %.2f, %.2f)" % (center.x, center.y, center.z)
|
||||
# )
|
||||
# we're dealing with half a circle here
|
||||
angle = getAngle(A) + math.pi / 2
|
||||
if cmd.Name in CmdMoveCW:
|
||||
@@ -413,34 +413,34 @@ def edgeForCmd(cmd, startPoint):
|
||||
else:
|
||||
C = A + B
|
||||
angle = getAngle(C)
|
||||
Path.Log.debug(
|
||||
"Arc (%8f) at: (%.2f, %.2f, %.2f) -> angle=%f"
|
||||
% (d, center.x, center.y, center.z, angle / math.pi)
|
||||
)
|
||||
# Path.Log.debug(
|
||||
# "Arc (%8f) at: (%.2f, %.2f, %.2f) -> angle=%f"
|
||||
# % (d, center.x, center.y, center.z, angle / math.pi)
|
||||
# )
|
||||
|
||||
R = A.Length
|
||||
Path.Log.debug(
|
||||
"arc: p1=(%.2f, %.2f) p2=(%.2f, %.2f) -> center=(%.2f, %.2f)"
|
||||
% (startPoint.x, startPoint.y, endPoint.x, endPoint.y, center.x, center.y)
|
||||
)
|
||||
Path.Log.debug("arc: A=(%.2f, %.2f) B=(%.2f, %.2f) -> d=%.2f" % (A.x, A.y, B.x, B.y, d))
|
||||
Path.Log.debug("arc: R=%.2f angle=%.2f" % (R, angle / math.pi))
|
||||
# Path.Log.debug(
|
||||
# "arc: p1=(%.2f, %.2f) p2=(%.2f, %.2f) -> center=(%.2f, %.2f)"
|
||||
# % (startPoint.x, startPoint.y, endPoint.x, endPoint.y, center.x, center.y)
|
||||
# )
|
||||
# Path.Log.debug("arc: A=(%.2f, %.2f) B=(%.2f, %.2f) -> d=%.2f" % (A.x, A.y, B.x, B.y, d))
|
||||
# Path.Log.debug("arc: R=%.2f angle=%.2f" % (R, angle / math.pi))
|
||||
if isRoughly(startPoint.z, endPoint.z):
|
||||
midPoint = center + Vector(math.cos(angle), math.sin(angle), 0) * R
|
||||
Path.Log.debug(
|
||||
"arc: (%.2f, %.2f) -> (%.2f, %.2f) -> (%.2f, %.2f)"
|
||||
% (
|
||||
startPoint.x,
|
||||
startPoint.y,
|
||||
midPoint.x,
|
||||
midPoint.y,
|
||||
endPoint.x,
|
||||
endPoint.y,
|
||||
)
|
||||
)
|
||||
Path.Log.debug("StartPoint:{}".format(startPoint))
|
||||
Path.Log.debug("MidPoint:{}".format(midPoint))
|
||||
Path.Log.debug("EndPoint:{}".format(endPoint))
|
||||
# Path.Log.debug(
|
||||
# "arc: (%.2f, %.2f) -> (%.2f, %.2f) -> (%.2f, %.2f)"
|
||||
# % (
|
||||
# startPoint.x,
|
||||
# startPoint.y,
|
||||
# midPoint.x,
|
||||
# midPoint.y,
|
||||
# endPoint.x,
|
||||
# endPoint.y,
|
||||
# )
|
||||
# )
|
||||
# Path.Log.debug("StartPoint:{}".format(startPoint))
|
||||
# Path.Log.debug("MidPoint:{}".format(midPoint))
|
||||
# Path.Log.debug("EndPoint:{}".format(endPoint))
|
||||
|
||||
if pointsCoincide(startPoint, endPoint, 0.001):
|
||||
return Part.makeCircle(R, center, FreeCAD.Vector(0, 0, 1))
|
||||
@@ -472,17 +472,19 @@ def wireForPath(path, startPoint=Vector(0, 0, 0)):
|
||||
Returns a wire representing all move commands found in the given path."""
|
||||
edges = []
|
||||
rapid = []
|
||||
rapid_indexes = set()
|
||||
if hasattr(path, "Commands"):
|
||||
for cmd in path.Commands:
|
||||
edge = edgeForCmd(cmd, startPoint)
|
||||
if edge:
|
||||
if cmd.Name in CmdMoveRapid:
|
||||
rapid.append(edge)
|
||||
rapid_indexes.add(len(edges))
|
||||
edges.append(edge)
|
||||
startPoint = commandEndPoint(cmd, startPoint)
|
||||
if not edges:
|
||||
return (None, rapid)
|
||||
return (Part.Wire(edges), rapid)
|
||||
return (None, rapid, rapid_indexes)
|
||||
return (Part.Wire(edges), rapid, rapid_indexes)
|
||||
|
||||
|
||||
def wiresForPath(path, startPoint=Vector(0, 0, 0)):
|
||||
|
||||
Reference in New Issue
Block a user