CAM: Tool Bit Library Fixes (#18973)

* remove unused else
* Enable reloading / re-selection of libraries
* Reload the libraries when exiting the editor
* Clear the model to prevent duplicate entries
* add missing docstring to reduce linting warnings
* Maintain any previous library selection when editing
This commit is contained in:
Daniel Wood
2025-02-03 16:58:24 +00:00
committed by GitHub
parent 65466d580b
commit 2e9e496b57

View File

@@ -59,6 +59,7 @@ translate = FreeCAD.Qt.translate
def checkWorkingDir():
"""Check the tool library directory writable and configure a new library if required"""
# users shouldn't use the example toolbits and libraries.
# working directory should be writable
Path.Log.track()
@@ -97,7 +98,8 @@ def checkWorkingDir():
Path.Preferences.setLastPathToolBit("{}{}Bit".format(workingdir, os.path.sep))
Path.Log.debug("setting workingdir to: {}".format(workingdir))
# Copy only files of default Path/Tool folder to working directory (targeting the README.md help file)
# Copy only files of default Path/Tool folder to working directory
# (targeting the README.md help file)
src_toolfiles = os.listdir(defaultdir)
for file_name in src_toolfiles:
if file_name in ["README.md"]:
@@ -216,6 +218,7 @@ class _TableView(PySide.QtGui.QTableView):
self._copyTool(uuid, dst + i)
def dropEvent(self, event):
"""Handle drop events on the tool table"""
Path.Log.track()
mime = event.mimeData()
data = mime.data("application/x-qstandarditemmodeldatalist")
@@ -247,6 +250,7 @@ class ModelFactory:
"""
Path.Log.track()
path = Path.Preferences.lastPathToolLibrary()
model.clear()
if os.path.isdir(path): # opening all tables in a directory
libFiles = [f for f in glob.glob(path + os.path.sep + "*.fctl")]
@@ -394,15 +398,16 @@ class ToolBitSelector(object):
self.factory = ModelFactory()
self.toolModel = PySide.QtGui.QStandardItemModel(0, len(self.columnNames()))
self.libraryModel = PySide.QtGui.QStandardItemModel(0, len(self.columnNames()))
self.factory.find_libraries(self.libraryModel)
self.setupUI()
self.title = self.form.windowTitle()
def columnNames(self):
"""Define the column names to display"""
return ["#", "Tool"]
def currentLibrary(self, shortNameOnly):
"""Get the file path for the current tool library"""
libfile = Path.Preferences.lastFileToolLibrary()
if libfile is None or libfile == "":
return ""
@@ -411,6 +416,7 @@ class ToolBitSelector(object):
return libfile
def loadData(self):
"""Load the toolbits for the selected tool library"""
Path.Log.track()
self.toolModel.clear()
self.toolModel.setHorizontalHeaderLabels(self.columnNames())
@@ -422,40 +428,58 @@ class ToolBitSelector(object):
# Get the data for the selected index
libPath = self.libraryModel.item(currentIndex).data(_PathRole)
self.factory.library_open(self.toolModel, libPath)
else:
pass
self.toolModel.takeColumn(3)
self.toolModel.takeColumn(2)
def setupUI(self):
def loadToolLibraries(self):
"""
Load the tool libraries in to self.libraryModel
and populate the tooldock form combobox with the
libraries names
"""
Path.Log.track()
self.loadData() # Load the initial data for the tool model
self.form.tools.setModel(self.toolModel)
self.form.tools.selectionModel().selectionChanged.connect(self.enableButtons)
self.form.tools.doubleClicked.connect(partial(self.selectedOrAllToolControllers))
# Get the current library so we can try and maintain any previous selection
current_lib = self.currentLibrary(True) # True to get short name only
# load the tool libraries
self.factory.find_libraries(self.libraryModel)
# Set the library model to the combobox
self.form.cboLibraries.setModel(self.libraryModel)
# Set the current library as the selected item in the combobox
currentIndex = self.form.cboLibraries.findText(current_lib)
# Set the selected library as the currentIndex in the combobox
if currentIndex == -1 and self.libraryModel.rowCount() > 0:
# If current library is not found, default to the first item
currentIndex = 0
self.form.cboLibraries.setCurrentIndex(currentIndex)
def setupUI(self):
"""Setup the form and load the tooltable data"""
Path.Log.track()
# Connect the library change to reload data and update tooltip
self.form.cboLibraries.currentIndexChanged.connect(self.loadData)
self.form.cboLibraries.currentIndexChanged.connect(self.updateLibraryTooltip)
# Set the current library as the selected item in the combobox
current_lib = self.currentLibrary(True) # True to get short name only
currentIndex = self.form.cboLibraries.findText(current_lib)
if currentIndex == -1 and self.libraryModel.rowCount() > 0:
# If current library is not found, default to the first item
currentIndex = 0
self.form.cboLibraries.setCurrentIndex(currentIndex)
self.updateLibraryTooltip(currentIndex) # Initialize the tooltip
# Load the tool libraries.
# This will trigger a change in current index of the cboLibraries combobox
self.loadToolLibraries()
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 updateLibraryTooltip(self, index):
"""Add a tooltip to the combobox"""
if index != -1:
item = self.libraryModel.item(index)
if item:
@@ -467,15 +491,18 @@ class ToolBitSelector(object):
self.form.cboLibraries.setToolTip(translate("CAM_Toolbit", "No library selected"))
def enableButtons(self):
"""Enable button to add tool controller when a tool is selected"""
# Set buttons inactive
self.form.addToolController.setEnabled(False)
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)
self.form.addToolController.setEnabled(selected and jobs)
def libraryEditorOpen(self):
library = ToolBitLibrary()
library.open()
self.loadData()
self.loadToolLibraries()
def selectedOrAllTools(self):
"""
@@ -565,6 +592,7 @@ class ToolBitLibrary(object):
self.title = self.form.windowTitle()
def toolBitNew(self):
"""Create a new toolbit"""
Path.Log.track()
# select the shape file
@@ -596,6 +624,7 @@ class ToolBitLibrary(object):
self.librarySave()
def toolBitExisting(self):
"""Add an existing toolbit to the library"""
filenames = PathToolBitGui.GetToolFiles()
@@ -612,6 +641,7 @@ class ToolBitLibrary(object):
self.librarySave()
def toolDelete(self):
"""Delete a tool"""
Path.Log.track()
selectedRows = set([index.row() for index in self.toolTableView.selectedIndexes()])
for row in sorted(list(selectedRows), key=lambda r: -r):
@@ -635,6 +665,7 @@ class ToolBitLibrary(object):
return self.form.exec_()
def libraryPath(self):
"""Select and load a tool library"""
Path.Log.track()
path = PySide.QtGui.QFileDialog.getExistingDirectory(
self.form, "Tool Library Path", Path.Preferences.lastPathToolLibrary()
@@ -646,6 +677,7 @@ class ToolBitLibrary(object):
self.loadData()
def cleanupDocument(self):
"""Clean up the document"""
# This feels like a hack. Remove the toolbit object
# remove the editor from the dialog
# re-enable all the controls
@@ -658,6 +690,7 @@ class ToolBitLibrary(object):
self.lockoff()
def accept(self):
"""Handle accept signal"""
self.editor.accept()
self.temptool.Proxy.saveToFile(self.temptool, self.temptool.File)
self.librarySave()
@@ -665,9 +698,11 @@ class ToolBitLibrary(object):
self.cleanupDocument()
def reject(self):
"""Handle reject signal"""
self.cleanupDocument()
def lockon(self):
"""Set the state of the form widgets: inactive"""
self.toolTableView.setEnabled(False)
self.form.toolCreate.setEnabled(False)
self.form.toolDelete.setEnabled(False)
@@ -679,6 +714,7 @@ class ToolBitLibrary(object):
self.form.librarySave.setEnabled(False)
def lockoff(self):
"""Set the state of the form widgets: active"""
self.toolTableView.setEnabled(True)
self.form.toolCreate.setEnabled(True)
self.form.toolDelete.setEnabled(True)
@@ -691,6 +727,7 @@ class ToolBitLibrary(object):
self.form.librarySave.setEnabled(True)
def toolEdit(self, selected):
"""Edit the selected tool bit"""
Path.Log.track()
item = self.toolModel.item(selected.row(), 0)
@@ -721,6 +758,7 @@ class ToolBitLibrary(object):
print("all done")
def libraryNew(self):
"""Create a new tool library"""
TooltableTypeJSON = translate("CAM_ToolBit", "Tooltable JSON (*.fctl)")
filename = PySide.QtGui.QFileDialog.getSaveFileName(
@@ -744,6 +782,7 @@ class ToolBitLibrary(object):
self.loadData()
def librarySave(self):
"""Save the tool library"""
library = {}
tools = []
library["version"] = 1
@@ -771,6 +810,7 @@ class ToolBitLibrary(object):
self.form.close()
def libPaths(self):
"""Get the file path for the last used tool library"""
lib = Path.Preferences.lastFileToolLibrary()
loc = Path.Preferences.lastPathToolLibrary()
@@ -785,6 +825,7 @@ class ToolBitLibrary(object):
]
def loadData(self, path=None):
"""Load tooltable data"""
Path.Log.track(path)
self.toolTableView.setUpdatesEnabled(False)
self.form.TableList.setUpdatesEnabled(False)
@@ -821,6 +862,7 @@ class ToolBitLibrary(object):
self.form.TableList.setUpdatesEnabled(True)
def setupUI(self):
"""Setup the form and load the tool library data"""
Path.Log.track()
self.form.TableList.setModel(self.listModel)
self.toolTableView.setModel(self.toolModel)
@@ -846,7 +888,7 @@ class ToolBitLibrary(object):
self.toolSelect([], [])
def librarySaveAs(self, path):
"""Save the tooltable to a format to use with an external system"""
TooltableTypeJSON = translate("CAM_ToolBit", "Tooltable JSON (*.fctl)")
TooltableTypeLinuxCNC = translate("CAM_ToolBit", "LinuxCNC tooltable (*.tbl)")
TooltableTypeCamotics = translate("CAM_ToolBit", "CAMotics tooltable (*.json)")
@@ -874,7 +916,7 @@ class ToolBitLibrary(object):
self.librarySave()
def libararySaveLinuxCNC(self, path):
# linuxcnc line template
"""Export the tool table to a file for use with linuxcnc"""
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")
@@ -938,6 +980,7 @@ class ToolBitLibrary(object):
Path.Log.error("Could not find tool #{} ".format(toolNr))
def libararySaveCamotics(self, path):
"""Export the tool table to a file for use with camotics"""
SHAPEMAP = {
"ballend": "Ballnose",