diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index c82bb15bcc..728e8fe0ea 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -10,6 +10,8 @@ set(Path_Scripts Init.py PathCommands.py TestPathApp.py + PathMachineState.py + PathFeedRate.py ) if(BUILD_GUI) @@ -144,6 +146,7 @@ SET(PathScripts_SRCS SET(Generator_SRCS Generators/drill_generator.py + Generators/rotation_generator.py ) SET(PathScripts_post_SRCS @@ -221,8 +224,10 @@ SET(PathTests_SRCS PathTests/TestPathDressupDogbone.py PathTests/TestPathDressupHoldingTags.py PathTests/TestPathDrillGenerator.py + PathTests/TestPathRotationGenerator.py PathTests/TestPathGeom.py PathTests/TestPathHelix.py + PathTests/TestPathHelpers.py PathTests/TestPathLog.py PathTests/TestPathOpTools.py PathTests/TestPathPost.py diff --git a/src/Mod/Path/PathFeedRate.py b/src/Mod/Path/PathFeedRate.py index 64ee30f501..bd33265aab 100644 --- a/src/Mod/Path/PathFeedRate.py +++ b/src/Mod/Path/PathFeedRate.py @@ -73,16 +73,16 @@ def setFeedRate(commandlist, ToolController): if command.Name not in feedcommands + rapidcommands: continue - if _isVertical(machine.getPosition(), command): + if _isVertical(FreeCAD.Vector(machine.X, machine.Y, machine.Z), command): rate = ( ToolController.VertRapid.Value - if command in rapidcommands + if command.Name in rapidcommands else ToolController.VertFeed.Value ) else: rate = ( ToolController.HorizRapid.Value - if command in rapidcommands + if command.Name in rapidcommands else ToolController.HorizFeed.Value ) diff --git a/src/Mod/Path/PathMachineState.py b/src/Mod/Path/PathMachineState.py index f852c66ef9..e57ebb9476 100644 --- a/src/Mod/Path/PathMachineState.py +++ b/src/Mod/Path/PathMachineState.py @@ -58,48 +58,61 @@ class MachineState: "G59.9", ] - X: float = field(default=None) - Y: float = field(default=None) - Z: float = field(default=None) - A: float = field(default=None) - B: float = field(default=None) - C: float = field(default=None) + X: float = field(default=0) + Y: float = field(default=0) + Z: float = field(default=0) + A: float = field(default=0) + B: float = field(default=0) + C: float = field(default=0) F: float = field(default=None) Coolant: bool = field(default=False) WCS: str = field(default="G54") Spindle: str = field(default="off") S: int = field(default=0) - T: str = field(default=None) + T: int = field(default=None) def addCommand(self, command): + """Processes a command and updates the internal state of the machine. Returns true if the command has alterned the machine state""" + oldstate = self.getState() if command.Name == "M6": - self.T = command.Parameters["T"] - return + self.T = int(command.Parameters["T"]) + return not oldstate == self.getState() if command.Name in ["M3", "M4"]: self.S = command.Parameters["S"] - self.SpindApple = "CW" if command.Name == "M3" else "CCW" - return + self.Spindle = "CW" if command.Name == "M3" else "CCW" + return not oldstate == self.getState() if command.Name in ["M2", "M5"]: self.S = 0 self.Spindle = "off" - return + return not oldstate == self.getState() if command.Name in self.WCSLIST: self.WCS = command.Name - return + return not oldstate == self.getState() for p in command.Parameters: self.__setattr__(p, command.Parameters[p]) - def getPosition(self): - """ - Returns an App.Vector of the current location - """ - x = 0 if self.X is None else self.X - y = 0 if self.Y is None else self.Y - z = 0 if self.Z is None else self.Z + return not oldstate == self.getState() - return FreeCAD.Vector(x, y, z) + def getState(self): + """ + Returns a dictionary of the current machine state + """ + state = {} + state['X'] = self.X + state['Y'] = self.Y + state['Z'] = self.Z + state['A'] = self.A + state['B'] = self.B + state['C'] = self.C + state['F'] = self.F + state['Coolant'] = self.Coolant + state['WCS'] = self.WCS + state['Spindle'] = self.Spindle + state['S'] = self.S + state['T'] = self.T + return state diff --git a/src/Mod/Path/PathScripts/drillableLib.py b/src/Mod/Path/PathScripts/drillableLib.py new file mode 100644 index 0000000000..4e8dd59342 --- /dev/null +++ b/src/Mod/Path/PathScripts/drillableLib.py @@ -0,0 +1,235 @@ +import PathScripts.PathLog as PathLog +import FreeCAD as App +import Part +import numpy +import math + +if False: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) + PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + +def isDrillableCylinder(obj, candidate, tooldiameter=None, vector=App.Vector(0, 0, 1)): + """ + checks if a candidate cylindrical face is drillable + """ + + matchToolDiameter = tooldiameter is not None + matchVector = vector is not None + + PathLog.debug( + "\n match tool diameter {} \n match vector {}".format( + matchToolDiameter, matchVector + ) + ) + + def raisedFeature(obj, candidate): + # check if the cylindrical 'lids' are inside the base + # object. This eliminates extruded circles but allows + # actual holes. + + startLidCenter = App.Vector( + candidate.BoundBox.Center.x, + candidate.BoundBox.Center.y, + candidate.BoundBox.ZMax, + ) + + endLidCenter = App.Vector( + candidate.BoundBox.Center.x, + candidate.BoundBox.Center.y, + candidate.BoundBox.ZMin, + ) + + return obj.isInside(startLidCenter, 1e-6, False) or obj.isInside( + endLidCenter, 1e-6, False + ) + + def getSeam(candidate): + # Finds the vertical seam edge in a cylinder + + for e in candidate.Edges: + if isinstance(e.Curve, Part.Line): # found the seam + return e + + if not candidate.ShapeType == "Face": + raise TypeError("expected a Face") + + if not str(candidate.Surface) == "": + raise TypeError("expected a cylinder") + + if len(candidate.Edges) != 3: + raise TypeError("cylinder does not have 3 edges. Not supported yet") + + if raisedFeature(obj, candidate): + PathLog.debug("The cylindrical face is a raised feature") + return False + + if not matchToolDiameter and not matchVector: + return True + + elif matchToolDiameter and tooldiameter / 2 > candidate.Surface.Radius: + PathLog.debug("The tool is larger than the target") + return False + + elif matchVector and not (compareVecs(getSeam(candidate).Curve.Direction, vector)): + PathLog.debug("The feature is not aligned with the given vector") + return False + else: + return True + + +def isDrillableCircle(obj, candidate, tooldiameter=None, vector=App.Vector(0, 0, 1)): + """ + checks if a flat face or 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 + ) + ) + + if candidate.ShapeType == "Face": + 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) + ) + ) + return False + + else: # edge + 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.") + return False + + if not matchToolDiameter and not matchVector: + return True + + elif 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)): + PathLog.debug("The feature is not aligned with the given vector") + return False + else: + return True + + +def isDrillable(obj, candidate, tooldiameter=None, vector=App.Vector(0, 0, 1)): + """ + Checks candidates to see if they can be drilled at the given vector. + Candidates can be either faces - circular or cylindrical or circular edges. + The tooldiameter can be optionally passed. if passed, the check will return + False for any holes smaller than the tooldiameter. + + vector defaults to (0,0,1) which aligns with the Z axis. By default will return False + for any candidate not drillable in this orientation. Pass 'None' to vector to test whether + the hole is drillable at any orientation. + + obj=Shape + candidate = Face or Edge + tooldiameter=float + vector=App.Vector or None + + """ + PathLog.debug( + "obj: {} candidate: {} tooldiameter {} vector {}".format( + obj, candidate, tooldiameter, vector + ) + ) + + if list == type(obj): + for shape in obj: + if isDrillable(shape, candidate, tooldiameter, vector): + return (True, shape) + return (False, None) + + if candidate.ShapeType not in ["Face", "Edge"]: + raise TypeError("expected a Face or Edge. Got a {}".format(candidate.ShapeType)) + + try: + if ( + candidate.ShapeType == "Face" + and str(candidate.Surface) == "" + ): + return isDrillableCylinder(obj, candidate, tooldiameter, vector) + else: + return isDrillableCircle(obj, candidate, tooldiameter, vector) + except TypeError as e: + PathLog.debug(e) + return False + # raise TypeError("{}".format(e)) + + +def compareVecs(vec1, vec2): + """ + compare the two vectors to see if they are aligned for drilling + 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 + ) + + +def getDrillableTargets(obj, ToolDiameter=None, vector=App.Vector(0, 0, 1)): + """ + Returns a list of tuples for drillable subelements from the given object + [(obj,'Face1'),(obj,'Face3')] + + Finds cylindrical faces that are larger than the tool diameter (if provided) and + oriented with the vector. If vector is None, all drillables are returned + + """ + + shp = obj.Shape + + results = [] + for i in range(1, len(shp.Faces)): + fname = 'Face{}'.format(i) + PathLog.debug(fname) + candidate = obj.getSubObject(fname) + + if not str(candidate.Surface) == '': + continue + + try: + drillable = isDrillable(shp, candidate, tooldiameter=ToolDiameter, vector=vector) + PathLog.debug("fname: {} : drillable {}".format(fname, drillable)) + except Exception as e: + PathLog.debug(e) + continue + + if drillable: + results.append((obj,fname)) + + return results diff --git a/src/Mod/Path/PathTests/Drilling_1.FCStd b/src/Mod/Path/PathTests/Drilling_1.FCStd new file mode 100644 index 0000000000..8ad87ac69a Binary files /dev/null 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 new file mode 100644 index 0000000000..c1eb7cc0dc --- /dev/null +++ b/src/Mod/Path/PathTests/TestPathDrillable.py @@ -0,0 +1,310 @@ +# -*- 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 FreeCAD as App +import PathScripts.PathLog as PathLog +import PathTests.PathTestUtils as PathTestUtils +import PathScripts.drillableLib as drillableLib + +PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) +PathLog.trackModule(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") + + def tearDown(self): + App.closeDocument(self.doc.Name) + + def test00(self): + """Test CompareVecs""" + + # Vec and origin + v1 = App.Vector(0, 0, 10) + v2 = App.Vector(0, 0, 0) + self.assertTrue(drillableLib.compareVecs(v1, v2)) + + # two valid vectors + v1 = App.Vector(0, 10, 0) + v2 = App.Vector(0, 20, 0) + self.assertTrue(drillableLib.compareVecs(v1, v2)) + + # two valid vectors not aligned + v1 = App.Vector(0, 10, 0) + v2 = App.Vector(10, 0, 0) + self.assertFalse(drillableLib.compareVecs(v1, v2)) + + def test10(self): + """Test isDrillable""" + + # Invalid types + candidate = self.obj.getSubObject("Vertex1") + self.assertRaises( + 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") + + # Typical drilling + self.assertTrue(drillableLib.isDrillable(self.obj.Shape, candidate)) + + # Drilling with smaller bit + self.assertTrue( + drillableLib.isDrillable(self.obj.Shape, candidate, tooldiameter=20) + ) + + # Drilling with bit too large + self.assertFalse( + drillableLib.isDrillable(self.obj.Shape, candidate, tooldiameter=30) + ) + + # off-axis hole + candidate = self.obj.getSubObject("Face42") + + # Typical drilling + self.assertFalse(drillableLib.isDrillable(self.obj.Shape, candidate)) + + # Passing None as vector + self.assertTrue( + drillableLib.isDrillable(self.obj.Shape, candidate, vector=None) + ) + + # Passing explicit vector + self.assertTrue( + drillableLib.isDrillable( + 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) + ) + ) + + # Drilling with bit too large + self.assertFalse( + drillableLib.isDrillable( + self.obj.Shape, candidate, tooldiameter=30, vector=App.Vector(0, 1, 0) + ) + ) + + # ellipse hole + candidate = self.obj.getSubObject("Face20") + + # Typical drilling + self.assertFalse(drillableLib.isDrillable(self.obj.Shape, candidate)) + + # Passing None as vector + self.assertFalse( + drillableLib.isDrillable(self.obj.Shape, candidate, vector=None) + ) + + # raised cylinder + candidate = self.obj.getSubObject("Face30") + + # Typical drilling + self.assertFalse(drillableLib.isDrillable(self.obj.Shape, candidate)) + + # Passing None as vector + self.assertFalse( + drillableLib.isDrillable(self.obj.Shape, candidate, vector=None) + ) + + + # cylinder on slope + candidate = self.obj.getSubObject("Face26") + # Typical drilling + self.assertTrue(drillableLib.isDrillable(self.obj.Shape, candidate)) + + # Passing None as vector + self.assertTrue( + drillableLib.isDrillable(self.obj.Shape, candidate, vector=None) + ) + + # Circular Faces + candidate = self.obj.getSubObject("Face51") + + # Typical drilling + self.assertTrue(drillableLib.isDrillable(self.obj.Shape, candidate)) + + # Passing None as vector + self.assertTrue( + drillableLib.isDrillable(self.obj.Shape, candidate, vector=None) + ) + + # Passing explicit vector + self.assertTrue( + drillableLib.isDrillable( + self.obj.Shape, candidate, vector=App.Vector(0, 0, 1) + ) + ) + + # Drilling with smaller bit + self.assertTrue( + drillableLib.isDrillable(self.obj.Shape, candidate, tooldiameter=10) + ) + + # Drilling with bit too large + self.assertFalse( + drillableLib.isDrillable(self.obj.Shape, candidate, tooldiameter=30) + ) + + # off-axis circular face hole + candidate = self.obj.getSubObject("Face54") + + # Typical drilling + self.assertFalse(drillableLib.isDrillable(self.obj.Shape, candidate)) + + # Passing None as vector + self.assertTrue( + drillableLib.isDrillable(self.obj.Shape, candidate, vector=None) + ) + + # Passing explicit vector + self.assertTrue( + drillableLib.isDrillable( + self.obj.Shape, candidate, vector=App.Vector(0, 1, 0) + ) + ) + + # raised face + candidate = self.obj.getSubObject("Face45") + # Typical drilling + self.assertTrue(drillableLib.isDrillable(self.obj.Shape, candidate)) + + # Passing None as vector + self.assertTrue( + drillableLib.isDrillable(self.obj.Shape, candidate, vector=None) + ) + + # interrupted Face + candidate = self.obj.getSubObject("Face46") + # Typical drilling + self.assertFalse(drillableLib.isDrillable(self.obj.Shape, candidate)) + + # Passing None as vector + self.assertFalse( + drillableLib.isDrillable(self.obj.Shape, candidate, vector=None) + ) + + # donut face + candidate = self.obj.getSubObject("Face44") + # Typical drilling + self.assertTrue(drillableLib.isDrillable(self.obj.Shape, candidate)) + + # Passing None as vector + self.assertTrue( + drillableLib.isDrillable(self.obj.Shape, candidate, vector=None) + ) + + # Test edges + # circular edge + candidate = self.obj.getSubObject("Edge53") + + # Typical drilling + self.assertTrue(drillableLib.isDrillable(self.obj.Shape, candidate)) + + # Passing None as vector + self.assertTrue( + drillableLib.isDrillable(self.obj.Shape, candidate, vector=None) + ) + + # Passing explicit vector + self.assertTrue( + drillableLib.isDrillable( + self.obj.Shape, candidate, vector=App.Vector(0, 0, 1) + ) + ) + + # Drilling with smaller bit + self.assertTrue( + drillableLib.isDrillable(self.obj.Shape, candidate, tooldiameter=10) + ) + + # Drilling with bit too large + self.assertFalse( + drillableLib.isDrillable(self.obj.Shape, candidate, tooldiameter=30) + ) + + # off-axis circular edge + candidate = self.obj.getSubObject("Edge72") + + # Typical drilling + self.assertFalse(drillableLib.isDrillable(self.obj.Shape, candidate)) + + # Passing None as vector + self.assertTrue( + drillableLib.isDrillable(self.obj.Shape, candidate, vector=None) + ) + + # Passing explicit vector + self.assertTrue( + drillableLib.isDrillable( + self.obj.Shape, candidate, vector=App.Vector(0, 1, 0) + ) + ) + + # incomplete circular edge + candidate = self.obj.getSubObject("Edge108") + # Typical drilling + self.assertFalse(drillableLib.isDrillable(self.obj.Shape, candidate)) + + # Passing None as vector + self.assertFalse( + drillableLib.isDrillable(self.obj.Shape, candidate, vector=None) + ) + + # elliptical edge + candidate = self.obj.getSubObject("Edge54") + # Typical drilling + self.assertFalse(drillableLib.isDrillable(self.obj.Shape, candidate)) + + # Passing None as vector + self.assertFalse( + 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) + + results = drillableLib.getDrillableTargets(self.obj, ToolDiameter= 20, vector=None) + self.assertEqual(len(results), 5) diff --git a/src/Mod/Path/PathTests/TestPathHelpers.py b/src/Mod/Path/PathTests/TestPathHelpers.py new file mode 100644 index 0000000000..6810c21f43 --- /dev/null +++ b/src/Mod/Path/PathTests/TestPathHelpers.py @@ -0,0 +1,103 @@ +# -*- 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 FreeCAD +import PathFeedRate +import PathMachineState +import Path +import PathScripts.PathToolController as PathToolController + +from PathTests.PathTestUtils import PathTestBase + + +class TestPathHelpers(PathTestBase): + def setUp(self): + self.doc = FreeCAD.newDocument("TestPathUtils") + + c1 = Path.Command("G0 Z10") + c2 = Path.Command("G0 X20 Y10") + c3 = Path.Command("G1 X20 Y10 Z5") + c4 = Path.Command("G1 X20 Y20") + + self.commandlist = [c1, c2, c3, c4] + + def tearDown(self): + FreeCAD.closeDocument("TestPathUtils") + + def test00(self): + """Test that FeedRate Helper populates horiz and vert feed rate based on TC""" + t = Path.Tool("test", "5.0") + tc = PathToolController.Create("TC0", t) + tc.VertRapid = 5 + tc.HorizRapid = 10 + tc.VertFeed = 15 + tc.HorizFeed = 20 + + resultlist = PathFeedRate.setFeedRate(self.commandlist, tc) + + self.assertTrue(resultlist[0].Parameters["F"] == 5) + self.assertTrue(resultlist[1].Parameters["F"] == 10) + self.assertTrue(resultlist[2].Parameters["F"] == 15) + self.assertTrue(resultlist[3].Parameters["F"] == 20) + + def test01(self): + """Test that Machine State initializes and stores position correctly""" + + machine = PathMachineState.MachineState() + state = machine.getState() + self.assertTrue(state['X'] == 0 ) + self.assertTrue(state['Y'] == 0 ) + self.assertTrue(state['Z'] == 0 ) + self.assertTrue(machine.WCS == "G54") + + for c in self.commandlist: + result = machine.addCommand(c) + + state = machine.getState() + self.assertTrue(state['X'] == 20 ) + self.assertTrue(state['Y'] == 20 ) + self.assertTrue(state['Z'] == 5 ) + + machine.addCommand(Path.Command("M3 S200")) + self.assertTrue(machine.S == 200) + self.assertTrue(machine.Spindle == "CW") + + machine.addCommand(Path.Command("M4 S200")) + self.assertTrue(machine.Spindle == "CCW") + + machine.addCommand(Path.Command("M2")) + self.assertTrue(machine.Spindle == "off") + self.assertTrue(machine.S == 0) + + machine.addCommand(Path.Command("G57")) + self.assertTrue(machine.WCS == "G57") + + machine.addCommand(Path.Command("M6 T5")) + self.assertTrue(machine.T == 5) + + # Test that non-change commands return false + result = machine.addCommand(Path.Command("G0 X20")) + self.assertFalse(result) + + result = machine.addCommand(Path.Command("G0 X30")) + self.assertTrue(result) + diff --git a/src/Mod/Path/TestPathApp.py b/src/Mod/Path/TestPathApp.py index 6f34e71c5a..37091fb6a2 100644 --- a/src/Mod/Path/TestPathApp.py +++ b/src/Mod/Path/TestPathApp.py @@ -22,6 +22,8 @@ import TestApp +# from PathTests.TestPathHelix import TestPathHelix +# from PathTests.TestPathPost import PathPostTestCases from PathTests.TestPathAdaptive import TestPathAdaptive from PathTests.TestPathCore import TestPathCore from PathTests.TestPathDeburr import TestPathDeburr @@ -30,12 +32,12 @@ from PathTests.TestPathDressupDogbone import TestDressupDogbone from PathTests.TestPathDressupHoldingTags import TestHoldingTags from PathTests.TestPathDrillGenerator import TestPathDrillGenerator from PathTests.TestPathGeom import TestPathGeom -# from PathTests.TestPathHelix import TestPathHelix +from PathTests.TestPathHelpers import TestPathHelpers from PathTests.TestPathLog import TestPathLog from PathTests.TestPathOpTools import TestPathOpTools -# from PathTests.TestPathPost import PathPostTestCases from PathTests.TestPathPreferences import TestPathPreferences from PathTests.TestPathPropertyBag import TestPathPropertyBag +from PathTests.TestPathRotationGenerator import TestPathRotationGenerator from PathTests.TestPathSetupSheet import TestPathSetupSheet from PathTests.TestPathStock import TestPathStock from PathTests.TestPathThreadMilling import TestPathThreadMilling @@ -57,10 +59,12 @@ False if TestPathCore.__name__ else True False if TestPathDeburr.__name__ else True False if TestPathGeom.__name__ else True # False if TestPathHelix.__name__ else True +False if TestPathHelpers.__name__ else True False if TestPathLog.__name__ else True False if TestPathOpTools.__name__ else True False if TestPathPreferences.__name__ else True False if TestPathPropertyBag.__name__ else True +False if TestPathRotationGenerator.__name__ else True False if TestPathSetupSheet.__name__ else True False if TestPathStock.__name__ else True False if TestPathThreadMilling.__name__ else True