Merge pull request #3461 from Russ4262/3D_Surface_updates
Path: 3D Surface and Waterline updates and fixes.
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>368</width>
|
||||
<height>400</height>
|
||||
<height>442</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@@ -57,142 +57,24 @@
|
||||
<item row="1" column="0">
|
||||
<widget class="QWidget" name="widget" native="true">
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="1" column="1" colspan="2">
|
||||
<widget class="QComboBox" name="scanType">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Planar: Flat, 3D surface scan. Rotational: 4th-axis rotational scan.</p></body></html></string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Planar</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Rotational</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1" colspan="2">
|
||||
<widget class="QComboBox" name="layerMode">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Complete the operation in a single pass at depth, or mulitiple passes to final depth.</p></body></html></string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Single-pass</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Multi-pass</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="1" colspan="2">
|
||||
<widget class="QSpinBox" name="stepOver">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>The amount by which the tool is laterally displaced on each cycle of the pattern, specified in percent of the tool diameter.</p><p>A step over of 100% results in no overlap between two different cycles.</p></body></html></string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>100</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<widget class="QLabel" name="stepOver_label">
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="cutPattern_label">
|
||||
<property name="text">
|
||||
<string>Step over</string>
|
||||
<string>Cut Pattern</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="0">
|
||||
<item row="12" column="0">
|
||||
<widget class="QLabel" name="sampleInterval_label">
|
||||
<property name="text">
|
||||
<string>Sample interval</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="layerMode_label">
|
||||
<property name="text">
|
||||
<string>Layer Mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="12" column="1">
|
||||
<widget class="QCheckBox" name="optimizeEnabled">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Enable optimization of linear paths (co-linear points). Removes unnecessary co-linear points from G-Code output.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Optimize Linear Paths</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="dropCutterDirSelect_label">
|
||||
<property name="text">
|
||||
<string>Drop Cutter Direction</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="boundBoxExtraOffset_label">
|
||||
<property name="text">
|
||||
<string>BoundBox extra offset X, Y</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="12" column="0">
|
||||
<widget class="QCheckBox" name="useStartPoint">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Make True, if specifying a Start Point</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Use Start Point</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="scanType_label">
|
||||
<property name="text">
|
||||
<string>Scan Type</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="boundBoxSelect_label">
|
||||
<property name="text">
|
||||
<string>BoundBox</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1" colspan="2">
|
||||
<widget class="Gui::InputField" name="depthOffset" native="true">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Set the Z-axis depth offset from the target surface.</p></body></html></string>
|
||||
</property>
|
||||
<property name="unit" stdset="0">
|
||||
<string notr="true">mm</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1" colspan="2">
|
||||
<item row="6" column="1" colspan="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="Gui::InputField" name="boundBoxExtraOffsetX" native="true">
|
||||
<widget class="Gui::InputField" name="boundBoxExtraOffsetX">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
@@ -208,7 +90,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Gui::InputField" name="boundBoxExtraOffsetY" native="true">
|
||||
<widget class="Gui::InputField" name="boundBoxExtraOffsetY">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Additional offset to the selected bounding box along the Y axis."</p></body></html></string>
|
||||
</property>
|
||||
@@ -219,8 +101,8 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="10" column="1" colspan="2">
|
||||
<widget class="Gui::InputField" name="sampleInterval" native="true">
|
||||
<item row="12" column="1" colspan="2">
|
||||
<widget class="Gui::InputField" name="sampleInterval">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Set the sampling resolution. Smaller values quickly increase processing time.</p></body></html></string>
|
||||
</property>
|
||||
@@ -229,28 +111,21 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="depthOffset_label">
|
||||
<item row="14" column="1">
|
||||
<widget class="QCheckBox" name="optimizeEnabled">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Enable optimization of linear paths (co-linear points). Removes unnecessary co-linear points from G-Code output.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Depth offset</string>
|
||||
<string>Optimize Linear Paths</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1" colspan="2">
|
||||
<widget class="QComboBox" name="dropCutterDirSelect">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Dropcutter lines are created parallel to this axis.</p></body></html></string>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="boundBoxSelect_label">
|
||||
<property name="text">
|
||||
<string>BoundBox</string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>X</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Y</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" colspan="2">
|
||||
@@ -270,7 +145,7 @@
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="13" column="1">
|
||||
<item row="15" column="1">
|
||||
<widget class="QCheckBox" name="optimizeStepOverTransitions">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Enable separate optimization of transitions between, and breaks within, each step over path.</p></body></html></string>
|
||||
@@ -280,10 +155,37 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="cutPattern_label">
|
||||
<item row="4" column="1" colspan="2">
|
||||
<widget class="QComboBox" name="comboBox">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Profile the edges of the selection.</p></body></html></string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>None</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Only</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>First</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Last</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="0">
|
||||
<widget class="QLabel" name="stepOver_label">
|
||||
<property name="text">
|
||||
<string>Cut Pattern</string>
|
||||
<string>Step over</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -324,6 +226,146 @@
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1" colspan="2">
|
||||
<widget class="QComboBox" name="scanType">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Planar: Flat, 3D surface scan. Rotational: 4th-axis rotational scan.</p></body></html></string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Planar</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Rotational</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="boundBoxExtraOffset_label">
|
||||
<property name="text">
|
||||
<string>BoundBox extra offset X, Y</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="0">
|
||||
<widget class="QLabel" name="depthOffset_label">
|
||||
<property name="text">
|
||||
<string>Depth offset</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1" colspan="2">
|
||||
<widget class="QComboBox" name="layerMode">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Complete the operation in a single pass at depth, or mulitiple passes to final depth.</p></body></html></string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Single-pass</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Multi-pass</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="layerMode_label">
|
||||
<property name="text">
|
||||
<string>Layer Mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="scanType_label">
|
||||
<property name="text">
|
||||
<string>Scan Type</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="1" colspan="2">
|
||||
<widget class="QComboBox" name="dropCutterDirSelect">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Dropcutter lines are created parallel to this axis.</p></body></html></string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>X</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Y</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="1" colspan="2">
|
||||
<widget class="Gui::InputField" name="depthOffset">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Set the Z-axis depth offset from the target surface.</p></body></html></string>
|
||||
</property>
|
||||
<property name="unit" stdset="0">
|
||||
<string notr="true">mm</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<widget class="QLabel" name="dropCutterDirSelect_label">
|
||||
<property name="text">
|
||||
<string>Drop Cutter Direction</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="14" column="0">
|
||||
<widget class="QCheckBox" name="useStartPoint">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Make True, if specifying a Start Point</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Use Start Point</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1" colspan="2">
|
||||
<widget class="QSpinBox" name="avoidLastX_Faces">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Avoid cutting the last 'N' faces in the Base Geometry list of selected faces.</p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="profileEdges_label">
|
||||
<property name="text">
|
||||
<string>Profile Edges</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="avoidLastX_Faces_label">
|
||||
<property name="text">
|
||||
<string>Avoid Last X Faces</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="1" colspan="2">
|
||||
<widget class="QSpinBox" name="stepOver">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>The amount by which the tool is laterally displaced on each cycle of the pattern, specified in percent of the tool diameter.</p><p>A step over of 100% results in no overlap between two different cycles.</p></body></html></string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@@ -194,12 +194,6 @@
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>100</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
|
||||
@@ -54,7 +54,6 @@ import math
|
||||
|
||||
# lazily loaded modules
|
||||
from lazy_loader.lazy_loader import LazyLoader
|
||||
MeshPart = LazyLoader('MeshPart', globals(), 'MeshPart')
|
||||
Part = LazyLoader('Part', globals(), 'Part')
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
@@ -72,18 +71,18 @@ def translate(context, text, disambig=None):
|
||||
class ObjectSurface(PathOp.ObjectOp):
|
||||
'''Proxy object for Surfacing operation.'''
|
||||
|
||||
def baseObject(self):
|
||||
'''baseObject() ... returns super of receiver
|
||||
Used to call base implementation in overwritten functions.'''
|
||||
return super(self.__class__, self)
|
||||
|
||||
def opFeatures(self, obj):
|
||||
'''opFeatures(obj) ... return all standard features and edges based geometries'''
|
||||
return PathOp.FeatureTool | PathOp.FeatureDepths | PathOp.FeatureHeights | PathOp.FeatureStepDown | PathOp.FeatureCoolant | PathOp.FeatureBaseFaces
|
||||
'''opFeatures(obj) ... return all standard features'''
|
||||
return PathOp.FeatureTool | PathOp.FeatureDepths \
|
||||
| PathOp.FeatureHeights | PathOp.FeatureStepDown \
|
||||
| PathOp.FeatureCoolant | PathOp.FeatureBaseFaces
|
||||
|
||||
def initOperation(self, obj):
|
||||
'''initPocketOp(obj) ... create operation specific properties'''
|
||||
self.initOpProperties(obj)
|
||||
'''initOperation(obj) ... Initialize the operation by
|
||||
managing property creation and property editor status.'''
|
||||
self.propertiesReady = False
|
||||
|
||||
self.initOpProperties(obj) # Initialize operation-specific properties
|
||||
|
||||
# For debugging
|
||||
if PathLog.getLevel(PathLog.thisModule()) != 4:
|
||||
@@ -94,28 +93,30 @@ class ObjectSurface(PathOp.ObjectOp):
|
||||
|
||||
def initOpProperties(self, obj, warn=False):
|
||||
'''initOpProperties(obj) ... create operation specific properties'''
|
||||
missing = list()
|
||||
self.addNewProps = list()
|
||||
|
||||
for (prtyp, nm, grp, tt) in self.opProperties():
|
||||
for (prtyp, nm, grp, tt) in self.opPropertyDefinitions():
|
||||
if not hasattr(obj, nm):
|
||||
obj.addProperty(prtyp, nm, grp, tt)
|
||||
missing.append(nm)
|
||||
if warn:
|
||||
newPropMsg = translate('PathSurface', 'New property added to') + ' "{}": '.format(obj.Label) + nm + '. '
|
||||
newPropMsg += translate('PathSurface', 'Check its default value.')
|
||||
PathLog.warning(newPropMsg)
|
||||
self.addNewProps.append(nm)
|
||||
|
||||
# Set enumeration lists for enumeration properties
|
||||
if len(missing) > 0:
|
||||
ENUMS = self.propertyEnumerations()
|
||||
if len(self.addNewProps) > 0:
|
||||
ENUMS = self.opPropertyEnumerations()
|
||||
for n in ENUMS:
|
||||
if n in missing:
|
||||
if n in self.addNewProps:
|
||||
setattr(obj, n, ENUMS[n])
|
||||
|
||||
self.addedAllProperties = True
|
||||
if warn:
|
||||
newPropMsg = translate('PathSurface', 'New property added to')
|
||||
newPropMsg += ' "{}": {}'.format(obj.Label, self.addNewProps) + '. '
|
||||
newPropMsg += translate('PathSurface', 'Check default value(s).')
|
||||
FreeCAD.Console.PrintWarning(newPropMsg + '\n')
|
||||
|
||||
def opProperties(self):
|
||||
'''opProperties(obj) ... Store operation specific properties'''
|
||||
self.propertiesReady = True
|
||||
|
||||
def opPropertyDefinitions(self):
|
||||
'''opPropertyDefinitions(obj) ... Store operation specific properties'''
|
||||
|
||||
return [
|
||||
("App::PropertyBool", "ShowTempObjects", "Debug",
|
||||
@@ -179,7 +180,7 @@ class ObjectSurface(PathOp.ObjectOp):
|
||||
QtCore.QT_TRANSLATE_NOOP("App::Property", "Profile the edges of the selection.")),
|
||||
("App::PropertyDistance", "SampleInterval", "Clearing Options",
|
||||
QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the sampling resolution. Smaller values quickly increase processing time.")),
|
||||
("App::PropertyPercent", "StepOver", "Clearing Options",
|
||||
("App::PropertyFloat", "StepOver", "Clearing Options",
|
||||
QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the stepover percentage, based on the tool's diameter.")),
|
||||
|
||||
("App::PropertyBool", "OptimizeLinearPaths", "Optimization",
|
||||
@@ -199,7 +200,7 @@ class ObjectSurface(PathOp.ObjectOp):
|
||||
QtCore.QT_TRANSLATE_NOOP("App::Property", "Make True, if specifying a Start Point"))
|
||||
]
|
||||
|
||||
def propertyEnumerations(self):
|
||||
def opPropertyEnumerations(self):
|
||||
# Enumeration lists for App::PropertyEnumeration properties
|
||||
return {
|
||||
'BoundBox': ['BaseBoundBox', 'Stock'],
|
||||
@@ -214,6 +215,59 @@ class ObjectSurface(PathOp.ObjectOp):
|
||||
'ScanType': ['Planar', 'Rotational']
|
||||
}
|
||||
|
||||
def opPropertyDefaults(self, obj, job):
|
||||
'''opPropertyDefaults(obj, job) ... returns a dictionary of default values
|
||||
for the operation's properties.'''
|
||||
defaults = {
|
||||
'OptimizeLinearPaths': True,
|
||||
'InternalFeaturesCut': True,
|
||||
'OptimizeStepOverTransitions': False,
|
||||
'CircularUseG2G3': False,
|
||||
'BoundaryEnforcement': True,
|
||||
'UseStartPoint': False,
|
||||
'AvoidLastX_InternalFeatures': True,
|
||||
'CutPatternReversed': False,
|
||||
'StartPoint': FreeCAD.Vector(0.0, 0.0, obj.ClearanceHeight.Value),
|
||||
'ProfileEdges': 'None',
|
||||
'LayerMode': 'Single-pass',
|
||||
'ScanType': 'Planar',
|
||||
'RotationAxis': 'X',
|
||||
'CutMode': 'Conventional',
|
||||
'CutPattern': 'Line',
|
||||
'HandleMultipleFeatures': 'Collectively',
|
||||
'PatternCenterAt': 'CenterOfMass',
|
||||
'GapSizes': 'No gaps identified.',
|
||||
'StepOver': 100.0,
|
||||
'CutPatternAngle': 0.0,
|
||||
'CutterTilt': 0.0,
|
||||
'StartIndex': 0.0,
|
||||
'StopIndex': 360.0,
|
||||
'SampleInterval': 1.0,
|
||||
'BoundaryAdjustment': 0.0,
|
||||
'InternalFeaturesAdjustment': 0.0,
|
||||
'AvoidLastX_Faces': 0,
|
||||
'PatternCenterCustom': FreeCAD.Vector(0.0, 0.0, 0.0),
|
||||
'GapThreshold': 0.005,
|
||||
'AngularDeflection': 0.25,
|
||||
'LinearDeflection': 0.0001,
|
||||
# For debugging
|
||||
'ShowTempObjects': False
|
||||
}
|
||||
|
||||
warn = True
|
||||
if hasattr(job, 'GeometryTolerance'):
|
||||
if job.GeometryTolerance.Value != 0.0:
|
||||
warn = False
|
||||
defaults['LinearDeflection'] = job.GeometryTolerance.Value
|
||||
if warn:
|
||||
msg = translate('PathSurface',
|
||||
'The GeometryTolerance for this Job is 0.0.')
|
||||
msg += translate('PathSurface',
|
||||
'Initializing LinearDeflection to 0.0001 mm.')
|
||||
FreeCAD.Console.PrintWarning(msg + '\n')
|
||||
|
||||
return defaults
|
||||
|
||||
def setEditorProperties(self, obj):
|
||||
# Used to hide inputs in properties list
|
||||
|
||||
@@ -241,23 +295,23 @@ class ObjectSurface(PathOp.ObjectOp):
|
||||
obj.setEditorMode('PatternCenterCustom', P2)
|
||||
|
||||
def onChanged(self, obj, prop):
|
||||
if hasattr(self, 'addedAllProperties'):
|
||||
if self.addedAllProperties is True:
|
||||
if prop == 'ScanType':
|
||||
self.setEditorProperties(obj)
|
||||
if prop == 'CutPattern':
|
||||
if hasattr(self, 'propertiesReady'):
|
||||
if self.propertiesReady:
|
||||
if prop in ['ScanType', 'CutPattern']:
|
||||
self.setEditorProperties(obj)
|
||||
|
||||
def opOnDocumentRestored(self, obj):
|
||||
self.initOpProperties(obj, warn=True)
|
||||
self.propertiesReady = False
|
||||
job = PathUtils.findParentJob(obj)
|
||||
|
||||
if PathLog.getLevel(PathLog.thisModule()) != 4:
|
||||
obj.setEditorMode('ShowTempObjects', 2) # hide
|
||||
else:
|
||||
obj.setEditorMode('ShowTempObjects', 0) # show
|
||||
self.initOpProperties(obj, warn=True)
|
||||
self.opApplyPropertyDefaults(obj, job, self.addNewProps)
|
||||
|
||||
mode = 2 if PathLog.getLevel(PathLog.thisModule()) != 4 else 0
|
||||
obj.setEditorMode('ShowTempObjects', mode)
|
||||
|
||||
# Repopulate enumerations in case of changes
|
||||
ENUMS = self.propertyEnumerations()
|
||||
ENUMS = self.opPropertyEnumerations()
|
||||
for n in ENUMS:
|
||||
restore = False
|
||||
if hasattr(obj, n):
|
||||
@@ -269,51 +323,28 @@ class ObjectSurface(PathOp.ObjectOp):
|
||||
|
||||
self.setEditorProperties(obj)
|
||||
|
||||
def opApplyPropertyDefaults(self, obj, job, propList):
|
||||
# Set standard property defaults
|
||||
PROP_DFLTS = self.opPropertyDefaults(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 opSetDefaultValues(self, obj, job):
|
||||
'''opSetDefaultValues(obj, job) ... initialize defaults'''
|
||||
job = PathUtils.findParentJob(obj)
|
||||
|
||||
obj.OptimizeLinearPaths = True
|
||||
obj.InternalFeaturesCut = True
|
||||
obj.OptimizeStepOverTransitions = False
|
||||
obj.CircularUseG2G3 = False
|
||||
obj.BoundaryEnforcement = True
|
||||
obj.UseStartPoint = False
|
||||
obj.AvoidLastX_InternalFeatures = True
|
||||
obj.CutPatternReversed = False
|
||||
obj.StartPoint.x = 0.0
|
||||
obj.StartPoint.y = 0.0
|
||||
obj.StartPoint.z = obj.ClearanceHeight.Value
|
||||
obj.ProfileEdges = 'None'
|
||||
obj.LayerMode = 'Single-pass'
|
||||
obj.ScanType = 'Planar'
|
||||
obj.RotationAxis = 'X'
|
||||
obj.CutMode = 'Conventional'
|
||||
obj.CutPattern = 'Line'
|
||||
obj.HandleMultipleFeatures = 'Collectively' # 'Individually'
|
||||
obj.PatternCenterAt = 'CenterOfMass' # 'CenterOfBoundBox', 'XminYmin', 'Custom'
|
||||
obj.GapSizes = 'No gaps identified.'
|
||||
obj.StepOver = 100
|
||||
obj.CutPatternAngle = 0.0
|
||||
obj.CutterTilt = 0.0
|
||||
obj.StartIndex = 0.0
|
||||
obj.StopIndex = 360.0
|
||||
obj.SampleInterval.Value = 1.0
|
||||
obj.BoundaryAdjustment.Value = 0.0
|
||||
obj.InternalFeaturesAdjustment.Value = 0.0
|
||||
obj.AvoidLastX_Faces = 0
|
||||
obj.PatternCenterCustom.x = 0.0
|
||||
obj.PatternCenterCustom.y = 0.0
|
||||
obj.PatternCenterCustom.z = 0.0
|
||||
obj.GapThreshold.Value = 0.005
|
||||
obj.AngularDeflection.Value = 0.25
|
||||
obj.LinearDeflection.Value = job.GeometryTolerance.Value
|
||||
# For debugging
|
||||
obj.ShowTempObjects = False
|
||||
|
||||
if job.GeometryTolerance.Value == 0.0:
|
||||
PathLog.warning(translate('PathSurface', 'The GeometryTolerance for this Job is 0.0. Initializing LinearDeflection to 0.0001 mm.'))
|
||||
obj.LinearDeflection.Value = 0.0001
|
||||
self.opApplyPropertyDefaults(obj, job, self.addNewProps)
|
||||
|
||||
# need to overwrite the default depth calculations for facing
|
||||
d = None
|
||||
@@ -373,10 +404,10 @@ class ObjectSurface(PathOp.ObjectOp):
|
||||
PathLog.error(translate('PathSurface', 'Cut pattern angle limits are +- 360 degrees.'))
|
||||
|
||||
# Limit StepOver to natural number percentage
|
||||
if obj.StepOver > 100:
|
||||
obj.StepOver = 100
|
||||
if obj.StepOver < 1:
|
||||
obj.StepOver = 1
|
||||
if obj.StepOver > 100.0:
|
||||
obj.StepOver = 100.0
|
||||
if obj.StepOver < 1.0:
|
||||
obj.StepOver = 1.0
|
||||
|
||||
# Limit AvoidLastX_Faces to zero and positive values
|
||||
if obj.AvoidLastX_Faces < 0:
|
||||
@@ -403,6 +434,7 @@ class ObjectSurface(PathOp.ObjectOp):
|
||||
self.closedGap = False
|
||||
self.tmpCOM = None
|
||||
self.gaps = [0.1, 0.2, 0.3]
|
||||
self.cancelOperation = False
|
||||
CMDS = list()
|
||||
modelVisibility = list()
|
||||
FCAD = FreeCAD.ActiveDocument
|
||||
@@ -423,7 +455,6 @@ class ObjectSurface(PathOp.ObjectOp):
|
||||
self.showDebugObjects = False
|
||||
|
||||
# mark beginning of operation and identify parent Job
|
||||
PathLog.info('\nBegin 3D Surface operation...')
|
||||
startTime = time.time()
|
||||
|
||||
# Identify parent Job
|
||||
@@ -531,18 +562,18 @@ class ObjectSurface(PathOp.ObjectOp):
|
||||
PSF.radius = self.radius
|
||||
PSF.depthParams = self.depthParams
|
||||
pPM = PSF.preProcessModel(self.module)
|
||||
|
||||
# Process selected faces, if available
|
||||
if pPM is False:
|
||||
PathLog.error('Unable to pre-process obj.Base.')
|
||||
else:
|
||||
if pPM:
|
||||
self.cancelOperation = False
|
||||
(FACES, VOIDS) = pPM
|
||||
self.modelSTLs = PSF.modelSTLs
|
||||
self.profileShapes = PSF.profileShapes
|
||||
|
||||
# Create OCL.stl model objects
|
||||
self._prepareModelSTLs(JOB, obj)
|
||||
|
||||
for m in range(0, len(JOB.Model.Group)):
|
||||
# Create OCL.stl model objects
|
||||
PathSurfaceSupport._prepareModelSTLs(self, JOB, obj, m, ocl)
|
||||
|
||||
Mdl = JOB.Model.Group[m]
|
||||
if FACES[m] is False:
|
||||
PathLog.error('No data for model base: {}'.format(JOB.Model.Group[m].Label))
|
||||
@@ -553,7 +584,7 @@ class ObjectSurface(PathOp.ObjectOp):
|
||||
CMDS.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid}))
|
||||
PathLog.info('Working on Model.Group[{}]: {}'.format(m, Mdl.Label))
|
||||
# make stock-model-voidShapes STL model for avoidance detection on transitions
|
||||
self._makeSafeSTL(JOB, obj, m, FACES[m], VOIDS[m])
|
||||
PathSurfaceSupport._makeSafeSTL(self, JOB, obj, m, FACES[m], VOIDS[m], ocl)
|
||||
# Process model/faces - OCL objects must be ready
|
||||
CMDS.extend(self._processCutAreas(JOB, obj, m, FACES[m], VOIDS[m]))
|
||||
|
||||
@@ -622,118 +653,22 @@ class ObjectSurface(PathOp.ObjectOp):
|
||||
del self.midDep
|
||||
|
||||
execTime = time.time() - startTime
|
||||
PathLog.info('Operation time: {} sec.'.format(execTime))
|
||||
if execTime > 60.0:
|
||||
tMins = math.floor(execTime / 60.0)
|
||||
tSecs = execTime - (tMins * 60.0)
|
||||
exTime = str(tMins) + ' min. ' + str(round(tSecs, 5)) + ' sec.'
|
||||
else:
|
||||
exTime = str(round(execTime, 5)) + ' sec.'
|
||||
FreeCAD.Console.PrintMessage('3D Surface operation time is {}\n'.format(exTime))
|
||||
|
||||
if self.cancelOperation:
|
||||
FreeCAD.ActiveDocument.openTransaction(translate("PathSurface", "Canceled 3D Surface operation."))
|
||||
FreeCAD.ActiveDocument.removeObject(obj.Name)
|
||||
FreeCAD.ActiveDocument.commitTransaction()
|
||||
|
||||
return True
|
||||
|
||||
# Methods for constructing the cut area
|
||||
def _prepareModelSTLs(self, JOB, obj):
|
||||
PathLog.debug('_prepareModelSTLs()')
|
||||
for m in range(0, len(JOB.Model.Group)):
|
||||
M = JOB.Model.Group[m]
|
||||
|
||||
# PathLog.debug(f" -self.modelTypes[{m}] == 'M'")
|
||||
if self.modelTypes[m] == 'M':
|
||||
# TODO: test if this works
|
||||
facets = M.Mesh.Facets.Points
|
||||
else:
|
||||
facets = Part.getFacets(M.Shape)
|
||||
|
||||
if self.modelSTLs[m] is True:
|
||||
stl = ocl.STLSurf()
|
||||
|
||||
for tri in facets:
|
||||
t = ocl.Triangle(ocl.Point(tri[0][0], tri[0][1], tri[0][2]),
|
||||
ocl.Point(tri[1][0], tri[1][1], tri[1][2]),
|
||||
ocl.Point(tri[2][0], tri[2][1], tri[2][2]))
|
||||
stl.addTriangle(t)
|
||||
self.modelSTLs[m] = stl
|
||||
return
|
||||
|
||||
def _makeSafeSTL(self, JOB, obj, mdlIdx, faceShapes, voidShapes):
|
||||
'''_makeSafeSTL(JOB, obj, mdlIdx, faceShapes, voidShapes)...
|
||||
Creates and OCL.stl object with combined data with waste stock,
|
||||
model, and avoided faces. Travel lines can be checked against this
|
||||
STL object to determine minimum travel height to clear stock and model.'''
|
||||
PathLog.debug('_makeSafeSTL()')
|
||||
|
||||
fuseShapes = list()
|
||||
Mdl = JOB.Model.Group[mdlIdx]
|
||||
mBB = Mdl.Shape.BoundBox
|
||||
sBB = JOB.Stock.Shape.BoundBox
|
||||
|
||||
# add Model shape to safeSTL shape
|
||||
fuseShapes.append(Mdl.Shape)
|
||||
|
||||
if obj.BoundBox == 'BaseBoundBox':
|
||||
cont = False
|
||||
extFwd = (sBB.ZLength)
|
||||
zmin = mBB.ZMin
|
||||
zmax = mBB.ZMin + extFwd
|
||||
stpDwn = (zmax - zmin) / 4.0
|
||||
dep_par = PathUtils.depth_params(zmax + 5.0, zmax + 3.0, zmax, stpDwn, 0.0, zmin)
|
||||
|
||||
try:
|
||||
envBB = PathUtils.getEnvelope(partshape=Mdl.Shape, depthparams=dep_par) # Produces .Shape
|
||||
cont = True
|
||||
except Exception as ee:
|
||||
PathLog.error(str(ee))
|
||||
shell = Mdl.Shape.Shells[0]
|
||||
solid = Part.makeSolid(shell)
|
||||
try:
|
||||
envBB = PathUtils.getEnvelope(partshape=solid, depthparams=dep_par) # Produces .Shape
|
||||
cont = True
|
||||
except Exception as eee:
|
||||
PathLog.error(str(eee))
|
||||
|
||||
if cont:
|
||||
stckWst = JOB.Stock.Shape.cut(envBB)
|
||||
if obj.BoundaryAdjustment > 0.0:
|
||||
cmpndFS = Part.makeCompound(faceShapes)
|
||||
baBB = PathUtils.getEnvelope(partshape=cmpndFS, depthparams=self.depthParams) # Produces .Shape
|
||||
adjStckWst = stckWst.cut(baBB)
|
||||
else:
|
||||
adjStckWst = stckWst
|
||||
fuseShapes.append(adjStckWst)
|
||||
else:
|
||||
PathLog.warning('Path transitions might not avoid the model. Verify paths.')
|
||||
else:
|
||||
# If boundbox is Job.Stock, add hidden pad under stock as base plate
|
||||
toolDiam = self.cutter.getDiameter()
|
||||
zMin = JOB.Stock.Shape.BoundBox.ZMin
|
||||
xMin = JOB.Stock.Shape.BoundBox.XMin - toolDiam
|
||||
yMin = JOB.Stock.Shape.BoundBox.YMin - toolDiam
|
||||
bL = JOB.Stock.Shape.BoundBox.XLength + (2 * toolDiam)
|
||||
bW = JOB.Stock.Shape.BoundBox.YLength + (2 * toolDiam)
|
||||
bH = 1.0
|
||||
crnr = FreeCAD.Vector(xMin, yMin, zMin - 1.0)
|
||||
B = Part.makeBox(bL, bW, bH, crnr, FreeCAD.Vector(0, 0, 1))
|
||||
fuseShapes.append(B)
|
||||
|
||||
if voidShapes is not False:
|
||||
voidComp = Part.makeCompound(voidShapes)
|
||||
voidEnv = PathUtils.getEnvelope(partshape=voidComp, depthparams=self.depthParams) # Produces .Shape
|
||||
fuseShapes.append(voidEnv)
|
||||
|
||||
fused = Part.makeCompound(fuseShapes)
|
||||
|
||||
if self.showDebugObjects:
|
||||
T = FreeCAD.ActiveDocument.addObject('Part::Feature', 'safeSTLShape')
|
||||
T.Shape = fused
|
||||
T.purgeTouched()
|
||||
self.tempGroup.addObject(T)
|
||||
|
||||
facets = Part.getFacets(fused)
|
||||
|
||||
stl = ocl.STLSurf()
|
||||
for tri in facets:
|
||||
t = ocl.Triangle(ocl.Point(tri[0][0], tri[0][1], tri[0][2]),
|
||||
ocl.Point(tri[1][0], tri[1][1], tri[1][2]),
|
||||
ocl.Point(tri[2][0], tri[2][1], tri[2][2]))
|
||||
stl.addTriangle(t)
|
||||
|
||||
self.safeSTLs[mdlIdx] = stl
|
||||
|
||||
# Methods for constructing the cut area and creating path geometry
|
||||
def _processCutAreas(self, JOB, obj, mdlIdx, FCS, VDS):
|
||||
'''_processCutAreas(JOB, obj, mdlIdx, FCS, VDS)...
|
||||
This method applies any avoided faces or regions to the selected faces.
|
||||
@@ -784,10 +719,9 @@ class ObjectSurface(PathOp.ObjectOp):
|
||||
|
||||
return final
|
||||
|
||||
# Methods for creating path geometry
|
||||
def _processPlanarOp(self, JOB, obj, mdlIdx, cmpdShp, fsi):
|
||||
'''_processPlanarOp(JOB, obj, mdlIdx, cmpdShp)...
|
||||
This method compiles the main components for the procedural portion of a planar operation (non-rotational).
|
||||
'''_processPlanarOp(JOB, obj, mdlIdx, cmpdShp)...
|
||||
This method compiles the main components for the procedural portion of a planar operation (non-rotational).
|
||||
It creates the OCL PathDropCutter objects: model and safeTravel.
|
||||
It makes the necessary facial geometries for the actual cut area.
|
||||
It calls the correct Single or Multi-pass method as needed.
|
||||
|
||||
@@ -141,11 +141,17 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
|
||||
|
||||
return signals
|
||||
|
||||
def updateVisibility(self):
|
||||
def updateVisibility(self, sentObj=None):
|
||||
'''updateVisibility(sentObj=None)... Updates visibility of Tasks panel objects.'''
|
||||
if self.form.scanType.currentText() == 'Planar':
|
||||
self.form.cutPattern.show()
|
||||
self.form.cutPattern_label.show()
|
||||
self.form.optimizeStepOverTransitions.show()
|
||||
if hasattr(self.form, 'profileEdges'):
|
||||
self.form.profileEdges.show()
|
||||
self.form.profileEdges_label.show()
|
||||
self.form.avoidLastX_Faces.show()
|
||||
self.form.avoidLastX_Faces_label.show()
|
||||
|
||||
self.form.boundBoxExtraOffsetX.hide()
|
||||
self.form.boundBoxExtraOffsetY.hide()
|
||||
@@ -156,6 +162,11 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
|
||||
self.form.cutPattern.hide()
|
||||
self.form.cutPattern_label.hide()
|
||||
self.form.optimizeStepOverTransitions.hide()
|
||||
if hasattr(self.form, 'profileEdges'):
|
||||
self.form.profileEdges.hide()
|
||||
self.form.profileEdges_label.hide()
|
||||
self.form.avoidLastX_Faces.hide()
|
||||
self.form.avoidLastX_Faces_label.hide()
|
||||
|
||||
self.form.boundBoxExtraOffsetX.show()
|
||||
self.form.boundBoxExtraOffsetY.show()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -54,7 +54,6 @@ import math
|
||||
|
||||
# lazily loaded modules
|
||||
from lazy_loader.lazy_loader import LazyLoader
|
||||
MeshPart = LazyLoader('MeshPart', globals(), 'MeshPart')
|
||||
Part = LazyLoader('Part', globals(), 'Part')
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
@@ -72,19 +71,18 @@ def translate(context, text, disambig=None):
|
||||
class ObjectWaterline(PathOp.ObjectOp):
|
||||
'''Proxy object for Surfacing operation.'''
|
||||
|
||||
def baseObject(self):
|
||||
'''baseObject() ... returns super of receiver
|
||||
Used to call base implementation in overwritten functions.'''
|
||||
return super(self.__class__, self)
|
||||
|
||||
def opFeatures(self, obj):
|
||||
'''opFeatures(obj) ... return all standard features and edges based geomtries'''
|
||||
return PathOp.FeatureTool | PathOp.FeatureDepths | PathOp.FeatureHeights | PathOp.FeatureStepDown | PathOp.FeatureCoolant | PathOp.FeatureBaseFaces
|
||||
'''opFeatures(obj) ... return all standard features'''
|
||||
return PathOp.FeatureTool | PathOp.FeatureDepths \
|
||||
| PathOp.FeatureHeights | PathOp.FeatureStepDown \
|
||||
| PathOp.FeatureCoolant | PathOp.FeatureBaseFaces
|
||||
|
||||
def initOperation(self, obj):
|
||||
'''initPocketOp(obj) ...
|
||||
Initialize the operation - property creation and property editor status.'''
|
||||
self.initOpProperties(obj)
|
||||
'''initOperation(obj) ... Initialize the operation by
|
||||
managing property creation and property editor status.'''
|
||||
self.propertiesReady = False
|
||||
|
||||
self.initOpProperties(obj) # Initialize operation-specific properties
|
||||
|
||||
# For debugging
|
||||
if PathLog.getLevel(PathLog.thisModule()) != 4:
|
||||
@@ -95,28 +93,30 @@ class ObjectWaterline(PathOp.ObjectOp):
|
||||
|
||||
def initOpProperties(self, obj, warn=False):
|
||||
'''initOpProperties(obj) ... create operation specific properties'''
|
||||
missing = list()
|
||||
self.addNewProps = list()
|
||||
|
||||
for (prtyp, nm, grp, tt) in self.opProperties():
|
||||
for (prtyp, nm, grp, tt) in self.opPropertyDefinitions():
|
||||
if not hasattr(obj, nm):
|
||||
obj.addProperty(prtyp, nm, grp, tt)
|
||||
missing.append(nm)
|
||||
if warn:
|
||||
newPropMsg = translate('PathWaterline', 'New property added to') + ' "{}": '.format(obj.Label) + nm + '. '
|
||||
newPropMsg += translate('PathWaterline', 'Check its default value.')
|
||||
PathLog.warning(newPropMsg)
|
||||
self.addNewProps.append(nm)
|
||||
|
||||
# Set enumeration lists for enumeration properties
|
||||
if len(missing) > 0:
|
||||
ENUMS = self.propertyEnumerations()
|
||||
if len(self.addNewProps) > 0:
|
||||
ENUMS = self.opPropertyEnumerations()
|
||||
for n in ENUMS:
|
||||
if n in missing:
|
||||
if n in self.addNewProps:
|
||||
setattr(obj, n, ENUMS[n])
|
||||
|
||||
self.addedAllProperties = True
|
||||
if warn:
|
||||
newPropMsg = translate('PathWaterline', 'New property added to')
|
||||
newPropMsg += ' "{}": {}'.format(obj.Label, self.addNewProps) + '. '
|
||||
newPropMsg += translate('PathWaterline', 'Check default value(s).')
|
||||
FreeCAD.Console.PrintWarning(newPropMsg + '\n')
|
||||
|
||||
def opProperties(self):
|
||||
'''opProperties() ... return list of tuples containing operation specific properties'''
|
||||
self.propertiesReady = True
|
||||
|
||||
def opPropertyDefinitions(self):
|
||||
'''opPropertyDefinitions() ... return list of tuples containing operation specific properties'''
|
||||
return [
|
||||
("App::PropertyBool", "ShowTempObjects", "Debug",
|
||||
QtCore.QT_TRANSLATE_NOOP("App::Property", "Show the temporary path construction objects when module is in DEBUG mode.")),
|
||||
@@ -167,7 +167,7 @@ class ObjectWaterline(PathOp.ObjectOp):
|
||||
QtCore.QT_TRANSLATE_NOOP("App::Property", "Choose location of the center point for starting the cut pattern.")),
|
||||
("App::PropertyDistance", "SampleInterval", "Clearing Options",
|
||||
QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the sampling resolution. Smaller values quickly increase processing time.")),
|
||||
("App::PropertyPercent", "StepOver", "Clearing Options",
|
||||
("App::PropertyFloat", "StepOver", "Clearing Options",
|
||||
QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the stepover percentage, based on the tool's diameter.")),
|
||||
|
||||
("App::PropertyBool", "OptimizeLinearPaths", "Optimization",
|
||||
@@ -185,7 +185,7 @@ class ObjectWaterline(PathOp.ObjectOp):
|
||||
QtCore.QT_TRANSLATE_NOOP("App::Property", "Make True, if specifying a Start Point"))
|
||||
]
|
||||
|
||||
def propertyEnumerations(self):
|
||||
def opPropertyEnumerations(self):
|
||||
# Enumeration lists for App::PropertyEnumeration properties
|
||||
return {
|
||||
'Algorithm': ['OCL Dropcutter', 'Experimental'],
|
||||
@@ -198,6 +198,56 @@ class ObjectWaterline(PathOp.ObjectOp):
|
||||
'LayerMode': ['Single-pass', 'Multi-pass'],
|
||||
}
|
||||
|
||||
def opPropertyDefaults(self, obj, job):
|
||||
'''opPropertyDefaults(obj, job) ... returns a dictionary
|
||||
of default values for the operation's properties.'''
|
||||
defaults = {
|
||||
'OptimizeLinearPaths': True,
|
||||
'InternalFeaturesCut': True,
|
||||
'OptimizeStepOverTransitions': False,
|
||||
'BoundaryEnforcement': True,
|
||||
'UseStartPoint': False,
|
||||
'AvoidLastX_InternalFeatures': True,
|
||||
'CutPatternReversed': False,
|
||||
'IgnoreOuterAbove': obj.StartDepth.Value + 0.00001,
|
||||
'StartPoint': FreeCAD.Vector(0.0, 0.0, obj.ClearanceHeight.Value),
|
||||
'Algorithm': 'OCL Dropcutter',
|
||||
'LayerMode': 'Single-pass',
|
||||
'CutMode': 'Conventional',
|
||||
'CutPattern': 'None',
|
||||
'HandleMultipleFeatures': 'Collectively',
|
||||
'PatternCenterAt': 'CenterOfMass',
|
||||
'GapSizes': 'No gaps identified.',
|
||||
'ClearLastLayer': 'Off',
|
||||
'StepOver': 100.0,
|
||||
'CutPatternAngle': 0.0,
|
||||
'DepthOffset': 0.0,
|
||||
'SampleInterval': 1.0,
|
||||
'BoundaryAdjustment': 0.0,
|
||||
'InternalFeaturesAdjustment': 0.0,
|
||||
'AvoidLastX_Faces': 0,
|
||||
'PatternCenterCustom': FreeCAD.Vector(0.0, 0.0, 0.0),
|
||||
'GapThreshold': 0.005,
|
||||
'AngularDeflection': 0.25,
|
||||
'LinearDeflection': 0.0001,
|
||||
# For debugging
|
||||
'ShowTempObjects': False
|
||||
}
|
||||
|
||||
warn = True
|
||||
if hasattr(job, 'GeometryTolerance'):
|
||||
if job.GeometryTolerance.Value != 0.0:
|
||||
warn = False
|
||||
defaults['LinearDeflection'] = job.GeometryTolerance.Value
|
||||
if warn:
|
||||
msg = translate('PathWaterline',
|
||||
'The GeometryTolerance for this Job is 0.0.')
|
||||
msg += translate('PathWaterline',
|
||||
'Initializing LinearDeflection to 0.0001 mm.')
|
||||
FreeCAD.Console.PrintWarning(msg + '\n')
|
||||
|
||||
return defaults
|
||||
|
||||
def setEditorProperties(self, obj):
|
||||
# Used to hide inputs in properties list
|
||||
expMode = G = 0
|
||||
@@ -251,21 +301,23 @@ class ObjectWaterline(PathOp.ObjectOp):
|
||||
obj.setEditorMode('AngularDeflection', expMode)
|
||||
|
||||
def onChanged(self, obj, prop):
|
||||
if hasattr(self, 'addedAllProperties'):
|
||||
if self.addedAllProperties is True:
|
||||
if hasattr(self, 'propertiesReady'):
|
||||
if self.propertiesReady:
|
||||
if prop in ['Algorithm', 'CutPattern']:
|
||||
self.setEditorProperties(obj)
|
||||
|
||||
def opOnDocumentRestored(self, obj):
|
||||
self.initOpProperties(obj, warn=True)
|
||||
self.propertiesReady = False
|
||||
job = PathUtils.findParentJob(obj)
|
||||
|
||||
if PathLog.getLevel(PathLog.thisModule()) != 4:
|
||||
obj.setEditorMode('ShowTempObjects', 2) # hide
|
||||
else:
|
||||
obj.setEditorMode('ShowTempObjects', 0) # show
|
||||
self.initOpProperties(obj, warn=True)
|
||||
self.opApplyPropertyDefaults(obj, job, self.addNewProps)
|
||||
|
||||
mode = 2 if PathLog.getLevel(PathLog.thisModule()) != 4 else 0
|
||||
obj.setEditorMode('ShowTempObjects', mode)
|
||||
|
||||
# Repopulate enumerations in case of changes
|
||||
ENUMS = self.propertyEnumerations()
|
||||
ENUMS = self.opPropertyEnumerations()
|
||||
for n in ENUMS:
|
||||
restore = False
|
||||
if hasattr(obj, n):
|
||||
@@ -277,40 +329,28 @@ class ObjectWaterline(PathOp.ObjectOp):
|
||||
|
||||
self.setEditorProperties(obj)
|
||||
|
||||
def opApplyPropertyDefaults(self, obj, job, propList):
|
||||
# Set standard property defaults
|
||||
PROP_DFLTS = self.opPropertyDefaults(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 opSetDefaultValues(self, obj, job):
|
||||
'''opSetDefaultValues(obj, job) ... initialize defaults'''
|
||||
job = PathUtils.findParentJob(obj)
|
||||
|
||||
obj.OptimizeLinearPaths = True
|
||||
obj.InternalFeaturesCut = True
|
||||
obj.OptimizeStepOverTransitions = False
|
||||
obj.BoundaryEnforcement = True
|
||||
obj.UseStartPoint = False
|
||||
obj.AvoidLastX_InternalFeatures = True
|
||||
obj.CutPatternReversed = False
|
||||
obj.IgnoreOuterAbove = obj.StartDepth.Value + 0.00001
|
||||
obj.StartPoint = FreeCAD.Vector(0.0, 0.0, obj.ClearanceHeight.Value)
|
||||
obj.Algorithm = 'OCL Dropcutter'
|
||||
obj.LayerMode = 'Single-pass'
|
||||
obj.CutMode = 'Conventional'
|
||||
obj.CutPattern = 'None'
|
||||
obj.HandleMultipleFeatures = 'Collectively' # 'Individually'
|
||||
obj.PatternCenterAt = 'CenterOfMass' # 'CenterOfBoundBox', 'XminYmin', 'Custom'
|
||||
obj.GapSizes = 'No gaps identified.'
|
||||
obj.ClearLastLayer = 'Off'
|
||||
obj.StepOver = 100
|
||||
obj.CutPatternAngle = 0.0
|
||||
obj.DepthOffset.Value = 0.0
|
||||
obj.SampleInterval.Value = 1.0
|
||||
obj.BoundaryAdjustment.Value = 0.0
|
||||
obj.InternalFeaturesAdjustment.Value = 0.0
|
||||
obj.AvoidLastX_Faces = 0
|
||||
obj.PatternCenterCustom = FreeCAD.Vector(0.0, 0.0, 0.0)
|
||||
obj.GapThreshold.Value = 0.005
|
||||
obj.LinearDeflection.Value = 0.0001
|
||||
obj.AngularDeflection.Value = 0.25
|
||||
# For debugging
|
||||
obj.ShowTempObjects = False
|
||||
self.opApplyPropertyDefaults(obj, job, self.addNewProps)
|
||||
|
||||
# need to overwrite the default depth calculations for facing
|
||||
d = None
|
||||
@@ -353,10 +393,10 @@ class ObjectWaterline(PathOp.ObjectOp):
|
||||
PathLog.error(translate('PathWaterline', 'Cut pattern angle limits are +- 360 degrees.'))
|
||||
|
||||
# Limit StepOver to natural number percentage
|
||||
if obj.StepOver > 100:
|
||||
obj.StepOver = 100
|
||||
if obj.StepOver < 1:
|
||||
obj.StepOver = 1
|
||||
if obj.StepOver > 100.0:
|
||||
obj.StepOver = 100.0
|
||||
if obj.StepOver < 1.0:
|
||||
obj.StepOver = 1.0
|
||||
|
||||
# Limit AvoidLastX_Faces to zero and positive values
|
||||
if obj.AvoidLastX_Faces < 0:
|
||||
@@ -536,13 +576,12 @@ class ObjectWaterline(PathOp.ObjectOp):
|
||||
self.modelSTLs = PSF.modelSTLs
|
||||
self.profileShapes = PSF.profileShapes
|
||||
|
||||
# Create OCL.stl model objects
|
||||
if obj.Algorithm == 'OCL Dropcutter':
|
||||
self._prepareModelSTLs(JOB, obj)
|
||||
PathLog.debug('obj.LinearDeflection.Value: {}'.format(obj.LinearDeflection.Value))
|
||||
PathLog.debug('obj.AngularDeflection.Value: {}'.format(obj.AngularDeflection.Value))
|
||||
|
||||
for m in range(0, len(JOB.Model.Group)):
|
||||
# Create OCL.stl model objects
|
||||
if obj.Algorithm == 'OCL Dropcutter':
|
||||
PathSurfaceSupport._prepareModelSTLs(self, JOB, obj, m, ocl)
|
||||
|
||||
Mdl = JOB.Model.Group[m]
|
||||
if FACES[m] is False:
|
||||
PathLog.error('No data for model base: {}'.format(JOB.Model.Group[m].Label))
|
||||
@@ -554,7 +593,7 @@ class ObjectWaterline(PathOp.ObjectOp):
|
||||
PathLog.info('Working on Model.Group[{}]: {}'.format(m, Mdl.Label))
|
||||
# make stock-model-voidShapes STL model for avoidance detection on transitions
|
||||
if obj.Algorithm == 'OCL Dropcutter':
|
||||
self._makeSafeSTL(JOB, obj, m, FACES[m], VOIDS[m])
|
||||
PathSurfaceSupport._makeSafeSTL(self, JOB, obj, m, FACES[m], VOIDS[m], ocl)
|
||||
# Process model/faces - OCL objects must be ready
|
||||
CMDS.extend(self._processWaterlineAreas(JOB, obj, m, FACES[m], VOIDS[m]))
|
||||
|
||||
@@ -627,114 +666,7 @@ class ObjectWaterline(PathOp.ObjectOp):
|
||||
|
||||
return True
|
||||
|
||||
# Methods for constructing the cut area
|
||||
def _prepareModelSTLs(self, JOB, obj):
|
||||
PathLog.debug('_prepareModelSTLs()')
|
||||
for m in range(0, len(JOB.Model.Group)):
|
||||
M = JOB.Model.Group[m]
|
||||
|
||||
# PathLog.debug(f" -self.modelTypes[{m}] == 'M'")
|
||||
if self.modelTypes[m] == 'M':
|
||||
# TODO: test if this works
|
||||
facets = M.Mesh.Facets.Points
|
||||
else:
|
||||
facets = Part.getFacets(M.Shape)
|
||||
|
||||
if self.modelSTLs[m] is True:
|
||||
stl = ocl.STLSurf()
|
||||
|
||||
for tri in facets:
|
||||
t = ocl.Triangle(ocl.Point(tri[0][0], tri[0][1], tri[0][2]),
|
||||
ocl.Point(tri[1][0], tri[1][1], tri[1][2]),
|
||||
ocl.Point(tri[2][0], tri[2][1], tri[2][2]))
|
||||
stl.addTriangle(t)
|
||||
self.modelSTLs[m] = stl
|
||||
return
|
||||
|
||||
def _makeSafeSTL(self, JOB, obj, mdlIdx, faceShapes, voidShapes):
|
||||
'''_makeSafeSTL(JOB, obj, mdlIdx, faceShapes, voidShapes)...
|
||||
Creates and OCL.stl object with combined data with waste stock,
|
||||
model, and avoided faces. Travel lines can be checked against this
|
||||
STL object to determine minimum travel height to clear stock and model.'''
|
||||
PathLog.debug('_makeSafeSTL()')
|
||||
|
||||
fuseShapes = list()
|
||||
Mdl = JOB.Model.Group[mdlIdx]
|
||||
mBB = Mdl.Shape.BoundBox
|
||||
sBB = JOB.Stock.Shape.BoundBox
|
||||
|
||||
# add Model shape to safeSTL shape
|
||||
fuseShapes.append(Mdl.Shape)
|
||||
|
||||
if obj.BoundBox == 'BaseBoundBox':
|
||||
cont = False
|
||||
extFwd = (sBB.ZLength)
|
||||
zmin = mBB.ZMin
|
||||
zmax = mBB.ZMin + extFwd
|
||||
stpDwn = (zmax - zmin) / 4.0
|
||||
dep_par = PathUtils.depth_params(zmax + 5.0, zmax + 3.0, zmax, stpDwn, 0.0, zmin)
|
||||
|
||||
try:
|
||||
envBB = PathUtils.getEnvelope(partshape=Mdl.Shape, depthparams=dep_par) # Produces .Shape
|
||||
cont = True
|
||||
except Exception as ee:
|
||||
PathLog.error(str(ee))
|
||||
shell = Mdl.Shape.Shells[0]
|
||||
solid = Part.makeSolid(shell)
|
||||
try:
|
||||
envBB = PathUtils.getEnvelope(partshape=solid, depthparams=dep_par) # Produces .Shape
|
||||
cont = True
|
||||
except Exception as eee:
|
||||
PathLog.error(str(eee))
|
||||
|
||||
if cont:
|
||||
stckWst = JOB.Stock.Shape.cut(envBB)
|
||||
if obj.BoundaryAdjustment > 0.0:
|
||||
cmpndFS = Part.makeCompound(faceShapes)
|
||||
baBB = PathUtils.getEnvelope(partshape=cmpndFS, depthparams=self.depthParams) # Produces .Shape
|
||||
adjStckWst = stckWst.cut(baBB)
|
||||
else:
|
||||
adjStckWst = stckWst
|
||||
fuseShapes.append(adjStckWst)
|
||||
else:
|
||||
PathLog.warning('Path transitions might not avoid the model. Verify paths.')
|
||||
else:
|
||||
# If boundbox is Job.Stock, add hidden pad under stock as base plate
|
||||
toolDiam = self.cutter.getDiameter()
|
||||
zMin = JOB.Stock.Shape.BoundBox.ZMin
|
||||
xMin = JOB.Stock.Shape.BoundBox.XMin - toolDiam
|
||||
yMin = JOB.Stock.Shape.BoundBox.YMin - toolDiam
|
||||
bL = JOB.Stock.Shape.BoundBox.XLength + (2 * toolDiam)
|
||||
bW = JOB.Stock.Shape.BoundBox.YLength + (2 * toolDiam)
|
||||
bH = 1.0
|
||||
crnr = FreeCAD.Vector(xMin, yMin, zMin - 1.0)
|
||||
B = Part.makeBox(bL, bW, bH, crnr, FreeCAD.Vector(0, 0, 1))
|
||||
fuseShapes.append(B)
|
||||
|
||||
if voidShapes is not False:
|
||||
voidComp = Part.makeCompound(voidShapes)
|
||||
voidEnv = PathUtils.getEnvelope(partshape=voidComp, depthparams=self.depthParams) # Produces .Shape
|
||||
fuseShapes.append(voidEnv)
|
||||
|
||||
fused = Part.makeCompound(fuseShapes)
|
||||
|
||||
if self.showDebugObjects is True:
|
||||
T = FreeCAD.ActiveDocument.addObject('Part::Feature', 'safeSTLShape')
|
||||
T.Shape = fused
|
||||
T.purgeTouched()
|
||||
self.tempGroup.addObject(T)
|
||||
|
||||
facets = Part.getFacets(fused)
|
||||
|
||||
stl = ocl.STLSurf()
|
||||
for tri in facets:
|
||||
t = ocl.Triangle(ocl.Point(tri[0][0], tri[0][1], tri[0][2]),
|
||||
ocl.Point(tri[1][0], tri[1][1], tri[1][2]),
|
||||
ocl.Point(tri[2][0], tri[2][1], tri[2][2]))
|
||||
stl.addTriangle(t)
|
||||
|
||||
self.safeSTLs[mdlIdx] = stl
|
||||
|
||||
# Methods for constructing the cut area and creating path geometry
|
||||
def _processWaterlineAreas(self, JOB, obj, mdlIdx, FCS, VDS):
|
||||
'''_processWaterlineAreas(JOB, obj, mdlIdx, FCS, VDS)...
|
||||
This method applies any avoided faces or regions to the selected faces.
|
||||
@@ -787,7 +719,6 @@ class ObjectWaterline(PathOp.ObjectOp):
|
||||
|
||||
return final
|
||||
|
||||
# Methods for creating path geometry
|
||||
def _getExperimentalWaterlinePaths(self, PNTSET, csHght, cutPattern):
|
||||
'''_getExperimentalWaterlinePaths(PNTSET, csHght, cutPattern)...
|
||||
Switching function for calling the appropriate path-geometry to OCL points conversion function
|
||||
@@ -864,7 +795,6 @@ class ObjectWaterline(PathOp.ObjectOp):
|
||||
height = first.z
|
||||
elif (minSTH + (2.0 * tolrnc)) >= max(first.z, lstPnt.z):
|
||||
height = False # allow end of Zig to cut to beginning of Zag
|
||||
|
||||
|
||||
# Create raise, shift, and optional lower commands
|
||||
if height is not False:
|
||||
@@ -979,7 +909,9 @@ class ObjectWaterline(PathOp.ObjectOp):
|
||||
scanLines[L].append(oclScan[pi])
|
||||
lenSL = len(scanLines)
|
||||
pntsPerLine = len(scanLines[0])
|
||||
PathLog.debug("--OCL scan: " + str(lenSL * pntsPerLine) + " points, with " + str(numScanLines) + " lines and " + str(pntsPerLine) + " pts/line")
|
||||
msg = "--OCL scan: " + str(lenSL * pntsPerLine) + " points, with "
|
||||
msg += str(numScanLines) + " lines and " + str(pntsPerLine) + " pts/line"
|
||||
PathLog.debug(msg)
|
||||
|
||||
# Extract Wl layers per depthparams
|
||||
lyr = 0
|
||||
@@ -1694,7 +1626,7 @@ class ObjectWaterline(PathOp.ObjectOp):
|
||||
return False
|
||||
|
||||
def _getModelCrossSection(self, shape, csHght):
|
||||
PathLog.debug('getCrossSection()')
|
||||
PathLog.debug('_getModelCrossSection()')
|
||||
wires = list()
|
||||
|
||||
def byArea(fc):
|
||||
|
||||
@@ -107,8 +107,8 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
|
||||
|
||||
return signals
|
||||
|
||||
def updateVisibility(self):
|
||||
'''updateVisibility()... Updates visibility of Tasks panel objects.'''
|
||||
def updateVisibility(self, sentObj=None):
|
||||
'''updateVisibility(sentObj=None)... Updates visibility of Tasks panel objects.'''
|
||||
Algorithm = self.form.algorithmSelect.currentText()
|
||||
self.form.optimizeEnabled.hide() # Has no independent QLabel object
|
||||
|
||||
|
||||
Reference in New Issue
Block a user