From 0a2183fe4f3f7ba1ff765a02c47829f07831fc92 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Tue, 6 Oct 2020 17:50:29 -0500 Subject: [PATCH 01/18] Basic workflow. No editing concept dock work most functions working. made linuxcnc export work Fixed some defaults on new install fixed display label in dock --- .../Gui/Resources/panels/ToolBitEditor.ui | 14 +- .../Resources/panels/ToolBitLibraryEdit.ui | 509 +++++------ .../Gui/Resources/panels/ToolBitSelector.ui | 212 ++--- src/Mod/Path/InitGui.py | 30 +- src/Mod/Path/PathScripts/PathPreferences.py | 98 ++- src/Mod/Path/PathScripts/PathToolBit.py | 91 +- src/Mod/Path/PathScripts/PathToolBitEdit.py | 12 +- src/Mod/Path/PathScripts/PathToolBitGui.py | 17 + .../Path/PathScripts/PathToolBitLibraryCmd.py | 115 ++- .../Path/PathScripts/PathToolBitLibraryGui.py | 797 ++++++++++-------- 10 files changed, 1047 insertions(+), 848 deletions(-) diff --git a/src/Mod/Path/Gui/Resources/panels/ToolBitEditor.ui b/src/Mod/Path/Gui/Resources/panels/ToolBitEditor.ui index 42a5e98364..bceff42fcd 100644 --- a/src/Mod/Path/Gui/Resources/panels/ToolBitEditor.ui +++ b/src/Mod/Path/Gui/Resources/panels/ToolBitEditor.ui @@ -6,8 +6,8 @@ 0 0 - 587 - 744 + 401 + 715 @@ -16,6 +16,12 @@ + + + 0 + 0 + + 0 @@ -114,7 +120,7 @@ - 180° + 0 ° ° @@ -131,7 +137,7 @@ - 0.00 + 0 mm mm diff --git a/src/Mod/Path/Gui/Resources/panels/ToolBitLibraryEdit.ui b/src/Mod/Path/Gui/Resources/panels/ToolBitLibraryEdit.ui index 83301b6b0d..f97deb479e 100644 --- a/src/Mod/Path/Gui/Resources/panels/ToolBitLibraryEdit.ui +++ b/src/Mod/Path/Gui/Resources/panels/ToolBitLibraryEdit.ui @@ -11,297 +11,11 @@ - ToolBit Library + Library Manager - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 0 - - - - - - 226 - 16777215 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 0 - - - - - Tool Libraries - - - - - - - - 32 - 32 - - - - <html><head/><body><p>Rename Tool Table</p></body></html> - - - - - - - :/icons/edit-edit.svg:/icons/edit-edit.svg - - - - - - - - 32 - 32 - - - - <html><head/><body><p>Add New Tool Table</p></body></html> - - - - - - - :/icons/list-add.svg:/icons/list-add.svg - - - - - - - - 32 - 32 - - - - <html><head/><body><p>Remove Tool Table from Disc</p></body></html> - - - - - - - :/icons/list-remove.svg:/icons/list-remove.svg - - - - - - - - - QFrame::Box - - - - - - - - - - 0 - - - 0 - - - - - true - - - <html><head/><body><p>Table of Tool Bits of the library.</p></body></html> - - - QFrame::Box - - - QFrame::Sunken - - - 1 - - - 0 - - - true - - - QAbstractItemView::InternalMove - - - Qt::MoveAction - - - QAbstractItemView::SelectRows - - - true - - - false - - - - - - - - - Add Tool Controller(s) to Job - - - - :/icons/edit_OK.svg:/icons/edit_OK.svg - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 140 - 20 - - - - - - - - <html><head/><body><p>Close the Tool Bit Library Editor</p></body></html> - - - Cancel - - - - :/icons/button_invalid.svg:/icons/button_invalid.svg - - - - - - - <html><head/><body><p>Save the current Library</p></body></html> - - - Save Table - - - - :/icons/document-save.svg:/icons/document-save.svg - - - - - - - <html><head/><body><p>Save the library to a new file</p></body></html> - - - Save Table As... - - - - :/icons/document-save-as.svg:/icons/document-save-as.svg - - - - - - - - - - - - - - - - - - - 0 - 0 - - - - - 420 - 0 - - - - true - - - - - - - <html><head/><body><p>Select the folder with the tool libraries to load.</p></body></html> - - - - - - - :/icons/document-open.svg:/icons/document-open.svg - - - + + + @@ -315,22 +29,45 @@ + + + + Create Toolbit + + + + :/icons/Path-ToolBit.svg:/icons/Path-ToolBit.svg + + + + + + 16777215 + 16777215 + + <html><head/><body><p>Add existing Tool Bit to this library.</p><p><br/></p></body></html> - Add Toolbit + Add Existing - - :/icons/list-add.svg:/icons/list-add.svg + + :/icons/Path-ToolDuplicate.svg:/icons/Path-ToolDuplicate.svg + + + 16777215 + 16777215 + + <html><head/><body><p>Delete selected Tool Bit(s) from the library.</p><p><br/></p></body></html> @@ -343,17 +80,194 @@ + + + + + + + + + + + + 16777215 + 16777215 + + + + <html><head/><body><p>Select a working path for the tool library editor.</p></body></html> + + + + + + + :/icons/document-open.svg:/icons/document-open.svg + + + + + + + + 16777215 + 16777215 + + + + <html><head/><body><p>Add New Tool Table</p></body></html> + + + + + + + :/icons/document-new.svg:/icons/document-new.svg + + + + 24 + 24 + + + + + + + + <html><head/><body><p>Save the selected library with a new name or export to another format</p></body></html> + + + + + + + :/icons/document-save.svg:/icons/document-save.svg + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + 0 + 0 + + + + QFrame::Box + + + + + + + true + + + <html><head/><body><p>Table of Tool Bits of the library.</p></body></html> + + + QFrame::Box + + + QFrame::Sunken + + + 1 + + + 0 + + + false + + + false + + + QAbstractItemView::DragOnly + + + Qt::IgnoreAction + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + true + + + false + + + true + + + + + + + + - + + + Qt::Horizontal + + + QSizePolicy::Preferred + + + + 65555555 + 20 + + + + + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + - <html><head/><body><p>Assign numbers to each Tool Bit according to its current position in the library. The first Tool Bit is assigned the ID 1.</p></body></html> + <html><head/><body><p>Save the current Library</p></body></html> - Enumerate + Close - :/icons/button_sort.svg:/icons/button_sort.svg + :/icons/edit_OK.svg:/icons/edit_OK.svg @@ -363,6 +277,7 @@ + diff --git a/src/Mod/Path/Gui/Resources/panels/ToolBitSelector.ui b/src/Mod/Path/Gui/Resources/panels/ToolBitSelector.ui index 4dde97be5a..32e87cb8fb 100644 --- a/src/Mod/Path/Gui/Resources/panels/ToolBitSelector.ui +++ b/src/Mod/Path/Gui/Resources/panels/ToolBitSelector.ui @@ -1,119 +1,135 @@ - Dialog - + ToolSelector + 0 0 - 588 - 396 + 350 + 542 - - Dialog + + + 0 + 0 + - - - - + + Tool Selector + + + + + + + + + + TextLabel + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + + + + :/icons/Path-ToolTable.svg:/icons/Path-ToolTable.svg + + + + 16 + 16 + + + + + + + + + - + <html><head/><body><p>Available Tool Bits to choose from.</p></body></html> QAbstractItemView::NoEditTriggers - - - - - - - - - <html><head/><body><p>Load an existing Tool Bit from a file.</p></body></html> - - - Load... - - - - - - - <html><head/><body><p>Create a new Tool Bit based on an existing shape.</p></body></html> - - - New - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - + + QAbstractItemView::ExtendedSelection + + + false + - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - + + + + + + + false + + + <html><head/><body><p>Create ToolControllers for the selected toolbits and add them to the Job</p></body></html> + + + Add To Job + + + + :/icons/edit_OK.svg:/icons/edit_OK.svg + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + - - - - buttonBox - accepted() - Dialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - Dialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - + + + + + diff --git a/src/Mod/Path/InitGui.py b/src/Mod/Path/InitGui.py index f40ff06ee6..cf0e8a36e6 100644 --- a/src/Mod/Path/InitGui.py +++ b/src/Mod/Path/InitGui.py @@ -76,20 +76,22 @@ class PathWorkbench (Workbench): from PathScripts import PathToolBitCmd from PathScripts import PathToolBitLibraryCmd - if PathPreferences.experimentalFeaturesEnabled(): - toolbitcmdlist = PathToolBitCmd.CommandList + ["Separator"] + PathToolBitLibraryCmd.CommandList + ["Path_ToolController", "Separator"] - self.toolbitctxmenu = ["Path_ToolBitLibraryLoad", "Path_ToolController"] - else: - toolbitcmdlist = [] - self.toolbitctxmenu = [] + + + #if PathPreferences.experimentalFeaturesEnabled(): + # #toolbitcmdlist = PathToolBitCmd.CommandList + ["Separator"] + PathToolBitLibraryCmd.CommandList + ["Path_ToolController", "Separator"] + # toolbitcmdlist = PathToolBitLibraryCmd.MenuList + # self.toolbitctxmenu = ["Path_ToolBitLibraryLoad", "Path_ToolController"] + #else: + # toolbitcmdlist = [] + # self.toolbitctxmenu = [] import PathCommands PathGuiInit.Startup() # build commands list projcmdlist = ["Path_Job", "Path_Post"] - toolcmdlist = ["Path_Inspect", "Path_Simulator", - "Path_ToolLibraryEdit", "Path_SelectLoop", + toolcmdlist = ["Path_Inspect", "Path_Simulator", "Path_SelectLoop", "Path_OpActiveToggle"] prepcmdlist = ["Path_Fixture", "Path_Comment", "Path_Stop", "Path_Custom", "Path_Probe"] @@ -107,6 +109,16 @@ class PathWorkbench (Workbench): # modcmdmore = ["Path_Hop",] # remotecmdlist = ["Path_Remote"] + + if PathPreferences.toolsReallyUseLegacyTools(): + toolcmdlist.append("Path_ToolLibraryEdit") + toolbitcmdlist = [] + else: + toolcmdlist.extend(PathToolBitLibraryCmd.BarList) + toolbitcmdlist = PathToolBitLibraryCmd.MenuList + + + engravecmdgroup = ['Path_EngraveTools'] FreeCADGui.addCommand('Path_EngraveTools', PathCommandGroup(engravecmdlist, QtCore.QT_TRANSLATE_NOOP("Path", 'Engraving Operations'))) @@ -134,7 +146,7 @@ class PathWorkbench (Workbench): self.appendToolbar(QtCore.QT_TRANSLATE_NOOP("Path", "Helpful Tools"), extracmdlist) self.appendMenu([QtCore.QT_TRANSLATE_NOOP("Path", "&Path")], projcmdlist + ["Path_ExportTemplate", "Separator"] + - toolbitcmdlist + toolcmdlist + ["Separator"] + twodopcmdlist + engravecmdlist + ["Separator"] + + toolcmdlist + toolbitcmdlist + ["Separator"] + twodopcmdlist + engravecmdlist + ["Separator"] + threedopcmdlist + ["Separator"]) self.appendMenu([QtCore.QT_TRANSLATE_NOOP("Path", "&Path"), QtCore.QT_TRANSLATE_NOOP( "Path", "Path Dressup")], dressupcmdlist) diff --git a/src/Mod/Path/PathScripts/PathPreferences.py b/src/Mod/Path/PathScripts/PathPreferences.py index ad6d2d5585..096c6ef349 100644 --- a/src/Mod/Path/PathScripts/PathPreferences.py +++ b/src/Mod/Path/PathScripts/PathPreferences.py @@ -1,5 +1,7 @@ # -*- coding: utf-8 -*- + # *************************************************************************** +# * * # * Copyright (c) 2014 Yorik van Havre * # * * # * This program is free software; you can redistribute it and/or modify * @@ -42,15 +44,19 @@ PostProcessorOutputPolicy = "PostProcessorOutputPolicy" LastPathToolBit = "LastPathToolBit" LastPathToolLibrary = "LastPathToolLibrary" LastPathToolShape = "LastPathToolShape" -LastPathToolTable ="LastPathToolTable" +LastPathToolTable = "LastPathToolTable" + +LastFileToolBit = "LastFileToolBit" +LastFileToolLibrary = "LastFileToolLibrary" +LastFileToolShape = "LastFileToolShape" UseLegacyTools = "UseLegacyTools" UseAbsoluteToolPaths = "UseAbsoluteToolPaths" OpenLastLibrary = "OpenLastLibrary" # Linear tolerance to use when generating Paths, eg when tessellating geometry -GeometryTolerance = "GeometryTolerance" -LibAreaCurveAccuracy = "LibAreaCurveAccuarcy" +GeometryTolerance = "GeometryTolerance" +LibAreaCurveAccuracy = "LibAreaCurveAccuarcy" EnableExperimentalFeatures = "EnableExperimentalFeatures" @@ -58,58 +64,70 @@ EnableExperimentalFeatures = "EnableExperimentalFeatures" def preferences(): return FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Path") + def pathScriptsSourcePath(): return os.path.join(FreeCAD.getHomePath(), "Mod/Path/PathScripts/") + def pathDefaultToolsPath(sub=None): if sub: return os.path.join(FreeCAD.getHomePath(), "Mod/Path/Tools/", sub) return os.path.join(FreeCAD.getHomePath(), "Mod/Path/Tools/") + def allAvailablePostProcessors(): allposts = [] for path in searchPathsPost(): - posts = [ str(os.path.split(os.path.splitext(p)[0])[1][:-5]) for p in glob.glob(path + '/*_post.py')] + posts = [str(os.path.split(os.path.splitext(p)[0])[1][:-5]) for p in glob.glob(path + '/*_post.py')] allposts.extend(posts) allposts.sort() return allposts -def allEnabledPostProcessors(include = None): + +def allEnabledPostProcessors(include=None): blacklist = postProcessorBlacklist() - enabled = [processor for processor in allAvailablePostProcessors() if not processor in blacklist] + enabled = [processor for processor in allAvailablePostProcessors() if processor not in blacklist] if include: - l = list(set(include + enabled)) - l.sort() - return l + postlist = list(set(include + enabled)) + postlist.sort() + return postlist return enabled + def defaultPostProcessor(): pref = preferences() return pref.GetString(PostProcessorDefault, "") + def defaultPostProcessorArgs(): pref = preferences() return pref.GetString(PostProcessorDefaultArgs, "") + def defaultGeometryTolerance(): return preferences().GetFloat(GeometryTolerance, 0.01) + def defaultLibAreaCurveAccuracy(): return preferences().GetFloat(LibAreaCurveAccuracy, 0.01) + def defaultFilePath(): return preferences().GetString(DefaultFilePath) + def filePath(): path = defaultFilePath() if not path: path = macroFilePath() return path + def macroFilePath(): grp = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Macro") return grp.GetString("MacroPath", FreeCAD.getUserMacroDir()) + def searchPaths(): paths = [] p = defaultFilePath() @@ -118,6 +136,7 @@ def searchPaths(): paths.append(macroFilePath()) return paths + def searchPathsPost(): paths = [] p = defaultFilePath() @@ -128,6 +147,7 @@ def searchPathsPost(): paths.append(pathScriptsSourcePath()) return paths + def searchPathsTool(sub='Bit'): paths = [] @@ -148,30 +168,37 @@ def searchPathsTool(sub='Bit'): appendPath(os.path.join(FreeCAD.getHomePath(), "Mod/Path/"), sub) return paths + def toolsUseLegacyTools(): - return preferences().GetBool(UseLegacyTools, True) + return preferences().GetBool(UseLegacyTools, False) + def toolsReallyUseLegacyTools(): - return toolsUseLegacyTools() or not experimentalFeaturesEnabled() + return toolsUseLegacyTools() + def toolsStoreAbsolutePaths(): return preferences().GetBool(UseAbsoluteToolPaths, False) + def toolsOpenLastLibrary(): return preferences().GetBool(OpenLastLibrary, False) + def setToolsSettings(legacy, relative, lastlibrary): pref = preferences() pref.SetBool(UseLegacyTools, legacy) pref.SetBool(UseAbsoluteToolPaths, relative) pref.SetBool(OpenLastLibrary, lastlibrary) + def defaultJobTemplate(): template = preferences().GetString(DefaultJobTemplate) if 'xml' not in template: return template return '' + def setJobDefaults(fileName, jobTemplate, geometryTolerance, curveAccuracy): PathLog.track("(%s='%s', %s, %s, %s)" % (DefaultFilePath, fileName, jobTemplate, geometryTolerance, curveAccuracy)) pref = preferences() @@ -180,12 +207,14 @@ def setJobDefaults(fileName, jobTemplate, geometryTolerance, curveAccuracy): pref.SetFloat(GeometryTolerance, geometryTolerance) pref.SetFloat(LibAreaCurveAccuracy, curveAccuracy) + def postProcessorBlacklist(): pref = preferences() blacklist = pref.GetString(PostProcessorBlacklist, "") if not blacklist: return [] - return eval(blacklist) # pylint: disable=eval-used + return eval(blacklist) # pylint: disable=eval-used + def setPostProcessorDefaults(processor, args, blacklist): pref = preferences() @@ -193,54 +222,99 @@ def setPostProcessorDefaults(processor, args, blacklist): pref.SetString(PostProcessorDefaultArgs, args) pref.SetString(PostProcessorBlacklist, "%s" % (blacklist)) + def setOutputFileDefaults(fileName, policy): pref = preferences() pref.SetString(PostProcessorOutputFile, fileName) pref.SetString(PostProcessorOutputPolicy, policy) + def defaultOutputFile(): pref = preferences() return pref.GetString(PostProcessorOutputFile, "") + def defaultOutputPolicy(): pref = preferences() return pref.GetString(PostProcessorOutputPolicy, "") + def defaultStockTemplate(): return preferences().GetString(DefaultStockTemplate, "") + def setDefaultStockTemplate(template): preferences().SetString(DefaultStockTemplate, template) + def defaultTaskPanelLayout(): return preferences().GetInt(DefaultTaskPanelLayout, 0) + def setDefaultTaskPanelLayout(style): preferences().SetInt(DefaultTaskPanelLayout, style) + def experimentalFeaturesEnabled(): return preferences().GetBool(EnableExperimentalFeatures, False) + +def lastFileToolLibrary(): + filename = preferences().GetString(LastFileToolLibrary) + if filename.endswith('.fctl') and os.path.isfile(filename): + return filename + + libpath = preferences().GetString(LastPathToolLibrary, pathDefaultToolsPath('Library')) + libFiles = [f for f in glob.glob(libpath + '/*.fctl')] + libFiles.sort() + if len(libFiles) >= 1: + filename = libFiles[0] + setLastFileToolLibrary(filename) + PathLog.track(filename) + return filename + else: + return None + + +def setLastFileToolLibrary(path): + PathLog.track(path) + if os.path.isfile(path): # keep the path and file in sync + preferences().SetString(LastPathToolLibrary, os.path.split(path)[0]) + return preferences().SetString(LastFileToolLibrary, path) + + def lastPathToolBit(): return preferences().GetString(LastPathToolBit, pathDefaultToolsPath('Bit')) + def setLastPathToolBit(path): return preferences().SetString(LastPathToolBit, path) + def lastPathToolLibrary(): + PathLog.track() return preferences().GetString(LastPathToolLibrary, pathDefaultToolsPath('Library')) + def setLastPathToolLibrary(path): + PathLog.track(path) + curLib = lastFileToolLibrary() + if os.path.split(curLib)[0] != path: + setLastFileToolLibrary('') # a path is known but not specific file return preferences().SetString(LastPathToolLibrary, path) + def lastPathToolShape(): return preferences().GetString(LastPathToolShape, pathDefaultToolsPath('Shape')) + def setLastPathToolShape(path): return preferences().SetString(LastPathToolShape, path) + def lastPathToolTable(): return preferences().GetString(LastPathToolTable, "") + def setLastPathToolTable(table): return preferences().SetString(LastPathToolTable, table) diff --git a/src/Mod/Path/PathScripts/PathToolBit.py b/src/Mod/Path/PathScripts/PathToolBit.py index d04473923d..efafe2c3a3 100644 --- a/src/Mod/Path/PathScripts/PathToolBit.py +++ b/src/Mod/Path/PathScripts/PathToolBit.py @@ -45,16 +45,17 @@ __doc__ = "Class to deal with and represent a tool bit." # PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) # PathLog.trackModule() + def translate(context, text, disambig=None): return PySide.QtCore.QCoreApplication.translate(context, text, disambig) + ParameterTypeConstraint = { 'Angle': 'App::PropertyAngle', 'Distance': 'App::PropertyLength', 'DistanceX': 'App::PropertyLength', 'DistanceY': 'App::PropertyLength', - 'Radius': 'App::PropertyLength' - } + 'Radius': 'App::PropertyLength'} def _findTool(path, typ, dbg=False): @@ -83,20 +84,27 @@ def _findTool(path, typ, dbg=False): return searchFor(path, '') + def findShape(path): - '''findShape(path) ... search for path, full and partially in all known shape directories.''' + ''' + findShape(path) ... search for path, full and partially + in all known shape directories. + ''' return _findTool(path, 'Shape') + def findBit(path): if path.endswith('.fctb'): return _findTool(path, 'Bit') return _findTool("{}.fctb".format(path), 'Bit') + def findLibrary(path, dbg=False): if path.endswith('.fctl'): return _findTool(path, 'Library', dbg) return _findTool("{}.fctl".format(path), 'Library', dbg) + def _findRelativePath(path, typ): relative = path for p in PathPreferences.searchPathsTool(typ): @@ -108,15 +116,19 @@ def _findRelativePath(path, typ): relative = p return relative + def findRelativePathShape(path): return _findRelativePath(path, 'Shape') + def findRelativePathTool(path): return _findRelativePath(path, 'Bit') + def findRelativePathLibrary(path): return _findRelativePath(path, 'Library') + def updateConstraint(sketch, name, value): for i, constraint in enumerate(sketch.Constraints): if constraint.Name.split(';')[0] == name: @@ -128,7 +140,9 @@ def updateConstraint(sketch, name, value): if constr is not None: if not PathGeom.isRoughly(constraint.Value, value.Value): - PathLog.track(name, constraint.Type, 'update', i, "(%.2f -> %.2f)" % (constraint.Value, value.Value)) + PathLog.track(name, constraint.Type, + 'update', i, "(%.2f -> %.2f)" + % (constraint.Value, value.Value)) sketch.delConstraint(i) sketch.recompute() n = sketch.addConstraint(constr) @@ -141,15 +155,21 @@ def updateConstraint(sketch, name, value): PropertyGroupBit = 'Bit' PropertyGroupAttribute = 'Attribute' + class ToolBit(object): def __init__(self, obj, shapeFile): PathLog.track(obj.Label, shapeFile) self.obj = obj - obj.addProperty('App::PropertyFile', 'BitShape', 'Base', translate('PathToolBit', 'Shape for bit shape')) - obj.addProperty('App::PropertyLink', 'BitBody', 'Base', translate('PathToolBit', 'The parametrized body representing the tool bit')) - obj.addProperty('App::PropertyFile', 'File', 'Base', translate('PathToolBit', 'The file of the tool')) - obj.addProperty('App::PropertyString', 'ShapeName', 'Base', translate('PathToolBit', 'The name of the shape file')) + obj.addProperty('App::PropertyFile', 'BitShape', 'Base', + translate('PathToolBit', 'Shape for bit shape')) + obj.addProperty('App::PropertyLink', 'BitBody', 'Base', + translate('PathToolBit', + 'The parametrized body representing the tool bit')) + obj.addProperty('App::PropertyFile', 'File', 'Base', + translate('PathToolBit', 'The file of the tool')) + obj.addProperty('App::PropertyString', 'ShapeName', 'Base', + translate('PathToolBit', 'The name of the shape file')) if shapeFile is None: obj.BitShape = 'endmill.fcstd' self._setupBitShape(obj) @@ -184,14 +204,14 @@ class ToolBit(object): for prop in self.propertyNamesBit(obj): obj.setEditorMode(prop, 1) # I currently don't see why these need to be read-only - #for prop in self.propertyNamesAttribute(obj): + # for prop in self.propertyNamesAttribute(obj): # obj.setEditorMode(prop, 1) def onChanged(self, obj, prop): PathLog.track(obj.Label, prop) - if prop == 'BitShape' and not 'Restore' in obj.State: + if prop == 'BitShape' and 'Restore' not in obj.State: self._setupBitShape(obj) - #elif obj.getGroupOfProperty(prop) == PropertyGroupBit: + # elif obj.getGroupOfProperty(prop) == PropertyGroupBit: # self._updateBitShape(obj, [prop]) def onDelete(self, obj, arg2=None): @@ -200,7 +220,7 @@ class ToolBit(object): obj.Document.removeObject(obj.Name) def _updateBitShape(self, obj, properties=None): - if not obj.BitBody is None: + if obj.BitBody is not None: if not properties: properties = self.propertyNamesBit(obj) for prop in properties: @@ -286,7 +306,7 @@ class ToolBit(object): prop = parts[0] desc = '' if len(parts) > 1: - desc = parts[1] + desc = parts[1] obj.addProperty(typ, prop, PropertyGroupBit, desc) obj.setEditorMode(prop, 1) value = constraint.Value @@ -315,12 +335,13 @@ class ToolBit(object): obj.File = path return True except (OSError, IOError) as e: - PathLog.error("Could not save tool %s to %s (%s)" % (obj.Label, path, e)) + PathLog.error("Could not save tool {} to {} ({})".format( + obj.Label, path, e)) raise def templateAttrs(self, obj): attrs = {} - attrs['version'] = 2 # Path.Tool is version 1 + attrs['version'] = 2 # Path.Tool is version 1 attrs['name'] = obj.Label if PathPreferences.toolsStoreAbsolutePaths(): attrs['shape'] = obj.BitShape @@ -332,7 +353,6 @@ class ToolBit(object): attrs['parameter'] = params params = {} for name in self.propertyNamesAttribute(obj): - #print(f"shapeattr {name}") if name == "UserAttributes": for key, value in obj.UserAttributes.items(): params[key] = value @@ -341,20 +361,33 @@ class ToolBit(object): attrs['attribute'] = params return attrs + def Declaration(path): with open(path, 'r') as fp: return json.load(fp) + class AttributePrototype(PathSetupSheetOpPrototype.OpPrototype): def __init__(self): PathSetupSheetOpPrototype.OpPrototype.__init__(self, 'ToolBitAttribute') - self.addProperty('App::PropertyEnumeration', 'Material', PropertyGroupAttribute, translate('PathToolBit', 'Tool bit material')) - self.Material = ['Carbide', 'CastAlloy', 'Ceramics', 'Diamond', 'HighCarbonToolSteel', 'HighSpeedSteel', 'Sialon'] - self.addProperty('App::PropertyDistance', 'LengthOffset', PropertyGroupAttribute, translate('PathToolBit', 'Length offset in Z direction')) - self.addProperty('App::PropertyInteger', 'Flutes', PropertyGroupAttribute, translate('PathToolBit', 'The number of flutes')) - self.addProperty('App::PropertyDistance', 'ChipLoad', PropertyGroupAttribute, translate('PathToolBit', 'Chipload as per manufacturer')) - self.addProperty('App::PropertyMap', 'UserAttributes', PropertyGroupAttribute, translate('PathTooolBit', 'User Defined Values')) + self.addProperty('App::PropertyEnumeration', 'Material', + PropertyGroupAttribute, + translate('PathToolBit', 'Tool bit material')) + self.Material = ['Carbide', 'CastAlloy', 'Ceramics', 'Diamond', + 'HighCarbonToolSteel', 'HighSpeedSteel', 'Sialon'] + self.addProperty('App::PropertyDistance', 'LengthOffset', + PropertyGroupAttribute, translate('PathToolBit', + 'Length offset in Z direction')) + self.addProperty('App::PropertyInteger', 'Flutes', + PropertyGroupAttribute, translate('PathToolBit', + 'The number of flutes')) + self.addProperty('App::PropertyDistance', 'ChipLoad', + PropertyGroupAttribute, translate('PathToolBit', + 'Chipload as per manufacturer')) + self.addProperty('App::PropertyMap', 'UserAttributes', + PropertyGroupAttribute, translate('PathToolBit', + 'User Defined Values')) class ToolBitFactory(object): @@ -372,23 +405,24 @@ class ToolBitFactory(object): proto = AttributePrototype() uservals = {} for pname in params: - #print(f"pname: {pname}") + # print(f"pname: {pname}") try: prop = proto.getProperty(pname) - val = prop.valueFromString(params[pname]) + # val = prop.valueFromString(params[pname]) prop.setupProperty(obj, pname, PropertyGroupAttribute, prop.valueFromString(params[pname])) - except: + except Exception: # prop = obj.addProperty('App::PropertyString', pname, "Attribute", translate('PathTooolBit', 'User Defined Value')) # setattr(obj, pname, params[pname]) prop = proto.getProperty("UserAttributes") uservals.update({pname: params[pname]}) - #prop.setupProperty(obj, pname, "UserAttributes", prop.valueFromString(params[pname])) + # prop.setupProperty(obj, pname, "UserAttributes", prop.valueFromString(params[pname])) if len(uservals.items()) > 0: - prop.setupProperty(obj, "UserAttributes", PropertyGroupAttribute, uservals) + prop.setupProperty(obj, "UserAttributes", + PropertyGroupAttribute, uservals) # print("prop[%s] = %s (%s)" % (pname, params[pname], type(val))) - #prop.setupProperty(obj, pname, PropertyGroupAttribute, prop.valueFromString(params[pname])) + # prop.setupProperty(obj, pname, PropertyGroupAttribute, prop.valueFromString(params[pname])) return obj def CreateFrom(self, path, name='ToolBit'): @@ -406,4 +440,5 @@ class ToolBitFactory(object): obj.Proxy = ToolBit(obj, shapeFile) return obj + Factory = ToolBitFactory() diff --git a/src/Mod/Path/PathScripts/PathToolBitEdit.py b/src/Mod/Path/PathScripts/PathToolBitEdit.py index a8e76cf20e..c3e0120149 100644 --- a/src/Mod/Path/PathScripts/PathToolBitEdit.py +++ b/src/Mod/Path/PathScripts/PathToolBitEdit.py @@ -45,13 +45,15 @@ class ToolBitEditor(object): The controller embeds the UI to the parentWidget which has to have a layout attached to it. ''' - def __init__(self, tool, parentWidget=None): + def __init__(self, tool, parentWidget=None, ondone=None): self.form = FreeCADGui.PySideUic.loadUi(":/panels/ToolBitEditor.ui") if parentWidget: self.form.setParent(parentWidget) parentWidget.layout().addWidget(self.form) + self.ondone = ondone + self.tool = tool if not tool.BitShape: self.tool.BitShape = 'endmill.fcstd' @@ -210,6 +212,9 @@ class ToolBitEditor(object): self.form.shapePath.setText(foo[0]) self.updateShape() + def ok(self): + self.form.close() + def setupUI(self): PathLog.track() self.updateUI() @@ -217,3 +222,8 @@ class ToolBitEditor(object): self.form.toolName.editingFinished.connect(self.refresh) self.form.shapePath.editingFinished.connect(self.updateShape) self.form.shapeSet.clicked.connect(self.selectShape) + self.form.buttonBox.accepted.connect(self.ok) + self.form.buttonBox.rejected.connect(self.ok) + if self.ondone is not None: + self.form.buttonBox.rejected.connect(self.ondone) + diff --git a/src/Mod/Path/PathScripts/PathToolBitGui.py b/src/Mod/Path/PathScripts/PathToolBitGui.py index 9702dfe469..9242dfc033 100644 --- a/src/Mod/Path/PathScripts/PathToolBitGui.py +++ b/src/Mod/Path/PathScripts/PathToolBitGui.py @@ -280,6 +280,23 @@ def GetToolFiles(parent = None): return foo[0] return [] +def GetToolShapeFile(parent = None): + if parent is None: + parent = QtGui.QApplication.activeWindow() + + location = PathPreferences.lastPathToolShape() + if os.path.isfile(location): + location = os.path.split(location)[0] + elif not os.path.isdir(location): + location = PathPreferences.filePath() + + fname = QtGui.QFileDialog.getOpenFileName(parent, 'Select Tool Shape', location, '*.fcstd') + if fname and fname[0]: + if fname != location: + PathPreferences.setLastPathToolShape(location) + return fname[0] + else: + return None def LoadTool(parent = None): '''LoadTool(parent=None) ... Open a file dialog to load a tool from a file.''' diff --git a/src/Mod/Path/PathScripts/PathToolBitLibraryCmd.py b/src/Mod/Path/PathScripts/PathToolBitLibraryCmd.py index 10ad578196..fcff3facaa 100644 --- a/src/Mod/Path/PathScripts/PathToolBitLibraryCmd.py +++ b/src/Mod/Path/PathScripts/PathToolBitLibraryCmd.py @@ -25,9 +25,10 @@ import FreeCADGui import PySide.QtCore as QtCore import PathScripts.PathPreferences as PathPreferences -class CommandToolBitLibraryOpen: + +class CommandToolBitSelectorOpen: ''' - Command to ToolBitLibrary editor. + Command to toggle the ToolBitSelector Dock ''' def __init__(self): @@ -35,11 +36,40 @@ class CommandToolBitLibraryOpen: def GetResources(self): return {'Pixmap': 'Path-ToolTable', - 'MenuText': QtCore.QT_TRANSLATE_NOOP("PathToolBitLibrary", "Open ToolBit Library editor"), + 'MenuText': QtCore.QT_TRANSLATE_NOOP("PathToolBitLibrary", "ToolBit Dock"), + 'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathToolBitLibrary", "Toggle the Toolbit Dock"), + 'Accel': "P, T"} + + def IsActive(self): + return FreeCAD.ActiveDocument is not None + + def Activated(self): + import PathScripts.PathToolBitLibraryGui as PathToolBitLibraryGui + dock = PathToolBitLibraryGui.ToolBitSelector() + + lastlib = PathPreferences.lastPathToolLibrary() + + if PathPreferences.toolsOpenLastLibrary(): + dock.open(lastlib) + else: + dock.open() + + +class CommandToolBitLibraryOpen: + ''' + Command to open ToolBitLibrary editor. + ''' + + def __init__(self): + pass + + def GetResources(self): + return {'Pixmap': 'Path-ToolTable', + 'MenuText': QtCore.QT_TRANSLATE_NOOP("PathToolBitLibrary", "ToolBit Library editor"), 'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathToolBitLibrary", "Open an editor to manage ToolBit libraries")} def IsActive(self): - return True + return FreeCAD.ActiveDocument is not None def Activated(self): import PathScripts.PathToolBitLibraryGui as PathToolBitLibraryGui @@ -52,55 +82,56 @@ class CommandToolBitLibraryOpen: else: library.open() -class CommandToolBitLibraryLoad: - ''' - Command used to load an entire ToolBitLibrary (or part of it) from a file into a job. - ''' +# class CommandToolBitLibraryLoad: +# ''' +# Command used to load an entire ToolBitLibrary (or part of it) from a file into a job. +# ''' - def __init__(self): - pass +# def __init__(self): +# pass - def GetResources(self): - return {'Pixmap': 'Path-ToolTable', - 'MenuText': QtCore.QT_TRANSLATE_NOOP("PathToolBitLibrary", "Load ToolBit Library"), - 'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathToolBitLibrary", "Load an entire ToolBit library or part of it into a job")} +# def GetResources(self): +# return {'Pixmap': 'Path-ToolTable', +# 'MenuText': QtCore.QT_TRANSLATE_NOOP("PathToolBitLibrary", "Load ToolBit Library"), +# 'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathToolBitLibrary", "Load an entire ToolBit library or part of it into a job")} - def selectedJob(self): - if FreeCAD.ActiveDocument: - sel = FreeCADGui.Selection.getSelectionEx() - if sel and sel[0].Object.Name[:3] == 'Job': - return sel[0].Object - jobs = [o for o in FreeCAD.ActiveDocument.Objects if o.Name[:3] == 'Job'] - if 1 == len(jobs): - return jobs[0] - return None +# def selectedJob(self): +# if FreeCAD.ActiveDocument: +# sel = FreeCADGui.Selection.getSelectionEx() +# if sel and sel[0].Object.Name[:3] == 'Job': +# return sel[0].Object +# jobs = [o for o in FreeCAD.ActiveDocument.Objects if o.Name[:3] == 'Job'] +# if 1 == len(jobs): +# return jobs[0] +# return None - def IsActive(self): - return not self.selectedJob() is None +# def IsActive(self): +# return not self.selectedJob() is None - def Activated(self): - job = self.selectedJob() - self.Execute(job) +# def Activated(self): +# job = self.selectedJob() +# self.Execute(job) - @classmethod - def Execute(cls, job): - import PathScripts.PathToolBitLibraryGui as PathToolBitLibraryGui - import PathScripts.PathToolControllerGui as PathToolControllerGui +# @classmethod +# def Execute(cls, job): +# import PathScripts.PathToolBitLibraryGui as PathToolBitLibraryGui +# import PathScripts.PathToolControllerGui as PathToolControllerGui - library = PathToolBitLibraryGui.ToolBitLibrary() +# library = PathToolBitLibraryGui.ToolBitLibrary() - if 1 == library.open() and job: - for nr, tool in library.selectedOrAllTools(): - tc = PathToolControllerGui.Create("TC: {}".format(tool.Label), tool, nr) - job.Proxy.addToolController(tc) - FreeCAD.ActiveDocument.recompute() - return True - return False +# if 1 == library.open() and job: +# for nr, tool in library.selectedOrAllTools(): +# tc = PathToolControllerGui.Create("TC: {}".format(tool.Label), tool, nr) +# job.Proxy.addToolController(tc) +# FreeCAD.ActiveDocument.recompute() +# return True +# return False if FreeCAD.GuiUp: FreeCADGui.addCommand('Path_ToolBitLibraryOpen', CommandToolBitLibraryOpen()) - FreeCADGui.addCommand('Path_ToolBitLibraryLoad', CommandToolBitLibraryLoad()) + FreeCADGui.addCommand('Path_ToolBitDock', CommandToolBitSelectorOpen()) -CommandList = ['Path_ToolBitLibraryOpen', 'Path_ToolBitLibraryLoad'] +BarList = ['Path_ToolBitDock'] +MenuList = ['Path_ToolBitLibraryOpen', 'Path_ToolBitDock'] FreeCAD.Console.PrintLog("Loading PathToolBitLibraryCmd... done\n") diff --git a/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py b/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py index 2dbb4c0ce9..07a826ee16 100644 --- a/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py +++ b/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py @@ -35,12 +35,13 @@ from PySide import QtCore, QtGui import PySide import json import os +import glob import traceback import uuid as UUID from functools import partial -# PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) -# PathLog.trackModule(PathLog.thisModule()) +PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) +PathLog.trackModule(PathLog.thisModule()) _UuidRole = PySide.QtCore.Qt.UserRole + 1 _PathRole = PySide.QtCore.Qt.UserRole + 2 @@ -55,11 +56,11 @@ class _TableView(PySide.QtGui.QTableView): def __init__(self, parent): PySide.QtGui.QTableView.__init__(self, parent) - self.setDragEnabled(True) - self.setAcceptDrops(True) - self.setDropIndicatorShown(True) - self.setDragDropMode(PySide.QtGui.QAbstractItemView.InternalMove) - self.setDefaultDropAction(PySide.QtCore.Qt.MoveAction) + self.setDragEnabled(False) + self.setAcceptDrops(False) + self.setDropIndicatorShown(False) + self.setDragDropMode(PySide.QtGui.QAbstractItemView.DragOnly) + self.setDefaultDropAction(PySide.QtCore.Qt.IgnoreAction) self.setSortingEnabled(True) self.setSelectionBehavior(PySide.QtGui.QAbstractItemView.SelectRows) self.verticalHeader().hide() @@ -68,18 +69,18 @@ class _TableView(PySide.QtGui.QTableView): return [PySide.QtCore.Qt.CopyAction, PySide.QtCore.Qt.MoveAction] def _uuidOfRow(self, row): - model = self.model() + model = self.toolModel() return model.data(model.index(row, 0), _UuidRole) def _rowWithUuid(self, uuid): - model = self.model() + model = self.toolModel() for row in range(model.rowCount()): if self._uuidOfRow(row) == uuid: return row return None def _copyTool(self, uuid_, dstRow): - model = self.model() + model = self.toolModel() model.insertRow(dstRow) srcRow = self._rowWithUuid(uuid_) for col in range(model.columnCount()): @@ -108,23 +109,9 @@ class _TableView(PySide.QtGui.QTableView): # pylint: disable=unused-variable row = stream.readInt32() srcRows.append(row) - # col = stream.readInt32() - # PathLog.track(row, col) - # cnt = stream.readInt32() - # for i in range(cnt): - # key = stream.readInt32() - # val = stream.readQVariant() - # PathLog.track(' ', i, key, val, type(val)) - # I have no idea what these three integers are, - # or if they even are three integers, - # but it seems to work out this way. - # i0 = stream.readInt32() - # i1 = stream.readInt32() - # i2 = stream.readInt32() - # PathLog.track(' ', i0, i1, i2) # get the uuids of all srcRows - model = self.model() + model = self.toolModel() srcUuids = [self._uuidOfRow(row) for row in set(srcRows)] destRow = self.rowAt(event.pos().y()) @@ -134,264 +121,378 @@ class _TableView(PySide.QtGui.QTableView): model.removeRow(self._rowWithUuid(uuid)) -class ToolBitLibrary(object): - '''ToolBitLibrary is the controller for displaying/selecting/creating/editing a collection of ToolBits.''' +class ModelFactory(object): + ''' Helper class to generate qtdata models for toolbit libraries + ''' def __init__(self, path=None): - self.path = path + PathLog.track() + self.path = "" + # self.currentLib = "" + + + def __libraryLoad(self, path, datamodel): + PathLog.track(path) + PathPreferences.setLastFileToolLibrary(path) + # self.currenLib = path + + with open(path) as fp: + library = json.load(fp) + + for toolBit in library['tools']: + nr = toolBit['nr'] + bit = PathToolBit.findBit(toolBit['path']) + if bit: + PathLog.track(bit) + tool = PathToolBit.Declaration(bit) + datamodel.appendRow(self._toolAdd(nr, tool, bit)) + else: + PathLog.error("Could not find tool #{}: {}".format(nr, toolBit['path'])) + + def _toolAdd(self, nr, tool, path): + + strShape = os.path.splitext(os.path.basename(tool['shape']))[0] + strDiam = tool['parameter']['Diameter'] + tooltip = "{}: {}".format(strShape, strDiam) + + toolNr = PySide.QtGui.QStandardItem() + toolNr.setData(nr, PySide.QtCore.Qt.EditRole) + toolNr.setToolTip(tool['shape']) + toolNr.setData(path, _PathRole) + toolNr.setData(UUID.uuid4(), _UuidRole) + toolNr.setToolTip(tooltip) + + toolName = PySide.QtGui.QStandardItem() + toolName.setData(tool['name'], PySide.QtCore.Qt.EditRole) + toolName.setEditable(False) + toolName.setToolTip(tooltip) + + toolShape = PySide.QtGui.QStandardItem() + toolShape.setData(strShape, PySide.QtCore.Qt.EditRole) + toolShape.setEditable(False) + + toolDiameter = PySide.QtGui.QStandardItem() + toolDiameter.setData(strDiam, PySide.QtCore.Qt.EditRole) + toolDiameter.setEditable(False) + + return [toolNr, toolName, toolShape, toolDiameter] + + def newTool(self, datamodel, path): + ''' + Adds a toolbit item to a model + ''' + PathLog.track() + + try: + nr = 0 + for row in range(datamodel.rowCount()): + itemNr = int(datamodel.item(row, 0).data(PySide.QtCore.Qt.EditRole)) + nr = max(nr, itemNr) + nr += 1 + tool = PathToolBit.Declaration(path) + except Exception: + PathLog.error(traceback.print_exc()) + + datamodel.appendRow(self._toolAdd(nr, tool, path)) + + def findLibraries(self, model): + ''' + Finds all the fctl files in a location + Returns a QStandardItemModel + ''' + PathLog.track() + path = PathPreferences.lastPathToolLibrary() + + if os.path.isdir(path): # opening all tables in a directory + libFiles = [f for f in glob.glob(path + '/*.fctl')] + libFiles.sort() + for libFile in libFiles: + loc, fnlong = os.path.split(libFile) + fn, ext = os.path.splitext(fnlong) + libItem = QtGui.QStandardItem(fn) + libItem.setToolTip(loc) + libItem.setData(libFile, _PathRole) + libItem.setIcon(QtGui.QPixmap(':/icons/Path-ToolTable.svg')) + model.appendRow(libItem) + + PathLog.debug('model rows: {}'.format(model.rowCount())) + return model + + def libraryOpen(self, model, lib=""): + ''' + opens the tools in library + Returns a QStandardItemModel + ''' + PathLog.track(lib) + + if lib == "": + lib = PathPreferences.lastFileToolLibrary() + + if os.path.isfile(lib): # An individual library is wanted + self.__libraryLoad(lib, model) + + PathLog.debug('model rows: {}'.format(model.rowCount())) + return model + + +class ToolBitSelector(object): + '''Controller for displaying a library and creating ToolControllers''' + + def __init__(self): + self.form = FreeCADGui.PySideUic.loadUi(':/panels/ToolBitSelector.ui') + self.factory = ModelFactory() + self.toolModel = PySide.QtGui.QStandardItemModel(0, len(self.columnNames())) + self.setupUI() + self.title = self.form.windowTitle() + + def columnNames(self): + return ['#', 'Tool'] + + def curLib(self): + libfile = os.path.split(PathPreferences.lastFileToolLibrary())[1] + libName = os.path.splitext(libfile)[0] + return libName + + def loadData(self): + PathLog.track() + self.toolModel.clear() + self.toolModel.setHorizontalHeaderLabels(self.columnNames()) + self.form.lblLibrary.setText(self.curLib()) + self.factory.libraryOpen(self.toolModel) + self.toolModel.takeColumn(3) + self.toolModel.takeColumn(2) + + def setupUI(self): + PathLog.track() + self.loadData() + + self.form.tools.setModel(self.toolModel) + self.form.tools.selectionModel().selectionChanged.connect(self.enableButtons) + self.form.tools.doubleClicked.connect(partial(self.selectedOrAllToolControllers)) + self.form.libraryEditorOpen.clicked.connect(self.libraryEditorOpen) + self.form.addToolController.clicked.connect(self.selectedOrAllToolControllers) + + def enableButtons(self): + selected = (len(self.form.tools.selectedIndexes()) >= 1) + if selected: + jobs = len([1 for j in FreeCAD.ActiveDocument.Objects if j.Name[:3] == "Job"]) >= 1 + self.form.addToolController.setEnabled(selected and jobs) + + def libraryEditorOpen(self): + library = ToolBitLibrary() + library.open() + self.loadData() + + def selectedOrAllTools(self): + ''' + Iterate the selection and add individual tools + If a group is selected, iterate and add children + ''' + + itemsToProcess = [] + for index in self.form.tools.selectedIndexes(): + item = index.model().itemFromIndex(index) + + if item.hasChildren(): + for i in range(item.rowCount()-1): + if item.child(i).column() == 0: + itemsToProcess.append(item.child(i)) + + elif item.column() == 0: + itemsToProcess.append(item) + + tools = [] + for item in itemsToProcess: + toolNr = int(item.data(PySide.QtCore.Qt.EditRole)) + toolPath = item.data(_PathRole) + tools.append((toolNr, PathToolBit.Factory.CreateFrom(toolPath))) + return tools + + def selectedOrAllToolControllers(self, index=None): + ''' + if no jobs, don't do anything, otherwise all TCs for all + selected toolbits + ''' + jobs = PathUtilsGui.PathUtils.GetJobs() + if len(jobs) == 0: + return + elif len(jobs) == 1: + job = jobs[0] + else: + userinput = PathUtilsGui.PathUtilsUserInput() + job = userinput.chooseJob(jobs) + + if job is None: # user may have canceled + return + + tools = self.selectedOrAllTools() + + for tool in tools: + tc = PathToolControllerGui.Create(tool[1].Label, tool[1], tool[0]) + job.Proxy.addToolController(tc) + FreeCAD.ActiveDocument.recompute() + + def open(self, path=None): + ''' load library stored in path and bring up ui''' + docs = FreeCADGui.getMainWindow().findChildren(QtGui.QDockWidget) + for doc in docs: + if doc.objectName() == "ToolSelector": + if doc.isVisible(): + doc.deleteLater() + return + else: + doc.setVisible(True) + + mw = FreeCADGui.getMainWindow() + mw.addDockWidget(QtCore.Qt.RightDockWidgetArea, self.form, + PySide.QtCore.Qt.Orientation.Vertical) + + +class ToolBitLibrary(object): + '''ToolBitLibrary is the controller for + displaying/selecting/creating/editing a collection of ToolBits.''' + + def __init__(self): + PathLog.track() + self.factory = ModelFactory() + self.toolModel = PySide.QtGui.QStandardItemModel(0, len(self.columnNames())) + self.listModel = PySide.QtGui.QStandardItemModel() self.form = FreeCADGui.PySideUic.loadUi(':/panels/ToolBitLibraryEdit.ui') self.toolTableView = _TableView(self.form.toolTableGroup) self.form.toolTableGroup.layout().replaceWidget(self.form.toolTable, self.toolTableView) self.form.toolTable.hide() self.setupUI() self.title = self.form.windowTitle() - self.LibFiles = [] - if path: - self.libraryLoad(path) - self.form.addToolController.setEnabled(False) - self.form.ButtonRemoveToolTable.setEnabled(False) - self.form.ButtonRenameToolTable.setEnabled(False) - - def _toolAdd(self, nr, tool, path): - toolNr = PySide.QtGui.QStandardItem() - toolNr.setData(nr, PySide.QtCore.Qt.EditRole) - toolNr.setData(path, _PathRole) - toolNr.setData(UUID.uuid4(), _UuidRole) - - toolName = PySide.QtGui.QStandardItem() - toolName.setData(tool['name'], PySide.QtCore.Qt.EditRole) - toolName.setEditable(False) - - toolShape = PySide.QtGui.QStandardItem() - toolShape.setData(os.path.splitext(os.path.basename(tool['shape']))[0], PySide.QtCore.Qt.EditRole) - toolShape.setEditable(False) - - toolDiameter = PySide.QtGui.QStandardItem() - toolDiameter.setData(tool['parameter']['Diameter'], PySide.QtCore.Qt.EditRole) - toolDiameter.setEditable(False) - - self.model.appendRow([toolNr, toolName, toolShape, toolDiameter]) - - def toolAdd(self): + def toolBitNew(self): PathLog.track() # pylint: disable=broad-except - try: - nr = 0 - for row in range(self.model.rowCount()): - itemNr = int(self.model.item(row, 0).data(PySide.QtCore.Qt.EditRole)) - nr = max(nr, itemNr) - nr += 1 - for i, foo in enumerate(PathToolBitGui.GetToolFiles(self.form)): - tool = PathToolBit.Declaration(foo) - self._toolAdd(nr + i, tool, foo) - self.toolTableView.resizeColumnsToContents() - except Exception: - PathLog.error('something happened') - PathLog.error(traceback.print_exc()) + # select the shape file + shapefile = PathToolBitGui.GetToolShapeFile() + if shapefile is None: # user canceled + return - def selectedOrAllTools(self): - selectedRows = set([index.row() for index in self.toolTableView.selectedIndexes()]) - if not selectedRows: - selectedRows = list(range(self.model.rowCount())) - tools = [] - for row in selectedRows: - item = self.model.item(row, 0) - toolNr = int(item.data(PySide.QtCore.Qt.EditRole)) - toolPath = item.data(_PathRole) - tools.append((toolNr, PathToolBit.Factory.CreateFrom(toolPath))) - return tools - def selectedOrAllToolControllers(self): - tools = self.selectedOrAllTools() + filename = PathToolBitGui.GetNewToolFile() + if filename == None: + return - userinput = PathUtilsGui.PathUtilsUserInput() - job = userinput.chooseJob(PathUtilsGui.PathUtils.GetJobs()) - for tool in tools: - print(tool) - tc = PathToolControllerGui.Create(tool[1].Label, tool[1], tool[0]) - job.Proxy.addToolController(tc) - FreeCAD.ActiveDocument.recompute() + # Parse out the name of the file and write the structure + loc, fil = os.path.split(filename) + fname = os.path.splitext(fil)[0] + fullpath = "{}/{}.fctb".format(loc, fname) + PathLog.debug(fullpath) + + f = PathToolBit.ToolBitFactory() + newtool = f.Create(name=fname) + newtool.BitShape = shapefile + newtool.Label = fname + newtool.Proxy.saveToFile(newtool, fullpath) + + # add it to the model + self.factory.newTool(self.toolModel, fullpath) + + def toolBitExisting(self): + + filenames = PathToolBitGui.GetToolFiles() + + if len(filenames) == 0: + return + + for f in filenames: + + loc, fil = os.path.split(f) + fname = os.path.splitext(fil)[0] + fullpath = "{}/{}.fctb".format(loc, fname) + + self.factory.newTool(self.toolModel, fullpath) def toolDelete(self): PathLog.track() selectedRows = set([index.row() for index in self.toolTableView.selectedIndexes()]) for row in sorted(list(selectedRows), key=lambda r: -r): - self.model.removeRows(row, 1) - - def libraryDelete(self): - PathLog.track() - reply = QtGui.QMessageBox.question(self.form, 'Warning', "Delete " + os.path.basename(self.path) + "?", QtGui.QMessageBox.Yes | QtGui.QMessageBox.Cancel) - if reply == QtGui.QMessageBox.Yes and len(self.path) > 0: - os.remove(self.path) - PathPreferences.setLastPathToolTable("") - self.libraryOpen(filedialog=False) - - def toolEnumerate(self): - PathLog.track() - for row in range(self.model.rowCount()): - self.model.setData(self.model.index(row, 0), row + 1, PySide.QtCore.Qt.EditRole) + self.toolModel.removeRows(row, 1) def toolSelect(self, selected, deselected): # pylint: disable=unused-argument sel = len(self.toolTableView.selectedIndexes()) > 0 self.form.toolDelete.setEnabled(sel) - if sel: - self.form.addToolController.setEnabled(True) - else: - self.form.addToolController.setEnabled(False) - def tableSelected(self, index): ''' loads the tools for the selected tool table ''' - name = self.form.TableList.itemWidget(self.form.TableList.itemFromIndex(index)).getTableName() - self.libraryLoad(PathPreferences.lastPathToolLibrary() + '/' + name) - self.form.ButtonRemoveToolTable.setEnabled(True) - self.form.ButtonRenameToolTable.setEnabled(True) + PathLog.track() + item = index.model().itemFromIndex(index) + libpath = item.data(_PathRole) + self.loadData(libpath) + self.path = libpath - def open(self, path=None, dialog=False): - '''open(path=None, dialog=False) ... load library stored in path and bring up ui. - Returns 1 if user pressed OK, 0 otherwise.''' - if path: - self.libraryOpen(path, filedialog=False) - elif dialog: - self.libraryOpen(None, True) - else: - self.libraryOpen(None, False) + def open(self): + PathLog.track() return self.form.exec_() - def updateToolbar(self): - if self.path: - self.form.librarySave.setEnabled(True) - else: - self.form.librarySave.setEnabled(False) - - def libraryOpen(self, path=None, filedialog=True): - import glob + def libraryPath(self): PathLog.track() + path = PySide.QtGui.QFileDialog.getExistingDirectory(self.form, 'Tool Library Path', PathPreferences.lastPathToolLibrary()) + if len(path) == 0: + return - # Load default search path - path = PathPreferences.lastPathToolLibrary() + PathPreferences.setLastPathToolLibrary(path) + self.loadData() - if filedialog or len(path) == 0: - path = PySide.QtGui.QFileDialog.getExistingDirectory(self.form, 'Tool Library Path', PathPreferences.lastPathToolLibrary()) - if len(path) > 0: - PathPreferences.setLastPathToolLibrary(path) - else: - return + def toolEdit(self, selected): + item = self.toolModel.item(selected.row(), 0) + if selected.column() == 0: # editing Nr + pass + else: + tbpath = item.data(_PathRole) - # Clear view - self.form.TableList.clear() - self.LibFiles.clear() - self.form.lineLibPath.clear() - self.form.lineLibPath.insert(path) + temptool = PathToolBit.ToolBitFactory().CreateFrom(tbpath, 'temptool') + self.editor = PathToolBitEdit.ToolBitEditor(temptool, + self.form.toolTableGroup, ondone=self.toolEditDone) - # Find all tool tables in directory - for file in glob.glob(path + '/*.fctl'): - self.LibFiles.append(file) + QBtn = QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel - self.LibFiles.sort() + buttonBox = QtGui.QDialogButtonBox(QBtn) + # self.buttonBox.accepted.connect(self.accept) + # self.buttonBox.rejected.connect(self.reject) - # Add all tables to list - for table in self.LibFiles: - listWidgetItem = QtGui.QListWidgetItem() - listItem = ToolTableListWidgetItem() - listItem.setTableName(os.path.basename(table)) - listItem.setIcon(QtGui.QPixmap(':/icons/Path-ToolTable.svg')) - listWidgetItem.setSizeHint(QtCore.QSize(0, 40)) - self.form.TableList.addItem(listWidgetItem) - self.form.TableList.setItemWidget(listWidgetItem, listItem) + layout = self.editor.form.layout() #QVBoxLayout() + layout.addWidget(buttonBox) + #self.setLayout(self.layout) - self.path = [] - self.form.ButtonRemoveToolTable.setEnabled(False) - self.form.ButtonRenameToolTable.setEnabled(False) - - self.toolTableView.setUpdatesEnabled(False) - self.model.clear() - self.model.setHorizontalHeaderLabels(self.columnNames()) - self.toolTableView.resizeColumnsToContents() - self.toolTableView.setUpdatesEnabled(True) - - # Search last selected table - if len(self.LibFiles) > 0: - for idx in range(len(self.LibFiles)): - if PathPreferences.lastPathToolTable() == os.path.basename(self.LibFiles[idx]): - break - # Not found, select first entry - if idx >= len(self.LibFiles): - idx = 0 - - # Load selected table - self.libraryLoad(self.LibFiles[idx]) - self.form.TableList.setCurrentRow(idx) - self.form.ButtonRemoveToolTable.setEnabled(True) - self.form.ButtonRenameToolTable.setEnabled(True) - - def libraryLoad(self, path): - self.toolTableView.setUpdatesEnabled(False) - self.model.clear() - self.model.setHorizontalHeaderLabels(self.columnNames()) - - if path: - with open(path) as fp: - PathPreferences.setLastPathToolTable(os.path.basename(path)) - library = json.load(fp) - - for toolBit in library['tools']: - nr = toolBit['nr'] - bit = PathToolBit.findBit(toolBit['path']) - if bit: - PathLog.track(bit) - tool = PathToolBit.Declaration(bit) - self._toolAdd(nr, tool, bit) - else: - PathLog.error("Could not find tool #{}: {}".format(nr, library['tools'][nr])) - - self.toolTableView.resizeColumnsToContents() - - self.toolTableView.setUpdatesEnabled(True) - - self.form.setWindowTitle("{} - {}".format(self.title, os.path.basename(path) if path else '')) - self.path = path - self.updateToolbar() + def toolEditDone(self, success=True): + FreeCAD.ActiveDocument.removeObject("temptool") + print('all done') def libraryNew(self): - self.libraryLoad(None) - self.librarySaveAs() + TooltableTypeJSON = translate("PathToolLibraryManager", "Tooltable JSON (*.fctl)") - def renameLibrary(self): - name = self.form.TableList.itemWidget(self.form.TableList.currentItem()).getTableName() - newName, ok = QtGui.QInputDialog.getText(None, translate( - "TooltableEditor", "Rename Tooltable"), translate( - "TooltableEditor", "Enter Name:"), QtGui.QLineEdit.Normal, name) - if ok and newName: - os.rename(PathPreferences.lastPathToolLibrary() + '/' + name, PathPreferences.lastPathToolLibrary() + '/' + newName) - self.libraryOpen(filedialog=False) + filename = PySide.QtGui.QFileDialog.getSaveFileName(self.form, + translate("TooltableEditor", "Save toolbit library", None), + PathPreferences.lastPathToolLibrary(), "{}".format(TooltableTypeJSON)) - # def createToolBit(self): - # tool = PathToolBit.ToolBitFactory().Create() + if not (filename and filename[0]): + self.loadData() - # #self.dialog = PySide.QtGui.QDialog(self.form) - # #layout = PySide.QtGui.QVBoxLayout(self.dialog) - # self.editor = PathToolBitEdit.ToolBitEditor(tool, self.form.toolTableGroup) - # self.editor.setupUI() - # self.buttons = PySide.QtGui.QDialogButtonBox( - # PySide.QtGui.QDialogButtonBox.Ok | PySide.QtGui.QDialogButtonBox.Cancel, - # PySide.QtCore.Qt.Horizontal, self.dialog) - # layout.addWidget(self.buttons) - # #self.buttons.accepted.connect(accept) - # #self.buttons.rejected.connect(reject) - # print(self.dialog.exec_()) + path = filename[0] if filename[0].endswith('.fctl') else "{}.fctl".format(filename[0]) + library = {} + tools = [] + library['version'] = 1 + library['tools'] = tools + with open(path, 'w') as fp: + json.dump(library, fp, sort_keys=True, indent=2) + + self.loadData() def librarySave(self): library = {} tools = [] library['version'] = 1 library['tools'] = tools - for row in range(self.model.rowCount()): - toolNr = self.model.data(self.model.index(row, 0), PySide.QtCore.Qt.EditRole) - toolPath = self.model.data(self.model.index(row, 0), _PathRole) + for row in range(self.toolModel.rowCount()): + toolNr = self.toolModel.data(self.toolModel.index(row, 0), PySide.QtCore.Qt.EditRole) + toolPath = self.toolModel.data(self.toolModel.index(row, 0), _PathRole) if PathPreferences.toolsStoreAbsolutePaths(): tools.append({'nr': toolNr, 'path': toolPath}) else: @@ -400,13 +501,112 @@ class ToolBitLibrary(object): with open(self.path, 'w') as fp: json.dump(library, fp, sort_keys=True, indent=2) + def libraryOk(self): + self.librarySave() + self.form.close() + + def libPaths(self): + lib = PathPreferences.lastFileToolLibrary() + loc = PathPreferences.lastPathToolLibrary() + #loc = os.path.split(lib)[0] + + PathLog.track("lib: {} loc: {}".format(lib, loc)) + return lib, loc + + def columnNames(self): + return ['Nr', 'Tool', 'Shape', 'Diameter'] + + def loadData(self, path=None): + PathLog.track(path) + self.toolTableView.setUpdatesEnabled(False) + self.form.TableList.setUpdatesEnabled(False) + + + if path is None: + path, loc = self.libPaths() + + self.toolModel.clear() + self.listModel.clear() + self.factory.libraryOpen(self.toolModel, lib=path) + self.factory.findLibraries(self.listModel) + + else: + self.toolModel.clear() + self.factory.libraryOpen(self.toolModel, lib=path) + + self.path = path + self.form.setWindowTitle("{}".format(PathPreferences.lastPathToolLibrary())) + self.toolModel.setHorizontalHeaderLabels(self.columnNames()) + self.listModel.setHorizontalHeaderLabels(['Library']) + + + # Select the current library in the list of tables + curIndex = None + for i in range(self.listModel.rowCount()): + item = self.listModel.item(i) + if item.data(_PathRole) == path: + curIndex = self.listModel.indexFromItem(item) + + if curIndex: + sm = self.form.TableList.selectionModel() + sm.select(curIndex, QtCore.QItemSelectionModel.Select) + + self.toolTableView.setUpdatesEnabled(True) + self.form.TableList.setUpdatesEnabled(True) + + def setupUI(self): + PathLog.track() + self.form.TableList.setModel(self.listModel) + self.toolTableView.setModel(self.toolModel) + + self.loadData() + + self.toolTableView.resizeColumnsToContents() + self.toolTableView.selectionModel().selectionChanged.connect(self.toolSelect) + self.toolTableView.doubleClicked.connect(self.toolEdit) + + self.form.TableList.clicked.connect(self.tableSelected) + + self.form.toolAdd.clicked.connect(self.toolBitExisting) + self.form.toolDelete.clicked.connect(self.toolDelete) + self.form.toolCreate.clicked.connect(self.toolBitNew) + + self.form.addToolTable.clicked.connect(self.libraryNew) + + self.form.libraryOpen.clicked.connect(self.libraryPath) + self.form.librarySave.clicked.connect(self.libraryOk) + self.form.libraryExport.clicked.connect(self.librarySaveAs) + + self.toolSelect([], []) + + def librarySaveAs(self, path): + + TooltableTypeJSON = translate("PathToolLibraryManager", "Tooltable JSON (*.fctl)") + TooltableTypeLinuxCNC = translate("PathToolLibraryManager", "LinuxCNC tooltable (*.tbl)") + + filename = PySide.QtGui.QFileDialog.getSaveFileName(self.form, + translate("TooltableEditor", "Save toolbit library", None), + PathPreferences.lastPathToolLibrary(), "{};;{}".format(TooltableTypeJSON, + TooltableTypeLinuxCNC)) + if filename and filename[0]: + if filename[1] == TooltableTypeLinuxCNC: + path = filename[0] if filename[0].endswith('.tbl') else "{}.tbl".format(filename[0]) + self.libararySaveLinuxCNC(path) + else: + path = filename[0] if filename[0].endswith('.fctl') else "{}.fctl".format(filename[0]) + self.path = path + self.librarySave() + self.updateToolbar() + def libararySaveLinuxCNC(self, path): + # linuxcnc line template + LIN = "T{} P{} X{} Y{} Z{} A{} B{} C{} U{} V{} W{} D{} I{} J{} Q{}; {}" with open(path, 'w') as fp: fp.write(";\n") - for row in range(self.model.rowCount()): - toolNr = self.model.data(self.model.index(row, 0), PySide.QtCore.Qt.EditRole) - toolPath = self.model.data(self.model.index(row, 0), _PathRole) + for row in range(self.toolModel.rowCount()): + toolNr = self.toolModel.data(self.toolModel.index(row, 0), PySide.QtCore.Qt.EditRole) + toolPath = self.toolModel.data(self.toolModel.index(row, 0), _PathRole) bit = PathToolBit.Factory.CreateFrom(toolPath) if bit: @@ -429,129 +629,12 @@ class ToolBitLibrary(object): orientation = bit.Orientation if hasattr(bit, "Orientation") else "0" remark = bit.Label - fp.write("T%s P%s X%s Y%s Z%s A%s B%s C%s U%s V%s W%s D%s I%s J%s Q%s ; %s\n" % - (toolNr, - pocket, - xoffset, - yoffset, - zoffset, - aoffset, - boffset, - coffset, - uoffset, - voffset, - woffset, - diameter, - frontangle, - backangle, - orientation, - remark)) + fp.write(LIN.format(toolNr, pocket, xoffset, yoffset, + zoffset, aoffset, boffset, coffset, uoffset, + voffset, woffset, diameter, frontangle, backangle, + orientation, remark) + "\n") FreeCAD.ActiveDocument.removeObject(bit.Name) else: PathLog.error("Could not find tool #{} ".format(toolNr)) - - def librarySaveAs(self): - TooltableTypeJSON = translate("PathToolLibraryManager", "Tooltable JSON (*.fctl)") - TooltableTypeLinuxCNC = translate("PathToolLibraryManager", "LinuxCNC tooltable (*.tbl)") - - filename = PySide.QtGui.QFileDialog.getSaveFileName(self.form, - translate("TooltableEditor", "Save toolbit library", None), - PathPreferences.lastPathToolLibrary(), "{};;{}".format(TooltableTypeJSON, - TooltableTypeLinuxCNC)) - # filename = PySide.QtGui.QFileDialog.getSaveFileName(self.form, \ - # 'Tool Library', PathPreferences.lastPathToolLibrary(), '*.fctl') - if filename and filename[0]: - if filename[1] == TooltableTypeLinuxCNC: - path = filename[0] if filename[0].endswith('.tbl') else "{}.tbl".format(filename[0]) - self.libararySaveLinuxCNC(path) - else: - path = filename[0] if filename[0].endswith('.fctl') else "{}.fctl".format(filename[0]) - PathPreferences.setLastPathToolLibrary(os.path.dirname(path)) - self.path = path - self.librarySave() - self.updateToolbar() - PathPreferences.setLastPathToolTable(os.path.basename(path)) - self.libraryOpen(None, False) - - def libraryCancel(self): - self.form.close() - - def columnNames(self): - return ['Nr', 'Tool', 'Shape', 'Diameter'] - - def toolEdit(self, selected): - print('here') - print(selected) - if selected.column() == 0: - print('nope') - else: - print('yep') - - def setupUI(self): - PathLog.track('+') - self.model = PySide.QtGui.QStandardItemModel(0, len(self.columnNames()), self.toolTableView) - self.model.setHorizontalHeaderLabels(self.columnNames()) - - self.toolTableView.setModel(self.model) - self.toolTableView.resizeColumnsToContents() - self.toolTableView.selectionModel().selectionChanged.connect(self.toolSelect) - self.toolTableView.doubleClicked.connect(self.toolEdit) - - self.form.toolAdd.clicked.connect(self.toolAdd) - self.form.toolDelete.clicked.connect(self.toolDelete) - self.form.toolEnumerate.clicked.connect(self.toolEnumerate) - # self.form.createToolBit.clicked.connect(self.createToolBit) - - self.form.ButtonAddToolTable.clicked.connect(self.libraryNew) - self.form.ButtonRemoveToolTable.clicked.connect(self.libraryDelete) - self.form.ButtonRenameToolTable.clicked.connect(self.renameLibrary) - - # self.form.libraryNew.clicked.connect(self.libraryNew) - self.form.libraryOpen.clicked.connect(partial(self.libraryOpen, filedialog=True)) - self.form.librarySave.clicked.connect(self.librarySave) - self.form.librarySaveAs.clicked.connect(self.librarySaveAs) - self.form.libraryCancel.clicked.connect(self.libraryCancel) - - self.form.addToolController.clicked.connect(self.selectedOrAllToolControllers) - - self.form.TableList.clicked.connect(self.tableSelected) - - self.toolSelect([], []) - self.updateToolbar() - PathLog.track('-') - - -class ToolTableListWidgetItem(QtGui.QWidget): - toolMoved = QtCore.Signal() - - def __init__(self): - super(ToolTableListWidgetItem, self).__init__() - - self.setAcceptDrops(True) - - self.mainLayout = QtGui.QHBoxLayout() - self.iconQLabel = QtGui.QLabel() - self.tableNameLabel = QtGui.QLabel() - self.mainLayout.addWidget(self.iconQLabel, 0) - self.mainLayout.addWidget(self.tableNameLabel, 1) - self.setLayout(self.mainLayout) - - def setTableName(self, text): - self.tableNameLabel.setText(text) - - def getTableName(self): - return self.tableNameLabel.text() - - def setIcon(self, icon): - icon = icon.scaled(22, 22) - self.iconQLabel.setPixmap(icon) - - # def dragEnterEvent(self, e): - # currentToolTable = self.tlm.getCurrentTableName() - # thisToolTable = self.getTableName() - - def dropEvent(self, e): - selectedTools = e.source().selectedIndexes() - print("Drop: {}, {}".format(selectedTools, selectedTools[1].data())) From 7c4c25ceed9af2fd81ab2ec0802a62700a0e29f8 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Mon, 2 Nov 2020 10:39:30 -0600 Subject: [PATCH 02/18] Editing Works --- src/Mod/Path/PathScripts/PathToolBitEdit.py | 62 ++++--- src/Mod/Path/PathScripts/PathToolBitGui.py | 156 +++++------------- .../Path/PathScripts/PathToolBitLibraryGui.py | 68 ++++++-- 3 files changed, 134 insertions(+), 152 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathToolBitEdit.py b/src/Mod/Path/PathScripts/PathToolBitEdit.py index c3e0120149..318229b19a 100644 --- a/src/Mod/Path/PathScripts/PathToolBitEdit.py +++ b/src/Mod/Path/PathScripts/PathToolBitEdit.py @@ -42,26 +42,31 @@ def translate(context, text, disambig=None): class ToolBitEditor(object): '''UI and controller for editing a ToolBit. - The controller embeds the UI to the parentWidget which has to have a layout attached to it. + The controller embeds the UI to the parentWidget which has to have a + layout attached to it. ''' - def __init__(self, tool, parentWidget=None, ondone=None): + def __init__(self, tool, parentWidget=None, loadBitBody=True): + PathLog.track() self.form = FreeCADGui.PySideUic.loadUi(":/panels/ToolBitEditor.ui") if parentWidget: self.form.setParent(parentWidget) parentWidget.layout().addWidget(self.form) - self.ondone = ondone - self.tool = tool + self.loadbitbody = loadBitBody if not tool.BitShape: self.tool.BitShape = 'endmill.fcstd' - self.tool.Proxy.loadBitBody(self.tool) + + if self.loadbitbody: + self.tool.Proxy.loadBitBody(self.tool) + self.setupTool(self.tool) self.setupAttributes(self.tool) def setupTool(self, tool): + PathLog.track() layout = self.form.bitParams.layout() for i in range(layout.rowCount() - 1, -1, -1): layout.removeRow(i) @@ -71,7 +76,8 @@ class ToolBitEditor(object): if tool.getGroupOfProperty(name) == PathToolBit.PropertyGroupBit: qsb = ui.createWidget('Gui::QuantitySpinBox') editor[name] = PathGui.QuantitySpinBox(qsb, tool, name) - label = QtGui.QLabel(re.sub('([A-Z][a-z]+)', r' \1', re.sub('([A-Z]+)', r' \1', name))) + label = QtGui.QLabel(re.sub('([A-Z][a-z]+)', r' \1', + re.sub('([A-Z]+)', r' \1', name))) layout.addRow(label, qsb) self.bitEditor = editor img = tool.Proxy.getBitThumbnail(tool) @@ -81,6 +87,7 @@ class ToolBitEditor(object): self.form.image.setPixmap(QtGui.QPixmap()) def setupAttributes(self, tool): + PathLog.track() self.proto = PathToolBit.AttributePrototype() self.props = sorted(self.proto.properties) self.delegate = PathSetupSheetGui.Delegate(self.form) @@ -101,10 +108,15 @@ class ToolBitEditor(object): else: - self.model.setData(self.model.index(i, 0), isset, QtCore.Qt.EditRole) - self.model.setData(self.model.index(i, 1), name, QtCore.Qt.EditRole) - self.model.setData(self.model.index(i, 2), prop, PathSetupSheetGui.Delegate.PropertyRole) - self.model.setData(self.model.index(i, 2), prop.displayString(), QtCore.Qt.DisplayRole) + self.model.setData(self.model.index(i, 0), isset, + QtCore.Qt.EditRole) + self.model.setData(self.model.index(i, 1), name, + QtCore.Qt.EditRole) + self.model.setData(self.model.index(i, 2), prop, + PathSetupSheetGui.Delegate.PropertyRole) + self.model.setData(self.model.index(i, 2), + prop.displayString(), + QtCore.Qt.DisplayRole) self.model.item(i, 0).setCheckable(True) self.model.item(i, 0).setText('') @@ -143,13 +155,15 @@ class ToolBitEditor(object): self.model.dataChanged.connect(self.updateData) def updateData(self, topLeft, bottomRight): - # pylint: disable=unused-argument + PathLog.track() if 0 == topLeft.column(): - isset = self.model.item(topLeft.row(), 0).checkState() == QtCore.Qt.Checked + isset = self.model.item(topLeft.row(), + 0).checkState() == QtCore.Qt.Checked self.model.item(topLeft.row(), 1).setEnabled(isset) self.model.item(topLeft.row(), 2).setEnabled(isset) def accept(self): + PathLog.track() self.refresh() self.tool.Proxy.unloadBitBody(self.tool) @@ -158,11 +172,14 @@ class ToolBitEditor(object): prop = self.proto.getProperty(name) enabled = self.model.item(i, 0).checkState() == QtCore.Qt.Checked if enabled and not prop.getValue() is None: - prop.setupProperty(self.tool, name, PathToolBit.PropertyGroupAttribute, prop.getValue()) + prop.setupProperty(self.tool, name, + PathToolBit.PropertyGroupAttribute, + prop.getValue()) elif hasattr(self.tool, name): self.tool.removeProperty(name) def reject(self): + PathLog.track() self.tool.Proxy.unloadBitBody(self.tool) def updateUI(self): @@ -174,6 +191,7 @@ class ToolBitEditor(object): self.bitEditor[editor].updateSpinBox() def updateShape(self): + PathLog.track() self.tool.BitShape = str(self.form.shapePath.text()) self.setupTool(self.tool) self.form.toolName.setText(self.tool.Label) @@ -182,7 +200,6 @@ class ToolBitEditor(object): self.bitEditor[editor].updateSpinBox() def updateTool(self): - # pylint: disable=protected-access PathLog.track() self.tool.Label = str(self.form.toolName.text()) self.tool.BitShape = str(self.form.shapePath.text()) @@ -190,7 +207,7 @@ class ToolBitEditor(object): for editor in self.bitEditor: self.bitEditor[editor].updateProperty() - self.tool.Proxy._updateBitShape(self.tool) + # self.tool.Proxy._updateBitShape(self.tool) def refresh(self): PathLog.track() @@ -200,21 +217,19 @@ class ToolBitEditor(object): self.form.blockSignals(False) def selectShape(self): + PathLog.track() path = self.tool.BitShape if not path: path = PathPreferences.lastPathToolShape() foo = QtGui.QFileDialog.getOpenFileName(self.form, - "Path - Tool Shape", - path, - "*.fcstd") + "Path - Tool Shape", + path, + "*.fcstd") if foo and foo[0]: PathPreferences.setLastPathToolShape(os.path.dirname(foo[0])) self.form.shapePath.setText(foo[0]) self.updateShape() - def ok(self): - self.form.close() - def setupUI(self): PathLog.track() self.updateUI() @@ -222,8 +237,3 @@ class ToolBitEditor(object): self.form.toolName.editingFinished.connect(self.refresh) self.form.shapePath.editingFinished.connect(self.updateShape) self.form.shapeSet.clicked.connect(self.selectShape) - self.form.buttonBox.accepted.connect(self.ok) - self.form.buttonBox.rejected.connect(self.ok) - if self.ondone is not None: - self.form.buttonBox.rejected.connect(self.ondone) - diff --git a/src/Mod/Path/PathScripts/PathToolBitGui.py b/src/Mod/Path/PathScripts/PathToolBitGui.py index 9242dfc033..35deff9db2 100644 --- a/src/Mod/Path/PathScripts/PathToolBitGui.py +++ b/src/Mod/Path/PathScripts/PathToolBitGui.py @@ -36,12 +36,14 @@ __author__ = "sliptonic (Brad Collette)" __url__ = "https://www.freecadweb.org" __doc__ = "Task panel editor for a ToolBit" + # Qt translation handling def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) -#PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) -#PathLog.trackModule(PathLog.thisModule()) +# PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) +# PathLog.trackModule(PathLog.thisModule()) + class ViewProvider(object): '''ViewProvider for a ToolBit. @@ -58,7 +60,7 @@ class ViewProvider(object): def attach(self, vobj): PathLog.track(vobj.Object) self.vobj = vobj - self.obj = vobj.Object + self.obj = vobj.Object def getIcon(self): png = self.obj.Proxy.getBitThumbnail(self.obj) @@ -113,6 +115,7 @@ class ViewProvider(object): def doubleClicked(self, vobj): self.setEdit(vobj) + class TaskPanel: '''TaskPanel for the SetupSheet - if it is being edited directly.''' @@ -123,14 +126,16 @@ class TaskPanel: self.editor = PathToolBitEdit.ToolBitEditor(self.obj) self.form = self.editor.form self.deleteOnReject = deleteOnReject - FreeCAD.ActiveDocument.openTransaction(translate('PathToolBit', 'Edit ToolBit')) + FreeCAD.ActiveDocument.openTransaction(translate('PathToolBit', + 'Edit ToolBit')) def reject(self): FreeCAD.ActiveDocument.abortTransaction() self.editor.reject() FreeCADGui.Control.closeDialog() if self.deleteOnReject: - FreeCAD.ActiveDocument.openTransaction(translate('PathToolBit', 'Uncreate ToolBit')) + FreeCAD.ActiveDocument.openTransaction(translate('PathToolBit', + 'Uncreate ToolBit')) self.editor.reject() FreeCAD.ActiveDocument.removeObject(self.obj.Name) FreeCAD.ActiveDocument.commitTransaction() @@ -155,132 +160,47 @@ class TaskPanel: self.editor.setupUI() -class ToolBitSelector(object): - ToolRole = QtCore.Qt.UserRole + 1 - - def __init__(self): - self.buttons = None - self.editor = None - self.dialog = None - self.form = FreeCADGui.PySideUic.loadUi(':/panels/ToolBitSelector.ui') - self.setupUI() - - def updateTools(self, selected=None): - PathLog.track() - selItem = None - self.form.tools.setUpdatesEnabled(False) - if selected is None and self.form.tools.currentItem(): - selected = self.form.tools.currentItem().text() - self.form.tools.clear() - for tool in sorted(self.loadedTools(), key=lambda t: t.Label): - icon = None - if tool.ViewObject and tool.ViewObject.Proxy: - icon = tool.ViewObject.Proxy.getIcon() - if icon and isinstance(icon, QtGui.QIcon): - item = QtGui.QListWidgetItem(icon, tool.Label) - else: - item = QtGui.QListWidgetItem(tool.Label) - item.setData(self.ToolRole, tool) - if selected == tool.Label: - selItem = item - self.form.tools.addItem(item) - if selItem: - self.form.tools.setCurrentItem(selItem) - self.updateSelection() - self.form.tools.setUpdatesEnabled(True) - - def getTool(self): - PathLog.track() - self.updateTools() - res = self.form.exec_() - if 1 == res and self.form.tools.currentItem(): - return self.form.tools.currentItem().data(self.ToolRole) - return None - - def loadedTools(self): - PathLog.track() - if FreeCAD.ActiveDocument: - return [o for o in FreeCAD.ActiveDocument.Objects if hasattr(o, 'Proxy') and isinstance(o.Proxy, PathToolBit.ToolBit)] - return [] - - def loadTool(self): - PathLog.track() - tool = LoadTool(self.form) - if tool: - self.updateTools(tool.Label) - - def createTool(self): - PathLog.track() - tool = PathToolBit.Factory.Create() - - def accept(): - self.editor.accept() - self.dialog.done(1) - self.updateTools(tool.Label) - - def reject(): - FreeCAD.ActiveDocument.openTransaction(translate('PathToolBit', 'Uncreate ToolBit')) - self.editor.reject() - self.dialog.done(0) - FreeCAD.ActiveDocument.removeObject(tool.Name) - FreeCAD.ActiveDocument.commitTransaction() - - self.dialog = QtGui.QDialog(self.form) - layout = QtGui.QVBoxLayout(self.dialog) - self.editor = PathToolBitEdit.ToolBitEditor(tool, self.dialog) - self.editor.setupUI() - self.buttons = QtGui.QDialogButtonBox( - QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel, - QtCore.Qt.Horizontal, self.dialog) - layout.addWidget(self.buttons) - self.buttons.accepted.connect(accept) - self.buttons.rejected.connect(reject) - print(self.dialog.exec_()) - - def updateSelection(self): - PathLog.track() - if self.form.tools.selectedItems(): - self.form.buttonBox.button(QtGui.QDialogButtonBox.Ok).setEnabled(True) - else: - self.form.buttonBox.button(QtGui.QDialogButtonBox.Ok).setEnabled(False) - - def setupUI(self): - PathLog.track() - self.form.toolCreate.clicked.connect(self.createTool) - self.form.toolLoad.clicked.connect(self.loadTool) - self.form.tools.itemSelectionChanged.connect(self.updateSelection) - self.form.tools.doubleClicked.connect(self.form.accept) - class ToolBitGuiFactory(PathToolBit.ToolBitFactory): def Create(self, name='ToolBit', shapeFile=None): - '''Create(name = 'ToolBit') ... creates a new tool bit. - It is assumed the tool will be edited immediately so the internal bit body is still attached.''' - FreeCAD.ActiveDocument.openTransaction(translate('PathToolBit', 'Create ToolBit')) + ''' + Create(name = 'ToolBit') ... creates a new tool bit. + It is assumed the tool will be edited immediately so the internal + bit body is still attached. + ''' + FreeCAD.ActiveDocument.openTransaction(translate('PathToolBit', + 'Create ToolBit')) tool = PathToolBit.ToolBitFactory.Create(self, name, shapeFile) PathIconViewProvider.Attach(tool.ViewObject, name) FreeCAD.ActiveDocument.commitTransaction() return tool -def GetToolFile(parent = None): + +def GetToolFile(parent=None): if parent is None: parent = QtGui.QApplication.activeWindow() - foo = QtGui.QFileDialog.getOpenFileName(parent, 'Tool', PathPreferences.lastPathToolBit(), '*.fctb') + foo = QtGui.QFileDialog.getOpenFileName(parent, 'Tool', + PathPreferences.lastPathToolBit(), + '*.fctb') if foo and foo[0]: PathPreferences.setLastPathToolBit(os.path.dirname(foo[0])) return foo[0] return None -def GetToolFiles(parent = None): + +def GetToolFiles(parent=None): if parent is None: parent = QtGui.QApplication.activeWindow() - foo = QtGui.QFileDialog.getOpenFileNames(parent, 'Tool', PathPreferences.lastPathToolBit(), '*.fctb') + foo = QtGui.QFileDialog.getOpenFileNames(parent, 'Tool', + PathPreferences.lastPathToolBit(), + '*.fctb') if foo and foo[0]: PathPreferences.setLastPathToolBit(os.path.dirname(foo[0][0])) return foo[0] return [] -def GetToolShapeFile(parent = None): + +def GetToolShapeFile(parent=None): if parent is None: parent = QtGui.QApplication.activeWindow() @@ -290,7 +210,8 @@ def GetToolShapeFile(parent = None): elif not os.path.isdir(location): location = PathPreferences.filePath() - fname = QtGui.QFileDialog.getOpenFileName(parent, 'Select Tool Shape', location, '*.fcstd') + fname = QtGui.QFileDialog.getOpenFileName(parent, 'Select Tool Shape', + location, '*.fcstd') if fname and fname[0]: if fname != location: PathPreferences.setLastPathToolShape(location) @@ -298,15 +219,22 @@ def GetToolShapeFile(parent = None): else: return None -def LoadTool(parent = None): - '''LoadTool(parent=None) ... Open a file dialog to load a tool from a file.''' + +def LoadTool(parent=None): + ''' + LoadTool(parent=None) ... Open a file dialog to load a tool from a file. + ''' foo = GetToolFile(parent) return PathToolBit.Factory.CreateFrom(foo) if foo else foo -def LoadTools(parent = None): - '''LoadTool(parent=None) ... Open a file dialog to load a tool from a file.''' + +def LoadTools(parent=None): + ''' + LoadTool(parent=None) ... Open a file dialog to load a tool from a file. + ''' return [PathToolBit.Factory.CreateFrom(foo) for foo in GetToolFiles(parent)] + # Set the factory so all tools are created with UI PathToolBit.Factory = ToolBitGuiFactory() diff --git a/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py b/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py index 07a826ee16..e0fa02a275 100644 --- a/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py +++ b/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py @@ -40,8 +40,8 @@ import traceback import uuid as UUID from functools import partial -PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) -PathLog.trackModule(PathLog.thisModule()) +# PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) +# PathLog.trackModule(PathLog.thisModule()) _UuidRole = PySide.QtCore.Qt.UserRole + 1 _PathRole = PySide.QtCore.Qt.UserRole + 2 @@ -355,6 +355,7 @@ class ToolBitLibrary(object): def __init__(self): PathLog.track() self.factory = ModelFactory() + self.temptool = None self.toolModel = PySide.QtGui.QStandardItemModel(0, len(self.columnNames())) self.listModel = PySide.QtGui.QStandardItemModel() self.form = FreeCADGui.PySideUic.loadUi(':/panels/ToolBitLibraryEdit.ui') @@ -415,7 +416,6 @@ class ToolBitLibrary(object): self.toolModel.removeRows(row, 1) def toolSelect(self, selected, deselected): - # pylint: disable=unused-argument sel = len(self.toolTableView.selectedIndexes()) > 0 self.form.toolDelete.setEnabled(sel) @@ -440,26 +440,70 @@ class ToolBitLibrary(object): PathPreferences.setLastPathToolLibrary(path) self.loadData() + def cleanupDocument(self): + # This feels like a hack. Remove the toolbit object + # remove the editor from the dialog + # re-enable all the controls + self.temptool.Document.removeObject(self.temptool.Name) + self.temptool = None + widget = self.form.toolTableGroup.children()[-1] + widget.setParent(None) + self.editor = None + self.lockoff() + + def accept(self): + self.temptool.Proxy.saveToFile(self.temptool, self.temptool.File) + self.loadData() + self.cleanupDocument() + + def reject(self): + self.cleanupDocument() + + def lockon(self): + self.toolTableView.setEnabled(False) + self.form.toolCreate.setEnabled(False) + self.form.toolDelete.setEnabled(False) + self.form.toolAdd.setEnabled(False) + self.form.TableList.setEnabled(False) + self.form.libraryOpen.setEnabled(False) + self.form.libraryExport.setEnabled(False) + self.form.addToolTable.setEnabled(False) + self.form.librarySave.setEnabled(False) + + def lockoff(self): + self.toolTableView.setEnabled(True) + self.form.toolCreate.setEnabled(True) + self.form.toolDelete.setEnabled(True) + self.form.toolAdd.setEnabled(True) + self.form.toolTable.setEnabled(True) + self.form.TableList.setEnabled(True) + self.form.libraryOpen.setEnabled(True) + self.form.libraryExport.setEnabled(True) + self.form.addToolTable.setEnabled(True) + self.form.librarySave.setEnabled(True) + def toolEdit(self, selected): item = self.toolModel.item(selected.row(), 0) + + if self.temptool is not None: + self.temptool.Document.removeObject(self.temptool.Name) + if selected.column() == 0: # editing Nr pass else: tbpath = item.data(_PathRole) - - temptool = PathToolBit.ToolBitFactory().CreateFrom(tbpath, 'temptool') - self.editor = PathToolBitEdit.ToolBitEditor(temptool, - self.form.toolTableGroup, ondone=self.toolEditDone) + self.temptool = PathToolBit.ToolBitFactory().CreateFrom(tbpath, 'temptool') + self.editor = PathToolBitEdit.ToolBitEditor(self.temptool, self.form.toolTableGroup, loadBitBody=False) QBtn = QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel - buttonBox = QtGui.QDialogButtonBox(QBtn) - # self.buttonBox.accepted.connect(self.accept) - # self.buttonBox.rejected.connect(self.reject) + buttonBox.accepted.connect(self.accept) + buttonBox.rejected.connect(self.reject) - layout = self.editor.form.layout() #QVBoxLayout() + layout = self.editor.form.layout() layout.addWidget(buttonBox) - #self.setLayout(self.layout) + self.lockon() + self.editor.setupUI() def toolEditDone(self, success=True): FreeCAD.ActiveDocument.removeObject("temptool") From 42f7854a5ee000d9b3a4880d6022ed53ae7070a1 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Wed, 4 Nov 2020 09:38:16 -0600 Subject: [PATCH 03/18] fix bug in create toolbit flow --- src/Mod/Path/PathScripts/PathToolBitGui.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Mod/Path/PathScripts/PathToolBitGui.py b/src/Mod/Path/PathScripts/PathToolBitGui.py index 35deff9db2..cac76acce2 100644 --- a/src/Mod/Path/PathScripts/PathToolBitGui.py +++ b/src/Mod/Path/PathScripts/PathToolBitGui.py @@ -176,6 +176,18 @@ class ToolBitGuiFactory(PathToolBit.ToolBitFactory): return tool +def GetNewToolFile(parent=None): + if parent is None: + parent = QtGui.QApplication.activeWindow() + foo = QtGui.QFileDialog.getSaveFileName(parent, 'Tool', + PathPreferences.lastPathToolBit(), + '*.fctb') + if foo and foo[0]: + PathPreferences.setLastPathToolBit(os.path.dirname(foo[0])) + return foo[0] + return None + + def GetToolFile(parent=None): if parent is None: parent = QtGui.QApplication.activeWindow() From 88a24475b8dca36d97e74226630283840101f9e1 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Wed, 4 Nov 2020 10:44:07 -0600 Subject: [PATCH 04/18] Better cleanup on new toolbit creation --- src/Mod/Path/PathScripts/PathToolBitLibraryGui.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py b/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py index e0fa02a275..3e3c025d2c 100644 --- a/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py +++ b/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py @@ -367,14 +367,12 @@ class ToolBitLibrary(object): def toolBitNew(self): PathLog.track() - # pylint: disable=broad-except # select the shape file shapefile = PathToolBitGui.GetToolShapeFile() if shapefile is None: # user canceled return - filename = PathToolBitGui.GetNewToolFile() if filename == None: return @@ -385,11 +383,13 @@ class ToolBitLibrary(object): fullpath = "{}/{}.fctb".format(loc, fname) PathLog.debug(fullpath) - f = PathToolBit.ToolBitFactory() - newtool = f.Create(name=fname) - newtool.BitShape = shapefile - newtool.Label = fname - newtool.Proxy.saveToFile(newtool, fullpath) + self.temptool = PathToolBit.ToolBitFactory().Create(name=fname) + self.temptool.BitShape = shapefile + self.temptool.Proxy.unloadBitBody(self.temptool) + self.temptool.Label = fname + self.temptool.Proxy.saveToFile(self.temptool, fullpath) + self.temptool.Document.removeObject(self.temptool.Name) + self.temptool = None # add it to the model self.factory.newTool(self.toolModel, fullpath) @@ -444,6 +444,7 @@ class ToolBitLibrary(object): # This feels like a hack. Remove the toolbit object # remove the editor from the dialog # re-enable all the controls + self.temptool.Proxy.unloadBitBody(self.temptool) self.temptool.Document.removeObject(self.temptool.Name) self.temptool = None widget = self.form.toolTableGroup.children()[-1] From 234b88cc18b95d61df84031b3646706a1602f9c6 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Wed, 4 Nov 2020 14:04:32 -0600 Subject: [PATCH 05/18] added attribute to Toolbit for spindlepower setting this to false will suppress M3 commands. User can configure a toolbit to never have the spindle turn on this is to prevent accidental powering of the spindle with unpowered tools like dragknife and probe --- src/Mod/Path/PathScripts/PathToolBit.py | 10 +-- src/Mod/Path/PathScripts/PathToolBitEdit.py | 16 +++-- .../Path/PathScripts/PathToolController.py | 66 ++++++++++++++----- 3 files changed, 65 insertions(+), 27 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathToolBit.py b/src/Mod/Path/PathScripts/PathToolBit.py index efafe2c3a3..e2f2c45e6d 100644 --- a/src/Mod/Path/PathScripts/PathToolBit.py +++ b/src/Mod/Path/PathScripts/PathToolBit.py @@ -42,8 +42,8 @@ __author__ = "sliptonic (Brad Collette)" __url__ = "https://www.freecadweb.org" __doc__ = "Class to deal with and represent a tool bit." -# PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) -# PathLog.trackModule() +PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) +PathLog.trackModule() def translate(context, text, disambig=None): @@ -363,6 +363,7 @@ class ToolBit(object): def Declaration(path): + print(path) with open(path, 'r') as fp: return json.load(fp) @@ -388,12 +389,13 @@ class AttributePrototype(PathSetupSheetOpPrototype.OpPrototype): self.addProperty('App::PropertyMap', 'UserAttributes', PropertyGroupAttribute, translate('PathToolBit', 'User Defined Values')) - + self.addProperty('App::PropertyBool', 'SpindlePower', + PropertyGroupAttribute, translate('PathToolBit', + 'Whether Spindle Power should be allowed')) class ToolBitFactory(object): def CreateFromAttrs(self, attrs, name='ToolBit'): - # pylint: disable=protected-access obj = Factory.Create(name, attrs['shape']) obj.Label = attrs['name'] params = attrs['parameter'] diff --git a/src/Mod/Path/PathScripts/PathToolBitEdit.py b/src/Mod/Path/PathScripts/PathToolBitEdit.py index 318229b19a..8ab67e77b0 100644 --- a/src/Mod/Path/PathScripts/PathToolBitEdit.py +++ b/src/Mod/Path/PathScripts/PathToolBitEdit.py @@ -169,14 +169,16 @@ class ToolBitEditor(object): # get the attributes for i, name in enumerate(self.props): + print('in accept: {}'.format(name)) prop = self.proto.getProperty(name) - enabled = self.model.item(i, 0).checkState() == QtCore.Qt.Checked - if enabled and not prop.getValue() is None: - prop.setupProperty(self.tool, name, - PathToolBit.PropertyGroupAttribute, - prop.getValue()) - elif hasattr(self.tool, name): - self.tool.removeProperty(name) + if self.model.item(i, 0) is not None: + enabled = self.model.item(i, 0).checkState() == QtCore.Qt.Checked + if enabled and not prop.getValue() is None: + prop.setupProperty(self.tool, name, + PathToolBit.PropertyGroupAttribute, + prop.getValue()) + elif hasattr(self.tool, name): + self.tool.removeProperty(name) def reject(self): PathLog.track() diff --git a/src/Mod/Path/PathScripts/PathToolController.py b/src/Mod/Path/PathScripts/PathToolController.py index a3c315288e..2ab0324f6a 100644 --- a/src/Mod/Path/PathScripts/PathToolController.py +++ b/src/Mod/Path/PathScripts/PathToolController.py @@ -30,16 +30,17 @@ import PathScripts.PathToolBit as PathToolBit from PySide import QtCore -#PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) -#PathLog.trackModule(PathLog.thisModule()) +# PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) +# PathLog.trackModule(PathLog.thisModule()) + # Qt translation handling def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) + class ToolControllerTemplate: '''Attribute and sub element strings for template export/import.''' - # pylint: disable=no-init Expressions = 'xengine' ExprExpr = 'expr' @@ -56,21 +57,36 @@ class ToolControllerTemplate: VertFeed = 'vfeed' VertRapid = 'vrapid' + class ToolController: def __init__(self, obj, cTool=False): PathLog.track('tool: {}'.format(cTool)) - obj.addProperty("App::PropertyIntegerConstraint", "ToolNumber", "Tool", QtCore.QT_TRANSLATE_NOOP("PathToolController", "The active tool")) + obj.addProperty("App::PropertyIntegerConstraint", "ToolNumber", + "Tool", QtCore.QT_TRANSLATE_NOOP("PathToolController", + "The active tool")) obj.ToolNumber = (0, 0, 10000, 1) self.ensureUseLegacyTool(obj, cTool) - obj.addProperty("App::PropertyFloat", "SpindleSpeed", "Tool", QtCore.QT_TRANSLATE_NOOP("PathToolController", "The speed of the cutting spindle in RPM")) - obj.addProperty("App::PropertyEnumeration", "SpindleDir", "Tool", QtCore.QT_TRANSLATE_NOOP("PathToolController", "Direction of spindle rotation")) + obj.addProperty("App::PropertyFloat", "SpindleSpeed", "Tool", + QtCore.QT_TRANSLATE_NOOP("PathToolController", + "The speed of the cutting spindle in RPM")) + obj.addProperty("App::PropertyEnumeration", "SpindleDir", "Tool", + QtCore.QT_TRANSLATE_NOOP("PathToolController", + "Direction of spindle rotation")) obj.SpindleDir = ['Forward', 'Reverse'] - obj.addProperty("App::PropertySpeed", "VertFeed", "Feed", QtCore.QT_TRANSLATE_NOOP("PathToolController", "Feed rate for vertical moves in Z")) - obj.addProperty("App::PropertySpeed", "HorizFeed", "Feed", QtCore.QT_TRANSLATE_NOOP("PathToolController", "Feed rate for horizontal moves")) - obj.addProperty("App::PropertySpeed", "VertRapid", "Rapid", QtCore.QT_TRANSLATE_NOOP("PathToolController", "Rapid rate for vertical moves in Z")) - obj.addProperty("App::PropertySpeed", "HorizRapid", "Rapid", QtCore.QT_TRANSLATE_NOOP("PathToolController", "Rapid rate for horizontal moves")) + obj.addProperty("App::PropertySpeed", "VertFeed", "Feed", + QtCore.QT_TRANSLATE_NOOP("PathToolController", + "Feed rate for vertical moves in Z")) + obj.addProperty("App::PropertySpeed", "HorizFeed", "Feed", + QtCore.QT_TRANSLATE_NOOP("PathToolController", + "Feed rate for horizontal moves")) + obj.addProperty("App::PropertySpeed", "VertRapid", "Rapid", + QtCore.QT_TRANSLATE_NOOP("PathToolController", + "Rapid rate for vertical moves in Z")) + obj.addProperty("App::PropertySpeed", "HorizRapid", "Rapid", + QtCore.QT_TRANSLATE_NOOP("PathToolController", + "Rapid rate for horizontal moves")) obj.setEditorMode('Placement', 2) def onDocumentRestored(self, obj): @@ -85,7 +101,10 @@ class ToolController: obj.Document.removeObject(obj.Tool.Name) def setFromTemplate(self, obj, template): - '''setFromTemplate(obj, xmlItem) ... extract properties from xmlItem and assign to receiver.''' + ''' + setFromTemplate(obj, xmlItem) ... extract properties from xmlItem + and assign to receiver. + ''' PathLog.track(obj.Name, template) version = 0 if template.get(ToolControllerTemplate.Version): @@ -158,10 +177,22 @@ class ToolController: commands += "(" + obj.Label + ")"+'\n' commands += 'M6 T'+str(obj.ToolNumber)+'\n' - if obj.SpindleDir == 'Forward': - commands += 'M3 S' + str(obj.SpindleSpeed) + '\n' - else: - commands += 'M4 S' + str(obj.SpindleSpeed) + '\n' + + # 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' + else: + commands += 'M4 S' + str(obj.SpindleSpeed) + '\n' if commands == "": commands += "(No commands processed)" @@ -195,7 +226,8 @@ class ToolController: else: obj.addProperty("App::PropertyLink", "Tool", "Base", QtCore.QT_TRANSLATE_NOOP("PathToolController", "The tool used by this controller")) -def Create(name = 'Default Tool', tool=None, toolNumber=1, assignViewProvider=True): + +def Create(name='Default Tool', tool=None, toolNumber=1, assignViewProvider=True): legacyTool = PathPreferences.toolsReallyUseLegacyTools() if tool is None else isinstance(tool, Path.Tool) PathLog.track(tool, toolNumber, legacyTool) @@ -224,6 +256,7 @@ def Create(name = 'Default Tool', tool=None, toolNumber=1, assignViewProvider=Tr obj.ToolNumber = toolNumber return obj + def FromTemplate(template, assignViewProvider=True): # pylint: disable=unused-argument PathLog.track() @@ -234,6 +267,7 @@ def FromTemplate(template, assignViewProvider=True): return obj + if FreeCAD.GuiUp: # need ViewProvider class in this file to support loading of old files from PathScripts.PathToolControllerGui import ViewProvider From e64f868765af3fa8a2e80eb1833bb15ad8cf7a43 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Wed, 4 Nov 2020 18:47:39 -0600 Subject: [PATCH 06/18] Fix default directory bug --- src/Mod/Path/PathScripts/PathSanity.py | 64 ++++++++++++++++++++------ 1 file changed, 49 insertions(+), 15 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathSanity.py b/src/Mod/Path/PathScripts/PathSanity.py index f602579a49..57230465e7 100644 --- a/src/Mod/Path/PathScripts/PathSanity.py +++ b/src/Mod/Path/PathScripts/PathSanity.py @@ -47,21 +47,54 @@ def translate(context, text, disambig=None): LOG_MODULE = 'PathSanity' -PathLog.setLevel(PathLog.Level.INFO, LOG_MODULE) +# PathLog.setLevel(PathLog.Level.INFO, LOG_MODULE) # PathLog.trackModule('PathSanity') class CommandPathSanity: - baseobj = None - outputpath = PathPreferences.defaultOutputFile() - if outputpath == "": - outputpath = PathPreferences.macroFilePath() - if outputpath[-1] != os.sep: - outputpath += os.sep - if not os.path.exists(outputpath): - os.makedirs(outputpath) - squawkData = {"items": []} + def resolveOutputPath(self, job): + if job.PostProcessorOutputFile != "": + filepath = job.PostProcessorOutputFile + elif PathPreferences.defaultOutputFile() != "": + filepath = PathPreferences.defaultOutputFile() + else: + filepath = PathPreferences.macroFilePath() + + if '%D' in filepath: + D = FreeCAD.ActiveDocument.FileName + if D: + D = os.path.dirname(D) + # in case the document is in the current working directory + if not D: + D = '.' + else: + FreeCAD.Console.PrintError("Please save document in order to resolve output path!\n") + return None + filepath = filepath.replace('%D', D) + + if '%d' in filepath: + d = FreeCAD.ActiveDocument.Label + filepath = filepath.replace('%d', d) + + if '%j' in filepath: + j = job.Label + filepath = filepath.replace('%j', j) + + if '%M' in filepath: + pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Macro") + M = pref.GetString("MacroPath", FreeCAD.getUserAppDataDir()) + filepath = filepath.replace('%M', M) + + PathLog.debug('filepath: {}'.format(filepath)) + + # starting at the derived filename, iterate up until we have a valid + # directory to write to + while not os.path.isdir(filepath): + filepath = os.path.dirname(filepath) + + PathLog.debug('filepath: {}'.format(filepath)) + return filepath + os.sep def GetResources(self): return {'Pixmap': 'Path-Sanity', @@ -77,9 +110,10 @@ class CommandPathSanity: def Activated(self): # if everything is ok, execute - self.squawkData["items"] = [] + self.squawkData = {"items": []} obj = FreeCADGui.Selection.getSelectionEx()[0].Object + self.outputpath = self.resolveOutputPath(obj) data = self.__summarize(obj) html = self.__report(data) if html is not None: @@ -369,8 +403,8 @@ class CommandPathSanity: # Save the report - reportraw = self.outputpath + '/setupreport.asciidoc' - reporthtml = self.outputpath + '/setupreport.html' + reportraw = self.outputpath + 'setupreport.asciidoc' + reporthtml = self.outputpath + 'setupreport.html' with open(reportraw, 'w') as fd: fd.write(report) fd.close() @@ -505,7 +539,7 @@ class CommandPathSanity: tooldata['partNumber'] = "" imagedata = TC.Tool.Proxy.getBitThumbnail(TC.Tool) - imagepath = '{}/T{}.png'.format(self.outputpath, TC.ToolNumber) + imagepath = '{}T{}.png'.format(self.outputpath, TC.ToolNumber) tooldata['feedrate'] = str(TC.HorizFeed) if TC.HorizFeed.Value == 0.0: self.squawk("PathSanity", @@ -646,7 +680,7 @@ class CommandPathSanity: view.showNormal() view.resize(320, 320) - imagepath = '{}/origin'.format(self.outputpath) + imagepath = '{}origin'.format(self.outputpath) FreeCADGui.Selection.clearSelection() FreeCADGui.SendMsgToActiveView("PerspectiveCamera") From 0a77421481b4977c0f7d8d6d51322c0ddbae9079 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Thu, 5 Nov 2020 09:57:43 -0600 Subject: [PATCH 07/18] Removed diameter dependencies so lathe tools work --- src/Mod/Path/PathScripts/PathToolBitLibraryGui.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py b/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py index 3e3c025d2c..3606228d96 100644 --- a/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py +++ b/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py @@ -152,8 +152,8 @@ class ModelFactory(object): def _toolAdd(self, nr, tool, path): strShape = os.path.splitext(os.path.basename(tool['shape']))[0] - strDiam = tool['parameter']['Diameter'] - tooltip = "{}: {}".format(strShape, strDiam) + # strDiam = tool['parameter']['Diameter'] + tooltip = "{}".format(strShape) toolNr = PySide.QtGui.QStandardItem() toolNr.setData(nr, PySide.QtCore.Qt.EditRole) @@ -171,11 +171,12 @@ class ModelFactory(object): toolShape.setData(strShape, PySide.QtCore.Qt.EditRole) toolShape.setEditable(False) - toolDiameter = PySide.QtGui.QStandardItem() - toolDiameter.setData(strDiam, PySide.QtCore.Qt.EditRole) - toolDiameter.setEditable(False) + # toolDiameter = PySide.QtGui.QStandardItem() + # toolDiameter.setData(strDiam, PySide.QtCore.Qt.EditRole) + # toolDiameter.setEditable(False) - return [toolNr, toolName, toolShape, toolDiameter] + #return [toolNr, toolName, toolShape, toolDiameter] + return [toolNr, toolName, toolShape] def newTool(self, datamodel, path): ''' @@ -559,7 +560,7 @@ class ToolBitLibrary(object): return lib, loc def columnNames(self): - return ['Nr', 'Tool', 'Shape', 'Diameter'] + return ['Nr', 'Tool', 'Shape'] def loadData(self, path=None): PathLog.track(path) From 7c3300dbf2f848a97f1a9c8317a17de0ead05b29 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Sat, 7 Nov 2020 12:00:58 -0600 Subject: [PATCH 08/18] Fix duplicate docs being created fix edit changes not being retained fix edit removing new toolbit from diretory --- src/Mod/Path/Gui/Resources/panels/ToolBitEditor.ui | 6 +++--- src/Mod/Path/PathScripts/PathToolBit.py | 10 ++++------ src/Mod/Path/PathScripts/PathToolBitEdit.py | 6 +++--- src/Mod/Path/PathScripts/PathToolBitGui.py | 1 + src/Mod/Path/PathScripts/PathToolBitLibraryGui.py | 3 +++ 5 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/Mod/Path/Gui/Resources/panels/ToolBitEditor.ui b/src/Mod/Path/Gui/Resources/panels/ToolBitEditor.ui index bceff42fcd..1342da5649 100644 --- a/src/Mod/Path/Gui/Resources/panels/ToolBitEditor.ui +++ b/src/Mod/Path/Gui/Resources/panels/ToolBitEditor.ui @@ -1,7 +1,7 @@ - Form - + ToolBitAttributes + 0 @@ -11,7 +11,7 @@ - Form + Tool Bit Attributes diff --git a/src/Mod/Path/PathScripts/PathToolBit.py b/src/Mod/Path/PathScripts/PathToolBit.py index e2f2c45e6d..9c76b141f6 100644 --- a/src/Mod/Path/PathScripts/PathToolBit.py +++ b/src/Mod/Path/PathScripts/PathToolBit.py @@ -42,8 +42,8 @@ __author__ = "sliptonic (Brad Collette)" __url__ = "https://www.freecadweb.org" __doc__ = "Class to deal with and represent a tool bit." -PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) -PathLog.trackModule() +# PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) +# PathLog.trackModule() def translate(context, text, disambig=None): @@ -254,7 +254,6 @@ class ToolBit(object): return (doc, docOpened) def _removeBitBody(self, obj): - print('in _removebitbody') if obj.BitBody: obj.BitBody.removeObjectsFromDocument() obj.Document.removeObject(obj.BitBody.Name) @@ -327,7 +326,7 @@ class ToolBit(object): return None def saveToFile(self, obj, path, setFile=True): - print('were saving now') + PathLog.track(path) try: with open(path, 'w') as fp: json.dump(self.templateAttrs(obj), fp, indent=' ') @@ -363,7 +362,6 @@ class ToolBit(object): def Declaration(path): - print(path) with open(path, 'r') as fp: return json.load(fp) @@ -396,6 +394,7 @@ class AttributePrototype(PathSetupSheetOpPrototype.OpPrototype): class ToolBitFactory(object): def CreateFromAttrs(self, attrs, name='ToolBit'): + PathLog.debug(attrs) obj = Factory.Create(name, attrs['shape']) obj.Label = attrs['name'] params = attrs['parameter'] @@ -407,7 +406,6 @@ class ToolBitFactory(object): proto = AttributePrototype() uservals = {} for pname in params: - # print(f"pname: {pname}") try: prop = proto.getProperty(pname) # val = prop.valueFromString(params[pname]) diff --git a/src/Mod/Path/PathScripts/PathToolBitEdit.py b/src/Mod/Path/PathScripts/PathToolBitEdit.py index 8ab67e77b0..1f3f4c4c4d 100644 --- a/src/Mod/Path/PathScripts/PathToolBitEdit.py +++ b/src/Mod/Path/PathScripts/PathToolBitEdit.py @@ -95,7 +95,7 @@ class ToolBitEditor(object): self.model.setHorizontalHeaderLabels(['Set', 'Property', 'Value']) for i, name in enumerate(self.props): - print("propname: %s " % name) + PathLog.debug("propname: %s " % name) prop = self.proto.getProperty(name) isset = hasattr(tool, name) @@ -133,7 +133,7 @@ class ToolBitEditor(object): if hasattr(tool, "UserAttributes"): for key, value in tool.UserAttributes.items(): - print(key, value) + PathLog.debug(key, value) c1 = QtGui.QStandardItem() c1.setCheckable(False) c1.setEditable(False) @@ -169,7 +169,7 @@ class ToolBitEditor(object): # get the attributes for i, name in enumerate(self.props): - print('in accept: {}'.format(name)) + PathLog.debug('in accept: {}'.format(name)) prop = self.proto.getProperty(name) if self.model.item(i, 0) is not None: enabled = self.model.item(i, 0).checkState() == QtCore.Qt.Checked diff --git a/src/Mod/Path/PathScripts/PathToolBitGui.py b/src/Mod/Path/PathScripts/PathToolBitGui.py index cac76acce2..adeab5ba89 100644 --- a/src/Mod/Path/PathScripts/PathToolBitGui.py +++ b/src/Mod/Path/PathScripts/PathToolBitGui.py @@ -150,6 +150,7 @@ class TaskPanel: FreeCAD.ActiveDocument.recompute() def updateUI(self): + PathLog.track() self.editor.updateUI() def updateModel(self): diff --git a/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py b/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py index 3606228d96..3f527e298b 100644 --- a/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py +++ b/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py @@ -343,6 +343,7 @@ class ToolBitSelector(object): return else: doc.setVisible(True) + return mw = FreeCADGui.getMainWindow() mw.addDockWidget(QtCore.Qt.RightDockWidgetArea, self.form, @@ -454,7 +455,9 @@ class ToolBitLibrary(object): self.lockoff() def accept(self): + self.editor.accept() self.temptool.Proxy.saveToFile(self.temptool, self.temptool.File) + self.librarySave() self.loadData() self.cleanupDocument() From aa59e98b37e5b39b430e88d2362f87b4b4dc4121 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Sat, 7 Nov 2020 14:41:43 -0600 Subject: [PATCH 09/18] Make Toolbit find Bit files relative to the current library directory --- src/Mod/Path/PathScripts/PathPreferences.py | 6 ++++-- src/Mod/Path/PathScripts/PathToolBit.py | 7 ++++++- src/Mod/Path/PathScripts/PathToolBitLibraryGui.py | 13 ++++++++++--- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathPreferences.py b/src/Mod/Path/PathScripts/PathPreferences.py index 096c6ef349..6fe58570ef 100644 --- a/src/Mod/Path/PathScripts/PathPreferences.py +++ b/src/Mod/Path/PathScripts/PathPreferences.py @@ -27,8 +27,8 @@ import glob import os import PathScripts.PathLog as PathLog -PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -#PathLog.trackModule() +# PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) +# PathLog.trackModule() DefaultFilePath = "DefaultFilePath" DefaultJobTemplate = "DefaultJobTemplate" @@ -152,7 +152,9 @@ def searchPathsTool(sub='Bit'): paths = [] if 'Bit' == sub: + paths.append("{}/Bit".format(os.path.dirname(lastPathToolLibrary()))) paths.append(lastPathToolBit()) + if 'Library' == sub: paths.append(lastPathToolLibrary()) if 'Shape' == sub: diff --git a/src/Mod/Path/PathScripts/PathToolBit.py b/src/Mod/Path/PathScripts/PathToolBit.py index 9c76b141f6..f468eacb0e 100644 --- a/src/Mod/Path/PathScripts/PathToolBit.py +++ b/src/Mod/Path/PathScripts/PathToolBit.py @@ -59,16 +59,19 @@ ParameterTypeConstraint = { def _findTool(path, typ, dbg=False): - if os.path.exists(path): + # PathLog.track("Path: {} typ: {}".format(path, typ)) + if os.path.exists(path): # absolute reference if dbg: PathLog.debug("Found {} at {}".format(typ, path)) return path def searchFor(pname, fname): + # PathLog.debug("pname: {} fname: {}".format(pname, fname)) if dbg: PathLog.debug("Looking for {}".format(pname)) if fname: for p in PathPreferences.searchPathsTool(typ): + PathLog.track(p) f = os.path.join(p, fname) if dbg: PathLog.debug(" Checking {}".format(f)) @@ -77,6 +80,7 @@ def _findTool(path, typ, dbg=False): 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) @@ -94,6 +98,7 @@ def findShape(path): def findBit(path): + PathLog.track(path) if path.endswith('.fctb'): return _findTool(path, 'Bit') return _findTool("{}.fctb".format(path), 'Bit') diff --git a/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py b/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py index 3f527e298b..c0b4375c1d 100644 --- a/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py +++ b/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py @@ -229,6 +229,9 @@ class ModelFactory(object): if lib == "": lib = PathPreferences.lastFileToolLibrary() + if lib == "" or lib is None: + return model + if os.path.isfile(lib): # An individual library is wanted self.__libraryLoad(lib, model) @@ -250,9 +253,13 @@ class ToolBitSelector(object): return ['#', 'Tool'] def curLib(self): - libfile = os.path.split(PathPreferences.lastFileToolLibrary())[1] - libName = os.path.splitext(libfile)[0] - return libName + libfile = PathPreferences.lastFileToolLibrary() + if libfile is None or libfile == "": + return "" + else: + libfile = os.path.split(PathPreferences.lastFileToolLibrary())[1] + libfile = os.path.splitext(libfile)[0] + return libfile def loadData(self): PathLog.track() From 472cb83ae73d664c8c28e8ee1e381a02431f0852 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Sat, 14 Nov 2020 11:06:02 -0600 Subject: [PATCH 10/18] Handle errors is toolbit json more gracefully --- src/Mod/Path/PathScripts/PathToolBit.py | 1 + .../Path/PathScripts/PathToolBitLibraryGui.py | 21 ++++++++++++------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathToolBit.py b/src/Mod/Path/PathScripts/PathToolBit.py index f468eacb0e..5b1b696594 100644 --- a/src/Mod/Path/PathScripts/PathToolBit.py +++ b/src/Mod/Path/PathScripts/PathToolBit.py @@ -367,6 +367,7 @@ class ToolBit(object): def Declaration(path): + PathLog.track(path) with open(path, 'r') as fp: return json.load(fp) diff --git a/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py b/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py index c0b4375c1d..72786f1a3f 100644 --- a/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py +++ b/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py @@ -140,14 +140,19 @@ class ModelFactory(object): library = json.load(fp) for toolBit in library['tools']: - nr = toolBit['nr'] - bit = PathToolBit.findBit(toolBit['path']) - if bit: - PathLog.track(bit) - tool = PathToolBit.Declaration(bit) - datamodel.appendRow(self._toolAdd(nr, tool, bit)) - else: - PathLog.error("Could not find tool #{}: {}".format(nr, toolBit['path'])) + try: + nr = toolBit['nr'] + bit = PathToolBit.findBit(toolBit['path']) + if bit: + PathLog.track(bit) + tool = PathToolBit.Declaration(bit) + datamodel.appendRow(self._toolAdd(nr, tool, bit)) + else: + PathLog.error("Could not find tool #{}: {}".format(nr, toolBit['path'])) + except Exception as e: + msg = "Error loading tool: {} : {}".format(toolBit['path'], e) + FreeCAD.Console.PrintError(msg) + def _toolAdd(self, nr, tool, path): From 2da3308bc31900547760b90d52ccfbf1cfec02ec Mon Sep 17 00:00:00 2001 From: sliptonic Date: Sat, 14 Nov 2020 12:06:25 -0600 Subject: [PATCH 11/18] Only allow editing of toolbit if shapefile is found --- src/Mod/Path/PathScripts/PathToolBitGui.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Mod/Path/PathScripts/PathToolBitGui.py b/src/Mod/Path/PathScripts/PathToolBitGui.py index adeab5ba89..30ba0e1648 100644 --- a/src/Mod/Path/PathScripts/PathToolBitGui.py +++ b/src/Mod/Path/PathScripts/PathToolBitGui.py @@ -113,7 +113,14 @@ class ViewProvider(object): return [] def doubleClicked(self, vobj): - self.setEdit(vobj) + if os.path.exists(vobj.Object.BitShape): + self.setEdit(vobj) + else: + msg = translate('PathToolBit', + 'Toolbit cannot be edited: Shapefile not found') + diag = QtGui.QMessageBox(QtGui.QMessageBox.Warning, 'Error', msg) + diag.setWindowModality(QtCore.Qt.ApplicationModal) + diag.exec_() class TaskPanel: From f2eaa98bf1cb1b5b5b70e22157424e03acc70fcb Mon Sep 17 00:00:00 2001 From: sliptonic Date: Sat, 14 Nov 2020 14:31:49 -0600 Subject: [PATCH 12/18] Add toolcontroller to open operation --- src/Mod/Path/PathScripts/PathJob.py | 11 +++++++++++ src/Mod/Path/PathScripts/PathOpGui.py | 9 +++++++++ src/Mod/Path/PathScripts/PathProfileGui.py | 5 +++-- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathJob.py b/src/Mod/Path/PathScripts/PathJob.py index d17cf3e717..ca6acf576b 100644 --- a/src/Mod/Path/PathScripts/PathJob.py +++ b/src/Mod/Path/PathScripts/PathJob.py @@ -94,8 +94,16 @@ def createModelResourceClone(obj, orig): return createResourceClone(obj, orig, 'Model', 'BaseGeometry') +class NotificationClass(QtCore.QObject): + updateTC = QtCore.Signal(object, object) + + +Notification = NotificationClass() + + class ObjectJob: + def __init__(self, obj, models, templateFile=None): self.obj = obj obj.addProperty("App::PropertyFile", "PostProcessorOutputFile", "Output", QtCore.QT_TRANSLATE_NOOP("PathJob", "The NC output file for this project")) @@ -147,6 +155,7 @@ class ObjectJob: self.tooltip = None self.tooltipArgs = None + obj.Proxy = self self.setFromTemplateFile(obj, templateFile) @@ -261,6 +270,7 @@ class ObjectJob: self.setupBaseModel(obj) self.fixupOperations(obj) self.setupSetupSheet(obj) + obj.setEditorMode('Operations', 2) # hide obj.setEditorMode('Placement', 2) @@ -413,6 +423,7 @@ class ObjectJob: tc.setExpression('HorizRapid', "%s.%s" % (self.setupSheet.expressionReference(), PathSetupSheet.Template.HorizRapid)) group.append(tc) self.obj.ToolController = group + Notification.updateTC.emit(self.obj, tc) def allOperations(self): ops = [] diff --git a/src/Mod/Path/PathScripts/PathOpGui.py b/src/Mod/Path/PathScripts/PathOpGui.py index b84be08568..f701d1bf98 100644 --- a/src/Mod/Path/PathScripts/PathOpGui.py +++ b/src/Mod/Path/PathScripts/PathOpGui.py @@ -25,6 +25,7 @@ import FreeCADGui import PathScripts.PathGeom as PathGeom import PathScripts.PathGetPoint as PathGetPoint import PathScripts.PathGui as PathGui +import PathScripts.PathJob as PathJob import PathScripts.PathLog as PathLog import PathScripts.PathOp as PathOp import PathScripts.PathPreferences as PathPreferences @@ -211,6 +212,9 @@ class TaskPanelPage(object): self.parent = None self.panelTitle = 'Operation' + if hasattr(self.form, 'toolController'): + PathJob.Notification.updateTC.connect(self.resetToolController) + def setParent(self, parent): '''setParent() ... used to transfer parent object link to child class. Do not overwrite.''' @@ -361,6 +365,11 @@ class TaskPanelPage(object): combo.setCurrentIndex(index) combo.blockSignals(False) + def resetToolController(self, job, tc): + self.obj.ToolController = tc + combo = self.form.toolController + self.setupToolController(self.obj, combo) + def setupToolController(self, obj, combo): '''setupToolController(obj, combo) ... helper function to setup obj's ToolController diff --git a/src/Mod/Path/PathScripts/PathProfileGui.py b/src/Mod/Path/PathScripts/PathProfileGui.py index 3d398fec6f..cbd58ae75a 100644 --- a/src/Mod/Path/PathScripts/PathProfileGui.py +++ b/src/Mod/Path/PathScripts/PathProfileGui.py @@ -38,6 +38,7 @@ __doc__ = "Profile operation page controller and command implementation." FeatureSide = 0x01 FeatureProcessing = 0x02 + def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) @@ -127,8 +128,8 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): def updateVisibility(self): hasFace = False - hasGeom = False - fullModel = False + # hasGeom = False + # fullModel = False objBase = list() if hasattr(self.obj, 'Base'): From c415272905a8511ece528560b32a878aadc36cb4 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Sun, 15 Nov 2020 18:13:46 -0600 Subject: [PATCH 13/18] Rework default library and toolbits --- .../Path/PathScripts/PathToolBitLibraryCmd.py | 6 ++- src/Mod/Path/Tools/Bit/45 degree chamfer.fctb | 14 ++++++ src/Mod/Path/Tools/Bit/5mm Drill.fctb | 11 +++++ src/Mod/Path/Tools/Bit/5mm Endmill.fctb | 12 +++++ src/Mod/Path/Tools/Bit/6 mm Bullnose.fctb | 13 ++++++ src/Mod/Path/Tools/Bit/60 degree Vbit.fctb | 14 ++++++ src/Mod/Path/Tools/Bit/6mm Ball End.fctb | 12 +++++ src/Mod/Path/Tools/Bit/probe.fctb | 11 +++++ src/Mod/Path/Tools/Bit/slittingsaw.fctb | 14 ++++++ src/Mod/Path/Tools/Bit/t1.fctb | 12 ----- src/Mod/Path/Tools/Bit/t2.fctb | 12 ----- src/Mod/Path/Tools/Bit/t3.fctb | 12 ----- src/Mod/Path/Tools/Bit/t4.fctb | 12 ----- src/Mod/Path/Tools/Bit/t5.fctb | 12 ----- src/Mod/Path/Tools/Bit/t6.fctb | 12 ----- src/Mod/Path/Tools/Bit/t7.fctb | 12 ----- src/Mod/Path/Tools/Bit/t8.fctb | 12 ----- src/Mod/Path/Tools/Bit/t9.fctb | 12 ----- src/Mod/Path/Tools/Library/Default.fctl | 37 ++++++++++++++++ src/Mod/Path/Tools/Library/endmills.fctl | 41 ------------------ src/Mod/Path/Tools/Shape/SlittingSaw.fcstd | Bin 0 -> 11644 bytes src/Mod/Path/Tools/Shape/ballend.fcstd | Bin 12320 -> 11454 bytes src/Mod/Path/Tools/Shape/bullnose.fcstd | Bin 12605 -> 12669 bytes src/Mod/Path/Tools/Shape/chamfer.fcstd | Bin 0 -> 12226 bytes src/Mod/Path/Tools/Shape/drill.fcstd | Bin 10267 -> 10381 bytes src/Mod/Path/Tools/Shape/endmill.fcstd | Bin 10521 -> 10590 bytes src/Mod/Path/Tools/Shape/probe.FCStd | Bin 0 -> 10824 bytes src/Mod/Path/Tools/Shape/v-bit.fcstd | Bin 12948 -> 12937 bytes 28 files changed, 142 insertions(+), 151 deletions(-) create mode 100644 src/Mod/Path/Tools/Bit/45 degree chamfer.fctb create mode 100644 src/Mod/Path/Tools/Bit/5mm Drill.fctb create mode 100644 src/Mod/Path/Tools/Bit/5mm Endmill.fctb create mode 100644 src/Mod/Path/Tools/Bit/6 mm Bullnose.fctb create mode 100644 src/Mod/Path/Tools/Bit/60 degree Vbit.fctb create mode 100644 src/Mod/Path/Tools/Bit/6mm Ball End.fctb create mode 100644 src/Mod/Path/Tools/Bit/probe.fctb create mode 100644 src/Mod/Path/Tools/Bit/slittingsaw.fctb delete mode 100644 src/Mod/Path/Tools/Bit/t1.fctb delete mode 100644 src/Mod/Path/Tools/Bit/t2.fctb delete mode 100644 src/Mod/Path/Tools/Bit/t3.fctb delete mode 100644 src/Mod/Path/Tools/Bit/t4.fctb delete mode 100644 src/Mod/Path/Tools/Bit/t5.fctb delete mode 100644 src/Mod/Path/Tools/Bit/t6.fctb delete mode 100644 src/Mod/Path/Tools/Bit/t7.fctb delete mode 100644 src/Mod/Path/Tools/Bit/t8.fctb delete mode 100644 src/Mod/Path/Tools/Bit/t9.fctb create mode 100644 src/Mod/Path/Tools/Library/Default.fctl delete mode 100644 src/Mod/Path/Tools/Library/endmills.fctl create mode 100644 src/Mod/Path/Tools/Shape/SlittingSaw.fcstd create mode 100644 src/Mod/Path/Tools/Shape/chamfer.fcstd create mode 100644 src/Mod/Path/Tools/Shape/probe.FCStd diff --git a/src/Mod/Path/PathScripts/PathToolBitLibraryCmd.py b/src/Mod/Path/PathScripts/PathToolBitLibraryCmd.py index fcff3facaa..12fc57f7d5 100644 --- a/src/Mod/Path/PathScripts/PathToolBitLibraryCmd.py +++ b/src/Mod/Path/PathScripts/PathToolBitLibraryCmd.py @@ -38,7 +38,8 @@ class CommandToolBitSelectorOpen: return {'Pixmap': 'Path-ToolTable', 'MenuText': QtCore.QT_TRANSLATE_NOOP("PathToolBitLibrary", "ToolBit Dock"), 'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathToolBitLibrary", "Toggle the Toolbit Dock"), - 'Accel': "P, T"} + 'Accel': "P, T", + 'CmdType': "ForEdit"} def IsActive(self): return FreeCAD.ActiveDocument is not None @@ -66,7 +67,8 @@ class CommandToolBitLibraryOpen: def GetResources(self): return {'Pixmap': 'Path-ToolTable', 'MenuText': QtCore.QT_TRANSLATE_NOOP("PathToolBitLibrary", "ToolBit Library editor"), - 'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathToolBitLibrary", "Open an editor to manage ToolBit libraries")} + 'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathToolBitLibrary", "Open an editor to manage ToolBit libraries"), + 'CmdType': "ForEdit"} def IsActive(self): return FreeCAD.ActiveDocument is not None diff --git a/src/Mod/Path/Tools/Bit/45 degree chamfer.fctb b/src/Mod/Path/Tools/Bit/45 degree chamfer.fctb new file mode 100644 index 0000000000..6c1231ed0f --- /dev/null +++ b/src/Mod/Path/Tools/Bit/45 degree chamfer.fctb @@ -0,0 +1,14 @@ +{ + "version": 2, + "name": "45 Deg. Chamfer", + "shape": "chamfer.fcstd", + "parameter": { + "CuttingEdgeAngle": "45.0000 \u00b0", + "CuttingEdgeHeight": "6.3500 mm", + "Diameter": "12.3323 mm", + "FlatRadius": "5.0000 mm", + "Length": "30.0000 mm", + "ShankDiameter": "6.3500 mm" + }, + "attribute": {} +} \ No newline at end of file diff --git a/src/Mod/Path/Tools/Bit/5mm Drill.fctb b/src/Mod/Path/Tools/Bit/5mm Drill.fctb new file mode 100644 index 0000000000..40243399a2 --- /dev/null +++ b/src/Mod/Path/Tools/Bit/5mm Drill.fctb @@ -0,0 +1,11 @@ +{ + "version": 2, + "name": "5mm Drill", + "shape": "drill.fcstd", + "parameter": { + "Diameter": "5.0000 mm", + "Length": "50.0000 mm", + "TipAngle": "119.0000 \u00b0" + }, + "attribute": {} +} \ No newline at end of file diff --git a/src/Mod/Path/Tools/Bit/5mm Endmill.fctb b/src/Mod/Path/Tools/Bit/5mm Endmill.fctb new file mode 100644 index 0000000000..8c7a208e28 --- /dev/null +++ b/src/Mod/Path/Tools/Bit/5mm Endmill.fctb @@ -0,0 +1,12 @@ +{ + "version": 2, + "name": "Endmill", + "shape": "endmill.fcstd", + "parameter": { + "CuttingEdgeHeight": "30.0000 mm", + "Diameter": "5.0000 mm", + "Length": "50.0000 mm", + "ShankDiameter": "3.0000 mm" + }, + "attribute": {} +} \ No newline at end of file diff --git a/src/Mod/Path/Tools/Bit/6 mm Bullnose.fctb b/src/Mod/Path/Tools/Bit/6 mm Bullnose.fctb new file mode 100644 index 0000000000..05d80b1d2a --- /dev/null +++ b/src/Mod/Path/Tools/Bit/6 mm Bullnose.fctb @@ -0,0 +1,13 @@ +{ + "version": 2, + "name": "6 mm Bull Nose", + "shape": "bullnose.fcstd", + "parameter": { + "CuttingEdgeHeight": "40.0000 mm", + "Diameter": "6.0000 mm", + "FlatRadius": "1.5000 mm", + "Length": "50.0000 mm", + "ShankDiameter": "3.0000 mm" + }, + "attribute": {} +} \ No newline at end of file diff --git a/src/Mod/Path/Tools/Bit/60 degree Vbit.fctb b/src/Mod/Path/Tools/Bit/60 degree Vbit.fctb new file mode 100644 index 0000000000..715361ec34 --- /dev/null +++ b/src/Mod/Path/Tools/Bit/60 degree Vbit.fctb @@ -0,0 +1,14 @@ +{ + "version": 2, + "name": "60 Deg. V-Bit", + "shape": "v-bit.fcstd", + "parameter": { + "CuttingEdgeAngle": "60.0000 \u00b0", + "Diameter": "10.0000 mm", + "FlatHeight": "1.0000 mm", + "FlatRadius": "0.5000 mm", + "Length": "20.0000 mm", + "ShankDiameter": "5.0000 mm" + }, + "attribute": {} +} \ No newline at end of file diff --git a/src/Mod/Path/Tools/Bit/6mm Ball End.fctb b/src/Mod/Path/Tools/Bit/6mm Ball End.fctb new file mode 100644 index 0000000000..9e9afbabc8 --- /dev/null +++ b/src/Mod/Path/Tools/Bit/6mm Ball End.fctb @@ -0,0 +1,12 @@ +{ + "version": 2, + "name": "6mm Ball End", + "shape": "ballend.fcstd", + "parameter": { + "CuttingEdgeHeight": "40.0000 mm", + "Diameter": "6.0000 mm", + "Length": "50.0000 mm", + "ShankDiameter": "3.0000 mm" + }, + "attribute": {} +} \ No newline at end of file diff --git a/src/Mod/Path/Tools/Bit/probe.fctb b/src/Mod/Path/Tools/Bit/probe.fctb new file mode 100644 index 0000000000..ebebaf4ee6 --- /dev/null +++ b/src/Mod/Path/Tools/Bit/probe.fctb @@ -0,0 +1,11 @@ +{ + "version": 2, + "name": "Probe004", + "shape": "probe.FCStd", + "parameter": { + "Diameter": "6.0000 mm", + "Length": "50.0000 mm", + "ShaftDiameter": "4.0000 mm" + }, + "attribute": {} +} \ No newline at end of file diff --git a/src/Mod/Path/Tools/Bit/slittingsaw.fctb b/src/Mod/Path/Tools/Bit/slittingsaw.fctb new file mode 100644 index 0000000000..3a74354076 --- /dev/null +++ b/src/Mod/Path/Tools/Bit/slittingsaw.fctb @@ -0,0 +1,14 @@ +{ + "version": 2, + "name": "Slitting Saw", + "shape": "SlittingSaw.fcstd", + "parameter": { + "BladeThickness": "3.0000 mm", + "BoltHeight": "3.0000 mm", + "BoltWidth": "8.0000 mm", + "Diameter": "76.2000 mm", + "Length": "50.0000 mm", + "ShankDiameter": "19.0500 mm" + }, + "attribute": {} +} \ No newline at end of file diff --git a/src/Mod/Path/Tools/Bit/t1.fctb b/src/Mod/Path/Tools/Bit/t1.fctb deleted file mode 100644 index 9221229563..0000000000 --- a/src/Mod/Path/Tools/Bit/t1.fctb +++ /dev/null @@ -1,12 +0,0 @@ -{ - "version": 2, - "name": "T1", - "shape": "endmill.fcstd", - "attribute": {}, - "parameter": { - "CuttingEdgeHeight": "30.000 mm", - "Diameter": "1.000 mm", - "Length": "50.000 mm", - "ShankDiameter": "3.000 mm" - } -} diff --git a/src/Mod/Path/Tools/Bit/t2.fctb b/src/Mod/Path/Tools/Bit/t2.fctb deleted file mode 100644 index 1c70485e5c..0000000000 --- a/src/Mod/Path/Tools/Bit/t2.fctb +++ /dev/null @@ -1,12 +0,0 @@ -{ - "version": 1, - "name": "T2", - "shape": "endmill.fcstd", - "attribute": {}, - "parameter": { - "CuttingEdgeHeight": "30.000 mm", - "Diameter": "2.000 mm", - "Length": "50.000 mm", - "ShankDiameter": "3.000 mm" - } -} diff --git a/src/Mod/Path/Tools/Bit/t3.fctb b/src/Mod/Path/Tools/Bit/t3.fctb deleted file mode 100644 index 86e6bf1110..0000000000 --- a/src/Mod/Path/Tools/Bit/t3.fctb +++ /dev/null @@ -1,12 +0,0 @@ -{ - "version": 2, - "name": "T3", - "shape": "endmill.fcstd", - "attribute": {}, - "parameter": { - "CuttingEdgeHeight": "30.000 mm", - "Diameter": "3.000 mm", - "Length": "50.000 mm", - "ShankDiameter": "3.000 mm" - } -} diff --git a/src/Mod/Path/Tools/Bit/t4.fctb b/src/Mod/Path/Tools/Bit/t4.fctb deleted file mode 100644 index c97b20feed..0000000000 --- a/src/Mod/Path/Tools/Bit/t4.fctb +++ /dev/null @@ -1,12 +0,0 @@ -{ - "version": 1, - "name": "T4", - "shape": "endmill.fcstd", - "attribute": {}, - "parameter": { - "CuttingEdgeHeight": "30.000 mm", - "Diameter": "4.000 mm", - "Length": "50.000 mm", - "ShankDiameter": "3.000 mm" - } -} diff --git a/src/Mod/Path/Tools/Bit/t5.fctb b/src/Mod/Path/Tools/Bit/t5.fctb deleted file mode 100644 index 014ebea50c..0000000000 --- a/src/Mod/Path/Tools/Bit/t5.fctb +++ /dev/null @@ -1,12 +0,0 @@ -{ - "version": 1, - "name": "T5", - "shape": "endmill.fcstd", - "attribute": {}, - "parameter": { - "CuttingEdgeHeight": "30.000 mm", - "Diameter": "5.000 mm", - "Length": "50.000 mm", - "ShankDiameter": "3.000 mm" - } -} diff --git a/src/Mod/Path/Tools/Bit/t6.fctb b/src/Mod/Path/Tools/Bit/t6.fctb deleted file mode 100644 index 521b489554..0000000000 --- a/src/Mod/Path/Tools/Bit/t6.fctb +++ /dev/null @@ -1,12 +0,0 @@ -{ - "version": 1, - "name": "T6", - "shape": "endmill.fcstd", - "attribute": {}, - "parameter": { - "CuttingEdgeHeight": "30.000 mm", - "Diameter": "6.000 mm", - "Length": "50.000 mm", - "ShankDiameter": "3.000 mm" - } -} diff --git a/src/Mod/Path/Tools/Bit/t7.fctb b/src/Mod/Path/Tools/Bit/t7.fctb deleted file mode 100644 index b10067d4aa..0000000000 --- a/src/Mod/Path/Tools/Bit/t7.fctb +++ /dev/null @@ -1,12 +0,0 @@ -{ - "version": 1, - "name": "T7", - "shape": "endmill.fcstd", - "attribute": {}, - "parameter": { - "CuttingEdgeHeight": "30.000 mm", - "Diameter": "7.000 mm", - "Length": "50.000 mm", - "ShankDiameter": "3.000 mm" - } -} diff --git a/src/Mod/Path/Tools/Bit/t8.fctb b/src/Mod/Path/Tools/Bit/t8.fctb deleted file mode 100644 index 2ad54eb330..0000000000 --- a/src/Mod/Path/Tools/Bit/t8.fctb +++ /dev/null @@ -1,12 +0,0 @@ -{ - "version": 1, - "name": "T8", - "shape": "endmill.fcstd", - "attribute": {}, - "parameter": { - "CuttingEdgeHeight": "30.000 mm", - "Diameter": "8.000 mm", - "Length": "50.000 mm", - "ShankDiameter": "3.000 mm" - } -} diff --git a/src/Mod/Path/Tools/Bit/t9.fctb b/src/Mod/Path/Tools/Bit/t9.fctb deleted file mode 100644 index 3a3dbc3f78..0000000000 --- a/src/Mod/Path/Tools/Bit/t9.fctb +++ /dev/null @@ -1,12 +0,0 @@ -{ - "version": 1, - "name": "T9", - "shape": "endmill.fcstd", - "attribute": {}, - "parameter": { - "CuttingEdgeHeight": "30.000 mm", - "Diameter": "9.000 mm", - "Length": "50.000 mm", - "ShankDiameter": "3.000 mm" - } -} diff --git a/src/Mod/Path/Tools/Library/Default.fctl b/src/Mod/Path/Tools/Library/Default.fctl new file mode 100644 index 0000000000..f0a35ef51f --- /dev/null +++ b/src/Mod/Path/Tools/Library/Default.fctl @@ -0,0 +1,37 @@ +{ + "tools": [ + { + "nr": 1, + "path": "5mm Endmill.fctb" + }, + { + "nr": 2, + "path": "5mm Drill.fctb" + }, + { + "nr": 3, + "path": "6mm Ball End.fctb" + }, + { + "nr": 4, + "path": "6 mm Bullnose.fctb" + }, + { + "nr": 5, + "path": "60 degree Vbit.fctb" + }, + { + "nr": 6, + "path": "45 degree chamfer.fctb" + }, + { + "nr": 7, + "path": "slittingsaw.fctb" + }, + { + "nr": 8, + "path": "probe.fctb" + } + ], + "version": 1 +} \ No newline at end of file diff --git a/src/Mod/Path/Tools/Library/endmills.fctl b/src/Mod/Path/Tools/Library/endmills.fctl deleted file mode 100644 index c443e6cd10..0000000000 --- a/src/Mod/Path/Tools/Library/endmills.fctl +++ /dev/null @@ -1,41 +0,0 @@ -{ - "tools": [ - { - "nr": 1, - "path": "t1.fctb" - }, - { - "nr": 2, - "path": "t2.fctb" - }, - { - "nr": 3, - "path": "t3.fctb" - }, - { - "nr": 4, - "path": "t4.fctb" - }, - { - "nr": 5, - "path": "t5.fctb" - }, - { - "nr": 6, - "path": "t6.fctb" - }, - { - "nr": 7, - "path": "t7.fctb" - }, - { - "nr": 8, - "path": "t8.fctb" - }, - { - "nr": 9, - "path": "t9.fctb" - } - ], - "version": 1 -} diff --git a/src/Mod/Path/Tools/Shape/SlittingSaw.fcstd b/src/Mod/Path/Tools/Shape/SlittingSaw.fcstd new file mode 100644 index 0000000000000000000000000000000000000000..81d3d7f3d96a340e7530dbbdb2c69afc705b171b GIT binary patch literal 11644 zcmai)1yo$iwyyEuZoz_kaCZpqF2OChySs!S!QI^*8h5wg?$Wqxut)a2`<+AYg_5A0knpN^r;1K8_ARy2nJKt>-mMdxllMz8cM#4cr;NQL#wlQ?FGPZW4 zbG5QO(bjZGl|=QPsMNcOSzU_necoCC#-UMgR@BV%MaRlq#&n}La7GJ4Qp(=WpRsom z(lJAm?!;R@^!Ydan4X?)Og`XydIml#-)}->A9;w;=lk6&+O}5n+qEs{CSL6q*t9=A zH)4hO9Np*1j3!)x25a-wgoS@YT27$_*?`sT%js2^H@i)~ zKE>7nG12Z4>7KXkv#ll<>OFSu$&-qY_k$~!62#hF2zzmohv(2NCr7;5{3aL%WR#$C z<$Y}}^ZaA`zN7++j0#46I}KM18oN4v=TzwW{hg7}(NE2AIs$!vsLc@Gbns7*p&@lw zctR!Xsf%ls1CKfz3(?GO+K+vfl2jj!rd2#CDD;sZUp8!8q!0ihjL}QlD$r(QxQ76S8|JSMY7tWpMN+5Oq{XP0~%KWBc|C( z<`X^*4#eO4+DUKnlc0sS1?oPdbtQ83MN1~eY zd2<~3tB2b9TiUmF1w5{%bE3_Y$C!y{1pD42E9&BFimu0f ztgZ|-RB2PdI1;VY{v3j?D<7yhw!ih0-XuI;VwSj?B8`K2pvN&n0WM5Yhf2~sSBMfh zkKjh<5iNA0uKIIxDg&4?Pc0C-;T8b0C+-8bOsK)SfSwC-&wZknZ%GM_2RtD(ws6ERTeM*B)^yy9d${|LIvP21-7&Oe?m<;Hhl1}ZCfKHfXTd)y# zfa#_&2!@nxZ4%E8`bW^nd3_A~aQ(n6%MGn?iHRE5E5+>~O-Kgni1)r2`0*pE!rm7( ztj~crtc7;alhr*=3VV_VIWi~QYdcf)CLe=FGOK#t85|(B@9U=By&8ACD($o;a-12D zdq6_H2hT8czJWu$$HF_O2jRZn<^ljC{jCt>C zsTi&jOmnoQW(7i?6jRweJ+r%Y&iftK@tW<3QlXkv+m%DhLcB(>Gfy z`*V6TTaIH<-L*jRlKay{Gt|Rfk!&;D7>4oW!;l+HI!^jA?L0Mea{uJLH#( zBsE7c)R=`y2{LmBRJ>0RqWyPgMe~iz*N|T7HiMmOum;jIbFi{-x7Hx0z3{jRF-ko$ zQejT#q|2k%3&|#SEx60OmPI>eDlW9jw}gK(JHIfj4UB*2_u%lMKFiMl0Y>p4f3$qH zh@qrOAlATP8$$&~eZdYRA8sZ~PN1_`z>!Sj55KEEE4geB!rer1vu5vYYvEz9m$~I} zX>p}nH(I$`Y1K%5S_5QgZe?%8OJ_)})w|<2gib(R(Tg=hLb?~twjGY zjI5jeJ}=pteWtBNnw>ki&ZK3z&)LmncgdRa6d(REeY_^goCLtWt5r;VbVhwoXYN(_RBJ z-K=GYwMdS$wJuE`3cj|^6Kq0Z z&XDL%AcPup-w)HXvQ0nhC7a-+dvlO00v$~Z!h@7F)pkG=48F5^P+tM&8IL9Epn(rl z)?nQ{@B4EocE6lX=Qc|2=?1`ou$(9z^{jR#<=ilxg1S>9WewYbl?I+{m|?gc)|nEj zR6u@FheH5EJ4Iv`#l6$PHWM0w==j2&Eodn{xY%_xrp{_pR}QUrjz~4Jt^WvS1A9Lf z*&M|-m+3}KYO7_DY)@?GvmkNb#$lB$PKJBt3Sr9f^dx-YjUlvL&$ivp3*iT<735NY zj;m{z_qBa%))_Du3*L=W2lm<@Gft-_lTi?)PcA@pu3Mrb6rG0`C2zyv*k01_O0U{q z069*vCX-B(=nlapJU5MV&C2mk5+G1VgKH;Qa8bLl4n3J3kfj#u;SzR^4#OSXNFtJ= z+7sobt{D=S=U0-HLS2#qGx1HB&IhKY_Z!h*1}?odX-hSHCaw<7#kT+P_z>j=jlK{g z(Cy1HHlI263f^LhLuw;e?w5w!GS6MVD zFG-RTLPDG@&IqA5M-0}uxKCa{%i0M8rZ^s|2&w9b5GPbAbUK6>5!e4-5==4ys=#jy zd@Ke*LR1+Uvh;zmEJ(Ch`MbPUljvT03-wt5%4Y z+=6!N`6FJoWkaJxLtoL_?!dQFnh9W7Wx`5_D~CAdVh-CyT6b47MReV0dT+9qwv58b zse;Rf3QYHDU*Uy}1aDMm?{~AozeVLgv)SL+O?Fcht~%VJ7(Db4ReR~e4EgXee)RN$ z%}jFjFAJ?xwGM0NVbB*CDkw{(9@mf%jgPY>5{$OJ==8@h9;~TAv0u(3yKy&{!vi=E ztn&s+dx*5Q4$1j<0ICj&%LQ@e)U(Q;craU3ss>o#vJdqz zM?z(b!cs+-yG^6)NPhsSdzh|47t1xDh``?0&oMS4w4R@YO2I;#jN8>s|G)<{t2nwsT(!gM@Jvuf_2T6Xhj6QJUpRMk=|CtV)j4lE?c zu!_*`9@?yO?bT~r2$kc>f#F<4cfCco>)v2RSYVtJpGlj1a@UX_tsA}+&uDs?OHE^% z>DzFtV+rPlYcP_08vOo%>4=R=Lu%>#J+1-S{vrbjaZnSL(`{#^e-mplB)&!#)jfTC zG~{gZygbNsYqm67qDt*2s6P5;-IB?zUo7BQN@h{bc~R4*`v8S(zp}Dg89T1}(GX8h z7jGcnbcf{7c7(wSfu*U+N1$PiEzoSPVB~aD!vJ51X5g|!zRth^Ncu{#i@a)*!5mtc zolx{Rszas0kKkC@FK{^6T!{m(WW(8T^nL8u{?4?;!>BgYma{}-Ix*%3t?SxcD{q~* zWiR(9=T(%ua~mjBcO1w>ke{V6M+rQ62UY}Wn#2tq8Y(bsJ(p&L61quPkSM+G^xLBV4PeMC{@20RTFCmBo@Ftd+;$szwR|{=jCu zPiYpm^%_KKO^|H{EE3j;2{MJKZUy=T&9Y>~PSH|EESY0ZzFyf5tn>EeQf%Y7+q$no zMlKp#jkey}qmA0<+q;8_|J+b#g;S`x!VyNl3j;NNXrSGt974SV)ckGJS zI7Q6v*(8EsJSSr6*lk zIV4mWq&St6(P*0veTY^_F8$C*(yz14EPjMCM?TutaJ5W|ggvG#Vek8TyJhRT6}9Pp z^}XrRNB*}ZY+Pi@dK5SaNHQS^2*TSER$kxUQOQ)_)|k$~-u6U8#s-HGx$RNyejym= zWZ|)o(f@je{wpPKrFaS<5t~1$;)|Dww50QVEd#B{aKY|Ir~d%6&01&G!Du|;nZBe{4LHtVbl z-hWz^&Sc|2<1!`%{bp+r9x_5zT0aZrczsI3OC_@|N^Xe2wP=KPm9kOj`Z#3LbZPno zaBr@jw?I(c3@p&e4fVpTs@-+@NWLAe0R%}s__2q0*(>tiD3?^OoO}2NoEV6_#bd*G z5DE7L@$e9}YcaubUTe%@r5pI0TrHmU1S$3|{Fjl(vyqY6R`oSL zIvvEF=oG#RZw%(+n`By-IJ<_qko39_H-Q$nm&}N2>W^W8$G+evWA%-G-<+Gce~RgX z13ilMYR?g?zsGmr&o)<*njUDQV%FVVY%)tJV) znd#&_sk_)tW*I%tTh^NE_Ommf!fZM{%i-?(y37NpYEX1-tTO?ej&d-OuQA~Dce*-r1klRF z;rLFm&fwIS0+RqW_TChR-VMHBqu2vHW3<{D7>XIfD0%w0CIME4OZV)j_k$9w-j)=+ zsD&|70x*rcOqK~;ty(bZKBi1Ve#ra{3rd{dp8=Q#LPHQZxMlB96`qqXTpMyjW=eC! zs*vQv-pi?kuy~Kk++h1+DEAM zS_=~5#m8A+%iBoL#Neh^|GR^cL9&X3CP;mU$-8< ztW$<{k-b*@@`;T#D}BP|cjq=wZLe*Df`ItIfq=Z#p#J4t#$VQLOOUbvV?-V}yP~qU zv>+McD;W z*)9+Ohb#-FIaiA5)MqV#65dBnKfBx5$X#WbTT{lO8v;(M#0im>5(*6W{M~*ytCmv> zPq|T#N(y%+nDZo-zb2lUHCnZi*J}8WhonR~>9A!wff;^?k4g!f@0$ChDJA_RR?YP> z%!#Y_r6YWOKK5j)94lm21Og!(Y1xKd#EPMFPLJuu4p*xd|2|DO{vbtG?=xynrtcD% zURm{haN&>Gpdveq*;+AXz{`GEIV~Sl>?d6?MmZ!c-|EP0V{U+yrm~C*xX(P<6f!T$ zq=pdbWf@Epq0*$joQQKp2p^BWg_QSvVERE?UiIX8U>Dk`<>N6RpVLF&Ju7XBmXXQb z_klIu+*oi{eIP8&kcZ<+@w%(v^3->^p=7w@0zsSOe(eS?eoAf)|8-?KhqNQ@{(HPFK3olXvDT$@TvhtVw~_aU=h zWBncsS7lW!d2eZ4ljM(JVEW%YF1O9_R~`pQ&8t`fuR>uxmgtM2#})LmQO)Jy#GfA1 z?LM(b3>e2GXrl-W!V(cIzol^?hk-p$I$mblaP475b9qJPPSXo9{}|uM+3s_VGR3af z1BdQdY0$mu*supceMs3fdtmZ5D%^b)#hYDZWeZ*`@UjJ^>v=OUT2U(B_?ke~T)+Ir ze6_j0?x?;;_hxoS|H0LmPE8RA_A)*0h#q~%a}7SxB@lvmQ24&TU{5I=EtVzO(rLwxnZ~;Ej{2jeH=*$|}d|Gma(PVb30!C~d2dYWK`xvB}fxQH$VfzDo zUt-=w-t0!$-~9OXK8gHT{NDGig)_37O`5(`p z-CJ-yX9-%1z#l#M4DTEK*LgA_ns+) z12#L3zn`9P=5wU3#kp+6&4pc+jupS`eHV7M5lgc{0vj(13U$oeRKxo^EmeKc0COVS zvH9aeRM%H#s{?%cvJP}d*9Gx~7L={R;`1vzO)VXIKCobL3NNBbY3 z@z?}seqqiby2RrYi`H3!tuT9CQos=Z?C9$qdy7ggExQ+GW6_bVf z?RS<)F6skGzbiZX`Be;p#C{T^HsSz*Ux5GK43d}Cdby$lfJ*c7ITg=HN;V*rgBm=) z2SEZR6^+L*F~$tke^kHtG#J_1Ihs;s@Y$mWHgomq4j5TwkA)6PP_Mc<*F_b$zKu0Az=sE*5|Zthv|L=DQ3i7d>jKQ&yRw$kYfOH7ot(q zMxYA{e9nHkL4{@J#KWAu*jFUtyE?C!#TQwdm!*0pj=8~&j`*(haQ_{>ql(qtCGvuJ zwo>cT&7@2L5fGS#Cwt`JextX zWED-VD5zQd>TLpV9P0{?#^)?TSt5;tx_q_Q<`zj349wkp=*w@V&Kw@Ne38*PqQ9KJ zuYJ1_v~a24Xg2&GR#@9fohL6maCC@Y-2l4e(n!4u7!q z*+fZ4yLe9(q5QeO5ZR3Q#-onT`eUrf@%j3t={=K28*`&Wu>)uaiyPj+4ri2>-;rLH zm&^Ycn|VK$H7955AJ<2Vp&~+1mZ$x+a2ZIh1`_ikK{^W5evo&_-LfX0wNSCeG zSgfy^#{z-QSf3rGq*+WqNPAS$8$s5%$<1TJaxtdoF0Fn03{r2nk-|LPgjYvtqufXO zF|s$=DtIU8xVp(Wy++F&GE02ISZ-gv>qm@lsMRpc+YvwRWV`2snLjW=WX$H%o#z8!j;;?y|b?D2_z2_eUy)E<(F6k_t#6n z3UXNAzVU4fcEFj*hxQJwQ>YL1-1LW?KQks4@Y93Z@Cpa?arx_yq5IaJ$wUS2J>gav zkCfS7#$qx%QtiR?0!@EBhu}}O@fM=1<%Vp*q)mOc^J;WAt6m~SS(`=yj)nR1niCn| zTim0WY+b6BS!+5Wc&uAb>uzC9mNvHedhDDMoc2MnEx2LPo51N%5nJTP57(P$8sr$7 z3(lZooUVXkEJMXZ(5-`EoXB@M8}c2zcn!hO7*jv?d|-Iyi&ox>6wP5`&xbE?WOk## zD?qMR!pw;qhSv)ptT)-R0F@vtRzF$Tu_c)`27qJRGPdV__cImRxfpg}wneb%@$|8l z$O!z))ASjxp1$84e4}0L5Ri7#m=@Ud`ml&BF2R^fyTi;7OzoM=Qrx|*VQAQ01z=$Y zKEaNB4hPelt;Zp(?!j1&$jN)WYe}L^?5A*jo)N4n=>Qx-=<1x++O}cBm6qKhn7>~gNJDo^R(1NOb6trK&j0qU> z-SLIW*h$;M!-_*&ac={Kq=8o-EVnK-ExK_q=Y4Z_JY6-RDBPDwg1?-tYIJU5Z0l7> z$hpvL=mMzvsz3!nOLajqrcV`FPszeV#gMhqq1_42Oo3@ELnf<$pY5hF#tSCjf&!+MClD3S%ma#3;Gp*sN*21N{wV?eGM z4;W>r8@nF4=2>-kda*NtMU&R*X-IjDQD4QXnyq~dc3NNg;1K|B_4?e>wyleZcpHcW zu|2J-X}a8Y0j;@q=Krml;hG1ye}2BXDTpT!LU72xQDGYEp|6LgZ-hpRjp>O;gp@%P zwzbv;8jg)f?CAc9N2n1L2BVnRQ|NRtH-J_YP0O{BqH}_=WezJagSVme;l?yINED#K zS^YRIsnuAhU3t;VHV{`eoc)SD2mIlqdcM*3x{7nZ8Mc6y8zq73CeDGdUknvkgObw* zky+MavtB7}v>E2RxDQw_6F$?w>l#EP&+uDa1awB@3N?Y!B4yNjYF^KoodeJazqR+H zJysLDD!rpLI1fDdYL$x7b@QFwITeFf$a4^ijrxRK`a5Uv` zWDX0sRIuiy;G-l{cl|p*G)BjC4TdJ@R9c72ZV6R=`RZj-d)ue8 z$k0QD+k>W)Z;*iLs?#&>+7){lKH-CKJNgEM$P?HPN$dsWtgRvfxfPWnzSmzwN-dfz zW=cMb?BfP>E!;GLBh=cE01!_jWQ6?_s3|L>H=FxOkKxo-vi09%ItxZ9i)?^CMmv>L z)6~GP`%%-sL#d)sS|ZtJc#Ksu;{NowS?dJeKoOL&oWX(p+B<#KRTqslX^EhsVk{vu z)lufbF@}>;1int{%3nf!5T-bPWgnHCuyN9Rc?KIxFq(|@qpva8!VzKmyfdYeMGx1l zdLYEX1J4O5XO7pNw|`#@FFbVdU99)*zW&~(@63w)O^YdK*D}nqNuEX_lbl)`hV`~=4J4{m zi(fKvt%bSCriN*;PiWxb(b5)!x=6-f=RB8$CBNO8Ekp1cISx07E;rfnU+cxktc|S^ zo2k0HFh-ZCyHt})snkQeT;M&?u~lmZX1OwRrruFqZ`Fj;B4jE_?wh{XUXHgMJ6sy) z%$%(cS{ST}UjSH0ao+ard-SMZ3>H`JcJp4~eFK(D+{7ake7vEy-F?zjhr}eGb0B6X znIG8(qFb`zB_1!D>o;@3_N&jiIY$86DO6jH&Qu4@EQZN72dPqP6wIrBs>KHr1GUS` z&>TKXSg7{kJ#cA!bT|l*E%rYW>D8-!=g>e7+k!m$aNBlc3h;alK3gAcfSvz}d7Qww zx-_T7W;lFjvu`~!o;(#1_^{nDk$E@v@FEk&I!>4L#BF_PR+RRMZHujUDnJJ?lu6`L zBP;!>Z9ZQels>w+kQz(9@5*p# z2EKqJKfx)Pc6(nzR5LF`c;emJ30$YdE2Ttygc{Z>yE%4B?455i-p-(OI2|Xe7trA0 zWq}<(@jo5sRn9RcBV{AKlAIN=+61j7FMKR~?p{be1?94ui8Rw`f}{Jf%o`&(W@^@lw`^x^DykF>rWvT>rKBkuWC}oWf3n~?G_dE)q5<9~V;ddjCk0Az zdZEgY4Kqz&nh^W6ZSRVyFHsEC*wRWle;p$&ul5zv@eWM9e8S zVYoKx_F5Gms>d;prX_8|dOvmK>>3zTVt}-UT}ErO>=nB*Xs32Q zYVGWG*03BagxO2gXh-_b=pXB`>A?={;VyAhCSli6eb7`Urv$AyBI!g`fF%*nQ!0~+ zaJQ)8bd5%d5TaP-v-2R)tCV3Cu9JIq)l+?a9O@DCTS#&WFeHXHu+K40cMPxOu^+~V zk{DLi>8X`Yjn6E^lU*T!b*&{=Jl3E=;!$xh%v5M-JU!-&F9>`D7Z$asjm4+zYt^D6 zQ_5tt1Vl9Q)8AOVEmYZTal|h`&qwuIpMLJs@SA?iVDDEkUNdEIIgOgwwA(3n^zSF8~*6GjUIz(5d{_uc*^gm{pW}ZBq;qqV_|3nOm8h1-yZSb zrsDK(_nbq|xp=&S|32#hsw7h>fPsLVyj|FDvmQrNCo2PMeKSi3dgZ@A(%D*@9KoME z0C!`y4D|FiZmw-%z@`ugN4p?My$ssNKo!#<38MQ19$pDY@AEB`D8Rv|J>{?lzK3zz zoDp14!N!^0yPl4U^eOH-rOq(VaL;wD)TNPpF)8*uMRkdQA6Xh~l;5b?B!Xd7mLS;Rlw3+QDF7S3fo7CYwnh{XLF?se)oVf72mst; z4QVW@I#li+e33)3P1(-9eR{ZRG;Z5#wI4}wk^dd1U>ko4Gg%P{XAX?`}m%{$dzE2QFG*evNvIDBQfA8tZ?P{&f=k@7oqEc7J!e;a}S#zE7PL>KOz==Mfsp;XFA4q=2 z(Rj`@5c2uvvI**HqJxF4DW1PY-=2vyJ*mC@L!6U8T9Qr{j0we27iSe|T)*uno&2rk zvBjf6Sl@%huIbqc<8&n8-^IOAdV2Rr5PzuqCxra(>iz@u-{dj<2YLS-yMLGW zi~9cz-v2}1KPXTzbnyRt81wDP{%Uo8zux}2#*&x%o&0kT?0+~AkUGDfw}1S%9kD;r zKa24HLcLJ`f&NvJ|C9Z*X8SKT?Cq)kZ?}I{aQ|fgtPlN*{rr|*|1bMjwdhaw&lTvu zSQfm0TaW$;|Cz}D1yhs#=d}LkulzHZejS|syVdzsQ~VzJf0F+Usb6;dyVd#aypg}h z|9@kDh2!6?&JUaNZP0!<>7VdFCI7PL->uG1<_-SMtbd~aG~Dm~`n{pQYi9g!@Gm?3 z-Rk@r{$2FH(f?WX@gJ(0{_OalBmKK-LaIMi|5x~T)hciBA4B~{Lqh#}F66h@$XnI( V+uJ?JAfooh#-9X*3IDe4{{c#oFkJuu literal 0 HcmV?d00001 diff --git a/src/Mod/Path/Tools/Shape/ballend.fcstd b/src/Mod/Path/Tools/Shape/ballend.fcstd index 430a6f816006ed51b4ad958701074deea60fa3ef..79cbd686054e1592b0924b65d25863a35bfcb248 100644 GIT binary patch delta 7362 zcmZu$1yqz>w;ocF?i#vVy1PSCI;2Coo0n7pr3M%z1ZhDUMnphDItHbsb7({uhLo%S z_y70a@4NS`^{#c!*?T?De)l=+to^KAWZP*D=xbwOl7T>=`yf!dw}Eu|H%(ds5at9jI$mMZ*srnU$b|Ta=-CgaDDtDnQ_g zu)Dhzp=x=B`h0ipb^PS;TzYMeW%I|XhJ@d}Nse{QJMsvn_yNY8i(4<$)>fIYQgV!F z1QUvGHk?SUmP6(vJ+a3<`NH=ghTQ5gkFg$Jwhd~0*`5TH46>guo{`m^j=0g*s`q5!zfh-1!M(?YxToOj@Z zAfi-9GgsaEC__#jb0CcQy2fW!{-l}>Q=a|e&Q@p_<(607h2dcl%V5?$wc_noT&&Ud z6xMcvnZshUK)P*P0Xy{+%Pe-yt>V@~>Ibl?!#-SK;_}D^6!zfP(0Iez0M`UTs(2T7 z8LW6e;iGTe!XPfd+g?)M<~W?b0bS@u|FSPYn~T3};p3)#Zz{%V>ca#JY4+U?8@=o> zYOqt66$M)pAC0H^XNtbg@^hzK_sK{J>dx;J`4%WgEAPwc{mu{l4(oV%{K%=Fal$`Z z(q#z=O_Fx!;k95i#|A!4z>`Y}^mQlM-T_mh<=&SQ_hKhxC-~(+tc$!1cIPT_VF`9J?WaBw7EkSg2Rh-Dgsm#7rl;W1F>>ehDgnu$Cq<`Yyr;um&aJrq$;@fTW9J zt2y_%;ZrH}%uvifv!`lv&1C!x&imXjKYF&ky~)|+N8tVW_4i&fP;Tu?UdHmw@o^bU z>S4I2vREICnc#6?DfhNU9czv30|8FOM3aE1J8|l0p?6xWuy__BG}?s5=O+3YjSmbK zY49VMF!!SNB zPC766S+rKNW~Uys

^(d9?cJ&{Su0q#gSSLiFNh>&V_iQZhoIYyWiir` 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�V_kUT@lgGyK~m?;=02UsJ{_WXzU1^lJaZ5T>-CDpVQ2Tf4)B& z$0hDOF<;qXubI4P1c>E?j@~;LVPoWyC>c9>DWQ3-jJ;jYnC=+DK5RIOhfC5QFQLxN z20VZ%My}Ru)L)j5BgXjrj96&caf}mK6P~mN?0zed77X9mj8V`%T13dsJ~>|cP&fMY zi1}6E{>Zo%ecsB|GKpT@Dq@Gg3 z3T4C3w??1_?iI3i0SygJr2c&G-!FdrjoJ|&d0PhuPS;fj+MoB&);=*cHIh z;JwljxgWNvuqY|F@Z&mT0ulcHblkk0l)XK?{S^KD>_UYc96{)s znwtNBhq&@Eu>3C@8af%q|JTieX!7z}{$cs2{`gJwuhAT%`n&C=Z|4_aR#BcBC zqp$sUv{<3l>LD}`CU?x; ze^)Vo{o{WynqzE#>ml_(H;6Sa72_YzzrQ8_@dJUHV~ALPzbx#3q3`q2$egnOZqvCr zI|uszm9Bq={lD}2_psSHAd39|4%?2O2Hk=K63PGXxZnIE-z6 zsR;g)3^FP}!+P=CPv6_kE8u^Q@BcdfA>ikP_z3));7W{NRR5Qqq*=I7+3tf<2HpSI+G0jF`n)Bpeg delta 8233 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 I{>lFT4}CyoZU6uP diff --git a/src/Mod/Path/Tools/Shape/bullnose.fcstd b/src/Mod/Path/Tools/Shape/bullnose.fcstd index a97cacde636c202d6ecf355b5ad571f9f53e5e01..c2f7240b723728a80ba16c91865648ee624c2bdf 100644 GIT binary patch delta 11471 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 Bn7#l2 delta 11320 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 diff --git a/src/Mod/Path/Tools/Shape/chamfer.fcstd b/src/Mod/Path/Tools/Shape/chamfer.fcstd new file mode 100644 index 0000000000000000000000000000000000000000..a6acac7a43e4bfea165ec6220d2c056139d9b551 GIT binary patch literal 12226 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 literal 0 HcmV?d00001 diff --git a/src/Mod/Path/Tools/Shape/drill.fcstd b/src/Mod/Path/Tools/Shape/drill.fcstd index 75f75738295835531bc1a02fba6ddf010edeede9..aa2a626d028b33ac6d97de7fbb248a978bc6a7b7 100644 GIT binary patch delta 8363 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 delta 9156 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�+ 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 delta 11083 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 JGqZR7{{R&D?fw7& From a5efc81ff8d2d88416eef7b87fe56444d18c071b Mon Sep 17 00:00:00 2001 From: sliptonic Date: Sun, 15 Nov 2020 18:41:08 -0600 Subject: [PATCH 14/18] minor change to make toolcontroller labels better --- src/Mod/Path/PathScripts/PathToolController.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Path/PathScripts/PathToolController.py b/src/Mod/Path/PathScripts/PathToolController.py index 2ab0324f6a..7eec1c2132 100644 --- a/src/Mod/Path/PathScripts/PathToolController.py +++ b/src/Mod/Path/PathScripts/PathToolController.py @@ -233,7 +233,7 @@ def Create(name='Default Tool', tool=None, toolNumber=1, assignViewProvider=True PathLog.track(tool, toolNumber, legacyTool) obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) - obj.Label = name + obj.Label = "TC: {}".format(name) obj.Proxy = ToolController(obj, legacyTool) if FreeCAD.GuiUp and assignViewProvider: From 138a395024046ace83da3e0c5133b698265ddb9e Mon Sep 17 00:00:00 2001 From: sliptonic Date: Mon, 16 Nov 2020 11:22:06 -0600 Subject: [PATCH 15/18] fix toolbit install --- src/Mod/Path/CMakeLists.txt | 22 ++++++++++-------- ...ree chamfer.fctb => 45degree_chamfer.fctb} | 0 .../Bit/{5mm Drill.fctb => 5mm_Drill.fctb} | 0 .../{5mm Endmill.fctb => 5mm_Endmill.fctb} | 4 ++-- ...60 degree Vbit.fctb => 60degree_Vbit.fctb} | 0 .../{6mm Ball End.fctb => 6mm_Ball_End.fctb} | 0 .../{6 mm Bullnose.fctb => 6mm_Bullnose.fctb} | 0 src/Mod/Path/Tools/Bit/probe.fctb | 4 ++-- src/Mod/Path/Tools/Bit/slittingsaw.fctb | 4 ++-- src/Mod/Path/Tools/Library/Default.fctl | 14 +++++------ .../Tools/Shape/{probe.FCStd => probe.fcstd} | Bin .../{SlittingSaw.fcstd => slittingsaw.fcstd} | Bin 12 files changed, 25 insertions(+), 23 deletions(-) rename src/Mod/Path/Tools/Bit/{45 degree chamfer.fctb => 45degree_chamfer.fctb} (100%) rename src/Mod/Path/Tools/Bit/{5mm Drill.fctb => 5mm_Drill.fctb} (100%) rename src/Mod/Path/Tools/Bit/{5mm Endmill.fctb => 5mm_Endmill.fctb} (88%) rename src/Mod/Path/Tools/Bit/{60 degree Vbit.fctb => 60degree_Vbit.fctb} (100%) rename src/Mod/Path/Tools/Bit/{6mm Ball End.fctb => 6mm_Ball_End.fctb} (100%) rename src/Mod/Path/Tools/Bit/{6 mm Bullnose.fctb => 6mm_Bullnose.fctb} (100%) rename src/Mod/Path/Tools/Shape/{probe.FCStd => probe.fcstd} (100%) rename src/Mod/Path/Tools/Shape/{SlittingSaw.fcstd => slittingsaw.fcstd} (100%) diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index 5b88d574de..7046290c7b 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -154,26 +154,28 @@ SET(PathScripts_post_SRCS ) SET(Tools_Bit_SRCS - Tools/Bit/t1.fctb - Tools/Bit/t2.fctb - Tools/Bit/t3.fctb - Tools/Bit/t4.fctb - Tools/Bit/t5.fctb - Tools/Bit/t6.fctb - Tools/Bit/t7.fctb - Tools/Bit/t8.fctb - Tools/Bit/t9.fctb + Tools/Bit/45degree_chamfer.fctb + Tools/Bit/5mm_Drill.fctb + Tools/Bit/5mm_Endmill.fctb + Tools/Bit/60degree_Vbit.fctb + Tools/Bit/6mm_Ball_End.fctb + Tools/Bit/6mm_Bullnose.fctb + Tools/Bit/slittingsaw.fctb + Tools/Bit/probe.fctb ) SET(Tools_Library_SRCS - Tools/Library/endmills.fctl + Tools/Library/Default.fctl ) SET(Tools_Shape_SRCS Tools/Shape/ballend.fcstd Tools/Shape/bullnose.fcstd Tools/Shape/drill.fcstd + Tools/Shape/chamfer.fcstd Tools/Shape/endmill.fcstd + Tools/Shape/probe.fcstd + Tools/Shape/slittingsaw.fcstd Tools/Shape/v-bit.fcstd ) diff --git a/src/Mod/Path/Tools/Bit/45 degree chamfer.fctb b/src/Mod/Path/Tools/Bit/45degree_chamfer.fctb similarity index 100% rename from src/Mod/Path/Tools/Bit/45 degree chamfer.fctb rename to src/Mod/Path/Tools/Bit/45degree_chamfer.fctb diff --git a/src/Mod/Path/Tools/Bit/5mm Drill.fctb b/src/Mod/Path/Tools/Bit/5mm_Drill.fctb similarity index 100% rename from src/Mod/Path/Tools/Bit/5mm Drill.fctb rename to src/Mod/Path/Tools/Bit/5mm_Drill.fctb diff --git a/src/Mod/Path/Tools/Bit/5mm Endmill.fctb b/src/Mod/Path/Tools/Bit/5mm_Endmill.fctb similarity index 88% rename from src/Mod/Path/Tools/Bit/5mm Endmill.fctb rename to src/Mod/Path/Tools/Bit/5mm_Endmill.fctb index 8c7a208e28..7cc72ba33c 100644 --- a/src/Mod/Path/Tools/Bit/5mm Endmill.fctb +++ b/src/Mod/Path/Tools/Bit/5mm_Endmill.fctb @@ -1,6 +1,6 @@ { "version": 2, - "name": "Endmill", + "name": "5mm Endmill", "shape": "endmill.fcstd", "parameter": { "CuttingEdgeHeight": "30.0000 mm", @@ -9,4 +9,4 @@ "ShankDiameter": "3.0000 mm" }, "attribute": {} -} \ No newline at end of file +} diff --git a/src/Mod/Path/Tools/Bit/60 degree Vbit.fctb b/src/Mod/Path/Tools/Bit/60degree_Vbit.fctb similarity index 100% rename from src/Mod/Path/Tools/Bit/60 degree Vbit.fctb rename to src/Mod/Path/Tools/Bit/60degree_Vbit.fctb diff --git a/src/Mod/Path/Tools/Bit/6mm Ball End.fctb b/src/Mod/Path/Tools/Bit/6mm_Ball_End.fctb similarity index 100% rename from src/Mod/Path/Tools/Bit/6mm Ball End.fctb rename to src/Mod/Path/Tools/Bit/6mm_Ball_End.fctb diff --git a/src/Mod/Path/Tools/Bit/6 mm Bullnose.fctb b/src/Mod/Path/Tools/Bit/6mm_Bullnose.fctb similarity index 100% rename from src/Mod/Path/Tools/Bit/6 mm Bullnose.fctb rename to src/Mod/Path/Tools/Bit/6mm_Bullnose.fctb diff --git a/src/Mod/Path/Tools/Bit/probe.fctb b/src/Mod/Path/Tools/Bit/probe.fctb index ebebaf4ee6..b92828e7ac 100644 --- a/src/Mod/Path/Tools/Bit/probe.fctb +++ b/src/Mod/Path/Tools/Bit/probe.fctb @@ -1,11 +1,11 @@ { "version": 2, "name": "Probe004", - "shape": "probe.FCStd", + "shape": "probe.fcstd", "parameter": { "Diameter": "6.0000 mm", "Length": "50.0000 mm", "ShaftDiameter": "4.0000 mm" }, "attribute": {} -} \ No newline at end of file +} diff --git a/src/Mod/Path/Tools/Bit/slittingsaw.fctb b/src/Mod/Path/Tools/Bit/slittingsaw.fctb index 3a74354076..e9d33fe571 100644 --- a/src/Mod/Path/Tools/Bit/slittingsaw.fctb +++ b/src/Mod/Path/Tools/Bit/slittingsaw.fctb @@ -1,7 +1,7 @@ { "version": 2, "name": "Slitting Saw", - "shape": "SlittingSaw.fcstd", + "shape": "slittingsaw.fcstd", "parameter": { "BladeThickness": "3.0000 mm", "BoltHeight": "3.0000 mm", @@ -11,4 +11,4 @@ "ShankDiameter": "19.0500 mm" }, "attribute": {} -} \ No newline at end of file +} diff --git a/src/Mod/Path/Tools/Library/Default.fctl b/src/Mod/Path/Tools/Library/Default.fctl index f0a35ef51f..b3b8ea23c4 100644 --- a/src/Mod/Path/Tools/Library/Default.fctl +++ b/src/Mod/Path/Tools/Library/Default.fctl @@ -2,27 +2,27 @@ "tools": [ { "nr": 1, - "path": "5mm Endmill.fctb" + "path": "5mm_Endmill.fctb" }, { "nr": 2, - "path": "5mm Drill.fctb" + "path": "5mm_Drill.fctb" }, { "nr": 3, - "path": "6mm Ball End.fctb" + "path": "6mm_Ball_End.fctb" }, { "nr": 4, - "path": "6 mm Bullnose.fctb" + "path": "6mm_Bullnose.fctb" }, { "nr": 5, - "path": "60 degree Vbit.fctb" + "path": "60degree_Vbit.fctb" }, { "nr": 6, - "path": "45 degree chamfer.fctb" + "path": "45degree_chamfer.fctb" }, { "nr": 7, @@ -34,4 +34,4 @@ } ], "version": 1 -} \ No newline at end of file +} diff --git a/src/Mod/Path/Tools/Shape/probe.FCStd b/src/Mod/Path/Tools/Shape/probe.fcstd similarity index 100% rename from src/Mod/Path/Tools/Shape/probe.FCStd rename to src/Mod/Path/Tools/Shape/probe.fcstd diff --git a/src/Mod/Path/Tools/Shape/SlittingSaw.fcstd b/src/Mod/Path/Tools/Shape/slittingsaw.fcstd similarity index 100% rename from src/Mod/Path/Tools/Shape/SlittingSaw.fcstd rename to src/Mod/Path/Tools/Shape/slittingsaw.fcstd From 26eac120b80e3570413fbe2050835128085b332a Mon Sep 17 00:00:00 2001 From: sliptonic Date: Mon, 16 Nov 2020 12:40:18 -0600 Subject: [PATCH 16/18] bug: on deleting tool controllers --- src/Mod/Path/PathScripts/PathOpGui.py | 7 ++++--- src/Mod/Path/PathScripts/PathToolController.py | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathOpGui.py b/src/Mod/Path/PathScripts/PathOpGui.py index f701d1bf98..c4ac06c291 100644 --- a/src/Mod/Path/PathScripts/PathOpGui.py +++ b/src/Mod/Path/PathScripts/PathOpGui.py @@ -366,9 +366,10 @@ class TaskPanelPage(object): combo.blockSignals(False) def resetToolController(self, job, tc): - self.obj.ToolController = tc - combo = self.form.toolController - self.setupToolController(self.obj, combo) + if self.obj is not None: + self.obj.ToolController = tc + combo = self.form.toolController + self.setupToolController(self.obj, combo) def setupToolController(self, obj, combo): '''setupToolController(obj, combo) ... diff --git a/src/Mod/Path/PathScripts/PathToolController.py b/src/Mod/Path/PathScripts/PathToolController.py index 7eec1c2132..11fe248b4f 100644 --- a/src/Mod/Path/PathScripts/PathToolController.py +++ b/src/Mod/Path/PathScripts/PathToolController.py @@ -98,7 +98,7 @@ class ToolController: if hasattr(obj.Tool, 'InList') and len(obj.Tool.InList) == 1: if hasattr(obj.Tool.Proxy, 'onDelete'): obj.Tool.Proxy.onDelete(obj.Tool) - obj.Document.removeObject(obj.Tool.Name) + #obj.Document.removeObject(obj.Name) def setFromTemplate(self, obj, template): ''' From 634fce6e35046f41c2c3cd45723e416d3d0ac5bf Mon Sep 17 00:00:00 2001 From: sliptonic Date: Thu, 19 Nov 2020 17:10:50 -0600 Subject: [PATCH 17/18] fix 'add' button in Job task panel fix chamfer bit lgtm cleanup --- src/Mod/Path/PathScripts/PathJob.py | 2 - src/Mod/Path/PathScripts/PathJobGui.py | 12 +++++- src/Mod/Path/PathScripts/PathOpGui.py | 7 +++- src/Mod/Path/PathScripts/PathProfileGui.py | 4 -- src/Mod/Path/PathScripts/PathToolBit.py | 10 +---- src/Mod/Path/PathScripts/PathToolBitEdit.py | 4 +- src/Mod/Path/PathScripts/PathToolBitGui.py | 8 ++-- .../Path/PathScripts/PathToolBitLibraryCmd.py | 1 + .../Path/PathScripts/PathToolBitLibraryGui.py | 17 ++------- .../Path/PathScripts/PathToolController.py | 2 - .../Path/PathScripts/PathToolControllerGui.py | 36 ++++++++++++------ src/Mod/Path/Tools/Shape/chamfer.fcstd | Bin 12226 -> 12146 bytes 12 files changed, 51 insertions(+), 52 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathJob.py b/src/Mod/Path/PathScripts/PathJob.py index ca6acf576b..d68b24cdcb 100644 --- a/src/Mod/Path/PathScripts/PathJob.py +++ b/src/Mod/Path/PathScripts/PathJob.py @@ -103,7 +103,6 @@ Notification = NotificationClass() class ObjectJob: - def __init__(self, obj, models, templateFile=None): self.obj = obj obj.addProperty("App::PropertyFile", "PostProcessorOutputFile", "Output", QtCore.QT_TRANSLATE_NOOP("PathJob", "The NC output file for this project")) @@ -155,7 +154,6 @@ class ObjectJob: self.tooltip = None self.tooltipArgs = None - obj.Proxy = self self.setFromTemplateFile(obj, templateFile) diff --git a/src/Mod/Path/PathScripts/PathJobGui.py b/src/Mod/Path/PathScripts/PathJobGui.py index 5c3d1daf9d..8e6781a2a2 100644 --- a/src/Mod/Path/PathScripts/PathJobGui.py +++ b/src/Mod/Path/PathScripts/PathJobGui.py @@ -25,7 +25,6 @@ from collections import Counter from contextlib import contextmanager import math import traceback - from pivy import coin from PySide import QtCore, QtGui @@ -45,6 +44,7 @@ import PathScripts.PathToolControllerGui as PathToolControllerGui import PathScripts.PathToolLibraryEditor as PathToolLibraryEditor import PathScripts.PathUtil as PathUtil import PathScripts.PathUtils as PathUtils +import PathScripts.PathToolBitGui as PathToolBitGui # lazily loaded modules from lazy_loader.lazy_loader import LazyLoader @@ -866,7 +866,15 @@ class TaskPanel: self.toolControllerSelect() def toolControllerAdd(self): - PathToolLibraryEditor.CommandToolLibraryEdit().edit(self.obj, self.updateToolController) + if PathPreferences.toolsUseLegacyTools(): + PathToolLibraryEditor.CommandToolLibraryEdit().edit(self.obj, self.updateToolController) + else: + tools = PathToolBitGui.LoadTools() + for tool in tools: + tc = PathToolControllerGui.Create(name=tool.Label, tool=tool) + self.obj.Proxy.addToolController(tc) + FreeCAD.ActiveDocument.recompute() + self.updateToolController() def toolControllerDelete(self): self.objectDelete(self.form.toolControllerList) diff --git a/src/Mod/Path/PathScripts/PathOpGui.py b/src/Mod/Path/PathScripts/PathOpGui.py index c4ac06c291..20e7dc678e 100644 --- a/src/Mod/Path/PathScripts/PathOpGui.py +++ b/src/Mod/Path/PathScripts/PathOpGui.py @@ -545,7 +545,7 @@ class TaskPanelBaseGeometryPage(TaskPanelPage): PathLog.error(translate("PathProject", "Faces are not supported")) return False else: - if not self.supportsPanels() or not 'Panel' in sel.Object.Name: + if not self.supportsPanels() or 'Panel' not in sel.Object.Name: if not ignoreErrors: PathLog.error(translate("PathProject", "Please select %s of a solid" % self.featureName())) return False @@ -636,6 +636,7 @@ class TaskPanelBaseGeometryPage(TaskPanelPage): row = (qList.count() + qList.frameWidth()) * 15 qList.setFixedSize(col, row) + class TaskPanelBaseLocationPage(TaskPanelPage): '''Page controller for base locations. Uses PathGetPoint.''' @@ -936,6 +937,7 @@ class TaskPanelDepthsPage(TaskPanelPage): self.form.startDepthSet.setEnabled(False) self.form.finalDepthSet.setEnabled(False) + class TaskPanelDiametersPage(TaskPanelPage): '''Page controller for diameters.''' @@ -960,7 +962,7 @@ class TaskPanelDiametersPage(TaskPanelPage): self.minDiameter.updateProperty() self.maxDiameter.updateProperty() - def setFields(self, obj): + def setFields(self, obj): self.minDiameter.updateSpinBox() self.maxDiameter.updateSpinBox() @@ -974,6 +976,7 @@ class TaskPanelDiametersPage(TaskPanelPage): if prop in ['MinDiameter', 'MaxDiameter']: self.setFields(obj) + class TaskPanel(object): ''' Generic TaskPanel implementation handling the standard Path operation layout. diff --git a/src/Mod/Path/PathScripts/PathProfileGui.py b/src/Mod/Path/PathScripts/PathProfileGui.py index cbd58ae75a..ff7b6d807f 100644 --- a/src/Mod/Path/PathScripts/PathProfileGui.py +++ b/src/Mod/Path/PathScripts/PathProfileGui.py @@ -128,8 +128,6 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): def updateVisibility(self): hasFace = False - # hasGeom = False - # fullModel = False objBase = list() if hasattr(self.obj, 'Base'): @@ -141,8 +139,6 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): if sub[:4] == 'Face': hasFace = True break - else: - fullModel = True if hasFace: self.form.processCircles.show() diff --git a/src/Mod/Path/PathScripts/PathToolBit.py b/src/Mod/Path/PathScripts/PathToolBit.py index 5b1b696594..a76150b597 100644 --- a/src/Mod/Path/PathScripts/PathToolBit.py +++ b/src/Mod/Path/PathScripts/PathToolBit.py @@ -59,8 +59,7 @@ ParameterTypeConstraint = { def _findTool(path, typ, dbg=False): - # PathLog.track("Path: {} typ: {}".format(path, typ)) - if os.path.exists(path): # absolute reference + if os.path.exists(path): # absolute reference if dbg: PathLog.debug("Found {} at {}".format(typ, path)) return path @@ -397,6 +396,7 @@ class AttributePrototype(PathSetupSheetOpPrototype.OpPrototype): PropertyGroupAttribute, translate('PathToolBit', 'Whether Spindle Power should be allowed')) + class ToolBitFactory(object): def CreateFromAttrs(self, attrs, name='ToolBit'): @@ -414,21 +414,15 @@ class ToolBitFactory(object): for pname in params: try: prop = proto.getProperty(pname) - # val = prop.valueFromString(params[pname]) prop.setupProperty(obj, pname, PropertyGroupAttribute, prop.valueFromString(params[pname])) except Exception: - # prop = obj.addProperty('App::PropertyString', pname, "Attribute", translate('PathTooolBit', 'User Defined Value')) - # setattr(obj, pname, params[pname]) prop = proto.getProperty("UserAttributes") uservals.update({pname: params[pname]}) - # prop.setupProperty(obj, pname, "UserAttributes", prop.valueFromString(params[pname])) if len(uservals.items()) > 0: prop.setupProperty(obj, "UserAttributes", PropertyGroupAttribute, uservals) - # print("prop[%s] = %s (%s)" % (pname, params[pname], type(val))) - # prop.setupProperty(obj, pname, PropertyGroupAttribute, prop.valueFromString(params[pname])) return obj def CreateFrom(self, path, name='ToolBit'): diff --git a/src/Mod/Path/PathScripts/PathToolBitEdit.py b/src/Mod/Path/PathScripts/PathToolBitEdit.py index 1f3f4c4c4d..7ac5e0c874 100644 --- a/src/Mod/Path/PathScripts/PathToolBitEdit.py +++ b/src/Mod/Path/PathScripts/PathToolBitEdit.py @@ -31,8 +31,8 @@ import re from PySide import QtCore, QtGui -# PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) -# PathLog.trackModule(PathLog.thisModule()) +PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) +PathLog.trackModule(PathLog.thisModule()) # Qt translation handling diff --git a/src/Mod/Path/PathScripts/PathToolBitGui.py b/src/Mod/Path/PathScripts/PathToolBitGui.py index 30ba0e1648..12d11241de 100644 --- a/src/Mod/Path/PathScripts/PathToolBitGui.py +++ b/src/Mod/Path/PathScripts/PathToolBitGui.py @@ -41,8 +41,9 @@ __doc__ = "Task panel editor for a ToolBit" def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) -# PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) -# PathLog.trackModule(PathLog.thisModule()) + +PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) +PathLog.trackModule(PathLog.thisModule()) class ViewProvider(object): @@ -141,8 +142,7 @@ class TaskPanel: self.editor.reject() FreeCADGui.Control.closeDialog() if self.deleteOnReject: - FreeCAD.ActiveDocument.openTransaction(translate('PathToolBit', - 'Uncreate ToolBit')) + FreeCAD.ActiveDocument.openTransaction(translate('PathToolBit', 'Uncreate ToolBit')) self.editor.reject() FreeCAD.ActiveDocument.removeObject(self.obj.Name) FreeCAD.ActiveDocument.commitTransaction() diff --git a/src/Mod/Path/PathScripts/PathToolBitLibraryCmd.py b/src/Mod/Path/PathScripts/PathToolBitLibraryCmd.py index 12fc57f7d5..3d18989ee5 100644 --- a/src/Mod/Path/PathScripts/PathToolBitLibraryCmd.py +++ b/src/Mod/Path/PathScripts/PathToolBitLibraryCmd.py @@ -129,6 +129,7 @@ class CommandToolBitLibraryOpen: # return True # return False + if FreeCAD.GuiUp: FreeCADGui.addCommand('Path_ToolBitLibraryOpen', CommandToolBitLibraryOpen()) FreeCADGui.addCommand('Path_ToolBitDock', CommandToolBitSelectorOpen()) diff --git a/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py b/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py index 72786f1a3f..a0a52f0de3 100644 --- a/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py +++ b/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py @@ -36,7 +36,6 @@ import PySide import json import os import glob -import traceback import uuid as UUID from functools import partial @@ -130,7 +129,6 @@ class ModelFactory(object): self.path = "" # self.currentLib = "" - def __libraryLoad(self, path, datamodel): PathLog.track(path) PathPreferences.setLastFileToolLibrary(path) @@ -153,7 +151,6 @@ class ModelFactory(object): msg = "Error loading tool: {} : {}".format(toolBit['path'], e) FreeCAD.Console.PrintError(msg) - def _toolAdd(self, nr, tool, path): strShape = os.path.splitext(os.path.basename(tool['shape']))[0] @@ -176,11 +173,6 @@ class ModelFactory(object): toolShape.setData(strShape, PySide.QtCore.Qt.EditRole) toolShape.setEditable(False) - # toolDiameter = PySide.QtGui.QStandardItem() - # toolDiameter.setData(strDiam, PySide.QtCore.Qt.EditRole) - # toolDiameter.setEditable(False) - - #return [toolNr, toolName, toolShape, toolDiameter] return [toolNr, toolName, toolShape] def newTool(self, datamodel, path): @@ -196,8 +188,8 @@ class ModelFactory(object): nr = max(nr, itemNr) nr += 1 tool = PathToolBit.Declaration(path) - except Exception: - PathLog.error(traceback.print_exc()) + except Exception as e: + PathLog.error(e) datamodel.appendRow(self._toolAdd(nr, tool, path)) @@ -388,7 +380,7 @@ class ToolBitLibrary(object): return filename = PathToolBitGui.GetNewToolFile() - if filename == None: + if filename is None: return # Parse out the name of the file and write the structure @@ -569,7 +561,6 @@ class ToolBitLibrary(object): def libPaths(self): lib = PathPreferences.lastFileToolLibrary() loc = PathPreferences.lastPathToolLibrary() - #loc = os.path.split(lib)[0] PathLog.track("lib: {} loc: {}".format(lib, loc)) return lib, loc @@ -582,7 +573,6 @@ class ToolBitLibrary(object): self.toolTableView.setUpdatesEnabled(False) self.form.TableList.setUpdatesEnabled(False) - if path is None: path, loc = self.libPaths() @@ -600,7 +590,6 @@ class ToolBitLibrary(object): self.toolModel.setHorizontalHeaderLabels(self.columnNames()) self.listModel.setHorizontalHeaderLabels(['Library']) - # Select the current library in the list of tables curIndex = None for i in range(self.listModel.rowCount()): diff --git a/src/Mod/Path/PathScripts/PathToolController.py b/src/Mod/Path/PathScripts/PathToolController.py index 11fe248b4f..45e468500f 100644 --- a/src/Mod/Path/PathScripts/PathToolController.py +++ b/src/Mod/Path/PathScripts/PathToolController.py @@ -98,7 +98,6 @@ class ToolController: if hasattr(obj.Tool, 'InList') and len(obj.Tool.InList) == 1: if hasattr(obj.Tool.Proxy, 'onDelete'): obj.Tool.Proxy.onDelete(obj.Tool) - #obj.Document.removeObject(obj.Name) def setFromTemplate(self, obj, template): ''' @@ -177,7 +176,6 @@ class ToolController: commands += "(" + obj.Label + ")"+'\n' commands += 'M6 T'+str(obj.ToolNumber)+'\n' - # 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 diff --git a/src/Mod/Path/PathScripts/PathToolControllerGui.py b/src/Mod/Path/PathScripts/PathToolControllerGui.py index c290abb299..54daefa5ca 100644 --- a/src/Mod/Path/PathScripts/PathToolControllerGui.py +++ b/src/Mod/Path/PathScripts/PathToolControllerGui.py @@ -35,10 +35,12 @@ from PySide import QtCore, QtGui from lazy_loader.lazy_loader import LazyLoader Part = LazyLoader('Part', globals(), 'Part') + # Qt translation handling def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) + class ViewProvider: def __init__(self, vobj): @@ -122,7 +124,8 @@ class ViewProvider: return [obj.Tool] return [] -def Create(name = 'Default Tool', tool=None, toolNumber=1): + +def Create(name='Default Tool', tool=None, toolNumber=1): PathLog.track(tool, toolNumber) obj = PathScripts.PathToolController.Create(name, tool, toolNumber) @@ -172,6 +175,7 @@ class CommandPathToolController(object): job.Proxy.addToolController(tc) FreeCAD.ActiveDocument.recompute() + class ToolControllerEditor(object): def __init__(self, obj, asDialog): @@ -180,13 +184,18 @@ class ToolControllerEditor(object): self.form.buttonBox.hide() self.obj = obj - self.vertFeed = PathGui.QuantitySpinBox(self.form.vertFeed, obj, 'VertFeed') - self.horizFeed = PathGui.QuantitySpinBox(self.form.horizFeed, obj, 'HorizFeed') - self.vertRapid = PathGui.QuantitySpinBox(self.form.vertRapid, obj, 'VertRapid') - self.horizRapid = PathGui.QuantitySpinBox(self.form.horizRapid, obj, 'HorizRapid') + self.vertFeed = PathGui.QuantitySpinBox(self.form.vertFeed, obj, + 'VertFeed') + self.horizFeed = PathGui.QuantitySpinBox(self.form.horizFeed, obj, + 'HorizFeed') + self.vertRapid = PathGui.QuantitySpinBox(self.form.vertRapid, obj, + 'VertRapid') + self.horizRapid = PathGui.QuantitySpinBox(self.form.horizRapid, obj, + 'HorizRapid') if obj.Proxy.usesLegacyTool(obj): - self.editor = PathToolEdit.ToolEditor(obj.Tool, self.form.toolEditor) + self.editor = PathToolEdit.ToolEditor(obj.Tool, + self.form.toolEditor) else: self.editor = None self.form.toolBox.widget(1).hide() @@ -201,7 +210,8 @@ class ToolControllerEditor(object): self.vertFeed.updateSpinBox() self.vertRapid.updateSpinBox() self.form.spindleSpeed.setValue(tc.SpindleSpeed) - index = self.form.spindleDirection.findText(tc.SpindleDir, QtCore.Qt.MatchFixedString) + index = self.form.spindleDirection.findText(tc.SpindleDir, + QtCore.Qt.MatchFixedString) if index >= 0: self.form.spindleDirection.setCurrentIndex(index) @@ -224,9 +234,9 @@ class ToolControllerEditor(object): self.editor.updateTool() tc.Tool = self.editor.tool - except Exception as e: # pylint: disable=broad-except - PathLog.error(translate("PathToolController", "Error updating TC: %s") % e) - + except Exception as e: + PathLog.error(translate("PathToolController", + "Error updating TC: %s") % e) def refresh(self): self.form.blockSignals(True) @@ -296,7 +306,8 @@ class TaskPanel: def setupUi(self): if self.editor.editor: t = Part.makeCylinder(1, 1) - self.toolrep = FreeCAD.ActiveDocument.addObject("Part::Feature", "tool") + self.toolrep = FreeCAD.ActiveDocument.addObject("Part::Feature", + "tool") self.toolrep.Shape = t self.setFields() @@ -311,7 +322,7 @@ class DlgToolControllerEdit: self.obj = obj def exec_(self): - restoreTC = self.obj.Proxy.templateAttrs(self.obj) + restoreTC = self.obj.Proxy.templateAttrs(self.obj) rc = False if not self.editor.form.exec_(): @@ -320,6 +331,7 @@ class DlgToolControllerEdit: rc = True return rc + if FreeCAD.GuiUp: # register the FreeCAD command FreeCADGui.addCommand('Path_ToolController', CommandPathToolController()) diff --git a/src/Mod/Path/Tools/Shape/chamfer.fcstd b/src/Mod/Path/Tools/Shape/chamfer.fcstd index a6acac7a43e4bfea165ec6220d2c056139d9b551..e56a1359d4d0ab4f8ecece0d9e11ce940360a23a 100644 GIT binary patch literal 12146 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 B#6kc7 literal 12226 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 From 56b6874526ce7c25fba6157ccf8c3059d8ecb1b9 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Sun, 22 Nov 2020 11:08:28 -0600 Subject: [PATCH 18/18] revert docstring format for help box formatting. log levels reset to INFO remove unnecessary comments. --- src/Mod/Path/InitGui.py | 9 --------- src/Mod/Path/PathScripts/PathToolBitEdit.py | 4 ++-- src/Mod/Path/PathScripts/PathToolBitGui.py | 12 +++++------- 3 files changed, 7 insertions(+), 18 deletions(-) diff --git a/src/Mod/Path/InitGui.py b/src/Mod/Path/InitGui.py index cf0e8a36e6..7580f38862 100644 --- a/src/Mod/Path/InitGui.py +++ b/src/Mod/Path/InitGui.py @@ -77,15 +77,6 @@ class PathWorkbench (Workbench): from PathScripts import PathToolBitCmd from PathScripts import PathToolBitLibraryCmd - - #if PathPreferences.experimentalFeaturesEnabled(): - # #toolbitcmdlist = PathToolBitCmd.CommandList + ["Separator"] + PathToolBitLibraryCmd.CommandList + ["Path_ToolController", "Separator"] - # toolbitcmdlist = PathToolBitLibraryCmd.MenuList - # self.toolbitctxmenu = ["Path_ToolBitLibraryLoad", "Path_ToolController"] - #else: - # toolbitcmdlist = [] - # self.toolbitctxmenu = [] - import PathCommands PathGuiInit.Startup() diff --git a/src/Mod/Path/PathScripts/PathToolBitEdit.py b/src/Mod/Path/PathScripts/PathToolBitEdit.py index 7ac5e0c874..0ee54747bb 100644 --- a/src/Mod/Path/PathScripts/PathToolBitEdit.py +++ b/src/Mod/Path/PathScripts/PathToolBitEdit.py @@ -31,8 +31,8 @@ import re from PySide import QtCore, QtGui -PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) -PathLog.trackModule(PathLog.thisModule()) +PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) +# PathLog.trackModule(PathLog.thisModule()) # Qt translation handling diff --git a/src/Mod/Path/PathScripts/PathToolBitGui.py b/src/Mod/Path/PathScripts/PathToolBitGui.py index 12d11241de..21a755f2ce 100644 --- a/src/Mod/Path/PathScripts/PathToolBitGui.py +++ b/src/Mod/Path/PathScripts/PathToolBitGui.py @@ -42,8 +42,8 @@ def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) -PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) -PathLog.trackModule(PathLog.thisModule()) +PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) +# PathLog.trackModule(PathLog.thisModule()) class ViewProvider(object): @@ -171,11 +171,9 @@ class TaskPanel: class ToolBitGuiFactory(PathToolBit.ToolBitFactory): def Create(self, name='ToolBit', shapeFile=None): - ''' - Create(name = 'ToolBit') ... creates a new tool bit. - It is assumed the tool will be edited immediately so the internal - bit body is still attached. - ''' + '''Create(name = 'ToolBit') ... creates a new tool bit. + It is assumed the tool will be edited immediately so the internal bit body is still attached.''' + FreeCAD.ActiveDocument.openTransaction(translate('PathToolBit', 'Create ToolBit')) tool = PathToolBit.ToolBitFactory.Create(self, name, shapeFile)