diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index 82f5a3eb2..60e46af17 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -114,12 +114,12 @@ def testSide(): testPrintSide(FreeCAD.Vector( 1, 0, 0), FreeCAD.Vector( 0,-1, 0)) def pathCommandForEdge(edge): - pt = edge.valueAt(edge.LastParameter) + 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.valueAt(edge.FirstParameter) + p1 = edge.Curve.StartPoint p2 = edge.valueAt((edge.FirstParameter + edge.LastParameter)/2) p3 = pt if Side.Left == Side.of(p2 - p1, p3 - p2): @@ -132,13 +132,25 @@ def pathCommandForEdge(edge): class Tag: - def __init__(self, x, y, width, height, angle, enabled=True): + + @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)) @@ -146,7 +158,14 @@ class Tag: 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 createSolidsAt(self, z): + self.z = z r1 = self.width / 2 height = self.height if self.angle == 90 and height > 0: @@ -162,6 +181,7 @@ class Tag: r2 = 0 height = r1 * tangens self.core = None + self.actualHeight = height self.solid = Part.makeCone(r1, r2, height) else: # degenerated case - no tag @@ -171,14 +191,144 @@ class Tag: if self.core: self.core.translate(self.originAt(z)) - @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 + class Intersection: + # An intersection with a tag has 4 markant points, where one might be optional. + # P1---P2 P2 + # / \ /\ + # / \ / \ + # / \ / \ + # ---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 intersect(self, edge): + #print("") + if self.state == self.P0: + #print("----- P0") + if self.tag.core: + self.state = self.P1 + i = self.tag.nextIntersectionClosestTo(edge, self.tag.core, edge.Curve.StartPoint) + if i: + if i == edge.Curve.StartPoint: + self.edges.append(Part.Edge(Part.Line(i, FreeCAD.Vector(i.x, i.y, self.tag.top())))) + else: + e, tail = self.tag.splitEdgeAt(edge, i) + self.edges.append(self.mapEdgeTo(e, self.tag.solid)) + edge = tail + else: + self.edges.append(self.mapEdgeTo(e, self.tag.solid)) + # we're done with this edge + return self + else: + p = self.tag.originAt(self.tag.bottom() + self.tag.actualHeight) + if DraftGeomUtils.isPtOnEdge(p, edge): + e, tail = self.tag.splitEdgeAt(edge, p) + self.edges.append(self.mapEdgeTo(e, self.tag.solid)) + edge = tail + self.state = self.P2 + else: + self.edges.append(self.mapEdgeTo(e, self.tag.solid)) + # we're done with this edge + return self + + if self.state == self.P1: + #print("----- P1") + # must have core, find end of plateau + i = self.tag.nextIntersectionClosestTo(edge, self.tag.core, edge.Curve.EndPoint) + if i and i != edge.Curve.StartPoint: + self.state = self.P2 + if i == edge.Curve.EndPoint: + self.edges.append(self.moveEdgeToPlateau(edge)) + # edge fully consumed + return self + else: + e, tail = self.tag.splitEdgeAt(edge, i) + self.edges.append(self.moveEdgeToPlateau(e)) + edge = tail + else: + self.edges.append(self.moveEdgeToPlateau(edge)) + # edge fully consumed, we're still in P1 + return self + + if self.state == self.P2: + #print("----- P2") + i = self.tag.nextIntersectionClosestTo(edge, self.tag.solid, edge.Curve.EndPoint) + if i: + self.state = self.P3 + #print("----- P3") + if i == edge.Curve.StartPoint: + self.edges.append(Part.Edge(Part.Line(FreeCAD.Vector(i.x, i.y, self.tag.top()), i))) + self.tail = edge + elif i == edge.Curve.EndPoint: + self.edges.append(self.mapEdgeTo(edge, self.tag.solid)) + self.tail = None + else: + e, tail = self.tag.splitEdgeAt(edge, i) + self.edges.append(self.mapEdgeTo(e, self.tag.solid)) + self.tail = tail + + return self + + + + def splitEdgeAt(self, edge, pt): + p = edge.Curve.parameter(pt) + wire = edge.split(p) + return wire.Edges + + + def nextIntersectionClosestTo(self, edge, solid, refPt): + pts = [] + for face in solid.Faces: + i = edge.Curve.intersect(face.Surface)[0] + pts.extend([FreeCAD.Vector(p.X, p.Y, p.Z) for p in i]) + if pts: + closest = sorted(pts, key=lambda pt: (pt - refPt).Length)[0] + return closest + return None + + def intersect(self, edge): + inters = self.Intersection(self) + if edge.Curve.StartPoint.z < self.top() or edge.Curve.EndPoint.z < self.top(): + i = self.nextIntersectionClosestTo(edge, self.solid, edge.Curve.StartPoint) + if i: + inters.state = self.Intersection.P0 + if i == edge.Curve.EndPoint: + inters.edges.append(edge) + return inters + if i == edge.Curve.StartPoint: + 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): @@ -235,16 +385,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.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) + 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.valueAt(e.FirstParameter) == pt][0] + edge = [e for e in base if e.Curve.StartPoint == pt][0] ordered.append(edge) base.remove(edge) - pt = edge.valueAt(edge.LastParameter) + pt = edge.Curve.EndPoint return ordered @@ -364,7 +514,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.valueAt(edge.FirstParameter)).Length): + for t in sorted(ts, key=lambda t: (t.originAt(self.minZ) - edge.Curve.StartPoint).Length): tags.remove(t) ordered.append(t) if tags: @@ -392,7 +542,7 @@ class ObjectDressup: def tagIntersection(self, face, edge): - p1 = edge.valueAt(edge.FirstParameter) + p1 = edge.Curve.StartPoint pts = edge.Curve.intersect(face.Surface) if pts[0]: closest = sorted(pts[0], key=lambda pt: (pt - p1).Length)[0] @@ -409,9 +559,9 @@ class ObjectDressup: for face in solid.Faces: pt = self.tagIntersection(face, edge) if pt: - if pt == edge.valueAt(edge.FirstParameter): + if pt == edge.Curve.StartPoint: pt - elif pt != edge.valueAt(edge.LastParameter): + elif pt != edge.Curve.EndPoint: parameter = edge.Curve.parameter(pt) wire = edge.split(parameter) commands.append(pathCommandForEdge(wire.Edges[0])) diff --git a/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py b/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py index 23554febf..006a34f35 100644 --- a/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py +++ b/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py @@ -44,61 +44,8 @@ def pointsCoincide(pt1, pt2): return False return True -class PathDressupHoldingTagsTestCases(unittest.TestCase): - """Unit tests for the HoldingTags dressup.""" - - def testTagBasics(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 testTagSolidBasic(self): - """For a 90 degree tag the core and solid are both defined and 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 testTagSolidFlatCone(self): - """Tests a Tag that has an angle leaving a flat face on top of the cone.""" - 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 testTagSolidCone(self): - """Tests a Tag who's angled sides coincide at the tag's height.""" - 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 testTagSolidShortCone(self): - """Tests a Tag that's not wide enough to reach full height.""" - 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 TagTestCaseBase(unittest.TestCase): + """Base class for all tag test cases providing additional assert functions.""" def assertCylinderAt(self, solid, pt, r, h): """Verify that solid is a cylinder at the specified location.""" @@ -127,14 +74,14 @@ class PathDressupHoldingTagsTestCases(unittest.TestCase): def assertCircle(self, edge, pt, r): """Verivy that edge is a circle at given location.""" curve = edge.Curve - self.assertTrue(type(curve), Part.Circle) + self.assertIs(type(curve), Part.Circle) self.assertCoincide(curve.Center, Vector(pt.x, pt.y, pt.z)) self.assertAbout(curve.Radius, r) def assertLine(self, edge, pt1, pt2): """Verify that edge is a line from pt1 to pt2.""" curve = edge.Curve - self.assertTrue(type(curve), Part.Line) + self.assertIs(type(curve), Part.Line) self.assertCoincide(curve.StartPoint, pt1) self.assertCoincide(curve.EndPoint, pt2) @@ -147,4 +94,254 @@ class PathDressupHoldingTagsTestCases(unittest.TestCase): def assertAbout(self, v1, v2): """Verify that 2 values are the same (accounting for float imprecision).""" #print("assertAbout(%f, %f)" % (v1, v2)) - self.assertTrue(math.fabs(v1 - v2) < slack) + if math.fabs(v1 - v2) > slack: + self.fail("%f != %f" % (v1, v2)) + + def assertTrapezoid(self, edgs, tail, spec): + """Check that there are 5 edges forming a trapezoid.""" + edges = list(edgs) + if tail: + edges.append(tail) + self.assertEqual(len(edges), 5) + + p0 = spec[0] + p1 = Vector(spec[1], p0.y, p0.z) + p2 = Vector(p1.x, p1.y, spec[2]) + p3 = Vector(-p2.x, p2.y, p2.z) + p4 = Vector(p3.x, p3.y, p0.z) + p5 = spec[3] + + self.assertLine(edges[0], p0, p1) + self.assertLine(edges[1], p1, p2) + self.assertLine(edges[2], p2, p3) + self.assertLine(edges[3], p3, p4) + self.assertLine(edges[4], p4, p5) + + +class TagTestCases(TagTestCaseBase): # ============= + """Unit tests for the HoldingTags dressup.""" + + def testTagBasics(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 testTagSolidBasic(self): + #"""For a 90 degree tag the core and solid are both defined and 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 testTagSolidFlatCone(self): + #"""Tests a Tag that has an angle leaving a flat face on top of the cone.""" + 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 testTagSolidCone(self): + #"""Tests a Tag who's angled sides coincide at the tag's height.""" + 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 testTagSolidShortCone(self): + #"""Tests a Tag that's not wide enough to reach full height.""" + 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 SquareTagTestCases(TagTestCaseBase): # ============= + """Unit tests for square tags.""" + + def testTagNoIntersect(self): + #"""Check that the returned tail if no intersection occurs matches the input.""" + tag = Tag( 0, 0, 4, 7, 90, True, 0) + pt1 = Vector(+5, 3, 0) + pt2 = Vector(-5, 3, 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 testTagIntersectLine(self): + #"""Test that a straight line passing through a cylindrical tag is split up into 5 segments.""" + tag = Tag( 0, 0, 4, 7, 90, True, 0) + pt1 = Vector(+5, 0, 0) + pt2 = Vector(-5, 0, 0) + edge = Part.Edge(Part.Line(pt1, pt2)) + + i = tag.intersect(edge) + self.assertIsNotNone(i) + self.assertTrue(i.isComplete()) + + pt0a = Vector(+2, 0, 0) + pt0b = Vector(+2, 0, 7) + pt0c = Vector(-2, 0, 7) + pt0d = Vector(-2, 0, 0) + + self.assertEqual(len(i.edges), 4) + self.assertLine(i.edges[0], pt1, pt0a) + self.assertLine(i.edges[1], pt0a, pt0b) + self.assertLine(i.edges[2], pt0b, pt0c) + self.assertLine(i.edges[3], pt0c, pt0d) + self.assertLine(i.tail, pt0d, pt2) + + + def testTagIntersectPartialLineP0(self): + #"""Make sure line is accounted for if it reaches P0.""" + tag = Tag( 0, 0, 4, 7, 90, True, 0) + edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(2, 0, 0))) + + i = tag.intersect(edge) + self.assertFalse(i.isComplete()) + + self.assertEqual(len(i.edges), 1) + self.assertLine(i.edges[0], edge.Curve.StartPoint, edge.Curve.EndPoint) + self.assertIsNone(i.tail) + + + def testTagIntersectPartialLineP1(self): + #"""Make sure line is accounted for if it reaches beyond P1.""" + tag = Tag( 0, 0, 4, 7, 90, True, 0) + edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(1, 0, 0))) + + i = tag.intersect(edge) + self.assertFalse(i.isComplete()) + + pt0a = Vector(+2, 0, 0) + pt0b = Vector(+2, 0, 7) + pt1a = Vector(+1, 0, 7) + + self.assertEqual(len(i.edges), 3) + self.assertLine(i.edges[0], edge.Curve.StartPoint, pt0a) + self.assertLine(i.edges[1], pt0a, pt0b) + self.assertLine(i.edges[2], pt0b, pt1a) + self.assertIsNone(i.tail) + + + def testTagIntersectPartialLineP2(self): + #"""Make sure line is accounted for if it reaches beyond P2.""" + tag = Tag( 0, 0, 4, 7, 90, True, 0) + edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(-1, 0, 0))) + + i = tag.intersect(edge) + self.assertFalse(i.isComplete()) + + pt0a = Vector(+2, 0, 0) + pt0b = Vector(+2, 0, 7) + pt1a = Vector(-1, 0, 7) + + self.assertEqual(len(i.edges), 3) + self.assertLine(i.edges[0], edge.Curve.StartPoint, pt0a) + self.assertLine(i.edges[1], pt0a, pt0b) + self.assertLine(i.edges[2], pt0b, pt1a) + self.assertIsNone(i.tail) + + def testTagIntersectPartialLineP11(self): + #"""Make sure a line is accounted for if it lies entirely between P1 and P2.""" + tag = Tag( 0, 0, 4, 7, 90, True, 0) + e1 = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(+1, 0, 0))) + + i = tag.intersect(e1) + self.assertFalse(i.isComplete()) + + e2 = Part.Edge(Part.Line(e1.Curve.EndPoint, Vector(0,0,0))) + i = i.intersect(e2) + + pt0a = Vector(+2, 0, 0) + pt0b = Vector(+2, 0, 7) + pt1a = Vector(+1, 0, 7) + pt1b = Vector( 0, 0, 7) + + self.assertEqual(len(i.edges), 4) + self.assertLine(i.edges[0], e1.Curve.StartPoint, pt0a) + self.assertLine(i.edges[1], pt0a, pt0b) + self.assertLine(i.edges[2], pt0b, pt1a) + self.assertLine(i.edges[3], pt1a, pt1b) + self.assertIsNone(i.tail) + + def testTagIntersectPartialLinesP11223(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.assertLine(i.edges[0], e0.Curve.StartPoint, pt0) + self.assertLine(i.edges[1], pt0, pt1) + self.assertLine(i.edges[2], pt1, pt2) + self.assertLine(i.edges[3], pt2, pt3) + self.assertLine(i.edges[4], pt3, pt4) + self.assertLine(i.edges[5], pt4, pt5) + self.assertLine(i.edges[6], pt5, pt6) + self.assertLine(i.edges[7], pt6, e5.Curve.EndPoint) + self.assertTrue(i.isComplete()) + + self.assertIsNotNone(i.tail) + self.assertLine(i.tail, e6.Curve.StartPoint, e6.Curve.EndPoint) + + def testTagIntersectLineAt(self): + tag = Tag( 0, 0, 4, 7, 90, True, 0) + # for all lines below 7 we get the trapezoid + for i in range(0, 7): + edge = Part.Edge(Part.Line(Vector(5, 0, i), Vector(-5, 0, i))) + s = tag.intersect(edge) + self.assertTrue(s.isComplete()) + self.assertTrapezoid(s.edges, s.tail, [edge.Curve.StartPoint, 2, 7, edge.Curve.EndPoint]) + + # 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) + diff --git a/src/Mod/Path/TestPathApp.py b/src/Mod/Path/TestPathApp.py index 90640ab54..c719142d1 100644 --- a/src/Mod/Path/TestPathApp.py +++ b/src/Mod/Path/TestPathApp.py @@ -25,4 +25,5 @@ import TestApp from PathTests.TestPathPost import PathPostTestCases -from PathTests.TestPathDressupHoldingTags import PathDressupHoldingTagsTestCases +from PathTests.TestPathDressupHoldingTags import TagTestCases +from PathTests.TestPathDressupHoldingTags import SquareTagTestCases