Using property container for endmill attributes.

This commit is contained in:
Markus Lampert
2020-12-20 18:14:29 -08:00
parent 8b2bfaa322
commit ad6d463e36
6 changed files with 116 additions and 80 deletions

View File

@@ -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):

View File

@@ -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):

View File

@@ -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

View File

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

View File

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