diff --git a/src/Mod/Path/Generators/toolchange_generator.py b/src/Mod/Path/Generators/toolchange_generator.py index 6fd9000a91..9a6f67a725 100644 --- a/src/Mod/Path/Generators/toolchange_generator.py +++ b/src/Mod/Path/Generators/toolchange_generator.py @@ -22,9 +22,7 @@ import PathScripts.PathLog as PathLog -import math import Path -import FreeCAD from enum import Enum __title__ = "Toolchange Path Generator" @@ -33,7 +31,7 @@ __url__ = "https://www.freecadweb.org" __doc__ = "Generates the rotation toolpath" -if True: +if False: PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) PathLog.trackModule(PathLog.thisModule()) else: @@ -59,7 +57,8 @@ def generate( ) if spindledirection is not SpindleDirection.OFF and spindlespeed == 0: - raise ValueError("Turning on spindle with zero speed is invalid") + 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") @@ -74,4 +73,5 @@ def generate( 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 3702fe2dfa..00df54f7fc 100644 --- a/src/Mod/Path/PathScripts/PathToolController.py +++ b/src/Mod/Path/PathScripts/PathToolController.py @@ -277,6 +277,8 @@ class ToolController: 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 diff --git a/src/Mod/Path/PathScripts/drillableLib.py b/src/Mod/Path/PathScripts/drillableLib.py index d5d49d54a7..e264e36bcc 100644 --- a/src/Mod/Path/PathScripts/drillableLib.py +++ b/src/Mod/Path/PathScripts/drillableLib.py @@ -4,7 +4,7 @@ import Part import numpy import math -if True: +if False: PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) PathLog.trackModule(PathLog.thisModule()) else: @@ -101,22 +101,24 @@ def isDrillableCylinder(obj, candidate, tooldiameter=None, vector=App.Vector(0, PathLog.debug("The tool is larger than the target") return False - if matchVector and not (compareVecs(getSeam(candidate).Curve.Direction, vector)): - PathLog.debug("The feature is not aligned with the given vector") - return False - bottomface = checkForBlindHole(obj, candidate) - PathLog.track(bottomface) - if bottomface is None: # thru hole - return True - else: - PathLog.track(bottomface.normalAt(0,0)) - result = compareVecs(bottomface.normalAt(0, 0), App.Vector(0, 0, 1), exact=True) + 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 + else: + 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 """ @@ -129,41 +131,57 @@ def isDrillableCircle(obj, candidate, tooldiameter=None, vector=App.Vector(0, 0, ) ) - if candidate.ShapeType == "Face": - PathLog.track() - 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 vector is not None: # Check for blind hole alignment - if not compareVecs(candidate.normalAt(0,0), vector, exact=True): - PathLog.track(False) - 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.") @@ -216,12 +234,16 @@ 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 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 index f9e61fd34e..f4ec58b06a 100644 --- a/src/Mod/Path/PathTests/TestPathToolChangeGenerator.py +++ b/src/Mod/Path/PathTests/TestPathToolChangeGenerator.py @@ -21,15 +21,13 @@ # *************************************************************************** import Path -import FreeCAD import Generators.toolchange_generator as generator from Generators.toolchange_generator import SpindleDirection import PathScripts.PathLog as PathLog import PathTests.PathTestUtils as PathTestUtils -import numpy as np -PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) +PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) PathLog.trackModule(PathLog.thisModule()) @@ -67,7 +65,9 @@ class TestPathToolChangeGenerator(PathTestUtils.PathTestBase): # speed zero with spindle on args["spindlespeed"] = 0 - self.assertRaises(ValueError, generator.generate, **args) + results = generator.generate(**args) + self.assertTrue(len(results) == 2) + PathLog.track(results) # negative spindlespeed args["spindlespeed"] = -10