diff --git a/src/Mod/Path/PathScripts/PathPropertyContainer.py b/src/Mod/Path/PathScripts/PathPropertyContainer.py index a1b52b8e08..baf033dfba 100644 --- a/src/Mod/Path/PathScripts/PathPropertyContainer.py +++ b/src/Mod/Path/PathScripts/PathPropertyContainer.py @@ -32,6 +32,31 @@ def translate(context, text, disambig=None): return PySide.QtCore.QCoreApplication.translate(context, text, disambig) +SupportedPropertyType = { + 'Angle' : 'App::PropertyAngle', + 'Bool' : 'App::PropertyBool', + 'Distance' : 'App::PropertyDistance', + # 'Enumeration' : 'App::PropertyEnumeration', + 'File' : 'App::PropertyFile', + 'Float' : 'App::PropertyFloat', + 'Integer' : 'App::PropertyInteger', + 'Length' : 'App::PropertyLength', + 'Percent' : 'App::PropertyPercent', + 'String' : 'App::PropertyString', + } + +def getPropertyType(o): + if type(o) == str: + return SupportedPropertyType['String'] + if type(o) == bool: + return SupportedPropertyType['Bool'] + if type(o) == int: + return SupportedPropertyType['Integer'] + if type(o) == float: + return SupportedPropertyType['Float'] + if type(o) == FreeCAD.Units.Quantity: + return SupportedPropertyType[o.Unit.Type] + class PropertyContainer(object): '''Property container object.''' @@ -39,22 +64,17 @@ class PropertyContainer(object): CustomPropertyGroupDefault = 'User' def __init__(self, obj): - self.obj = obj obj.addProperty('App::PropertyStringList', self.CustomPropertyGroups, 'Base', PySide.QtCore.QT_TRANSLATE_NOOP('PathPropertyContainer', 'List of custom property groups')) - obj.setEditorMode(self.CustomPropertyGroups, 2) # hide + self.onDocumentRestored(obj) def __getstate__(self): return None def __setstate__(self, state): - for obj in FreeCAD.ActiveDocument.Objects: - if hasattr(obj, 'Proxy') and obj.Proxy == self: - self.obj = obj - obj.setEditorMode(self.CustomPropertyGroups, 2) # hide - break return None def onDocumentRestored(self, obj): + self.obj = obj obj.setEditorMode(self.CustomPropertyGroups, 2) # hide def getCustomProperties(self): diff --git a/src/Mod/Path/PathScripts/PathPropertyContainerGui.py b/src/Mod/Path/PathScripts/PathPropertyContainerGui.py index c5c799d19c..d12ee29466 100644 --- a/src/Mod/Path/PathScripts/PathPropertyContainerGui.py +++ b/src/Mod/Path/PathScripts/PathPropertyContainerGui.py @@ -43,19 +43,6 @@ PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) -SupportedPropertyType = { - 'Angle' : 'App::PropertyAngle', - 'Bool' : 'App::PropertyBool', - 'Distance' : 'App::PropertyDistance', - # 'Enumeration' : 'App::PropertyEnumeration', - 'File' : 'App::PropertyFile', - 'Float' : 'App::PropertyFloat', - 'Integer' : 'App::PropertyInteger', - 'Length' : 'App::PropertyLength', - 'Percent' : 'App::PropertyPercent', - 'String' : 'App::PropertyString', - } - class ViewProvider(object): '''ViewProvider for a PropertyContainer. It's sole job is to provide an icon and invoke the TaskPanel on edit.''' @@ -148,9 +135,9 @@ class PropertyCreate(object): if grp: self.form.propertyGroup.setCurrentText(grp) - for t in sorted(SupportedPropertyType): + for t in sorted(PathPropertyContainer.SupportedPropertyType): self.form.propertyType.addItem(t) - if SupportedPropertyType[t] == typ: + if PathPropertyContainer.SupportedPropertyType[t] == typ: typ = t if typ: self.form.propertyType.setCurrentText(typ) @@ -176,7 +163,7 @@ class PropertyCreate(object): def propertyGroup(self): return self.form.propertyGroup.currentText().strip() def propertyType(self): - return SupportedPropertyType[self.form.propertyType.currentText()].strip() + return PathPropertyContainer.SupportedPropertyType[self.form.propertyType.currentText()].strip() def propertyInfo(self): return self.form.propertyInfo.toPlainText().strip() def createAnother(self): diff --git a/src/Mod/Path/PathScripts/PathToolBit.py b/src/Mod/Path/PathScripts/PathToolBit.py index 5ffe7503dd..5e63902c34 100644 --- a/src/Mod/Path/PathScripts/PathToolBit.py +++ b/src/Mod/Path/PathScripts/PathToolBit.py @@ -24,6 +24,7 @@ import FreeCAD import PathScripts.PathGeom as PathGeom import PathScripts.PathLog as PathLog import PathScripts.PathPreferences as PathPreferences +import PathScripts.PathPropertyContainer as PathPropertyContainer import PathScripts.PathSetupSheetOpPrototype as PathSetupSheetOpPrototype import PathScripts.PathUtil as PathUtil import PySide @@ -42,8 +43,10 @@ __author__ = "sliptonic (Brad Collette)" __url__ = "https://www.freecadweb.org" __doc__ = "Class to deal with and represent a tool bit." -# PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) -# PathLog.trackModule() +_DebugFindTool = True + +PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) +PathLog.trackModule() def translate(context, text, disambig=None): @@ -58,7 +61,8 @@ ParameterTypeConstraint = { 'Radius': 'App::PropertyLength'} -def _findTool(path, typ, dbg=False): +def _findTool(path, typ, dbg=_DebugFindTool): + PathLog.track(path) if os.path.exists(path): # absolute reference if dbg: PathLog.debug("Found {} at {}".format(typ, path)) @@ -67,7 +71,7 @@ def _findTool(path, typ, dbg=False): def searchFor(pname, fname): # PathLog.debug("pname: {} fname: {}".format(pname, fname)) if dbg: - PathLog.debug("Looking for {}".format(pname)) + PathLog.debug("Looking for {} in {}".format(pname, fname)) if fname: for p in PathPreferences.searchPathsTool(typ): PathLog.track(p) @@ -174,6 +178,9 @@ class ToolBit(object): translate('PathToolBit', 'The file of the tool')) obj.addProperty('App::PropertyString', 'ShapeName', 'Base', translate('PathToolBit', 'The name of the shape file')) + obj.addProperty('App::PropertyStringList', 'BitPropertyNames', 'Base', + translate('PathToolBit', 'List of all properties inherited from the bit')) + if shapeFile is None: obj.BitShape = 'endmill.fcstd' self._setupBitShape(obj) @@ -193,9 +200,6 @@ class ToolBit(object): break return None - def propertyNamesBit(self, obj): - return [prop for prop in obj.PropertiesList if obj.getGroupOfProperty(prop) == PropertyGroupBit] - def propertyNamesAttribute(self, obj): return [prop for prop in obj.PropertiesList if obj.getGroupOfProperty(prop) == PropertyGroupAttribute] @@ -206,8 +210,9 @@ class ToolBit(object): obj.setEditorMode('BitBody', 2) obj.setEditorMode('File', 1) obj.setEditorMode('Shape', 2) + obj.setEditorMode('BitPropertyNames', 2) - for prop in self.propertyNamesBit(obj): + for prop in obj.BitPropertyNames: obj.setEditorMode(prop, 1) # I currently don't see why these need to be read-only # for prop in self.propertyNamesAttribute(obj): @@ -227,12 +232,9 @@ class ToolBit(object): def _updateBitShape(self, obj, properties=None): if obj.BitBody is not None: - if not properties: - properties = self.propertyNamesBit(obj) - for prop in properties: - for sketch in [o for o in obj.BitBody.Group if o.TypeId == 'Sketcher::SketchObject']: - PathLog.track(obj.Label, sketch.Label, prop) - updateConstraint(sketch, prop, obj.getPropertyByName(prop)) + for attributes in [o for o in obj.BitBody.Group if hasattr(o, 'Proxy') and hasattr(o.Proxy, 'getCustomProperties')]: + for prop in attributes.Proxy.getCustomProperties(): + setattr(attributes, prop, obj.getPropertyByName(prop)) self._copyBitShape(obj) def _copyBitShape(self, obj): @@ -243,6 +245,7 @@ class ToolBit(object): obj.Shape = Part.Shape() def _loadBitBody(self, obj, path=None): + PathLog.track(obj.Label, path) p = path if path else obj.BitShape docOpened = False doc = None @@ -254,9 +257,12 @@ class ToolBit(object): p = findShape(p) if not path and p != obj.BitShape: obj.BitShape = p + PathLog.debug("ToolBit {} using shape file: {}".format(obj.Label, p)) doc = FreeCAD.openDocument(p, True) obj.ShapeName = doc.Name docOpened = True + else: + PathLog.debug("ToolBit {} already open: {}".format(obj.Label, doc)) return (doc, docOpened) def _removeBitBody(self, obj): @@ -269,7 +275,7 @@ class ToolBit(object): PathLog.track(obj.Label) self._removeBitBody(obj) self._copyBitShape(obj) - for prop in self.propertyNamesBit(obj): + for prop in obj.BitPropertyNames: obj.removeProperty(prop) def loadBitBody(self, obj, force=False): @@ -296,6 +302,10 @@ class ToolBit(object): obj.Label = doc.RootObjects[0].Label self._deleteBitSetup(obj) bitBody = obj.Document.copyObject(doc.RootObjects[0], True) + + for o in doc.RootObjects[0].Group: + PathLog.debug("..... {}: {}".format(o.Label, o.Name)) + if docOpened: FreeCAD.setActiveDocument(activeDoc.Name) FreeCAD.closeDocument(doc.Name) @@ -303,23 +313,35 @@ class ToolBit(object): if bitBody.ViewObject: bitBody.ViewObject.Visibility = False - 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) - if typ is not None: - parts = [p.strip() for p in constraint.Name.split(';')] - prop = parts[0] - desc = '' - if len(parts) > 1: - desc = parts[1] - obj.addProperty(typ, prop, PropertyGroupBit, desc) - obj.setEditorMode(prop, 1) - value = constraint.Value - if constraint.Type == 'Angle': - value = value * 180 / math.pi - PathUtil.setProperty(obj, prop, value) + PathLog.debug("bitBody.{} ({}): {}".format(bitBody.Label, bitBody.Name, type(bitBody))) + + def isAttributes(o): + if not hasattr(o, 'Proxy'): + PathLog.debug(" {} has not Proxy ({})".format(o.Label, type(o))) + return False + if not hasattr(o.Proxy, 'getCustomProperties'): + PathLog.debug(" {}.Proxy has no getCustomProperties ({})".format(o.Label, type(o.Proxy))) + return False + PathLog.debug(" {} <-".format(o.Label)) + return True + + propNames = [] + for attributes in [o for o in bitBody.Group if isAttributes(o)]: + PathLog.debug("Process properties from {}".format(attributes.Label)) + for prop in attributes.Proxy.getCustomProperties(): + # extract property parameters and values so it can be copied + src = attributes.getPropertyByName(prop) + typ = PathPropertyContainer.getPropertyType(src) + grp = attributes.getGroupOfProperty(prop) + dsc = attributes.getDocumentationOfProperty(prop) + + obj.addProperty(typ, prop, grp, dsc) + obj.setEditorMode(prop, 1) + PathUtil.setProperty(obj, prop, src) + propNames.append(prop) + # has to happen last because it could trigger op.execute evaluations + obj.BitPropertyNames = propNames obj.BitBody = bitBody self._copyBitShape(obj) @@ -360,7 +382,7 @@ class ToolBit(object): else: attrs['shape'] = findRelativePathShape(obj.BitShape) params = {} - for name in self.propertyNamesBit(obj): + for name in obj.BitPropertyNames: params[name] = PathUtil.getPropertyValueString(obj, name) attrs['parameter'] = params params = {} @@ -435,6 +457,7 @@ class ToolBitFactory(object): return obj def CreateFrom(self, path, name='ToolBit'): + PathLog.track(name, path) try: data = Declaration(path) bit = Factory.CreateFromAttrs(data, name) @@ -445,6 +468,7 @@ class ToolBitFactory(object): raise def Create(self, name='ToolBit', shapeFile=None): + PathLog.track(name, shapeFile) obj = FreeCAD.ActiveDocument.addObject('Part::FeaturePython', name) obj.Proxy = ToolBit(obj, shapeFile) return obj diff --git a/src/Mod/Path/PathScripts/PathToolBitEdit.py b/src/Mod/Path/PathScripts/PathToolBitEdit.py index 3488bcc95d..b2cc823d76 100644 --- a/src/Mod/Path/PathScripts/PathToolBitEdit.py +++ b/src/Mod/Path/PathScripts/PathToolBitEdit.py @@ -84,32 +84,32 @@ class ToolBitEditor(object): layout = self.form.bitParams.layout() 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: - 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 + for nr, name in enumerate(tool.BitPropertyNames): + 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, type(qsb))) + if hasattr(qsb, 'editingFinished'): + qsb.editingFinished.connect(self.updateTool) + + if nr >= layout.rowCount(): + layout.addRow(label, qsb) # hide all rows which aren't being used - for i in range(nr, len(self.widgets)): + for i in range(len(tool.BitPropertyNames), len(self.widgets)): label, qsb, editor = self.widgets[i] label.hide() qsb.hide() @@ -242,13 +242,18 @@ class ToolBitEditor(object): def updateTool(self): PathLog.track() - self.tool.Label = str(self.form.toolName.text()) - self.tool.BitShape = str(self.form.shapePath.text()) + + label = str(self.form.toolName.text()) + shape = str(self.form.shapePath.text()) + if self.tool.Label != label: + self.tool.Label = label + if self.tool.BitShape != shape: + self.tool.BitShape = shape for lbl, qsb, editor in self.widgets: editor.updateProperty() - # self.tool.Proxy._updateBitShape(self.tool) + self.tool.Proxy._updateBitShape(self.tool) def refresh(self): PathLog.track() diff --git a/src/Mod/Path/PathScripts/PathToolBitGui.py b/src/Mod/Path/PathScripts/PathToolBitGui.py index 1fe1df5370..c36bb83ec0 100644 --- a/src/Mod/Path/PathScripts/PathToolBitGui.py +++ b/src/Mod/Path/PathScripts/PathToolBitGui.py @@ -174,8 +174,8 @@ class ToolBitGuiFactory(PathToolBit.ToolBitFactory): '''Create(name = 'ToolBit') ... creates a new tool bit. It is assumed the tool will be edited immediately so the internal bit body is still attached.''' - FreeCAD.ActiveDocument.openTransaction(translate('PathToolBit', - 'Create ToolBit')) + PathLog.track(name, shapeFile) + FreeCAD.ActiveDocument.openTransaction(translate('PathToolBit', 'Create ToolBit')) tool = PathToolBit.ToolBitFactory.Create(self, name, shapeFile) PathIconViewProvider.Attach(tool.ViewObject, name) FreeCAD.ActiveDocument.commitTransaction() diff --git a/src/Mod/Path/Tools/Shape/endmill.fcstd b/src/Mod/Path/Tools/Shape/endmill.fcstd index 98888db1e3..c5be12d745 100644 Binary files a/src/Mod/Path/Tools/Shape/endmill.fcstd and b/src/Mod/Path/Tools/Shape/endmill.fcstd differ