From 88e9d26ecb0740edc162c2330fb94978e03690ea Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Tue, 29 Nov 2016 02:40:29 -0800 Subject: [PATCH 01/27] Added HoldingTags dressup with tests, using PathGeom and PathTestUtils. --- src/Mod/Path/CMakeLists.txt | 2 + src/Mod/Path/Gui/Resources/Path.qrc | 1 + .../Gui/Resources/panels/HoldingTagsEdit.ui | 240 +++++ src/Mod/Path/InitGui.py | 5 +- .../PathScripts/PathDressupHoldingTags.py | 946 ++++++++++++++++++ src/Mod/Path/PathTests/PathTestUtils.py | 48 +- .../PathTests/TestPathDressupHoldingTags.py | 546 ++++++++++ src/Mod/Path/TestPathApp.py | 5 + 8 files changed, 1785 insertions(+), 8 deletions(-) create mode 100644 src/Mod/Path/Gui/Resources/panels/HoldingTagsEdit.ui create mode 100644 src/Mod/Path/PathScripts/PathDressupHoldingTags.py create mode 100644 src/Mod/Path/PathTests/TestPathDressupHoldingTags.py diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index 4a0d5edfc..701ba44b1 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -26,6 +26,7 @@ SET(PathScripts_SRCS PathScripts/PathDressup.py PathScripts/PathDressupDogbone.py PathScripts/PathDressupDragknife.py + PathScripts/PathDressupHoldingTags.py PathScripts/PathDrilling.py PathScripts/PathEngrave.py PathScripts/PathFacePocket.py @@ -73,6 +74,7 @@ SET(PathScripts_SRCS PathScripts/slic3r_pre.py PathTests/PathTestUtils.py PathTests/TestPathDepthParams.py + PathTests/TestPathDressupHoldingTags.py PathTests/TestPathGeom.py PathTests/TestPathPost.py PathTests/__init__.py diff --git a/src/Mod/Path/Gui/Resources/Path.qrc b/src/Mod/Path/Gui/Resources/Path.qrc index bb147e140..5be33f8ea 100644 --- a/src/Mod/Path/Gui/Resources/Path.qrc +++ b/src/Mod/Path/Gui/Resources/Path.qrc @@ -53,6 +53,7 @@ panels/DogboneEdit.ui panels/DrillingEdit.ui panels/EngraveEdit.ui + panels/HoldingTagsEdit.ui panels/JobEdit.ui panels/MillFaceEdit.ui panels/PocketEdit.ui diff --git a/src/Mod/Path/Gui/Resources/panels/HoldingTagsEdit.ui b/src/Mod/Path/Gui/Resources/panels/HoldingTagsEdit.ui new file mode 100644 index 000000000..465b0ad6e --- /dev/null +++ b/src/Mod/Path/Gui/Resources/panels/HoldingTagsEdit.ui @@ -0,0 +1,240 @@ + + + TaskPanel + + + + 0 + 0 + 352 + 387 + + + + Holding Tags + + + + + + QFrame::NoFrame + + + 0 + + + + + 0 + 0 + 334 + 311 + + + + Tags + + + + + + + 0 + 0 + + + + true + + + 80 + + + false + + + + X + + + + + Y + + + + + Width + + + + + Height + + + + + Angle + + + + + + + + + + + Delete + + + + + + + Disable + + + + + + + Add + + + + + + + + + + + + 0 + 0 + 334 + 311 + + + + Generate + + + + + + Width + + + + + + + <html><head/><body><p>Width of each tag.</p></body></html> + + + + + + + Height + + + + + + + <html><head/><body><p>The height of the holding tag measured from the bottom of the path. By default this is set to the (estimated) height of the path.</p></body></html> + + + + + + + true + + + Angle + + + + + + + true + + + <html><head/><body><p>Angle of tag walls.</p></body></html> + + + + + + + QDialogButtonBox::Apply|QDialogButtonBox::Ok + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Layout + + + + + + Count + + + + + + + <html><head/><body><p>Enter the number of tags you wish to have.</p><p><br/></p><p>Note that sometimes it's necessary to enter a larger than desired count number and disable the ones tags you don't want in order to get the holding tag layout you want.</p></body></html> + + + + + + + Spacing + + + + + + + + + + + + + Auto Apply + + + + + + + + + + + + diff --git a/src/Mod/Path/InitGui.py b/src/Mod/Path/InitGui.py index cc8405ef8..e046eb542 100644 --- a/src/Mod/Path/InitGui.py +++ b/src/Mod/Path/InitGui.py @@ -73,6 +73,7 @@ class PathWorkbench (Workbench): from PathScripts import PathProfileEdges from PathScripts import PathDressupDogbone from PathScripts import PathMillFace + from PathScripts import PathDressupHoldingTags import PathCommands # build commands list @@ -82,7 +83,7 @@ class PathWorkbench (Workbench): twodopcmdlist = ["Path_Contour", "Path_Profile", "Path_Profile_Edges", "Path_Pocket", "Path_Drilling", "Path_Engrave", "Path_MillFace"] threedopcmdlist = ["Path_Surfacing"] modcmdlist = ["Path_Copy", "Path_CompoundExtended", "Path_Array", "Path_SimpleCopy" ] - dressupcmdlist = ["PathDressup_Dogbone", "PathDressup_DragKnife"] + dressupcmdlist = ["PathDressup_Dogbone", "PathDressup_DragKnife", "PathDressup_HoldingTags"] extracmdlist = ["Path_SelectLoop"] #modcmdmore = ["Path_Hop",] #remotecmdlist = ["Path_Remote"] @@ -134,7 +135,7 @@ class PathWorkbench (Workbench): if len(FreeCADGui.Selection.getSelection()) == 1: if FreeCADGui.Selection.getSelection()[0].isDerivedFrom("Path::Feature"): self.appendContextMenu("", ["Path_Inspect"]) - if FreeCADGui.Selection.getSelection()[0].Name in ["Profile", "Contour"]: + if "Profile" or "Contour" in FreeCADGui.Selection.getSelection()[0].Name: self.appendContextMenu("", ["Add_Tag"]) self.appendContextMenu("", ["Set_StartPoint"]) self.appendContextMenu("", ["Set_EndPoint"]) diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py new file mode 100644 index 000000000..4c13da8ea --- /dev/null +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -0,0 +1,946 @@ +# -*- coding: utf-8 -*- + +# *************************************************************************** +# * * +# * Copyright (c) 2016 sliptonic * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** +import FreeCAD +import FreeCADGui +import DraftGeomUtils +import Path +import Part +import math + +from PathScripts import PathUtils +from PathScripts.PathGeom import * +from PySide import QtCore, QtGui + +"""Holding Tags Dressup object and FreeCAD command""" + +# Qt tanslation handling +try: + _encoding = QtGui.QApplication.UnicodeUTF8 + + def translate(context, text, disambig=None): + return QtGui.QApplication.translate(context, text, disambig, _encoding) + +except AttributeError: + + def translate(context, text, disambig=None): + return QtGui.QApplication.translate(context, text, disambig) + +debugDressup = True + +def debugMarker(vector, label, color = None, radius = 0.5): + if debugDressup: + obj = FreeCAD.ActiveDocument.addObject("Part::Sphere", label) + obj.Label = label + obj.Radius = radius + obj.Placement = FreeCAD.Placement(vector, FreeCAD.Rotation(FreeCAD.Vector(0,0,1), 0)) + if color: + obj.ViewObject.ShapeColor = color + +movecommands = ['G0', 'G00', 'G1', 'G01', 'G2', 'G02', 'G3', 'G03'] +movestraight = ['G1', 'G01'] +movecw = ['G2', 'G02'] +moveccw = ['G3', 'G03'] +movearc = movecw + moveccw + +slack = 0.0000001 + +def pathCommandForEdge(edge): + pt = edge.Curve.EndPoint + params = {'X': pt.x, 'Y': pt.y, 'Z': pt.z} + if type(edge.Curve) == Part.Line: + return Part.Command('G1', params) + + p1 = edge.Curve.StartPoint + p2 = edge.valueAt((edge.FirstParameter + edge.LastParameter)/2) + p3 = pt + if Side.Left == Side.of(p2 - p1, p3 - p2): + cmd = 'G3' + else: + cmd = 'G2' + offset = pt1 - edge.Curve.Center + params.update({'I': offset.x, 'J': offset.y, 'K': offset.z}) + return Part.Command(cmd, params) + + +class Tag: + + @classmethod + def FromString(cls, string): + try: + t = eval(string) + return Tag(t[0], t[1], t[2], t[3], t[4], t[5]) + except: + return None + + def __init__(self, x, y, width, height, angle, enabled=True, z=None): + self.x = x + self.y = y + self.width = math.fabs(width) + self.height = math.fabs(height) + self.actualHeight = self.height + self.angle = math.fabs(angle) + self.enabled = enabled + if z is not None: + self.createSolidsAt(z) + + def toString(self): + return str((self.x, self.y, self.width, self.height, self.angle, self.enabled)) + + def originAt(self, z): + return FreeCAD.Vector(self.x, self.y, z) + + def bottom(self): + return self.z + + def top(self): + return self.z + self.actualHeight + + def centerLine(self): + return Part.Line(self.originAt(self.bottom()), self.originAt(self.top())) + + def createSolidsAt(self, z): + self.z = z + r1 = self.width / 2 + height = self.height + if self.angle == 90 and height > 0: + self.solid = Part.makeCylinder(r1, height) + self.core = self.solid.copy() + elif self.angle > 0.0 and height > 0.0: + tangens = math.tan(math.radians(self.angle)) + dr = height / tangens + if dr < r1: + r2 = r1 - dr + self.core = Part.makeCylinder(r2, height) + else: + r2 = 0 + height = r1 * tangens + self.core = None + self.actualHeight = height + self.solid = Part.makeCone(r1, r2, height) + else: + # degenerated case - no tag + self.solid = Part.makeSphere(r1 / 10000) + self.core = None + self.solid.translate(self.originAt(z)) + 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. + P0 = 2 + P1 = 3 + P2 = 4 + P3 = 5 + + def __init__(self, tag): + self.tag = tag + self.state = self.P3 + self.edges = [] + self.tail = None + + def isComplete(self): + return self.state == self.P3 + + def moveEdgeToPlateau(self, edge): + if type(edge.Curve) is Part.Line: + pt1 = edge.Curve.StartPoint + pt2 = edge.Curve.EndPoint + pt1.z = self.tag.top() + pt2.z = self.tag.top() + #print("\nplateau= %s - %s" %(pt1, pt2)) + return Part.Edge(Part.Line(pt1, pt2)) + + def intersectP0Core(self, edge): + #print("----- P0 (%s - %s)" % (edge.Curve.StartPoint, edge.Curve.EndPoint)) + + i = self.tag.nextIntersectionClosestTo(edge, self.tag.core, edge.Curve.StartPoint) + if i: + if PathGeom.pointsCoincide(i, edge.Curve.StartPoint): + # if P0 and P1 are the same, we need to insert a segment for the rise + #print("------- insert vertical rise (%s)" % i) + self.edges.append(Part.Edge(Part.Line(i, FreeCAD.Vector(i.x, i.y, self.tag.top())))) + self.p1 = i + self.state = self.P1 + return edge + if PathGeom.pointsCoincide(i, edge.Curve.EndPoint): + #print("------- consumed (%s)" % i) + e = edge + tail = None + else: + #print("------- split at (%s)" % i) + e, tail = self.tag.splitEdgeAt(edge, i) + self.p1 = e.Curve.EndPoint + self.edges.append(self.tag.mapEdgeToSolid(e)) + self.state = self.P1 + return tail + # no intersection, the entire edge fits between P0 and P1 + #print("------- no intersection") + self.edges.append(self.tag.mapEdgeToSolid(edge)) + return None + + def intersectP0(self, edge): + 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()) + i = DraftGeomUtils.findIntersection(line, edge) + if i: + if PathGeom.pointsCoincide(i[0], edge.Curve.EndPoint): + e = edge + tail = None + else: + e, tail = self.tag.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: + e = edge + tail = None + self.edges.append(self.tag.mapEdgeToSolid(e)) + return tail + + + + def intersectP1(self, edge): + #print("----- P1 (%s - %s)" % (edge.Curve.StartPoint, edge.Curve.EndPoint)) + i = self.tag.nextIntersectionClosestTo(edge, self.tag.core, edge.Curve.EndPoint) + if i: + if PathGeom.pointsCoincide(i, edge.Curve.StartPoint): + self.edges.append(self.tag.mapEdgeToSolid(edge)) + return self + if PathGeom.pointsCoincide(i, edge.Curve.EndPoint): + e = edge + tail = None + else: + e, tail = self.tag.splitEdgeAt(edge, i) + self.p2 = e.Curve.EndPoint + self.state = self.P2 + else: + e = edge + tail = None + self.edges.append(self.moveEdgeToPlateau(e)) + return tail + + def intersectP2(self, edge): + #print("----- P2 (%s - %s)" % (edge.Curve.StartPoint, edge.Curve.EndPoint)) + i = self.tag.nextIntersectionClosestTo(edge, self.tag.solid, edge.Curve.EndPoint) + if i: + if PathGeom.pointsCoincide(i, edge.Curve.StartPoint): + #print("------- insert exit plunge (%s)" % i) + self.edges.append(Part.Edge(Part.Line(FreeCAD.Vector(i.x, i.y, self.tag.top()), i))) + e = None + tail = edge + elif PathGeom.pointsCoincide(i, edge.Curve.EndPoint): + #print("------- entire segment added (%s)" % i) + e = edge + tail = None + else: + e, tail = self.tag.splitEdgeAt(edge, i) + #if tail: + # print("----- P3 (%s - %s)" % (tail.Curve.StartPoint, tail.Curve.EndPoint)) + #else: + # print("----- P3 (---)") + self.state = self.P3 + self.tail = tail + else: + e = edge + tail = None + if e: + self.edges.append(self.tag.mapEdgeToSolid(e)) + return tail + + def intersect(self, edge): + #print("") + #print(" >>> (%s - %s)" % (edge.Curve.StartPoint, edge.Curve.EndPoint)) + 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 splitEdgeAt(self, edge, pt): + p = edge.Curve.parameter(pt) + wire = edge.split(p) + return wire.Edges + + def mapEdgeToSolid(self, edge): + #print("mapEdgeToSolid: (%s %s)" % (edge.Curve.StartPoint, edge.Curve.EndPoint)) + p1a = edge.Curve.StartPoint + p1b = FreeCAD.Vector(p1a.x, p1a.y, p1a.z + self.height) + e1 = Part.Edge(Part.Line(p1a, p1b)) + p1 = self.nextIntersectionClosestTo(e1, self.solid, p1b) # top most intersection + #print(" p1: (%s %s) -> %s" % (p1a, p1b, p1)) + + p2a = edge.Curve.EndPoint + p2b = FreeCAD.Vector(p2a.x, p2a.y, p2a.z + self.height) + e2 = Part.Edge(Part.Line(p2a, p2b)) + p2 = self.nextIntersectionClosestTo(e2, self.solid, p2b) # top most intersection + #print(" p2: (%s %s) -> %s" % (p2a, p2b, p2)) + + if type(edge.Curve) == Part.Line: + return Part.Edge(Part.Line(p1, p2)) + + def filterIntersections(self, pts, face): + if type(face.Surface) == Part.Cone or type(face.Surface) == Part.Cylinder: + return filter(lambda pt: pt.z >= self.bottom() and pt.z <= self.top(), pts) + if type(face.Surface) == Part.Plane: + c = face.Edges[0].Curve + if (type(c) == Part.Circle): + return filter(lambda pt: (pt - c.Center).Length <= c.Radius, pts) + print("==== we got a %s" % face.Surface) + + + def nextIntersectionClosestTo(self, edge, solid, refPt): + pts = [] + for index, face in enumerate(solid.Faces): + i = edge.Curve.intersect(face.Surface)[0] + ps = self.filterIntersections([FreeCAD.Vector(p.X, p.Y, p.Z) for p in i], face) + pts.extend(ps) + if pts: + closest = sorted(pts, key=lambda pt: (pt - refPt).Length)[0] + #print("--pts: %s -> %s" % (pts, closest)) + return closest + return None + + def intersect(self, edge): + inters = self.Intersection(self) + 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: + inters.state = self.Intersection.P0 + inters.p0 = i + if PathGeom.pointsCoincide(i, edge.valueAt(edge.LastParameter)): + inters.edges.append(edge) + return inters + if PathGeom.pointsCoincide(i, edge.valueAt(edge.FirstParameter)): + tail = edge + else: + e,tail = self.splitEdgeAt(edge, i) + inters.edges.append(e) + return inters.intersect(tail) + # if we get here there is no intersection with the tag + inters.state = self.Intersection.P3 + inters.tail = edge + return inters + +class PathData: + def __init__(self, obj): + self.obj = obj + self.wire = PathGeom.wireForPath(obj.Base.Path) + self.edges = wire.Edges + self.base = self.findBottomWire(self.edges) + # determine overall length + self.length = self.base.Length + + def findBottomWire(self, edges): + (minZ, maxZ) = self.findZLimits(edges) + self.minZ = minZ + self.maxZ = maxZ + 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)) + # 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.Curve.StartPoint.z == self.minZ and e.Curve.EndPoint.z != self.maxZ] + exit = sorted(edges, key=lambda e: -e.Curve.EndPoint.z)[0] + pt = exit.Curve.StartPoint + # then find the first base edge, and sort them until done + ordered = [] + while base: + edge = [e for e in base if e.Curve.StartPoint == pt][0] + ordered.append(edge) + base.remove(edge) + pt = edge.Curve.EndPoint + 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 + maxZ = minZ + for e in edges: + for v in e.Vertexes: + if v.Point.z < minZ: + minZ = v.Point.z + if v.Point.z > maxZ: + maxZ = v.Point.z + return (minZ, maxZ) + + def shortestAndLongestPathEdge(self): + edges = sorted(self.base.Edges, key=lambda e: e.Length) + return (edges[0], edges[-1]) + + def generateTags(self, obj, count=None, width=None, height=None, angle=90, spacing=None): + #print("generateTags(%s, %s, %s, %s, %s)" % (count, width, height, angle, spacing)) + #for e in self.base.Edges: + # debugMarker(e.Vertexes[0].Point, 'base', (0.0, 1.0, 1.0), 0.2) + + if spacing: + tagDistance = spacing + else: + if count: + tagDistance = self.base.Length / count + else: + tagDistance = self.base.Length / 4 + if width: + W = width + else: + W = self.tagWidth() + if height: + H = height + else: + H = self.tagHeight() + + + # start assigning tags on the longest segment + (shortestEdge, longestEdge) = self.shortestAndLongestPathEdge() + startIndex = 0 + for i in range(0, len(self.base.Edges)): + edge = self.base.Edges[i] + if edge.Length == longestEdge.Length: + startIndex = i + break + + startEdge = self.base.Edges[startIndex] + startCount = int(startEdge.Length / tagDistance) + if (longestEdge.Length - shortestEdge.Length) > shortestEdge.Length: + startCount = int(startEdge.Length / tagDistance) + 1 + + lastTagLength = (startEdge.Length + (startCount - 1) * tagDistance) / 2 + currentLength = startEdge.Length + + minLength = min(2. * W, longestEdge.Length) + + #print("length=%.2f shortestEdge=%.2f(%.2f) longestEdge=%.2f(%.2f)" % (self.base.Length, shortestEdge.Length, shortestEdge.Length/self.base.Length, longestEdge.Length, longestEdge.Length / self.base.Length)) + #print(" start: index=%-2d count=%d (length=%.2f, distance=%.2f)" % (startIndex, startCount, startEdge.Length, tagDistance)) + #print(" -> lastTagLength=%.2f)" % lastTagLength) + #print(" -> currentLength=%.2f)" % currentLength) + + edgeDict = { startIndex: startCount } + + for i in range(startIndex + 1, len(self.base.Edges)): + edge = self.base.Edges[i] + (currentLength, lastTagLength) = self.processEdge(i, edge, currentLength, lastTagLength, tagDistance, minLength, edgeDict) + for i in range(0, startIndex): + edge = self.base.Edges[i] + (currentLength, lastTagLength) = self.processEdge(i, edge, currentLength, lastTagLength, tagDistance, minLength, edgeDict) + + tags = [] + + for (i, count) in edgeDict.iteritems(): + edge = self.base.Edges[i] + #print(" %d: %d" % (i, count)) + #debugMarker(edge.Vertexes[0].Point, 'base', (1.0, 0.0, 0.0), 0.2) + #debugMarker(edge.Vertexes[1].Point, 'base', (0.0, 1.0, 0.0), 0.2) + distance = (edge.LastParameter - edge.FirstParameter) / count + for j in range(0, count): + tag = edge.Curve.value((j+0.5) * distance) + tags.append(Tag(tag.x, tag.y, W, H, angle, True)) + + return tags + + def processEdge(self, index, edge, currentLength, lastTagLength, tagDistance, minLength, edgeDict): + tagCount = 0 + currentLength += edge.Length + if edge.Length > minLength: + while lastTagLength + tagDistance < currentLength: + tagCount += 1 + lastTagLength += tagDistance + if tagCount > 0: + #print(" index=%d -> count=%d" % (index, tagCount)) + edgeDict[index] = tagCount + #else: + #print(" skipping=%-2d (%.2f)" % (index, edge.Length)) + + return (currentLength, lastTagLength) + + def tagHeight(self): + return self.maxZ - self.minZ + + def tagWidth(self): + return self.shortestAndLongestPathEdge()[1].Length / 10 + + def tagAngle(self): + return 90 + + def pathLength(self): + return self.base.Length + + def sortedTags(self, tags): + ordered = [] + for edge in self.base.Edges: + ts = [t for t in tags if DraftGeomUtils.isPtOnEdge(t.originAt(self.minZ), edge)] + for t in sorted(ts, key=lambda t: (t.originAt(self.minZ) - edge.Curve.StartPoint).Length): + tags.remove(t) + ordered.append(t) + if tags: + raise ValueError("There's something really wrong here") + return ordered + + +class ObjectDressup: + + def __init__(self, obj): + self.obj = obj + obj.addProperty("App::PropertyLink", "Base","Base", QtCore.QT_TRANSLATE_NOOP("PathDressup_HoldingTags", "The base path to modify")) + obj.addProperty("App::PropertyStringList", "Tags", "Tag", QtCore.QT_TRANSLATE_NOOP("PathDressup_holdingTags", "Inserted tags")) + obj.setEditorMode("Tags", 2) + obj.Proxy = self + + def __getstate__(self): + return None + + def __setstate__(self, state): + return None + + def generateTags(self, obj, count=None, width=None, height=None, angle=90, spacing=None): + return self.pathData.generateTags(obj, count, width, height, angle, spacing) + + + def tagIntersection(self, face, edge): + p1 = edge.Curve.StartPoint + 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, tagSolids): + commands = [] + i = 0 + while i != len(edges): + edge = edges[i] + while edge: + for solid in tagSolids: + for face in solid.Faces: + pt = self.tagIntersection(face, edge) + if pt: + if pt == edge.Curve.StartPoint: + pt + elif pt != edge.Curve.EndPoint: + parameter = edge.Curve.parameter(pt) + wire = edge.split(parameter) + commands.append(pathCommandForEdge(wire.Edges[0])) + edge = wire.Edges[1] + break; + else: + commands.append(pathCommandForEdge(edge)) + edge = None + i += 1 + break + if not edge: + break + if edge: + commands.append(pathCommandForEdge(edge)) + edge = None + return self.obj.Path + + + def execute(self, obj): + if not obj.Base: + return + if not obj.Base.isDerivedFrom("Path::Feature"): + return + if not obj.Base.Path: + return + if not obj.Base.Path.Commands: + return + + pathData = self.setup(obj) + if not pathData: + print("execute - no pathData") + return + + if hasattr(obj, 'Tags') and obj.Tags: + if self.fingerprint == obj.Tags: + print("execute - cache valid") + return + print("execute - tags from property") + tags = [Tag.FromString(tag) for tag in obj.Tags] + else: + print("execute - default tags") + tags = self.generateTags(obj, 4.) + + if not tags: + print("execute - no tags") + self.tags = [] + obj.Path = obj.Base.Path + return + + tagID = 0 + for tag in tags: + tagID += 1 + if tag.enabled: + #print("x=%s, y=%s, z=%s" % (tag.x, tag.y, pathData.minZ)) + debugMarker(FreeCAD.Vector(tag.x, tag.y, pathData.minZ), "tag-%02d" % tagID , (1.0, 0.0, 1.0), 0.5) + + tags = pathData.sortedTags(tags) + for tag in tags: + tag.createSolidsAt(pathData.minZ) + + self.fingerprint = [tag.toString() for tag in tags] + self.tags = tags + + #obj.Path = self.createPath(pathData.edges, tags) + obj.Path = self.Base.Path + + def setTags(self, obj, tags): + obj.Tags = [tag.toString() for tag in tags] + self.execute(obj) + + def getTags(self, obj): + if hasattr(self, 'tags'): + return self.tags + return self.setup(obj).generateTags(obj, 4) + + def setup(self, obj): + if not hasattr(self, "pathData") or not self.pathData: + try: + pathData = PathData(obj) + except ValueError: + FreeCAD.Console.PrintError(translate("PathDressup_HoldingTags", "Cannot insert holding tags for this path - please select a Profile path\n")) + return None + + ## setup the object's properties, in case they're not set yet + #obj.Count = self.tagCount(obj) + #obj.Angle = self.tagAngle(obj) + #obj.Blacklist = self.tagBlacklist(obj) + + # if the heigt isn't set, use the height of the path + #if not hasattr(obj, "Height") or not obj.Height: + # obj.Height = pathData.maxZ - pathData.minZ + # try and take an educated guess at the width + #if not hasattr(obj, "Width") or not obj.Width: + # width = sorted(pathData.base.Edges, key=lambda e: -e.Length)[0].Length / 10 + # while obj.Count > len([e for e in pathData.base.Edges if e.Length > 3*width]): + # width = widht / 2 + # obj.Width = width + + # and the tool radius, not sure yet if it's needed + #self.toolRadius = 5 + #toolLoad = PathUtils.getLastToolLoad(obj) + #if toolLoad is None or toolLoad.ToolNumber == 0: + # self.toolRadius = 5 + #else: + # tool = PathUtils.getTool(obj, toolLoad.ToolNumber) + # if not tool or tool.Diameter == 0: + # self.toolRadius = 5 + # else: + # self.toolRadius = tool.Diameter / 2 + self.pathData = pathData + return self.pathData + + def getHeight(self, obj): + return self.pathData.tagHeight() + + def getWidth(self, obj): + return self.pathData.tagWidth() + + def getAngle(self, obj): + return self.pathData.tagAngle() + + def getPathLength(self, obj): + return self.pathData.pathLength() + +class TaskPanel: + DataTag = QtCore.Qt.ItemDataRole.UserRole + DataValue = QtCore.Qt.ItemDataRole.DisplayRole + + def __init__(self, obj): + self.obj = obj + self.form = FreeCADGui.PySideUic.loadUi(":/panels/HoldingTagsEdit.ui") + FreeCAD.ActiveDocument.openTransaction(translate("PathDressup_HoldingTags", "Edit HoldingTags Dress-up")) + + def reject(self): + FreeCAD.ActiveDocument.abortTransaction() + FreeCADGui.Control.closeDialog() + FreeCAD.ActiveDocument.recompute() + FreeCADGui.Selection.removeObserver(self.s) + + def accept(self): + FreeCAD.ActiveDocument.commitTransaction() + FreeCADGui.ActiveDocument.resetEdit() + FreeCADGui.Control.closeDialog() + FreeCAD.ActiveDocument.recompute() + FreeCADGui.Selection.removeObserver(self.s) + FreeCAD.ActiveDocument.recompute() + + def open(self): + self.s = SelObserver() + # install the function mode resident + FreeCADGui.Selection.addObserver(self.s) + + def tableWidgetItem(self, tag, val): + item = QtGui.QTableWidgetItem() + item.setTextAlignment(QtCore.Qt.AlignRight) + item.setData(self.DataTag, tag) + item.setData(self.DataValue, val) + return item + + def getFields(self): + tags = [] + for row in range(0, self.form.twTags.rowCount()): + x = self.form.twTags.item(row, 0).data(self.DataValue) + y = self.form.twTags.item(row, 1).data(self.DataValue) + w = self.form.twTags.item(row, 2).data(self.DataValue) + h = self.form.twTags.item(row, 3).data(self.DataValue) + a = self.form.twTags.item(row, 4).data(self.DataValue) + tags.append(Tag(x, y, w, h, a, True)) + print("getFields: %d" % (len(tags))) + self.obj.Proxy.setTags(self.obj, tags) + + def updateTags(self): + self.tags = self.obj.Proxy.getTags(self.obj) + self.form.twTags.blockSignals(True) + self.form.twTags.setSortingEnabled(False) + self.form.twTags.clearSpans() + print("updateTags: %d" % (len(self.tags))) + self.form.twTags.setRowCount(len(self.tags)) + for row, tag in enumerate(self.tags): + self.form.twTags.setItem(row, 0, self.tableWidgetItem(tag, tag.x)) + self.form.twTags.setItem(row, 1, self.tableWidgetItem(tag, tag.y)) + self.form.twTags.setItem(row, 2, self.tableWidgetItem(tag, tag.width)) + self.form.twTags.setItem(row, 3, self.tableWidgetItem(tag, tag.height)) + self.form.twTags.setItem(row, 4, self.tableWidgetItem(tag, tag.angle)) + self.form.twTags.setSortingEnabled(True) + self.form.twTags.blockSignals(False) + + def cleanupUI(self): + print("cleanupUI") + if debugDressup: + for obj in FreeCAD.ActiveDocument.Objects: + if obj.Name.startswith('tag'): + FreeCAD.ActiveDocument.removeObject(obj.Name) + + def updateUI(self): + print("updateUI") + self.cleanupUI() + self.getFields() + if debugDressup: + FreeCAD.ActiveDocument.recompute() + + + def whenApplyClicked(self): + print("whenApplyClicked") + self.cleanupUI() + + count = self.form.sbCount.value() + spacing = self.form.dsbSpacing.value() + width = self.form.dsbWidth.value() + height = self.form.dsbHeight.value() + angle = self.form.dsbAngle.value() + + tags = self.obj.Proxy.generateTags(self.obj, count, width, height, angle, spacing * 0.99) + + self.obj.Proxy.setTags(self.obj, tags) + self.updateTags() + if debugDressup: + # this causes a big of an echo and a double click on the spin buttons, don't know why though + FreeCAD.ActiveDocument.recompute() + + def autoApply(self): + print("autoApply") + if self.form.cbAutoApply.checkState() == QtCore.Qt.CheckState.Checked: + self.whenApplyClicked() + + def updateTagSpacing(self, count): + print("updateTagSpacing") + if count == 0: + spacing = 0 + else: + spacing = self.pathLength / count + self.form.dsbSpacing.blockSignals(True) + self.form.dsbSpacing.setValue(spacing) + self.form.dsbSpacing.blockSignals(False) + + def whenCountChanged(self): + print("whenCountChanged") + self.updateTagSpacing(self.form.sbCount.value()) + self.autoApply() + + def whenSpacingChanged(self): + print("whenSpacingChanged") + if self.form.dsbSpacing.value() == 0: + count = 0 + else: + count = int(self.pathLength / self.form.dsbSpacing.value()) + self.form.sbCount.blockSignals(True) + self.form.sbCount.setValue(count) + self.form.sbCount.blockSignals(False) + self.autoApply() + + def whenOkClicked(self): + print("whenOkClicked") + self.whenApplyClicked() + self.form.toolBox.setCurrentWidget(self.form.tbpTags) + + def setupSpinBox(self, widget, val, decimals = 2): + widget.setMinimum(0) + if decimals: + widget.setDecimals(decimals) + widget.setValue(val) + + def setFields(self): + self.pathLength = self.obj.Proxy.getPathLength(self.obj) + vHeader = self.form.twTags.verticalHeader() + vHeader.setResizeMode(QtGui.QHeaderView.Fixed) + vHeader.setDefaultSectionSize(20) + self.updateTags() + self.setupSpinBox(self.form.sbCount, self.form.twTags.rowCount(), None) + self.setupSpinBox(self.form.dsbSpacing, 0) + self.setupSpinBox(self.form.dsbHeight, self.obj.Proxy.getHeight(self.obj)) + self.setupSpinBox(self.form.dsbWidth, self.obj.Proxy.getWidth(self.obj)) + self.setupSpinBox(self.form.dsbAngle, self.obj.Proxy.getAngle(self.obj)) + self.updateTagSpacing(self.form.twTags.rowCount()) + + def setupUi(self): + self.setFields() + self.form.sbCount.valueChanged.connect(self.whenCountChanged) + self.form.dsbSpacing.valueChanged.connect(self.whenSpacingChanged) + self.form.dsbHeight.valueChanged.connect(self.autoApply) + self.form.dsbWidth.valueChanged.connect(self.autoApply) + self.form.dsbAngle.valueChanged.connect(self.autoApply) + #self.form.pbAdd.clicked.connect(self.) + self.form.buttonBox.button(QtGui.QDialogButtonBox.Apply).clicked.connect(self.whenApplyClicked) + self.form.buttonBox.button(QtGui.QDialogButtonBox.Ok).clicked.connect(self.whenOkClicked) + self.form.twTags.itemChanged.connect(self.updateUI) + +class SelObserver: + def __init__(self): + import PathScripts.PathSelection as PST + PST.eselect() + + def __del__(self): + import PathScripts.PathSelection as PST + PST.clear() + + def addSelection(self, doc, obj, sub, pnt): + FreeCADGui.doCommand('Gui.Selection.addSelection(FreeCAD.ActiveDocument.' + obj + ')') + FreeCADGui.updateGui() + +class ViewProviderDressup: + + def __init__(self, vobj): + vobj.Proxy = self + + def attach(self, vobj): + self.Object = vobj.Object + return + + def claimChildren(self): + for i in self.Object.Base.InList: + if hasattr(i, "Group"): + group = i.Group + for g in group: + if g.Name == self.Object.Base.Name: + group.remove(g) + i.Group = group + print i.Group + #FreeCADGui.ActiveDocument.getObject(obj.Base.Name).Visibility = False + return [self.Object.Base] + + def setEdit(self, vobj, mode=0): + FreeCADGui.Control.closeDialog() + panel = TaskPanel(vobj.Object) + FreeCADGui.Control.showDialog(panel) + panel.setupUi() + return True + + def __getstate__(self): + return None + + def __setstate__(self, state): + return None + + def onDelete(self, arg1=None, arg2=None): + '''this makes sure that the base operation is added back to the project and visible''' + FreeCADGui.ActiveDocument.getObject(arg1.Object.Base.Name).Visibility = True + PathUtils.addToJob(arg1.Object.Base) + return True + +class CommandPathDressupHoldingTags: + + def GetResources(self): + return {'Pixmap': 'Path-Dressup', + 'MenuText': QtCore.QT_TRANSLATE_NOOP("PathDressup_HoldingTags", "HoldingTags Dress-up"), + 'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathDressup_HoldingTags", "Creates a HoldingTags Dress-up object from a selected path")} + + def IsActive(self): + if FreeCAD.ActiveDocument is not None: + for o in FreeCAD.ActiveDocument.Objects: + if o.Name[:3] == "Job": + return True + return False + + def Activated(self): + + # check that the selection contains exactly what we want + selection = FreeCADGui.Selection.getSelection() + if len(selection) != 1: + FreeCAD.Console.PrintError(translate("PathDressup_HoldingTags", "Please select one path object\n")) + return + baseObject = selection[0] + if not baseObject.isDerivedFrom("Path::Feature"): + FreeCAD.Console.PrintError(translate("PathDressup_HoldingTags", "The selected object is not a path\n")) + return + if baseObject.isDerivedFrom("Path::FeatureCompoundPython"): + FreeCAD.Console.PrintError(translate("PathDressup_HoldingTags", "Please select a Profile object")) + return + + # everything ok! + FreeCAD.ActiveDocument.openTransaction(translate("PathDressup_HoldingTags", "Create HoldingTags Dress-up")) + FreeCADGui.addModule("PathScripts.PathDressupHoldingTags") + FreeCADGui.addModule("PathScripts.PathUtils") + FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", "HoldingTagsDressup")') + FreeCADGui.doCommand('dbo = PathScripts.PathDressupHoldingTags.ObjectDressup(obj)') + FreeCADGui.doCommand('obj.Base = FreeCAD.ActiveDocument.' + selection[0].Name) + FreeCADGui.doCommand('PathScripts.PathDressupHoldingTags.ViewProviderDressup(obj.ViewObject)') + FreeCADGui.doCommand('PathScripts.PathUtils.addToJob(obj)') + FreeCADGui.doCommand('Gui.ActiveDocument.getObject(obj.Base.Name).Visibility = False') + FreeCADGui.doCommand('dbo.setup(obj)') + FreeCAD.ActiveDocument.commitTransaction() + FreeCAD.ActiveDocument.recompute() + +if FreeCAD.GuiUp: + # register the FreeCAD command + FreeCADGui.addCommand('PathDressup_HoldingTags', CommandPathDressupHoldingTags()) + +FreeCAD.Console.PrintLog("Loading PathDressupHoldingTags... done\n") diff --git a/src/Mod/Path/PathTests/PathTestUtils.py b/src/Mod/Path/PathTests/PathTestUtils.py index 30b56024e..029ca7914 100644 --- a/src/Mod/Path/PathTests/PathTestUtils.py +++ b/src/Mod/Path/PathTests/PathTestUtils.py @@ -27,6 +27,7 @@ import Part import math import unittest +from FreeCAD import Vector from PathScripts.PathGeom import Side class PathTestBase(unittest.TestCase): @@ -48,24 +49,35 @@ class PathTestBase(unittest.TestCase): self.assertCoincide(edge.Curve.StartPoint, pt1) self.assertCoincide(edge.Curve.EndPoint, pt2) + def assertLines(self, edgs, tail, points): + """Verify that the edges match the polygon resulting from points.""" + edges = list(edgs) + if tail: + edges.append(tail) + self.assertEqual(len(edges), len(points) - 1) + + for i in range(0, len(edges)): + self.assertLine(edges[i], points[i], points[i+1]) + def assertArc(self, edge, pt1, pt2, direction = 'CW'): """Verify that edge is an arc between pt1 and pt2 with the given direction.""" - # If an Arc is wrapped into edge, then it's curve is represented as a circle - # and not as an Arc (GeomTrimmedCurve) - #self.assertIs(type(edge.Curve), Part.Arc) self.assertIs(type(edge.Curve), Part.Circle) self.assertCoincide(edge.valueAt(edge.FirstParameter), pt1) self.assertCoincide(edge.valueAt(edge.LastParameter), pt2) ptm = edge.valueAt((edge.LastParameter + edge.FirstParameter)/2) side = Side.of(pt2 - pt1, ptm - pt1) - #print("(%.2f, %.2f) (%.2f, %.2f) (%.2f, %.2f)" % (pt1.x, pt1.y, ptm.x, ptm.y, pt2.x, pt2.y)) - #print(" (%.2f, %.2f) (%.2f, %.2f) -> %s" % ((pt2-pt1).x, (pt2-pt1).y, (ptm-pt1).x, (ptm-pt1).y, Side.toString(side))) - #print(" (%.2f, %.2f) (%.2f, %.2f) -> (%.2f, %.2f)" % (pf.x,pf.y, pl.x,pl.y, pm.x, pmy)) if 'CW' == direction: self.assertEqual(side, Side.Left) else: self.assertEqual(side, Side.Right) + def assertCircle(self, edge, pt, r): + """Verivy that edge is a circle at given location.""" + curve = edge.Curve + self.assertIs(type(curve), Part.Circle) + self.assertCoincide(curve.Center, Vector(pt.x, pt.y, pt.z)) + self.assertRoughly(curve.Radius, r) + def assertCurve(self, edge, p1, p2, p3): """Verify that the edge goes through the given 3 points, representing start, mid and end point respectively.""" @@ -73,3 +85,27 @@ class PathTestBase(unittest.TestCase): self.assertCoincide(edge.valueAt(edge.LastParameter), p3) self.assertCoincide(edge.valueAt((edge.FirstParameter + edge.LastParameter)/2), p2) + def assertCylinderAt(self, solid, pt, r, h): + """Verify that solid is a cylinder at the specified location.""" + self.assertEqual(len(solid.Edges), 3) + + lid = solid.Edges[0] + hull = solid.Edges[1] + base = solid.Edges[2] + + self.assertCircle(lid, Vector(pt.x, pt.y, pt.z+h), r) + self.assertLine(hull, Vector(pt.x+r, pt.y, pt.z), Vector(pt.x+r, pt.y, pt.z+h)) + self.assertCircle(base, Vector(pt.x, pt.y, pt.z), r) + + def assertConeAt(self, solid, pt, r1, r2, h): + """Verify that solid is a cone at the specified location.""" + self.assertEqual(len(solid.Edges), 3) + + lid = solid.Edges[0] + hull = solid.Edges[1] + base = solid.Edges[2] + + self.assertCircle(lid, Vector(pt.x, pt.y, pt.z+h), r2) + self.assertLine(hull, Vector(pt.x+r1, pt.y, pt.z), Vector(pt.x+r2, pt.y, pt.z+h)) + self.assertCircle(base, Vector(pt.x, pt.y, pt.z), r1) + diff --git a/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py b/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py new file mode 100644 index 000000000..f5db3e386 --- /dev/null +++ b/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py @@ -0,0 +1,546 @@ +# -*- coding: utf-8 -*- + +# *************************************************************************** +# * * +# * Copyright (c) 2016 sliptonic * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** + +import FreeCAD +import Part +import Path +import PathScripts +import math +import unittest + +from FreeCAD import Vector +from PathScripts.PathDressupHoldingTags import * +from PathTests.PathTestUtils import PathTestBase + +class TestTag01BasicTag(PathTestBase): # ============= + """Unit tests for the HoldingTags dressup.""" + + def test00(self): + """Check Tag origin, serialization and de-serialization.""" + tag = Tag(77, 13, 4, 5, 90, True) + self.assertCoincide(tag.originAt(3), Vector(77, 13, 3)) + s = tag.toString() + tagCopy = Tag.FromString(s) + self.assertEqual(tag.x, tagCopy.x) + self.assertEqual(tag.y, tagCopy.y) + self.assertEqual(tag.height, tagCopy.height) + self.assertEqual(tag.width, tagCopy.width) + self.assertEqual(tag.enabled, tagCopy.enabled) + + + def test01(self): + """Verify solid and core for a 90 degree tag are identical cylinders.""" + tag = Tag(100, 200, 4, 5, 90, True) + tag.createSolidsAt(17) + + self.assertIsNotNone(tag.solid) + self.assertCylinderAt(tag.solid, Vector(100, 200, 17), 2, 5) + + self.assertIsNotNone(tag.core) + self.assertCylinderAt(tag.core, Vector(100, 200, 17), 2, 5) + + def test02(self): + """Verify trapezoidal tag has a cone shape with a lid, and cylinder core.""" + tag = Tag(0, 0, 18, 5, 45, True) + tag.createSolidsAt(0) + + self.assertIsNotNone(tag.solid) + self.assertConeAt(tag.solid, Vector(0,0,0), 9, 4, 5) + + self.assertIsNotNone(tag.core) + self.assertCylinderAt(tag.core, Vector(0,0,0), 4, 5) + + def test03(self): + """Verify pointy cone shape of tag with pointy end if width, angle and height match up.""" + tag = Tag(0, 0, 10, 5, 45, True) + tag.createSolidsAt(0) + self.assertIsNotNone(tag.solid) + self.assertConeAt(tag.solid, Vector(0,0,0), 5, 0, 5) + + self.assertIsNone(tag.core) + + def test04(self): + """Verify height adjustment if tag isn't wide eough for angle.""" + tag = Tag(0, 0, 5, 17, 60, True) + tag.createSolidsAt(0) + self.assertIsNotNone(tag.solid) + self.assertConeAt(tag.solid, Vector(0,0,0), 2.5, 0, 2.5 * math.tan((60/180.0)*math.pi)) + + 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.Line(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.Line(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.Curve.StartPoint, edge.Curve.EndPoint) + 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.Line(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.Curve.StartPoint, 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.Line(edge.Curve.EndPoint, 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.Line(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.Curve.StartPoint + 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.Line(Vector(5, 0, 0), Vector(0, 0, 0))) + i = tag.intersect(edge) + edge = Part.Edge(Part.Line(edge.Curve.EndPoint, 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.Line(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.Curve.StartPoint + p1 = Vector( 4, 0, 0) + p2 = Vector( 4, 0, 3) + p3 = Vector(-4, 0, 3) + p4 = Vector(-4, 0, 0) + p5 = edge.Curve.EndPoint + 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.Line(Vector(5, 0, 0), Vector(+2, 0, 0))) + e1 = Part.Edge(Part.Line(e0.Curve.EndPoint, Vector(+1, 0, 0))) + e2 = Part.Edge(Part.Line(e1.Curve.EndPoint, Vector(+0.5, 0, 0))) + e3 = Part.Edge(Part.Line(e2.Curve.EndPoint, Vector(-0.5, 0, 0))) + e4 = Part.Edge(Part.Line(e3.Curve.EndPoint, Vector(-1, 0, 0))) + e5 = Part.Edge(Part.Line(e4.Curve.EndPoint, Vector(-2, 0, 0))) + e6 = Part.Edge(Part.Line(e5.Curve.EndPoint, 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.Curve.StartPoint, pt0, pt1, pt2, pt3, pt4, pt5, pt6, e6.Curve.StartPoint, e6.Curve.EndPoint]) + 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.Line(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.Line(Vector(5, 0, i), Vector(-5, 0, i))) + s = tag.intersect(edge) + self.assertTrue(s.isComplete()) + self.assertLine(s.tail, edge.Curve.StartPoint, edge.Curve.EndPoint) + +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.Line(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.Line(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.Curve.StartPoint, edge.Curve.EndPoint) + self.assertIsNone(i.tail) + + # now add another segment that doesn't reach the top of the cone + edge = Part.Edge(Part.Line(edge.Curve.EndPoint, Vector(3, 0, 0))) + i = i.intersect(edge) + # still a P0 and edge fully consumed + p1 = Vector(edge.Curve.StartPoint) + p1.z = 0 + p2 = Vector(edge.Curve.EndPoint) + 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.Line(edge.Curve.EndPoint, Vector(2, 0, 0))) + i = i.intersect(edge) + # still a P0 and edge fully consumed + p3 = Vector(edge.Curve.EndPoint) + 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.Line(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.Curve.StartPoint, 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.Line(edge.Curve.EndPoint, 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.Line(Vector(5, 0, 0), Vector(-1, 0, 0))) + + i = tag.intersect(edge) + self.assertEqual(i.state, Tag.Intersection.P2) + p0 = Vector(edge.Curve.StartPoint) + 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.Line(Vector(5, 0, 0), Vector(1, 0, 0))) + i = tag.intersect(edge) + edge = Part.Edge(Part.Line(edge.Curve.EndPoint, 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.Line(Vector(5, 0, 0), Vector(0.5, 0, 0))) + i = tag.intersect(edge) + edge = Part.Edge(Part.Line(edge.Curve.EndPoint, 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.Line(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.Line(edge.Curve.EndPoint, 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.Line(edge.Curve.EndPoint, 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.Line(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.Line(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.Curve.EndPoint]) + 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.Line(Vector(5, 0, 0), Vector(+4, 0, 0))) + e1 = Part.Edge(Part.Line(e0.Curve.EndPoint, Vector(+2, 0, 0))) + e2 = Part.Edge(Part.Line(e1.Curve.EndPoint, Vector(+0.5, 0, 0))) + e3 = Part.Edge(Part.Line(e2.Curve.EndPoint, Vector(-0.5, 0, 0))) + e4 = Part.Edge(Part.Line(e3.Curve.EndPoint, Vector(-1, 0, 0))) + e5 = Part.Edge(Part.Line(e4.Curve.EndPoint, Vector(-2, 0, 0))) + e6 = Part.Edge(Part.Line(e5.Curve.EndPoint, 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.Curve.StartPoint, p0, p1, p2, p3, p4, p5, p6, p7, e6.Curve.EndPoint]) + 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.Line(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.Line(Vector(5, 0, i), Vector(-5, 0, i))) + s = tag.intersect(edge) + self.assertTrue(s.isComplete()) + self.assertLine(s.tail, edge.Curve.StartPoint, edge.Curve.EndPoint) + + +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.Line(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.Curve.StartPoint, edge.Curve.EndPoint) + 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.Line(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.Curve.StartPoint, p1, p2]) + self.assertIsNone(i.tail) + + # verify we stay in P1 if we add another segment + edge = Part.Edge(Part.Line(edge.Curve.EndPoint, 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.Line(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.Line(edge.Curve.EndPoint, 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.Line(p0, edge.Curve.EndPoint)) + 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.Line(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.Line(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) + diff --git a/src/Mod/Path/TestPathApp.py b/src/Mod/Path/TestPathApp.py index 9886b77d5..872916353 100644 --- a/src/Mod/Path/TestPathApp.py +++ b/src/Mod/Path/TestPathApp.py @@ -29,3 +29,8 @@ 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 6462d775e35c282fd9f29f501559acfd55192b93 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Tue, 29 Nov 2016 03:11:03 -0800 Subject: [PATCH 02/27] Added support for arcs on square tags. --- .../PathScripts/PathDressupHoldingTags.py | 90 ++++++++++--------- .../PathTests/TestPathDressupHoldingTags.py | 22 +++++ 2 files changed, 69 insertions(+), 43 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index 4c13da8ea..e501c8173 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -173,38 +173,38 @@ class Tag: def moveEdgeToPlateau(self, edge): if type(edge.Curve) is Part.Line: - pt1 = edge.Curve.StartPoint - pt2 = edge.Curve.EndPoint - pt1.z = self.tag.top() - pt2.z = self.tag.top() - #print("\nplateau= %s - %s" %(pt1, pt2)) - return Part.Edge(Part.Line(pt1, pt2)) + z = edge.Curve.StartPoint.z + elif type(edge.Curve) is Part.Circle: + z = edge.Curve.Center.z + + edge.translate(Vector(0, 0, self.tag.top() - z)) + return edge def intersectP0Core(self, edge): - #print("----- P0 (%s - %s)" % (edge.Curve.StartPoint, edge.Curve.EndPoint)) + print("----- P0 (%s - %s)" % (edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter))) - i = self.tag.nextIntersectionClosestTo(edge, self.tag.core, edge.Curve.StartPoint) + i = self.tag.nextIntersectionClosestTo(edge, self.tag.core, edge.valueAt(edge.FirstParameter)) if i: - if PathGeom.pointsCoincide(i, edge.Curve.StartPoint): + if PathGeom.pointsCoincide(i, edge.valueAt(edge.FirstParameter)): # if P0 and P1 are the same, we need to insert a segment for the rise - #print("------- insert vertical rise (%s)" % i) + print("------- insert vertical rise (%s)" % i) self.edges.append(Part.Edge(Part.Line(i, FreeCAD.Vector(i.x, i.y, self.tag.top())))) self.p1 = i self.state = self.P1 return edge - if PathGeom.pointsCoincide(i, edge.Curve.EndPoint): - #print("------- consumed (%s)" % i) + if PathGeom.pointsCoincide(i, edge.valueAt(edge.LastParameter)): + print("------- consumed (%s)" % i) e = edge tail = None else: - #print("------- split at (%s)" % i) + print("------- split at (%s)" % i) e, tail = self.tag.splitEdgeAt(edge, i) - self.p1 = e.Curve.EndPoint + self.p1 = e.valueAt(edge.LastParameter) self.edges.append(self.tag.mapEdgeToSolid(e)) self.state = self.P1 return tail # no intersection, the entire edge fits between P0 and P1 - #print("------- no intersection") + print("------- no intersection") self.edges.append(self.tag.mapEdgeToSolid(edge)) return None @@ -215,7 +215,7 @@ class Tag: line = Part.Edge(self.tag.centerLine()) i = DraftGeomUtils.findIntersection(line, edge) if i: - if PathGeom.pointsCoincide(i[0], edge.Curve.EndPoint): + if PathGeom.pointsCoincide(i[0], edge.valueAt(edge.LastParameter)): e = edge tail = None else: @@ -232,18 +232,18 @@ class Tag: def intersectP1(self, edge): - #print("----- P1 (%s - %s)" % (edge.Curve.StartPoint, edge.Curve.EndPoint)) - i = self.tag.nextIntersectionClosestTo(edge, self.tag.core, edge.Curve.EndPoint) + print("----- P1 (%s - %s)" % (edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter))) + i = self.tag.nextIntersectionClosestTo(edge, self.tag.core, edge.valueAt(edge.LastParameter)) if i: - if PathGeom.pointsCoincide(i, edge.Curve.StartPoint): + if PathGeom.pointsCoincide(i, edge.valueAt(edge.FirstParameter)): self.edges.append(self.tag.mapEdgeToSolid(edge)) return self - if PathGeom.pointsCoincide(i, edge.Curve.EndPoint): + if PathGeom.pointsCoincide(i, edge.valueAt(edge.LastParameter)): e = edge tail = None else: e, tail = self.tag.splitEdgeAt(edge, i) - self.p2 = e.Curve.EndPoint + self.p2 = e.valueAt(edge.LastParameter) self.state = self.P2 else: e = edge @@ -252,22 +252,22 @@ class Tag: return tail def intersectP2(self, edge): - #print("----- P2 (%s - %s)" % (edge.Curve.StartPoint, edge.Curve.EndPoint)) - i = self.tag.nextIntersectionClosestTo(edge, self.tag.solid, edge.Curve.EndPoint) + print("----- P2 (%s - %s)" % (edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter))) + i = self.tag.nextIntersectionClosestTo(edge, self.tag.solid, edge.valueAt(edge.LastParameter)) if i: - if PathGeom.pointsCoincide(i, edge.Curve.StartPoint): - #print("------- insert exit plunge (%s)" % i) + if PathGeom.pointsCoincide(i, edge.valueAt(edge.FirstParameter)): + print("------- insert exit plunge (%s)" % i) self.edges.append(Part.Edge(Part.Line(FreeCAD.Vector(i.x, i.y, self.tag.top()), i))) e = None tail = edge - elif PathGeom.pointsCoincide(i, edge.Curve.EndPoint): - #print("------- entire segment added (%s)" % i) + elif PathGeom.pointsCoincide(i, edge.valueAt(edge.LastParameter)): + print("------- entire segment added (%s)" % i) e = edge tail = None else: e, tail = self.tag.splitEdgeAt(edge, i) #if tail: - # print("----- P3 (%s - %s)" % (tail.Curve.StartPoint, tail.Curve.EndPoint)) + # print("----- P3 (%s - %s)" % (tail.valueAt(edge.FirstParameter), tail.valueAt(edge.LastParameter))) #else: # print("----- P3 (---)") self.state = self.P3 @@ -280,8 +280,8 @@ class Tag: return tail def intersect(self, edge): - #print("") - #print(" >>> (%s - %s)" % (edge.Curve.StartPoint, edge.Curve.EndPoint)) + 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: @@ -297,18 +297,18 @@ class Tag: return wire.Edges def mapEdgeToSolid(self, edge): - #print("mapEdgeToSolid: (%s %s)" % (edge.Curve.StartPoint, edge.Curve.EndPoint)) - p1a = edge.Curve.StartPoint + print("mapEdgeToSolid: (%s %s)" % (edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter))) + p1a = edge.valueAt(edge.FirstParameter) p1b = FreeCAD.Vector(p1a.x, p1a.y, p1a.z + self.height) e1 = Part.Edge(Part.Line(p1a, p1b)) p1 = self.nextIntersectionClosestTo(e1, self.solid, p1b) # top most intersection - #print(" p1: (%s %s) -> %s" % (p1a, p1b, p1)) + print(" p1: (%s %s) -> %s" % (p1a, p1b, p1)) - p2a = edge.Curve.EndPoint + p2a = edge.valueAt(edge.LastParameter) p2b = FreeCAD.Vector(p2a.x, p2a.y, p2a.z + self.height) e2 = Part.Edge(Part.Line(p2a, p2b)) p2 = self.nextIntersectionClosestTo(e2, self.solid, p2b) # top most intersection - #print(" p2: (%s %s) -> %s" % (p2a, p2b, p2)) + print(" p2: (%s %s) -> %s" % (p2a, p2b, p2)) if type(edge.Curve) == Part.Line: return Part.Edge(Part.Line(p1, p2)) @@ -331,7 +331,7 @@ class Tag: pts.extend(ps) if pts: closest = sorted(pts, key=lambda pt: (pt - refPt).Length)[0] - #print("--pts: %s -> %s" % (pts, closest)) + print("--pts: %s -> %s" % (pts, closest)) return closest return None @@ -351,6 +351,10 @@ class Tag: e,tail = self.splitEdgeAt(edge, i) inters.edges.append(e) return inters.intersect(tail) + else: + print("No intersection found.") + else: + print("Fly by") # if we get here there is no intersection with the tag inters.state = self.Intersection.P3 inters.tail = edge @@ -379,16 +383,16 @@ class PathData: def sortedBase(self, base): # first find the exit point, where base wire is closed - edges = [e for e in self.edges if e.Curve.StartPoint.z == self.minZ and e.Curve.EndPoint.z != self.maxZ] - exit = sorted(edges, key=lambda e: -e.Curve.EndPoint.z)[0] - pt = exit.Curve.StartPoint + edges = [e for e in self.edges if e.valueAt(edge.FirstParameter).z == self.minZ and e.valueAt(edge.LastParameter).z != self.maxZ] + exit = sorted(edges, key=lambda e: -e.valueAt(edge.LastParameter).z)[0] + pt = exit.valueAt(edge.FirstParameter) # then find the first base edge, and sort them until done ordered = [] while base: - edge = [e for e in base if e.Curve.StartPoint == pt][0] + edge = [e for e in base if e.valueAt(edge.FirstParameter) == pt][0] ordered.append(edge) base.remove(edge) - pt = edge.Curve.EndPoint + pt = edge.valueAt(edge.LastParameter) return ordered @@ -508,7 +512,7 @@ class PathData: ordered = [] for edge in self.base.Edges: ts = [t for t in tags if DraftGeomUtils.isPtOnEdge(t.originAt(self.minZ), edge)] - for t in sorted(ts, key=lambda t: (t.originAt(self.minZ) - edge.Curve.StartPoint).Length): + for t in sorted(ts, key=lambda t: (t.originAt(self.minZ) - edge.valueAt(edge.FirstParameter)).Length): tags.remove(t) ordered.append(t) if tags: @@ -536,7 +540,7 @@ class ObjectDressup: def tagIntersection(self, face, edge): - p1 = edge.Curve.StartPoint + 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] diff --git a/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py b/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py index f5db3e386..e5e8917bd 100644 --- a/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py +++ b/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py @@ -248,6 +248,28 @@ class TestTag02SquareTag(PathTestBase): # ============= self.assertTrue(s.isComplete()) self.assertLine(s.tail, edge.Curve.StartPoint, edge.Curve.EndPoint) + def test10(self): + """Verify intersection of square tag with an arc.""" + print + 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) + + print("(%s - %s) - %s" % (edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter), edge.valueAt((edge.FirstParameter + edge.LastParameter)/2))) + 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) + + class TestTag03TrapezoidTag(PathTestBase): # ============= """Unit tests for trapezoid tags.""" From 4a810bc107d3775ce758c89d6578c11f1792bd7c Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Wed, 30 Nov 2016 09:24:47 -0800 Subject: [PATCH 03/27] Support for arcs and helix with tests. --- .../PathScripts/PathDressupHoldingTags.py | 96 +++++++++++++---- .../PathTests/TestPathDressupHoldingTags.py | 101 +++++++++++++++++- 2 files changed, 174 insertions(+), 23 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index e501c8173..3441eb042 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -175,36 +175,43 @@ class Tag: if type(edge.Curve) is Part.Line: z = edge.Curve.StartPoint.z elif type(edge.Curve) is Part.Circle: + # it's an arc 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)) + edge = Part.Edge(Part.Arc(p1, p2, p3)) edge.translate(Vector(0, 0, self.tag.top() - z)) return edge def intersectP0Core(self, edge): - print("----- P0 (%s - %s)" % (edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter))) + #print("----- P0 (%s - %s)" % (edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter))) 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 - print("------- insert vertical rise (%s)" % i) + #print("------- insert vertical rise (%s)" % i) self.edges.append(Part.Edge(Part.Line(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)): - print("------- consumed (%s)" % i) + #print("------- consumed (%s)" % i) e = edge tail = None else: - print("------- split at (%s)" % i) + #print("------- split at (%s)" % i) e, tail = self.tag.splitEdgeAt(edge, i) self.p1 = e.valueAt(edge.LastParameter) self.edges.append(self.tag.mapEdgeToSolid(e)) self.state = self.P1 return tail # no intersection, the entire edge fits between P0 and P1 - print("------- no intersection") + #print("------- no intersection") self.edges.append(self.tag.mapEdgeToSolid(edge)) return None @@ -232,36 +239,46 @@ class Tag: def intersectP1(self, edge): - print("----- P1 (%s - %s)" % (edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter))) + #print("----- P1 (%s - %s)" % (edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter))) i = self.tag.nextIntersectionClosestTo(edge, self.tag.core, edge.valueAt(edge.LastParameter)) if i: if PathGeom.pointsCoincide(i, edge.valueAt(edge.FirstParameter)): + #print("----- P1 edge too short") self.edges.append(self.tag.mapEdgeToSolid(edge)) return self if PathGeom.pointsCoincide(i, edge.valueAt(edge.LastParameter)): + #print("----- P1 edge at end") e = edge tail = None else: + #print("----- P1 split edge @ (%.2f, %.2f, %.2f)" % (i.x, i.y, i.z)) e, tail = self.tag.splitEdgeAt(edge, i) - self.p2 = e.valueAt(edge.LastParameter) + f = e.valueAt(e.FirstParameter) + l = e.valueAt(e.LastParameter) + #print("----- 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: + #print("----- P1 no intersect") e = edge tail = None + f = e.valueAt(e.FirstParameter) + l = e.valueAt(e.LastParameter) + #print("----- 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): - print("----- P2 (%s - %s)" % (edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter))) + #print("----- P2 (%s - %s)" % (edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter))) i = self.tag.nextIntersectionClosestTo(edge, self.tag.solid, edge.valueAt(edge.LastParameter)) if i: if PathGeom.pointsCoincide(i, edge.valueAt(edge.FirstParameter)): - print("------- insert exit plunge (%s)" % i) + #print("------- insert exit plunge (%s)" % i) self.edges.append(Part.Edge(Part.Line(FreeCAD.Vector(i.x, i.y, self.tag.top()), i))) e = None tail = edge elif PathGeom.pointsCoincide(i, edge.valueAt(edge.LastParameter)): - print("------- entire segment added (%s)" % i) + #print("------- entire segment added (%s)" % i) e = edge tail = None else: @@ -280,8 +297,8 @@ class Tag: return tail def intersect(self, edge): - print("") - print(" >>> (%s - %s)" % (edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter))) + #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: @@ -294,25 +311,62 @@ class Tag: def splitEdgeAt(self, edge, pt): p = edge.Curve.parameter(pt) wire = edge.split(p) + # split does not carry the Placement of the original curve foward ... + wire.transformShape(edge.Placement.toMatrix()) return wire.Edges def mapEdgeToSolid(self, edge): - print("mapEdgeToSolid: (%s %s)" % (edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter))) + #print("mapEdgeToSolid: (%s %s)" % (edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter))) p1a = edge.valueAt(edge.FirstParameter) p1b = FreeCAD.Vector(p1a.x, p1a.y, p1a.z + self.height) e1 = Part.Edge(Part.Line(p1a, p1b)) p1 = self.nextIntersectionClosestTo(e1, self.solid, p1b) # top most intersection - print(" p1: (%s %s) -> %s" % (p1a, p1b, p1)) + #print(" p1: (%s %s) -> %s" % (p1a, p1b, p1)) p2a = edge.valueAt(edge.LastParameter) p2b = FreeCAD.Vector(p2a.x, p2a.y, p2a.z + self.height) e2 = Part.Edge(Part.Line(p2a, p2b)) p2 = self.nextIntersectionClosestTo(e2, self.solid, p2b) # top most intersection - print(" p2: (%s %s) -> %s" % (p2a, p2b, p2)) + #print(" p2: (%s %s) -> %s" % (p2a, p2b, p2)) if type(edge.Curve) == Part.Line: return Part.Edge(Part.Line(p1, p2)) + 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] + return e + + # it's already a helix, just need to lift it to the plateau + m.A33 = pd.z / (p2a.z - p1a.z) + m.A34 = (1 - m.A33) + if pd.z < 0: + m.A34 *= p2a.z + else: + m.A34 *= p1a.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") + return e + + def filterIntersections(self, pts, face): if type(face.Surface) == Part.Cone or type(face.Surface) == Part.Cylinder: return filter(lambda pt: pt.z >= self.bottom() and pt.z <= self.top(), pts) @@ -331,7 +385,7 @@ class Tag: pts.extend(ps) if pts: closest = sorted(pts, key=lambda pt: (pt - refPt).Length)[0] - print("--pts: %s -> %s" % (pts, closest)) + #print("--pts: %s -> %s" % (pts, closest)) return closest return None @@ -351,10 +405,10 @@ class Tag: e,tail = self.splitEdgeAt(edge, i) inters.edges.append(e) return inters.intersect(tail) - else: - print("No intersection found.") - else: - print("Fly by") + #else: + # print("No intersection found.") + #else: + # print("Fly by") # if we get here there is no intersection with the tag inters.state = self.Intersection.P3 inters.tail = edge diff --git a/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py b/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py index e5e8917bd..4d4768498 100644 --- a/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py +++ b/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py @@ -250,7 +250,6 @@ class TestTag02SquareTag(PathTestBase): # ============= def test10(self): """Verify intersection of square tag with an arc.""" - print tag = Tag( 0, 0, 8, 3, 90, True, 0) p1 = Vector(10, -10, 0) p2 = Vector(10, +10, 0) @@ -259,7 +258,6 @@ class TestTag02SquareTag(PathTestBase): # ============= pi = Vector(0.8, -3.919184, 0) pj = Vector(0.8, +3.919184, 0) - print("(%s - %s) - %s" % (edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter), edge.valueAt((edge.FirstParameter + edge.LastParameter)/2))) s = tag.intersect(edge) self.assertTrue(s.isComplete()) self.assertEqual(len(s.edges), 4) @@ -269,6 +267,25 @@ class TestTag02SquareTag(PathTestBase): # ============= 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.""" @@ -481,6 +498,48 @@ class TestTag03TrapezoidTag(PathTestBase): # ============= self.assertTrue(s.isComplete()) self.assertLine(s.tail, edge.Curve.StartPoint, edge.Curve.EndPoint) + 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.""" @@ -566,3 +625,41 @@ class TestTag04TriangularTag(PathTestBase): # ======================== 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.000001, 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) + From 23196b4c69d93bf651d3823f6be8d981c0c7ff87 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sun, 4 Dec 2016 01:18:15 -0800 Subject: [PATCH 04/27] Fixed most of the state machine issues, still a problem with intersection of edges. Need to rebase. --- .../PathScripts/PathDressupHoldingTags.py | 381 ++++++++++++------ src/Mod/Path/PathScripts/PathGeom.py | 2 + .../PathTests/TestPathDressupHoldingTags.py | 10 +- 3 files changed, 274 insertions(+), 119 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index 3441eb042..56211686f 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -26,6 +26,7 @@ import FreeCADGui import DraftGeomUtils import Path import Part +import copy import math from PathScripts import PathUtils @@ -47,6 +48,19 @@ except AttributeError: return QtGui.QApplication.translate(context, text, disambig) debugDressup = True +debugComponents = ['P0', 'P1', 'P2', 'P3'] + +def debugPrint(comp, msg): + if debugDressup and comp in debugComponents: + print(msg) + +def debugEdge(edge, prefix, comp = None): + pf = edge.valueAt(edge.FirstParameter) + 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: + 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): if debugDressup: @@ -57,6 +71,17 @@ def debugMarker(vector, label, color = None, radius = 0.5): if color: obj.ViewObject.ShapeColor = color +def debugCylinder(vector, r, height, label, color = None): + if debugDressup: + obj = FreeCAD.ActiveDocument.addObject("Part::Cylinder", label) + obj.Label = label + obj.Radius = r + obj.Height = height + obj.Placement = FreeCAD.Placement(vector, FreeCAD.Rotation(FreeCAD.Vector(0,0,1), 0)) + obj.ViewObject.Transparency = 90 + if color: + obj.ViewObject.ShapeColor = color + movecommands = ['G0', 'G00', 'G1', 'G01', 'G2', 'G02', 'G3', 'G03'] movestraight = ['G1', 'G01'] movecw = ['G2', 'G02'] @@ -66,21 +91,30 @@ movearc = movecw + moveccw slack = 0.0000001 def pathCommandForEdge(edge): - pt = edge.Curve.EndPoint + pt = edge.valueAt(edge.LastParameter) params = {'X': pt.x, 'Y': pt.y, 'Z': pt.z} if type(edge.Curve) == Part.Line: - return Part.Command('G1', params) - - p1 = edge.Curve.StartPoint - p2 = edge.valueAt((edge.FirstParameter + edge.LastParameter)/2) - p3 = pt - if Side.Left == Side.of(p2 - p1, p3 - p2): - cmd = 'G3' + command = Path.Command('G1', params) else: - cmd = 'G2' - offset = pt1 - edge.Curve.Center - params.update({'I': offset.x, 'J': offset.y, 'K': offset.z}) - return Part.Command(cmd, params) + p1 = edge.valueAt(edge.FirstParameter) + p2 = edge.valueAt((edge.FirstParameter + edge.LastParameter)/2) + p3 = pt + if Side.Left == Side.of(p2 - p1, p3 - p2): + cmd = 'G3' + else: + cmd = 'G2' + pa = PathGeom.xy(p1) + pb = PathGeom.xy(p2) + pc = PathGeom.xy(p3) + pd = Part.Circle(PathGeom.xy(p1), PathGeom.xy(p2), PathGeom.xy(p3)).Center + print("**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (pa.x, pa.y, pa.z, pc.x, pc.y, pc.z)) + print("**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (pb.x, pb.y, pb.z, pd.x, pd.y, pd.z)) + offset = Part.Circle(PathGeom.xy(p1), PathGeom.xy(p2), PathGeom.xy(p3)).Center - p1 + print("**** (%.2f, %.2f, %.2f)" % (offset.x, offset.y, offset.z)) + params.update({'I': offset.x, 'J': offset.y, 'K': (p3.z - p1.z)/2}) + command = Path.Command(cmd, params) + print command + return command class Tag: @@ -117,7 +151,7 @@ class Tag: return self.z + self.actualHeight def centerLine(self): - return Part.Line(self.originAt(self.bottom()), self.originAt(self.top())) + return Part.Line(self.originAt(self.bottom() - 1), self.originAt(self.top() + 1)) def createSolidsAt(self, z): self.z = z @@ -157,6 +191,7 @@ class Tag: # # 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 @@ -164,18 +199,23 @@ class Tag: def __init__(self, tag): self.tag = tag - self.state = self.P3 + self.state = self.Pnone self.edges = [] self.tail = None def isComplete(self): - return self.state == self.P3 + 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: + e = copy.copy(edge) z = edge.Curve.StartPoint.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 @@ -183,45 +223,54 @@ class Tag: p1 = PathGeom.xy(edge.valueAt(edge.FirstParameter)) p2 = PathGeom.xy(edge.valueAt((edge.FirstParameter + edge.LastParameter)/2)) p3 = PathGeom.xy(edge.valueAt(edge.LastParameter)) - edge = Part.Edge(Part.Arc(p1, p2, p3)) - edge.translate(Vector(0, 0, self.tag.top() - z)) - return edge + 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): - #print("----- P0 (%s - %s)" % (edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter))) + 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 - #print("------- insert vertical rise (%s)" % i) + debugPrint('P0', "------- insert vertical rise (%s)" % i) self.edges.append(Part.Edge(Part.Line(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)): - #print("------- consumed (%s)" % i) + debugPrint('P0', "------- consumed (%s)" % i) e = edge tail = None else: - #print("------- split at (%s)" % i) + debugPrint('P0', "------- split at (%s)" % i) e, tail = self.tag.splitEdgeAt(edge, i) self.p1 = e.valueAt(edge.LastParameter) - self.edges.append(self.tag.mapEdgeToSolid(e)) + 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 - #print("------- no intersection") - self.edges.append(self.tag.mapEdgeToSolid(edge)) + 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()) - i = DraftGeomUtils.findIntersection(line, edge) + debugEdge(line, "------- center line", 'P0') + #i = DraftGeomUtils.findIntersection(line, edge, True) + 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 @@ -231,69 +280,82 @@ class Tag: self.p1 = i[0] self.p2 = i[0] else: + debugPrint('P0', '------- P0 no intersect') e = edge tail = None - self.edges.append(self.tag.mapEdgeToSolid(e)) + self.edges.extend(self.tag.mapEdgeToSolid(e, 'P0')) return tail def intersectP1(self, edge): - #print("----- P1 (%s - %s)" % (edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter))) + 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)): - #print("----- P1 edge too short") - self.edges.append(self.tag.mapEdgeToSolid(edge)) - return self + 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)): - #print("----- P1 edge at end") + debugPrint('P1', "----- P1 edge at end") e = edge tail = None else: - #print("----- P1 split edge @ (%.2f, %.2f, %.2f)" % (i.x, i.y, i.z)) + debugPrint('P1', "----- P1 split edge @ (%.2f, %.2f, %.2f)" % (i.x, i.y, i.z)) e, tail = self.tag.splitEdgeAt(edge, i) f = e.valueAt(e.FirstParameter) l = e.valueAt(e.LastParameter) - #print("----- P1 (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (f.x, f.y, f.z, l.x, l.y, l.z)) + 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: - #print("----- P1 no intersect") + debugPrint('P1', "----- P1 no intersect") e = edge tail = None f = e.valueAt(e.FirstParameter) l = e.valueAt(e.LastParameter) - #print("----- P1 (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (f.x, f.y, f.z, l.x, l.y, l.z)) + 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): - #print("----- P2 (%s - %s)" % (edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter))) + 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)): - #print("------- insert exit plunge (%s)" % i) + debugPrint('P2', "------- insert exit plunge (%s)" % i) self.edges.append(Part.Edge(Part.Line(FreeCAD.Vector(i.x, i.y, self.tag.top()), i))) e = None tail = edge elif PathGeom.pointsCoincide(i, edge.valueAt(edge.LastParameter)): - #print("------- entire segment added (%s)" % i) + debugPrint('P2', "------- entire segment added (%s)" % i) e = edge tail = None else: e, tail = self.tag.splitEdgeAt(edge, i) - #if tail: - # print("----- P3 (%s - %s)" % (tail.valueAt(edge.FirstParameter), tail.valueAt(edge.LastParameter))) - #else: - # print("----- P3 (---)") + 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: - self.edges.append(self.tag.mapEdgeToSolid(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): @@ -310,27 +372,56 @@ class Tag: def splitEdgeAt(self, edge, pt): p = edge.Curve.parameter(pt) + + pf = edge.valueAt(edge.FirstParameter) + pl = edge.valueAt(edge.LastParameter) + print("-------- splitAt((%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f): (%.2f, %.2f, %.2f)) -> param[%.2f -> %.2f]: %.2f" % (pf.x, pf.y, pf.z, pl.x, pl.y, pl.z, pt.x, pt.y, pt.z, edge.FirstParameter, edge.LastParameter, p)) + wire = edge.split(p) # split does not carry the Placement of the original curve foward ... wire.transformShape(edge.Placement.toMatrix()) return wire.Edges - def mapEdgeToSolid(self, edge): - #print("mapEdgeToSolid: (%s %s)" % (edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter))) + 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) p1b = FreeCAD.Vector(p1a.x, p1a.y, p1a.z + self.height) + p1a.z = self.bottom() e1 = Part.Edge(Part.Line(p1a, p1b)) p1 = self.nextIntersectionClosestTo(e1, self.solid, p1b) # top most intersection - #print(" p1: (%s %s) -> %s" % (p1a, p1b, p1)) + 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) p2b = FreeCAD.Vector(p2a.x, p2a.y, p2a.z + self.height) + p2a.z = self.bottom() e2 = Part.Edge(Part.Line(p2a, p2b)) p2 = self.nextIntersectionClosestTo(e2, self.solid, p2b) # top most intersection - #print(" p2: (%s %s) -> %s" % (p2a, p2b, p2)) + 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: - return Part.Edge(Part.Line(p1, p2)) + e = Part.Edge(Part.Line(p1, p2)) + debugEdge(e, "-------- >>") + return [e] m = FreeCAD.Matrix() m.unity() @@ -344,15 +435,16 @@ class Tag: else: m.A34 *= p1.y e = edge.transformGeometry(m).Edges[0] - return e + debugEdge(e, "-------- >>") + return [e] # it's already a helix, just need to lift it to the plateau - m.A33 = pd.z / (p2a.z - p1a.z) + 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 *= p2a.z + m.A34 *= edge.valueAt(edge.LastParameter).z else: - m.A34 *= p1a.z + m.A34 *= edge.valueAt(edge.FirstParameter).z #print pf = edge.valueAt(edge.FirstParameter) @@ -364,53 +456,89 @@ class Tag: 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") - return e + 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") return filter(lambda pt: pt.z >= self.bottom() and pt.z <= self.top(), pts) if type(face.Surface) == Part.Plane: + #print("it's a plane, checking R") c = face.Edges[0].Curve if (type(c) == Part.Circle): return filter(lambda pt: (pt - c.Center).Length <= c.Radius, pts) print("==== we got a %s" % face.Surface) + def isPointOnEdge(self, pt, edge): + param = edge.Curve.parameter(pt) + if edge.FirstParameter <= param <= edge.LastParameter: + return True + if edge.LastParameter <= param <= edge.FirstParameter: + return True + if PathGeom.isRoughly(edge.FirstParameter, param) or PathGeom.isRoughly(edge.LastParameter, param): + return True + print("-------- X %.2f <= %.2f <=%.2f (%.2f, %.2f, %.2f)" % (edge.FirstParameter, param, edge.LastParameter, pt.x, pt.y, pt.z)) + return False + def nextIntersectionClosestTo(self, edge, solid, refPt): + ef = edge.valueAt(edge.FirstParameter) + el = edge.valueAt(edge.LastParameter) + print("-------- intersect %s (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f) refp=(%.2f, %.2f, %.2f)" % (type(edge.Curve), ef.x, ef.y, ef.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] ps = self.filterIntersections([FreeCAD.Vector(p.X, p.Y, p.Z) for p in i], face) - pts.extend(ps) + 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))) + for p in ps: + included = '+' if p in filtered else '-' + print("-------- %s (%.2f, %.2f, %.2f)" % (included, p.x, p.y, p.z)) if pts: closest = sorted(pts, key=lambda pt: (pt - refPt).Length)[0] - #print("--pts: %s -> %s" % (pts, closest)) + 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") return None - def intersect(self, edge): + def intersect(self, edge, check = True): + print("--- intersect") inters = self.Intersection(self) - 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: - inters.state = self.Intersection.P0 - inters.p0 = i - if PathGeom.pointsCoincide(i, edge.valueAt(edge.LastParameter)): - inters.edges.append(edge) - return inters - if PathGeom.pointsCoincide(i, edge.valueAt(edge.FirstParameter)): - tail = edge + 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 = self.splitEdgeAt(edge, i) + inters.edges.append(e) + return inters.intersect(tail) else: - e,tail = self.splitEdgeAt(edge, i) - inters.edges.append(e) - return inters.intersect(tail) - #else: - # print("No intersection found.") - #else: - # print("Fly by") + print("---- No intersection found.") + else: + print("---- Fly by") + else: + print("---- skipped") # if we get here there is no intersection with the tag - inters.state = self.Intersection.P3 + inters.state = self.Intersection.Pnone inters.tail = edge return inters @@ -418,7 +546,7 @@ class PathData: def __init__(self, obj): self.obj = obj self.wire = PathGeom.wireForPath(obj.Base.Path) - self.edges = wire.Edges + self.edges = self.wire.Edges self.base = self.findBottomWire(self.edges) # determine overall length self.length = self.base.Length @@ -430,20 +558,30 @@ 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 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(edge.FirstParameter).z == self.minZ and e.valueAt(edge.LastParameter).z != self.maxZ] - exit = sorted(edges, key=lambda e: -e.valueAt(edge.LastParameter).z)[0] - pt = exit.valueAt(edge.FirstParameter) + 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: - edge = [e for e in base if e.valueAt(edge.FirstParameter) == pt][0] + 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) @@ -467,7 +605,7 @@ class PathData: return (edges[0], edges[-1]) def generateTags(self, obj, count=None, width=None, height=None, angle=90, spacing=None): - #print("generateTags(%s, %s, %s, %s, %s)" % (count, width, height, angle, spacing)) + print("generateTags(%s, %s, %s, %s, %s)" % (count, width, height, angle, spacing)) #for e in self.base.Edges: # debugMarker(e.Vertexes[0].Point, 'base', (0.0, 1.0, 1.0), 0.2) @@ -601,35 +739,49 @@ class ObjectDressup: return closest return None - def createPath(self, edges, tagSolids): + def createPath(self, edges, tags): commands = [] - i = 0 - while i != len(edges): - edge = edges[i] - while edge: - for solid in tagSolids: - for face in solid.Faces: - pt = self.tagIntersection(face, edge) - if pt: - if pt == edge.Curve.StartPoint: - pt - elif pt != edge.Curve.EndPoint: - parameter = edge.Curve.parameter(pt) - wire = edge.split(parameter) - commands.append(pathCommandForEdge(wire.Edges[0])) - edge = wire.Edges[1] - break; - else: - commands.append(pathCommandForEdge(edge)) - edge = None - i += 1 - break - if not edge: - break + lastEdge = 0 + lastTag = 0 + sameTag = None + t = 0 + inters = None + edge = None + + while edge or lastEdge < len(edges): + 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: + 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 + + if inters.isComplete(): + if inters.hasEdges(): + sameTag = (t + lastTag - 1) % len(tags) + lastTag = sameTag + t = 1 + for e in inters.edges: + commands.append(pathCommandForEdge(e)) + inters = None + + if t >= len(tags): + # gone through all tags, consume edge and move on if edge: commands.append(pathCommandForEdge(edge)) - edge = None - return self.obj.Path + edge = None + t = 0 + + return Path.Path(commands) def execute(self, obj): @@ -648,14 +800,14 @@ class ObjectDressup: return if hasattr(obj, 'Tags') and obj.Tags: - if self.fingerprint == obj.Tags: + if False and self.fingerprint == obj.Tags: print("execute - cache valid") return print("execute - tags from property") tags = [Tag.FromString(tag) for tag in obj.Tags] else: print("execute - default tags") - tags = self.generateTags(obj, 4.) + tags = self.generateTags(obj, 2.) if not tags: print("execute - no tags") @@ -663,12 +815,14 @@ class ObjectDressup: obj.Path = obj.Base.Path return + print("execute - %d tags" % (len(tags))) tagID = 0 for tag in tags: tagID += 1 if tag.enabled: #print("x=%s, y=%s, z=%s" % (tag.x, tag.y, pathData.minZ)) - debugMarker(FreeCAD.Vector(tag.x, tag.y, pathData.minZ), "tag-%02d" % tagID , (1.0, 0.0, 1.0), 0.5) + #debugMarker(FreeCAD.Vector(tag.x, tag.y, pathData.minZ), "tag-%02d" % tagID , (1.0, 0.0, 1.0), 0.5) + debugCylinder(tag.originAt(pathData.minZ), tag.width/2, tag.actualHeight, "tag-%02d" % tagID) tags = pathData.sortedTags(tags) for tag in tags: @@ -677,8 +831,7 @@ class ObjectDressup: self.fingerprint = [tag.toString() for tag in tags] self.tags = tags - #obj.Path = self.createPath(pathData.edges, tags) - obj.Path = self.Base.Path + obj.Path = self.createPath(pathData.edges, tags) def setTags(self, obj, tags): obj.Tags = [tag.toString() for tag in tags] @@ -690,7 +843,7 @@ class ObjectDressup: return self.setup(obj).generateTags(obj, 4) def setup(self, obj): - if not hasattr(self, "pathData") or not self.pathData: + if False or not hasattr(self, "pathData") or not self.pathData: try: pathData = PathData(obj) except ValueError: diff --git a/src/Mod/Path/PathScripts/PathGeom.py b/src/Mod/Path/PathScripts/PathGeom.py index 3effa097f..2fff4b1e0 100644 --- a/src/Mod/Path/PathScripts/PathGeom.py +++ b/src/Mod/Path/PathScripts/PathGeom.py @@ -135,6 +135,8 @@ class PathGeom: endPoint = cls.commandEndPoint(cmd, startPoint) if (cmd.Name in cls.CmdMoveStraight) or (cmd.Name in cls.CmdMoveFast): + if cls.pointsCoincide(startPoint, endPoint): + return None return Part.Edge(Part.LineSegment(startPoint, endPoint)) if cmd.Name in cls.CmdMoveArc: diff --git a/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py b/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py index 4d4768498..13e4d9103 100644 --- a/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py +++ b/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py @@ -544,7 +544,7 @@ class TestTag03TrapezoidTag(PathTestBase): # ============= class TestTag04TriangularTag(PathTestBase): # ======================== """Unit tests for tags that take on a triangular shape.""" - def test00(self): + def xtest00(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.Line(Vector(5, 0, 0), Vector(4, 0, 0))) @@ -555,7 +555,7 @@ class TestTag04TriangularTag(PathTestBase): # ======================== self.assertLine(i.edges[0], edge.Curve.StartPoint, edge.Curve.EndPoint) self.assertIsNone(i.tail) - def test01(self): + def xtest01(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.Line(Vector(5, 0, 0), Vector(3, 0, 0))) @@ -576,7 +576,7 @@ class TestTag04TriangularTag(PathTestBase): # ======================== self.assertLine(i.edges[2], p2, p3) self.assertIsNone(i.tail) - def test02(self): + def xtest02(self): """Verify proper down plunge on exit of triangular tag.""" tag = Tag( 0, 0, 8, 7, 45, True, 0) @@ -603,7 +603,7 @@ class TestTag04TriangularTag(PathTestBase): # ======================== self.assertEqual(i.state, Tag.Intersection.P2) self.assertLines(i.edges, i.tail, [p0, p1, p2, p3]) - def test03(self): + def xtest03(self): """Verify triangular tag shap on intersection.""" tag = Tag( 0, 0, 8, 7, 45, True, 0) @@ -625,7 +625,7 @@ class TestTag04TriangularTag(PathTestBase): # ======================== self.assertLines(i.edges, i.tail, [p0, p1, p2, p3, p4]) self.assertIsNotNone(i.tail) - def test10(self): + def xtest10(self): """Verify intersection with an arc.""" tag = Tag( 0, 0, 8, 7, 45, True, 0) p1 = Vector(10, -10, 0) From 198ab6db2ef2f368825c8b14d80cd7a99976ae8a Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sun, 4 Dec 2016 07:16:58 -0800 Subject: [PATCH 05/27] Rebase on new Line/LineSegment code. --- src/Mod/Draft/DraftGeomUtils.py | 2 +- .../PathScripts/PathDressupHoldingTags.py | 24 +-- src/Mod/Path/PathTests/PathTestUtils.py | 6 +- .../PathTests/TestPathDressupHoldingTags.py | 146 +++++++++--------- 4 files changed, 89 insertions(+), 89 deletions(-) diff --git a/src/Mod/Draft/DraftGeomUtils.py b/src/Mod/Draft/DraftGeomUtils.py index d6b04f4c1..45297b3df 100755 --- a/src/Mod/Draft/DraftGeomUtils.py +++ b/src/Mod/Draft/DraftGeomUtils.py @@ -454,7 +454,7 @@ def findIntersection(edge1,edge2,infinite1=False,infinite2=False,ex1=False,ex2=F return int else: - # print("DraftGeomUtils: Unsupported curve type: (" + str(edge1.Curve) + ", " + str(edge2.Curve) + ")") + print("DraftGeomUtils: Unsupported curve type: (" + str(edge1.Curve) + ", " + str(edge2.Curve) + ")") return [] def wiresIntersect(wire1,wire2): diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index 56211686f..60c76861a 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -93,7 +93,7 @@ slack = 0.0000001 def pathCommandForEdge(edge): pt = edge.valueAt(edge.LastParameter) params = {'X': pt.x, 'Y': pt.y, 'Z': pt.z} - if type(edge.Curve) == Part.Line: + if type(edge.Curve) == Part.Line or type(edge.Curve) == Part.LineSegment: command = Path.Command('G1', params) else: p1 = edge.valueAt(edge.FirstParameter) @@ -151,7 +151,7 @@ class Tag: return self.z + self.actualHeight def centerLine(self): - return Part.Line(self.originAt(self.bottom() - 1), self.originAt(self.top() + 1)) + return Part.LineSegment(self.originAt(self.bottom() - 1), self.originAt(self.top() + 1)) def createSolidsAt(self, z): self.z = z @@ -210,9 +210,9 @@ class Tag: return self.state != self.Pnone def moveEdgeToPlateau(self, edge): - if type(edge.Curve) is Part.Line: + if type(edge.Curve) is Part.Line or type(edge.Curve) is Part.LineSegment: e = copy.copy(edge) - z = edge.Curve.StartPoint.z + z = edge.valueAt(edge.FirstParameter).z elif type(edge.Curve) is Part.Circle: # it's an arc e = copy.copy(edge) @@ -236,7 +236,7 @@ class Tag: 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.Line(i, FreeCAD.Vector(i.x, i.y, self.tag.top())))) + 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 @@ -267,8 +267,8 @@ class Tag: # if we have no core the tip is the origin of the Tag line = Part.Edge(self.tag.centerLine()) debugEdge(line, "------- center line", 'P0') - #i = DraftGeomUtils.findIntersection(line, edge, True) - i = line.Curve.intersect(edge) + i = DraftGeomUtils.findIntersection(line, edge, True) + #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)): @@ -329,7 +329,7 @@ class Tag: if i: if PathGeom.pointsCoincide(i, edge.valueAt(edge.FirstParameter)): debugPrint('P2', "------- insert exit plunge (%s)" % i) - self.edges.append(Part.Edge(Part.Line(FreeCAD.Vector(i.x, i.y, self.tag.top()), 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)): @@ -390,7 +390,7 @@ class Tag: p1a = edge.valueAt(edge.FirstParameter) p1b = FreeCAD.Vector(p1a.x, p1a.y, p1a.z + self.height) p1a.z = self.bottom() - e1 = Part.Edge(Part.Line(p1a, p1b)) + 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: @@ -400,7 +400,7 @@ class Tag: p2a = edge.valueAt(edge.LastParameter) p2b = FreeCAD.Vector(p2a.x, p2a.y, p2a.z + self.height) p2a.z = self.bottom() - e2 = Part.Edge(Part.Line(p2a, p2b)) + 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) @@ -418,8 +418,8 @@ class Tag: if PathGeom.pointsCoincide(p1, p2): return [] - if type(edge.Curve) == Part.Line: - e = Part.Edge(Part.Line(p1, p2)) + if type(edge.Curve) == Part.Line or type(edge.Curve) == Part.LineSegment: + e = Part.Edge(Part.LineSegment(p1, p2)) debugEdge(e, "-------- >>") return [e] diff --git a/src/Mod/Path/PathTests/PathTestUtils.py b/src/Mod/Path/PathTests/PathTestUtils.py index 029ca7914..518eeb90c 100644 --- a/src/Mod/Path/PathTests/PathTestUtils.py +++ b/src/Mod/Path/PathTests/PathTestUtils.py @@ -45,9 +45,9 @@ class PathTestBase(unittest.TestCase): def assertLine(self, edge, pt1, pt2): """Verify that edge is a line from pt1 to pt2.""" - self.assertIs(type(edge.Curve), Part.LineSegment) - self.assertCoincide(edge.Curve.StartPoint, pt1) - self.assertCoincide(edge.Curve.EndPoint, pt2) + self.assertIs(type(edge.Curve), Part.Line) + self.assertCoincide(edge.valueAt(edge.FirstParameter), pt1) + self.assertCoincide(edge.valueAt(edge.LastParameter), pt2) def assertLines(self, edgs, tail, points): """Verify that the edges match the polygon resulting from points.""" diff --git a/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py b/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py index 13e4d9103..49e6be09c 100644 --- a/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py +++ b/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py @@ -97,7 +97,7 @@ class TestTag02SquareTag(PathTestBase): # ============= tag = Tag( 0, 0, 4, 7, 90, True, 0) pt1 = Vector(+5, 5, 0) pt2 = Vector(-5, 5, 0) - edge = Part.Edge(Part.Line(pt1, pt2)) + edge = Part.Edge(Part.LineSegment(pt1, pt2)) i = tag.intersect(edge) self.assertIsNotNone(i) @@ -109,18 +109,18 @@ class TestTag02SquareTag(PathTestBase): # ============= 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.Line(Vector(5, 0, 0), Vector(4, 0, 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.Curve.StartPoint, edge.Curve.EndPoint) + 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.Line(Vector(5, 0, 0), Vector(1, 0, 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) @@ -128,13 +128,13 @@ class TestTag02SquareTag(PathTestBase): # ============= p1 = Vector(4, 0, 0) p2 = Vector(4, 0, 3) p3 = Vector(1, 0, 3) - self.assertLine(i.edges[0], edge.Curve.StartPoint, p1) + 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.Line(edge.Curve.EndPoint, Vector(0, 0, 0))) + 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) @@ -145,12 +145,12 @@ class TestTag02SquareTag(PathTestBase): # ============= 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.Line(Vector(5, 0, 0), Vector(-4, 0, 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.Curve.StartPoint + p0 = edge.valueAt(edge.FirstParameter) p1 = Vector( 4, 0, 0) p2 = Vector( 4, 0, 3) p3 = Vector(-4, 0, 3) @@ -160,9 +160,9 @@ class TestTag02SquareTag(PathTestBase): # ============= self.assertIsNone(i.tail) # make sure it also works if we get there not directly - edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(0, 0, 0))) + edge = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(0, 0, 0))) i = tag.intersect(edge) - edge = Part.Edge(Part.Line(edge.Curve.EndPoint, Vector(-4, 0, 0))) + 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) @@ -176,18 +176,18 @@ class TestTag02SquareTag(PathTestBase): # ============= 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.Line(Vector(5, 0, 0), Vector(-5, 0, 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.Curve.StartPoint + 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.Curve.EndPoint + 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) @@ -198,13 +198,13 @@ class TestTag02SquareTag(PathTestBase): # ============= 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.Line(Vector(5, 0, 0), Vector(+2, 0, 0))) - e1 = Part.Edge(Part.Line(e0.Curve.EndPoint, Vector(+1, 0, 0))) - e2 = Part.Edge(Part.Line(e1.Curve.EndPoint, Vector(+0.5, 0, 0))) - e3 = Part.Edge(Part.Line(e2.Curve.EndPoint, Vector(-0.5, 0, 0))) - e4 = Part.Edge(Part.Line(e3.Curve.EndPoint, Vector(-1, 0, 0))) - e5 = Part.Edge(Part.Line(e4.Curve.EndPoint, Vector(-2, 0, 0))) - e6 = Part.Edge(Part.Line(e5.Curve.EndPoint, Vector(-5, 0, 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]: @@ -222,7 +222,7 @@ class TestTag02SquareTag(PathTestBase): # ============= pt6 = Vector(-2, 0, 7) self.assertEqual(len(i.edges), 8) - self.assertLines(i.edges, i.tail, [e0.Curve.StartPoint, pt0, pt1, pt2, pt3, pt4, pt5, pt6, e6.Curve.StartPoint, e6.Curve.EndPoint]) + 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): @@ -236,17 +236,17 @@ class TestTag02SquareTag(PathTestBase): # ============= p3 = Vector(-2, 0, 7) p4 = Vector(-2, 0, i) p5 = Vector(-5, 0, i) - edge = Part.Edge(Part.Line(p0, p5)) + 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.Line(Vector(5, 0, i), Vector(-5, 0, i))) + 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.Curve.StartPoint, edge.Curve.EndPoint) + self.assertLine(s.tail, edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter)) def test10(self): """Verify intersection of square tag with an arc.""" @@ -295,7 +295,7 @@ class TestTag03TrapezoidTag(PathTestBase): # ============= tag = Tag( 0, 0, 8, 3, 45, True, 0) pt1 = Vector(+5, 5, 0) pt2 = Vector(-5, 5, 0) - edge = Part.Edge(Part.Line(pt1, pt2)) + edge = Part.Edge(Part.LineSegment(pt1, pt2)) i = tag.intersect(edge) self.assertIsNotNone(i) @@ -307,21 +307,21 @@ class TestTag03TrapezoidTag(PathTestBase): # ============= 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.Line(Vector(5, 0, 0), Vector(4, 0, 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.Curve.StartPoint, edge.Curve.EndPoint) + 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.Line(edge.Curve.EndPoint, Vector(3, 0, 0))) + 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.Curve.StartPoint) + p1 = Vector(edge.valueAt(edge.FirstParameter)) p1.z = 0 - p2 = Vector(edge.Curve.EndPoint) + 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) @@ -329,10 +329,10 @@ class TestTag03TrapezoidTag(PathTestBase): # ============= self.assertIsNone(i.tail) # add another segment to verify starting point offset - edge = Part.Edge(Part.Line(edge.Curve.EndPoint, Vector(2, 0, 0))) + 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.Curve.EndPoint) + 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) @@ -342,19 +342,19 @@ class TestTag03TrapezoidTag(PathTestBase): # ============= 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.Line(Vector(5, 0, 0), Vector(1, 0, 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.Curve.StartPoint, p1) + 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.Line(edge.Curve.EndPoint, Vector(0, 0, 0))) + 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) @@ -365,11 +365,11 @@ class TestTag03TrapezoidTag(PathTestBase): # ============= 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.Line(Vector(5, 0, 0), Vector(-1, 0, 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.Curve.StartPoint) + p0 = Vector(edge.valueAt(edge.FirstParameter)) p1 = Vector(4, 0, 0) p2 = Vector(1, 0, 3) p3 = Vector(-1, 0, 3) @@ -377,18 +377,18 @@ class TestTag03TrapezoidTag(PathTestBase): # ============= self.assertIsNone(i.tail) # make sure we get the same result if there's another edge - edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(1, 0, 0))) + edge = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(1, 0, 0))) i = tag.intersect(edge) - edge = Part.Edge(Part.Line(edge.Curve.EndPoint, Vector(-1, 0, 0))) + 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.Line(Vector(5, 0, 0), Vector(0.5, 0, 0))) + edge = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(0.5, 0, 0))) i = tag.intersect(edge) - edge = Part.Edge(Part.Line(edge.Curve.EndPoint, Vector(-1, 0, 0))) + 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) @@ -398,7 +398,7 @@ class TestTag03TrapezoidTag(PathTestBase): # ============= def test04(self): """Verify proper down plunge on trapezoid tag exit.""" tag = Tag( 0, 0, 8, 3, 45, True, 0) - edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(-2, 0, 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) @@ -411,7 +411,7 @@ class TestTag03TrapezoidTag(PathTestBase): # ============= self.assertIsNone(i.tail) # make sure adding another segment doesn't change the state - edge = Part.Edge(Part.Line(edge.Curve.EndPoint, Vector(-3, 0, 0))) + 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) @@ -420,7 +420,7 @@ class TestTag03TrapezoidTag(PathTestBase): # ============= self.assertIsNone(i.tail) # now if we complete to P3 .... - edge = Part.Edge(Part.Line(edge.Curve.EndPoint, Vector(-4, 0, 0))) + 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()) @@ -430,7 +430,7 @@ class TestTag03TrapezoidTag(PathTestBase): # ============= self.assertIsNone(i.tail) # verify proper operation if there is a single edge going through all - edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(-4, 0, 0))) + 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()) @@ -438,23 +438,23 @@ class TestTag03TrapezoidTag(PathTestBase): # ============= self.assertIsNone(i.tail) # verify tail is added as well - edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(-5, 0, 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.assertLines(i.edges, i.tail, [p0, p1, p2, p3, p6, edge.Curve.EndPoint]) + 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.Line(Vector(5, 0, 0), Vector(+4, 0, 0))) - e1 = Part.Edge(Part.Line(e0.Curve.EndPoint, Vector(+2, 0, 0))) - e2 = Part.Edge(Part.Line(e1.Curve.EndPoint, Vector(+0.5, 0, 0))) - e3 = Part.Edge(Part.Line(e2.Curve.EndPoint, Vector(-0.5, 0, 0))) - e4 = Part.Edge(Part.Line(e3.Curve.EndPoint, Vector(-1, 0, 0))) - e5 = Part.Edge(Part.Line(e4.Curve.EndPoint, Vector(-2, 0, 0))) - e6 = Part.Edge(Part.Line(e5.Curve.EndPoint, Vector(-5, 0, 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]: @@ -472,7 +472,7 @@ class TestTag03TrapezoidTag(PathTestBase): # ============= p6 = Vector(-2, 0, 2) p7 = Vector(-4, 0, 0) - self.assertLines(i.edges, i.tail, [e0.Curve.StartPoint, p0, p1, p2, p3, p4, p5, p6, p7, e6.Curve.EndPoint]) + 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): @@ -486,17 +486,17 @@ class TestTag03TrapezoidTag(PathTestBase): # ============= p3 = Vector(-1, 0, 3) p4 = Vector(-4+i, 0, i) p5 = Vector(-5, 0, i) - edge = Part.Edge(Part.Line(p0, p5)) + 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.Line(Vector(5, 0, i), Vector(-5, 0, i))) + 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.Curve.StartPoint, edge.Curve.EndPoint) + self.assertLine(s.tail, edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter)) def test10(self): """Verify intersection with an arc.""" @@ -544,31 +544,31 @@ class TestTag03TrapezoidTag(PathTestBase): # ============= class TestTag04TriangularTag(PathTestBase): # ======================== """Unit tests for tags that take on a triangular shape.""" - def xtest00(self): + 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.Line(Vector(5, 0, 0), Vector(4, 0, 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.Curve.StartPoint, edge.Curve.EndPoint) + self.assertLine(i.edges[0], edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter)) self.assertIsNone(i.tail) - def xtest01(self): + 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.Line(Vector(5, 0, 0), Vector(3, 0, 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.Curve.StartPoint, p1, p2]) + 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.Line(edge.Curve.EndPoint, Vector(1, 0, 0))) + 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) @@ -576,21 +576,21 @@ class TestTag04TriangularTag(PathTestBase): # ======================== self.assertLine(i.edges[2], p2, p3) self.assertIsNone(i.tail) - def xtest02(self): + 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.Line(p0, FreeCAD.Vector(0,0,0))) + 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.Line(edge.Curve.EndPoint, FreeCAD.Vector(-3,0,0))) + 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) @@ -598,12 +598,12 @@ class TestTag04TriangularTag(PathTestBase): # ======================== self.assertLines(i.edges, i.tail, [p0, p1, p2, p3]) # same result if all is one line - edge = Part.Edge(Part.Line(p0, edge.Curve.EndPoint)) + 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 xtest03(self): + def test03(self): """Verify triangular tag shap on intersection.""" tag = Tag( 0, 0, 8, 7, 45, True, 0) @@ -611,7 +611,7 @@ class TestTag04TriangularTag(PathTestBase): # ======================== p1 = Vector(4, 0, 0) p2 = Vector(0, 0, 4) p3 = Vector(-4, 0, 0) - edge = Part.Edge(Part.Line(p0, p3)) + 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]) @@ -619,13 +619,13 @@ class TestTag04TriangularTag(PathTestBase): # ======================== # this should also work if there is some excess, aka tail p4 = Vector(-5, 0, 0) - edge = Part.Edge(Part.Line(p0, p4)) + 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 xtest10(self): + def test10(self): """Verify intersection with an arc.""" tag = Tag( 0, 0, 8, 7, 45, True, 0) p1 = Vector(10, -10, 0) From a3ae53a82b386cdd034fdb243f5a9209459abf93 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Mon, 5 Dec 2016 04:30:49 -0800 Subject: [PATCH 06/27] Added arcToHelix. --- .../PathScripts/PathDressupHoldingTags.py | 68 ++++++++++++++++--- src/Mod/Path/PathScripts/PathGeom.py | 25 +++++++ src/Mod/Path/PathTests/TestPathGeom.py | 27 ++++++++ 3 files changed, 111 insertions(+), 9 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index 60c76861a..34c3d48dd 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -267,7 +267,20 @@ class Tag: # if we have no core the tip is the origin of the Tag line = Part.Edge(self.tag.centerLine()) debugEdge(line, "------- center line", 'P0') - i = DraftGeomUtils.findIntersection(line, edge, True) + 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) + for p in aps: + print("%s - p=%.2f" % (p, arc.Curve.parameter(p))) + i = [edge.valueAt(arc.Curve.parameter(p)) 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)) @@ -371,16 +384,53 @@ class Tag: def splitEdgeAt(self, edge, pt): - p = edge.Curve.parameter(pt) + # I'm getting rather tired of this interface, so I decided to implement this myself. + # How hard can it be? + # There are only 3 types of edges passing through here, Line, Circle and Helix ... + if False: + p = edge.Curve.parameter(pt) + #p = edge.parameterAt(Part.Vertex(pt.x, pt.y, pt.z)) - pf = edge.valueAt(edge.FirstParameter) - pl = edge.valueAt(edge.LastParameter) - print("-------- splitAt((%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f): (%.2f, %.2f, %.2f)) -> param[%.2f -> %.2f]: %.2f" % (pf.x, pf.y, pf.z, pl.x, pl.y, pl.z, pt.x, pt.y, pt.z, edge.FirstParameter, edge.LastParameter, p)) + pf = edge.valueAt(edge.FirstParameter) + pl = edge.valueAt(edge.LastParameter) + print("-------- splitAt((%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f): (%.2f, %.2f, %.2f)) -> param[%.2f -> %.2f]: %.2f" % (pf.x, pf.y, pf.z, pl.x, pl.y, pl.z, pt.x, pt.y, pt.z, edge.FirstParameter, edge.LastParameter, p)) - wire = edge.split(p) - # split does not carry the Placement of the original curve foward ... - wire.transformShape(edge.Placement.toMatrix()) - return wire.Edges + print("-------- splitAt(%.2f <= %.2f <= %.2f" % (edge.FirstParameter, p, edge.LastParameter)) + wire = edge.split(p) + # split does not carry the Placement of the original curve foward ... + wire.transformShape(edge.Placement.toMatrix()) + return wire.Edges + p1 = edge.valueAt(edge.FirstParameter) + p2 = pt + p3 = edge.valueAt(edge.LastParameter) + edges = [] + if type(edge.Curve) == Part.Line or type(edge.Curve) == Part.LineSegment: + edges.append(Part.LineSegment(p1, p2)) + edges.append(Part.LineSegment(p2, p3)) + elif type(edge.Curve) == Part.Circle: + # it's an arc + p = edge.Curve.parameterAt(p2) + p12 = edge.Curve.value((edge.Curve.FirstParameter + p)/2) + p23 = edge.Curve.value((p + edge.Curve.LastParameter)/2) + edges.append(Part.Edge(Part.Arc(p1, p12, p2))) + edges.append(Part.Edge(Part.Arc(p2, p23, p3))) + else: + # it's a helix + # convert to arc + p01 = FreeCAD.Vector(p1.x, p1.y, 0) + p02 = FreeCAD.Vector(p2.x, p2.y, 0) + p03 = FreeCAD.Vector(p3.x, p3.y, 0) + e0 = Part.Edge(Part.Arc(p01, p02, p03)) + # split arc + p0 = e0.Curve.parameterAt(p02) + p012 = e0.Curve.value((e0.Curve.FirstParameter + p0)/2) + p023 = e0.Curve.value((p0 + e0.Curve.LastParameter)/2) + e01 = Part.Edge(Part.Arc(p01, p012, p02)) + e02 = Part.Edge(Part.Arc(p02, p023, p03)) + # transform arcs into helical form + edges.append(self.arcToHelix(e01, p1.z, p2.z)) + edges.append(self.arcToHelix(e02, p2.z, p3.z)) + return edges def mapEdgeToSolid(self, edge, label): pf = edge.valueAt(edge.FirstParameter) diff --git a/src/Mod/Path/PathScripts/PathGeom.py b/src/Mod/Path/PathScripts/PathGeom.py index 2fff4b1e0..7e06829d8 100644 --- a/src/Mod/Path/PathScripts/PathGeom.py +++ b/src/Mod/Path/PathScripts/PathGeom.py @@ -213,3 +213,28 @@ class PathGeom: wires.append(Part.Wire(edges)) return wires + @classmethod + def arcToHelix(cls, edge, z0, z1): + m = FreeCAD.Matrix() + m.unity() + + p1 = edge.valueAt(edge.FirstParameter) + p2 = edge.valueAt(edge.LastParameter) + z = p1.z + + pd = p2 - p1 + dz = z1 - z0 + + #print("arcToHelix(%.2f, %.2f): dz=%.2f, dy=%.2f, z=%.2f" % (z0 ,z1, dz, pd.y, z)) + + m.A32 = dz / pd.y + m.A34 = - m.A32 + if dz < 0: + m.A34 *= p2.y + m.A34 += z1 - z + else: + m.A34 *= p1.y + m.A34 += z0 - z + + e = edge.transformGeometry(m).Edges[0] + return e diff --git a/src/Mod/Path/PathTests/TestPathGeom.py b/src/Mod/Path/PathTests/TestPathGeom.py index 1b72b7edf..779767ccb 100644 --- a/src/Mod/Path/PathTests/TestPathGeom.py +++ b/src/Mod/Path/PathTests/TestPathGeom.py @@ -145,3 +145,30 @@ class TestPathGeom(PathTestBase): self.assertEqual(len(wires[1].Edges), 1) self.assertLine(wires[1].Edges[0], Vector(0,1,0), Vector(0,0,0)) + + def test60(self): + """Verify arcToHelix returns proper helix.""" + p1 = Vector(10,-10,0) + p2 = Vector(0,0,0) + p3 = Vector(10,10,0) + + e = PathGeom.arcToHelix(Part.Edge(Part.Arc(p1, p2, p3)), 0, 2) + self.assertCurve(e, p1, p2 + Vector(0,0,1), p3 + Vector(0,0,2)) + + e = PathGeom.arcToHelix(Part.Edge(Part.Arc(p1, p2, p3)), 3, 7) + self.assertCurve(e, p1 + Vector(0,0,3), p2 + Vector(0,0,5), p3 + Vector(0,0,7)) + + e = PathGeom.arcToHelix(Part.Edge(Part.Arc(p1, p2, p3)), 9, 1) + self.assertCurve(e, p1 + Vector(0,0,9), p2 + Vector(0,0,5), p3 + Vector(0,0,1)) + + dz = Vector(0,0,3) + p11 = p1 + dz + p12 = p2 + dz + p13 = p3 + dz + + e = PathGeom.arcToHelix(Part.Edge(Part.Arc(p11, p12, p13)), 0, 8) + self.assertCurve(e, p1, p2 + Vector(0,0,4), p3 + Vector(0,0,8)) + + e = PathGeom.arcToHelix(Part.Edge(Part.Arc(p11, p12, p13)), 2, -2) + self.assertCurve(e, p1 + Vector(0,0,2), p2, p3 + Vector(0,0,-2)) + From 0fabe5c079da5e332a04f16c8b672d3a4ed33748 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Mon, 5 Dec 2016 14:37:36 -0800 Subject: [PATCH 07/27] Fixed helix construction. --- src/Mod/Path/PathScripts/PathGeom.py | 107 +++++++++++++++++++++---- src/Mod/Path/PathTests/TestPathGeom.py | 66 +++++++++++++++ 2 files changed, 158 insertions(+), 15 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathGeom.py b/src/Mod/Path/PathScripts/PathGeom.py index 7e06829d8..9280a7f51 100644 --- a/src/Mod/Path/PathScripts/PathGeom.py +++ b/src/Mod/Path/PathScripts/PathGeom.py @@ -128,6 +128,33 @@ class PathGeom: Convenience function to return the projection of the Vector in the XY-plane.""" return Vector(point.x, point.y, 0) + @classmethod + def cmdForEdge(cls, edge): + pt = edge.valueAt(edge.LastParameter) + params = {'X': pt.x, 'Y': pt.y, 'Z': pt.z} + if type(edge.Curve) == Part.Line or type(edge.Curve) == Part.LineSegment: + command = Path.Command('G1', params) + else: + p1 = edge.valueAt(edge.FirstParameter) + p2 = edge.valueAt((edge.FirstParameter + edge.LastParameter)/2) + p3 = pt + if Side.Left == Side.of(p2 - p1, p3 - p2): + cmd = 'G3' + else: + cmd = 'G2' + pa = PathGeom.xy(p1) + pb = PathGeom.xy(p2) + pc = PathGeom.xy(p3) + pd = Part.Circle(PathGeom.xy(p1), PathGeom.xy(p2), PathGeom.xy(p3)).Center + #print("**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (pa.x, pa.y, pa.z, pc.x, pc.y, pc.z)) + #print("**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (pb.x, pb.y, pb.z, pd.x, pd.y, pd.z)) + offset = Part.Circle(PathGeom.xy(p1), PathGeom.xy(p2), PathGeom.xy(p3)).Center - p1 + #print("**** (%.2f, %.2f, %.2f)" % (offset.x, offset.y, offset.z)) + params.update({'I': offset.x, 'J': offset.y, 'K': (p3.z - p1.z)/2}) + command = Path.Command(cmd, params) + #print command + return command + @classmethod def edgeForCmd(cls, cmd, startPoint): """(cmd, startPoint). @@ -215,26 +242,76 @@ class PathGeom: @classmethod def arcToHelix(cls, edge, z0, z1): - m = FreeCAD.Matrix() - m.unity() + """(edge, z0, z1) + Assuming edge is an arc it'll return a helix matching the arc starting at z0 and rising/falling to z1.""" + p1 = edge.valueAt(edge.FirstParameter) p2 = edge.valueAt(edge.LastParameter) - z = p1.z - pd = p2 - p1 - dz = z1 - z0 + cmd = cls.cmdForEdge(edge) + params = cmd.Parameters + params.update({'Z': z1, 'K': (z1 - z0)/2}) + command = Path.Command(cmd.Name, params) - #print("arcToHelix(%.2f, %.2f): dz=%.2f, dy=%.2f, z=%.2f" % (z0 ,z1, dz, pd.y, z)) + return cls.edgeForCmd(command, FreeCAD.Vector(p1.x, p1.y, z0)) - m.A32 = dz / pd.y - m.A34 = - m.A32 - if dz < 0: - m.A34 *= p2.y - m.A34 += z1 - z + + @classmethod + def helixToArc(cls, edge, z = 0): + """(edge, z=0) + Returns the projection of the helix onto the XY-plane with a given offset.""" + p1 = edge.valueAt(edge.FirstParameter) + p2 = edge.valueAt((edge.FirstParameter + edge.LastParameter)/2) + p3 = edge.valueAt(edge.LastParameter) + p01 = FreeCAD.Vector(p1.x, p1.y, z) + p02 = FreeCAD.Vector(p2.x, p2.y, z) + p03 = FreeCAD.Vector(p3.x, p3.y, z) + return Part.Edge(Part.Arc(p01, p02, p03)) + + @classmethod + def splitArcAt(cls, edge, pt): + """(edge, pt) + Returns a list of 2 edges which together form the original arc split at the given point. + The Vector pt has to represnt a point on the given arc.""" + p1 = edge.valueAt(edge.FirstParameter) + p2 = pt + p3 = edge.valueAt(edge.LastParameter) + edges = [] + + p = edge.Curve.parameter(p2) + #print("splitArcAt(%.2f, %.2f, %.2f): %.2f - %.2f - %.2f" % (pt.x, pt.y, pt.z, edge.FirstParameter, p, edge.LastParameter)) + + p12 = edge.Curve.value((edge.FirstParameter + p)/2) + p23 = edge.Curve.value((p + edge.LastParameter)/2) + #print("splitArcAt: p12=(%.2f, %.2f, %.2f) p23=(%.2f, %.2f, %.2f)" % (p12.x, p12.y, p12.z, p23.x, p23.y, p23.z)) + + edges.append(Part.Edge(Part.Arc(p1, p12, p2))) + edges.append(Part.Edge(Part.Arc(p2, p23, p3))) + + return edges + + @classmethod + def splitEdgeAt(cls, edge, pt): + """(edge, pt) + Returns a list of 2 edges, forming the original edge split at the given point. + The results are undefined if the Vector representing the point is not part of the edge.""" + # I could not get the OCC parameterAt and split to work ... + # pt HAS to be on the edge, otherwise the results are undefined + p1 = edge.valueAt(edge.FirstParameter) + p2 = pt + p3 = edge.valueAt(edge.LastParameter) + edges = [] + + if type(edge.Curve) == Part.Line or type(edge.Curve) == Part.LineSegment: + # it's a line + return [Part.Edge(Part.LineSegment(p1, p2)), Part.Edge(Part.LineSegment(p2, p3))] + elif type(edge.Curve) == Part.Circle: + # it's an arc + return cls.splitArcAt(edge, pt) else: - m.A34 *= p1.y - m.A34 += z0 - z + # it's a helix + arc = cls.helixToArc(edge, 0) + aes = cls.splitArcAt(arc, FreeCAD.Vector(pt.x, pt.y, 0)) + return [cls.arcToHelix(aes[0], p1.z, p2.z), cls.arcToHelix(aes[1], p2.z, p3.z)] - e = edge.transformGeometry(m).Edges[0] - return e diff --git a/src/Mod/Path/PathTests/TestPathGeom.py b/src/Mod/Path/PathTests/TestPathGeom.py index 779767ccb..7ab7a5396 100644 --- a/src/Mod/Path/PathTests/TestPathGeom.py +++ b/src/Mod/Path/PathTests/TestPathGeom.py @@ -172,3 +172,69 @@ class TestPathGeom(PathTestBase): e = PathGeom.arcToHelix(Part.Edge(Part.Arc(p11, p12, p13)), 2, -2) self.assertCurve(e, p1 + Vector(0,0,2), p2, p3 + Vector(0,0,-2)) + o = 10*math.sin(math.pi/4) + p1 = Vector(10, -10, 1) + p2 = Vector(10 - 10*math.sin(math.pi/4), -10*math.cos(math.pi/4), 1) + p3 = Vector(0, 0, 1) + e = PathGeom.arcToHelix(Part.Edge(Part.Arc(p1, p2, p3)), 0, 5) + self.assertCurve(e, Vector(10,-10,0), Vector(p2.x,p2.y,2.5), Vector(0, 0, 5)) + + + def test62(self): + """Verify splitArcAt returns proper subarcs.""" + p1 = Vector(10,-10,0) + p2 = Vector(0,0,0) + p3 = Vector(10,10,0) + + arc = Part.Edge(Part.Arc(p1, p2, p3)) + + o = 10*math.sin(math.pi/4) + p12 = Vector(10 - o, -o, 0) + p23 = Vector(10 - o, +o, 0) + + e = PathGeom.splitArcAt(arc, p2) + self.assertCurve(e[0], p1, p12, p2) + self.assertCurve(e[1], p2, p23, p3) + + p34 = Vector(10 - 10*math.sin(1*math.pi/8), -10*math.cos(1*math.pi/8), 0) + p45 = Vector(10 - 10*math.sin(5*math.pi/8), -10*math.cos(5*math.pi/8), 0) + + e = PathGeom.splitArcAt(arc, p12) + self.assertCurve(e[0], p1, p34, p12) + self.assertCurve(e[1], p12, p45, p3) + + + def test65(self): + """Verify splitEdgeAt.""" + e = PathGeom.splitEdgeAt(Part.Edge(Part.LineSegment(Vector(), Vector(2, 4, 6))), Vector(1, 2, 3)) + self.assertLine(e[0], Vector(), Vector(1,2,3)) + self.assertLine(e[1], Vector(1,2,3), Vector(2,4,6)) + + # split an arc + p1 = Vector(10,-10,1) + p2 = Vector(0,0,1) + p3 = Vector(10,10,1) + arc = Part.Edge(Part.Arc(p1, p2, p3)) + e = PathGeom.splitEdgeAt(arc, p2) + o = 10*math.sin(math.pi/4) + p12 = Vector(10 - o, -o, 1) + p23 = Vector(10 - o, +o, 1) + self.assertCurve(e[0], p1, p12, p2) + self.assertCurve(e[1], p2, p23, p3) + + + # split a helix + p1 = Vector(10,-10,0) + p2 = Vector(0,0,5) + p3 = Vector(10,10,10) + h = PathGeom.arcToHelix(arc, 0, 10) + self.assertCurve(h, p1, p2, p3) + + e = PathGeom.splitEdgeAt(h, p2) + o = 10*math.sin(math.pi/4) + p12 = Vector(10 - o, -o, 2.5) + p23 = Vector(10 - o, +o, 7.5) + pf = e[0].valueAt((e[0].FirstParameter + e[0].LastParameter)/2) + pl = e[1].valueAt((e[1].FirstParameter + e[1].LastParameter)/2) + self.assertCurve(e[0], p1, p12, p2) + self.assertCurve(e[1], p2, p23, p3) From f294821f9d3bc32f16947f5cf64b41f26e58f8c1 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Tue, 6 Dec 2016 05:03:48 -0800 Subject: [PATCH 08/27] Use PathGeom for holding tags dressup. --- .../PathScripts/PathDressupHoldingTags.py | 119 ++++-------------- src/Mod/Path/PathScripts/PathGeom.py | 3 + .../PathTests/TestPathDressupHoldingTags.py | 4 +- 3 files changed, 27 insertions(+), 99 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index 34c3d48dd..f49d32039 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -82,41 +82,6 @@ def debugCylinder(vector, r, height, label, color = None): if color: obj.ViewObject.ShapeColor = color -movecommands = ['G0', 'G00', 'G1', 'G01', 'G2', 'G02', 'G3', 'G03'] -movestraight = ['G1', 'G01'] -movecw = ['G2', 'G02'] -moveccw = ['G3', 'G03'] -movearc = movecw + moveccw - -slack = 0.0000001 - -def pathCommandForEdge(edge): - pt = edge.valueAt(edge.LastParameter) - params = {'X': pt.x, 'Y': pt.y, 'Z': pt.z} - if type(edge.Curve) == Part.Line or type(edge.Curve) == Part.LineSegment: - command = Path.Command('G1', params) - else: - p1 = edge.valueAt(edge.FirstParameter) - p2 = edge.valueAt((edge.FirstParameter + edge.LastParameter)/2) - p3 = pt - if Side.Left == Side.of(p2 - p1, p3 - p2): - cmd = 'G3' - else: - cmd = 'G2' - pa = PathGeom.xy(p1) - pb = PathGeom.xy(p2) - pc = PathGeom.xy(p3) - pd = Part.Circle(PathGeom.xy(p1), PathGeom.xy(p2), PathGeom.xy(p3)).Center - print("**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (pa.x, pa.y, pa.z, pc.x, pc.y, pc.z)) - print("**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (pb.x, pb.y, pb.z, pd.x, pd.y, pd.z)) - offset = Part.Circle(PathGeom.xy(p1), PathGeom.xy(p2), PathGeom.xy(p3)).Center - p1 - print("**** (%.2f, %.2f, %.2f)" % (offset.x, offset.y, offset.z)) - params.update({'I': offset.x, 'J': offset.y, 'K': (p3.z - p1.z)/2}) - command = Path.Command(cmd, params) - print command - return command - - class Tag: @classmethod @@ -246,7 +211,7 @@ class Tag: tail = None else: debugPrint('P0', "------- split at (%s)" % i) - e, tail = self.tag.splitEdgeAt(edge, 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 @@ -276,9 +241,10 @@ class Tag: 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)) for p in aps] + i = [edge.valueAt(arc.Curve.parameter(p) * paramScale) for p in aps] else: i = DraftGeomUtils.findIntersection(line, edge) #i = line.Curve.intersect(edge) @@ -288,7 +254,7 @@ class Tag: e = edge tail = None else: - e, tail = self.tag.splitEdgeAt(edge, i[0]) + 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] @@ -318,7 +284,7 @@ class Tag: tail = None else: debugPrint('P1', "----- P1 split edge @ (%.2f, %.2f, %.2f)" % (i.x, i.y, i.z)) - e, tail = self.tag.splitEdgeAt(edge, i) + 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)) @@ -350,7 +316,7 @@ class Tag: e = edge tail = None else: - e, tail = self.tag.splitEdgeAt(edge, i) + e, tail = PathGeom.splitEdgeAt(edge, i) if tail: pf = tail.valueAt(tail.FirstParameter) pl = tail.valueAt(tail.LastParameter) @@ -383,63 +349,14 @@ class Tag: return self - def splitEdgeAt(self, edge, pt): - # I'm getting rather tired of this interface, so I decided to implement this myself. - # How hard can it be? - # There are only 3 types of edges passing through here, Line, Circle and Helix ... - if False: - p = edge.Curve.parameter(pt) - #p = edge.parameterAt(Part.Vertex(pt.x, pt.y, pt.z)) - - pf = edge.valueAt(edge.FirstParameter) - pl = edge.valueAt(edge.LastParameter) - print("-------- splitAt((%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f): (%.2f, %.2f, %.2f)) -> param[%.2f -> %.2f]: %.2f" % (pf.x, pf.y, pf.z, pl.x, pl.y, pl.z, pt.x, pt.y, pt.z, edge.FirstParameter, edge.LastParameter, p)) - - print("-------- splitAt(%.2f <= %.2f <= %.2f" % (edge.FirstParameter, p, edge.LastParameter)) - wire = edge.split(p) - # split does not carry the Placement of the original curve foward ... - wire.transformShape(edge.Placement.toMatrix()) - return wire.Edges - p1 = edge.valueAt(edge.FirstParameter) - p2 = pt - p3 = edge.valueAt(edge.LastParameter) - edges = [] - if type(edge.Curve) == Part.Line or type(edge.Curve) == Part.LineSegment: - edges.append(Part.LineSegment(p1, p2)) - edges.append(Part.LineSegment(p2, p3)) - elif type(edge.Curve) == Part.Circle: - # it's an arc - p = edge.Curve.parameterAt(p2) - p12 = edge.Curve.value((edge.Curve.FirstParameter + p)/2) - p23 = edge.Curve.value((p + edge.Curve.LastParameter)/2) - edges.append(Part.Edge(Part.Arc(p1, p12, p2))) - edges.append(Part.Edge(Part.Arc(p2, p23, p3))) - else: - # it's a helix - # convert to arc - p01 = FreeCAD.Vector(p1.x, p1.y, 0) - p02 = FreeCAD.Vector(p2.x, p2.y, 0) - p03 = FreeCAD.Vector(p3.x, p3.y, 0) - e0 = Part.Edge(Part.Arc(p01, p02, p03)) - # split arc - p0 = e0.Curve.parameterAt(p02) - p012 = e0.Curve.value((e0.Curve.FirstParameter + p0)/2) - p023 = e0.Curve.value((p0 + e0.Curve.LastParameter)/2) - e01 = Part.Edge(Part.Arc(p01, p012, p02)) - e02 = Part.Edge(Part.Arc(p02, p023, p03)) - # transform arcs into helical form - edges.append(self.arcToHelix(e01, p1.z, p2.z)) - edges.append(self.arcToHelix(e02, p2.z, p3.z)) - return edges - 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) - p1b = FreeCAD.Vector(p1a.x, p1a.y, p1a.z + self.height) 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))) @@ -448,8 +365,8 @@ class Tag: return [] p2a = edge.valueAt(edge.LastParameter) - p2b = FreeCAD.Vector(p2a.x, p2a.y, p2a.z + self.height) 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: @@ -518,7 +435,7 @@ class Tag: #print("it's a plane, checking R") c = face.Edges[0].Curve if (type(c) == Part.Circle): - return filter(lambda pt: (pt - c.Center).Length <= c.Radius, pts) + 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) def isPointOnEdge(self, pt, edge): @@ -529,18 +446,26 @@ 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)" % (edge.FirstParameter, param, edge.LastParameter, pt.x, pt.y, pt.z)) + 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 def nextIntersectionClosestTo(self, edge, solid, refPt): 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) refp=(%.2f, %.2f, %.2f)" % (type(edge.Curve), ef.x, ef.y, ef.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 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)): @@ -578,7 +503,7 @@ class Tag: tail = edge else: print("---- split edge") - e,tail = self.splitEdgeAt(edge, i) + e,tail = PathGeom.splitEdgeAt(edge, i) inters.edges.append(e) return inters.intersect(tail) else: @@ -821,13 +746,13 @@ class ObjectDressup: lastTag = sameTag t = 1 for e in inters.edges: - commands.append(pathCommandForEdge(e)) + commands.append(PathGeom.cmdForEdge(e)) inters = None if t >= len(tags): # gone through all tags, consume edge and move on if edge: - commands.append(pathCommandForEdge(edge)) + commands.append(PathGeom.cmdForEdge(edge)) edge = None t = 0 diff --git a/src/Mod/Path/PathScripts/PathGeom.py b/src/Mod/Path/PathScripts/PathGeom.py index 9280a7f51..7f09f4001 100644 --- a/src/Mod/Path/PathScripts/PathGeom.py +++ b/src/Mod/Path/PathScripts/PathGeom.py @@ -254,6 +254,9 @@ 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)) + 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 49e6be09c..902f83251 100644 --- a/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py +++ b/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py @@ -538,7 +538,7 @@ class TestTag03TrapezoidTag(PathTestBase): # ============= 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) + self.assertCurve(s.tail, pl, Vector(3.996548, +7.997409, 1.590060), p2) class TestTag04TriangularTag(PathTestBase): # ======================== @@ -652,7 +652,7 @@ class TestTag04TriangularTag(PathTestBase): # ======================== 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.000001, 0, 4) + pj = Vector(0, 0, 4) pk = Vector(0.397586, +2.791711, 1.180119) s = tag.intersect(edge) From e807094eda1074d066d81346d2652705e28bd111 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Tue, 6 Dec 2016 08:18:18 -0800 Subject: [PATCH 09/27] Create cones for debugging cone shaped tags. --- .../PathScripts/PathDressupHoldingTags.py | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index f49d32039..66ba48656 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -82,6 +82,18 @@ def debugCylinder(vector, r, height, label, color = None): if color: obj.ViewObject.ShapeColor = color +def debugCone(vector, r1, r2, height, label, color = None): + if debugDressup: + obj = FreeCAD.ActiveDocument.addObject("Part::Cone", label) + obj.Label = label + obj.Radius1 = r1 + obj.Radius2 = r2 + obj.Height = height + obj.Placement = FreeCAD.Placement(vector, FreeCAD.Rotation(FreeCAD.Vector(0,0,1), 0)) + obj.ViewObject.Transparency = 90 + if color: + obj.ViewObject.ShapeColor = color + class Tag: @classmethod @@ -121,6 +133,8 @@ class Tag: def createSolidsAt(self, z): self.z = z r1 = self.width / 2 + self.r1 = r1 + self.r2 = r1 height = self.height if self.angle == 90 and height > 0: self.solid = Part.makeCylinder(r1, height) @@ -136,6 +150,7 @@ class Tag: height = r1 * tangens self.core = None self.actualHeight = height + self.r2 = r2 self.solid = Part.makeCone(r1, r2, height) else: # degenerated case - no tag @@ -791,17 +806,20 @@ class ObjectDressup: return print("execute - %d tags" % (len(tags))) + tags = pathData.sortedTags(tags) + for tag in tags: + tag.createSolidsAt(pathData.minZ) + tagID = 0 for tag in tags: tagID += 1 if tag.enabled: #print("x=%s, y=%s, z=%s" % (tag.x, tag.y, pathData.minZ)) #debugMarker(FreeCAD.Vector(tag.x, tag.y, pathData.minZ), "tag-%02d" % tagID , (1.0, 0.0, 1.0), 0.5) - debugCylinder(tag.originAt(pathData.minZ), tag.width/2, tag.actualHeight, "tag-%02d" % tagID) - - tags = pathData.sortedTags(tags) - for tag in tags: - tag.createSolidsAt(pathData.minZ) + if tag.angle != 90: + debugCone(tag.originAt(pathData.minZ), tag.r1, tag.r2, tag.actualHeight, "tag-%02d" % tagID) + else: + debugCylinder(tag.originAt(pathData.minZ), tag.width/2, tag.actualHeight, "tag-%02d" % tagID) self.fingerprint = [tag.toString() for tag in tags] self.tags = tags From 9eca75e67381f251b2353b1e60faa98d4f2c5f9f Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sat, 10 Dec 2016 12:13:29 -0800 Subject: [PATCH 10/27] Added pixellation of arbitrary path curve. --- src/Mod/Path/PathScripts/PathGeom.py | 80 ++++++++++++++++++---------- 1 file changed, 53 insertions(+), 27 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathGeom.py b/src/Mod/Path/PathScripts/PathGeom.py index 7f09f4001..c0710f28f 100644 --- a/src/Mod/Path/PathScripts/PathGeom.py +++ b/src/Mod/Path/PathScripts/PathGeom.py @@ -89,13 +89,13 @@ class PathGeom: return cls.pointsCoincide(edge.valueAt(edge.FirstParameter), vector) or cls.pointsCoincide(edge.valueAt(edge.LastParameter), vector) @classmethod - def getAngle(cls, vertex): - """(vertex) - Returns the angle [-pi,pi] of a vertex using the X-axis as the reference. + def getAngle(cls, vector): + """(vector) + Returns the angle [-pi,pi] of a vector using the X-axis as the reference. Positive angles for vertexes in the upper hemishpere (positive y values) and negative angles for the lower hemishpere.""" - a = vertex.getAngle(FreeCAD.Vector(1,0,0)) - if vertex.y < 0: + a = vector.getAngle(FreeCAD.Vector(1,0,0)) + if vector.y < 0: return -a return a @@ -129,31 +129,57 @@ class PathGeom: return Vector(point.x, point.y, 0) @classmethod - def cmdForEdge(cls, edge): - pt = edge.valueAt(edge.LastParameter) + def cmdsForEdge(cls, edge, flip = False, useHelixForBSpline = True): + 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: - command = Path.Command('G1', params) + commands = [Path.Command('G1', params)] else: - p1 = edge.valueAt(edge.FirstParameter) - p2 = edge.valueAt((edge.FirstParameter + edge.LastParameter)/2) - p3 = pt - if Side.Left == Side.of(p2 - p1, p3 - p2): - cmd = 'G3' + if not flip: + p1 = edge.valueAt(edge.FirstParameter) + p3 = pt else: - cmd = 'G2' - pa = PathGeom.xy(p1) - pb = PathGeom.xy(p2) - pc = PathGeom.xy(p3) - pd = Part.Circle(PathGeom.xy(p1), PathGeom.xy(p2), PathGeom.xy(p3)).Center - #print("**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (pa.x, pa.y, pa.z, pc.x, pc.y, pc.z)) - #print("**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (pb.x, pb.y, pb.z, pd.x, pd.y, pd.z)) - offset = Part.Circle(PathGeom.xy(p1), PathGeom.xy(p2), PathGeom.xy(p3)).Center - p1 - #print("**** (%.2f, %.2f, %.2f)" % (offset.x, offset.y, offset.z)) - params.update({'I': offset.x, 'J': offset.y, 'K': (p3.z - p1.z)/2}) - command = Path.Command(cmd, params) - #print command - return command + p1 = pt + p3 = edge.valueAt(edge.LastParameter) + p2 = edge.valueAt((edge.FirstParameter + edge.LastParameter)/2) + if type(edge.Curve) == Part.Circle or (useHelixForBSpline and type(edge.Curve) == Part.BSplineCurve): + if Side.Left == Side.of(p2 - p1, p3 - p2): + 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)) + pd = Part.Circle(PathGeom.xy(p1), PathGeom.xy(p2), PathGeom.xy(p3)).Center + + pa = PathGeom.xy(p1) + pb = PathGeom.xy(p2) + pc = PathGeom.xy(p3) + #print("**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (pa.x, pa.y, pa.z, pc.x, pc.y, pc.z)) + #print("**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (pb.x, pb.y, pb.z, pd.x, pd.y, pd.z)) + offset = Part.Circle(PathGeom.xy(p1), PathGeom.xy(p2), PathGeom.xy(p3)).Center - p1 + #print("**** (%.2f, %.2f, %.2f)" % (offset.x, offset.y, offset.z)) + params.update({'I': offset.x, 'J': offset.y, 'K': (p3.z - p1.z)/2}) + commands = [ Path.Command(cmd, params) ] + else: + eStraight = Part.Edge(Part.LineSegment(p1, p3)) + esP2 = eStraight.valueAt((eStraight.FirstParameter + eStraight.LastParameter)/2) + deviation = (p2 - esP2).Length + if cls.isRoughly(deviation, 0): + return [ Path.Command('G1', {'X': p3.x, 'Y': p3.y, 'Z': p3.z}) ] + # at this point pixellation is all we can do + commands = [] + segments = int(math.ceil((deviation / eStraight.Length) * 1000)) + print("**** pixellation with %d segments" % segments) + dParameter = (edge.LastParameter - edge.FirstParameter) / segments + for i in range(0, segments): + if flip: + p = edge.valueAt(edge.LastParameter - (i + 1) * dParameter) + 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) + commands.append(cmd) + #print commands + return commands @classmethod def edgeForCmd(cls, cmd, startPoint): @@ -249,7 +275,7 @@ class PathGeom: p1 = edge.valueAt(edge.FirstParameter) p2 = edge.valueAt(edge.LastParameter) - cmd = cls.cmdForEdge(edge) + cmd = cls.cmdsForEdge(edge)[0] params = cmd.Parameters params.update({'Z': z1, 'K': (z1 - z0)/2}) command = Path.Command(cmd.Name, params) From b26a7ad7949afebf79a8daffdc9d55108a0b1012 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sat, 10 Dec 2016 12:39:23 -0800 Subject: [PATCH 11/27] 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 From 1635d73b1befd917fb4774b8736ef7b347acf52c Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sat, 10 Dec 2016 16:35:54 -0800 Subject: [PATCH 12/27] Removed core which isn't used anymore; some debugging info to figure out the save/restore issus. --- .../PathScripts/PathDressupHoldingTags.py | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index a9b53e8a7..35763d8b3 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -104,9 +104,14 @@ class Tag: except: return None + def toString(self): + return str((self.x, self.y, self.width, self.height, self.angle, self.enabled)) + def __init__(self, x, y, width, height, angle, enabled=True, z=None): + print("Tag(%.2f, %.2f, %.2f, %.2f, %.2f, %d, %s)" % (x, y, width, height, angle/math.pi, enabled, z)) self.x = x self.y = y + self.z = z self.width = math.fabs(width) self.height = math.fabs(height) self.actualHeight = self.height @@ -115,9 +120,6 @@ class Tag: if z is not None: self.createSolidsAt(z) - def toString(self): - return str((self.x, self.y, self.width, self.height, self.angle, self.enabled)) - def originAt(self, z): return FreeCAD.Vector(self.x, self.y, z) @@ -135,27 +137,21 @@ class Tag: height = self.height if self.angle == 90 and height > 0: self.solid = Part.makeCylinder(r1, height) - self.core = self.solid.copy() elif self.angle > 0.0 and height > 0.0: tangens = math.tan(math.radians(self.angle)) dr = height / tangens if dr < r1: r2 = r1 - dr - self.core = Part.makeCylinder(r2, height) else: r2 = 0 height = r1 * tangens - self.core = None self.actualHeight = height self.r2 = r2 self.solid = Part.makeCone(r1, r2, height) else: # degenerated case - no tag self.solid = Part.makeSphere(r1 / 10000) - self.core = None self.solid.translate(self.originAt(z)) - if self.core: - self.core.translate(self.originAt(z)) def filterIntersections(self, pts, face): if type(face.Surface) == Part.Cone or type(face.Surface) == Part.Cylinder: @@ -536,6 +532,8 @@ class ObjectDressup: edge = None t = 0 + for cmd in commands: + print(cmd) return Path.Path(commands) @@ -549,6 +547,10 @@ class ObjectDressup: if not obj.Base.Path.Commands: return + if obj.Path: + for cmd in obj.Path.Commands: + print(cmd) + pathData = self.setup(obj) if not pathData: print("execute - no pathData") @@ -572,6 +574,7 @@ class ObjectDressup: print("execute - %d tags" % (len(tags))) tags = pathData.sortedTags(tags) + self.setTags(obj, tags, False) for tag in tags: tag.createSolidsAt(pathData.minZ) @@ -591,9 +594,13 @@ class ObjectDressup: obj.Path = self.createPath(pathData.edges, tags) - def setTags(self, obj, tags): + def setTags(self, obj, tags, update = True): + print("setTags(.....)") + for t in tags: + print(" .... %s" % t.toString()) obj.Tags = [tag.toString() for tag in tags] - self.execute(obj) + if update: + self.execute(obj) def getTags(self, obj): if hasattr(self, 'tags'): From 5e1efba51210ceb6da9f6f00037e41fa360b31d5 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sun, 11 Dec 2016 08:53:51 -0800 Subject: [PATCH 13/27] Fixed generation and edge case where there is no wire to cut the tag. --- .../PathScripts/PathDressupHoldingTags.py | 40 +++++++++---------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index 35763d8b3..d28621430 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -312,12 +312,13 @@ class MapWireToTag: self.addEdge(e) self.tail = tail self.exit = i - shell = self.wire.extrude(FreeCAD.Vector(0, 0, 10)) - face = shell.common(self.tag.solid) + if self.wire: + 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)) + 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): @@ -387,6 +388,7 @@ class PathData: startIndex = 0 for i in range(0, len(self.base.Edges)): edge = self.base.Edges[i] + print(' %d: %.2f' % (i, edge.Length)) if edge.Length == longestEdge.Length: startIndex = i break @@ -401,10 +403,10 @@ class PathData: minLength = min(2. * W, longestEdge.Length) - #print("length=%.2f shortestEdge=%.2f(%.2f) longestEdge=%.2f(%.2f)" % (self.base.Length, shortestEdge.Length, shortestEdge.Length/self.base.Length, longestEdge.Length, longestEdge.Length / self.base.Length)) - #print(" start: index=%-2d count=%d (length=%.2f, distance=%.2f)" % (startIndex, startCount, startEdge.Length, tagDistance)) - #print(" -> lastTagLength=%.2f)" % lastTagLength) - #print(" -> currentLength=%.2f)" % currentLength) + print("length=%.2f shortestEdge=%.2f(%.2f) longestEdge=%.2f(%.2f) minLength=%.2f" % (self.base.Length, shortestEdge.Length, shortestEdge.Length/self.base.Length, longestEdge.Length, longestEdge.Length / self.base.Length, minLength)) + print(" start: index=%-2d count=%d (length=%.2f, distance=%.2f)" % (startIndex, startCount, startEdge.Length, tagDistance)) + print(" -> lastTagLength=%.2f)" % lastTagLength) + print(" -> currentLength=%.2f)" % currentLength) edgeDict = { startIndex: startCount } @@ -419,7 +421,7 @@ class PathData: for (i, count) in edgeDict.iteritems(): edge = self.base.Edges[i] - #print(" %d: %d" % (i, count)) + print(" %d: %d" % (i, count)) #debugMarker(edge.Vertexes[0].Point, 'base', (1.0, 0.0, 0.0), 0.2) #debugMarker(edge.Vertexes[1].Point, 'base', (0.0, 1.0, 0.0), 0.2) distance = (edge.LastParameter - edge.FirstParameter) / count @@ -432,15 +434,15 @@ class PathData: def processEdge(self, index, edge, currentLength, lastTagLength, tagDistance, minLength, edgeDict): tagCount = 0 currentLength += edge.Length - if edge.Length > minLength: + if edge.Length >= minLength: while lastTagLength + tagDistance < currentLength: tagCount += 1 lastTagLength += tagDistance if tagCount > 0: - #print(" index=%d -> count=%d" % (index, tagCount)) + print(" index=%d -> count=%d" % (index, tagCount)) edgeDict[index] = tagCount - #else: - #print(" skipping=%-2d (%.2f)" % (index, edge.Length)) + else: + print(" skipping=%-2d (%.2f)" % (index, edge.Length)) return (currentLength, lastTagLength) @@ -532,8 +534,8 @@ class ObjectDressup: edge = None t = 0 - for cmd in commands: - print(cmd) + #for cmd in commands: + # print(cmd) return Path.Path(commands) @@ -547,10 +549,6 @@ class ObjectDressup: if not obj.Base.Path.Commands: return - if obj.Path: - for cmd in obj.Path.Commands: - print(cmd) - pathData = self.setup(obj) if not pathData: print("execute - no pathData") @@ -595,7 +593,7 @@ class ObjectDressup: obj.Path = self.createPath(pathData.edges, tags) def setTags(self, obj, tags, update = True): - print("setTags(.....)") + print("setTags(%d, %d)" % (len(tags), update)) for t in tags: print(" .... %s" % t.toString()) obj.Tags = [tag.toString() for tag in tags] From ab382ce4362038dea022e1407481a07d0058991b Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sun, 11 Dec 2016 10:49:10 -0800 Subject: [PATCH 14/27] Improved tag height based on obj.Base properties, if they exist. --- src/Mod/Path/PathScripts/PathDressupHoldingTags.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index d28621430..adeae6e98 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 = False +debugDressup = True debugComponents = ['P0', 'P1', 'P2', 'P3'] def debugPrint(comp, msg): @@ -246,13 +246,16 @@ class MapWireToTag: def cleanupEdges(self, edges): # first remove all internal struts + debugEdge(Part.Edge(Part.LineSegment(self.entry, self.exit)), '------> cleanupEdges') inputEdges = copy.copy(edges) plinths = [] for e in edges: + debugEdge(e, '........ cleanup') 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)): + debugEdge(e, '......... X0 %d/%d' % (PathGeom.edgeConnectsTo(e, self.entry), PathGeom.edgeConnectsTo(e, self.exit))) inputEdges.remove(e) if p1.z > p2.z: plinths.append(p2) @@ -262,13 +265,15 @@ class MapWireToTag: # 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): + debugEdge(e, '......... X1') inputEdges.remove(e) continue for p in plinths: if PathGeom.edgeConnectsTo(e, p): + debugEdge(e, '......... X2') inputEdges.remove(e) break - # the remaining edges form walk around the tag + # the remaining edges form a walk around the tag # they need to be ordered and potentially flipped though outputEdges = [] p = self.entry @@ -447,6 +452,8 @@ class PathData: return (currentLength, lastTagLength) def tagHeight(self): + if hasattr(self.obj, 'Base') and hasattr(self.obj.Base, 'StartDepth') and hasattr(self.obj.Base, 'FinalDepth'): + return self.obj.Base.StartDepth - self.obj.Base.FinalDepth return self.maxZ - self.minZ def tagWidth(self): From 6b758e2714650b40630b99f05cb9269e79a0d4be Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Mon, 12 Dec 2016 14:39:30 -0800 Subject: [PATCH 15/27] Added support for vertical paths along the edge of a cylindrical tag. --- .../PathScripts/PathDressupHoldingTags.py | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index adeae6e98..bf7b37f80 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -215,10 +215,12 @@ class Tag: class MapWireToTag: def __init__(self, edge, tag, i): + debugEdge(edge, 'MapWireToTag(%.2f, %.2f, %.2f)' % (i.x, i.y, i.z)) self.tag = tag if PathGeom.pointsCoincide(edge.valueAt(edge.FirstParameter), i): tail = edge self.commands = [] + debugEdge(tail, '.........=') elif PathGeom.pointsCoincide(edge.valueAt(edge.LastParameter), i): debugEdge(edge, '++++++++ .') self.commands = PathGeom.cmdsForEdge(edge) @@ -227,6 +229,7 @@ class MapWireToTag: e, tail = PathGeom.splitEdgeAt(edge, i) debugEdge(e, '++++++++ .') self.commands = PathGeom.cmdsForEdge(e) + debugEdge(tail, '.........-') self.tail = tail self.edges = [] self.entry = i @@ -234,6 +237,7 @@ class MapWireToTag: self.wire = None def addEdge(self, edge): + debugEdge(edge, '..........') if self.wire: self.wire.add(edge) else: @@ -244,6 +248,18 @@ class MapWireToTag: return True, edge.valueAt(edge.FirstParameter) return False, edge.valueAt(edge.LastParameter) + def isEntryOrExitStrut(self, p1, p2): + p = PathGeom.xy(p1) + pEntry0 = PathGeom.xy(self.entry) + pExit0 = PathGeom.xy(self.exit) + # it can only be an entry strut if the strut coincides with the entry point and is above it + if PathGeom.pointsCoincide(p, pEntry0) and p1.z >= self.entry.z and p2.z >= self.entry.z: + return True + if PathGeom.pointsCoincide(p, pExit0) and p1.z >= self.exit.z and p2.z >= self.exit.z: + return True + return False + + def cleanupEdges(self, edges): # first remove all internal struts debugEdge(Part.Edge(Part.LineSegment(self.entry, self.exit)), '------> cleanupEdges') @@ -253,8 +269,9 @@ class MapWireToTag: debugEdge(e, '........ cleanup') 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)): + if PathGeom.pointsCoincide(PathGeom.xy(p1), PathGeom.xy(p2)): + #it's a strut + if not self.isEntryOrExitStrut(p1, p2): debugEdge(e, '......... X0 %d/%d' % (PathGeom.edgeConnectsTo(e, self.entry), PathGeom.edgeConnectsTo(e, self.exit))) inputEdges.remove(e) if p1.z > p2.z: @@ -495,6 +512,16 @@ class ObjectDressup: def generateTags(self, obj, count=None, width=None, height=None, angle=90, spacing=None): return self.pathData.generateTags(obj, count, width, height, angle, spacing) + def isValidTagStartIntersection(self, edge, i): + if PathGeom.pointsCoincide(i, edge.valueAt(edge.LastParameter)): + return False + p1 = edge.valueAt(edge.FirstParameter) + p2 = edge.valueAt(edge.LastParameter) + if PathGeom.pointsCoincide(PathGeom.xy(p1), PathGeom.xy(p2)): + # if this vertical goes up, it can't be the start of a tag intersection + if p1.z < p2.z: + return False + return True def createPath(self, edges, tags): commands = [] @@ -528,7 +555,7 @@ class ObjectDressup: tIndex = (t + lastTag) % len(tags) t += 1 i = tags[tIndex].intersects(edge, edge.FirstParameter) - if i: + if i and self.isValidTagStartIntersection(edge, i): mapper = MapWireToTag(edge, tags[tIndex], i) edge = mapper.tail From c1fc88c6a36b89ebf5fdb0750c1f5cb247a1dbe6 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Mon, 12 Dec 2016 20:53:58 -0800 Subject: [PATCH 16/27] Fixed caching issue. --- src/Mod/Path/PathScripts/PathDressupHoldingTags.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index bf7b37f80..2617134b7 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -348,6 +348,7 @@ class MapWireToTag: class PathData: def __init__(self, obj): + print("PathData(%s)" % obj.Base.Name) self.obj = obj self.wire = PathGeom.wireForPath(obj.Base.Path) self.edges = self.wire.Edges @@ -640,7 +641,7 @@ class ObjectDressup: return self.setup(obj).generateTags(obj, 4) def setup(self, obj): - if False or not hasattr(self, "pathData") or not self.pathData: + if True or not hasattr(self, "pathData") or not self.pathData: try: pathData = PathData(obj) except ValueError: From d6c588e57b08a15e93fee2ecf906f0cd77e4f423 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Mon, 12 Dec 2016 22:41:18 -0800 Subject: [PATCH 17/27] Added resiliancy against vertical path elements. --- .../PathScripts/PathDressupHoldingTags.py | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index 2617134b7..1d43a6039 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -279,17 +279,20 @@ class MapWireToTag: 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): - debugEdge(e, '......... X1') - inputEdges.remove(e) - continue for p in plinths: if PathGeom.edgeConnectsTo(e, p): - debugEdge(e, '......... X2') + debugEdge(e, '......... X1') inputEdges.remove(e) break + # if there are any edges beside a direct edge remaining, the direct edge between + # entry and exit is redundant + if len(inputEdges) > 1: + for e in copy.copy(inputEdges): + if PathGeom.edgeConnectsTo(e, self.entry) and PathGeom.edgeConnectsTo(e, self.exit): + debugEdge(e, '......... X2') + inputEdges.remove(e) + # the remaining edges form a walk around the tag # they need to be ordered and potentially flipped though outputEdges = [] @@ -321,6 +324,13 @@ class MapWireToTag: # print("xxxxxx (%.2f, %.2f, %.2f)" % (p.x, p.y, p.z)) return outputEdges + def shell(self): + shell = self.wire.extrude(FreeCAD.Vector(0, 0, 10)) + redundant = filter(lambda f: f.Area == 0, shell.childShapes()) + if redundant: + return shell.removeShape(redundant) + return shell + def add(self, edge): self.tail = None if self.tag.solid.isInside(edge.valueAt(edge.LastParameter), 0.000001, True): @@ -335,8 +345,7 @@ class MapWireToTag: self.tail = tail self.exit = i if self.wire: - shell = self.wire.extrude(FreeCAD.Vector(0, 0, 10)) - face = shell.common(self.tag.solid) + face = self.shell().common(self.tag.solid) for e,flip in self.cleanupEdges(face.Edges): debugEdge(e, '++++++++ %s' % ('.' if not flip else '@')) From 068dc2d72dbf91ad53fd32bad1ff1fb42fb58a37 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Tue, 13 Dec 2016 11:29:16 -0800 Subject: [PATCH 18/27] Reduced logging. --- .../PathScripts/PathDressupHoldingTags.py | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index 1d43a6039..1f72b0094 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -47,11 +47,10 @@ except AttributeError: def translate(context, text, disambig=None): return QtGui.QApplication.translate(context, text, disambig) -debugDressup = True -debugComponents = ['P0', 'P1', 'P2', 'P3'] +debugDressup = False -def debugPrint(comp, msg): - if debugDressup and comp in debugComponents: +def debugPrint(msg): + if debugDressup: print(msg) def debugEdge(edge, prefix, comp = None): @@ -108,7 +107,7 @@ class Tag: return str((self.x, self.y, self.width, self.height, self.angle, self.enabled)) def __init__(self, x, y, width, height, angle, enabled=True, z=None): - print("Tag(%.2f, %.2f, %.2f, %.2f, %.2f, %d, %s)" % (x, y, width, height, angle/math.pi, enabled, z)) + debugPrint("Tag(%.2f, %.2f, %.2f, %.2f, %.2f, %d, %s)" % (x, y, width, height, angle/math.pi, enabled, z)) self.x = x self.y = y self.z = z @@ -357,7 +356,7 @@ class MapWireToTag: class PathData: def __init__(self, obj): - print("PathData(%s)" % obj.Base.Name) + debugPrint("PathData(%s)" % obj.Base.Name) self.obj = obj self.wire = PathGeom.wireForPath(obj.Base.Path) self.edges = self.wire.Edges @@ -420,7 +419,7 @@ class PathData: startIndex = 0 for i in range(0, len(self.base.Edges)): edge = self.base.Edges[i] - print(' %d: %.2f' % (i, edge.Length)) + debugPrint(' %d: %.2f' % (i, edge.Length)) if edge.Length == longestEdge.Length: startIndex = i break @@ -435,10 +434,10 @@ class PathData: minLength = min(2. * W, longestEdge.Length) - print("length=%.2f shortestEdge=%.2f(%.2f) longestEdge=%.2f(%.2f) minLength=%.2f" % (self.base.Length, shortestEdge.Length, shortestEdge.Length/self.base.Length, longestEdge.Length, longestEdge.Length / self.base.Length, minLength)) - print(" start: index=%-2d count=%d (length=%.2f, distance=%.2f)" % (startIndex, startCount, startEdge.Length, tagDistance)) - print(" -> lastTagLength=%.2f)" % lastTagLength) - print(" -> currentLength=%.2f)" % currentLength) + debugPrint("length=%.2f shortestEdge=%.2f(%.2f) longestEdge=%.2f(%.2f) minLength=%.2f" % (self.base.Length, shortestEdge.Length, shortestEdge.Length/self.base.Length, longestEdge.Length, longestEdge.Length / self.base.Length, minLength)) + debugPrint(" start: index=%-2d count=%d (length=%.2f, distance=%.2f)" % (startIndex, startCount, startEdge.Length, tagDistance)) + debugPrint(" -> lastTagLength=%.2f)" % lastTagLength) + debugPrint(" -> currentLength=%.2f)" % currentLength) edgeDict = { startIndex: startCount } @@ -453,7 +452,7 @@ class PathData: for (i, count) in edgeDict.iteritems(): edge = self.base.Edges[i] - print(" %d: %d" % (i, count)) + debugPrint(" %d: %d" % (i, count)) #debugMarker(edge.Vertexes[0].Point, 'base', (1.0, 0.0, 0.0), 0.2) #debugMarker(edge.Vertexes[1].Point, 'base', (0.0, 1.0, 0.0), 0.2) distance = (edge.LastParameter - edge.FirstParameter) / count @@ -471,10 +470,10 @@ class PathData: tagCount += 1 lastTagLength += tagDistance if tagCount > 0: - print(" index=%d -> count=%d" % (index, tagCount)) + debugPrint(" index=%d -> count=%d" % (index, tagCount)) edgeDict[index] = tagCount else: - print(" skipping=%-2d (%.2f)" % (index, edge.Length)) + debugPrint(" skipping=%-2d (%.2f)" % (index, edge.Length)) return (currentLength, lastTagLength) @@ -606,7 +605,7 @@ class ObjectDressup: tags = [Tag.FromString(tag) for tag in obj.Tags] else: print("execute - default tags") - tags = self.generateTags(obj, 2.) + tags = self.generateTags(obj, 4.) if not tags: print("execute - no tags") From f232096eb4689fe85e7c5f71aa290cb1f64c6723 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Thu, 15 Dec 2016 15:47:52 -0800 Subject: [PATCH 19/27] Fixed build and tests. --- .../Path/PathTests/TestPathDressupHoldingTags.py | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py b/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py index d332a5b62..516b35e7d 100644 --- a/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py +++ b/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py @@ -50,27 +50,21 @@ class TestHoldingTags(PathTestBase): def test01(self): - """Verify solid and core for a 90 degree tag are identical cylinders.""" + """Verify solid for a 90 degree tag is a cylinder.""" tag = Tag(100, 200, 4, 5, 90, True) tag.createSolidsAt(17) self.assertIsNotNone(tag.solid) self.assertCylinderAt(tag.solid, Vector(100, 200, 17), 2, 5) - self.assertIsNotNone(tag.core) - self.assertCylinderAt(tag.core, Vector(100, 200, 17), 2, 5) - def test02(self): - """Verify trapezoidal tag has a cone shape with a lid, and cylinder core.""" + """Verify trapezoidal tag has a cone shape with a lid.""" tag = Tag(0, 0, 18, 5, 45, True) tag.createSolidsAt(0) self.assertIsNotNone(tag.solid) self.assertConeAt(tag.solid, Vector(0,0,0), 9, 4, 5) - self.assertIsNotNone(tag.core) - self.assertCylinderAt(tag.core, Vector(0,0,0), 4, 5) - def test03(self): """Verify pointy cone shape of tag with pointy end if width, angle and height match up.""" tag = Tag(0, 0, 10, 5, 45, True) @@ -78,8 +72,6 @@ class TestHoldingTags(PathTestBase): self.assertIsNotNone(tag.solid) self.assertConeAt(tag.solid, Vector(0,0,0), 5, 0, 5) - self.assertIsNone(tag.core) - def test04(self): """Verify height adjustment if tag isn't wide eough for angle.""" tag = Tag(0, 0, 5, 17, 60, True) @@ -87,5 +79,3 @@ class TestHoldingTags(PathTestBase): self.assertIsNotNone(tag.solid) self.assertConeAt(tag.solid, Vector(0,0,0), 2.5, 0, 2.5 * math.tan((60/180.0)*math.pi)) - self.assertIsNone(tag.core) - From 27b71ab1ae026ddb72e906bf12ce728cf4298baf Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Thu, 15 Dec 2016 18:22:09 -0800 Subject: [PATCH 20/27] Maintaining rapid commands. --- .../PathScripts/PathDressupHoldingTags.py | 41 +++++++++++++++---- src/Mod/Path/PathScripts/PathGeom.py | 11 +++-- 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index 1f72b0094..31d474f44 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -53,12 +53,10 @@ def debugPrint(msg): if debugDressup: print(msg) -def debugEdge(edge, prefix, comp = None): +def debugEdge(edge, prefix, force = False): pf = edge.valueAt(edge.FirstParameter) 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)) - elif debugDressup: + if force or 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): @@ -354,11 +352,34 @@ class MapWireToTag: def mappingComplete(self): return self.complete +class _RapidEdges: + def __init__(self, rapid): + self.rapid = rapid + + def isRapid(self, edge, removeIfFound=True): + if type(edge.Curve) == Part.Line or type(edge.Curve) == Part.LineSegment: + v0 = edge.Vertexes[0] + v1 = edge.Vertexes[1] + for r in self.rapid: + r0 = r.Vertexes[0] + r1 = r.Vertexes[1] + if PathGeom.isRoughly(r0.X, v0.X) and PathGeom.isRoughly(r0.Y, v0.Y) and PathGeom.isRoughly(r0.Z, v0.Z) and PathGeom.isRoughly(r1.X, v1.X) and PathGeom.isRoughly(r1.Y, v1.Y) and PathGeom.isRoughly(r1.Z, v1.Z): + if removeIfFound: + self.rapid.remove(r) + return True + return False + + def p(self): + print('rapid:') + for r in self.rapid: + debugEdge(r, ' ', True) + class PathData: def __init__(self, obj): debugPrint("PathData(%s)" % obj.Base.Name) self.obj = obj - self.wire = PathGeom.wireForPath(obj.Base.Path) + self.wire, rapid = PathGeom.wireForPath(obj.Base.Path) + self.rapid = _RapidEdges(rapid) self.edges = self.wire.Edges self.base = self.findBottomWire(self.edges) # determine overall length @@ -532,7 +553,7 @@ class ObjectDressup: return False return True - def createPath(self, edges, tags): + def createPath(self, edges, tags, rapid): commands = [] lastEdge = 0 lastTag = 0 @@ -573,7 +594,11 @@ class ObjectDressup: # gone through all tags, consume edge and move on if edge: debugEdge(edge, '++++++++') - commands.extend(PathGeom.cmdsForEdge(edge)) + if rapid.isRapid(edge, True): + v = edge.Vertexes[1] + commands.append(Path.Command('G0', {'X': v.X, 'Y': v.Y, 'Z': v.Z})) + else: + commands.extend(PathGeom.cmdsForEdge(edge)) edge = None t = 0 @@ -633,7 +658,7 @@ class ObjectDressup: self.fingerprint = [tag.toString() for tag in tags] self.tags = tags - obj.Path = self.createPath(pathData.edges, tags) + obj.Path = self.createPath(pathData.edges, tags, pathData.rapid) def setTags(self, obj, tags, update = True): print("setTags(%d, %d)" % (len(tags), update)) diff --git a/src/Mod/Path/PathScripts/PathGeom.py b/src/Mod/Path/PathScripts/PathGeom.py index 1b6d7369e..4eaf54510 100644 --- a/src/Mod/Path/PathScripts/PathGeom.py +++ b/src/Mod/Path/PathScripts/PathGeom.py @@ -63,7 +63,7 @@ class Side: class PathGeom: """Class to transform Path Commands into Edges and Wire and back again. The interface might eventuallly become part of Path itself.""" - CmdMoveFast = ['G0', 'G00'] + CmdMoveRapid = ['G0', 'G00'] CmdMoveStraight = ['G1', 'G01'] CmdMoveCW = ['G2', 'G02'] CmdMoveCCW = ['G3', 'G03'] @@ -195,7 +195,7 @@ class PathGeom: Returns an Edge representing the given command, assuming a given startPoint.""" endPoint = cls.commandEndPoint(cmd, startPoint) - if (cmd.Name in cls.CmdMoveStraight) or (cmd.Name in cls.CmdMoveFast): + if (cmd.Name in cls.CmdMoveStraight) or (cmd.Name in cls.CmdMoveRapid): if cls.pointsCoincide(startPoint, endPoint): return None return Part.Edge(Part.LineSegment(startPoint, endPoint)) @@ -247,13 +247,16 @@ class PathGeom: """(path, [startPoint=Vector(0,0,0)]) Returns a wire representing all move commands found in the given path.""" edges = [] + rapid = [] if hasattr(path, "Commands"): for cmd in path.Commands: edge = cls.edgeForCmd(cmd, startPoint) if edge: + if cmd.Name in cls.CmdMoveRapid: + rapid.append(edge) edges.append(edge) startPoint = cls.commandEndPoint(cmd, startPoint) - return Part.Wire(edges) + return (Part.Wire(edges), rapid) @classmethod def wiresForPath(cls, path, startPoint = FreeCAD.Vector(0, 0, 0)): @@ -266,7 +269,7 @@ class PathGeom: if cmd.Name in cls.CmdMove: edges.append(cls.edgeForCmd(cmd, startPoint)) startPoint = cls.commandEndPoint(cmd, startPoint) - elif cmd.Name in cls.CmdMoveFast: + elif cmd.Name in cls.CmdMoveRapid: wires.append(Part.Wire(edges)) edges = [] startPoint = cls.commandEndPoint(cmd, startPoint) From cb85072bbd34554dd6f110e3f5e7b872b9cc21ad Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Fri, 30 Dec 2016 19:16:27 -0800 Subject: [PATCH 21/27] Fixed alignment issue and unit tests. The trick is really to over-extend edges before creationg shapes for the common operation, and trying to avoid alignment of the edge with the cone's seam. --- .../Path/Gui/Resources/panels/DogboneEdit.ui | 6 +- .../Gui/Resources/panels/HoldingTagsEdit.ui | 239 +++------ .../PathScripts/PathDressupHoldingTags.py | 473 ++++++++++-------- src/Mod/Path/PathScripts/PathGeom.py | 12 + .../PathTests/TestPathDressupHoldingTags.py | 8 +- src/Mod/Path/PathTests/TestPathGeom.py | 4 +- 6 files changed, 355 insertions(+), 387 deletions(-) diff --git a/src/Mod/Path/Gui/Resources/panels/DogboneEdit.ui b/src/Mod/Path/Gui/Resources/panels/DogboneEdit.ui index 6b7f57f88..d628b7e3d 100644 --- a/src/Mod/Path/Gui/Resources/panels/DogboneEdit.ui +++ b/src/Mod/Path/Gui/Resources/panels/DogboneEdit.ui @@ -6,7 +6,7 @@ 0 0 - 352 + 376 387 @@ -27,8 +27,8 @@ 0 0 - 334 - 340 + 358 + 333 diff --git a/src/Mod/Path/Gui/Resources/panels/HoldingTagsEdit.ui b/src/Mod/Path/Gui/Resources/panels/HoldingTagsEdit.ui index 465b0ad6e..d8289aca7 100644 --- a/src/Mod/Path/Gui/Resources/panels/HoldingTagsEdit.ui +++ b/src/Mod/Path/Gui/Resources/panels/HoldingTagsEdit.ui @@ -6,8 +6,8 @@ 0 0 - 352 - 387 + 363 + 530 @@ -27,8 +27,8 @@ 0 0 - 334 - 311 + 345 + 476 @@ -36,199 +36,118 @@ - - - - 0 - 0 - - - - true - - - 80 - - - false - - - - X + + + + QFormLayout::AllNonFixedFieldsGrow - - - - Y - - - - - Width - - - - - Height - - - - - Angle - - - - - - - - - + + - Delete + Width - - - - Disable + + + + <html><head/><body><p>Specify the resulting width of tags at the base.</p><p>The initial default width is based on the longest edge found in the base path.</p></body></html> - - + + - Add + Height + + + + + + + <html><head/><body><p>Height of holding tags.</p></body></html> + + + + + + + Angle + + + + + + + <html><head/><body><p>Angle of ascend and descend of the tool for the holding tag cutout.</p><p><br/></p><p>If the angle is too flat for the given width to reach the specified height the resulting tag will have a triangular shape and not as high as specified.</p></body></html> - - - - - - 0 - 0 - 334 - 311 - - - - Generate - - - - - - Width - - - - - + + - <html><head/><body><p>Width of each tag.</p></body></html> + <html><head/><body><p>List of current tags.</p><p>Edit coordinates to move tag or invoker snapper tool.</p></body></html> - - - - Height - - - - - - - <html><head/><body><p>The height of the holding tag measured from the bottom of the path. By default this is set to the (estimated) height of the path.</p></body></html> - - - - - - - true - - - Angle - - - - - - - true - - - <html><head/><body><p>Angle of tag walls.</p></body></html> - - - - - - - QDialogButtonBox::Apply|QDialogButtonBox::Ok - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - Layout - - + + + - + + + false + - Count + Delete - - - <html><head/><body><p>Enter the number of tags you wish to have.</p><p><br/></p><p>Note that sometimes it's necessary to enter a larger than desired count number and disable the ones tags you don't want in order to get the holding tag layout you want.</p></body></html> + + + Add... + + + + + + + + + + Auto Generate + + + + + + + + + false + + + Replace All - Spacing + Number of Tags + + + Qt::AlignCenter - - - - - - - Auto Apply - - - diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index 31d474f44..3be16f87e 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -29,6 +29,9 @@ import Part import copy import math +import cProfile +import time + from PathScripts import PathUtils from PathScripts.PathGeom import * from PySide import QtCore, QtGui @@ -57,7 +60,11 @@ def debugEdge(edge, prefix, force = False): pf = edge.valueAt(edge.FirstParameter) pl = edge.valueAt(edge.LastParameter) if force or 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)) + if type(edge.Curve) == Part.Line or type(edge.Curve) == Part.LineSegment: + 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)) + else: + pm = edge.valueAt((edge.FirstParameter+edge.LastParameter)/2) + print("%s %s((%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f))" % (prefix, type(edge.Curve), pf.x, pf.y, pf.z, pm.x, pm.y, pm.z, pl.x, pl.y, pl.z)) def debugMarker(vector, label, color = None, radius = 0.5): if debugDressup: @@ -104,18 +111,18 @@ class Tag: def toString(self): return str((self.x, self.y, self.width, self.height, self.angle, self.enabled)) - def __init__(self, x, y, width, height, angle, enabled=True, z=None): - debugPrint("Tag(%.2f, %.2f, %.2f, %.2f, %.2f, %d, %s)" % (x, y, width, height, angle/math.pi, enabled, z)) + def __init__(self, x, y, width, height, angle, enabled=True): + debugPrint("Tag(%.2f, %.2f, %.2f, %.2f, %.2f, %d)" % (x, y, width, height, angle/math.pi, enabled)) self.x = x self.y = y - self.z = z self.width = math.fabs(width) self.height = math.fabs(height) self.actualHeight = self.height self.angle = math.fabs(angle) self.enabled = enabled - if z is not None: - self.createSolidsAt(z) + + def fullWidth(self): + return 2 * self.toolRadius + self.width def originAt(self, z): return FreeCAD.Vector(self.x, self.y, z) @@ -126,14 +133,16 @@ class Tag: def top(self): return self.z + self.actualHeight - def createSolidsAt(self, z): + def createSolidsAt(self, z, R): self.z = z - r1 = self.width / 2 + self.toolRadius = R + r1 = self.fullWidth() / 2 self.r1 = r1 self.r2 = r1 height = self.height if self.angle == 90 and height > 0: self.solid = Part.makeCylinder(r1, height) + print("Part.makeCone(%f, %f)" % (r1, height)) elif self.angle > 0.0 and height > 0.0: tangens = math.tan(math.radians(self.angle)) dr = height / tangens @@ -144,10 +153,17 @@ class Tag: height = r1 * tangens self.actualHeight = height self.r2 = r2 + print("Part.makeCone(%f, %f, %f)" % (r1, r2, height)) self.solid = Part.makeCone(r1, r2, height) else: # degenerated case - no tag + print("Part.makeSphere(%f / 10000)" % (r1)) self.solid = Part.makeSphere(r1 / 10000) + if not R == 0: # testing is easier if the solid is not rotated + angle = -PathGeom.getAngle(self.originAt(0)) * 180 / math.pi + print("solid.rotate(%f)" % angle) + self.solid.rotate(FreeCAD.Vector(0,0,0), FreeCAD.Vector(0,0,1), angle) + print("solid.translate(%s)" % self.originAt(z)) self.solid.translate(self.originAt(z)) def filterIntersections(self, pts, face): @@ -206,8 +222,9 @@ class Tag: return None 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)) + if self.enabled: + 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: @@ -231,105 +248,131 @@ class MapWireToTag: self.edges = [] self.entry = i self.complete = False - self.wire = None + self.haveProblem = False def addEdge(self, edge): debugEdge(edge, '..........') - if self.wire: - self.wire.add(edge) - else: - self.wire = Part.Wire(edge) + self.edges.append(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 isEntryOrExitStrut(self, p1, p2): - p = PathGeom.xy(p1) - pEntry0 = PathGeom.xy(self.entry) - pExit0 = PathGeom.xy(self.exit) - # it can only be an entry strut if the strut coincides with the entry point and is above it - if PathGeom.pointsCoincide(p, pEntry0) and p1.z >= self.entry.z and p2.z >= self.entry.z: - return True - if PathGeom.pointsCoincide(p, pExit0) and p1.z >= self.exit.z and p2.z >= self.exit.z: - return True - return False + def isEntryOrExitStrut(self, e): + p1 = e.valueAt(e.FirstParameter) + p2 = e.valueAt(e.LastParameter) + if PathGeom.pointsCoincide(p1, self.entry) and p2.z >= self.entry.z: + return 1 + if PathGeom.pointsCoincide(p2, self.entry) and p1.z >= self.entry.z: + return 1 + if PathGeom.pointsCoincide(p1, self.exit) and p2.z >= self.exit.z: + return 2 + if PathGeom.pointsCoincide(p2, self.exit) and p1.z >= self.exit.z: + return 2 + return 0 - - def cleanupEdges(self, edges): - # first remove all internal struts - debugEdge(Part.Edge(Part.LineSegment(self.entry, self.exit)), '------> cleanupEdges') - inputEdges = copy.copy(edges) - plinths = [] + def cleanupEdges(self, edges, baseEdge): + # want to remove all edges from the wire itself, and all internal struts + print("+cleanupEdges") + print(" base:") + debugEdge(baseEdge, ' ', True) + print(" edges:") for e in edges: - debugEdge(e, '........ cleanup') - p1 = e.valueAt(e.FirstParameter) - p2 = e.valueAt(e.LastParameter) - if PathGeom.pointsCoincide(PathGeom.xy(p1), PathGeom.xy(p2)): - #it's a strut - if not self.isEntryOrExitStrut(p1, p2): - debugEdge(e, '......... X0 %d/%d' % (PathGeom.edgeConnectsTo(e, self.entry), 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 - for e in copy.copy(inputEdges): - for p in plinths: - if PathGeom.edgeConnectsTo(e, p): - debugEdge(e, '......... X1') - inputEdges.remove(e) - break - # if there are any edges beside a direct edge remaining, the direct edge between - # entry and exit is redundant - if len(inputEdges) > 1: - for e in copy.copy(inputEdges): - if PathGeom.edgeConnectsTo(e, self.entry) and PathGeom.edgeConnectsTo(e, self.exit): - debugEdge(e, '......... X2') - inputEdges.remove(e) + debugEdge(e, ' ', True) + print(":") - # the remaining edges form a walk around the tag - # they need to be ordered and potentially flipped though + haveEntry = False + for e in copy.copy(edges): + if PathGeom.edgesMatch(e, baseEdge): + debugEdge(e, '......... X0') + edges.remove(e) + elif self.isStrut(e): + typ = self.isEntryOrExitStrut(e) + debugEdge(e, '......... |%d' % typ) + if 0 == typ: # neither entry nor exit + debugEdge(e, '......... X1') + edges.remove(e) + elif 1 == typ: + haveEntry = True + + print("entry(%.2f, %.2f, %.2f), exit(%.2f, %.2f, %.2f)" % (self.entry.x, self.entry.y, self.entry.z, self.exit.x, self.exit.y, self.exit.z)) + # the remaininng edges for the path from xy(baseEdge) along the tags surface outputEdges = [] - p = self.entry - lastP = p - while inputEdges: - for e in inputEdges: + p0 = baseEdge.valueAt(baseEdge.FirstParameter) + ignoreZ = False + if not haveEntry: + ignoreZ = True + p0 = PathGeom.xy(p0) + lastP = p0 + while edges: + print("(%.2f, %.2f, %.2f) %d %d" % (p0.x, p0.y, p0.z, haveEntry, ignoreZ)) + for e in edges: 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 + if PathGeom.pointsCoincide(PathGeom.xy(p1) if ignoreZ else p1, p0): + outputEdges.append((e, False)) + edges.remove(e) + lastP = None + ignoreZ = False + p0 = p2 debugEdge(e, ">>>>> no flip") break - elif PathGeom.pointsCoincide(p2, p): - outputEdges.append((e,True)) - inputEdges.remove(e) - lastP = p - p = p1 + elif PathGeom.pointsCoincide(PathGeom.xy(p2) if ignoreZ else p2, p0): + outputEdges.append((e, True)) + edges.remove(e) + lastP = None + ignoreZ = False + p0 = 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)) + else: + debugEdge(e, "<<<<< (%.2f, %.2f, %.2f)" % (p0.x, p0.y, p0.z)) + if lastP == p0: + raise ValueError("No connection to %s" % (p0)) + elif lastP: + print("xxxxxx (%.2f, %.2f, %.2f) (%.2f, %.2f, %.2f)" % (p0.x, p0.y, p0.z, lastP.x, lastP.y, lastP.z)) + else: + print("xxxxxx (%.2f, %.2f, %.2f) -" % (p0.x, p0.y, p0.z)) + lastP = p0 + print("-cleanupEdges") return outputEdges - def shell(self): - shell = self.wire.extrude(FreeCAD.Vector(0, 0, 10)) - redundant = filter(lambda f: f.Area == 0, shell.childShapes()) - if redundant: - return shell.removeShape(redundant) - return shell + def isStrut(self, edge): + p1 = PathGeom.xy(edge.valueAt(edge.FirstParameter)) + p2 = PathGeom.xy(edge.valueAt(edge.LastParameter)) + return PathGeom.pointsCoincide(p1, p2) + + def cmdsForEdge(self, edge): + cmds = [] + + # OCC doesn't like it very much if the shapes align with each other. So if we have a slightly + # extended edge for the last edge in list we'll use that instead for stable results. + if PathGeom.pointsCoincide(edge.valueAt(edge.FirstParameter), self.lastEdge.valueAt(self.lastEdge.FirstParameter)): + shell = self.lastEdge.extrude(FreeCAD.Vector(0, 0, self.tag.height + 1)) + else: + shell = edge.extrude(FreeCAD.Vector(0, 0, self.tag.height + 1)) + shape = shell.common(self.tag.solid) + + if not shape.Edges: + self.haveProblem = True + + for e,flip in self.cleanupEdges(shape.Edges, edge): + debugEdge(e, '++++++++ %s' % ('.' if not flip else '@')) + cmds.extend(PathGeom.cmdsForEdge(e, flip, False)) + return cmds + + def commandsForEdges(self): + commands = [] + for e in self.edges: + if self.isStrut(e): + continue + commands.extend(self.cmdsForEdge(e)) + return commands def add(self, edge): self.tail = None + self.lastEdge = edge # see cmdsForEdge if self.tag.solid.isInside(edge.valueAt(edge.LastParameter), 0.000001, True): self.addEdge(edge) else: @@ -341,13 +384,8 @@ class MapWireToTag: self.addEdge(e) self.tail = tail self.exit = i - if self.wire: - face = self.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 + self.commands.extend(self.commandsForEdges()) def mappingComplete(self): return self.complete @@ -399,9 +437,11 @@ class PathData: 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 - maxZ = minZ + minZ = 99999999999 + maxZ = -99999999999 for e in edges: + if self.rapid.isRapid(e): + continue for v in e.Vertexes: if v.Point.z < minZ: minZ = v.Point.z @@ -476,10 +516,11 @@ class PathData: debugPrint(" %d: %d" % (i, count)) #debugMarker(edge.Vertexes[0].Point, 'base', (1.0, 0.0, 0.0), 0.2) #debugMarker(edge.Vertexes[1].Point, 'base', (0.0, 1.0, 0.0), 0.2) - distance = (edge.LastParameter - edge.FirstParameter) / count - for j in range(0, count): - tag = edge.Curve.value((j+0.5) * distance) - tags.append(Tag(tag.x, tag.y, W, H, angle, True)) + if 0 != count: + distance = (edge.LastParameter - edge.FirstParameter) / count + for j in range(0, count): + tag = edge.Curve.value((j+0.5) * distance) + tags.append(Tag(tag.x, tag.y, W, H, angle, True)) return tags @@ -554,6 +595,7 @@ class ObjectDressup: return True def createPath(self, edges, tags, rapid): + print("createPath") commands = [] lastEdge = 0 lastTag = 0 @@ -562,10 +604,11 @@ class ObjectDressup: inters = None edge = None + self.mappers = [] mapper = None while edge or lastEdge < len(edges): - #print("------- lastEdge = %d/%d.%d/%d" % (lastEdge, lastTag, t, len(tags))) + debugPrint("------- 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))) @@ -587,6 +630,7 @@ class ObjectDressup: i = tags[tIndex].intersects(edge, edge.FirstParameter) if i and self.isValidTagStartIntersection(edge, i): mapper = MapWireToTag(edge, tags[tIndex], i) + self.mappers.append(mapper) edge = mapper.tail @@ -606,8 +650,17 @@ class ObjectDressup: # print(cmd) return Path.Path(commands) + def problems(self): + return filter(lambda m: m.haveProblem, self.mappers) def execute(self, obj): + #pr = cProfile.Profile() + #pr.enable() + self.doExecute(obj) + #pr.disable() + #pr.print_stats() + + def doExecute(self,obj): if not obj.Base: return if not obj.Base.isDerivedFrom("Path::Feature"): @@ -642,7 +695,7 @@ class ObjectDressup: tags = pathData.sortedTags(tags) self.setTags(obj, tags, False) for tag in tags: - tag.createSolidsAt(pathData.minZ) + tag.createSolidsAt(pathData.minZ, self.toolRadius) tagID = 0 for tag in tags: @@ -653,12 +706,13 @@ class ObjectDressup: if tag.angle != 90: debugCone(tag.originAt(pathData.minZ), tag.r1, tag.r2, tag.actualHeight, "tag-%02d" % tagID) else: - debugCylinder(tag.originAt(pathData.minZ), tag.width/2, tag.actualHeight, "tag-%02d" % tagID) + debugCylinder(tag.originAt(pathData.minZ), tag.fullWidth()/2, tag.actualHeight, "tag-%02d" % tagID) self.fingerprint = [tag.toString() for tag in tags] self.tags = tags obj.Path = self.createPath(pathData.edges, tags, pathData.rapid) + print("execute - done") def setTags(self, obj, tags, update = True): print("setTags(%d, %d)" % (len(tags), update)) @@ -681,32 +735,16 @@ class ObjectDressup: FreeCAD.Console.PrintError(translate("PathDressup_HoldingTags", "Cannot insert holding tags for this path - please select a Profile path\n")) return None - ## setup the object's properties, in case they're not set yet - #obj.Count = self.tagCount(obj) - #obj.Angle = self.tagAngle(obj) - #obj.Blacklist = self.tagBlacklist(obj) - - # if the heigt isn't set, use the height of the path - #if not hasattr(obj, "Height") or not obj.Height: - # obj.Height = pathData.maxZ - pathData.minZ - # try and take an educated guess at the width - #if not hasattr(obj, "Width") or not obj.Width: - # width = sorted(pathData.base.Edges, key=lambda e: -e.Length)[0].Length / 10 - # while obj.Count > len([e for e in pathData.base.Edges if e.Length > 3*width]): - # width = widht / 2 - # obj.Width = width - - # and the tool radius, not sure yet if it's needed - #self.toolRadius = 5 - #toolLoad = PathUtils.getLastToolLoad(obj) - #if toolLoad is None or toolLoad.ToolNumber == 0: - # self.toolRadius = 5 - #else: - # tool = PathUtils.getTool(obj, toolLoad.ToolNumber) - # if not tool or tool.Diameter == 0: - # self.toolRadius = 5 - # else: - # self.toolRadius = tool.Diameter / 2 + self.toolRadius = 5 + toolLoad = PathUtils.getLastToolLoad(obj) + if toolLoad is None or toolLoad.ToolNumber == 0: + self.toolRadius = 5 + else: + tool = PathUtils.getTool(obj, toolLoad.ToolNumber) + if not tool or tool.Diameter == 0: + self.toolRadius = 5 + else: + self.toolRadius = tool.Diameter / 2 self.pathData = pathData return self.pathData @@ -723,8 +761,8 @@ class ObjectDressup: return self.pathData.pathLength() class TaskPanel: - DataTag = QtCore.Qt.ItemDataRole.UserRole - DataValue = QtCore.Qt.ItemDataRole.DisplayRole + DataX = QtCore.Qt.ItemDataRole.UserRole + DataY = QtCore.Qt.ItemDataRole.UserRole + 1 def __init__(self, obj): self.obj = obj @@ -738,6 +776,7 @@ class TaskPanel: FreeCADGui.Selection.removeObserver(self.s) def accept(self): + self.getFields() FreeCAD.ActiveDocument.commitTransaction() FreeCADGui.ActiveDocument.resetEdit() FreeCADGui.Control.closeDialog() @@ -750,40 +789,38 @@ class TaskPanel: # install the function mode resident FreeCADGui.Selection.addObserver(self.s) - def tableWidgetItem(self, tag, val): - item = QtGui.QTableWidgetItem() - item.setTextAlignment(QtCore.Qt.AlignRight) - item.setData(self.DataTag, tag) - item.setData(self.DataValue, val) - return item - def getFields(self): + width = self.form.dsbWidth.value() + height = self.form.dsbHeight.value() + angle = self.form.dsbAngle.value() tags = [] - for row in range(0, self.form.twTags.rowCount()): - x = self.form.twTags.item(row, 0).data(self.DataValue) - y = self.form.twTags.item(row, 1).data(self.DataValue) - w = self.form.twTags.item(row, 2).data(self.DataValue) - h = self.form.twTags.item(row, 3).data(self.DataValue) - a = self.form.twTags.item(row, 4).data(self.DataValue) - tags.append(Tag(x, y, w, h, a, True)) - print("getFields: %d" % (len(tags))) + for i in range(0, self.form.lwTags.count()): + item = self.form.lwTags.item(i) + enabled = item.checkState() == QtCore.Qt.CheckState.Checked + x = item.data(self.DataX) + y = item.data(self.DataY) + tags.append(Tag(x, y, width, height, angle, enabled)) self.obj.Proxy.setTags(self.obj, tags) - def updateTags(self): + def updateTagsView(self): self.tags = self.obj.Proxy.getTags(self.obj) - self.form.twTags.blockSignals(True) - self.form.twTags.setSortingEnabled(False) - self.form.twTags.clearSpans() - print("updateTags: %d" % (len(self.tags))) - self.form.twTags.setRowCount(len(self.tags)) - for row, tag in enumerate(self.tags): - self.form.twTags.setItem(row, 0, self.tableWidgetItem(tag, tag.x)) - self.form.twTags.setItem(row, 1, self.tableWidgetItem(tag, tag.y)) - self.form.twTags.setItem(row, 2, self.tableWidgetItem(tag, tag.width)) - self.form.twTags.setItem(row, 3, self.tableWidgetItem(tag, tag.height)) - self.form.twTags.setItem(row, 4, self.tableWidgetItem(tag, tag.angle)) - self.form.twTags.setSortingEnabled(True) - self.form.twTags.blockSignals(False) + print("updateTagsView: %d" % (len(self.tags))) + self.form.lwTags.blockSignals(True) + self.form.lwTags.clear() + for tag in self.tags: + lbl = "(%.2f, %.2f)" % (tag.x, tag.y) + item = QtGui.QListWidgetItem(lbl) + item.setData(self.DataX, tag.x) + item.setData(self.DataY, tag.y) + if tag.enabled: + item.setCheckState(QtCore.Qt.CheckState.Checked) + else: + item.setCheckState(QtCore.Qt.CheckState.Unchecked) + flags = QtCore.Qt.ItemFlag.ItemIsSelectable + flags |= QtCore.Qt.ItemFlag.ItemIsEnabled | QtCore.Qt.ItemFlag.ItemIsUserCheckable + item.setFlags(flags) + self.form.lwTags.addItem(item) + self.form.lwTags.blockSignals(False) def cleanupUI(self): print("cleanupUI") @@ -792,67 +829,50 @@ class TaskPanel: if obj.Name.startswith('tag'): FreeCAD.ActiveDocument.removeObject(obj.Name) - def updateUI(self): - print("updateUI") - self.cleanupUI() - self.getFields() - if debugDressup: - FreeCAD.ActiveDocument.recompute() - - - def whenApplyClicked(self): - print("whenApplyClicked") + def generateNewTags(self): + print("generateNewTags") self.cleanupUI() count = self.form.sbCount.value() - spacing = self.form.dsbSpacing.value() width = self.form.dsbWidth.value() height = self.form.dsbHeight.value() angle = self.form.dsbAngle.value() - tags = self.obj.Proxy.generateTags(self.obj, count, width, height, angle, spacing * 0.99) + tags = self.obj.Proxy.generateTags(self.obj, count, width, height, angle) self.obj.Proxy.setTags(self.obj, tags) - self.updateTags() - if debugDressup: - # this causes a big of an echo and a double click on the spin buttons, don't know why though - FreeCAD.ActiveDocument.recompute() + self.updateTagsView() + #if debugDressup: + # # this causes a big of an echo and a double click on the spin buttons, don't know why though + # FreeCAD.ActiveDocument.recompute() - def autoApply(self): - print("autoApply") - if self.form.cbAutoApply.checkState() == QtCore.Qt.CheckState.Checked: - self.whenApplyClicked() +# def autoApply(self): +# print("autoApply") +# if self.form.cbAutoApply.checkState() == QtCore.Qt.CheckState.Checked: +# self.whenApplyClicked() - def updateTagSpacing(self, count): - print("updateTagSpacing") - if count == 0: - spacing = 0 - else: - spacing = self.pathLength / count - self.form.dsbSpacing.blockSignals(True) - self.form.dsbSpacing.setValue(spacing) - self.form.dsbSpacing.blockSignals(False) + def updateModel(self): + self.getFields() + self.updateTagsView() + #FreeCAD.ActiveDocument.recompute() def whenCountChanged(self): print("whenCountChanged") - self.updateTagSpacing(self.form.sbCount.value()) - self.autoApply() + count = self.form.sbCount.value() + self.form.pbGenerate.setEnabled(count) - def whenSpacingChanged(self): - print("whenSpacingChanged") - if self.form.dsbSpacing.value() == 0: - count = 0 - else: - count = int(self.pathLength / self.form.dsbSpacing.value()) - self.form.sbCount.blockSignals(True) - self.form.sbCount.setValue(count) - self.form.sbCount.blockSignals(False) - self.autoApply() + def whenTagSelectionChanged(self): + print('whenTagSelectionChanged') + item = self.form.lwTags.currentItem() + self.form.pbDelete.setEnabled(not item is None) - def whenOkClicked(self): - print("whenOkClicked") - self.whenApplyClicked() - self.form.toolBox.setCurrentWidget(self.form.tbpTags) + def deleteSelectedTag(self): + item = self.form.lwTags.currentItem() + x = item.data(self.DataX) + y = item.data(self.DataY) + tags = filter(lambda t: t.x != x or t.y != y, self.tags) + self.obj.Proxy.setTags(self.obj, tags) + self.updateTagsView() def setupSpinBox(self, widget, val, decimals = 2): widget.setMinimum(0) @@ -861,29 +881,44 @@ class TaskPanel: widget.setValue(val) def setFields(self): - self.pathLength = self.obj.Proxy.getPathLength(self.obj) - vHeader = self.form.twTags.verticalHeader() - vHeader.setResizeMode(QtGui.QHeaderView.Fixed) - vHeader.setDefaultSectionSize(20) - self.updateTags() - self.setupSpinBox(self.form.sbCount, self.form.twTags.rowCount(), None) - self.setupSpinBox(self.form.dsbSpacing, 0) + self.updateTagsView() + self.setupSpinBox(self.form.sbCount, len(self.tags), None) self.setupSpinBox(self.form.dsbHeight, self.obj.Proxy.getHeight(self.obj)) self.setupSpinBox(self.form.dsbWidth, self.obj.Proxy.getWidth(self.obj)) - self.setupSpinBox(self.form.dsbAngle, self.obj.Proxy.getAngle(self.obj)) - self.updateTagSpacing(self.form.twTags.rowCount()) + self.setupSpinBox(self.form.dsbAngle, self.obj.Proxy.getAngle(self.obj), 0) + self.form.dsbAngle.setMaximum(90) + self.form.dsbAngle.setSingleStep(5.) + + def updateModelHeight(self): + print('updateModelHeight') + self.updateModel() + + def updateModelWidth(self): + print('updateModelWidth') + self.updateModel() + + def updateModelAngle(self): + print('updateModelAngle') + self.updateModel() + + def updateModelTags(self): + print('updateModelTags') + self.updateModel() def setupUi(self): self.setFields() + self.whenCountChanged() + self.form.sbCount.valueChanged.connect(self.whenCountChanged) - self.form.dsbSpacing.valueChanged.connect(self.whenSpacingChanged) - self.form.dsbHeight.valueChanged.connect(self.autoApply) - self.form.dsbWidth.valueChanged.connect(self.autoApply) - self.form.dsbAngle.valueChanged.connect(self.autoApply) - #self.form.pbAdd.clicked.connect(self.) - self.form.buttonBox.button(QtGui.QDialogButtonBox.Apply).clicked.connect(self.whenApplyClicked) - self.form.buttonBox.button(QtGui.QDialogButtonBox.Ok).clicked.connect(self.whenOkClicked) - self.form.twTags.itemChanged.connect(self.updateUI) + self.form.pbGenerate.clicked.connect(self.generateNewTags) + + self.form.dsbHeight.editingFinished.connect(self.updateModelHeight) + self.form.dsbWidth.editingFinished.connect(self.updateModelWidth) + self.form.dsbAngle.editingFinished.connect(self.updateModelAngle) + self.form.lwTags.itemChanged.connect(self.updateModelTags) + self.form.lwTags.itemSelectionChanged.connect(self.whenTagSelectionChanged) + + self.form.pbDelete.clicked.connect(self.deleteSelectedTag) class SelObserver: def __init__(self): diff --git a/src/Mod/Path/PathScripts/PathGeom.py b/src/Mod/Path/PathScripts/PathGeom.py index 4eaf54510..388b10952 100644 --- a/src/Mod/Path/PathScripts/PathGeom.py +++ b/src/Mod/Path/PathScripts/PathGeom.py @@ -82,6 +82,18 @@ class PathGeom: Return True if two points are roughly identical (see also isRoughly).""" return cls.isRoughly(p1.x, p2.x, error) and cls.isRoughly(p1.y, p2.y, error) and cls.isRoughly(p1.z, p2.z, error) + @classmethod + def edgesMatch(cls, e0, e1, error=0.0000001): + """(e0, e1, [error=0.0000001] + Return true if the edges start and end at the same point and have the same type of curve.""" + if type(e0.Curve) != type(e1.Curve): + return False + if not cls.pointsCoincide(e0.valueAt(e0.FirstParameter), e1.valueAt(e1.FirstParameter)): + return False + if not cls.pointsCoincide(e0.valueAt(e0.LastParameter), e1.valueAt(e1.LastParameter)): + return False + return True + @classmethod def edgeConnectsTo(cls, edge, vector): """(edge, vector) diff --git a/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py b/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py index 516b35e7d..2d74688da 100644 --- a/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py +++ b/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py @@ -52,7 +52,7 @@ class TestHoldingTags(PathTestBase): def test01(self): """Verify solid for a 90 degree tag is a cylinder.""" tag = Tag(100, 200, 4, 5, 90, True) - tag.createSolidsAt(17) + tag.createSolidsAt(17, 0) self.assertIsNotNone(tag.solid) self.assertCylinderAt(tag.solid, Vector(100, 200, 17), 2, 5) @@ -60,7 +60,7 @@ class TestHoldingTags(PathTestBase): def test02(self): """Verify trapezoidal tag has a cone shape with a lid.""" tag = Tag(0, 0, 18, 5, 45, True) - tag.createSolidsAt(0) + tag.createSolidsAt(0, 0) self.assertIsNotNone(tag.solid) self.assertConeAt(tag.solid, Vector(0,0,0), 9, 4, 5) @@ -68,14 +68,14 @@ class TestHoldingTags(PathTestBase): def test03(self): """Verify pointy cone shape of tag with pointy end if width, angle and height match up.""" tag = Tag(0, 0, 10, 5, 45, True) - tag.createSolidsAt(0) + tag.createSolidsAt(0, 0) self.assertIsNotNone(tag.solid) self.assertConeAt(tag.solid, Vector(0,0,0), 5, 0, 5) def test04(self): """Verify height adjustment if tag isn't wide eough for angle.""" tag = Tag(0, 0, 5, 17, 60, True) - tag.createSolidsAt(0) + tag.createSolidsAt(0, 0) self.assertIsNotNone(tag.solid) self.assertConeAt(tag.solid, Vector(0,0,0), 2.5, 0, 2.5 * math.tan((60/180.0)*math.pi)) diff --git a/src/Mod/Path/PathTests/TestPathGeom.py b/src/Mod/Path/PathTests/TestPathGeom.py index 7ab7a5396..b6a3703c5 100644 --- a/src/Mod/Path/PathTests/TestPathGeom.py +++ b/src/Mod/Path/PathTests/TestPathGeom.py @@ -130,12 +130,14 @@ class TestPathGeom(PathTestBase): commands.append(Path.Command('G0', {'X': 0})) commands.append(Path.Command('G1', {'Y': 0})) - wire = PathGeom.wireForPath(Path.Path(commands)) + wire,rapid = PathGeom.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)) self.assertLine(wire.Edges[2], Vector(1,1,0), Vector(0,1,0)) self.assertLine(wire.Edges[3], Vector(0,1,0), Vector(0,0,0)) + self.assertEqual(len(rapid), 1) + self.assertTrue(PathGeom.edgesMatch(rapid[0], wire.Edges[2])) wires = PathGeom.wiresForPath(Path.Path(commands)) self.assertEqual(len(wires), 2) From 9bf58b1c94c9b434ace19cf611b48edc2135c347 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Fri, 30 Dec 2016 23:40:16 -0800 Subject: [PATCH 22/27] Reduced logging. --- .../PathScripts/PathDressupHoldingTags.py | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index 3be16f87e..aa12276c8 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -142,7 +142,7 @@ class Tag: height = self.height if self.angle == 90 and height > 0: self.solid = Part.makeCylinder(r1, height) - print("Part.makeCone(%f, %f)" % (r1, height)) + debugPrint("Part.makeCone(%f, %f)" % (r1, height)) elif self.angle > 0.0 and height > 0.0: tangens = math.tan(math.radians(self.angle)) dr = height / tangens @@ -153,17 +153,17 @@ class Tag: height = r1 * tangens self.actualHeight = height self.r2 = r2 - print("Part.makeCone(%f, %f, %f)" % (r1, r2, height)) + debugPrint("Part.makeCone(%f, %f, %f)" % (r1, r2, height)) self.solid = Part.makeCone(r1, r2, height) else: # degenerated case - no tag - print("Part.makeSphere(%f / 10000)" % (r1)) + debugPrint("Part.makeSphere(%f / 10000)" % (r1)) self.solid = Part.makeSphere(r1 / 10000) if not R == 0: # testing is easier if the solid is not rotated angle = -PathGeom.getAngle(self.originAt(0)) * 180 / math.pi - print("solid.rotate(%f)" % angle) + debugPrint("solid.rotate(%f)" % angle) self.solid.rotate(FreeCAD.Vector(0,0,0), FreeCAD.Vector(0,0,1), angle) - print("solid.translate(%s)" % self.originAt(z)) + debugPrint("solid.translate(%s)" % self.originAt(z)) self.solid.translate(self.originAt(z)) def filterIntersections(self, pts, face): @@ -274,13 +274,13 @@ class MapWireToTag: def cleanupEdges(self, edges, baseEdge): # want to remove all edges from the wire itself, and all internal struts - print("+cleanupEdges") - print(" base:") - debugEdge(baseEdge, ' ', True) - print(" edges:") + #print("+cleanupEdges") + #print(" base:") + debugEdge(baseEdge, ' ') + #print(" edges:") for e in edges: - debugEdge(e, ' ', True) - print(":") + debugEdge(e, ' ') + #print(":") haveEntry = False for e in copy.copy(edges): @@ -296,7 +296,7 @@ class MapWireToTag: elif 1 == typ: haveEntry = True - print("entry(%.2f, %.2f, %.2f), exit(%.2f, %.2f, %.2f)" % (self.entry.x, self.entry.y, self.entry.z, self.exit.x, self.exit.y, self.exit.z)) + #print("entry(%.2f, %.2f, %.2f), exit(%.2f, %.2f, %.2f)" % (self.entry.x, self.entry.y, self.entry.z, self.exit.x, self.exit.y, self.exit.z)) # the remaininng edges for the path from xy(baseEdge) along the tags surface outputEdges = [] p0 = baseEdge.valueAt(baseEdge.FirstParameter) @@ -306,7 +306,7 @@ class MapWireToTag: p0 = PathGeom.xy(p0) lastP = p0 while edges: - print("(%.2f, %.2f, %.2f) %d %d" % (p0.x, p0.y, p0.z, haveEntry, ignoreZ)) + #print("(%.2f, %.2f, %.2f) %d %d" % (p0.x, p0.y, p0.z, haveEntry, ignoreZ)) for e in edges: p1 = e.valueAt(e.FirstParameter) p2 = e.valueAt(e.LastParameter) @@ -331,11 +331,11 @@ class MapWireToTag: if lastP == p0: raise ValueError("No connection to %s" % (p0)) elif lastP: - print("xxxxxx (%.2f, %.2f, %.2f) (%.2f, %.2f, %.2f)" % (p0.x, p0.y, p0.z, lastP.x, lastP.y, lastP.z)) + debugPrint("xxxxxx (%.2f, %.2f, %.2f) (%.2f, %.2f, %.2f)" % (p0.x, p0.y, p0.z, lastP.x, lastP.y, lastP.z)) else: - print("xxxxxx (%.2f, %.2f, %.2f) -" % (p0.x, p0.y, p0.z)) + debugPrint("xxxxxx (%.2f, %.2f, %.2f) -" % (p0.x, p0.y, p0.z)) lastP = p0 - print("-cleanupEdges") + #print("-cleanupEdges") return outputEdges def isStrut(self, edge): @@ -454,7 +454,7 @@ class PathData: return (edges[0], edges[-1]) def generateTags(self, obj, count=None, width=None, height=None, angle=90, spacing=None): - print("generateTags(%s, %s, %s, %s, %s)" % (count, width, height, angle, spacing)) + debugPrint("generateTags(%s, %s, %s, %s, %s)" % (count, width, height, angle, spacing)) #for e in self.base.Edges: # debugMarker(e.Vertexes[0].Point, 'base', (0.0, 1.0, 1.0), 0.2) @@ -716,8 +716,8 @@ class ObjectDressup: def setTags(self, obj, tags, update = True): print("setTags(%d, %d)" % (len(tags), update)) - for t in tags: - print(" .... %s" % t.toString()) + #for t in tags: + # print(" .... %s" % t.toString()) obj.Tags = [tag.toString() for tag in tags] if update: self.execute(obj) From 34f21054996eb688b9f0702449aef0738697ec85 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sat, 31 Dec 2016 14:59:07 -0800 Subject: [PATCH 23/27] Fixed edit dialog initialisation. --- src/Mod/Path/PathScripts/PathDressupHoldingTags.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index aa12276c8..5c3ab7f9e 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -723,9 +723,9 @@ class ObjectDressup: self.execute(obj) def getTags(self, obj): - if hasattr(self, 'tags'): - return self.tags - return self.setup(obj).generateTags(obj, 4) + if not hasattr(self, 'tags'): + self.execute(obj) + return self.tags def setup(self, obj): if True or not hasattr(self, "pathData") or not self.pathData: From 807cf80a77751aa2846edf860c21800fcfd88ac0 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sat, 31 Dec 2016 16:37:56 -0800 Subject: [PATCH 24/27] Hide job while editing dressup. --- .../PathScripts/PathDressupHoldingTags.py | 35 ++++++++++++++----- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index 5c3ab7f9e..cccab2a00 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -768,21 +768,30 @@ class TaskPanel: self.obj = obj self.form = FreeCADGui.PySideUic.loadUi(":/panels/HoldingTagsEdit.ui") FreeCAD.ActiveDocument.openTransaction(translate("PathDressup_HoldingTags", "Edit HoldingTags Dress-up")) + self.jvo = PathUtils.findParentJob(obj).ViewObject + self.jvoVisible = self.jvo.isVisible() + if self.jvoVisible: + self.jvo.hide() def reject(self): + print("reject") FreeCAD.ActiveDocument.abortTransaction() - FreeCADGui.Control.closeDialog() - FreeCAD.ActiveDocument.recompute() - FreeCADGui.Selection.removeObserver(self.s) + self.cleanup() def accept(self): + print("accept") self.getFields() FreeCAD.ActiveDocument.commitTransaction() FreeCADGui.ActiveDocument.resetEdit() + self.cleanup() + FreeCAD.ActiveDocument.recompute() + + def cleanup(self): FreeCADGui.Control.closeDialog() FreeCAD.ActiveDocument.recompute() FreeCADGui.Selection.removeObserver(self.s) - FreeCAD.ActiveDocument.recompute() + if self.jvoVisible: + self.jvo.show() def open(self): self.s = SelObserver() @@ -868,11 +877,18 @@ class TaskPanel: def deleteSelectedTag(self): item = self.form.lwTags.currentItem() - x = item.data(self.DataX) - y = item.data(self.DataY) - tags = filter(lambda t: t.x != x or t.y != y, self.tags) - self.obj.Proxy.setTags(self.obj, tags) - self.updateTagsView() + if item: + x = item.data(self.DataX) + y = item.data(self.DataY) + tags = filter(lambda t: t.x != x or t.y != y, self.tags) + self.obj.Proxy.setTags(self.obj, tags) + self.updateTagsView() + + def addNewTagAt(self, point, what): + print("%s '%s'" %( point, what.Name)) + + def addNewTag(self): + FreeCADGui.Snapper.getPoint(callback=self.addNewTagAt) def setupSpinBox(self, widget, val, decimals = 2): widget.setMinimum(0) @@ -919,6 +935,7 @@ class TaskPanel: self.form.lwTags.itemSelectionChanged.connect(self.whenTagSelectionChanged) self.form.pbDelete.clicked.connect(self.deleteSelectedTag) + self.form.pbAdd.clicked.connect(self.addNewTag) class SelObserver: def __init__(self): From 8efbe9e6451c3299552b893ced42d0abf8b2a9e9 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sun, 1 Jan 2017 19:23:19 -0800 Subject: [PATCH 25/27] Basic UI for holding tags. --- .../PathScripts/PathDressupHoldingTags.py | 257 +++++++++--------- 1 file changed, 128 insertions(+), 129 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index cccab2a00..66ff43785 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -32,6 +32,7 @@ import math import cProfile import time +from DraftGui import todo from PathScripts import PathUtils from PathScripts.PathGeom import * from PySide import QtCore, QtGui @@ -468,11 +469,11 @@ class PathData: if width: W = width else: - W = self.tagWidth() + W = self.defaultTagWidth() if height: H = height else: - H = self.tagHeight() + H = self.defaultTagHeight() # start assigning tags on the longest segment @@ -539,20 +540,17 @@ class PathData: return (currentLength, lastTagLength) - def tagHeight(self): + def defaultTagHeight(self): if hasattr(self.obj, 'Base') and hasattr(self.obj.Base, 'StartDepth') and hasattr(self.obj.Base, 'FinalDepth'): - return self.obj.Base.StartDepth - self.obj.Base.FinalDepth + return (self.obj.Base.StartDepth - self.obj.Base.FinalDepth).Value return self.maxZ - self.minZ - def tagWidth(self): + def defaultTagWidth(self): return self.shortestAndLongestPathEdge()[1].Length / 10 - def tagAngle(self): + def defaultTagAngle(self): return 90 - def pathLength(self): - return self.base.Length - def sortedTags(self, tags): ordered = [] for edge in self.base.Edges: @@ -570,8 +568,11 @@ class ObjectDressup: def __init__(self, obj): self.obj = obj obj.addProperty("App::PropertyLink", "Base","Base", QtCore.QT_TRANSLATE_NOOP("PathDressup_HoldingTags", "The base path to modify")) - obj.addProperty("App::PropertyStringList", "Tags", "Tag", QtCore.QT_TRANSLATE_NOOP("PathDressup_holdingTags", "Inserted tags")) - obj.setEditorMode("Tags", 2) + obj.addProperty("App::PropertyFloat", "Width", "Tag", QtCore.QT_TRANSLATE_NOOP("PathDressup_HoldingTags", "Width of tags.")) + obj.addProperty("App::PropertyFloat", "Height", "Tag", QtCore.QT_TRANSLATE_NOOP("PathDressup_HoldingTags", "Height of tags.")) + obj.addProperty("App::PropertyFloat", "Angle", "Tag", QtCore.QT_TRANSLATE_NOOP("PathDressup_HoldingTags", "Angle of tag plunge and ascent.")) + obj.addProperty("App::PropertyVectorList", "Positions", "Tag", QtCore.QT_TRANSLATE_NOOP("PathDressup_HoldingTags", "Locations of insterted holding tags")) + obj.addProperty("App::PropertyIntegerList", "Disabled", "Tag", QtCore.QT_TRANSLATE_NOOP("PathDressup_HoldingTags", "Ids of disabled holding tags")) obj.Proxy = self def __getstate__(self): @@ -580,8 +581,14 @@ class ObjectDressup: def __setstate__(self, state): return None - def generateTags(self, obj, count=None, width=None, height=None, angle=90, spacing=None): - return self.pathData.generateTags(obj, count, width, height, angle, spacing) + def generateTags(self, obj, count): + if hasattr(self, "pathData"): + self.tags = self.pathData.generateTags(obj, count, obj.Width, obj.Height, obj.Angle, None) + obj.Positions = [tag.originAt(0) for tag in self.tags] + obj.Disabled = [] + else: + self.setup(obj, count) + self.execute(obj) def isValidTagStartIntersection(self, edge, i): if PathGeom.pointsCoincide(i, edge.valueAt(edge.LastParameter)): @@ -675,103 +682,87 @@ class ObjectDressup: print("execute - no pathData") return - if hasattr(obj, 'Tags') and obj.Tags: - if False and self.fingerprint == obj.Tags: - print("execute - cache valid") - return - print("execute - tags from property") - tags = [Tag.FromString(tag) for tag in obj.Tags] - else: - print("execute - default tags") - tags = self.generateTags(obj, 4.) + self.tags = [] + if hasattr(obj, "Positions"): + for i, pos in enumerate(obj.Positions): + tag = Tag(pos.x, pos.y, obj.Width, obj.Height, obj.Angle, not i in obj.Disabled) + tag.createSolidsAt(pathData.minZ, self.toolRadius) + self.tags.append(tag) - if not tags: + if not self.tags: print("execute - no tags") - self.tags = [] obj.Path = obj.Base.Path return - print("execute - %d tags" % (len(tags))) - tags = pathData.sortedTags(tags) - self.setTags(obj, tags, False) - for tag in tags: - tag.createSolidsAt(pathData.minZ, self.toolRadius) - tagID = 0 - for tag in tags: - tagID += 1 - if tag.enabled: - #print("x=%s, y=%s, z=%s" % (tag.x, tag.y, pathData.minZ)) - #debugMarker(FreeCAD.Vector(tag.x, tag.y, pathData.minZ), "tag-%02d" % tagID , (1.0, 0.0, 1.0), 0.5) - if tag.angle != 90: - debugCone(tag.originAt(pathData.minZ), tag.r1, tag.r2, tag.actualHeight, "tag-%02d" % tagID) - else: - debugCylinder(tag.originAt(pathData.minZ), tag.fullWidth()/2, tag.actualHeight, "tag-%02d" % tagID) + if debugDressup: + for tag in self.tags: + tagID += 1 + if tag.enabled: + print("x=%s, y=%s, z=%s" % (tag.x, tag.y, pathData.minZ)) + debugMarker(FreeCAD.Vector(tag.x, tag.y, pathData.minZ), "tag-%02d" % tagID , (1.0, 0.0, 1.0), 0.5) + if tag.angle != 90: + debugCone(tag.originAt(pathData.minZ), tag.r1, tag.r2, tag.actualHeight, "tag-%02d" % tagID) + else: + debugCylinder(tag.originAt(pathData.minZ), tag.fullWidth()/2, tag.actualHeight, "tag-%02d" % tagID) - self.fingerprint = [tag.toString() for tag in tags] - self.tags = tags - - obj.Path = self.createPath(pathData.edges, tags, pathData.rapid) + obj.Path = self.createPath(pathData.edges, self.tags, pathData.rapid) print("execute - done") - def setTags(self, obj, tags, update = True): - print("setTags(%d, %d)" % (len(tags), update)) - #for t in tags: - # print(" .... %s" % t.toString()) - obj.Tags = [tag.toString() for tag in tags] - if update: - self.execute(obj) - - def getTags(self, obj): - if not hasattr(self, 'tags'): - self.execute(obj) - return self.tags - - def setup(self, obj): - if True or not hasattr(self, "pathData") or not self.pathData: - try: - pathData = PathData(obj) - except ValueError: - FreeCAD.Console.PrintError(translate("PathDressup_HoldingTags", "Cannot insert holding tags for this path - please select a Profile path\n")) - return None + def setup(self, obj, generate=None): + print("setup") + self.obj = obj + try: + pathData = PathData(obj) + except ValueError: + FreeCAD.Console.PrintError(translate("PathDressup_HoldingTags", "Cannot insert holding tags for this path - please select a Profile path\n")) + return None + self.toolRadius = 5 + toolLoad = PathUtils.getLastToolLoad(obj) + if toolLoad is None or toolLoad.ToolNumber == 0: self.toolRadius = 5 - toolLoad = PathUtils.getLastToolLoad(obj) - if toolLoad is None or toolLoad.ToolNumber == 0: + else: + tool = PathUtils.getTool(obj, toolLoad.ToolNumber) + if not tool or tool.Diameter == 0: self.toolRadius = 5 else: - tool = PathUtils.getTool(obj, toolLoad.ToolNumber) - if not tool or tool.Diameter == 0: - self.toolRadius = 5 - else: - self.toolRadius = tool.Diameter / 2 - self.pathData = pathData + self.toolRadius = tool.Diameter / 2 + self.pathData = pathData + if generate: + obj.Height = self.pathData.defaultTagHeight() + obj.Width = self.pathData.defaultTagWidth() + obj.Angle = self.pathData.defaultTagAngle() + self.generateTags(obj, generate) return self.pathData - def getHeight(self, obj): - return self.pathData.tagHeight() - - def getWidth(self, obj): - return self.pathData.tagWidth() - - def getAngle(self, obj): - return self.pathData.tagAngle() - - def getPathLength(self, obj): - return self.pathData.pathLength() + def setXyEnabled(self, triples): + positions = [] + disabled = [] + for i, (x, y, enabled) in enumerate(triples): + positions.append(FreeCAD.Vector(x, y, 0)) + if not enabled: + disabled.append(i) + self.obj.Positions = positions + self.obj.Disabled = disabled + self.execute(self.obj) class TaskPanel: DataX = QtCore.Qt.ItemDataRole.UserRole DataY = QtCore.Qt.ItemDataRole.UserRole + 1 - def __init__(self, obj): + def __init__(self, obj, jvoVisibility=None): self.obj = obj + self.obj.Proxy.obj = obj self.form = FreeCADGui.PySideUic.loadUi(":/panels/HoldingTagsEdit.ui") - FreeCAD.ActiveDocument.openTransaction(translate("PathDressup_HoldingTags", "Edit HoldingTags Dress-up")) self.jvo = PathUtils.findParentJob(obj).ViewObject - self.jvoVisible = self.jvo.isVisible() - if self.jvoVisible: - self.jvo.hide() + if jvoVisibility is None: + FreeCAD.ActiveDocument.openTransaction(translate("PathDressup_HoldingTags", "Edit HoldingTags Dress-up")) + self.jvoVisible = self.jvo.isVisible() + if self.jvoVisible: + self.jvo.hide() + else: + self.jvoVisible = jvoVisibility def reject(self): print("reject") @@ -782,51 +773,64 @@ class TaskPanel: print("accept") self.getFields() FreeCAD.ActiveDocument.commitTransaction() - FreeCADGui.ActiveDocument.resetEdit() self.cleanup() FreeCAD.ActiveDocument.recompute() def cleanup(self): + FreeCADGui.ActiveDocument.resetEdit() FreeCADGui.Control.closeDialog() FreeCAD.ActiveDocument.recompute() FreeCADGui.Selection.removeObserver(self.s) if self.jvoVisible: self.jvo.show() + def closeDialog(self): + print("closed") + def open(self): self.s = SelObserver() # install the function mode resident FreeCADGui.Selection.addObserver(self.s) - def getFields(self): - width = self.form.dsbWidth.value() - height = self.form.dsbHeight.value() - angle = self.form.dsbAngle.value() + def getTags(self, includeCurrent): tags = [] + index = self.form.lwTags.currentRow() for i in range(0, self.form.lwTags.count()): item = self.form.lwTags.item(i) enabled = item.checkState() == QtCore.Qt.CheckState.Checked x = item.data(self.DataX) y = item.data(self.DataY) - tags.append(Tag(x, y, width, height, angle, enabled)) - self.obj.Proxy.setTags(self.obj, tags) + print("(%.2f, %.2f) i=%d/%s" % (x, y, i, index)) + if includeCurrent or i != index: + tags.append((x, y, enabled)) + return tags + + def getTagParameters(self): + self.obj.Width = self.form.dsbWidth.value() + self.obj.Height = self.form.dsbHeight.value() + self.obj.Angle = self.form.dsbAngle.value() + + def getFields(self): + self.getTagParameters() + tags = self.getTags(True) + self.obj.Proxy.setXyEnabled(tags) def updateTagsView(self): - self.tags = self.obj.Proxy.getTags(self.obj) - print("updateTagsView: %d" % (len(self.tags))) + print("updateTagsView") self.form.lwTags.blockSignals(True) self.form.lwTags.clear() - for tag in self.tags: - lbl = "(%.2f, %.2f)" % (tag.x, tag.y) + for i, pos in enumerate(self.obj.Positions): + lbl = "%d: (%.2f, %.2f)" % (i, pos.x, pos.y) item = QtGui.QListWidgetItem(lbl) - item.setData(self.DataX, tag.x) - item.setData(self.DataY, tag.y) - if tag.enabled: - item.setCheckState(QtCore.Qt.CheckState.Checked) - else: + item.setData(self.DataX, pos.x) + item.setData(self.DataY, pos.y) + if i in self.obj.Disabled: item.setCheckState(QtCore.Qt.CheckState.Unchecked) + else: + item.setCheckState(QtCore.Qt.CheckState.Checked) flags = QtCore.Qt.ItemFlag.ItemIsSelectable - flags |= QtCore.Qt.ItemFlag.ItemIsEnabled | QtCore.Qt.ItemFlag.ItemIsUserCheckable + flags |= QtCore.Qt.ItemFlag.ItemIsEnabled + flags |= QtCore.Qt.ItemFlag.ItemIsUserCheckable item.setFlags(flags) self.form.lwTags.addItem(item) self.form.lwTags.blockSignals(False) @@ -843,22 +847,13 @@ class TaskPanel: self.cleanupUI() count = self.form.sbCount.value() - width = self.form.dsbWidth.value() - height = self.form.dsbHeight.value() - angle = self.form.dsbAngle.value() + self.obj.Proxy.generateTags(self.obj, count) - tags = self.obj.Proxy.generateTags(self.obj, count, width, height, angle) - - self.obj.Proxy.setTags(self.obj, tags) self.updateTagsView() #if debugDressup: # # this causes a big of an echo and a double click on the spin buttons, don't know why though # FreeCAD.ActiveDocument.recompute() -# def autoApply(self): -# print("autoApply") -# if self.form.cbAutoApply.checkState() == QtCore.Qt.CheckState.Checked: -# self.whenApplyClicked() def updateModel(self): self.getFields() @@ -876,19 +871,23 @@ class TaskPanel: self.form.pbDelete.setEnabled(not item is None) def deleteSelectedTag(self): - item = self.form.lwTags.currentItem() - if item: - x = item.data(self.DataX) - y = item.data(self.DataY) - tags = filter(lambda t: t.x != x or t.y != y, self.tags) - self.obj.Proxy.setTags(self.obj, tags) - self.updateTagsView() + self.obj.Proxy.setXyEnabled(self.getTags(False)) + self.updateTagsView() def addNewTagAt(self, point, what): - print("%s '%s'" %( point, what.Name)) + if what == self.obj: + print("%s '%s'" %( point, what.Name)) + tags = self.tags + tags.append((point.x, point.y, True)) + self.obj.Proxy.setXyEnabled(tags) + panel = TaskPanel(self.obj, self.jvoVisible) + todo.delay(FreeCADGui.Control.closeDialog, None) + todo.delay(FreeCADGui.Control.showDialog, panel) + todo.delay(panel.setupUi, None) def addNewTag(self): - FreeCADGui.Snapper.getPoint(callback=self.addNewTagAt) + self.tags = self.getTags(True) + FreeCADGui.Snapper.getPoint(callback=self.addNewTagAt, extradlg=[self]) def setupSpinBox(self, widget, val, decimals = 2): widget.setMinimum(0) @@ -898,10 +897,10 @@ class TaskPanel: def setFields(self): self.updateTagsView() - self.setupSpinBox(self.form.sbCount, len(self.tags), None) - self.setupSpinBox(self.form.dsbHeight, self.obj.Proxy.getHeight(self.obj)) - self.setupSpinBox(self.form.dsbWidth, self.obj.Proxy.getWidth(self.obj)) - self.setupSpinBox(self.form.dsbAngle, self.obj.Proxy.getAngle(self.obj), 0) + self.setupSpinBox(self.form.sbCount, len(self.obj.Positions), None) + self.setupSpinBox(self.form.dsbHeight, self.obj.Height) + self.setupSpinBox(self.form.dsbWidth, self.obj.Width) + self.setupSpinBox(self.form.dsbAngle, self.obj.Angle, 0) self.form.dsbAngle.setMaximum(90) self.form.dsbAngle.setSingleStep(5.) @@ -947,7 +946,7 @@ class SelObserver: PST.clear() def addSelection(self, doc, obj, sub, pnt): - FreeCADGui.doCommand('Gui.Selection.addSelection(FreeCAD.ActiveDocument.' + obj + ')') + #FreeCADGui.doCommand('Gui.Selection.addSelection(FreeCAD.ActiveDocument.' + obj + ')') FreeCADGui.updateGui() class ViewProviderDressup: @@ -1029,7 +1028,7 @@ class CommandPathDressupHoldingTags: FreeCADGui.doCommand('PathScripts.PathDressupHoldingTags.ViewProviderDressup(obj.ViewObject)') FreeCADGui.doCommand('PathScripts.PathUtils.addToJob(obj)') FreeCADGui.doCommand('Gui.ActiveDocument.getObject(obj.Base.Name).Visibility = False') - FreeCADGui.doCommand('dbo.setup(obj)') + FreeCADGui.doCommand('dbo.setup(obj, 4.)') FreeCAD.ActiveDocument.commitTransaction() FreeCAD.ActiveDocument.recompute() From 556e25e47f2d300ff31135b445491a2eb1dc05ab Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sun, 1 Jan 2017 19:32:37 -0800 Subject: [PATCH 26/27] Fixed another initialisation issue - depending on how the dressup is created. --- src/Mod/Path/PathScripts/PathDressupHoldingTags.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index 66ff43785..f4f3276f1 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -586,9 +586,11 @@ class ObjectDressup: self.tags = self.pathData.generateTags(obj, count, obj.Width, obj.Height, obj.Angle, None) obj.Positions = [tag.originAt(0) for tag in self.tags] obj.Disabled = [] + return False else: self.setup(obj, count) self.execute(obj) + return True def isValidTagStartIntersection(self, edge, i): if PathGeom.pointsCoincide(i, edge.valueAt(edge.LastParameter)): @@ -847,7 +849,8 @@ class TaskPanel: self.cleanupUI() count = self.form.sbCount.value() - self.obj.Proxy.generateTags(self.obj, count) + if not self.obj.Proxy.generateTags(self.obj, count): + self.obj.Proxy.execute(self.obj) self.updateTagsView() #if debugDressup: From e92f3153796d58890f1aefd7159ee6a94cfa7c3d Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Mon, 2 Jan 2017 17:32:48 -0800 Subject: [PATCH 27/27] Changed default values for tags and disabled old popup menu. --- src/Mod/Path/InitGui.py | 2 +- src/Mod/Path/PathScripts/PathDressupHoldingTags.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Mod/Path/InitGui.py b/src/Mod/Path/InitGui.py index e046eb542..b5f020413 100644 --- a/src/Mod/Path/InitGui.py +++ b/src/Mod/Path/InitGui.py @@ -136,7 +136,7 @@ class PathWorkbench (Workbench): if FreeCADGui.Selection.getSelection()[0].isDerivedFrom("Path::Feature"): self.appendContextMenu("", ["Path_Inspect"]) if "Profile" or "Contour" in FreeCADGui.Selection.getSelection()[0].Name: - self.appendContextMenu("", ["Add_Tag"]) + #self.appendContextMenu("", ["Add_Tag"]) self.appendContextMenu("", ["Set_StartPoint"]) self.appendContextMenu("", ["Set_EndPoint"]) if "Remote" in FreeCADGui.Selection.getSelection()[0].Name: diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index f4f3276f1..d05292806 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -542,14 +542,14 @@ class PathData: def defaultTagHeight(self): if hasattr(self.obj, 'Base') and hasattr(self.obj.Base, 'StartDepth') and hasattr(self.obj.Base, 'FinalDepth'): - return (self.obj.Base.StartDepth - self.obj.Base.FinalDepth).Value - return self.maxZ - self.minZ + return (self.obj.Base.StartDepth - self.obj.Base.FinalDepth).Value / 2 + return (self.maxZ - self.minZ) / 2 def defaultTagWidth(self): return self.shortestAndLongestPathEdge()[1].Length / 10 def defaultTagAngle(self): - return 90 + return 45 def sortedTags(self, tags): ordered = []