Path: Additional fixes and improvements to unified Profile operation

This commit is contained in:
Russell Johnson
2020-05-10 23:06:33 -05:00
parent 00226b6654
commit edbe6c4f2f
2 changed files with 265 additions and 218 deletions

View File

@@ -3,6 +3,7 @@
# ***************************************************************************
# * *
# * Copyright (c) 2014 Yorik van Havre <yorik@uncreated.net> *
# * Copyright (c) 2016 sliptonic <shopinthewoods@gmail.com> *
# * Copyright (c) 2020 Schildkroet *
# * *
# * This program is free software; you can redistribute it and/or modify *
@@ -38,13 +39,13 @@ from PySide import QtCore
from lazy_loader.lazy_loader import LazyLoader
ArchPanel = LazyLoader('ArchPanel', globals(), 'ArchPanel')
Part = LazyLoader('Part', globals(), 'Part')
DraftGeomUtils = LazyLoader('DraftGeomUtils', globals(), 'DraftGeomUtils')
__title__ = "Path Profile Faces Operation"
__author__ = "sliptonic (Brad Collette), Schildkroet"
__author__ = "sliptonic (Brad Collette)"
__url__ = "http://www.freecadweb.org"
__doc__ = "Path Profile operation based on faces."
__contributors__ = "Schildkroet"
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
@@ -57,17 +58,14 @@ def translate(context, text, disambig=None):
class ObjectProfile(PathAreaOp.ObjectOp):
'''Proxy object for Profile operations based on faces.'''
def baseObject(self):
'''baseObject() ... returns super of receiver
Used to call base implementation in overwritten functions.'''
return super(self.__class__, self)
def areaOpFeatures(self, obj):
'''areaOpFeatures(obj) ... returns features specific to the operation'''
return PathOp.FeatureBaseFaces | PathOp.FeatureBasePanels | PathOp.FeatureBaseEdges
'''areaOpFeatures(obj) ... returns operation-specific features'''
return PathOp.FeatureBaseFaces | PathOp.FeatureBasePanels \
| PathOp.FeatureBaseEdges
def initAreaOp(self, obj):
'''initAreaOp(obj) ... creates all profile specific properties.'''
self.propertiesReady = False
self.initAreaOpProperties(obj)
obj.setEditorMode('MiterLimit', 2)
@@ -75,29 +73,26 @@ class ObjectProfile(PathAreaOp.ObjectOp):
def initAreaOpProperties(self, obj, warn=False):
'''initAreaOpProperties(obj) ... create operation specific properties'''
missing = list()
JOB = PathUtils.findParentJob(obj)
self.addNewProps = list()
for (prtyp, nm, grp, tt) in self.areaOpProperties():
if not hasattr(obj, nm):
obj.addProperty(prtyp, nm, grp, tt)
missing.append(nm)
if warn:
newPropMsg = translate('PathProfile', 'New property added to') + ' "{}": '.format(obj.Label) + nm + '. '
newPropMsg += translate('PathProfile', 'Check its default value.')
PathLog.warning(newPropMsg)
self.addNewProps.append(nm)
if len(missing) > 0:
if len(self.addNewProps) > 0:
# Set enumeration lists for enumeration properties
ENUMS = self.areaOpPropertyEnumerations()
for n in ENUMS:
if n in missing:
if n in self.addNewProps:
setattr(obj, n, ENUMS[n])
# Set default values
PROP_DFLTS = self.areaOpPropertyDefaults(obj, JOB)
for n in PROP_DFLTS:
if n in missing:
setattr(obj, n, PROP_DFLTS[n])
if warn:
newPropMsg = translate('PathProfile', 'New property added to')
newPropMsg += ' "{}": {}'.format(obj.Label, self.addNewProps) + '. '
newPropMsg += translate('PathProfile', 'Check its default value.') + '\n'
FreeCAD.Console.PrintWarning(newPropMsg)
self.propertiesReady = True
def areaOpProperties(self):
'''areaOpProperties(obj) ... returns a tuples.
@@ -146,8 +141,8 @@ class ObjectProfile(PathAreaOp.ObjectOp):
'Side': ['Outside', 'Inside'], # side of profile that cutter is on in relation to direction of profile
}
def areaOpPropertyDefaults(self, obj=None, job=None):
'''areaOpPropertyDefaults(obj=None, job=None) ... returns a dictionary of default values
def areaOpPropertyDefaults(self, obj, job):
'''areaOpPropertyDefaults(obj, job) ... returns a dictionary of default values
for the operation's properties.'''
return {
'AttemptInverseAngle': True,
@@ -166,40 +161,77 @@ class ObjectProfile(PathAreaOp.ObjectOp):
'processPerimeter': True
}
def areaOpOnChanged(self, obj, prop):
'''areaOpOnChanged(obj, prop) ... updates certain property visibilities depending on changed properties.'''
if prop in ['UseComp', 'JoinType', 'EnableRotation']:
self.setOpEditorProperties(obj)
def areaOpApplyPropertyDefaults(self, obj, job, propList):
# Set standard property defaults
PROP_DFLTS = self.areaOpPropertyDefaults(obj, job)
for n in PROP_DFLTS:
if n in propList:
prop = getattr(obj, n)
val = PROP_DFLTS[n]
setVal = False
if hasattr(prop, 'Value'):
if isinstance(val, int) or isinstance(val, float):
setVal = True
if setVal:
propVal = getattr(prop, 'Value')
setattr(prop, 'Value', val)
else:
setattr(obj, n, val)
def areaOpSetDefaultValues(self, obj, job):
if self.addNewProps and self.addNewProps.__len__() > 0:
self.areaOpApplyPropertyDefaults(obj, job, self.addNewProps)
def setOpEditorProperties(self, obj):
'''setOpEditorProperties(obj, porp) ... Process operation-specific changes to properties visibility.'''
side = 2
if obj.UseComp:
if len(obj.Base) > 0:
side = 0
fc = 2
# ml = 0 if obj.JoinType == 'Miter' else 2
rotation = 2 if obj.EnableRotation == 'Off' else 0
side = 0 if obj.UseComp else 2
opType = self.getOperationType(obj)
if opType == 'Contour':
side = 2
elif opType == 'Face':
fc = 0
elif opType == 'Edge':
pass
obj.setEditorMode('JoinType', 2)
obj.setEditorMode('MiterLimit', 2) # ml
obj.setEditorMode('Side', side)
obj.setEditorMode('HandleMultipleFeatures', fc)
obj.setEditorMode('processCircles', fc)
obj.setEditorMode('processHoles', fc)
obj.setEditorMode('processPerimeter', fc)
if obj.JoinType == 'Miter':
obj.setEditorMode('MiterLimit', 0)
else:
obj.setEditorMode('MiterLimit', 2)
rotation = 2
if obj.EnableRotation != 'Off':
rotation = 0
obj.setEditorMode('ReverseDirection', rotation)
obj.setEditorMode('InverseAngle', rotation)
obj.setEditorMode('AttemptInverseAngle', rotation)
obj.setEditorMode('LimitDepthToFace', rotation)
def getOperationType(self, obj):
if len(obj.Base) == 0:
return 'Contour'
# return first geometry type selected
(base, subsList) = obj.Base[0]
return subsList[0][:4]
def areaOpOnDocumentRestored(self, obj):
self.propertiesReady = False
self.initAreaOpProperties(obj, warn=True)
for prop in ['UseComp', 'JoinType']:
self.areaOpOnChanged(obj, prop)
self.areaOpSetDefaultValues(obj, PathUtils.findParentJob(obj))
self.setOpEditorProperties(obj)
def areaOpOnChanged(self, obj, prop):
'''areaOpOnChanged(obj, prop) ... updates certain property visibilities depending on changed properties.'''
if prop in ['UseComp', 'JoinType', 'EnableRotation', 'Base']:
if hasattr(self, 'propertiesReady') and self.propertiesReady:
self.setOpEditorProperties(obj)
def areaOpAreaParams(self, obj, isHole):
'''areaOpAreaParams(obj, isHole) ... returns dictionary with area parameters.
Do not overwrite.'''
@@ -499,6 +531,7 @@ class ObjectProfile(PathAreaOp.ObjectOp):
# Edges pre-processing
def _processEdges(self, obj):
import DraftGeomUtils
shapes = list()
basewires = list()
delPairs = list()

View File

@@ -1,173 +1,187 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * *
# * Copyright (c) 2017 sliptonic <shopinthewoods@gmail.com> *
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU Lesser General Public License (LGPL) *
# * as published by the Free Software Foundation; either version 2 of *
# * the License, or (at your option) any later version. *
# * for detail see the LICENCE text file. *
# * *
# * This program is distributed in the hope that it will be useful, *
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
# * GNU Library General Public License for more details. *
# * *
# * You should have received a copy of the GNU Library General Public *
# * License along with this program; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************
import FreeCAD
import FreeCADGui
import PathScripts.PathGui as PathGui
import PathScripts.PathOpGui as PathOpGui
import PathScripts.PathProfile as PathProfile
from PySide import QtCore
__title__ = "Path Profile Operation UI"
__author__ = "sliptonic (Brad Collette)"
__url__ = "http://www.freecadweb.org"
__doc__ = "Profile operation page controller and command implementation."
FeatureSide = 0x01
FeatureProcessing = 0x02
def translate(context, text, disambig=None):
return QtCore.QCoreApplication.translate(context, text, disambig)
class TaskPanelOpPage(PathOpGui.TaskPanelPage):
'''Base class for profile operation page controllers. Two sub features are supported:
FeatureSide ... Is the Side property exposed in the UI
FeatureProcessing ... Are the processing check boxes supported by the operation
'''
def initPage(self, obj):
self.updateVisibility(obj)
def profileFeatures(self):
'''profileFeatures() ... return which of the optional profile features are supported.
Currently two features are supported and returned:
FeatureSide ... Is the Side property exposed in the UI
FeatureProcessing ... Are the processing check boxes supported by the operation
.'''
return FeatureSide | FeatureProcessing
def getForm(self):
'''getForm() ... returns UI customized according to profileFeatures()'''
form = FreeCADGui.PySideUic.loadUi(":/panels/PageOpProfileFullEdit.ui")
return form
def getFields(self, obj):
'''getFields(obj) ... transfers values from UI to obj's proprties'''
self.updateToolController(obj, self.form.toolController)
self.updateCoolant(obj, self.form.coolantController)
if obj.Side != str(self.form.cutSide.currentText()):
obj.Side = str(self.form.cutSide.currentText())
if obj.Direction != str(self.form.direction.currentText()):
obj.Direction = str(self.form.direction.currentText())
PathGui.updateInputField(obj, 'OffsetExtra', self.form.extraOffset)
if obj.EnableRotation != str(self.form.enableRotation.currentText()):
obj.EnableRotation = str(self.form.enableRotation.currentText())
if obj.UseComp != self.form.useCompensation.isChecked():
obj.UseComp = self.form.useCompensation.isChecked()
if obj.UseStartPoint != self.form.useStartPoint.isChecked():
obj.UseStartPoint = self.form.useStartPoint.isChecked()
if obj.processHoles != self.form.processHoles.isChecked():
obj.processHoles = self.form.processHoles.isChecked()
if obj.processPerimeter != self.form.processPerimeter.isChecked():
obj.processPerimeter = self.form.processPerimeter.isChecked()
if obj.processCircles != self.form.processCircles.isChecked():
obj.processCircles = self.form.processCircles.isChecked()
def setFields(self, obj):
'''setFields(obj) ... transfers obj's property values to UI'''
self.setupToolController(obj, self.form.toolController)
self.setupCoolant(obj, self.form.coolantController)
self.selectInComboBox(obj.Side, self.form.cutSide)
self.selectInComboBox(obj.Direction, self.form.direction)
self.form.extraOffset.setText(FreeCAD.Units.Quantity(obj.OffsetExtra.Value, FreeCAD.Units.Length).UserString)
self.selectInComboBox(obj.EnableRotation, self.form.enableRotation)
self.form.useCompensation.setChecked(obj.UseComp)
self.form.useStartPoint.setChecked(obj.UseStartPoint)
self.form.processHoles.setChecked(obj.processHoles)
self.form.processPerimeter.setChecked(obj.processPerimeter)
self.form.processCircles.setChecked(obj.processCircles)
self.updateVisibility(obj)
def getSignalsForUpdate(self, obj):
'''getSignalsForUpdate(obj) ... return list of signals for updating obj'''
signals = []
signals.append(self.form.toolController.currentIndexChanged)
signals.append(self.form.coolantController.currentIndexChanged)
signals.append(self.form.cutSide.currentIndexChanged)
signals.append(self.form.direction.currentIndexChanged)
signals.append(self.form.extraOffset.editingFinished)
signals.append(self.form.enableRotation.currentIndexChanged)
signals.append(self.form.useCompensation.stateChanged)
signals.append(self.form.useStartPoint.stateChanged)
signals.append(self.form.processHoles.stateChanged)
signals.append(self.form.processPerimeter.stateChanged)
signals.append(self.form.processCircles.stateChanged)
return signals
def updateVisibility(self, obj):
hasFace = False
fullModel = False
if len(obj.Base) > 0:
for (base, subsList) in obj.Base:
for sub in subsList:
if sub[:4] == 'Face':
hasFace = True
break
else:
fullModel = True
if hasFace:
self.form.processCircles.show()
self.form.processHoles.show()
self.form.processPerimeter.show()
else:
self.form.processCircles.hide()
self.form.processHoles.hide()
self.form.processPerimeter.hide()
if self.form.useCompensation.isChecked() is True and not fullModel:
self.form.cutSide.show()
self.form.cutSideLabel.show()
else:
# Reset cutSide to 'Outside' for full model before hiding cutSide input
if self.form.cutSide.currentText() == 'Inside':
self.selectInComboBox('Outside', self.form.cutSide)
self.form.cutSide.hide()
self.form.cutSideLabel.hide()
def registerSignalHandlers(self, obj):
self.form.useCompensation.stateChanged.connect(self.updateVisibility)
# Eclass
Command = PathOpGui.SetupOperation('Profile',
PathProfile.Create,
TaskPanelOpPage,
'Path-Contour',
QtCore.QT_TRANSLATE_NOOP("PathProfile", "Profile"),
QtCore.QT_TRANSLATE_NOOP("PathProfile", "Profile entire model, selected face(s) or selected edge(s)"),
PathProfile.SetupProperties)
FreeCAD.Console.PrintLog("Loading PathProfileFacesGui... done\n")
# -*- coding: utf-8 -*-
# ***************************************************************************
# * *
# * Copyright (c) 2017 sliptonic <shopinthewoods@gmail.com> *
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU Lesser General Public License (LGPL) *
# * as published by the Free Software Foundation; either version 2 of *
# * the License, or (at your option) any later version. *
# * for detail see the LICENCE text file. *
# * *
# * This program is distributed in the hope that it will be useful, *
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
# * GNU Library General Public License for more details. *
# * *
# * You should have received a copy of the GNU Library General Public *
# * License along with this program; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************
import FreeCAD
import FreeCADGui
import PathScripts.PathGui as PathGui
import PathScripts.PathOpGui as PathOpGui
import PathScripts.PathProfile as PathProfile
from PySide import QtCore
__title__ = "Path Profile Operation UI"
__author__ = "sliptonic (Brad Collette)"
__url__ = "http://www.freecadweb.org"
__doc__ = "Profile operation page controller and command implementation."
FeatureSide = 0x01
FeatureProcessing = 0x02
def translate(context, text, disambig=None):
return QtCore.QCoreApplication.translate(context, text, disambig)
class TaskPanelOpPage(PathOpGui.TaskPanelPage):
'''Base class for profile operation page controllers. Two sub features are supported:
FeatureSide ... Is the Side property exposed in the UI
FeatureProcessing ... Are the processing check boxes supported by the operation
'''
def initPage(self, obj):
self.updateVisibility(obj)
def profileFeatures(self):
'''profileFeatures() ... return which of the optional profile features are supported.
Currently two features are supported and returned:
FeatureSide ... Is the Side property exposed in the UI
FeatureProcessing ... Are the processing check boxes supported by the operation
.'''
return FeatureSide | FeatureProcessing
def getForm(self):
'''getForm() ... returns UI customized according to profileFeatures()'''
form = FreeCADGui.PySideUic.loadUi(":/panels/PageOpProfileFullEdit.ui")
return form
def getFields(self, obj):
'''getFields(obj) ... transfers values from UI to obj's proprties'''
self.updateToolController(obj, self.form.toolController)
self.updateCoolant(obj, self.form.coolantController)
if obj.Side != str(self.form.cutSide.currentText()):
obj.Side = str(self.form.cutSide.currentText())
if obj.Direction != str(self.form.direction.currentText()):
obj.Direction = str(self.form.direction.currentText())
PathGui.updateInputField(obj, 'OffsetExtra', self.form.extraOffset)
if obj.EnableRotation != str(self.form.enableRotation.currentText()):
obj.EnableRotation = str(self.form.enableRotation.currentText())
if obj.UseComp != self.form.useCompensation.isChecked():
obj.UseComp = self.form.useCompensation.isChecked()
if obj.UseStartPoint != self.form.useStartPoint.isChecked():
obj.UseStartPoint = self.form.useStartPoint.isChecked()
if obj.processHoles != self.form.processHoles.isChecked():
obj.processHoles = self.form.processHoles.isChecked()
if obj.processPerimeter != self.form.processPerimeter.isChecked():
obj.processPerimeter = self.form.processPerimeter.isChecked()
if obj.processCircles != self.form.processCircles.isChecked():
obj.processCircles = self.form.processCircles.isChecked()
def setFields(self, obj):
'''setFields(obj) ... transfers obj's property values to UI'''
self.setupToolController(obj, self.form.toolController)
self.setupCoolant(obj, self.form.coolantController)
self.selectInComboBox(obj.Side, self.form.cutSide)
self.selectInComboBox(obj.Direction, self.form.direction)
self.form.extraOffset.setText(FreeCAD.Units.Quantity(obj.OffsetExtra.Value, FreeCAD.Units.Length).UserString)
self.selectInComboBox(obj.EnableRotation, self.form.enableRotation)
self.form.useCompensation.setChecked(obj.UseComp)
self.form.useStartPoint.setChecked(obj.UseStartPoint)
self.form.processHoles.setChecked(obj.processHoles)
self.form.processPerimeter.setChecked(obj.processPerimeter)
self.form.processCircles.setChecked(obj.processCircles)
self.updateVisibility(obj)
def getSignalsForUpdate(self, obj):
'''getSignalsForUpdate(obj) ... return list of signals for updating obj'''
signals = []
signals.append(self.form.toolController.currentIndexChanged)
signals.append(self.form.coolantController.currentIndexChanged)
signals.append(self.form.cutSide.currentIndexChanged)
signals.append(self.form.direction.currentIndexChanged)
signals.append(self.form.extraOffset.editingFinished)
signals.append(self.form.enableRotation.currentIndexChanged)
signals.append(self.form.useCompensation.stateChanged)
signals.append(self.form.useStartPoint.stateChanged)
signals.append(self.form.processHoles.stateChanged)
signals.append(self.form.processPerimeter.stateChanged)
signals.append(self.form.processCircles.stateChanged)
return signals
def updateVisibility(self, sentObj=None):
hasFace = False
hasGeom = False
fullModel = False
objBase = list()
if sentObj:
if hasattr(sentObj, 'Base'):
objBase = sentObj.Base
elif hasattr(self.obj, 'Base'):
objBase = self.obj.Base
if objBase.__len__() > 0:
for (base, subsList) in objBase:
for sub in subsList:
if sub[:4] == 'Face':
hasFace = True
break
else:
fullModel = True
if hasFace:
self.form.processCircles.show()
self.form.processHoles.show()
self.form.processPerimeter.show()
else:
self.form.processCircles.hide()
self.form.processHoles.hide()
self.form.processPerimeter.hide()
side = False
if self.form.useCompensation.isChecked() is True:
if not fullModel:
side = True
if side:
self.form.cutSide.show()
self.form.cutSideLabel.show()
else:
# Reset cutSide to 'Outside' for full model before hiding cutSide input
if self.form.cutSide.currentText() == 'Inside':
self.selectInComboBox('Outside', self.form.cutSide)
self.form.cutSide.hide()
self.form.cutSideLabel.hide()
def registerSignalHandlers(self, obj):
self.form.useCompensation.stateChanged.connect(self.updateVisibility)
# Eclass
Command = PathOpGui.SetupOperation('Profile',
PathProfile.Create,
TaskPanelOpPage,
'Path-Contour',
QtCore.QT_TRANSLATE_NOOP("PathProfile", "Profile"),
QtCore.QT_TRANSLATE_NOOP("PathProfile", "Profile entire model, selected face(s) or selected edge(s)"),
PathProfile.SetupProperties)
FreeCAD.Console.PrintLog("Loading PathProfileFacesGui... done\n")