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()))