Merge branch 'master' into posttodxf

This commit is contained in:
sliptonic
2020-12-19 15:40:16 -06:00
committed by GitHub
66 changed files with 2517 additions and 1524 deletions

View File

@@ -23,6 +23,7 @@
import FreeCAD
import PathScripts.PathEngraveBase as PathEngraveBase
import PathScripts.PathGeom as PathGeom
import PathScripts.PathLog as PathLog
import PathScripts.PathOp as PathOp
import PathScripts.PathOpTools as PathOpTools
@@ -48,35 +49,44 @@ def translate(context, text, disambig=None):
return QtCore.QCoreApplication.translate(context, text, disambig)
def toolDepthAndOffset(width, extraDepth, tool):
def toolDepthAndOffset(width, extraDepth, tool, printInfo):
'''toolDepthAndOffset(width, extraDepth, tool) ... return tuple for given\n
parameters.'''
if not hasattr(tool, 'Diameter'):
raise ValueError('Deburr requires tool with diameter\n')
if not hasattr(tool, 'CuttingEdgeAngle'):
angle = 180
FreeCAD.Console.PrintMessage('The selected tool has No CuttingEdgeAngle property. Assuming Endmill\n')
else:
suppressInfo = False
if hasattr(tool, 'CuttingEdgeAngle'):
angle = float(tool.CuttingEdgeAngle)
if not hasattr(tool, 'FlatRadius'):
toolOffset = float(tool.Diameter / 2)
FreeCAD.Console.PrintMessage('The selected tool has no FlatRadius property. Using Diameter\n')
if PathGeom.isRoughly(angle, 180) or PathGeom.isRoughly(angle, 0):
angle = 180
toolOffset = float(tool.Diameter) / 2
else:
if hasattr(tool, 'TipDiameter'):
toolOffset = float(tool.TipDiameter) / 2
elif hasattr(tool, 'FlatRadius'):
toolOffset = float(tool.FlatRadius)
else:
toolOffset = 0.0
if printInfo and not suppressInfo:
FreeCAD.Console.PrintMessage(translate('PathDeburr', "The selected tool has no FlatRadius and no TipDiameter property. Assuming {}\n").format("Endmill" if angle == 180 else "V-Bit"))
suppressInfo = True
else:
toolOffset = float(tool.FlatRadius)
if angle == 0:
angle = 180
toolOffset = float(tool.Diameter) / 2
if printInfo:
FreeCAD.Console.PrintMessage(translate('PathDeburr', 'The selected tool has no CuttingEdgeAngle property. Assuming Endmill\n'))
suppressInfo = True
tan = math.tan(math.radians(angle / 2))
toolDepth = 0 if 0 == tan else width / tan
toolDepth = 0 if PathGeom.isRoughly(tan, 0) else width / tan
depth = toolDepth + extraDepth
extraOffset = float(tool.Diameter) / 2 - width if angle == 180 else extraDepth / tan
extraOffset = -width if angle == 180 else (extraDepth / tan)
offset = toolOffset + extraOffset
return (depth, offset)
return (depth, offset, suppressInfo)
class ObjectDeburr(PathEngraveBase.ObjectOp):
@@ -110,8 +120,11 @@ class ObjectDeburr(PathEngraveBase.ObjectOp):
def opExecute(self, obj):
PathLog.track(obj.Label)
if not hasattr(self, 'printInfo'):
self.printInfo = True
try:
(depth, offset) = toolDepthAndOffset(obj.Width.Value, obj.ExtraDepth.Value, self.tool)
(depth, offset, suppressInfo) = toolDepthAndOffset(obj.Width.Value, obj.ExtraDepth.Value, self.tool, self.printInfo)
self.printInfo = not suppressInfo
except ValueError as e:
msg = "{} \n No path will be generated".format(e)
raise ValueError(msg)

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

@@ -41,12 +41,8 @@ __doc__ = "Class and implementation of Mill Facing operation."
__contributors__ = "russ4262 (Russell Johnson)"
DEBUG = False
if DEBUG:
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
PathLog.trackModule()
else:
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
# PathLog.trackModule()
# Qt translation handling
@@ -82,7 +78,7 @@ class ObjectFace(PathPocketBase.ObjectPocket):
obj.OpStartDepth = job.Stock.Shape.BoundBox.ZMax
if len(obj.Base) >= 1:
print('processing')
PathLog.debug('processing')
sublist = []
for i in obj.Base:
o = i[0]

View File

@@ -27,6 +27,7 @@ from PySide import QtCore
import Path
import PathScripts.PathGeom as PathGeom
import PathScripts.PathLog as PathLog
import PathScripts.PathPreferences as PathPreferences
import PathScripts.PathUtil as PathUtil
import PathScripts.PathUtils as PathUtils
from PathScripts.PathUtils import waiting_effects
@@ -547,11 +548,11 @@ class ObjectOp(object):
hRapidrate = tc.HorizRapid.Value
vRapidrate = tc.VertRapid.Value
if hFeedrate == 0 or vFeedrate == 0:
if (hFeedrate == 0 or vFeedrate == 0) and not PathPreferences.suppressAllSpeedsWarning():
PathLog.warning(translate("Path", "Tool Controller feedrates required to calculate the cycle time."))
return translate('Path', 'Feedrate Error')
if hRapidrate == 0 or vRapidrate == 0:
if (hRapidrate == 0 or vRapidrate == 0) and not PathPreferences.suppressRapidSpeedsWarning():
PathLog.warning(translate("Path", "Add Tool Controller Rapid Speeds on the SetupSheet for more accurate cycle times."))
# Get the cycle time in seconds

View File

@@ -29,35 +29,39 @@ import PathScripts.PathLog as PathLog
# PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
# PathLog.trackModule()
DefaultFilePath = "DefaultFilePath"
DefaultJobTemplate = "DefaultJobTemplate"
DefaultStockTemplate = "DefaultStockTemplate"
DefaultTaskPanelLayout = "DefaultTaskPanelLayout"
DefaultFilePath = "DefaultFilePath"
DefaultJobTemplate = "DefaultJobTemplate"
DefaultStockTemplate = "DefaultStockTemplate"
DefaultTaskPanelLayout = "DefaultTaskPanelLayout"
PostProcessorDefault = "PostProcessorDefault"
PostProcessorDefaultArgs = "PostProcessorDefaultArgs"
PostProcessorBlacklist = "PostProcessorBlacklist"
PostProcessorOutputFile = "PostProcessorOutputFile"
PostProcessorOutputPolicy = "PostProcessorOutputPolicy"
PostProcessorDefault = "PostProcessorDefault"
PostProcessorDefaultArgs = "PostProcessorDefaultArgs"
PostProcessorBlacklist = "PostProcessorBlacklist"
PostProcessorOutputFile = "PostProcessorOutputFile"
PostProcessorOutputPolicy = "PostProcessorOutputPolicy"
LastPathToolBit = "LastPathToolBit"
LastPathToolLibrary = "LastPathToolLibrary"
LastPathToolShape = "LastPathToolShape"
LastPathToolTable = "LastPathToolTable"
LastPathToolBit = "LastPathToolBit"
LastPathToolLibrary = "LastPathToolLibrary"
LastPathToolShape = "LastPathToolShape"
LastPathToolTable = "LastPathToolTable"
LastFileToolBit = "LastFileToolBit"
LastFileToolLibrary = "LastFileToolLibrary"
LastFileToolShape = "LastFileToolShape"
LastFileToolBit = "LastFileToolBit"
LastFileToolLibrary = "LastFileToolLibrary"
LastFileToolShape = "LastFileToolShape"
UseLegacyTools = "UseLegacyTools"
UseAbsoluteToolPaths = "UseAbsoluteToolPaths"
OpenLastLibrary = "OpenLastLibrary"
UseLegacyTools = "UseLegacyTools"
UseAbsoluteToolPaths = "UseAbsoluteToolPaths"
OpenLastLibrary = "OpenLastLibrary"
# Linear tolerance to use when generating Paths, eg when tessellating geometry
GeometryTolerance = "GeometryTolerance"
LibAreaCurveAccuracy = "LibAreaCurveAccuarcy"
GeometryTolerance = "GeometryTolerance"
LibAreaCurveAccuracy = "LibAreaCurveAccuarcy"
EnableExperimentalFeatures = "EnableExperimentalFeatures"
WarningSuppressRapidSpeeds = "WarningSuppressRapidSpeeds"
WarningSuppressAllSpeeds = "WarningSuppressAllSpeeds"
WarningSuppressSelectionMode = "WarningSuppressSelectionMode"
WarningSuppressOpenCamLib = "WarningSuppressOpenCamLib"
EnableExperimentalFeatures = "EnableExperimentalFeatures"
def preferences():
@@ -259,6 +263,18 @@ def setDefaultTaskPanelLayout(style):
def experimentalFeaturesEnabled():
return preferences().GetBool(EnableExperimentalFeatures, False)
def suppressAllSpeedsWarning():
return preferences().GetBool(WarningSuppressAllSpeeds, True)
def suppressRapidSpeedsWarning():
return suppressAllSpeedsWarning() or preferences().GetBool(WarningSuppressRapidSpeeds, True)
def suppressSelectionModeWarning():
return preferences().GetBool(WarningSuppressSelectionMode, True)
def suppressOpenCamLibWarning():
return preferences().GetBool(WarningSuppressOpenCamLib, True)
def lastFileToolLibrary():
filename = preferences().GetString(LastFileToolLibrary)

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

@@ -25,6 +25,7 @@
import FreeCAD
import FreeCADGui
import PathScripts.PathLog as PathLog
import PathScripts.PathPreferences as PathPreferences
import PathScripts.PathUtils as PathUtils
import math
@@ -286,52 +287,62 @@ class ALLGate(PathBaseGate):
def contourselect():
FreeCADGui.Selection.addSelectionGate(CONTOURGate())
FreeCAD.Console.PrintWarning("Contour Select Mode\n")
if not PathPreferences.suppressSelectionModeWarning():
FreeCAD.Console.PrintWarning("Contour Select Mode\n")
def eselect():
FreeCADGui.Selection.addSelectionGate(EGate())
FreeCAD.Console.PrintWarning("Edge Select Mode\n")
if not PathPreferences.suppressSelectionModeWarning():
FreeCAD.Console.PrintWarning("Edge Select Mode\n")
def drillselect():
FreeCADGui.Selection.addSelectionGate(DRILLGate())
FreeCAD.Console.PrintWarning("Drilling Select Mode\n")
if not PathPreferences.suppressSelectionModeWarning():
FreeCAD.Console.PrintWarning("Drilling Select Mode\n")
def engraveselect():
FreeCADGui.Selection.addSelectionGate(ENGRAVEGate())
FreeCAD.Console.PrintWarning("Engraving Select Mode\n")
if not PathPreferences.suppressSelectionModeWarning():
FreeCAD.Console.PrintWarning("Engraving Select Mode\n")
def fselect():
FreeCADGui.Selection.addSelectionGate(FACEGate()) # Was PROFILEGate()
FreeCAD.Console.PrintWarning("Profiling Select Mode\n")
if not PathPreferences.suppressSelectionModeWarning():
FreeCAD.Console.PrintWarning("Profiling Select Mode\n")
def chamferselect():
FreeCADGui.Selection.addSelectionGate(CHAMFERGate())
FreeCAD.Console.PrintWarning("Deburr Select Mode\n")
if not PathPreferences.suppressSelectionModeWarning():
FreeCAD.Console.PrintWarning("Deburr Select Mode\n")
def profileselect():
FreeCADGui.Selection.addSelectionGate(PROFILEGate())
FreeCAD.Console.PrintWarning("Profiling Select Mode\n")
if not PathPreferences.suppressSelectionModeWarning():
FreeCAD.Console.PrintWarning("Profiling Select Mode\n")
def pocketselect():
FreeCADGui.Selection.addSelectionGate(POCKETGate())
FreeCAD.Console.PrintWarning("Pocketing Select Mode\n")
if not PathPreferences.suppressSelectionModeWarning():
FreeCAD.Console.PrintWarning("Pocketing Select Mode\n")
def adaptiveselect():
FreeCADGui.Selection.addSelectionGate(ADAPTIVEGate())
FreeCAD.Console.PrintWarning("Adaptive Select Mode\n")
if not PathPreferences.suppressSelectionModeWarning():
FreeCAD.Console.PrintWarning("Adaptive Select Mode\n")
def slotselect():
FreeCADGui.Selection.addSelectionGate(ALLGate())
FreeCAD.Console.PrintWarning("Slot Cutter Select Mode\n")
if not PathPreferences.suppressSelectionModeWarning():
FreeCAD.Console.PrintWarning("Slot Cutter Select Mode\n")
def surfaceselect():
@@ -339,26 +350,31 @@ def surfaceselect():
if(MESHGate() or FACEGate()):
gate = True
FreeCADGui.Selection.addSelectionGate(gate)
FreeCAD.Console.PrintWarning("Surfacing Select Mode\n")
if not PathPreferences.suppressSelectionModeWarning():
FreeCAD.Console.PrintWarning("Surfacing Select Mode\n")
def vcarveselect():
FreeCADGui.Selection.addSelectionGate(VCARVEGate())
FreeCAD.Console.PrintWarning("Vcarve Select Mode\n")
if not PathPreferences.suppressSelectionModeWarning():
FreeCAD.Console.PrintWarning("Vcarve Select Mode\n")
def probeselect():
FreeCADGui.Selection.addSelectionGate(PROBEGate())
FreeCAD.Console.PrintWarning("Probe Select Mode\n")
if not PathPreferences.suppressSelectionModeWarning():
FreeCAD.Console.PrintWarning("Probe Select Mode\n")
def customselect():
FreeCAD.Console.PrintWarning("Custom Select Mode\n")
if not PathPreferences.suppressSelectionModeWarning():
FreeCAD.Console.PrintWarning("Custom Select Mode\n")
def turnselect():
FreeCADGui.Selection.addSelectionGate(TURNGate())
FreeCAD.Console.PrintWarning("Turning Select Mode\n")
if not PathPreferences.suppressSelectionModeWarning():
FreeCAD.Console.PrintWarning("Turning Select Mode\n")
def select(op):
@@ -392,4 +408,5 @@ def select(op):
def clear():
FreeCADGui.Selection.removeSelectionGate()
FreeCAD.Console.PrintWarning("Free Select\n")
if not PathPreferences.suppressSelectionModeWarning():
FreeCAD.Console.PrintWarning("Free Select\n")

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)

View File

@@ -401,7 +401,7 @@ def getToolControllers(obj, proxy=None):
except Exception: # pylint: disable=broad-except
job = None
print("op={} ({})".format(obj.Label, type(obj)))
PathLog.debug("op={} ({})".format(obj.Label, type(obj)))
if job:
return [c for c in job.ToolController if proxy.isToolSupported(obj, c.Tool)]
return []
@@ -538,7 +538,7 @@ def arc(cx, cy, sx, sy, ex, ey, horizFeed=0, ez=None, ccw=False):
eps = 0.01
if (math.sqrt((cx - sx)**2 + (cy - sy)**2) - math.sqrt((cx - ex)**2 + (cy - ey)**2)) >= eps:
print("ERROR: Illegal arc: Start and end radii not equal")
PathLog.error(translate("Path", "Illegal arc: Start and end radii not equal"))
return ""
retstr = ""

View File

@@ -324,4 +324,4 @@ def parse(pathobj):
return out
print(__name__ + " gcode postprocessor loaded.")
# print(__name__ + " gcode postprocessor loaded.")

View File

@@ -94,4 +94,4 @@ def parse(pathobj):
out += str(c) + "\n"
return out
print(__name__ + " gcode postprocessor loaded.")
# print(__name__ + " gcode postprocessor loaded.")

View File

@@ -367,4 +367,4 @@ def parse(pathobj):
return out
print(__name__ + " gcode postprocessor loaded.")
# print(__name__ + " gcode postprocessor loaded.")

View File

@@ -100,4 +100,4 @@ def parse(inputstring):
print("done postprocessing.")
return output
print(__name__ + " gcode postprocessor loaded.")
# print(__name__ + " gcode postprocessor loaded.")

View File

@@ -300,4 +300,4 @@ def parse(pathobj):
return out
print(__name__ + " gcode postprocessor loaded.")
# print(__name__ + " gcode postprocessor loaded.")

View File

@@ -570,4 +570,4 @@ def drill_translate(outstring, cmd, params):
return trBuff
print(__name__ + ": GCode postprocessor loaded.")
# print(__name__ + ": GCode postprocessor loaded.")

View File

@@ -333,4 +333,4 @@ def parse(pathobj):
return out
print(__name__ + " gcode postprocessor loaded.")
# print(__name__ + " gcode postprocessor loaded.")

View File

@@ -398,4 +398,4 @@ def parse(pathobj):
return out
print(__name__ + " gcode postprocessor loaded.")
# print(__name__ + " gcode postprocessor loaded.")

View File

@@ -434,4 +434,4 @@ def parse(pathobj):
return out
print(__name__ + " gcode postprocessor loaded.")
# print(__name__ + " gcode postprocessor loaded.")

View File

@@ -763,7 +763,7 @@ def drill_translate(outlist, cmd, params):
return Drill.gcode
print(__name__ + ': GCode postprocessor loaded.')
# print(__name__ + ': GCode postprocessor loaded.')
# PEP8 format passed using: http://pep8online.com/, which primarily covers
# indentation and line length. Some other aspects of PEP8 which have not

View File

@@ -364,4 +364,4 @@ def linenumber():
return ""
print(__name__ + " gcode postprocessor loaded.")
# print(__name__ + " gcode postprocessor loaded.")

View File

@@ -256,5 +256,5 @@ def parse(inputstring):
return '\n'.join(output)
print (__name__ + " gcode postprocessor loaded.")
# print (__name__ + " gcode postprocessor loaded.")

View File

@@ -425,4 +425,4 @@ def parse(pathobj):
return out
print(__name__ + " gcode postprocessor loaded.")
# print(__name__ + " gcode postprocessor loaded.")