From d0b8f34ba5ebd413d90426cc229c8bb39a46afb1 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sun, 27 Oct 2019 23:58:07 -0700 Subject: [PATCH] Added library commands and drag&drop for rearranging and copying of tools --- .../Resources/panels/ToolBitLibraryEdit.ui | 36 +-- src/Mod/Path/PathScripts/PathToolBit.py | 20 +- .../Path/PathScripts/PathToolBitLibraryGui.py | 269 ++++++++++++++---- src/Mod/Path/Tools/Library/endmills.fctl | 14 + 4 files changed, 272 insertions(+), 67 deletions(-) create mode 100644 src/Mod/Path/Tools/Library/endmills.fctl diff --git a/src/Mod/Path/Gui/Resources/panels/ToolBitLibraryEdit.ui b/src/Mod/Path/Gui/Resources/panels/ToolBitLibraryEdit.ui index 7f0f2b040f..48a32c705f 100644 --- a/src/Mod/Path/Gui/Resources/panels/ToolBitLibraryEdit.ui +++ b/src/Mod/Path/Gui/Resources/panels/ToolBitLibraryEdit.ui @@ -101,7 +101,7 @@ - + 0 @@ -111,12 +111,27 @@ + + true + + + true + + + QAbstractItemView::InternalMove + + + Qt::MoveAction + QAbstractItemView::SelectRows true + + false + @@ -157,24 +172,9 @@ - + - Up - - - - :/icons/button_up.svg:/icons/button_up.svg - - - - - - - Down - - - - :/icons/button_down.svg:/icons/button_down.svg + Enumerate diff --git a/src/Mod/Path/PathScripts/PathToolBit.py b/src/Mod/Path/PathScripts/PathToolBit.py index 4d5ae87a79..1813b4c2b8 100644 --- a/src/Mod/Path/PathScripts/PathToolBit.py +++ b/src/Mod/Path/PathScripts/PathToolBit.py @@ -56,15 +56,23 @@ ParameterTypeConstraint = { } -def _findTool(path, typ): +def _findTool(path, typ, dbg=False): if os.path.exists(path): + if dbg: + PathLog.debug("Found {} at {}".format(typ, path)) return path def searchFor(pname, fname): + if dbg: + PathLog.debug("Looking for {}".format(pname)) if fname: for p in PathPreferences.searchPathsTool(typ): f = os.path.join(p, fname) + if dbg: + PathLog.debug(" Checking {}".format(f)) if os.path.exists(f): + if dbg: + PathLog.debug(" Found {} at {}".format(typ, f)) return f if pname and '/' != pname: ppname, pfname = os.path.split(pname) @@ -78,6 +86,16 @@ def findTemplate(path): '''findTemplate(path) ... search for path, full and partially in all known template directories.''' return _findTool(path, 'Template') +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 updateConstraint(sketch, name, value): for i, constraint in enumerate(sketch.Constraints): if constraint.Name.split(';')[0] == name: diff --git a/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py b/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py index 00bc56104f..b652cce0ca 100644 --- a/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py +++ b/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py @@ -25,93 +25,266 @@ import FreeCADGui import PathScripts.PathLog as PathLog +import PathScripts.PathPreferences as PathPreferences import PathScripts.PathToolBit as PathToolBit import PathScripts.PathToolBitGui as PathToolBitGui import PySide - +import json import os import traceback +import uuid PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) PathLog.trackModule(PathLog.thisModule()) -class Delegate(PySide.QtGui.QStyledItemDelegate): +_UuidRole = PySide.QtCore.Qt.UserRole + 1 +_PathRole = PySide.QtCore.Qt.UserRole + 2 - def createEditor(self, parent, option, index): - PathLog.track(index) +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.setSortingEnabled(True) + self.setSelectionBehavior(PySide.QtGui.QAbstractItemView.SelectRows) + self.verticalHeader().hide() + + def supportedDropActions(self): + return [PySide.QtCore.Qt.CopyAction, PySide.QtCore.Qt.MoveAction] + + def _uuidOfRow(self, row): + model = self.model() + return model.data(model.index(row, 0), _UuidRole) + + def _rowWithUuid(self, uuid): + model = self.model() + for row in range(model.rowCount()): + if self._uuidOfRow(row) == uuid: + return row return None - def setEditorData(self, widget, index): - PathLog.track(index) - def setModelData(self, widget, model, index): - PathLog.track(index) - def updateEditorGeometry(self, widget, option, index): - PathLog.track(index) - widget.setGeometry(option.rect) + + def _copyTool(self, uuid_, dstRow): + model = self.model() + items = [] + model.insertRow(dstRow) + srcRow = self._rowWithUuid(uuid_) + for col in range(model.columnCount()): + srcItem = model.item(srcRow, col) + + model.setData(model.index(dstRow, col), srcItem.data(PySide.QtCore.Qt.EditRole), PySide.QtCore.Qt.EditRole) + if col == 0: + model.setData(model.index(dstRow, col), srcItem.data(_PathRole), _PathRole) + model.setData(model.index(dstRow, col), uuid.uuid4(), _UuidRole) + else: + model.item(dstRow, col).setEditable(False) + + def _copyTools(self, uuids, dst): + for i, uuid in enumerate(uuids): + self._copyTool(uuid, dst + i) + + def dropEvent(self, event): + PathLog.track() + mime = event.mimeData() + data = mime.data('application/x-qstandarditemmodeldatalist') + stream = PySide.QtCore.QDataStream(data) + srcRows = [] + while not stream.atEnd(): + 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() + srcUuids = [self._uuidOfRow(row) for row in set(srcRows)] + destRow = self.rowAt(event.pos().y()) + + self._copyTools(srcUuids, destRow) + if PySide.QtCore.Qt.DropAction.MoveAction == event.proposedAction(): + for uuid in srcUuids: + model.removeRow(self._rowWithUuid(uuid)) + +#class ToolTableModel(PySide.QtGui.QStandardItemModel): + class ToolBitLibrary(object): - def __init__(self): + def __init__(self, path=None): + self.path = path self.form = FreeCADGui.PySideUic.loadUi(':/panels/ToolBitLibraryEdit.ui') - #self.form = FreeCADGui.PySideUic.loadUi('src/Mod/Path/Gui/Resources/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() + if path: + self.libraryLoad(path) + + 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) + + toolTemplate = PySide.QtGui.QStandardItem() + toolTemplate.setData(os.path.splitext(os.path.basename(tool['template']))[0], PySide.QtCore.Qt.EditRole) + toolTemplate.setEditable(False) + + toolDiameter = PySide.QtGui.QStandardItem() + toolDiameter.setData(tool['parameter']['Diameter'], PySide.QtCore.Qt.EditRole) + toolDiameter.setEditable(False) + + self.model.appendRow([toolNr, toolName, toolTemplate, toolDiameter]) def toolAdd(self): PathLog.track() try: - foo = PathToolBitGui.GetToolFile(self.form) - if foo: + 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) - 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) - - toolNr = PySide.QtGui.QStandardItem() - toolNr.setData(nr + 1, PySide.QtCore.Qt.EditRole) - - toolName = PySide.QtGui.QStandardItem() - toolName.setData(tool['name'], PySide.QtCore.Qt.EditRole) - toolName.setEditable(False) - - toolTemplate = PySide.QtGui.QStandardItem() - toolTemplate.setData(os.path.splitext(os.path.basename(tool['template']))[0], PySide.QtCore.Qt.EditRole) - toolTemplate.setEditable(False) - - toolDiameter = PySide.QtGui.QStandardItem() - toolDiameter.setData(tool['parameter']['Diameter'], PySide.QtCore.Qt.EditRole) - toolDiameter.setEditable(False) - - self.model.appendRow([toolNr, toolName, toolTemplate, toolDiameter]) - - self.form.toolTable.resizeColumnsToContents() - else: - PathLog.info("no tool") + self._toolAdd(nr + i, tool, foo) + self.toolTableView.resizeColumnsToContents() except: PathLog.error('something happened') PathLog.error(traceback.print_exc()) def toolDelete(self): PathLog.track() - def toolUp(self): + 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 toolEnumerate(self): PathLog.track() - def toolDown(self): + for row in range(self.model.rowCount()): + self.model.setData(self.model.index(row, 0), row + 1, PySide.QtCore.Qt.EditRole) + + def toolSelect(self, selected, deselected): + self.form.toolDelete.setEnabled(len(self.toolTableView.selectedIndexes()) > 0) + + def open(self, path=None): + if path: + fullPath = PathToolBit.findLibrary(path) + if fullPath: + self.libraryLoad(fullPath) + else: + self.libraryOpen() + return self.form.exec_() + + def updateToolbar(self): + if self.path: + self.form.librarySave.setEnabled(True) + else: + self.form.librarySave.setEnabled(False) + + def libraryOpen(self): PathLog.track() + foo = PySide.QtGui.QFileDialog.getOpenFileName(self.form, 'Tool Library', PathPreferences.lastPathToolLibrary(), '*.fctl') + if foo and foo[0]: + path = foo[0] + PathPreferences.setLastPathToolLibrary(os.path.dirname(path)) + self.libraryLoad(path) + + def libraryLoad(self, path): + self.toolTableView.setUpdatesEnabled(False) + self.model.clear() + if path: + with open(path) as fp: + library = json.load(fp) + for nr in library['tools']: + bit = PathToolBit.findBit(library['tools'][nr]) + 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 libraryNew(self): + self.libraryLoad(None) + + 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) + tools[toolNr] = toolPath + + with open(self.path, 'w') as fp: + json.dump(library, fp, sort_keys=True, indent=2) + + def librarySaveAs(self): + foo = PySide.QtGui.QFileDialog.getSaveFileName(self.form, 'Tool Library', PathPreferences.lastPathToolLibrary(), '*.fctl') + if foo and foo[0]: + path = foo[0] if foo[0].endswith('.fctl') else "{}.fctl".format(foo[0]) + PathPreferences.setLastPathToolLibrary(os.path.dirname(path)) + self.path = path + self.librarySave() + self.updateToolbar() def columnNames(self): return ['Nr', 'Tool', 'Template', 'Diameter'] + def rowsMoved(self, parent, start, end, dest, row): + PathLog.track(parent, start, end, dest, row) + + def rowsAboutToBeMoved(self, srcParent, srcStart, srcEnd, destParent, destRow): + PathLog.track(srcParent, srcStart, srcEnd, destParent, destRow) + def setupUI(self): PathLog.track('+') - self.delegate = Delegate(self.form) - self.model = PySide.QtGui.QStandardItemModel(0, len(self.columnNames()), self.form) + self.model = PySide.QtGui.QStandardItemModel(0, len(self.columnNames()), self.toolTableView) self.model.setHorizontalHeaderLabels(self.columnNames()) + self.model.rowsAboutToBeMoved.connect(self.rowsAboutToBeMoved) + self.model.rowsMoved.connect(self.rowsMoved) - self.form.toolTable.setModel(self.model) - self.form.toolTable.resizeColumnsToContents() + self.toolTableView.setModel(self.model) + self.toolTableView.resizeColumnsToContents() + self.toolTableView.selectionModel().selectionChanged.connect(self.toolSelect) self.form.toolAdd.clicked.connect(self.toolAdd) self.form.toolDelete.clicked.connect(self.toolDelete) - self.form.toolUp.clicked.connect(self.toolUp) - self.form.toolDown.clicked.connect(self.toolDown) + self.form.toolEnumerate.clicked.connect(self.toolEnumerate) + self.form.libraryNew.clicked.connect(self.libraryNew) + self.form.libraryOpen.clicked.connect(self.libraryOpen) + self.form.librarySave.clicked.connect(self.librarySave) + self.form.librarySaveAs.clicked.connect(self.librarySaveAs) + + self.toolSelect([], []) + self.updateToolbar() PathLog.track('-') diff --git a/src/Mod/Path/Tools/Library/endmills.fctl b/src/Mod/Path/Tools/Library/endmills.fctl new file mode 100644 index 0000000000..ae9c37c9f8 --- /dev/null +++ b/src/Mod/Path/Tools/Library/endmills.fctl @@ -0,0 +1,14 @@ +{ + "tools": { + "1": "t1", + "2": "t2", + "3": "t3", + "4": "t4", + "5": "t5", + "6": "t6", + "7": "t7", + "8": "t8", + "9": "t9" + }, + "version": 1 +}