From 7049c09448a615d1e82dd505d1dee716d9920562 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Thu, 17 Dec 2020 19:14:44 -0800 Subject: [PATCH 1/7] Only update the shape if it has changed. --- src/Mod/Path/PathScripts/PathToolBitEdit.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathToolBitEdit.py b/src/Mod/Path/PathScripts/PathToolBitEdit.py index 0ee54747bb..c9cf238ebd 100644 --- a/src/Mod/Path/PathScripts/PathToolBitEdit.py +++ b/src/Mod/Path/PathScripts/PathToolBitEdit.py @@ -194,12 +194,14 @@ class ToolBitEditor(object): def updateShape(self): PathLog.track() - self.tool.BitShape = str(self.form.shapePath.text()) - self.setupTool(self.tool) - self.form.toolName.setText(self.tool.Label) + shapePath = str(self.form.shapePath.text()) + if self.tool.BitShape != shapePath: + self.tool.BitShape = shapePath + self.setupTool(self.tool) + self.form.toolName.setText(self.tool.Label) - for editor in self.bitEditor: - self.bitEditor[editor].updateSpinBox() + for editor in self.bitEditor: + self.bitEditor[editor].updateSpinBox() def updateTool(self): PathLog.track() From 3da82fa6afc29d845334a58cd102336d5f5fbdca Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Thu, 17 Dec 2020 19:55:59 -0800 Subject: [PATCH 2/7] Hide document when loading a ToolBit. --- src/Mod/Path/PathScripts/PathToolBit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Path/PathScripts/PathToolBit.py b/src/Mod/Path/PathScripts/PathToolBit.py index 9cd6f488de..4c8929b26f 100644 --- a/src/Mod/Path/PathScripts/PathToolBit.py +++ b/src/Mod/Path/PathScripts/PathToolBit.py @@ -252,7 +252,7 @@ class ToolBit(object): p = findShape(p) if not path and p != obj.BitShape: obj.BitShape = p - doc = FreeCAD.open(p) + doc = FreeCAD.openDocument(p, True) obj.ShapeName = doc.Name docOpened = True return (doc, docOpened) From a5e992f700a07f4544fc7618154261fb0475ec07 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Thu, 17 Dec 2020 19:59:44 -0800 Subject: [PATCH 3/7] Make BitShape read/writeable, so the tool can be edited when shared to a different system --- src/Mod/Path/PathScripts/PathToolBit.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Mod/Path/PathScripts/PathToolBit.py b/src/Mod/Path/PathScripts/PathToolBit.py index 4c8929b26f..4c502c528a 100644 --- a/src/Mod/Path/PathScripts/PathToolBit.py +++ b/src/Mod/Path/PathScripts/PathToolBit.py @@ -200,7 +200,9 @@ class ToolBit(object): return [prop for prop in obj.PropertiesList if obj.getGroupOfProperty(prop) == PropertyGroupAttribute] def onDocumentRestored(self, obj): - obj.setEditorMode('BitShape', 1) + # when files are shared it is essential to be able to change/set the shape file, + # otherwise the file is hard to use + # obj.setEditorMode('BitShape', 1) obj.setEditorMode('BitBody', 2) obj.setEditorMode('File', 1) obj.setEditorMode('Shape', 2) From d1363943761587664a58886ac1b52949969370d4 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Thu, 17 Dec 2020 20:27:42 -0800 Subject: [PATCH 4/7] Rearranged BitTool shape update so dependent ops don't execute with invalid tool. --- src/Mod/Path/PathScripts/PathToolBit.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathToolBit.py b/src/Mod/Path/PathScripts/PathToolBit.py index 4c502c528a..5ffe7503dd 100644 --- a/src/Mod/Path/PathScripts/PathToolBit.py +++ b/src/Mod/Path/PathScripts/PathToolBit.py @@ -288,21 +288,22 @@ class ToolBit(object): self._removeBitBody(obj) def _setupBitShape(self, obj, path=None): + PathLog.track(obj.Label) + activeDoc = FreeCAD.ActiveDocument (doc, docOpened) = self._loadBitBody(obj, path) obj.Label = doc.RootObjects[0].Label self._deleteBitSetup(obj) - obj.BitBody = obj.Document.copyObject(doc.RootObjects[0], True) + bitBody = obj.Document.copyObject(doc.RootObjects[0], True) if docOpened: FreeCAD.setActiveDocument(activeDoc.Name) FreeCAD.closeDocument(doc.Name) - if obj.BitBody.ViewObject: - obj.BitBody.ViewObject.Visibility = False - self._copyBitShape(obj) + if bitBody.ViewObject: + bitBody.ViewObject.Visibility = False - for sketch in [o for o in obj.BitBody.Group if o.TypeId == 'Sketcher::SketchObject']: + for sketch in [o for o in bitBody.Group if o.TypeId == 'Sketcher::SketchObject']: for constraint in [c for c in sketch.Constraints if c.Name != '']: typ = ParameterTypeConstraint.get(constraint.Type) PathLog.track(constraint, typ) @@ -318,6 +319,9 @@ class ToolBit(object): if constraint.Type == 'Angle': value = value * 180 / math.pi PathUtil.setProperty(obj, prop, value) + # has to happen last because it could trigger op.execute evaluations + obj.BitBody = bitBody + self._copyBitShape(obj) def getBitThumbnail(self, obj): if obj.BitShape: From c1549ba3a88327044621467c953d6fa49ecd70bf Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Thu, 17 Dec 2020 22:56:42 -0800 Subject: [PATCH 5/7] Allowing QuantitySpinBox to be reused for different attribute; using properties to get values to work around build differences. --- src/Mod/Path/PathScripts/PathGui.py | 41 ++++++++++++++++------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathGui.py b/src/Mod/Path/PathScripts/PathGui.py index d7fdd6be32..c9fee42062 100644 --- a/src/Mod/Path/PathScripts/PathGui.py +++ b/src/Mod/Path/PathScripts/PathGui.py @@ -35,13 +35,8 @@ __doc__ = "A collection of helper and utility functions for the Path GUI." def translate(context, text, disambig=None): return PySide.QtCore.QCoreApplication.translate(context, text, disambig) -LOGLEVEL = False - -if LOGLEVEL: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) -else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) +PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) +# PathLog.trackModule(PathLog.thisModule()) def updateInputField(obj, prop, widget, onBeforeChange=None): @@ -53,7 +48,7 @@ def updateInputField(obj, prop, widget, onBeforeChange=None): If onBeforeChange is specified it is called before a new value is assigned to the property. Returns True if a new value was assigned, False otherwise (new value is the same as the current). ''' - value = FreeCAD.Units.Quantity(widget.text()).Value + value = widget.property('rawValue') attr = PathUtil.getProperty(obj, prop) attrValue = attr.Value if hasattr(attr, 'Value') else attr @@ -72,10 +67,10 @@ def updateInputField(obj, prop, widget, onBeforeChange=None): isDiff = True break if noExpr: - widget.setReadOnly(False) + widget.setProperty('readonly', False) widget.setStyleSheet("color: black") else: - widget.setReadOnly(True) + widget.setProperty('readonly', True) widget.setStyleSheet("color: gray") widget.update() @@ -100,19 +95,26 @@ class QuantitySpinBox: ''' def __init__(self, widget, obj, prop, onBeforeChange=None): - self.obj = obj + PathLog.track(widget) self.widget = widget - self.prop = prop self.onBeforeChange = onBeforeChange + self.attachTo(obj, prop) - attr = PathUtil.getProperty(self.obj, self.prop) - if attr is not None: - if hasattr(attr, 'Value'): - widget.setProperty('unit', attr.getUserPreferred()[2]) - widget.setProperty('binding', "%s.%s" % (obj.Name, prop)) - self.valid = True + def attachTo(self, obj, prop = None): + '''attachTo(obj, prop=None) ... use an existing editor for the given object and property''' + self.obj = obj + self.prop = prop + if obj and prop: + attr = PathUtil.getProperty(obj, prop) + if attr is not None: + if hasattr(attr, 'Value'): + self.widget.setProperty('unit', attr.getUserPreferred()[2]) + self.widget.setProperty('binding', "%s.%s" % (obj.Name, prop)) + self.valid = True + else: + PathLog.warning(translate('PathGui', "Cannot find property %s of %s") % (prop, obj.Label)) + self.valid = False else: - PathLog.warning(translate('PathGui', "Cannot find property %s of %s") % (prop, obj.Label)) self.valid = False def expression(self): @@ -122,6 +124,7 @@ class QuantitySpinBox: return '' def setMinimum(self, quantity): + '''setMinimum(quantity) ... set the minimum''' if self.valid: value = quantity.Value if hasattr(quantity, 'Value') else quantity self.widget.setProperty('setMinimum', value) From 3b69d23571ae073ded725ad03d029cf2ddb8750c Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Thu, 17 Dec 2020 23:01:55 -0800 Subject: [PATCH 6/7] Reuse existing QuantitySpinBox'es in order to avoid segfault on focus change. --- src/Mod/Path/PathScripts/PathToolBitEdit.py | 67 ++++++++++++++++----- 1 file changed, 52 insertions(+), 15 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathToolBitEdit.py b/src/Mod/Path/PathScripts/PathToolBitEdit.py index c9cf238ebd..3488bcc95d 100644 --- a/src/Mod/Path/PathScripts/PathToolBitEdit.py +++ b/src/Mod/Path/PathScripts/PathToolBitEdit.py @@ -62,24 +62,60 @@ class ToolBitEditor(object): if self.loadbitbody: self.tool.Proxy.loadBitBody(self.tool) + # remove example widgets + layout = self.form.bitParams.layout() + for i in range(layout.rowCount() - 1, -1, -1): + layout.removeRow(i) + # used to track property widgets and editors + self.widgets = [] + self.setupTool(self.tool) self.setupAttributes(self.tool) def setupTool(self, tool): PathLog.track() + # Can't delete and add fields to the form because of dangling references in case of + # a focus change. see https://forum.freecadweb.org/viewtopic.php?f=10&t=52246#p458583 + # Instead we keep widgets once created and use them for new properties, and hide all + # which aren't being needed anymore. + + def labelText(name): + return re.sub('([A-Z][a-z]+)', r' \1', re.sub('([A-Z]+)', r' \1', name)) + layout = self.form.bitParams.layout() - for i in range(layout.rowCount() - 1, -1, -1): - layout.removeRow(i) - editor = {} ui = FreeCADGui.UiLoader() + nr = 0 + + # for all properties either assign them to existing labels and editors + # or create additional ones for them if not enough have already been + # created. for name in tool.PropertiesList: 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))) - layout.addRow(label, qsb) - self.bitEditor = editor + if nr < len(self.widgets): + PathLog.debug("re-use row: {} [{}]".format(nr, name)) + label, qsb, editor = self.widgets[nr] + label.setText(labelText(name)) + editor.attachTo(tool, name) + label.show() + qsb.show() + else: + qsb = ui.createWidget('Gui::QuantitySpinBox') + editor = PathGui.QuantitySpinBox(qsb, tool, name) + label = QtGui.QLabel(labelText(name)) + self.widgets.append((label, qsb, editor)) + PathLog.debug("create row: {} [{}]".format(nr, name)) + if nr >= layout.rowCount(): + layout.addRow(label, qsb) + nr = nr + 1 + + # hide all rows which aren't being used + for i in range(nr, len(self.widgets)): + label, qsb, editor = self.widgets[i] + label.hide() + qsb.hide() + editor.attachTo(None) + PathLog.debug(" hide row: {}".format(i)) + img = tool.Proxy.getBitThumbnail(tool) if img: self.form.image.setPixmap(QtGui.QPixmap(QtGui.QImage.fromData(img))) @@ -189,27 +225,28 @@ class ToolBitEditor(object): self.form.toolName.setText(self.tool.Label) self.form.shapePath.setText(self.tool.BitShape) - for editor in self.bitEditor: - self.bitEditor[editor].updateSpinBox() + for lbl, qsb, editor in self.widgets: + editor.updateSpinBox() def updateShape(self): PathLog.track() shapePath = str(self.form.shapePath.text()) + # Only need to go through this exercise if the shape actually changed. if self.tool.BitShape != shapePath: self.tool.BitShape = shapePath self.setupTool(self.tool) self.form.toolName.setText(self.tool.Label) - for editor in self.bitEditor: - self.bitEditor[editor].updateSpinBox() + for lbl, qsb, editor in self.widgets: + editor.updateSpinBox() def updateTool(self): PathLog.track() self.tool.Label = str(self.form.toolName.text()) self.tool.BitShape = str(self.form.shapePath.text()) - for editor in self.bitEditor: - self.bitEditor[editor].updateProperty() + for lbl, qsb, editor in self.widgets: + editor.updateProperty() # self.tool.Proxy._updateBitShape(self.tool) From 72b70c52a14a710ac4be99d1cd806dce6fa4cef5 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Thu, 17 Dec 2020 23:11:02 -0800 Subject: [PATCH 7/7] Added provision for gcc peculiarities in unit tests --- src/Mod/Path/PathTests/TestPathCore.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Mod/Path/PathTests/TestPathCore.py b/src/Mod/Path/PathTests/TestPathCore.py index dae7c091e5..34d84d6477 100644 --- a/src/Mod/Path/PathTests/TestPathCore.py +++ b/src/Mod/Path/PathTests/TestPathCore.py @@ -152,7 +152,9 @@ G0 Z0.500000 table.addTools(t2) self.assertEqual(len(table.Tools), 2) - self.assertEqual(str(table.Tools), '{1: Tool 12.7mm Drill Bit, 2: Tool my other tool}' ) + # gcc7 build needs some special treatment (makes 1L out of a 1) ... + if str(table.Tools) != '{1L: Tool 12.7mm Drill Bit, 2L: Tool my other tool}': + self.assertEqual(str(table.Tools), '{1: Tool 12.7mm Drill Bit, 2: Tool my other tool}') def test50(self): """Test Path.Length calculation"""