From b26a7ad7949afebf79a8daffdc9d55108a0b1012 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sat, 10 Dec 2016 12:39:23 -0800 Subject: [PATCH] OCC based tag line generation by extruding the edges cutting through a tag and retreiving the common with the tag solid. --- .../PathScripts/PathDressupHoldingTags.py | 525 +++++----------- src/Mod/Path/PathScripts/PathGeom.py | 18 +- .../PathTests/TestPathDressupHoldingTags.py | 576 +----------------- src/Mod/Path/TestPathApp.py | 6 +- 4 files changed, 160 insertions(+), 965 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index 66ba48656..a9b53e8a7 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -47,7 +47,7 @@ except AttributeError: def translate(context, text, disambig=None): return QtGui.QApplication.translate(context, text, disambig) -debugDressup = True +debugDressup = False debugComponents = ['P0', 'P1', 'P2', 'P3'] def debugPrint(comp, msg): @@ -59,7 +59,7 @@ def debugEdge(edge, prefix, comp = None): pl = edge.valueAt(edge.LastParameter) if comp: debugPrint(comp, "%s %s((%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f))" % (prefix, type(edge.Curve), pf.x, pf.y, pf.z, pl.x, pl.y, pl.z)) - else: + elif debugDressup: print("%s %s((%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f))" % (prefix, type(edge.Curve), pf.x, pf.y, pf.z, pl.x, pl.y, pl.z)) def debugMarker(vector, label, color = None, radius = 0.5): @@ -127,9 +127,6 @@ class Tag: def top(self): return self.z + self.actualHeight - def centerLine(self): - return Part.LineSegment(self.originAt(self.bottom() - 1), self.originAt(self.top() + 1)) - def createSolidsAt(self, z): self.z = z r1 = self.width / 2 @@ -160,288 +157,6 @@ class Tag: if self.core: self.core.translate(self.originAt(z)) - class Intersection: - # An intersection with a tag has 4 markant points, where one might be optional. - # - # P1---P2 P1---P2 P2 - # | | / \ /\ - # | | / \ / \ - # | | / \ / \ - # ---P0 P3--- ---P0 P3--- ---P0 P3--- - # - # If no intersection occured the Intersection can be viewed as being - # at P3 with no additional edges. - Pnone = 1 - P0 = 2 - P1 = 3 - P2 = 4 - P3 = 5 - - def __init__(self, tag): - self.tag = tag - self.state = self.Pnone - self.edges = [] - self.tail = None - - def isComplete(self): - return self.state == self.Pnone or self.state == self.P3 - - def hasEdges(self): - return self.state != self.Pnone - - def moveEdgeToPlateau(self, edge): - if type(edge.Curve) is Part.Line or type(edge.Curve) is Part.LineSegment: - e = copy.copy(edge) - z = edge.valueAt(edge.FirstParameter).z - elif type(edge.Curve) is Part.Circle: - # it's an arc - e = copy.copy(edge) - z = edge.Curve.Center.z - else: - # it's a helix -> transform to arc - z = 0 - p1 = PathGeom.xy(edge.valueAt(edge.FirstParameter)) - p2 = PathGeom.xy(edge.valueAt((edge.FirstParameter + edge.LastParameter)/2)) - p3 = PathGeom.xy(edge.valueAt(edge.LastParameter)) - e = Part.Edge(Part.Arc(p1, p2, p3)) - print("-------- moveEdgeToPlateau") - e.translate(Vector(0, 0, self.tag.top() - z)) - return e - - def intersectP0Core(self, edge): - debugPrint('P0', "----- P0-core") - - i = self.tag.nextIntersectionClosestTo(edge, self.tag.core, edge.valueAt(edge.FirstParameter)) - if i: - if PathGeom.pointsCoincide(i, edge.valueAt(edge.FirstParameter)): - # if P0 and P1 are the same, we need to insert a segment for the rise - debugPrint('P0', "------- insert vertical rise (%s)" % i) - self.edges.append(Part.Edge(Part.LineSegment(i, FreeCAD.Vector(i.x, i.y, self.tag.top())))) - self.p1 = i - self.state = self.P1 - return edge - if PathGeom.pointsCoincide(i, edge.valueAt(edge.LastParameter)): - debugPrint('P0', "------- consumed (%s)" % i) - e = edge - tail = None - else: - debugPrint('P0', "------- split at (%s)" % i) - e, tail = PathGeom.splitEdgeAt(edge, i) - self.p1 = e.valueAt(edge.LastParameter) - self.edges.extend(self.tag.mapEdgeToSolid(e, 'P0-core-1')) - self.state = self.P1 - return tail - # no intersection, the entire edge fits between P0 and P1 - debugPrint('P0', "------- no intersection") - self.edges.extend(self.tag.mapEdgeToSolid(edge, 'P0-core-2')) - return None - - def intersectP0(self, edge): - pf = edge.valueAt(edge.FirstParameter) - pl = edge.valueAt(edge.LastParameter) - debugPrint('P0', "----- P0 %s(%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f))" % (type(edge.Curve), pf.x, pf.y, pf.z, pl.x, pl.y, pl.z)) - - if self.tag.core: - return self.intersectP0Core(edge) - - # if we have no core the tip is the origin of the Tag - line = Part.Edge(self.tag.centerLine()) - debugEdge(line, "------- center line", 'P0') - if type(edge.Curve) != Part.Circle and type(edge.Curve) != Part.Line: - p1 = edge.valueAt(edge.FirstParameter) - p2 = edge.valueAt((edge.FirstParameter + edge.LastParameter)/2) - p3 = edge.valueAt(edge.LastParameter) - p1.z = 0 - p2.z = 0 - p3.z = 0 - arc = Part.Edge(Part.Arc(p1, p2, p3)) - aps = DraftGeomUtils.findIntersection(line, arc) - paramScale = (edge.LastParameter - edge.FirstParameter) / (arc.LastParameter - arc.FirstParameter) - for p in aps: - print("%s - p=%.2f" % (p, arc.Curve.parameter(p))) - i = [edge.valueAt(arc.Curve.parameter(p) * paramScale) for p in aps] - else: - i = DraftGeomUtils.findIntersection(line, edge) - #i = line.Curve.intersect(edge) - if i: - debugPrint('P0', '------- P0 split @ (%.2f, %.2f, %.2f)' % (i[0].x, i[0].y, i[0].z)) - if PathGeom.pointsCoincide(i[0], edge.valueAt(edge.LastParameter)): - e = edge - tail = None - else: - e, tail = PathGeom.splitEdgeAt(edge, i[0]) - self.state = self.P2 # P1 and P2 are identical for triangular tags - self.p1 = i[0] - self.p2 = i[0] - else: - debugPrint('P0', '------- P0 no intersect') - e = edge - tail = None - self.edges.extend(self.tag.mapEdgeToSolid(e, 'P0')) - return tail - - - - def intersectP1(self, edge): - pf = edge.valueAt(edge.FirstParameter) - pl = edge.valueAt(edge.LastParameter) - debugPrint('P1', "----- P1 (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f))" % (pf.x, pf.y, pf.z, pl.x, pl.y, pl.z)) - i = self.tag.nextIntersectionClosestTo(edge, self.tag.core, edge.valueAt(edge.LastParameter)) - if i: - if PathGeom.pointsCoincide(i, edge.valueAt(edge.FirstParameter)): - debugPrint('P1', "----- P1 edge too short") - #self.edges.extend(self.tag.mapEdgeToSolid(edge, 'P1')) - self.edges.append(self.moveEdgeToPlateau(edge)) - return None - if PathGeom.pointsCoincide(i, edge.valueAt(edge.LastParameter)): - debugPrint('P1', "----- P1 edge at end") - e = edge - tail = None - else: - debugPrint('P1', "----- P1 split edge @ (%.2f, %.2f, %.2f)" % (i.x, i.y, i.z)) - e, tail = PathGeom.splitEdgeAt(edge, i) - f = e.valueAt(e.FirstParameter) - l = e.valueAt(e.LastParameter) - debugPrint('P1', "----- P1 (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (f.x, f.y, f.z, l.x, l.y, l.z)) - self.p2 = e.valueAt(e.LastParameter) - self.state = self.P2 - else: - debugPrint('P1', "----- P1 no intersect") - e = edge - tail = None - f = e.valueAt(e.FirstParameter) - l = e.valueAt(e.LastParameter) - debugPrint('P1', "----- P1 (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (f.x, f.y, f.z, l.x, l.y, l.z)) - self.edges.append(self.moveEdgeToPlateau(e)) - return tail - - def intersectP2(self, edge): - pf = edge.valueAt(edge.FirstParameter) - pl = edge.valueAt(edge.LastParameter) - debugPrint('P2', "----- P2 (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f))" % (pf.x, pf.y, pf.z, pl.x, pl.y, pl.z)) - i = self.tag.nextIntersectionClosestTo(edge, self.tag.solid, edge.valueAt(edge.LastParameter)) - if i: - if PathGeom.pointsCoincide(i, edge.valueAt(edge.FirstParameter)): - debugPrint('P2', "------- insert exit plunge (%s)" % i) - self.edges.append(Part.Edge(Part.LineSegment(FreeCAD.Vector(i.x, i.y, self.tag.top()), i))) - e = None - tail = edge - elif PathGeom.pointsCoincide(i, edge.valueAt(edge.LastParameter)): - debugPrint('P2', "------- entire segment added (%s)" % i) - e = edge - tail = None - else: - e, tail = PathGeom.splitEdgeAt(edge, i) - if tail: - pf = tail.valueAt(tail.FirstParameter) - pl = tail.valueAt(tail.LastParameter) - debugPrint('P3', "----- P3 (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f))" % (pf.x, pf.y, pf.z, pl.x, pl.y, pl.z)) - else: - debugPrint('P3', "----- P3 (---)") - self.state = self.P3 - self.tail = tail - else: - debugPrint('P2', "----- P2 no intersection") - e = edge - tail = None - if e: - pf = e.valueAt(e.FirstParameter) - pl = e.valueAt(e.LastParameter) - s = 'P2' if self.state == self.P2 else 'P3' - debugPrint(s, "----- %s (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f))" % (s, pf.x, pf.y, pf.z, pl.x, pl.y, pl.z)) - self.edges.extend(self.tag.mapEdgeToSolid(e, 'P2')) - return tail - - def intersect(self, edge): - #print("") - #print(" >>> (%s - %s)" % (edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter))) - if edge and self.state == self.P0: - edge = self.intersectP0(edge) - if edge and self.state == self.P1: - edge = self.intersectP1(edge) - if edge and self.state == self.P2: - edge = self.intersectP2(edge) - return self - - - def mapEdgeToSolid(self, edge, label): - pf = edge.valueAt(edge.FirstParameter) - pl = edge.valueAt(edge.LastParameter) - print("--------- mapEdgeToSolid-%s: %s((%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f))" % (label, type(edge.Curve), pf.x, pf.y, pf.z, pl.x, pl.y, pl.z)) - - p1a = edge.valueAt(edge.FirstParameter) - p1a.z = self.bottom() - p1b = FreeCAD.Vector(p1a.x, p1a.y, self.height * 1.01) - e1 = Part.Edge(Part.LineSegment(p1a, p1b)) - p1 = self.nextIntersectionClosestTo(e1, self.solid, p1b) # top most intersection - print("---------- p1: (%s %s) -> %s %d" % (p1a, p1b, p1, self.solid.isInside(p1, 0.0000001, True))) - if not p1: - raise Exception('no p1') - return [] - - p2a = edge.valueAt(edge.LastParameter) - p2a.z = self.bottom() - p2b = FreeCAD.Vector(p2a.x, p2a.y, self.height * 1.01) - e2 = Part.Edge(Part.LineSegment(p2a, p2b)) - p2 = self.nextIntersectionClosestTo(e2, self.solid, p2b) # top most intersection - if not p2: - p1 = edge.valueAt(edge.FirstParameter) - p2 = edge.valueAt(edge.LastParameter) - print("---------- p1: %d%d" % (self.solid.isInside(p1, 0.0000001, True), self.solid.isInside(p1, 0.0000001, False))) - print("---------- p2: %d%d" % (self.solid.isInside(p2, 0.0000001, True), self.solid.isInside(p2, 0.0000001, False))) - #if not self.solid.isInside(p1, 0.0000001, False): - # p1 is on the solid - - raise Exception('no p2') - return [] - - print("---------- %s - %s" % (p1, p2)) - print("---------- (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (p2.x, p2.y, p2.z, p1.x, p1.y, p1.z)) - - if PathGeom.pointsCoincide(p1, p2): - return [] - - if type(edge.Curve) == Part.Line or type(edge.Curve) == Part.LineSegment: - e = Part.Edge(Part.LineSegment(p1, p2)) - debugEdge(e, "-------- >>") - return [e] - - m = FreeCAD.Matrix() - m.unity() - pd = p2 - p1 - - if type(edge.Curve) == Part.Circle: - m.A32 = pd.z / pd.y - m.A34 = - m.A32 - if pd.z < 0: - m.A34 *= p2.y - else: - m.A34 *= p1.y - e = edge.transformGeometry(m).Edges[0] - debugEdge(e, "-------- >>") - return [e] - - # it's already a helix, just need to lift it to the plateau - m.A33 = pd.z / (edge.valueAt(edge.LastParameter).z - edge.valueAt(edge.FirstParameter).z) - m.A34 = (1 - m.A33) - if pd.z < 0: - m.A34 *= edge.valueAt(edge.LastParameter).z - else: - m.A34 *= edge.valueAt(edge.FirstParameter).z - - #print - pf = edge.valueAt(edge.FirstParameter) - pl = edge.valueAt(edge.LastParameter) - #print("(%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f): %.2f" % (pf.x, pf.y, pf.z, pl.x, pl.y, pl.z, m.A33)) - #print("**** %.2f %.2f (%.2f - %.2f)" % (pd.z, p2a.z-p1a.z, p2a.z, p1a.z)) - e = edge.transformGeometry(m).Edges[0] - pf = e.valueAt(e.FirstParameter) - pl = e.valueAt(e.LastParameter) - #print("(%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (pf.x, pf.y, pf.z, pl.x, pl.y, pl.z)) - #raise Exception("mensch") - debugEdge(e, "-------- >>") - return [e] - - def filterIntersections(self, pts, face): if type(face.Surface) == Part.Cone or type(face.Surface) == Part.Cylinder: #print("it's a cone/cylinder, checking z") @@ -451,7 +166,7 @@ class Tag: c = face.Edges[0].Curve if (type(c) == Part.Circle): return filter(lambda pt: (pt - c.Center).Length <= c.Radius or PathGeom.isRoughly((pt - c.Center).Length, c.Radius), pts) - print("==== we got a %s" % face.Surface) + #print("==== we got a %s" % face.Surface) def isPointOnEdge(self, pt, edge): param = edge.Curve.parameter(pt) @@ -461,13 +176,11 @@ class Tag: return True if PathGeom.isRoughly(edge.FirstParameter, param) or PathGeom.isRoughly(edge.LastParameter, param): return True - print("-------- X %.2f <= %.2f <=%.2f (%.2f, %.2f, %.2f) %.2f:%.2f" % (edge.FirstParameter, param, edge.LastParameter, pt.x, pt.y, pt.z, edge.Curve.parameter(edge.valueAt(edge.FirstParameter)), edge.Curve.parameter(edge.valueAt(edge.LastParameter)))) + #print("-------- X %.2f <= %.2f <=%.2f (%.2f, %.2f, %.2f) %.2f:%.2f" % (edge.FirstParameter, param, edge.LastParameter, pt.x, pt.y, pt.z, edge.Curve.parameter(edge.valueAt(edge.FirstParameter)), edge.Curve.parameter(edge.valueAt(edge.LastParameter)))) p1 = edge.Vertexes[0] f1 = edge.Curve.parameter(FreeCAD.Vector(p1.X, p1.Y, p1.Z)) p2 = edge.Vertexes[1] f2 = edge.Curve.parameter(FreeCAD.Vector(p2.X, p2.Y, p2.Z)) - print("-------- (%.2f, %.2f, %.2f):%.2f (%.2f, %.2f, %.2f):%.2f" % (p1.X, p1.Y, p1.Z, f1, p2.X, p2.Y, p2.Z, f2)) - print("-------- %s %s" % (edge.Placement, edge.Orientation)) return False @@ -475,62 +188,144 @@ class Tag: ef = edge.valueAt(edge.FirstParameter) em = edge.valueAt((edge.FirstParameter+edge.LastParameter)/2) el = edge.valueAt(edge.LastParameter) - print("-------- intersect %s (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f) refp=(%.2f, %.2f, %.2f)" % (type(edge.Curve), ef.x, ef.y, ef.z, em.x, em.y, em.z, el.x, el.y, el.z, refPt.x, refPt.y, refPt.z)) + #print("-------- intersect %s (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f) refp=(%.2f, %.2f, %.2f)" % (type(edge.Curve), ef.x, ef.y, ef.z, em.x, em.y, em.z, el.x, el.y, el.z, refPt.x, refPt.y, refPt.z)) pts = [] for index, face in enumerate(solid.Faces): i = edge.Curve.intersect(face.Surface)[0] - print i + #print i ps = self.filterIntersections([FreeCAD.Vector(p.X, p.Y, p.Z) for p in i], face) pts.extend(filter(lambda pt: self.isPointOnEdge(pt, edge), ps)) if len(ps) != len(filter(lambda pt: self.isPointOnEdge(pt, edge), ps)): filtered = filter(lambda pt: self.isPointOnEdge(pt, edge), ps) - print("-------- ++ len(ps)=%d, len(filtered)=%d" % (len(ps), len(filtered))) + #print("-------- ++ len(ps)=%d, len(filtered)=%d" % (len(ps), len(filtered))) for p in ps: included = '+' if p in filtered else '-' - print("-------- %s (%.2f, %.2f, %.2f)" % (included, p.x, p.y, p.z)) + #print("-------- %s (%.2f, %.2f, %.2f)" % (included, p.x, p.y, p.z)) if pts: closest = sorted(pts, key=lambda pt: (pt - refPt).Length)[0] - for p in pts: - print("-------- - intersect pt : (%.2f, %.2f, %.2f)" % (p.x, p.y, p.z)) - print("-------- -> (%.2f, %.2f, %.2f)" % (closest.x, closest.y, closest.z)) + #for p in pts: + # print("-------- - intersect pt : (%.2f, %.2f, %.2f)" % (p.x, p.y, p.z)) + #print("-------- -> (%.2f, %.2f, %.2f)" % (closest.x, closest.y, closest.z)) return closest - print("-------- -> None") + #print("-------- -> None") return None - def intersect(self, edge, check = True): - print("--- intersect") - inters = self.Intersection(self) - if check: - if edge.valueAt(edge.FirstParameter).z < self.top() or edge.valueAt(edge.LastParameter).z < self.top(): - i = self.nextIntersectionClosestTo(edge, self.solid, edge.valueAt(edge.FirstParameter)) - if i: - print("---- (%.2f, %.2f, %.2f)" % (i.x, i.y, i.z)) - inters.state = self.Intersection.P0 - inters.p0 = i - if PathGeom.pointsCoincide(i, edge.valueAt(edge.LastParameter)): - print("---- entire edge consumed.") - inters.edges.append(edge) - return inters - if PathGeom.pointsCoincide(i, edge.valueAt(edge.FirstParameter)): - print("---- nothing of edge consumed.") - tail = edge - else: - print("---- split edge") - e,tail = PathGeom.splitEdgeAt(edge, i) - inters.edges.append(e) - return inters.intersect(tail) - else: - print("---- No intersection found.") - else: - print("---- Fly by") + def intersects(self, edge, param): + if edge.valueAt(edge.FirstParameter).z < self.top() or edge.valueAt(edge.LastParameter).z < self.top(): + return self.nextIntersectionClosestTo(edge, self.solid, edge.valueAt(param)) + return None + +class MapWireToTag: + def __init__(self, edge, tag, i): + self.tag = tag + if PathGeom.pointsCoincide(edge.valueAt(edge.FirstParameter), i): + tail = edge + self.commands = [] + elif PathGeom.pointsCoincide(edge.valueAt(edge.LastParameter), i): + debugEdge(edge, '++++++++ .') + self.commands = PathGeom.cmdsForEdge(edge) + tail = None else: - print("---- skipped") - # if we get here there is no intersection with the tag - inters.state = self.Intersection.Pnone - inters.tail = edge - return inters + e, tail = PathGeom.splitEdgeAt(edge, i) + debugEdge(e, '++++++++ .') + self.commands = PathGeom.cmdsForEdge(e) + self.tail = tail + self.edges = [] + self.entry = i + self.complete = False + self.wire = None + + def addEdge(self, edge): + if self.wire: + self.wire.add(edge) + else: + self.wire = Part.Wire(edge) + + def needToFlipEdge(self, edge, p): + if PathGeom.pointsCoincide(edge.valueAt(edge.LastParameter), p): + return True, edge.valueAt(edge.FirstParameter) + return False, edge.valueAt(edge.LastParameter) + + def cleanupEdges(self, edges): + # first remove all internal struts + inputEdges = copy.copy(edges) + plinths = [] + for e in edges: + p1 = e.valueAt(e.FirstParameter) + p2 = e.valueAt(e.LastParameter) + if p1.x == p2.x and p1.y == p2.y: + if not (PathGeom.edgeConnectsTo(e, self.entry) or PathGeom.edgeConnectsTo(e, self.exit)): + inputEdges.remove(e) + if p1.z > p2.z: + plinths.append(p2) + else: + plinths.append(p1) + # remove all edges that are connected to the plinths of the (former) internal struts + # including the edge that connects the entry and exit point directly + for e in copy.copy(inputEdges): + if PathGeom.edgeConnectsTo(e, self.entry) and PathGeom.edgeConnectsTo(e, self.exit): + inputEdges.remove(e) + continue + for p in plinths: + if PathGeom.edgeConnectsTo(e, p): + inputEdges.remove(e) + break + # the remaining edges form walk around the tag + # they need to be ordered and potentially flipped though + outputEdges = [] + p = self.entry + lastP = p + while inputEdges: + for e in inputEdges: + p1 = e.valueAt(e.FirstParameter) + p2 = e.valueAt(e.LastParameter) + if PathGeom.pointsCoincide(p1, p): + outputEdges.append((e,False)) + inputEdges.remove(e) + lastP = p + p = p2 + debugEdge(e, ">>>>> no flip") + break + elif PathGeom.pointsCoincide(p2, p): + outputEdges.append((e,True)) + inputEdges.remove(e) + lastP = p + p = p1 + debugEdge(e, ">>>>> flip") + break + #else: + # debugEdge(e, "<<<<< (%.2f, %.2f, %.2f)" % (p.x, p.y, p.z)) + if lastP == p: + raise ValueError("No connection to %s" % (p)) + #else: + # print("xxxxxx (%.2f, %.2f, %.2f)" % (p.x, p.y, p.z)) + return outputEdges + + def add(self, edge): + self.tail = None + if self.tag.solid.isInside(edge.valueAt(edge.LastParameter), 0.000001, True): + self.addEdge(edge) + else: + i = self.tag.intersects(edge, edge.LastParameter) + if PathGeom.pointsCoincide(i, edge.valueAt(edge.FirstParameter)): + self.tail = edge + else: + e, tail = PathGeom.splitEdgeAt(edge, i) + self.addEdge(e) + self.tail = tail + self.exit = i + shell = self.wire.extrude(FreeCAD.Vector(0, 0, 10)) + face = shell.common(self.tag.solid) + + for e,flip in self.cleanupEdges(face.Edges): + debugEdge(e, '++++++++ %s' % ('.' if not flip else '@')) + self.commands.extend(PathGeom.cmdsForEdge(e, flip, False)) + self.complete = True + + def mappingComplete(self): + return self.complete class PathData: def __init__(self, obj): @@ -548,36 +343,11 @@ class PathData: bottom = [e for e in edges if e.Vertexes[0].Point.z == minZ and e.Vertexes[1].Point.z == minZ] wire = Part.Wire(bottom) if wire.isClosed(): - #return Part.Wire(self.sortedBase(bottom)) return wire # if we get here there are already holding tags, or we're not looking at a profile # let's try and insert the missing pieces - another day raise ValueError("Selected path doesn't seem to be a Profile operation.") - def sortedBase(self, base): - # first find the exit point, where base wire is closed - edges = [e for e in self.edges if e.valueAt(e.FirstParameter).z == self.minZ and e.valueAt(e.LastParameter).z != self.maxZ] - exit = sorted(edges, key=lambda e: -e.valueAt(e.LastParameter).z)[0] - pt = exit.valueAt(exit.FirstParameter) - # then find the first base edge, and sort them until done - ordered = [] - while base: - edges = [e for e in base if e.valueAt(e.FirstParameter) == pt] - if not edges: - print ordered - print base - print("(%.2f, %.2f, %.2f)" % (pt.x, pt.y, pt.z)) - for e in base: - pf = e.valueAt(e.FirstParameter) - pl = e.valueAt(e.LastParameter) - print("(%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (pf.x, pf.y, pf.z, pl.x, pl.y, pl.z)) - edge = edges[0] - ordered.append(edge) - base.remove(edge) - pt = edge.valueAt(edge.LastParameter) - return ordered - - def findZLimits(self, edges): # not considering arcs and spheres in Z direction, find the highes and lowest Z values minZ = edges[0].Vertexes[0].Point.z @@ -721,14 +491,6 @@ class ObjectDressup: return self.pathData.generateTags(obj, count, width, height, angle, spacing) - def tagIntersection(self, face, edge): - p1 = edge.valueAt(edge.FirstParameter) - pts = edge.Curve.intersect(face.Surface) - if pts[0]: - closest = sorted(pts[0], key=lambda pt: (pt - p1).Length)[0] - return closest - return None - def createPath(self, edges, tags): commands = [] lastEdge = 0 @@ -738,36 +500,39 @@ class ObjectDressup: inters = None edge = None + mapper = None + while edge or lastEdge < len(edges): - print("------- lastEdge = %d/%d.%d/%d" % (lastEdge, lastTag, t, len(tags))) + #print("------- lastEdge = %d/%d.%d/%d" % (lastEdge, lastTag, t, len(tags))) if not edge: edge = edges[lastEdge] debugEdge(edge, "======= new edge: %d/%d" % (lastEdge, len(edges))) lastEdge += 1 sameTag = None - if inters: - inters = inters.intersect(edge) - else: + if mapper: + mapper.add(edge) + if mapper.mappingComplete(): + commands.extend(mapper.commands) + edge = mapper.tail + mapper = None + else: + edge = None + + if edge: tIndex = (t + lastTag) % len(tags) t += 1 - print("<<<<< lastTag=%d, t=%d, tIndex=%d, sameTag=%s >>>>>>" % (lastTag, t, tIndex, sameTag)) - inters = tags[tIndex].intersect(edge, True or tIndex != sameTag) - edge = inters.tail + i = tags[tIndex].intersects(edge, edge.FirstParameter) + if i: + mapper = MapWireToTag(edge, tags[tIndex], i) + edge = mapper.tail - if inters.isComplete(): - if inters.hasEdges(): - sameTag = (t + lastTag - 1) % len(tags) - lastTag = sameTag - t = 1 - for e in inters.edges: - commands.append(PathGeom.cmdForEdge(e)) - inters = None - if t >= len(tags): + if not mapper and t >= len(tags): # gone through all tags, consume edge and move on if edge: - commands.append(PathGeom.cmdForEdge(edge)) + debugEdge(edge, '++++++++') + commands.extend(PathGeom.cmdsForEdge(edge)) edge = None t = 0 diff --git a/src/Mod/Path/PathScripts/PathGeom.py b/src/Mod/Path/PathScripts/PathGeom.py index c0710f28f..1b6d7369e 100644 --- a/src/Mod/Path/PathScripts/PathGeom.py +++ b/src/Mod/Path/PathScripts/PathGeom.py @@ -130,6 +130,14 @@ class PathGeom: @classmethod def cmdsForEdge(cls, edge, flip = False, useHelixForBSpline = True): + """(edge, flip = False, useHelixForBSpline = True) -> List(Path.Command) + Returns a list of Path.Command representing the given edge. + If flip is True the edge is considered to be backwards. + If useHelixForBSpline is True an Edge based on a BSplineCurve is considered + to represent a helix and results in G2 or G3 command. Otherwise edge has + no direct Path.Command mapping and will be approximated by straight segments. + Approximation is also the approach for edges that are neither straight lines + nor arcs (nor helixes).""" pt = edge.valueAt(edge.LastParameter) if not flip else edge.valueAt(edge.FirstParameter) params = {'X': pt.x, 'Y': pt.y, 'Z': pt.z} if type(edge.Curve) == Part.Line or type(edge.Curve) == Part.LineSegment: @@ -147,7 +155,7 @@ class PathGeom: cmd = 'G3' else: cmd = 'G2' - print("**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (p1.x, p1.y, p1.z, p2.x, p2.y, p2.z, p3.x, p3.y, p3.z)) + #print("**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (p1.x, p1.y, p1.z, p2.x, p2.y, p2.z, p3.x, p3.y, p3.z)) pd = Part.Circle(PathGeom.xy(p1), PathGeom.xy(p2), PathGeom.xy(p3)).Center pa = PathGeom.xy(p1) @@ -168,7 +176,7 @@ class PathGeom: # at this point pixellation is all we can do commands = [] segments = int(math.ceil((deviation / eStraight.Length) * 1000)) - print("**** pixellation with %d segments" % segments) + #print("**** pixellation with %d segments" % segments) dParameter = (edge.LastParameter - edge.FirstParameter) / segments for i in range(0, segments): if flip: @@ -176,7 +184,7 @@ class PathGeom: else: p = edge.valueAt(edge.FirstParameter + (i + 1) * dParameter) cmd = Path.Command('G1', {'X': p.x, 'Y': p.y, 'Z': p.z}) - print("***** %s" % cmd) + #print("***** %s" % cmd) commands.append(cmd) #print commands return commands @@ -280,8 +288,8 @@ class PathGeom: params.update({'Z': z1, 'K': (z1 - z0)/2}) command = Path.Command(cmd.Name, params) - print("- (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f): %.2f:%.2f" % (edge.Vertexes[0].X, edge.Vertexes[0].Y, edge.Vertexes[0].Z, edge.Vertexes[1].X, edge.Vertexes[1].Y, edge.Vertexes[1].Z, z0, z1)) - print("- %s -> %s" % (cmd, command)) + #print("- (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f): %.2f:%.2f" % (edge.Vertexes[0].X, edge.Vertexes[0].Y, edge.Vertexes[0].Z, edge.Vertexes[1].X, edge.Vertexes[1].Y, edge.Vertexes[1].Z, z0, z1)) + #print("- %s -> %s" % (cmd, command)) return cls.edgeForCmd(command, FreeCAD.Vector(p1.x, p1.y, z0)) diff --git a/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py b/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py index 902f83251..d332a5b62 100644 --- a/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py +++ b/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py @@ -33,7 +33,7 @@ from FreeCAD import Vector from PathScripts.PathDressupHoldingTags import * from PathTests.PathTestUtils import PathTestBase -class TestTag01BasicTag(PathTestBase): # ============= +class TestHoldingTags(PathTestBase): """Unit tests for the HoldingTags dressup.""" def test00(self): @@ -89,577 +89,3 @@ class TestTag01BasicTag(PathTestBase): # ============= self.assertIsNone(tag.core) -class TestTag02SquareTag(PathTestBase): # ============= - """Unit tests for square tags.""" - - def test00(self): - """Verify no intersection.""" - tag = Tag( 0, 0, 4, 7, 90, True, 0) - pt1 = Vector(+5, 5, 0) - pt2 = Vector(-5, 5, 0) - edge = Part.Edge(Part.LineSegment(pt1, pt2)) - - i = tag.intersect(edge) - self.assertIsNotNone(i) - self.assertTrue(i.isComplete()) - self.assertIsNotNone(i.edges) - self.assertFalse(i.edges) - self.assertLine(i.tail, pt1, pt2) - - def test01(self): - """Verify intersection of square tag with line ending at tag start.""" - tag = Tag( 0, 0, 8, 3, 90, True, 0) - edge = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(4, 0, 0))) - - i = tag.intersect(edge) - self.assertEqual(i.state, Tag.Intersection.P0) - self.assertEqual(len(i.edges), 1) - self.assertLine(i.edges[0], edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter)) - self.assertIsNone(i.tail) - - def test02(self): - """Verify intersection of square tag with line ending between P1 and P2.""" - tag = Tag( 0, 0, 8, 3, 90, True, 0) - edge = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(1, 0, 0))) - - i = tag.intersect(edge) - self.assertEqual(i.state, Tag.Intersection.P1) - self.assertEqual(len(i.edges), 3) - p1 = Vector(4, 0, 0) - p2 = Vector(4, 0, 3) - p3 = Vector(1, 0, 3) - self.assertLine(i.edges[0], edge.valueAt(edge.FirstParameter), p1) - self.assertLine(i.edges[1], p1, p2) - self.assertLine(i.edges[2], p2, p3) - self.assertIsNone(i.tail) - - # verify we stay in P1 if we add another segment - edge = Part.Edge(Part.LineSegment(edge.valueAt(edge.LastParameter), Vector(0, 0, 0))) - i = i.intersect(edge) - self.assertEqual(i.state, Tag.Intersection.P1) - self.assertEqual(len(i.edges), 4) - p4 = Vector(0, 0, 3) - self.assertLine(i.edges[3], p3, p4) - self.assertIsNone(i.tail) - - def test03(self): - """Verify intesection of square tag with line ending on P2.""" - tag = Tag( 0, 0, 8, 3, 90, True, 0) - edge = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(-4, 0, 0))) - - i = tag.intersect(edge) - self.assertEqual(i.state, Tag.Intersection.P2) - self.assertEqual(len(i.edges), 3) - p0 = edge.valueAt(edge.FirstParameter) - p1 = Vector( 4, 0, 0) - p2 = Vector( 4, 0, 3) - p3 = Vector(-4, 0, 3) - self.assertLine(i.edges[0], p0, p1) - self.assertLine(i.edges[1], p1, p2) - self.assertLine(i.edges[2], p2, p3) - self.assertIsNone(i.tail) - - # make sure it also works if we get there not directly - edge = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(0, 0, 0))) - i = tag.intersect(edge) - edge = Part.Edge(Part.LineSegment(edge.valueAt(edge.LastParameter), Vector(-4, 0, 0))) - i = i.intersect(edge) - self.assertEqual(i.state, Tag.Intersection.P2) - self.assertEqual(len(i.edges), 4) - p2a = Vector( 0, 0, 3) - self.assertLine(i.edges[0], p0, p1) - self.assertLine(i.edges[1], p1, p2) - self.assertLine(i.edges[2], p2, p2a) - self.assertLine(i.edges[3], p2a, p3) - self.assertIsNone(i.tail) - - def test04(self): - """Verify plunge down is inserted for square tag on exit.""" - tag = Tag( 0, 0, 8, 3, 90, True, 0) - edge = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(-5, 0, 0))) - - i = tag.intersect(edge) - self.assertEqual(i.state, Tag.Intersection.P3) - self.assertTrue(i.isComplete()) - self.assertEqual(len(i.edges), 4) - p0 = edge.valueAt(edge.FirstParameter) - p1 = Vector( 4, 0, 0) - p2 = Vector( 4, 0, 3) - p3 = Vector(-4, 0, 3) - p4 = Vector(-4, 0, 0) - p5 = edge.valueAt(edge.LastParameter) - self.assertLine(i.edges[0], p0, p1) - self.assertLine(i.edges[1], p1, p2) - self.assertLine(i.edges[2], p2, p3) - self.assertLine(i.edges[3], p3, p4) - self.assertIsNotNone(i.tail) - self.assertLine(i.tail, p4, p5) - - def test05(self): - """Verify all lines between P0 and P3 are added.""" - tag = Tag( 0, 0, 4, 7, 90, True, 0) - e0 = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(+2, 0, 0))) - e1 = Part.Edge(Part.LineSegment(e0.valueAt(e0.LastParameter), Vector(+1, 0, 0))) - e2 = Part.Edge(Part.LineSegment(e1.valueAt(e1.LastParameter), Vector(+0.5, 0, 0))) - e3 = Part.Edge(Part.LineSegment(e2.valueAt(e2.LastParameter), Vector(-0.5, 0, 0))) - e4 = Part.Edge(Part.LineSegment(e3.valueAt(e3.LastParameter), Vector(-1, 0, 0))) - e5 = Part.Edge(Part.LineSegment(e4.valueAt(e4.LastParameter), Vector(-2, 0, 0))) - e6 = Part.Edge(Part.LineSegment(e5.valueAt(e5.LastParameter), Vector(-5, 0, 0))) - - i = tag - for e in [e0, e1, e2, e3, e4, e5]: - i = i.intersect(e) - self.assertFalse(i.isComplete()) - i = i.intersect(e6) - self.assertTrue(i.isComplete()) - - pt0 = Vector(2, 0, 0) - pt1 = Vector(2, 0, 7) - pt2 = Vector(1, 0, 7) - pt3 = Vector(0.5, 0, 7) - pt4 = Vector(-0.5, 0, 7) - pt5 = Vector(-1, 0, 7) - pt6 = Vector(-2, 0, 7) - - self.assertEqual(len(i.edges), 8) - self.assertLines(i.edges, i.tail, [e0.valueAt(e0.FirstParameter), pt0, pt1, pt2, pt3, pt4, pt5, pt6, e6.valueAt(e6.FirstParameter), e6.valueAt(e6.LastParameter)]) - self.assertIsNotNone(i.tail) - - def test06(self): - """Verify intersection of different z levels.""" - tag = Tag( 0, 0, 4, 7, 90, True, 0) - # for all lines below 7 we get the trapezoid - for i in range(0, 7): - p0 = Vector(5, 0, i) - p1 = Vector(2, 0, i) - p2 = Vector(2, 0, 7) - p3 = Vector(-2, 0, 7) - p4 = Vector(-2, 0, i) - p5 = Vector(-5, 0, i) - edge = Part.Edge(Part.LineSegment(p0, p5)) - s = tag.intersect(edge) - self.assertTrue(s.isComplete()) - self.assertLines(s.edges, s.tail, [p0, p1, p2, p3, p4, p5]) - - # for all edges at height or above the original line is used - for i in range(7, 9): - edge = Part.Edge(Part.LineSegment(Vector(5, 0, i), Vector(-5, 0, i))) - s = tag.intersect(edge) - self.assertTrue(s.isComplete()) - self.assertLine(s.tail, edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter)) - - def test10(self): - """Verify intersection of square tag with an arc.""" - tag = Tag( 0, 0, 8, 3, 90, True, 0) - p1 = Vector(10, -10, 0) - p2 = Vector(10, +10, 0) - edge = PathGeom.edgeForCmd(Path.Command('G2', {'X': p2.x, 'Y': p2.y, 'Z': p2.z, 'J': 10}), p1) - - pi = Vector(0.8, -3.919184, 0) - pj = Vector(0.8, +3.919184, 0) - - s = tag.intersect(edge) - self.assertTrue(s.isComplete()) - self.assertEqual(len(s.edges), 4) - self.assertCurve(s.edges[0], p1, Vector(4.486010, -8.342417, 0), pi) - self.assertLine(s.edges[1], pi, pi + Vector(0, 0, 3)) - self.assertCurve(s.edges[2], pi + Vector(0, 0, 3), Vector(0, 0, 3), pj + Vector(0, 0, 3)) - self.assertLine(s.edges[3], pj + Vector(0, 0, 3), pj) - self.assertCurve(s.tail, pj, Vector(4.486010, +8.342417, 0), p2) - - def test20(self): - """Verify intersection of square tag with a helix.""" - tag = Tag( 0, 0, 8, 3, 90, True, 0) - p1 = Vector(10, -10, 0) - p2 = Vector(10, +10, 2) - edge = PathGeom.edgeForCmd(Path.Command('G2', {'X': p2.x, 'Y': p2.y, 'Z': p2.z, 'J': 10, 'K': 1}), p1) - - pi = Vector(0.8, -3.919184, 0.743623) - pj = Vector(0.8, +3.919184, 1.256377) - - s = tag.intersect(edge) - self.assertTrue(s.isComplete()) - self.assertEqual(len(s.edges), 4) - self.assertCurve(s.edges[0], p1, Vector(4.486010, -8.342417, 0.371812), pi) - self.assertLine(s.edges[1], pi, pi + Vector(0, 0, 3-pi.z)) - self.assertCurve(s.edges[2], pi + Vector(0, 0, 3-pi.z), Vector(0, 0, 3), pj + Vector(0, 0, 3-pj.z)) - self.assertLine(s.edges[3], pj + Vector(0, 0, 3-pj.z), pj) - self.assertCurve(s.tail, pj, Vector(4.486010, +8.342417, 1.628188), p2) - - -class TestTag03TrapezoidTag(PathTestBase): # ============= - """Unit tests for trapezoid tags.""" - - def test00(self): - """Verify no intersection.""" - tag = Tag( 0, 0, 8, 3, 45, True, 0) - pt1 = Vector(+5, 5, 0) - pt2 = Vector(-5, 5, 0) - edge = Part.Edge(Part.LineSegment(pt1, pt2)) - - i = tag.intersect(edge) - self.assertIsNotNone(i) - self.assertTrue(i.isComplete()) - self.assertIsNotNone(i.edges) - self.assertFalse(i.edges) - self.assertLine(i.tail, pt1, pt2) - - def test01(self): - """Veify intersection of trapezoid tag with line ending before P1.""" - tag = Tag( 0, 0, 8, 3, 45, True, 0) - edge = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(4, 0, 0))) - - i = tag.intersect(edge) - self.assertEqual(i.state, Tag.Intersection.P0) - self.assertEqual(len(i.edges), 1) - self.assertLine(i.edges[0], edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter)) - self.assertIsNone(i.tail) - - # now add another segment that doesn't reach the top of the cone - edge = Part.Edge(Part.LineSegment(edge.valueAt(edge.LastParameter), Vector(3, 0, 0))) - i = i.intersect(edge) - # still a P0 and edge fully consumed - p1 = Vector(edge.valueAt(edge.FirstParameter)) - p1.z = 0 - p2 = Vector(edge.valueAt(edge.LastParameter)) - p2.z = 1 # height of cone @ (3,0) - self.assertEqual(i.state, Tag.Intersection.P0) - self.assertEqual(len(i.edges), 2) - self.assertLine(i.edges[1], p1, p2) - self.assertIsNone(i.tail) - - # add another segment to verify starting point offset - edge = Part.Edge(Part.LineSegment(edge.valueAt(edge.LastParameter), Vector(2, 0, 0))) - i = i.intersect(edge) - # still a P0 and edge fully consumed - p3 = Vector(edge.valueAt(edge.LastParameter)) - p3.z = 2 # height of cone @ (2,0) - self.assertEqual(i.state, Tag.Intersection.P0) - self.assertEqual(len(i.edges), 3) - self.assertLine(i.edges[2], p2, p3) - self.assertIsNone(i.tail) - - def test02(self): - """Verify intersection of trapezoid tag with line ending between P1 and P2""" - tag = Tag( 0, 0, 8, 3, 45, True, 0) - edge = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(1, 0, 0))) - - i = tag.intersect(edge) - self.assertEqual(i.state, Tag.Intersection.P1) - self.assertEqual(len(i.edges), 2) - p1 = Vector(4, 0, 0) - p2 = Vector(1, 0, 3) - self.assertLine(i.edges[0], edge.valueAt(edge.FirstParameter), p1) - self.assertLine(i.edges[1], p1, p2) - self.assertIsNone(i.tail) - - # verify we stay in P1 if we add another segment - edge = Part.Edge(Part.LineSegment(edge.valueAt(edge.LastParameter), Vector(0, 0, 0))) - i = i.intersect(edge) - self.assertEqual(i.state, Tag.Intersection.P1) - self.assertEqual(len(i.edges), 3) - p3 = Vector(0, 0, 3) - self.assertLine(i.edges[2], p2, p3) - self.assertIsNone(i.tail) - - def test03(self): - """Verify intersection of trapezoid tag with edge ending on P2.""" - tag = Tag( 0, 0, 8, 3, 45, True, 0) - edge = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(-1, 0, 0))) - - i = tag.intersect(edge) - self.assertEqual(i.state, Tag.Intersection.P2) - p0 = Vector(edge.valueAt(edge.FirstParameter)) - p1 = Vector(4, 0, 0) - p2 = Vector(1, 0, 3) - p3 = Vector(-1, 0, 3) - self.assertLines(i.edges, i.tail, [p0, p1, p2, p3]) - self.assertIsNone(i.tail) - - # make sure we get the same result if there's another edge - edge = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(1, 0, 0))) - i = tag.intersect(edge) - edge = Part.Edge(Part.LineSegment(edge.valueAt(edge.LastParameter), Vector(-1, 0, 0))) - i = i.intersect(edge) - self.assertEqual(i.state, Tag.Intersection.P2) - self.assertLines(i.edges, i.tail, [p0, p1, p2, p3]) - self.assertIsNone(i.tail) - - # and also if the last segment doesn't cross the entire plateau - edge = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(0.5, 0, 0))) - i = tag.intersect(edge) - edge = Part.Edge(Part.LineSegment(edge.valueAt(edge.LastParameter), Vector(-1, 0, 0))) - i = i.intersect(edge) - self.assertEqual(i.state, Tag.Intersection.P2) - p2a = Vector(0.5, 0, 3) - self.assertLines(i.edges, i.tail, [p0, p1, p2, p2a, p3]) - self.assertIsNone(i.tail) - - def test04(self): - """Verify proper down plunge on trapezoid tag exit.""" - tag = Tag( 0, 0, 8, 3, 45, True, 0) - edge = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(-2, 0, 0))) - - i = tag.intersect(edge) - self.assertEqual(i.state, Tag.Intersection.P2) - p0 = Vector(5, 0, 0) - p1 = Vector(4, 0, 0) - p2 = Vector(1, 0, 3) - p3 = Vector(-1, 0, 3) - p4 = Vector(-2, 0, 2) - self.assertLines(i.edges, i.tail, [p0, p1, p2, p3, p4]) - self.assertIsNone(i.tail) - - # make sure adding another segment doesn't change the state - edge = Part.Edge(Part.LineSegment(edge.valueAt(edge.LastParameter), Vector(-3, 0, 0))) - i = i.intersect(edge) - self.assertEqual(i.state, Tag.Intersection.P2) - self.assertEqual(len(i.edges), 5) - p5 = Vector(-3, 0, 1) - self.assertLine(i.edges[4], p4, p5) - self.assertIsNone(i.tail) - - # now if we complete to P3 .... - edge = Part.Edge(Part.LineSegment(edge.valueAt(edge.LastParameter), Vector(-4, 0, 0))) - i = i.intersect(edge) - self.assertEqual(i.state, Tag.Intersection.P3) - self.assertTrue(i.isComplete()) - self.assertEqual(len(i.edges), 6) - p6 = Vector(-4, 0, 0) - self.assertLine(i.edges[5], p5, p6) - self.assertIsNone(i.tail) - - # verify proper operation if there is a single edge going through all - edge = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(-4, 0, 0))) - i = tag.intersect(edge) - self.assertEqual(i.state, Tag.Intersection.P3) - self.assertTrue(i.isComplete()) - self.assertLines(i.edges, i.tail, [p0, p1, p2, p3, p6]) - self.assertIsNone(i.tail) - - # verify tail is added as well - edge = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(-5, 0, 0))) - i = tag.intersect(edge) - self.assertEqual(i.state, Tag.Intersection.P3) - self.assertTrue(i.isComplete()) - self.assertLines(i.edges, i.tail, [p0, p1, p2, p3, p6, edge.valueAt(edge.LastParameter)]) - self.assertIsNotNone(i.tail) - - def test05(self): - """Verify all lines between P0 and P3 are added.""" - tag = Tag( 0, 0, 8, 3, 45, True, 0) - e0 = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(+4, 0, 0))) - e1 = Part.Edge(Part.LineSegment(e0.valueAt(e0.LastParameter), Vector(+2, 0, 0))) - e2 = Part.Edge(Part.LineSegment(e1.valueAt(e1.LastParameter), Vector(+0.5, 0, 0))) - e3 = Part.Edge(Part.LineSegment(e2.valueAt(e2.LastParameter), Vector(-0.5, 0, 0))) - e4 = Part.Edge(Part.LineSegment(e3.valueAt(e3.LastParameter), Vector(-1, 0, 0))) - e5 = Part.Edge(Part.LineSegment(e4.valueAt(e4.LastParameter), Vector(-2, 0, 0))) - e6 = Part.Edge(Part.LineSegment(e5.valueAt(e5.LastParameter), Vector(-5, 0, 0))) - - i = tag - for e in [e0, e1, e2, e3, e4, e5]: - i = i.intersect(e) - self.assertFalse(i.isComplete()) - i = i.intersect(e6) - self.assertTrue(i.isComplete()) - - p0 = Vector(4, 0, 0) - p1 = Vector(2, 0, 2) - p2 = Vector(1, 0, 3) - p3 = Vector(0.5, 0, 3) - p4 = Vector(-0.5, 0, 3) - p5 = Vector(-1, 0, 3) - p6 = Vector(-2, 0, 2) - p7 = Vector(-4, 0, 0) - - self.assertLines(i.edges, i.tail, [e0.valueAt(e0.FirstParameter), p0, p1, p2, p3, p4, p5, p6, p7, e6.valueAt(e6.LastParameter)]) - self.assertIsNotNone(i.tail) - - def test06(self): - """Verify intersection for different z levels.""" - tag = Tag( 0, 0, 8, 3, 45, True, 0) - # for all lines below 3 we get the trapezoid - for i in range(0, 3): - p0 = Vector(5, 0, i) - p1 = Vector(4-i, 0, i) - p2 = Vector(1, 0, 3) - p3 = Vector(-1, 0, 3) - p4 = Vector(-4+i, 0, i) - p5 = Vector(-5, 0, i) - edge = Part.Edge(Part.LineSegment(p0, p5)) - s = tag.intersect(edge) - self.assertTrue(s.isComplete()) - self.assertLines(s.edges, s.tail, [p0, p1, p2, p3, p4, p5]) - - # for all edges at height or above the original line is used - for i in range(3, 5): - edge = Part.Edge(Part.LineSegment(Vector(5, 0, i), Vector(-5, 0, i))) - s = tag.intersect(edge) - self.assertTrue(s.isComplete()) - self.assertLine(s.tail, edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter)) - - def test10(self): - """Verify intersection with an arc.""" - tag = Tag( 0, 0, 8, 3, 45, True, 0) - p1 = Vector(10, -10, 0) - p2 = Vector(10, +10, 0) - edge = PathGeom.edgeForCmd(Path.Command('G2', {'X': p2.x, 'Y': p2.y, 'Z': p2.z, 'J': 10}), p1) - - pi = Vector(0.8, -3.919184, 0) - pj = Vector(0.05, -0.998749, 3) - pk = Vector(0.05, +0.998749, 3) - pl = Vector(0.8, +3.919184, 0) - - s = tag.intersect(edge) - self.assertTrue(s.isComplete()) - self.assertEqual(len(s.edges), 4) - self.assertCurve(s.edges[0], p1, Vector(4.486010, -8.342417, 0), pi) - self.assertCurve(s.edges[1], pi, Vector(0.314296, -2.487396, 1.470795), pj) - self.assertCurve(s.edges[2], pj, Vector(0, 0, 3), pk) - self.assertCurve(s.edges[3], pk, Vector(.3142960, +2.487396, 1.470795), pl) - self.assertCurve(s.tail, pl, Vector(4.486010, +8.342417, 0), p2) - - def test20(self): - """Verify intersection with a helix.""" - tag = Tag( 0, 0, 8, 3, 45, True, 0) - p1 = Vector(10, -10, 0) - p2 = Vector(10, +10, 2) - edge = PathGeom.edgeForCmd(Path.Command('G2', {'X': p2.x, 'Y': p2.y, 'Z': p2.z, 'J': 10, 'K': 1}), p1) - - pi = Vector(0.513574, -3.163498, 0.795085) - pj = Vector(0.050001, -0.998749, 3) - pk = Vector(0.050001, +0.998749, 3) - pl = Vector(0.397586, +2.791711, 1.180119) - - s = tag.intersect(edge) - self.assertTrue(s.isComplete()) - self.assertEqual(len(s.edges), 4) - self.assertCurve(s.edges[0], p1, Vector(4.153420, -8.112798, 0.397543), pi) - self.assertCurve(s.edges[1], pi, Vector(0.221698, -2.093992, 1.897543), pj) - self.assertCurve(s.edges[2], pj, Vector(0, 0, 3), pk) - self.assertCurve(s.edges[3], pk, Vector(0.182776, 1.903182, 2.090060), pl) - self.assertCurve(s.tail, pl, Vector(3.996548, +7.997409, 1.590060), p2) - - -class TestTag04TriangularTag(PathTestBase): # ======================== - """Unit tests for tags that take on a triangular shape.""" - - def test00(self): - """Verify intersection of triangular tag with line ending at tag start.""" - tag = Tag( 0, 0, 8, 7, 45, True, 0) - edge = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(4, 0, 0))) - - i = tag.intersect(edge) - self.assertEqual(i.state, Tag.Intersection.P0) - self.assertEqual(len(i.edges), 1) - self.assertLine(i.edges[0], edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter)) - self.assertIsNone(i.tail) - - def test01(self): - """Verify intersection of triangular tag with line ending between P0 and P1.""" - tag = Tag( 0, 0, 8, 7, 45, True, 0) - edge = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(3, 0, 0))) - - i = tag.intersect(edge) - self.assertEqual(i.state, Tag.Intersection.P0) - p1 = Vector(4, 0, 0) - p2 = Vector(3, 0, 1) - self.assertLines(i.edges, i.tail, [edge.valueAt(edge.FirstParameter), p1, p2]) - self.assertIsNone(i.tail) - - # verify we stay in P1 if we add another segment - edge = Part.Edge(Part.LineSegment(edge.valueAt(edge.LastParameter), Vector(1, 0, 0))) - i = i.intersect(edge) - self.assertEqual(i.state, Tag.Intersection.P0) - self.assertEqual(len(i.edges), 3) - p3 = Vector(1, 0, 3) - self.assertLine(i.edges[2], p2, p3) - self.assertIsNone(i.tail) - - def test02(self): - """Verify proper down plunge on exit of triangular tag.""" - tag = Tag( 0, 0, 8, 7, 45, True, 0) - - p0 = Vector(5, 0, 0) - p1 = Vector(4, 0, 0) - p2 = Vector(0, 0, 4) - edge = Part.Edge(Part.LineSegment(p0, FreeCAD.Vector(0,0,0))) - i = tag.intersect(edge) - self.assertEqual(i.state, Tag.Intersection.P2) - self.assertEqual(len(i.edges), 2) - self.assertLines(i.edges, i.tail, [p0, p1, p2]) - - # adding another segment doesn't make a difference - edge = Part.Edge(Part.LineSegment(edge.valueAt(edge.LastParameter), FreeCAD.Vector(-3,0,0))) - i = i.intersect(edge) - self.assertEqual(i.state, Tag.Intersection.P2) - self.assertEqual(len(i.edges), 3) - p3 = Vector(-3, 0, 1) - self.assertLines(i.edges, i.tail, [p0, p1, p2, p3]) - - # same result if all is one line - edge = Part.Edge(Part.LineSegment(p0, edge.valueAt(edge.LastParameter))) - i = tag.intersect(edge) - self.assertEqual(i.state, Tag.Intersection.P2) - self.assertLines(i.edges, i.tail, [p0, p1, p2, p3]) - - def test03(self): - """Verify triangular tag shap on intersection.""" - tag = Tag( 0, 0, 8, 7, 45, True, 0) - - p0 = Vector(5, 0, 0) - p1 = Vector(4, 0, 0) - p2 = Vector(0, 0, 4) - p3 = Vector(-4, 0, 0) - edge = Part.Edge(Part.LineSegment(p0, p3)) - i = tag.intersect(edge) - self.assertTrue(i.isComplete()) - self.assertLines(i.edges, i.tail, [p0, p1, p2, p3]) - self.assertIsNone(i.tail) - - # this should also work if there is some excess, aka tail - p4 = Vector(-5, 0, 0) - edge = Part.Edge(Part.LineSegment(p0, p4)) - i = tag.intersect(edge) - self.assertTrue(i.isComplete()) - self.assertLines(i.edges, i.tail, [p0, p1, p2, p3, p4]) - self.assertIsNotNone(i.tail) - - def test10(self): - """Verify intersection with an arc.""" - tag = Tag( 0, 0, 8, 7, 45, True, 0) - p1 = Vector(10, -10, 0) - p2 = Vector(10, +10, 0) - edge = PathGeom.edgeForCmd(Path.Command('G2', {'X': p2.x, 'Y': p2.y, 'Z': p2.z, 'J': 10}), p1) - - pi = Vector(0.8, -3.919184, 0) - pj = Vector(0.0, 0.0, 4) - pk = Vector(0.8, +3.919184, 0) - - s = tag.intersect(edge) - self.assertTrue(s.isComplete()) - self.assertEqual(len(s.edges), 3) - self.assertCurve(s.edges[0], p1, Vector(4.486010, -8.342417, 0), pi) - self.assertCurve(s.edges[1], pi, Vector(0.202041, -2., 1.958759), pj) - self.assertCurve(s.edges[2], pj, Vector(0.202041, +2., 1.958759), pk) - self.assertCurve(s.tail, pk, Vector(4.486010, +8.342417, 0), p2) - - def test20(self): - """Verify intersection with a helix.""" - tag = Tag( 0, 0, 8, 7, 45, True, 0) - p1 = Vector(10, -10, 0) - p2 = Vector(10, +10, 2) - edge = PathGeom.edgeForCmd(Path.Command('G2', {'X': p2.x, 'Y': p2.y, 'Z': p2.z, 'J': 10, 'K': 1}), p1) - - pi = Vector(0.513574, -3.163498, 0.795085) - pj = Vector(0, 0, 4) - pk = Vector(0.397586, +2.791711, 1.180119) - - s = tag.intersect(edge) - self.assertTrue(s.isComplete()) - self.assertEqual(len(s.edges), 3) - self.assertCurve(s.edges[0], p1, Vector(4.153420, -8.112798, 0.397543), pi) - self.assertCurve(s.edges[1], pi, Vector(0.129229, -1.602457, 2.397542), pj) - self.assertCurve(s.edges[2], pj, Vector(0.099896, 1.409940, 2.590059), pk) - self.assertCurve(s.tail, pk, Vector(3.996548, +7.997409, 1.590060), p2) - diff --git a/src/Mod/Path/TestPathApp.py b/src/Mod/Path/TestPathApp.py index 872916353..098aa3220 100644 --- a/src/Mod/Path/TestPathApp.py +++ b/src/Mod/Path/TestPathApp.py @@ -29,8 +29,4 @@ from PathTests.TestPathPost import PathPostTestCases from PathTests.TestPathGeom import TestPathGeom from PathTests.TestPathDepthParams import depthTestCases -from PathTests.TestPathDressupHoldingTags import TestTag01BasicTag -from PathTests.TestPathDressupHoldingTags import TestTag02SquareTag -from PathTests.TestPathDressupHoldingTags import TestTag03TrapezoidTag -from PathTests.TestPathDressupHoldingTags import TestTag04TriangularTag - +from PathTests.TestPathDressupHoldingTags import TestHoldingTags