Merge branch 'master' into adaptive-work

This commit is contained in:
bryanbendall
2020-12-19 10:50:49 -05:00
committed by GitHub
36 changed files with 2211 additions and 1413 deletions

View File

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

View File

@@ -33,12 +33,15 @@ import os
from PySide import QtCore, QtGui
# Qt translation handling
def translate(context, text, disambig=None):
return QtCore.QCoreApplication.translate(context, text, disambig)
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
#PathLog.trackModule(PathLog.thisModule())
# PathLog.trackModule(PathLog.thisModule())
class CommandJobCreate:
'''
@@ -87,7 +90,6 @@ class CommandJobTemplateExport:
on Job creation and be available for selection.
'''
def __init__(self):
pass
@@ -111,7 +113,6 @@ class CommandJobTemplateExport:
return job
return None
def IsActive(self):
return self.GetJob() is not None
@@ -163,7 +164,12 @@ class CommandJobTemplateExport:
# setup sheet
setupSheetAttrs = None
if dialog:
setupSheetAttrs = job.Proxy.setupSheet.templateAttributes(dialog.includeSettingToolRapid(), dialog.includeSettingOperationHeights(), dialog.includeSettingOperationDepths(), dialog.includeSettingOpsSettings())
setupSheetAttrs = job.Proxy.setupSheet.templateAttributes(
dialog.includeSettingToolRapid(),
dialog.includeSettingCoolant(),
dialog.includeSettingOperationHeights(),
dialog.includeSettingOperationDepths(),
dialog.includeSettingOpsSettings())
else:
setupSheetAttrs = job.Proxy.setupSheet.templateAttributes(True, True, True)
if setupSheetAttrs:
@@ -174,10 +180,10 @@ class CommandJobTemplateExport:
with open(PathUtil.toUnicode(path), 'w') as fp:
json.dump(encoded, fp, sort_keys=True, indent=2)
if FreeCAD.GuiUp:
# register the FreeCAD command
FreeCADGui.addCommand('Path_Job', CommandJobCreate())
FreeCADGui.addCommand('Path_ExportTemplate', CommandJobTemplateExport())
FreeCAD.Console.PrintLog("Loading PathJobCmd... done\n")

View File

@@ -32,12 +32,15 @@ import os
from PySide import QtCore, QtGui
from collections import Counter
# Qt translation handling
def translate(context, text, disambig=None):
return QtCore.QCoreApplication.translate(context, text, disambig)
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
#PathLog.trackModule(PathLog.thisModule())
# PathLog.trackModule(PathLog.thisModule())
class _ItemDelegate(QtGui.QStyledItemDelegate):
@@ -148,7 +151,6 @@ class JobCreate:
self.model = QtGui.QStandardItemModel(self.dialog)
self.model.setHorizontalHeaderLabels(['Model', 'Count'])
if self.itemsSolid.hasChildren():
self.model.appendRow(self.itemsSolid)
if expandSolids or not (expand2Ds or expandJobs):
@@ -244,7 +246,7 @@ class JobCreate:
models = []
for i in range(self.itemsSolid.rowCount()):
for j in range(self.itemsSolid.child(i, 1).data(QtCore.Qt.EditRole)): # pylint: disable=unused-variable
for j in range(self.itemsSolid.child(i, 1).data(QtCore.Qt.EditRole)): # pylint: disable=unused-variable
models.append(self.itemsSolid.child(i).data(self.DataObject))
for i in range(self.items2D.rowCount()):
@@ -320,12 +322,14 @@ class JobTemplateExport:
rapidChanged = not job.SetupSheet.Proxy.hasDefaultToolRapids()
depthsChanged = not job.SetupSheet.Proxy.hasDefaultOperationDepths()
heightsChanged = not job.SetupSheet.Proxy.hasDefaultOperationHeights()
coolantChanged = not job.SetupSheet.Proxy.hasDefaultCoolantMode()
opsWithSettings = job.SetupSheet.Proxy.operationsWithSettings()
settingsChanged = rapidChanged or depthsChanged or heightsChanged or 0 != len(opsWithSettings)
settingsChanged = rapidChanged or depthsChanged or heightsChanged or coolantChanged or 0 != len(opsWithSettings)
self.dialog.settingsGroup.setChecked(settingsChanged)
self.dialog.settingToolRapid.setChecked(rapidChanged)
self.dialog.settingOperationDepths.setChecked(depthsChanged)
self.dialog.settingOperationHeights.setChecked(heightsChanged)
self.dialog.settingCoolant.setChecked(coolantChanged)
self.dialog.settingsOpsList.clear()
for op in opsWithSettings:
@@ -358,20 +362,28 @@ class JobTemplateExport:
def includeStock(self):
return self.dialog.stockGroup.isChecked()
def includeStockExtent(self):
return self.dialog.stockExtent.isChecked()
def includeStockPlacement(self):
return self.dialog.stockPlacement.isChecked()
def includeSettings(self):
return self.dialog.settingsGroup.isChecked()
def includeSettingToolRapid(self):
return self.dialog.settingToolRapid.isChecked()
def includeSettingOperationHeights(self):
return self.dialog.settingOperationHeights.isChecked()
def includeSettingOperationDepths(self):
return self.dialog.settingOperationDepths.isChecked()
def includeSettingCoolant(self):
return self.dialog.settingCoolant.isChecked()
def includeSettingOpsSettings(self):
ops = []
for i in range(self.dialog.settingsOpsList.count()):
@@ -382,4 +394,3 @@ class JobTemplateExport:
def exec_(self):
return self.dialog.exec_()

View File

@@ -262,7 +262,7 @@ class ObjectProfile(PathAreaOp.ObjectOp):
return params
def areaOpAreaParamsExpandProfile(self, obj, isHole):
'''areaOpPathParamsExpandProfile(obj, isHole) ... return dictionary with area parameters for expaned profile'''
'''areaOpPathParamsExpandProfile(obj, isHole) ... return dictionary with area parameters for expanded profile'''
params = {}
params['Fill'] = 1

View File

@@ -35,11 +35,13 @@ __doc__ = "A container for all default values and job specific configuration val
_RegisteredOps = {}
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
#PathLog.trackModule(PathLog.thisModule())
# PathLog.trackModule(PathLog.thisModule())
def translate(context, text, disambig=None):
return PySide.QtCore.QCoreApplication.translate(context, text, disambig)
class Template:
# pylint: disable=no-init
@@ -59,7 +61,7 @@ class Template:
def _traverseTemplateAttributes(attrs, codec):
coded = {}
for key,value in PathUtil.keyValueIter(attrs):
for key, value in PathUtil.keyValueIter(attrs):
if type(value) == dict:
PathLog.debug("%s is a dict" % key)
coded[key] = _traverseTemplateAttributes(value, codec)
@@ -74,6 +76,7 @@ def _traverseTemplateAttributes(attrs, codec):
coded[key] = value
return coded
class SetupSheet:
'''Property container object used by a Job to hold global reference values. '''
@@ -92,20 +95,20 @@ class SetupSheet:
def __init__(self, obj):
self.obj = obj
obj.addProperty('App::PropertySpeed', 'VertRapid', 'ToolController', translate('PathSetupSheet', 'Default speed for horizontal rapid moves.'))
obj.addProperty('App::PropertySpeed', 'VertRapid', 'ToolController', translate('PathSetupSheet', 'Default speed for horizontal rapid moves.'))
obj.addProperty('App::PropertySpeed', 'HorizRapid', 'ToolController', translate('PathSetupSheet', 'Default speed for vertical rapid moves.'))
obj.addProperty('App::PropertyStringList', 'CoolantModes', 'CoolantMode', translate('PathSetupSheet', 'Coolant Modes'))
obj.addProperty('App::PropertyEnumeration', 'CoolantMode', 'CoolantMode', translate('PathSetupSheet', 'Default coolant mode.'))
obj.addProperty('App::PropertyLength', 'SafeHeightOffset', 'OperationHeights', translate('PathSetupSheet', 'The usage of this field depends on SafeHeightExpression - by default its value is added to StartDepth and used for SafeHeight of an operation.'))
obj.addProperty('App::PropertyString', 'SafeHeightExpression', 'OperationHeights', translate('PathSetupSheet', 'Expression set for the SafeHeight of new operations.'))
obj.addProperty('App::PropertyLength', 'ClearanceHeightOffset', 'OperationHeights', translate('PathSetupSheet', 'The usage of this field depends on ClearanceHeightExpression - by default is value is added to StartDepth and used for ClearanceHeight of an operation.'))
obj.addProperty('App::PropertyLength', 'SafeHeightOffset', 'OperationHeights', translate('PathSetupSheet', 'The usage of this field depends on SafeHeightExpression - by default its value is added to StartDepth and used for SafeHeight of an operation.'))
obj.addProperty('App::PropertyString', 'SafeHeightExpression', 'OperationHeights', translate('PathSetupSheet', 'Expression set for the SafeHeight of new operations.'))
obj.addProperty('App::PropertyLength', 'ClearanceHeightOffset', 'OperationHeights', translate('PathSetupSheet', 'The usage of this field depends on ClearanceHeightExpression - by default is value is added to StartDepth and used for ClearanceHeight of an operation.'))
obj.addProperty('App::PropertyString', 'ClearanceHeightExpression', 'OperationHeights', translate('PathSetupSheet', 'Expression set for the ClearanceHeight of new operations.'))
obj.addProperty('App::PropertyString', 'StartDepthExpression', 'OperationDepths', translate('PathSetupSheet', 'Expression used for StartDepth of new operations.'))
obj.addProperty('App::PropertyString', 'FinalDepthExpression', 'OperationDepths', translate('PathSetupSheet', 'Expression used for FinalDepth of new operations.'))
obj.addProperty('App::PropertyString', 'StepDownExpression', 'OperationDepths', translate('PathSetupSheet', 'Expression used for StepDown of new operations.'))
obj.addProperty('App::PropertyString', 'StepDownExpression', 'OperationDepths', translate('PathSetupSheet', 'Expression used for StepDown of new operations.'))
obj.SafeHeightOffset = self.decodeAttributeString(self.DefaultSafeHeightOffset)
obj.ClearanceHeightOffset = self.decodeAttributeString(self.DefaultClearanceHeightOffset)
@@ -154,31 +157,38 @@ class SetupSheet:
return False
return True
def hasDefaultCoolantMode(self):
return self.obj.CoolantMode == "None"
def setFromTemplate(self, attrs):
'''setFromTemplate(attrs) ... sets the default values from the given dictionary.'''
for name in Template.All:
if attrs.get(name) is not None:
setattr(self.obj, name, attrs[name])
for opName,op in PathUtil.keyValueIter(_RegisteredOps):
for opName, op in PathUtil.keyValueIter(_RegisteredOps):
opSetting = attrs.get(opName)
if opSetting is not None:
prototype = op.prototype(opName)
for propName in op.properties():
value = opSetting.get(propName)
if not value is None:
if value is not None:
prop = prototype.getProperty(propName)
propertyName = OpPropertyName(opName, propName)
propertyGroup = OpPropertyGroup(opName)
prop.setupProperty(self.obj, propertyName, propertyGroup, prop.valueFromString(value))
def templateAttributes(self, includeRapids=True, includeCoolantMode=True, includeHeights=True, includeDepths=True, includeOps=None):
def templateAttributes(self,
includeRapids=True,
includeCoolantMode=True,
includeHeights=True,
includeDepths=True,
includeOps=None):
'''templateAttributes(includeRapids, includeHeights, includeDepths) ... answers a dictionary with the default values.'''
attrs = {}
if includeRapids:
attrs[Template.VertRapid] = self.obj.VertRapid.UserString
attrs[Template.VertRapid] = self.obj.VertRapid.UserString
attrs[Template.HorizRapid] = self.obj.HorizRapid.UserString
if includeCoolantMode:
@@ -232,6 +242,7 @@ class SetupSheet:
def encodeAttributeString(self, attr):
'''encodeAttributeString(attr) ... return the encoded string of a template attribute.'''
return PathUtil.toUnicode(attr.replace(self.expressionReference(), self.TemplateReference))
def decodeAttributeString(self, attr):
'''decodeAttributeString(attr) ... return the decoded string of a template attribute.'''
return PathUtil.toUnicode(attr.replace(self.TemplateReference, self.expressionReference()))
@@ -247,7 +258,7 @@ class SetupSheet:
def operationsWithSettings(self):
'''operationsWithSettings() ... returns a list of operations which currently have some settings defined.'''
ops = []
for name,value in PathUtil.keyValueIter(_RegisteredOps):
for name, value in PathUtil.keyValueIter(_RegisteredOps):
for prop in value.registeredPropertyNames(name):
if hasattr(self.obj, prop):
ops.append(name)
@@ -262,9 +273,9 @@ class SetupSheet:
propName = OpPropertyName(opName, prop)
if hasattr(self.obj, propName):
setattr(obj, prop, getattr(self.obj, propName))
except Exception: # pylint: disable=broad-except
except Exception:
PathLog.info("SetupSheet has no support for {}".format(opName))
#traceback.print_exc()
# traceback.print_exc()
def onDocumentRestored(self, obj):
@@ -272,16 +283,17 @@ class SetupSheet:
obj.addProperty('App::PropertyStringList', 'CoolantModes', 'CoolantMode', translate('PathSetupSheet', 'Coolant Modes'))
obj.CoolantModes = self.DefaultCoolantModes
if not hasattr(obj, 'CoolantMode'):
obj.addProperty('App::PropertyEnumeration', 'CoolantMode', 'CoolantMode', translate('PathSetupSheet', 'Default coolant mode.'))
obj.CoolantMode = self.DefaultCoolantModes
def Create(name = 'SetupSheet'):
def Create(name='SetupSheet'):
obj = FreeCAD.ActiveDocument.addObject('App::FeaturePython', name)
obj.Proxy = SetupSheet(obj)
return obj
class _RegisteredOp(object):
def __init__(self, factory, properties):
@@ -296,14 +308,19 @@ class _RegisteredOp(object):
self.factory("OpPrototype.%s" % name, ptt)
return ptt
def RegisterOperation(name, objFactory, setupProperties):
global _RegisteredOps # pylint: disable=global-statement
global _RegisteredOps
_RegisteredOps[name] = _RegisteredOp(objFactory, setupProperties)
def OpNamePrefix(name):
return name.replace('Path', '').replace(' ', '').replace('_', '')
def OpPropertyName(opName, propName):
return "{}{}".format(OpNamePrefix(opName), propName)
def OpPropertyGroup(opName):
return "Op {}".format(opName)

View File

@@ -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)
@@ -252,7 +254,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)
@@ -286,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)
@@ -316,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:

View File

@@ -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,25 +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()
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())
# 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)