diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index 7ba19b451e..1c37306219 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -149,6 +149,7 @@ SET(Generator_SRCS Generators/drill_generator.py Generators/rotation_generator.py Generators/helix_generator.py + Generators/toolchange_generator.py ) SET(PathScripts_post_SRCS @@ -240,6 +241,7 @@ SET(PathTests_SRCS PathTests/TestPathPropertyBag.py PathTests/TestPathSetupSheet.py PathTests/TestPathStock.py + PathTests/TestPathToolChangeGenerator.py PathTests/TestPathThreadMilling.py PathTests/TestPathTool.py PathTests/TestPathToolBit.py diff --git a/src/Mod/Path/Generators/toolchange_generator.py b/src/Mod/Path/Generators/toolchange_generator.py new file mode 100644 index 0000000000..9a6f67a725 --- /dev/null +++ b/src/Mod/Path/Generators/toolchange_generator.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- +# *************************************************************************** +# * Copyright (c) 2021 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 PathScripts.PathLog as PathLog +import Path +from enum import Enum + +__title__ = "Toolchange Path Generator" +__author__ = "sliptonic (Brad Collette)" +__url__ = "https://www.freecadweb.org" +__doc__ = "Generates the rotation toolpath" + + +if False: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) + PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + + +class SpindleDirection(Enum): + OFF = "OFF" + CW = "M3" + CCW = "M4" + + +def generate( + toolnumber, toollabel, spindlespeed=0, spindledirection=SpindleDirection.OFF +): + """ + Generates Gcode for a simple toolchange. + + """ + + PathLog.track( + f"toolnumber:{toolnumber} toollabel: {toollabel} spindlespeed:{spindlespeed} spindledirection: {spindledirection}" + ) + + if spindledirection is not SpindleDirection.OFF and spindlespeed == 0: + spindledirection = SpindleDirection.OFF + # raise ValueError("Turning on spindle with zero speed is invalid") + + if spindlespeed < 0: + raise ValueError("Spindle speed must be a positive value") + + commands = [] + + commands.append(Path.Command(f"({toollabel})")) + commands.append(Path.Command("M6", {"T": int(toolnumber)})) + + if spindledirection is SpindleDirection.OFF: + return commands + else: + commands.append(Path.Command(spindledirection.value, {"S": spindlespeed})) + + PathLog.track(commands) + return commands diff --git a/src/Mod/Path/PathScripts/PathToolController.py b/src/Mod/Path/PathScripts/PathToolController.py index 5704fcfb87..00df54f7fc 100644 --- a/src/Mod/Path/PathScripts/PathToolController.py +++ b/src/Mod/Path/PathScripts/PathToolController.py @@ -28,6 +28,8 @@ import Path import PathScripts.PathLog as PathLog import PathScripts.PathPreferences as PathPreferences import PathScripts.PathToolBit as PathToolBit +from Generators import toolchange_generator as toolchange_generator +from Generators.toolchange_generator import SpindleDirection if False: @@ -129,6 +131,7 @@ class ToolController: # Enumeration lists for App::PropertyEnumeration properties enums = { "SpindleDir": [ + (translate("Path_ToolController", "None"), "None"), (translate("Path_ToolController", "Forward"), "Forward"), (translate("Path_ToolController", "Reverse"), "Reverse"), ], # this is the direction that the profile runs @@ -259,27 +262,30 @@ class ToolController: def execute(self, obj): PathLog.track() - commands = "" - commands += "(" + obj.Label + ")" + "\n" - commands += "M6 T" + str(obj.ToolNumber) + "\n" + args = { + "toolnumber": obj.ToolNumber, + "toollabel": obj.Label, + "spindlespeed": obj.SpindleSpeed, + "spindledirection": SpindleDirection.OFF, + } - # If a toolbit is used, check to see if spindlepower is allowed. - # This is to prevent accidentally spinning the spindle with an - # unpowered tool like probe or dragknife - - allowSpindlePower = True - if not isinstance(obj.Tool, Path.Tool) and hasattr(obj.Tool, "SpindlePower"): - allowSpindlePower = obj.Tool.SpindlePower - - if allowSpindlePower: - PathLog.debug("selected tool preventing spindle power") - if obj.SpindleDir == "Forward": - commands += "M3 S" + str(obj.SpindleSpeed) + "\n" + if hasattr(obj.Tool, "SpindlePower"): + if not obj.Tool.SpindlePower: + args["spindledirection"] = SpindleDirection.OFF else: - commands += "M4 S" + str(obj.SpindleSpeed) + "\n" + if obj.SpindleDir == "Forward": + args["spindledirection"] = SpindleDirection.CW + else: + args["spindledirection"] = SpindleDirection.CCW + elif obj.SpindleDir == "None": + args["spindledirection"] = SpindleDirection.OFF + else: + if obj.SpindleDir == "Forward": + args["spindledirection"] = SpindleDirection.CW + else: + args["spindledirection"] = SpindleDirection.CCW - if commands == "": - commands += "(No commands processed)" + commands = toolchange_generator.generate(**args) path = Path.Path(commands) obj.Path = path diff --git a/src/Mod/Path/PathScripts/drillableLib.py b/src/Mod/Path/PathScripts/drillableLib.py index 7a5fe4108b..e264e36bcc 100644 --- a/src/Mod/Path/PathScripts/drillableLib.py +++ b/src/Mod/Path/PathScripts/drillableLib.py @@ -11,6 +11,34 @@ else: PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) +def checkForBlindHole(baseshape, selectedFace): + """ + check for blind holes, returns the bottom face if found, none + if the hole is a thru-hole + """ + circularFaces = [ + f + for f in baseshape.Faces + if len(f.OuterWire.Edges) == 1 + and type(f.OuterWire.Edges[0].Curve) == Part.Circle + ] + + circularFaceEdges = [f.OuterWire.Edges[0] for f in circularFaces] + commonedges = [ + i for i in selectedFace.Edges for x in circularFaceEdges if i.isSame(x) + ] + + bottomface = None + for f in circularFaces: + for e in f.Edges: + for i in commonedges: + if e.isSame(i): + bottomface = f + break + + return bottomface + + def isDrillableCylinder(obj, candidate, tooldiameter=None, vector=App.Vector(0, 0, 1)): """ checks if a candidate cylindrical face is drillable @@ -69,10 +97,20 @@ def isDrillableCylinder(obj, candidate, tooldiameter=None, vector=App.Vector(0, if not matchToolDiameter and not matchVector: return True - elif matchToolDiameter and tooldiameter / 2 > candidate.Surface.Radius: + if matchToolDiameter and tooldiameter / 2 > candidate.Surface.Radius: PathLog.debug("The tool is larger than the target") return False + bottomface = checkForBlindHole(obj, candidate) + PathLog.track("candidate is a blind hole") + + if ( + bottomface is not None and matchVector + ): # blind holes only drillable at exact vector + result = compareVecs(bottomface.normalAt(0, 0), vector, exact=True) + PathLog.track(result) + return result + elif matchVector and not (compareVecs(getSeam(candidate).Curve.Direction, vector)): PathLog.debug("The feature is not aligned with the given vector") return False @@ -80,7 +118,7 @@ def isDrillableCylinder(obj, candidate, tooldiameter=None, vector=App.Vector(0, return True -def isDrillableCircle(obj, candidate, tooldiameter=None, vector=App.Vector(0, 0, 1)): +def isDrillableFace(obj, candidate, tooldiameter=None, vector=App.Vector(0, 0, 1)): """ checks if a flat face or edge is drillable """ @@ -93,36 +131,57 @@ def isDrillableCircle(obj, candidate, tooldiameter=None, vector=App.Vector(0, 0, ) ) - if candidate.ShapeType == "Face": - if not type(candidate.Surface) == Part.Plane: - PathLog.debug("Drilling on non-planar faces not supported") - return False + PathLog.track() + if not type(candidate.Surface) == Part.Plane: + PathLog.debug("Drilling on non-planar faces not supported") + return False - if ( - len(candidate.Edges) == 1 and type(candidate.Edges[0].Curve) == Part.Circle - ): # Regular circular face - edge = candidate.Edges[0] - elif ( - len(candidate.Edges) == 2 - and type(candidate.Edges[0].Curve) == Part.Circle - and type(candidate.Edges[1].Curve) == Part.Circle - ): # process a donut - e1 = candidate.Edges[0] - e2 = candidate.Edges[1] - edge = e1 if e1.Curve.Radius < e2.Curve.Radius else e2 - else: - PathLog.debug( - "expected a Face with one or two circular edges got a face with {} edges".format( - len(candidate.Edges) - ) + if ( + len(candidate.Edges) == 1 and type(candidate.Edges[0].Curve) == Part.Circle + ): # Regular circular face + edge = candidate.Edges[0] + elif ( + len(candidate.Edges) == 2 + and type(candidate.Edges[0].Curve) == Part.Circle + and type(candidate.Edges[1].Curve) == Part.Circle + ): # process a donut + e1 = candidate.Edges[0] + e2 = candidate.Edges[1] + edge = e1 if e1.Curve.Radius < e2.Curve.Radius else e2 + else: + PathLog.debug( + "expected a Face with one or two circular edges got a face with {} edges".format( + len(candidate.Edges) ) + ) + return False + if vector is not None: # Check for blind hole alignment + if not compareVecs(candidate.normalAt(0, 0), vector, exact=True): return False + if matchToolDiameter and edge.Curve.Radius < tooldiameter / 2: + PathLog.track() + return False + else: + return True - else: # edge - edge = candidate - if not (isinstance(edge.Curve, Part.Circle) and edge.isClosed()): - PathLog.debug("expected a closed circular edge") - return False + +def isDrillableEdge(obj, candidate, tooldiameter=None, vector=App.Vector(0, 0, 1)): + """ + checks if an edge is drillable + """ + + matchToolDiameter = tooldiameter is not None + matchVector = vector is not None + PathLog.debug( + "\n match tool diameter {} \n match vector {}".format( + matchToolDiameter, matchVector + ) + ) + + edge = candidate + if not (isinstance(edge.Curve, Part.Circle) and edge.isClosed()): + PathLog.debug("expected a closed circular edge") + return False if not hasattr(edge.Curve, "Radius"): PathLog.debug("The Feature edge has no radius - Ellipse.") @@ -131,11 +190,11 @@ def isDrillableCircle(obj, candidate, tooldiameter=None, vector=App.Vector(0, 0, if not matchToolDiameter and not matchVector: return True - elif matchToolDiameter and tooldiameter / 2 > edge.Curve.Radius: + if matchToolDiameter and tooldiameter / 2 > edge.Curve.Radius: PathLog.debug("The tool is larger than the target") return False - elif matchVector and not (compareVecs(edge.Curve.Axis, vector)): + if matchVector and not (compareVecs(edge.Curve.Axis, vector)): PathLog.debug("The feature is not aligned with the given vector") return False else: @@ -175,30 +234,38 @@ def isDrillable(obj, candidate, tooldiameter=None, vector=App.Vector(0, 0, 1)): raise TypeError("expected a Face or Edge. Got a {}".format(candidate.ShapeType)) try: - if candidate.ShapeType == "Face" and isinstance( - candidate.Surface, Part.Cylinder - ): - return isDrillableCylinder(obj, candidate, tooldiameter, vector) + if candidate.ShapeType == "Face": + if isinstance(candidate.Surface, Part.Cylinder): + return isDrillableCylinder(obj, candidate, tooldiameter, vector) + else: + return isDrillableFace(obj, candidate, tooldiameter, vector) + if candidate.ShapeType == "Edge": + return isDrillableEdge(obj, candidate, tooldiameter, vector) else: - return isDrillableCircle(obj, candidate, tooldiameter, vector) + return False + except TypeError as e: PathLog.debug(e) return False # raise TypeError("{}".format(e)) -def compareVecs(vec1, vec2): +def compareVecs(vec1, vec2, exact=False): """ - compare the two vectors to see if they are aligned for drilling + compare the two vectors to see if they are aligned for drilling. + if exact is True, vectors must match direction. Otherwise, alignment can indicate the vectors are the same or exactly opposite """ angle = vec1.getAngle(vec2) angle = 0 if math.isnan(angle) else math.degrees(angle) PathLog.debug("vector angle: {}".format(angle)) - return numpy.isclose(angle, 0, rtol=1e-05, atol=1e-06) or numpy.isclose( - angle, 180, rtol=1e-05, atol=1e-06 - ) + if exact: + return numpy.isclose(angle, 0, rtol=1e-05, atol=1e-06) + else: + return numpy.isclose(angle, 0, rtol=1e-05, atol=1e-06) or numpy.isclose( + angle, 180, rtol=1e-05, atol=1e-06 + ) def getDrillableTargets(obj, ToolDiameter=None, vector=App.Vector(0, 0, 1)): diff --git a/src/Mod/Path/PathTests/Drilling_1.FCStd b/src/Mod/Path/PathTests/Drilling_1.FCStd index 8ad87ac69a..ce3ffdb5c9 100644 Binary files a/src/Mod/Path/PathTests/Drilling_1.FCStd and b/src/Mod/Path/PathTests/Drilling_1.FCStd differ diff --git a/src/Mod/Path/PathTests/TestPathDrillable.py b/src/Mod/Path/PathTests/TestPathDrillable.py index 76728b0149..24a6549841 100644 --- a/src/Mod/Path/PathTests/TestPathDrillable.py +++ b/src/Mod/Path/PathTests/TestPathDrillable.py @@ -33,10 +33,11 @@ if False: else: PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + class TestPathDrillable(PathTestUtils.PathTestBase): def setUp(self): self.doc = App.open(App.getHomePath() + "/Mod/Path/PathTests/Drilling_1.FCStd") - self.obj = self.doc.getObject("Pocket010") + self.obj = self.doc.getObject("Pocket011") def tearDown(self): App.closeDocument(self.doc.Name) @@ -68,16 +69,10 @@ class TestPathDrillable(PathTestUtils.PathTestBase): TypeError, lambda: drillableLib.isDrillable(self.obj.Shape, candidate) ) - # # partial cylinder - # candidate = self.obj.getSubObject("Face10") - # self.assertRaises( - # TypeError, lambda: drillableLib.isDrillable(self.obj.Shape, candidate) - # ) - # Test cylinder faces # thru-hole - candidate = self.obj.getSubObject("Face25") + candidate = self.obj.getSubObject("Face30") # Typical drilling self.assertTrue(drillableLib.isDrillable(self.obj.Shape, candidate)) @@ -93,7 +88,7 @@ class TestPathDrillable(PathTestUtils.PathTestBase): ) # off-axis hole - candidate = self.obj.getSubObject("Face42") + candidate = self.obj.getSubObject("Face44") # Typical drilling self.assertFalse(drillableLib.isDrillable(self.obj.Shape, candidate)) @@ -106,26 +101,26 @@ class TestPathDrillable(PathTestUtils.PathTestBase): # Passing explicit vector self.assertTrue( drillableLib.isDrillable( - self.obj.Shape, candidate, vector=App.Vector(0, 1, 0) + self.obj.Shape, candidate, vector=App.Vector(0, -1, 0) ) ) # Drilling with smaller bit self.assertTrue( drillableLib.isDrillable( - self.obj.Shape, candidate, tooldiameter=10, vector=App.Vector(0, 1, 0) + self.obj.Shape, candidate, tooldiameter=10, vector=App.Vector(0, -1, 0) ) ) # Drilling with bit too large self.assertFalse( drillableLib.isDrillable( - self.obj.Shape, candidate, tooldiameter=30, vector=App.Vector(0, 1, 0) + self.obj.Shape, candidate, tooldiameter=30, vector=App.Vector(0, -1, 0) ) ) # ellipse hole - candidate = self.obj.getSubObject("Face20") + candidate = self.obj.getSubObject("Face29") # Typical drilling self.assertFalse(drillableLib.isDrillable(self.obj.Shape, candidate)) @@ -136,7 +131,7 @@ class TestPathDrillable(PathTestUtils.PathTestBase): ) # raised cylinder - candidate = self.obj.getSubObject("Face30") + candidate = self.obj.getSubObject("Face32") # Typical drilling self.assertFalse(drillableLib.isDrillable(self.obj.Shape, candidate)) @@ -146,9 +141,8 @@ class TestPathDrillable(PathTestUtils.PathTestBase): drillableLib.isDrillable(self.obj.Shape, candidate, vector=None) ) - # cylinder on slope - candidate = self.obj.getSubObject("Face26") + candidate = self.obj.getSubObject("Face24") # Typical drilling self.assertTrue(drillableLib.isDrillable(self.obj.Shape, candidate)) @@ -158,7 +152,7 @@ class TestPathDrillable(PathTestUtils.PathTestBase): ) # Circular Faces - candidate = self.obj.getSubObject("Face51") + candidate = self.obj.getSubObject("Face54") # Typical drilling self.assertTrue(drillableLib.isDrillable(self.obj.Shape, candidate)) @@ -186,7 +180,7 @@ class TestPathDrillable(PathTestUtils.PathTestBase): ) # off-axis circular face hole - candidate = self.obj.getSubObject("Face54") + candidate = self.obj.getSubObject("Face58") # Typical drilling self.assertFalse(drillableLib.isDrillable(self.obj.Shape, candidate)) @@ -199,12 +193,12 @@ class TestPathDrillable(PathTestUtils.PathTestBase): # Passing explicit vector self.assertTrue( drillableLib.isDrillable( - self.obj.Shape, candidate, vector=App.Vector(0, 1, 0) + self.obj.Shape, candidate, vector=App.Vector(0, -1, 0) ) ) # raised face - candidate = self.obj.getSubObject("Face45") + candidate = self.obj.getSubObject("Face49") # Typical drilling self.assertTrue(drillableLib.isDrillable(self.obj.Shape, candidate)) @@ -214,7 +208,7 @@ class TestPathDrillable(PathTestUtils.PathTestBase): ) # interrupted Face - candidate = self.obj.getSubObject("Face46") + candidate = self.obj.getSubObject("Face50") # Typical drilling self.assertFalse(drillableLib.isDrillable(self.obj.Shape, candidate)) @@ -224,7 +218,7 @@ class TestPathDrillable(PathTestUtils.PathTestBase): ) # donut face - candidate = self.obj.getSubObject("Face44") + candidate = self.obj.getSubObject("Face48") # Typical drilling self.assertTrue(drillableLib.isDrillable(self.obj.Shape, candidate)) @@ -235,7 +229,7 @@ class TestPathDrillable(PathTestUtils.PathTestBase): # Test edges # circular edge - candidate = self.obj.getSubObject("Edge53") + candidate = self.obj.getSubObject("Edge55") # Typical drilling self.assertTrue(drillableLib.isDrillable(self.obj.Shape, candidate)) @@ -259,11 +253,13 @@ class TestPathDrillable(PathTestUtils.PathTestBase): # Drilling with bit too large self.assertFalse( - drillableLib.isDrillable(self.obj.Shape, candidate, tooldiameter=30) + drillableLib.isDrillable( + self.obj.Shape, candidate, tooldiameter=30, vector=None + ) ) # off-axis circular edge - candidate = self.obj.getSubObject("Edge72") + candidate = self.obj.getSubObject("Edge74") # Typical drilling self.assertFalse(drillableLib.isDrillable(self.obj.Shape, candidate)) @@ -281,7 +277,7 @@ class TestPathDrillable(PathTestUtils.PathTestBase): ) # incomplete circular edge - candidate = self.obj.getSubObject("Edge108") + candidate = self.obj.getSubObject("Edge39") # Typical drilling self.assertFalse(drillableLib.isDrillable(self.obj.Shape, candidate)) @@ -291,7 +287,7 @@ class TestPathDrillable(PathTestUtils.PathTestBase): ) # elliptical edge - candidate = self.obj.getSubObject("Edge54") + candidate = self.obj.getSubObject("Edge56") # Typical drilling self.assertFalse(drillableLib.isDrillable(self.obj.Shape, candidate)) @@ -300,14 +296,15 @@ class TestPathDrillable(PathTestUtils.PathTestBase): drillableLib.isDrillable(self.obj.Shape, candidate, vector=None) ) - def test20(self): """Test getDrillableTargets""" results = drillableLib.getDrillableTargets(self.obj) self.assertEqual(len(results), 15) results = drillableLib.getDrillableTargets(self.obj, vector=None) - self.assertEqual(len(results), 18) + self.assertEqual(len(results), 20) - results = drillableLib.getDrillableTargets(self.obj, ToolDiameter= 20, vector=None) + results = drillableLib.getDrillableTargets( + self.obj, ToolDiameter=20, vector=None + ) self.assertEqual(len(results), 5) diff --git a/src/Mod/Path/PathTests/TestPathToolChangeGenerator.py b/src/Mod/Path/PathTests/TestPathToolChangeGenerator.py new file mode 100644 index 0000000000..f4ec58b06a --- /dev/null +++ b/src/Mod/Path/PathTests/TestPathToolChangeGenerator.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- +# *************************************************************************** +# * Copyright (c) 2021 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 Path +import Generators.toolchange_generator as generator +from Generators.toolchange_generator import SpindleDirection + +import PathScripts.PathLog as PathLog +import PathTests.PathTestUtils as PathTestUtils + +PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) +PathLog.trackModule(PathLog.thisModule()) + + +class TestPathToolChangeGenerator(PathTestUtils.PathTestBase): + def test00(self): + """Test Basic Tool Change Generator Return""" + + args = { + "toolnumber": 1, + "toollabel": "My Label", + "spindlespeed": 500, + "spindledirection": SpindleDirection.OFF, + } + + results = generator.generate(**args) + + # Get a label + self.assertTrue(len(results) == 2) + commentcommand = results[0] + self.assertTrue(isinstance(commentcommand, Path.Command)) + self.assertTrue(commentcommand.toGCode() == "(My Label)") + + # Get a tool command + toolcommand = results[1] + self.assertTrue(toolcommand.Name == "M6") + + # Turn on the spindle + args["spindledirection"] = SpindleDirection.CW + results = generator.generate(**args) + self.assertTrue(len(results) == 3) + + speedcommand = results[2] + self.assertTrue(speedcommand.Name == "M3") + self.assertTrue(speedcommand.Parameters["S"] == 500) + + # speed zero with spindle on + args["spindlespeed"] = 0 + results = generator.generate(**args) + self.assertTrue(len(results) == 2) + PathLog.track(results) + + # negative spindlespeed + args["spindlespeed"] = -10 + self.assertRaises(ValueError, generator.generate, **args) diff --git a/src/Mod/Path/TestPathApp.py b/src/Mod/Path/TestPathApp.py index df94f86591..dab2a245dc 100644 --- a/src/Mod/Path/TestPathApp.py +++ b/src/Mod/Path/TestPathApp.py @@ -46,6 +46,7 @@ from PathTests.TestPathStock import TestPathStock from PathTests.TestPathThreadMilling import TestPathThreadMilling from PathTests.TestPathTool import TestPathTool from PathTests.TestPathToolBit import TestPathToolBit +from PathTests.TestPathToolChangeGenerator import TestPathToolChangeGenerator from PathTests.TestPathToolController import TestPathToolController from PathTests.TestPathTooltable import TestPathTooltable from PathTests.TestPathUtil import TestPathUtil @@ -76,6 +77,7 @@ False if TestPathStock.__name__ else True False if TestPathThreadMilling.__name__ else True False if TestPathTool.__name__ else True False if TestPathToolBit.__name__ else True +False if TestPathToolChangeGenerator.__name__ else True False if TestPathToolController.__name__ else True False if TestPathTooltable.__name__ else True False if TestPathUtil.__name__ else True