From df6de8693e5a8dda5973480829306d113b4f3ca7 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Thu, 14 Jan 2021 23:16:15 -0800 Subject: [PATCH] Changed tool-bit search path to either absolute, local install or relative to context path --- src/Mod/Path/CMakeLists.txt | 3 + src/Mod/Path/PathScripts/PathPreferences.py | 21 +--- src/Mod/Path/PathScripts/PathToolBit.py | 86 ++++++------- src/Mod/Path/PathTests/TestPathToolBit.py | 119 +++++++++++++++--- .../Tools/Bit/test-path-tool-bit-bit-00.fctb | 12 ++ .../test-path-tool-bit-library-00.fctl | 41 ++++++ .../Shape/test-path-tool-bit-shape-00.fcstd | Bin 0 -> 11829 bytes 7 files changed, 203 insertions(+), 79 deletions(-) create mode 100644 src/Mod/Path/PathTests/Tools/Bit/test-path-tool-bit-bit-00.fctb create mode 100644 src/Mod/Path/PathTests/Tools/Library/test-path-tool-bit-library-00.fctl create mode 100644 src/Mod/Path/PathTests/Tools/Shape/test-path-tool-bit-shape-00.fcstd diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index 8e25c23ef3..918bb073b1 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -212,6 +212,9 @@ SET(PathTests_SRCS PathTests/TestPathUtil.py PathTests/TestPathVcarve.py PathTests/TestPathVoronoi.py + PathTests/Tools/Bit/test-path-tool-bit-bit-00.fctb + PathTests/Tools/Library/test-path-tool-bit-library-00.fctl + PathTests/Tools/Shape/test-path-tool-bit-shape-00.fcstd PathTests/boxtest.fcstd PathTests/test_centroid_00.ngc PathTests/test_geomop.fcstd diff --git a/src/Mod/Path/PathScripts/PathPreferences.py b/src/Mod/Path/PathScripts/PathPreferences.py index d8dad1d07c..5b3760a767 100644 --- a/src/Mod/Path/PathScripts/PathPreferences.py +++ b/src/Mod/Path/PathScripts/PathPreferences.py @@ -151,26 +151,9 @@ def searchPathsPost(): return paths -def searchPathsTool(sub='Bit'): +def searchPathsTool(sub): paths = [] - def appendPath(p, sub): - if p: - paths.append(os.path.join(p, 'Tools', sub)) - paths.append(os.path.join(p, sub)) - paths.append(p) - appendPath(defaultFilePath(), sub) - appendPath(macroFilePath(), sub) - appendPath(os.path.join(FreeCAD.getHomePath(), "Mod/Path/"), sub) - - if 'Bit' == sub: - paths.append("{}/Bit".format(os.path.dirname(lastPathToolLibrary()))) - paths.append(lastPathToolBit()) - - if 'Library' == sub: - paths.append(lastPathToolLibrary()) - if 'Shape' == sub: - paths.append(lastPathToolShape()) - + paths.append(os.path.join(FreeCAD.getHomePath(), 'Mod', 'Path', 'Tools', sub)) return paths diff --git a/src/Mod/Path/PathScripts/PathToolBit.py b/src/Mod/Path/PathScripts/PathToolBit.py index 4a72dedab2..a92ad85f64 100644 --- a/src/Mod/Path/PathScripts/PathToolBit.py +++ b/src/Mod/Path/PathScripts/PathToolBit.py @@ -54,56 +54,56 @@ PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) def translate(context, text, disambig=None): return PySide.QtCore.QCoreApplication.translate(context, text, disambig) -def _findTool(path, typ, dbg=_DebugFindTool): - PathLog.track(path) - if os.path.exists(path): # absolute reference +def _findToolFile(name, containerFile, typ, dbg=_DebugFindTool): + PathLog.track(name) + if os.path.exists(name): # absolute reference if dbg: - PathLog.debug("Found {} at {}".format(typ, path)) - return path + PathLog.debug("Found {} at {}".format(typ, name)) + return name - def searchFor(pname, fname): - # PathLog.debug("pname: {} fname: {}".format(pname, fname)) - if dbg: - PathLog.debug("Looking for {} in {}".format(pname, fname)) - if fname: - for p in PathPreferences.searchPathsTool(typ): - PathLog.track(p) - f = os.path.join(p, fname) - if dbg: - PathLog.debug(" Checking {}".format(f)) - if os.path.exists(f): - if dbg: - PathLog.debug(" Found {} at {}".format(typ, f)) - return f - if pname and os.path.sep != pname: - PathLog.track(pname) - ppname, pfname = os.path.split(pname) - ffname = os.path.join(pfname, fname) if fname else pfname - return searchFor(ppname, ffname) - return None + if containerFile: + rootPath = os.path.dirname(os.path.dirname(containerFile)) + paths = [os.path.join(rootPath, typ)] + else: + paths = [] + paths.extend(PathPreferences.searchPathsTool(typ)) - return searchFor(path, '') + def _findFile(path, name): + PathLog.track(path, name) + fullPath = os.path.join(path, name) + if os.path.exists(fullPath): + return (True, fullPath) + for root, ds, fs in os.walk(path): + for d in ds: + found, fullPath = _findFile(d, name) + if found: + return (True, fullPath) + return (False, None) -def findShape(path): - ''' - findShape(path) ... search for path, full and partially - in all known shape directories. - ''' - return _findTool(path, 'Shape') + for p in paths: + found, path = _findFile(p, name) + if found: + return path + return None -def findBit(path): - PathLog.track(path) - if path.endswith('.fctb'): - return _findTool(path, 'Bit') - return _findTool("{}.fctb".format(path), 'Bit') +def _findShape(name, path=None): + '''_findShape(name, path) ... search for name, full and partially starting with path in known shape directories''' + return _findToolFile(name, path, 'Shape') -def findLibrary(path, dbg=False): - if path.endswith('.fctl'): - return _findTool(path, 'Library', dbg) - return _findTool("{}.fctl".format(path), 'Library', dbg) +def findBit(name, path=None): + PathLog.track(name) + if name.endswith('.fctb'): + return _findToolFile(name, path, 'Bit') + return _findToolFile("{}.fctb".format(name), path, 'Bit') + + +def findLibrary(name, path=None, dbg=False): + if name.endswith('.fctl'): + return _findToolFile(name, path, 'Library', dbg) + return _findToolFile("{}.fctl".format(name), path, 'Library', dbg) def _findRelativePath(path, typ): @@ -222,7 +222,7 @@ class ToolBit(object): doc = FreeCAD.getDocument(d) break if doc is None: - p = findShape(p) + p = _findShape(p) if not path and p != obj.BitShape: obj.BitShape = p PathLog.debug("ToolBit {} using shape file: {}".format(obj.Label, p)) @@ -330,7 +330,7 @@ class ToolBit(object): def getBitThumbnail(self, obj): if obj.BitShape: - path = findShape(obj.BitShape) + path = _findShape(obj.BitShape) if path: with open(path, 'rb') as fd: try: diff --git a/src/Mod/Path/PathTests/TestPathToolBit.py b/src/Mod/Path/PathTests/TestPathToolBit.py index dd82c7fc63..6d3b1022b4 100644 --- a/src/Mod/Path/PathTests/TestPathToolBit.py +++ b/src/Mod/Path/PathTests/TestPathToolBit.py @@ -22,37 +22,122 @@ import PathScripts.PathToolBit as PathToolBit import PathTests.PathTestUtils as PathTestUtils +import os +TestToolDir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'Tools') +TestInvalidDir = os.path.join(TestToolDir, 'some', 'silly', 'path', 'that', 'should', 'not', 'exist') + +TestToolBitName = 'test-path-tool-bit-bit-00.fctb' +TestToolShapeName = 'test-path-tool-bit-shape-00.fcstd' +TestToolLibraryName = 'test-path-tool-bit-library-00.fctl' + +def testToolShape(path = TestToolDir, name = TestToolShapeName): + return os.path.join(path, 'Shape', name) + +def testToolBit(path = TestToolDir, name = TestToolBitName): + return os.path.join(path, 'Bit', name) + +def testToolLibrary(path = TestToolDir, name = TestToolLibraryName): + return os.path.join(path, 'Library', name) class TestPathToolBit(PathTestUtils.PathTestBase): def test00(self): - '''Find a tool shapee from file name''' - - path = PathToolBit.findShape('endmill.fcstd') + '''Find a tool shape from file name''' + path = PathToolBit._findShape('endmill.fcstd') self.assertIsNot(path, None) self.assertNotEqual(path, 'endmill.fcstd') - def test01(self): - '''Find a tool shapee from an invalid absolute path.''' - path = PathToolBit.findShape('/this/is/unlikely/a/valid/path/v-bit.fcstd') + def test01(self): + '''Not find a relative path shape if not stored in default location''' + path = PathToolBit._findShape(TestToolShapeName) + self.assertIsNone(path) + + + def test02(self): + '''Find a relative path shape if it's local to a bit path''' + path = PathToolBit._findShape(TestToolShapeName, testToolBit()) self.assertIsNot(path, None) - self.assertNotEqual(path, '/this/is/unlikely/a/valid/path/v-bit.fcstd') + self.assertEqual(path, testToolShape()) + + + def test03(self): + '''Not find a tool shape from an invalid absolute path.''' + path = PathToolBit._findShape(testToolShape(TestInvalidDir)) + self.assertIsNone(path) + + + def test04(self): + '''Find a tool shape from a valid absolute path.''' + path = PathToolBit._findShape(testToolShape()) + self.assertIsNot(path, None) + self.assertEqual(path, testToolShape()) def test10(self): - '''find the relative path of a tool bit''' - shape = 'endmill.fcstd' - path = PathToolBit.findShape(shape) + '''Find a tool bit from file name''' + path = PathToolBit.findBit('5mm_Endmill.fctb') self.assertIsNot(path, None) - self.assertGreater(len(path), len(shape)) - rel = PathToolBit.findRelativePathShape(path) - self.assertEqual(rel, shape) + self.assertNotEqual(path, '5mm_Endmill.fctb') + def test11(self): - '''store full path if relative path isn't found''' - path = '/this/is/unlikely/a/valid/path/v-bit.fcstd' - rel = PathToolBit.findRelativePathShape(path) - self.assertEqual(rel, path) + '''Not find a relative path bit if not stored in default location''' + path = PathToolBit.findBit(TestToolBitName) + self.assertIsNone(path) + + + def test12(self): + '''Find a relative path bit if it's local to a library path''' + path = PathToolBit.findBit(TestToolBitName, testToolLibrary()) + self.assertIsNot(path, None) + self.assertEqual(path, testToolBit()) + + + def test13(self): + '''Not find a tool bit from an invalid absolute path.''' + path = PathToolBit.findBit(testToolBit(TestInvalidDir)) + self.assertIsNone(path) + + + def test14(self): + '''Find a tool bit from a valid absolute path.''' + path = PathToolBit.findBit(testToolBit()) + self.assertIsNot(path, None) + self.assertEqual(path, testToolBit()) + + + + def test20(self): + '''Find a tool library from file name''' + path = PathToolBit.findBit('5mm_Endmill.fctb') + self.assertIsNot(path, None) + self.assertNotEqual(path, '5mm_Endmill.fctb') + + + def test21(self): + '''Not find a relative path library if not stored in default location''' + path = PathToolBit.findBit(TestToolBitName) + self.assertIsNone(path) + + + def test22(self): + '''[skipped] Find a relative path library if it's local to ''' + # this is not a valid test for libraries because t + self.assertTrue(True) + + + def test23(self): + '''Not find a tool library from an invalid absolute path.''' + path = PathToolBit.findBit(testToolBit(TestInvalidDir)) + self.assertIsNone(path) + + + def test24(self): + '''Find a tool library from a valid absolute path.''' + path = PathToolBit.findBit(testToolBit()) + self.assertIsNot(path, None) + self.assertEqual(path, testToolBit()) + diff --git a/src/Mod/Path/PathTests/Tools/Bit/test-path-tool-bit-bit-00.fctb b/src/Mod/Path/PathTests/Tools/Bit/test-path-tool-bit-bit-00.fctb new file mode 100644 index 0000000000..7cc72ba33c --- /dev/null +++ b/src/Mod/Path/PathTests/Tools/Bit/test-path-tool-bit-bit-00.fctb @@ -0,0 +1,12 @@ +{ + "version": 2, + "name": "5mm Endmill", + "shape": "endmill.fcstd", + "parameter": { + "CuttingEdgeHeight": "30.0000 mm", + "Diameter": "5.0000 mm", + "Length": "50.0000 mm", + "ShankDiameter": "3.0000 mm" + }, + "attribute": {} +} diff --git a/src/Mod/Path/PathTests/Tools/Library/test-path-tool-bit-library-00.fctl b/src/Mod/Path/PathTests/Tools/Library/test-path-tool-bit-library-00.fctl new file mode 100644 index 0000000000..60de98e08e --- /dev/null +++ b/src/Mod/Path/PathTests/Tools/Library/test-path-tool-bit-library-00.fctl @@ -0,0 +1,41 @@ +{ + "tools": [ + { + "nr": 1, + "path": "5mm_Endmill.fctb" + }, + { + "nr": 2, + "path": "5mm_Drill.fctb" + }, + { + "nr": 3, + "path": "6mm_Ball_End.fctb" + }, + { + "nr": 4, + "path": "6mm_Bullnose.fctb" + }, + { + "nr": 5, + "path": "60degree_Vbit.fctb" + }, + { + "nr": 6, + "path": "45degree_chamfer.fctb" + }, + { + "nr": 7, + "path": "slittingsaw.fctb" + }, + { + "nr": 8, + "path": "probe.fctb" + }, + { + "nr": 9, + "path": "5mm-thread-cutter.fctb" + } + ], + "version": 1 +} diff --git a/src/Mod/Path/PathTests/Tools/Shape/test-path-tool-bit-shape-00.fcstd b/src/Mod/Path/PathTests/Tools/Shape/test-path-tool-bit-shape-00.fcstd new file mode 100644 index 0000000000000000000000000000000000000000..5b5a76dc41bc35e827c9cdbb9bbd0f5dbe86fb57 GIT binary patch literal 11829 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 literal 0 HcmV?d00001