Merge pull request #5442 from sliptonic/bug/translationAreaOp

[PATH] Bug/translation cleanup (surface, waterline, propertybag, area ops)
This commit is contained in:
sliptonic
2022-01-27 11:39:44 -06:00
committed by GitHub
15 changed files with 1787 additions and 1411 deletions

View File

@@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>420</width>
<width>498</width>
<height>541</height>
</rect>
</property>
@@ -82,7 +82,7 @@
</sizepolicy>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Additional offset to the selected bounding box along the X axis."&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Additional offset to the selected bounding box along the X axis.&quot;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="unit" stdset="0">
<string notr="true">mm</string>
@@ -92,7 +92,7 @@
<item>
<widget class="Gui::InputField" name="boundBoxExtraOffsetY">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Additional offset to the selected bounding box along the Y axis."&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Additional offset to the selected bounding box along the Y axis.&quot;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="unit" stdset="0">
<string notr="true">mm</string>
@@ -133,16 +133,6 @@
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select the overall boundary for the operation.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<item>
<property name="text">
<string>Stock</string>
</property>
</item>
<item>
<property name="text">
<string>BaseBoundBox</string>
</property>
</item>
</widget>
</item>
<item row="15" column="1">
@@ -160,26 +150,6 @@
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Profile the edges of the selection.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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">
@@ -194,36 +164,6 @@
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Set the geometric clearing pattern to use for the operation.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<item>
<property name="text">
<string>Circular</string>
</property>
</item>
<item>
<property name="text">
<string>CircularZigZag</string>
</property>
</item>
<item>
<property name="text">
<string>Line</string>
</property>
</item>
<item>
<property name="text">
<string>Offset</string>
</property>
</item>
<item>
<property name="text">
<string>Spiral</string>
</property>
</item>
<item>
<property name="text">
<string>ZigZag</string>
</property>
</item>
</widget>
</item>
<item row="1" column="1" colspan="2">
@@ -231,16 +171,6 @@
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Planar: Flat, 3D surface scan. Rotational: 4th-axis rotational scan.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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">
@@ -262,16 +192,6 @@
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Complete the operation in a single pass at depth, or mulitiple passes to final depth.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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">
@@ -293,16 +213,6 @@
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Dropcutter lines are created parallel to this axis.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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">

View File

@@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>350</width>
<width>389</width>
<height>400</height>
</rect>
</property>
@@ -53,87 +53,80 @@
<item>
<widget class="QWidget" name="widget" native="true">
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="3">
<item row="0" column="0">
<widget class="QLabel" name="algorithmSelect_label">
<property name="text">
<string>Algorithm</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="algorithmSelect">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select the algorithm to use: OCL Dropcutter*, or Experimental (Not OCL based).&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="boundBoxSelect_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>BoundBox</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="boundBoxSelect">
<property name="font">
<font>
<pointsize>8</pointsize>
<pointsize>12</pointsize>
</font>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select the overall boundary for the operation.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<item>
<property name="text">
<string>Stock</string>
</property>
</item>
<item>
<property name="text">
<string>BaseBoundBox</string>
</property>
</item>
</widget>
</item>
<item row="4" column="3">
<widget class="QLineEdit" name="boundaryAdjustment">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Positive values push the cutter toward, or beyond, the boundary. Negative values retract the cutter away from the boundary.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<item row="2" column="0">
<widget class="QLabel" name="layerMode_label">
<property name="text">
<string>Layer Mode</string>
</property>
</widget>
</item>
<item row="2" column="3">
<item row="2" column="1">
<widget class="QComboBox" name="layerMode">
<property name="font">
<font>
<pointsize>8</pointsize>
<pointsize>12</pointsize>
</font>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Complete the operation in a single pass at depth, or mulitiple passes to final depth.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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="0" column="3">
<widget class="QComboBox" name="algorithmSelect">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select the algorithm to use: OCL Dropcutter*, or Experimental (Not OCL based).&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<item>
<property name="text">
<string>OCL Dropcutter</string>
</property>
</item>
<item>
<property name="text">
<string>Experimental</string>
</property>
</item>
</widget>
</item>
<item row="11" column="3">
<widget class="QCheckBox" name="optimizeEnabled">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enable optimization of linear paths (co-linear points). Removes unnecessary co-linear points from G-Code output.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<item row="3" column="0">
<widget class="QLabel" name="cutPattern_label">
<property name="text">
<string>Optimize Linear Paths</string>
<string>Cut Pattern</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="cutPattern">
<property name="font">
<font>
<pointsize>12</pointsize>
</font>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Set the geometric clearing pattern to use for the operation.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
@@ -150,54 +143,24 @@
</property>
</widget>
</item>
<item row="3" column="3">
<widget class="QComboBox" name="cutPattern">
<property name="font">
<font>
<pointsize>8</pointsize>
</font>
</property>
<item row="4" column="1">
<widget class="Gui::InputField" name="boundaryAdjustment">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Set the geometric clearing pattern to use for the operation.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Set the Z-axis depth offset from the target surface.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="unit" stdset="0">
<string notr="true">mm</string>
</property>
<item>
<property name="text">
<string>None</string>
</property>
</item>
<item>
<property name="text">
<string>Circular</string>
</property>
</item>
<item>
<property name="text">
<string>CircularZigZag</string>
</property>
</item>
<item>
<property name="text">
<string>Line</string>
</property>
</item>
<item>
<property name="text">
<string>Offset</string>
</property>
</item>
<item>
<property name="text">
<string>Spiral</string>
</property>
</item>
<item>
<property name="text">
<string>ZigZag</string>
</property>
</item>
</widget>
</item>
<item row="8" column="3">
<item row="5" column="0">
<widget class="QLabel" name="stepOver_label">
<property name="text">
<string>Step over</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QSpinBox" name="stepOver">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
@@ -216,35 +179,15 @@
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="layerMode_label">
<item row="6" column="0">
<widget class="QLabel" name="sampleInterval_label">
<property name="text">
<string>Layer Mode</string>
<string>Sample interval</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="boundBoxSelect_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>BoundBox</string>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="stepOver_label">
<property name="text">
<string>Step over</string>
</property>
</widget>
</item>
<item row="9" column="3">
<widget class="Gui::InputField" name="sampleInterval" native="true">
<item row="6" column="1">
<widget class="Gui::InputField" name="sampleInterval">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Set the sampling resolution. Smaller values quickly increase processing time.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
@@ -253,24 +196,13 @@
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="cutPattern_label">
<property name="text">
<string>Cut Pattern</string>
<item row="7" column="1">
<widget class="QCheckBox" name="optimizeEnabled">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enable optimization of linear paths (co-linear points). Removes unnecessary co-linear points from G-Code output.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="sampleInterval_label">
<property name="text">
<string>Sample interval</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="algorithmSelect_label">
<property name="text">
<string>Algorithm</string>
<string>Optimize Linear Paths</string>
</property>
</widget>
</item>
@@ -306,7 +238,6 @@
<tabstop>boundBoxSelect</tabstop>
<tabstop>layerMode</tabstop>
<tabstop>cutPattern</tabstop>
<tabstop>boundaryAdjustment</tabstop>
<tabstop>stepOver</tabstop>
<tabstop>sampleInterval</tabstop>
<tabstop>optimizeEnabled</tabstop>

View File

@@ -20,22 +20,20 @@
# * *
# ***************************************************************************
from PySide.QtCore import QT_TRANSLATE_NOOP
import FreeCAD
import Path
import PathScripts.PathLog as PathLog
import PathScripts.PathOp as PathOp
import PathScripts.PathUtils as PathUtils
import PathScripts.PathGeom as PathGeom
import math
from PySide import QtCore
# lazily loaded modules
from lazy_loader.lazy_loader import LazyLoader
Draft = LazyLoader('Draft', globals(), 'Draft')
Part = LazyLoader('Part', globals(), 'Part')
if FreeCAD.GuiUp:
import FreeCADGui
Draft = LazyLoader("Draft", globals(), "Draft")
Part = LazyLoader("Part", globals(), "Part")
__title__ = "Base class for PathArea based operations."
__author__ = "sliptonic (Brad Collette)"
@@ -44,95 +42,112 @@ __doc__ = "Base class and properties for Path.Area based operations."
__contributors__ = "russ4262 (Russell Johnson)"
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
# PathLog.trackModule(PathLog.thisModule())
if False:
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
PathLog.trackModule(PathLog.thisModule())
else:
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
# Qt translation handling
def translate(context, text, disambig=None):
return QtCore.QCoreApplication.translate(context, text, disambig)
translate = FreeCAD.Qt.translate
class ObjectOp(PathOp.ObjectOp):
'''Base class for all Path.Area based operations.
"""Base class for all Path.Area based operations.
Provides standard features including debugging properties AreaParams,
PathParams and removalshape, all hidden.
The main reason for existence is to implement the standard interface
to Path.Area so subclasses only have to provide the shapes for the
operations.'''
operations."""
def opFeatures(self, obj):
'''opFeatures(obj) ... returns the base features supported by all Path.Area based operations.
"""opFeatures(obj) ... returns the base features supported by all Path.Area based operations.
The standard feature list is OR'ed with the return value of areaOpFeatures().
Do not overwrite, implement areaOpFeatures(obj) instead.'''
return PathOp.FeatureTool | PathOp.FeatureDepths | PathOp.FeatureStepDown \
| PathOp.FeatureHeights | PathOp.FeatureStartPoint \
| self.areaOpFeatures(obj) | PathOp.FeatureCoolant
Do not overwrite, implement areaOpFeatures(obj) instead."""
return (
PathOp.FeatureTool
| PathOp.FeatureDepths
| PathOp.FeatureStepDown
| PathOp.FeatureHeights
| PathOp.FeatureStartPoint
| self.areaOpFeatures(obj)
| PathOp.FeatureCoolant
)
def areaOpFeatures(self, obj):
'''areaOpFeatures(obj) ... overwrite to add operation specific features.
Can safely be overwritten by subclasses.'''
"""areaOpFeatures(obj) ... overwrite to add operation specific features.
Can safely be overwritten by subclasses."""
# pylint: disable=unused-argument
return 0
def initOperation(self, obj):
'''initOperation(obj) ... sets up standard Path.Area properties and calls initAreaOp().
Do not overwrite, overwrite initAreaOp(obj) instead.'''
"""initOperation(obj) ... sets up standard Path.Area properties and calls initAreaOp().
Do not overwrite, overwrite initAreaOp(obj) instead."""
PathLog.track()
# Debugging
obj.addProperty("App::PropertyString", "AreaParams", "Path")
obj.setEditorMode('AreaParams', 2) # hide
obj.setEditorMode("AreaParams", 2) # hide
obj.addProperty("App::PropertyString", "PathParams", "Path")
obj.setEditorMode('PathParams', 2) # hide
obj.setEditorMode("PathParams", 2) # hide
obj.addProperty("Part::PropertyPartShape", "removalshape", "Path")
obj.setEditorMode('removalshape', 2) # hide
obj.setEditorMode("removalshape", 2) # hide
obj.addProperty("App::PropertyBool", "SplitArcs", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "Split Arcs into discrete segments"))
obj.addProperty(
"App::PropertyBool",
"SplitArcs",
"Path",
QT_TRANSLATE_NOOP("App::Property", "Split Arcs into discrete segments"),
)
# obj.Proxy = self
self.initAreaOp(obj)
def initAreaOp(self, obj):
'''initAreaOp(obj) ... overwrite if the receiver class needs initialisation.
Can safely be overwritten by subclasses.'''
pass # pylint: disable=unnecessary-pass
"""initAreaOp(obj) ... overwrite if the receiver class needs initialisation.
Can safely be overwritten by subclasses."""
pass # pylint: disable=unnecessary-pass
def areaOpShapeForDepths(self, obj, job):
'''areaOpShapeForDepths(obj) ... returns the shape used to make an initial calculation for the depths being used.
The default implementation returns the job's Base.Shape'''
"""areaOpShapeForDepths(obj) ... returns the shape used to make an initial calculation for the depths being used.
The default implementation returns the job's Base.Shape"""
if job:
if job.Stock:
PathLog.debug("job=%s base=%s shape=%s" % (job, job.Stock, job.Stock.Shape))
PathLog.debug(
"job=%s base=%s shape=%s" % (job, job.Stock, job.Stock.Shape)
)
return job.Stock.Shape
else:
PathLog.warning(translate("PathAreaOp", "job %s has no Base.") % job.Label)
PathLog.warning(
translate("PathAreaOp", "job %s has no Base.") % job.Label
)
else:
PathLog.warning(translate("PathAreaOp", "no job for op %s found.") % obj.Label)
PathLog.warning(
translate("PathAreaOp", "no job for op %s found.") % obj.Label
)
return None
def areaOpOnChanged(self, obj, prop):
'''areaOpOnChanged(obj, porp) ... overwrite to process operation specific changes to properties.
Can safely be overwritten by subclasses.'''
pass # pylint: disable=unnecessary-pass
"""areaOpOnChanged(obj, porp) ... overwrite to process operation specific changes to properties.
Can safely be overwritten by subclasses."""
pass # pylint: disable=unnecessary-pass
def opOnChanged(self, obj, prop):
'''opOnChanged(obj, prop) ... base implementation of the notification framework - do not overwrite.
"""opOnChanged(obj, prop) ... base implementation of the notification framework - do not overwrite.
The base implementation takes a stab at determining Heights and Depths if the operations's Base
changes.
Do not overwrite, overwrite areaOpOnChanged(obj, prop) instead.'''
Do not overwrite, overwrite areaOpOnChanged(obj, prop) instead."""
# PathLog.track(obj.Label, prop)
if prop in ['AreaParams', 'PathParams', 'removalshape']:
if prop in ["AreaParams", "PathParams", "removalshape"]:
obj.setEditorMode(prop, 2)
if prop == 'Base' and len(obj.Base) == 1:
if prop == "Base" and len(obj.Base) == 1:
(base, sub) = obj.Base[0]
bb = base.Shape.BoundBox # parent boundbox
subobj = base.Shape.getElement(sub[0])
fbb = subobj.BoundBox # feature boundbox
if hasattr(obj, 'Side'):
if hasattr(obj, "Side"):
if bb.XLength == fbb.XLength and bb.YLength == fbb.YLength:
obj.Side = "Outside"
else:
@@ -141,29 +156,34 @@ class ObjectOp(PathOp.ObjectOp):
self.areaOpOnChanged(obj, prop)
def opOnDocumentRestored(self, obj):
for prop in ['AreaParams', 'PathParams', 'removalshape']:
for prop in ["AreaParams", "PathParams", "removalshape"]:
if hasattr(obj, prop):
obj.setEditorMode(prop, 2)
if not hasattr(obj, 'SplitArcs'):
obj.addProperty("App::PropertyBool", "SplitArcs", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "Split Arcs into discrete segments"))
if not hasattr(obj, "SplitArcs"):
obj.addProperty(
"App::PropertyBool",
"SplitArcs",
"Path",
QT_TRANSLATE_NOOP("App::Property", "Split Arcs into discrete segments"),
)
self.areaOpOnDocumentRestored(obj)
def areaOpOnDocumentRestored(self, obj):
'''areaOpOnDocumentRestored(obj) ... overwrite to fully restore receiver'''
pass # pylint: disable=unnecessary-pass
"""areaOpOnDocumentRestored(obj) ... overwrite to fully restore receiver"""
pass # pylint: disable=unnecessary-pass
def opSetDefaultValues(self, obj, job):
'''opSetDefaultValues(obj) ... base implementation, do not overwrite.
"""opSetDefaultValues(obj) ... base implementation, do not overwrite.
The base implementation sets the depths and heights based on the
areaOpShapeForDepths() return value.
Do not overwrite, overwrite areaOpSetDefaultValues(obj, job) instead.'''
Do not overwrite, overwrite areaOpSetDefaultValues(obj, job) instead."""
PathLog.debug("opSetDefaultValues(%s, %s)" % (obj.Label, job.Label))
if PathOp.FeatureDepths & self.opFeatures(obj):
try:
shape = self.areaOpShapeForDepths(obj, job)
except Exception as ee: # pylint: disable=broad-except
except Exception as ee: # pylint: disable=broad-except
PathLog.error(ee)
shape = None
@@ -182,136 +202,158 @@ class ObjectOp(PathOp.ObjectOp):
obj.OpStartDepth.Value = startDepth
obj.OpFinalDepth.Value = finalDepth
PathLog.debug("Default OpDepths are Start: {}, and Final: {}".format(obj.OpStartDepth.Value, obj.OpFinalDepth.Value))
PathLog.debug("Default Depths are Start: {}, and Final: {}".format(startDepth, finalDepth))
PathLog.debug(
"Default OpDepths are Start: {}, and Final: {}".format(
obj.OpStartDepth.Value, obj.OpFinalDepth.Value
)
)
PathLog.debug(
"Default Depths are Start: {}, and Final: {}".format(
startDepth, finalDepth
)
)
self.areaOpSetDefaultValues(obj, job)
def areaOpSetDefaultValues(self, obj, job):
'''areaOpSetDefaultValues(obj, job) ... overwrite to set initial values of operation specific properties.
Can safely be overwritten by subclasses.'''
pass # pylint: disable=unnecessary-pass
"""areaOpSetDefaultValues(obj, job) ... overwrite to set initial values of operation specific properties.
Can safely be overwritten by subclasses."""
pass # pylint: disable=unnecessary-pass
def _buildPathArea(self, obj, baseobject, isHole, start, getsim):
'''_buildPathArea(obj, baseobject, isHole, start, getsim) ... internal function.'''
"""_buildPathArea(obj, baseobject, isHole, start, getsim) ... internal function."""
# pylint: disable=unused-argument
PathLog.track()
area = Path.Area()
area.setPlane(PathUtils.makeWorkplane(baseobject))
area.add(baseobject)
areaParams = self.areaOpAreaParams(obj, isHole) # pylint: disable=assignment-from-no-return
areaParams = self.areaOpAreaParams(
obj, isHole
) # pylint: disable=assignment-from-no-return
heights = [i for i in self.depthparams]
PathLog.debug('depths: {}'.format(heights))
PathLog.debug("depths: {}".format(heights))
area.setParams(**areaParams)
obj.AreaParams = str(area.getParams())
PathLog.debug("Area with params: {}".format(area.getParams()))
sections = area.makeSections(mode=0, project=self.areaOpUseProjection(obj), heights=heights)
sections = area.makeSections(
mode=0, project=self.areaOpUseProjection(obj), heights=heights
)
PathLog.debug("sections = %s" % sections)
shapelist = [sec.getShape() for sec in sections]
PathLog.debug("shapelist = %s" % shapelist)
pathParams = self.areaOpPathParams(obj, isHole) # pylint: disable=assignment-from-no-return
pathParams['shapes'] = shapelist
pathParams['feedrate'] = self.horizFeed
pathParams['feedrate_v'] = self.vertFeed
pathParams['verbose'] = True
pathParams['resume_height'] = obj.SafeHeight.Value
pathParams['retraction'] = obj.ClearanceHeight.Value
pathParams['return_end'] = True
pathParams = self.areaOpPathParams(
obj, isHole
) # pylint: disable=assignment-from-no-return
pathParams["shapes"] = shapelist
pathParams["feedrate"] = self.horizFeed
pathParams["feedrate_v"] = self.vertFeed
pathParams["verbose"] = True
pathParams["resume_height"] = obj.SafeHeight.Value
pathParams["retraction"] = obj.ClearanceHeight.Value
pathParams["return_end"] = True
# Note that emitting preambles between moves breaks some dressups and prevents path optimization on some controllers
pathParams['preamble'] = False
pathParams["preamble"] = False
if not self.areaOpRetractTool(obj):
pathParams['threshold'] = 2.001 * self.radius
pathParams["threshold"] = 2.001 * self.radius
if self.endVector is not None:
pathParams['start'] = self.endVector
pathParams["start"] = self.endVector
elif PathOp.FeatureStartPoint & self.opFeatures(obj) and obj.UseStartPoint:
pathParams['start'] = obj.StartPoint
pathParams["start"] = obj.StartPoint
obj.PathParams = str({key: value for key, value in pathParams.items() if key != 'shapes'})
obj.PathParams = str(
{key: value for key, value in pathParams.items() if key != "shapes"}
)
PathLog.debug("Path with params: {}".format(obj.PathParams))
(pp, end_vector) = Path.fromShapes(**pathParams)
PathLog.debug('pp: {}, end vector: {}'.format(pp, end_vector))
self.endVector = end_vector # pylint: disable=attribute-defined-outside-init
PathLog.debug("pp: {}, end vector: {}".format(pp, end_vector))
self.endVector = end_vector # pylint: disable=attribute-defined-outside-init
simobj = None
if getsim:
areaParams['Thicken'] = True
areaParams['ToolRadius'] = self.radius - self.radius * .005
areaParams["Thicken"] = True
areaParams["ToolRadius"] = self.radius - self.radius * 0.005
area.setParams(**areaParams)
sec = area.makeSections(mode=0, project=False, heights=heights)[-1].getShape()
sec = area.makeSections(mode=0, project=False, heights=heights)[
-1
].getShape()
simobj = sec.extrude(FreeCAD.Vector(0, 0, baseobject.BoundBox.ZMax))
return pp, simobj
def _buildProfileOpenEdges(self, obj, edgeList, isHole, start, getsim):
'''_buildPathArea(obj, edgeList, isHole, start, getsim) ... internal function.'''
"""_buildPathArea(obj, edgeList, isHole, start, getsim) ... internal function."""
# pylint: disable=unused-argument
PathLog.track()
paths = []
heights = [i for i in self.depthparams]
PathLog.debug('depths: {}'.format(heights))
PathLog.debug("depths: {}".format(heights))
for i in range(0, len(heights)):
for baseShape in edgeList:
hWire = Part.Wire(Part.__sortEdges__(baseShape.Edges))
hWire.translate(FreeCAD.Vector(0, 0, heights[i] - hWire.BoundBox.ZMin))
pathParams = {} # pylint: disable=assignment-from-no-return
pathParams['shapes'] = [hWire]
pathParams['feedrate'] = self.horizFeed
pathParams['feedrate_v'] = self.vertFeed
pathParams['verbose'] = True
pathParams['resume_height'] = obj.SafeHeight.Value
pathParams['retraction'] = obj.ClearanceHeight.Value
pathParams['return_end'] = True
pathParams = {} # pylint: disable=assignment-from-no-return
pathParams["shapes"] = [hWire]
pathParams["feedrate"] = self.horizFeed
pathParams["feedrate_v"] = self.vertFeed
pathParams["verbose"] = True
pathParams["resume_height"] = obj.SafeHeight.Value
pathParams["retraction"] = obj.ClearanceHeight.Value
pathParams["return_end"] = True
# Note that emitting preambles between moves breaks some dressups and prevents path optimization on some controllers
pathParams['preamble'] = False
pathParams["preamble"] = False
if self.endVector is None:
V = hWire.Wires[0].Vertexes
lv = len(V) - 1
pathParams['start'] = FreeCAD.Vector(V[0].X, V[0].Y, V[0].Z)
if obj.Direction == 'CCW':
pathParams['start'] = FreeCAD.Vector(V[lv].X, V[lv].Y, V[lv].Z)
pathParams["start"] = FreeCAD.Vector(V[0].X, V[0].Y, V[0].Z)
if obj.Direction == "CCW":
pathParams["start"] = FreeCAD.Vector(V[lv].X, V[lv].Y, V[lv].Z)
else:
pathParams['start'] = self.endVector
pathParams["start"] = self.endVector
obj.PathParams = str({key: value for key, value in pathParams.items() if key != 'shapes'})
obj.PathParams = str(
{key: value for key, value in pathParams.items() if key != "shapes"}
)
PathLog.debug("Path with params: {}".format(obj.PathParams))
(pp, end_vector) = Path.fromShapes(**pathParams)
paths.extend(pp.Commands)
PathLog.debug('pp: {}, end vector: {}'.format(pp, end_vector))
PathLog.debug("pp: {}, end vector: {}".format(pp, end_vector))
self.endVector = end_vector
simobj = None
return paths, simobj
def opExecute(self, obj, getsim=False): # pylint: disable=arguments-differ
'''opExecute(obj, getsim=False) ... implementation of Path.Area ops.
def opExecute(self, obj, getsim=False): # pylint: disable=arguments-differ
"""opExecute(obj, getsim=False) ... implementation of Path.Area ops.
determines the parameters for _buildPathArea().
Do not overwrite, implement
areaOpAreaParams(obj, isHole) ... op specific area param dictionary
areaOpPathParams(obj, isHole) ... op specific path param dictionary
areaOpShapes(obj) ... the shape for path area to process
areaOpUseProjection(obj) ... return true if operation can use projection
instead.'''
instead."""
PathLog.track()
# Instantiate class variables for operation reference
self.endVector = None # pylint: disable=attribute-defined-outside-init
self.endVector = None # pylint: disable=attribute-defined-outside-init
self.leadIn = 2.0 # pylint: disable=attribute-defined-outside-init
# Initiate depthparams and calculate operation heights for operation
self.depthparams = self._customDepthParams(obj, obj.StartDepth.Value, obj.FinalDepth.Value)
self.depthparams = self._customDepthParams(
obj, obj.StartDepth.Value, obj.FinalDepth.Value
)
# Set start point
if PathOp.FeatureStartPoint & self.opFeatures(obj) and obj.UseStartPoint:
@@ -319,7 +361,7 @@ class ObjectOp(PathOp.ObjectOp):
else:
start = None
aOS = self.areaOpShapes(obj) # pylint: disable=assignment-from-no-return
aOS = self.areaOpShapes(obj) # pylint: disable=assignment-from-no-return
# Adjust tuples length received from other PathWB tools/operations
shapes = []
@@ -327,7 +369,7 @@ class ObjectOp(PathOp.ObjectOp):
if len(shp) == 2:
(fc, iH) = shp
# fc, iH, sub or description
tup = fc, iH, 'otherOp'
tup = fc, iH, "otherOp"
shapes.append(tup)
else:
shapes.append(shp)
@@ -335,38 +377,47 @@ class ObjectOp(PathOp.ObjectOp):
if len(shapes) > 1:
locations = []
for s in shapes:
if s[2] == 'OpenEdge':
if s[2] == "OpenEdge":
shp = Part.makeCompound(s[0])
else:
shp = s[0]
locations.append({
'x': shp.BoundBox.XMax,
'y': shp.BoundBox.YMax,
'shape': s
})
locations.append(
{"x": shp.BoundBox.XMax, "y": shp.BoundBox.YMax, "shape": s}
)
locations = PathUtils.sort_locations(locations, ['x', 'y'])
locations = PathUtils.sort_locations(locations, ["x", "y"])
shapes = [j['shape'] for j in locations]
shapes = [j["shape"] for j in locations]
sims = []
for shape, isHole, sub in shapes:
profileEdgesIsOpen = False
if sub == 'OpenEdge':
if sub == "OpenEdge":
profileEdgesIsOpen = True
if PathOp.FeatureStartPoint & self.opFeatures(obj) and obj.UseStartPoint:
if (
PathOp.FeatureStartPoint & self.opFeatures(obj)
and obj.UseStartPoint
):
osp = obj.StartPoint
self.commandlist.append(Path.Command('G0', {'X': osp.x, 'Y': osp.y, 'F': self.horizRapid}))
self.commandlist.append(
Path.Command(
"G0", {"X": osp.x, "Y": osp.y, "F": self.horizRapid}
)
)
try:
if profileEdgesIsOpen:
(pp, sim) = self._buildProfileOpenEdges(obj, shape, isHole, start, getsim)
(pp, sim) = self._buildProfileOpenEdges(
obj, shape, isHole, start, getsim
)
else:
(pp, sim) = self._buildPathArea(obj, shape, isHole, start, getsim)
except Exception as e: # pylint: disable=broad-except
except Exception as e: # pylint: disable=broad-except
FreeCAD.Console.PrintError(e)
FreeCAD.Console.PrintError("Something unexpected happened. Check project and tool config.")
FreeCAD.Console.PrintError(
"Something unexpected happened. Check project and tool config."
)
else:
if profileEdgesIsOpen:
ppCmds = pp
@@ -378,41 +429,49 @@ class ObjectOp(PathOp.ObjectOp):
sims.append(sim)
# Eif
if self.areaOpRetractTool(obj) and self.endVector is not None and len(self.commandlist) > 1:
if (
self.areaOpRetractTool(obj)
and self.endVector is not None
and len(self.commandlist) > 1
):
self.endVector[2] = obj.ClearanceHeight.Value
self.commandlist.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid}))
self.commandlist.append(
Path.Command(
"G0", {"Z": obj.ClearanceHeight.Value, "F": self.vertRapid}
)
)
PathLog.debug("obj.Name: " + str(obj.Name) + "\n\n")
return sims
def areaOpRetractTool(self, obj):
'''areaOpRetractTool(obj) ... return False to keep the tool at current level between shapes. Default is True.'''
"""areaOpRetractTool(obj) ... return False to keep the tool at current level between shapes. Default is True."""
# pylint: disable=unused-argument
return True
def areaOpAreaParams(self, obj, isHole):
'''areaOpAreaParams(obj, isHole) ... return operation specific area parameters in a dictionary.
"""areaOpAreaParams(obj, isHole) ... return operation specific area parameters in a dictionary.
Note that the resulting parameters are stored in the property AreaParams.
Must be overwritten by subclasses.'''
Must be overwritten by subclasses."""
# pylint: disable=unused-argument
pass # pylint: disable=unnecessary-pass
pass # pylint: disable=unnecessary-pass
def areaOpPathParams(self, obj, isHole):
'''areaOpPathParams(obj, isHole) ... return operation specific path parameters in a dictionary.
"""areaOpPathParams(obj, isHole) ... return operation specific path parameters in a dictionary.
Note that the resulting parameters are stored in the property PathParams.
Must be overwritten by subclasses.'''
Must be overwritten by subclasses."""
# pylint: disable=unused-argument
pass # pylint: disable=unnecessary-pass
pass # pylint: disable=unnecessary-pass
def areaOpShapes(self, obj):
'''areaOpShapes(obj) ... return all shapes to be processed by Path.Area for this op.
Must be overwritten by subclasses.'''
"""areaOpShapes(obj) ... return all shapes to be processed by Path.Area for this op.
Must be overwritten by subclasses."""
# pylint: disable=unused-argument
pass # pylint: disable=unnecessary-pass
pass # pylint: disable=unnecessary-pass
def areaOpUseProjection(self, obj):
'''areaOpUseProcjection(obj) ... return True if the operation can use procjection, defaults to False.
Can safely be overwritten by subclasses.'''
"""areaOpUseProcjection(obj) ... return True if the operation can use procjection, defaults to False.
Can safely be overwritten by subclasses."""
# pylint: disable=unused-argument
return False
@@ -426,10 +485,14 @@ class ObjectOp(PathOp.ObjectOp):
step_down=obj.StepDown.Value,
z_finish_step=finish_step,
final_depth=finDep,
user_depths=None)
user_depths=None,
)
return cdp
# Eclass
def SetupProperties():
setup = []
return setup

View File

@@ -20,15 +20,12 @@
# * *
# ***************************************************************************
from PySide.QtCore import QT_TRANSLATE_NOOP
import FreeCAD
import PathScripts.PathLog as PathLog
import PathScripts.PathOp as PathOp
# import PathScripts.PathUtils as PathUtils
import PathScripts.drillableLib as drillableLib
from PySide import QtCore
# lazily loaded modules
from lazy_loader.lazy_loader import LazyLoader
@@ -43,9 +40,7 @@ __url__ = "https://www.freecadweb.org"
__doc__ = "Base class an implementation for operations on circular holes."
# Qt translation handling
def translate(context, text, disambig=None):
return QtCore.QCoreApplication.translate(context, text, disambig)
translate = FreeCAD.Qt.translate
if False:
@@ -54,6 +49,7 @@ if False:
else:
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
class ObjectOp(PathOp.ObjectOp):
"""Base class for proxy objects of all operations on circular holes."""
@@ -81,7 +77,7 @@ class ObjectOp(PathOp.ObjectOp):
"App::PropertyStringList",
"Disabled",
"Base",
QtCore.QT_TRANSLATE_NOOP("Path", "List of disabled features"),
QT_TRANSLATE_NOOP("App::Property", "List of disabled features"),
)
self.initCircularHoleOperation(obj)

View File

@@ -39,6 +39,24 @@ else:
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
def populateCombobox(form, enumTups, comboBoxesPropertyMap):
"""fillComboboxes(form, comboBoxesPropertyMap) ... populate comboboxes with translated enumerations
** comboBoxesPropertyMap will be unnecessary if UI files use strict combobox naming protocol.
Args:
form = UI form
enumTups = list of (translated_text, data_string) tuples
comboBoxesPropertyMap = list of (translated_text, data_string) tuples
"""
PathLog.track(enumTups)
# Load appropriate enumerations in each combobox
for cb, prop in comboBoxesPropertyMap:
box = getattr(form, cb) # Get the combobox
box.clear() # clear the combobox
for text, data in enumTups[prop]: # load enumerations
box.addItem(text, data)
def updateInputField(obj, prop, widget, onBeforeChange=None):
"""updateInputField(obj, prop, widget) ... update obj's property prop with the value of widget.
The property's value is only assigned if the new value differs from the current value.
@@ -50,6 +68,7 @@ def updateInputField(obj, prop, widget, onBeforeChange=None):
"""
PathLog.track()
value = widget.property("rawValue")
PathLog.track("value: {}".format(value))
attr = PathUtil.getProperty(obj, prop)
attrValue = attr.Value if hasattr(attr, "Value") else attr

View File

@@ -107,6 +107,8 @@ def _log(level, module_line_func, msg):
def debug(msg):
"""(message)"""
module, line, func = _caller()
msg = "({}) - {}".format(line, msg)
return _log(Level.DEBUG, _caller(), msg)
def info(msg):
"""(message)"""

View File

@@ -20,17 +20,17 @@
# * *
# ***************************************************************************
import time
from PySide import QtCore
import FreeCAD
from PathScripts.PathUtils import waiting_effects
from PySide.QtCore import QT_TRANSLATE_NOOP
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
import time
# lazily loaded modules
from lazy_loader.lazy_loader import LazyLoader
@@ -42,13 +42,13 @@ __author__ = "sliptonic (Brad Collette)"
__url__ = "https://www.freecadweb.org"
__doc__ = "Base class and properties implementation for all Path operations."
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
# PathLog.trackModule()
if False:
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
PathLog.trackModule(PathLog.thisModule())
else:
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
# Qt translation handling
def translate(context, text, disambig=None):
return QtCore.QCoreApplication.translate(context, text, disambig)
translate = FreeCAD.Qt.translate
FeatureTool = 0x0001 # ToolController
@@ -105,7 +105,7 @@ class ObjectOp(object):
"App::PropertyLinkSubListGlobal",
"Base",
"Path",
QtCore.QT_TRANSLATE_NOOP("PathOp", "The base geometry for this operation"),
QT_TRANSLATE_NOOP("App::Property", "The base geometry for this operation"),
)
def addOpValues(self, obj, values):
@@ -114,8 +114,8 @@ class ObjectOp(object):
"App::PropertyDistance",
"OpStartDepth",
"Op Values",
QtCore.QT_TRANSLATE_NOOP(
"PathOp", "Holds the calculated value for the StartDepth"
QT_TRANSLATE_NOOP(
"App::Property", "Holds the calculated value for the StartDepth"
),
)
obj.setEditorMode("OpStartDepth", 1) # read-only
@@ -124,8 +124,8 @@ class ObjectOp(object):
"App::PropertyDistance",
"OpFinalDepth",
"Op Values",
QtCore.QT_TRANSLATE_NOOP(
"PathOp", "Holds the calculated value for the FinalDepth"
QT_TRANSLATE_NOOP(
"App::Property", "Holds the calculated value for the FinalDepth"
),
)
obj.setEditorMode("OpFinalDepth", 1) # read-only
@@ -134,7 +134,7 @@ class ObjectOp(object):
"App::PropertyDistance",
"OpToolDiameter",
"Op Values",
QtCore.QT_TRANSLATE_NOOP("PathOp", "Holds the diameter of the tool"),
QT_TRANSLATE_NOOP("App::Property", "Holds the diameter of the tool"),
)
obj.setEditorMode("OpToolDiameter", 1) # read-only
if "stockz" in values:
@@ -142,14 +142,14 @@ class ObjectOp(object):
"App::PropertyDistance",
"OpStockZMax",
"Op Values",
QtCore.QT_TRANSLATE_NOOP("PathOp", "Holds the max Z value of Stock"),
QT_TRANSLATE_NOOP("App::Property", "Holds the max Z value of Stock"),
)
obj.setEditorMode("OpStockZMax", 1) # read-only
obj.addProperty(
"App::PropertyDistance",
"OpStockZMin",
"Op Values",
QtCore.QT_TRANSLATE_NOOP("PathOp", "Holds the min Z value of Stock"),
QT_TRANSLATE_NOOP("App::Property", "Holds the min Z value of Stock"),
)
obj.setEditorMode("OpStockZMin", 1) # read-only
@@ -160,29 +160,29 @@ class ObjectOp(object):
"App::PropertyBool",
"Active",
"Path",
QtCore.QT_TRANSLATE_NOOP(
"PathOp", "Make False, to prevent operation from generating code"
QT_TRANSLATE_NOOP(
"App::Property", "Make False, to prevent operation from generating code"
),
)
obj.addProperty(
"App::PropertyString",
"Comment",
"Path",
QtCore.QT_TRANSLATE_NOOP(
"PathOp", "An optional comment for this Operation"
QT_TRANSLATE_NOOP(
"App::Property", "An optional comment for this Operation"
),
)
obj.addProperty(
"App::PropertyString",
"UserLabel",
"Path",
QtCore.QT_TRANSLATE_NOOP("PathOp", "User Assigned Label"),
QT_TRANSLATE_NOOP("App::Property", "User Assigned Label"),
)
obj.addProperty(
"App::PropertyString",
"CycleTime",
"Path",
QtCore.QT_TRANSLATE_NOOP("PathOp", "Operations Cycle Time Estimation"),
QT_TRANSLATE_NOOP("App::Property", "Operations Cycle Time Estimation"),
)
obj.setEditorMode("CycleTime", 1) # read-only
@@ -196,7 +196,7 @@ class ObjectOp(object):
"App::PropertyVectorList",
"Locations",
"Path",
QtCore.QT_TRANSLATE_NOOP("PathOp", "Base locations for this operation"),
QT_TRANSLATE_NOOP("App::Property", "Base locations for this operation"),
)
if FeatureTool & features:
@@ -204,8 +204,8 @@ class ObjectOp(object):
"App::PropertyLink",
"ToolController",
"Path",
QtCore.QT_TRANSLATE_NOOP(
"PathOp",
QT_TRANSLATE_NOOP(
"App::Property",
"The tool controller that will be used to calculate the path",
),
)
@@ -213,10 +213,10 @@ class ObjectOp(object):
if FeatureCoolant & features:
obj.addProperty(
"App::PropertyString",
"App::PropertyEnumeration",
"CoolantMode",
"Path",
QtCore.QT_TRANSLATE_NOOP("PathOp", "Coolant mode for this operation"),
QT_TRANSLATE_NOOP("App::Property", "Coolant mode for this operation"),
)
if FeatureDepths & features:
@@ -224,16 +224,16 @@ class ObjectOp(object):
"App::PropertyDistance",
"StartDepth",
"Depth",
QtCore.QT_TRANSLATE_NOOP(
"PathOp", "Starting Depth of Tool- first cut depth in Z"
QT_TRANSLATE_NOOP(
"App::Property", "Starting Depth of Tool- first cut depth in Z"
),
)
obj.addProperty(
"App::PropertyDistance",
"FinalDepth",
"Depth",
QtCore.QT_TRANSLATE_NOOP(
"PathOp", "Final Depth of Tool- lowest value in Z"
QT_TRANSLATE_NOOP(
"App::Property", "Final Depth of Tool- lowest value in Z"
),
)
if FeatureNoFinalDepth & features:
@@ -245,8 +245,9 @@ class ObjectOp(object):
"App::PropertyDistance",
"StartDepth",
"Depth",
QtCore.QT_TRANSLATE_NOOP(
"PathOp", "Starting Depth internal use only for derived values"
QT_TRANSLATE_NOOP(
"App::Property",
"Starting Depth internal use only for derived values",
),
)
obj.setEditorMode("StartDepth", 1) # read-only
@@ -258,7 +259,7 @@ class ObjectOp(object):
"App::PropertyDistance",
"StepDown",
"Depth",
QtCore.QT_TRANSLATE_NOOP("PathOp", "Incremental Step Down of Tool"),
QT_TRANSLATE_NOOP("App::Property", "Incremental Step Down of Tool"),
)
if FeatureFinishDepth & features:
@@ -266,8 +267,8 @@ class ObjectOp(object):
"App::PropertyDistance",
"FinishDepth",
"Depth",
QtCore.QT_TRANSLATE_NOOP(
"PathOp", "Maximum material removed on final pass."
QT_TRANSLATE_NOOP(
"App::Property", "Maximum material removed on final pass."
),
)
@@ -276,16 +277,17 @@ class ObjectOp(object):
"App::PropertyDistance",
"ClearanceHeight",
"Depth",
QtCore.QT_TRANSLATE_NOOP(
"PathOp", "The height needed to clear clamps and obstructions"
QT_TRANSLATE_NOOP(
"App::Property",
"The height needed to clear clamps and obstructions",
),
)
obj.addProperty(
"App::PropertyDistance",
"SafeHeight",
"Depth",
QtCore.QT_TRANSLATE_NOOP(
"PathOp", "Rapid Safety Height between locations."
QT_TRANSLATE_NOOP(
"App::Property", "Rapid Safety Height between locations."
),
)
@@ -294,14 +296,14 @@ class ObjectOp(object):
"App::PropertyVectorDistance",
"StartPoint",
"Start Point",
QtCore.QT_TRANSLATE_NOOP("PathOp", "The start point of this path"),
QT_TRANSLATE_NOOP("App::Property", "The start point of this path"),
)
obj.addProperty(
"App::PropertyBool",
"UseStartPoint",
"Start Point",
QtCore.QT_TRANSLATE_NOOP(
"PathOp", "Make True, if specifying a Start Point"
QT_TRANSLATE_NOOP(
"App::Property", "Make True, if specifying a Start Point"
),
)
@@ -310,19 +312,24 @@ class ObjectOp(object):
"App::PropertyDistance",
"MinDiameter",
"Diameter",
QtCore.QT_TRANSLATE_NOOP(
"PathOp", "Lower limit of the turning diameter"
QT_TRANSLATE_NOOP(
"App::Property", "Lower limit of the turning diameter"
),
)
obj.addProperty(
"App::PropertyDistance",
"MaxDiameter",
"Diameter",
QtCore.QT_TRANSLATE_NOOP(
"PathOp", "Upper limit of the turning diameter."
QT_TRANSLATE_NOOP(
"App::Property", "Upper limit of the turning diameter."
),
)
for n in self.opPropertyEnumerations():
PathLog.debug("n[0]: {} n[1]: {}".format(n[0], n[1]))
if hasattr(obj, n[0]):
setattr(obj, n[0], n[1])
# members being set later
self.commandlist = None
self.horizFeed = None
@@ -347,6 +354,39 @@ class ObjectOp(object):
obj.recompute()
obj.Proxy = self
@classmethod
def opPropertyEnumerations(self, dataType="data"):
"""propertyEnumerations(dataType="data")... return property enumeration lists of specified dataType.
Args:
dataType = 'data', 'raw', 'translated'
Notes:
'data' is list of internal string literals used in code
'raw' is list of (translated_text, data_string) tuples
'translated' is list of translated string literals
"""
enums = {
"CoolantMode": [
(translate("Path_Operation", "None"), "None"),
(translate("Path_Operation", "Flood"), "Flood"),
(translate("Path_Operation", "Mist"), "Mist"),
],
}
if dataType == "raw":
return enums
data = list()
idx = 0 if dataType == "translated" else 1
PathLog.debug(enums)
for k, v in enumerate(enums):
data.append((v, [tup[idx] for tup in enums[v]]))
PathLog.debug(data)
return data
def setEditorModes(self, obj, features):
"""Editor modes are not preserved during document store/restore, set editor modes for all properties"""
@@ -359,6 +399,7 @@ class ObjectOp(object):
obj.setEditorMode("OpFinalDepth", 2)
def onDocumentRestored(self, obj):
PathLog.track()
features = self.opFeatures(obj)
if (
FeatureBaseGeometry & features
@@ -375,13 +416,28 @@ class ObjectOp(object):
if FeatureTool & features and not hasattr(obj, "OpToolDiameter"):
self.addOpValues(obj, ["tooldia"])
if FeatureCoolant & features and not hasattr(obj, "CoolantMode"):
obj.addProperty(
"App::PropertyString",
"CoolantMode",
"Path",
QtCore.QT_TRANSLATE_NOOP("PathOp", "Coolant option for this operation"),
)
if FeatureCoolant & features:
oldvalue = str(obj.CoolantMode) if hasattr(obj, "CoolantMode") else "None"
if (
hasattr(obj, "CoolantMode")
and not obj.getTypeIdOfProperty("CoolantMode")
== "App::PropertyEnumeration"
):
obj.removeProperty("CoolantMode")
if not hasattr(obj, "CoolantMode"):
obj.addProperty(
"App::PropertyEnumeration",
"CoolantMode",
"Path",
QT_TRANSLATE_NOOP(
"App::Property", "Coolant option for this operation"
),
)
for n in self.opPropertyEnumerations():
if n[0] == "CoolantMode":
setattr(obj, n[0], n[1])
obj.CoolantMode = oldvalue
if FeatureDepths & features and not hasattr(obj, "OpStartDepth"):
self.addOpValues(obj, ["start", "final"])
@@ -396,7 +452,7 @@ class ObjectOp(object):
"App::PropertyString",
"CycleTime",
"Path",
QtCore.QT_TRANSLATE_NOOP("PathOp", "Operations Cycle Time Estimation"),
QT_TRANSLATE_NOOP("App::Property", "Operations Cycle Time Estimation"),
)
self.setEditorModes(obj, features)
@@ -515,6 +571,8 @@ class ObjectOp(object):
obj.OpToolDiameter = obj.ToolController.Tool.Diameter
if FeatureCoolant & features:
PathLog.track()
PathLog.debug(obj.getEnumerationsOfProperty("CoolantMode"))
obj.CoolantMode = job.SetupSheet.CoolantMode
if FeatureDepths & features:
@@ -711,14 +769,6 @@ class ObjectOp(object):
# make sure Base is still valid or clear it
self.sanitizeBase(obj)
if FeatureCoolant & self.opFeatures(obj):
if not hasattr(obj, "CoolantMode"):
PathLog.error(
translate(
"Path", "No coolant property found. Please recreate operation."
)
)
if FeatureTool & self.opFeatures(obj):
tc = obj.ToolController
if tc is None or tc.ToolNumber == 0:

View File

@@ -21,40 +21,46 @@
# * *
# ***************************************************************************
from PySide.QtCore import QT_TRANSLATE_NOOP
import FreeCAD
import PathScripts.PathGeom as PathGeom
import PathScripts.PathLog as PathLog
import math
from PySide import QtCore
# lazily loaded modules
from lazy_loader.lazy_loader import LazyLoader
Part = LazyLoader('Part', globals(), 'Part')
Part = LazyLoader("Part", globals(), "Part")
__title__ = "PathOpTools - Tools for Path operations."
__author__ = "sliptonic (Brad Collette)"
__url__ = "https://www.freecadweb.org"
__doc__ = "Collection of functions used by various Path operations. The functions are specific to Path and the algorithms employed by Path's operations."
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
#PathLog.trackModule(PathLog.thisModule())
PrintWireDebug = False
# Qt translation handling
def translate(context, text, disambig=None):
return QtCore.QCoreApplication.translate(context, text, disambig)
if False:
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
PathLog.trackModule(PathLog.thisModule())
else:
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
translate = FreeCAD.Qt.translate
def debugEdge(label, e):
'''debugEdge(label, e) ... prints a python statement to create e
Currently lines and arcs are supported.'''
"""debugEdge(label, e) ... prints a python statement to create e
Currently lines and arcs are supported."""
if not PrintWireDebug:
return
p0 = e.valueAt(e.FirstParameter)
p1 = e.valueAt(e.LastParameter)
if Part.Line == type(e.Curve):
print("%s Part.makeLine((%.2f, %.2f, %.2f), (%.2f, %.2f, %.2f))" % (label, p0.x, p0.y, p0.z, p1.x, p1.y, p1.z))
print(
"%s Part.makeLine((%.2f, %.2f, %.2f), (%.2f, %.2f, %.2f))"
% (label, p0.x, p0.y, p0.z, p1.x, p1.y, p1.z)
)
elif Part.Circle == type(e.Curve):
r = e.Curve.Radius
c = e.Curve.Center
@@ -65,26 +71,37 @@ def debugEdge(label, e):
else:
first = math.degrees(xu + e.FirstParameter)
last = first + math.degrees(e.LastParameter - e.FirstParameter)
print("%s Part.makeCircle(%.2f, App.Vector(%.2f, %.2f, %.2f), App.Vector(%.2f, %.2f, %.2f), %.2f, %.2f)" % (label, r, c.x, c.y, c.z, a.x, a.y, a.z, first, last))
print(
"%s Part.makeCircle(%.2f, App.Vector(%.2f, %.2f, %.2f), App.Vector(%.2f, %.2f, %.2f), %.2f, %.2f)"
% (label, r, c.x, c.y, c.z, a.x, a.y, a.z, first, last)
)
else:
print("%s %s (%.2f, %.2f, %.2f) -> (%.2f, %.2f, %.2f)" % (label, type(e.Curve).__name__, p0.x, p0.y, p0.z, p1.x, p1.y, p1.z))
print(
"%s %s (%.2f, %.2f, %.2f) -> (%.2f, %.2f, %.2f)"
% (label, type(e.Curve).__name__, p0.x, p0.y, p0.z, p1.x, p1.y, p1.z)
)
def debugWire(label, w):
'''debugWire(label, w) ... prints python statements for all edges of w to be added to the object tree in a group.'''
"""debugWire(label, w) ... prints python statements for all edges of w to be added to the object tree in a group."""
if not PrintWireDebug:
return
print("#%s wire >>>>>>>>>>>>>>>>>>>>>>>>" % label)
print("grp = FreeCAD.ActiveDocument.addObject('App::DocumentObjectGroup', '%s')" % label)
for i,e in enumerate(w.Edges):
print(
"grp = FreeCAD.ActiveDocument.addObject('App::DocumentObjectGroup', '%s')"
% label
)
for i, e in enumerate(w.Edges):
edge = "%s_e%d" % (label, i)
debugEdge("%s = " % edge, e)
print("Part.show(%s, '%s')" % (edge, edge))
print("grp.addObject(FreeCAD.ActiveDocument.ActiveObject)")
print("#%s wire <<<<<<<<<<<<<<<<<<<<<<<<" % label)
def _orientEdges(inEdges):
'''_orientEdges(inEdges) ... internal worker function to orient edges so the last vertex of one edge connects to the first vertex of the next edge.
Assumes the edges are in an order so they can be connected.'''
"""_orientEdges(inEdges) ... internal worker function to orient edges so the last vertex of one edge connects to the first vertex of the next edge.
Assumes the edges are in an order so they can be connected."""
PathLog.track()
# orient all edges of the wire so each edge's last value connects to the next edge's first value
e0 = inEdges[0]
@@ -92,21 +109,28 @@ def _orientEdges(inEdges):
if 1 < len(inEdges):
last = e0.valueAt(e0.LastParameter)
e1 = inEdges[1]
if not PathGeom.pointsCoincide(last, e1.valueAt(e1.FirstParameter)) and not PathGeom.pointsCoincide(last, e1.valueAt(e1.LastParameter)):
debugEdge('# _orientEdges - flip first', e0)
if not PathGeom.pointsCoincide(
last, e1.valueAt(e1.FirstParameter)
) and not PathGeom.pointsCoincide(last, e1.valueAt(e1.LastParameter)):
debugEdge("# _orientEdges - flip first", e0)
e0 = PathGeom.flipEdge(e0)
edges = [e0]
last = e0.valueAt(e0.LastParameter)
for e in inEdges[1:]:
edge = e if PathGeom.pointsCoincide(last, e.valueAt(e.FirstParameter)) else PathGeom.flipEdge(e)
edge = (
e
if PathGeom.pointsCoincide(last, e.valueAt(e.FirstParameter))
else PathGeom.flipEdge(e)
)
edges.append(edge)
last = edge.valueAt(edge.LastParameter)
return edges
def _isWireClockwise(w):
'''_isWireClockwise(w) ... return True if wire is oriented clockwise.
Assumes the edges of w are already properly oriented - for generic access use isWireClockwise(w).'''
"""_isWireClockwise(w) ... return True if wire is oriented clockwise.
Assumes the edges of w are already properly oriented - for generic access use isWireClockwise(w)."""
# handle wires consisting of a single circle or 2 edges where one is an arc.
# in both cases, because the edges are expected to be oriented correctly, the orientation can be
# determined by looking at (one of) the circle curves.
@@ -125,31 +149,33 @@ def _isWireClockwise(w):
PathLog.track(area)
return area < 0
def isWireClockwise(w):
'''isWireClockwise(w) ... returns True if the wire winds clockwise. '''
"""isWireClockwise(w) ... returns True if the wire winds clockwise."""
return _isWireClockwise(Part.Wire(_orientEdges(w.Edges)))
def orientWire(w, forward=True):
'''orientWire(w, forward=True) ... orients given wire in a specific direction.
"""orientWire(w, forward=True) ... orients given wire in a specific direction.
If forward = True (the default) the wire is oriented clockwise, looking down the negative Z axis.
If forward = False the wire is oriented counter clockwise.
If forward = None the orientation is determined by the order in which the edges appear in the wire.'''
PathLog.debug('orienting forward: {}'.format(forward))
If forward = None the orientation is determined by the order in which the edges appear in the wire."""
PathLog.debug("orienting forward: {}".format(forward))
wire = Part.Wire(_orientEdges(w.Edges))
if forward is not None:
if forward != _isWireClockwise(wire):
PathLog.track('orientWire - needs flipping')
PathLog.track("orientWire - needs flipping")
return PathGeom.flipWire(wire)
PathLog.track('orientWire - ok')
PathLog.track("orientWire - ok")
return wire
def offsetWire(wire, base, offset, forward, Side = None):
'''offsetWire(wire, base, offset, forward) ... offsets the wire away from base and orients the wire accordingly.
def offsetWire(wire, base, offset, forward, Side=None):
"""offsetWire(wire, base, offset, forward) ... offsets the wire away from base and orients the wire accordingly.
The function tries to avoid most of the pitfalls of Part.makeOffset2D which is possible because all offsetting
happens in the XY plane.
'''
PathLog.track('offsetWire')
"""
PathLog.track("offsetWire")
if 1 == len(wire.Edges):
edge = wire.Edges[0]
@@ -159,24 +185,34 @@ def offsetWire(wire, base, offset, forward, Side = None):
# https://www.freecadweb.org/wiki/Part%20Offset2D
# it's easy to construct them manually though
z = -1 if forward else 1
new_edge = Part.makeCircle(curve.Radius + offset, curve.Center, FreeCAD.Vector(0, 0, z))
if base.isInside(new_edge.Vertexes[0].Point, offset/2, True):
new_edge = Part.makeCircle(
curve.Radius + offset, curve.Center, FreeCAD.Vector(0, 0, z)
)
if base.isInside(new_edge.Vertexes[0].Point, offset / 2, True):
if offset > curve.Radius or PathGeom.isRoughly(offset, curve.Radius):
# offsetting a hole by its own radius (or more) makes the hole vanish
return None
if Side:
Side[0] = "Inside"
print("inside")
new_edge = Part.makeCircle(curve.Radius - offset, curve.Center, FreeCAD.Vector(0, 0, -z))
new_edge = Part.makeCircle(
curve.Radius - offset, curve.Center, FreeCAD.Vector(0, 0, -z)
)
return Part.Wire([new_edge])
if Part.Circle == type(curve) and not wire.isClosed():
# Process arc segment
z = -1 if forward else 1
l1 = math.sqrt((edge.Vertexes[0].Point.x - curve.Center.x)**2 + (edge.Vertexes[0].Point.y - curve.Center.y)**2)
l2 = math.sqrt((edge.Vertexes[1].Point.x - curve.Center.x)**2 + (edge.Vertexes[1].Point.y - curve.Center.y)**2)
l1 = math.sqrt(
(edge.Vertexes[0].Point.x - curve.Center.x) ** 2
+ (edge.Vertexes[0].Point.y - curve.Center.y) ** 2
)
l2 = math.sqrt(
(edge.Vertexes[1].Point.x - curve.Center.x) ** 2
+ (edge.Vertexes[1].Point.y - curve.Center.y) ** 2
)
# Calculate angles based on x-axis (0 - PI/2)
start_angle = math.acos((edge.Vertexes[0].Point.x - curve.Center.x) / l1)
end_angle = math.acos((edge.Vertexes[1].Point.x - curve.Center.x) / l2)
@@ -186,26 +222,41 @@ def offsetWire(wire, base, offset, forward, Side = None):
start_angle *= -1
if edge.Vertexes[1].Point.y < curve.Center.y:
end_angle *= -1
if (edge.Vertexes[0].Point.x > curve.Center.x or edge.Vertexes[1].Point.x > curve.Center.x) and curve.AngleXU < 0:
if (
edge.Vertexes[0].Point.x > curve.Center.x
or edge.Vertexes[1].Point.x > curve.Center.x
) and curve.AngleXU < 0:
tmp = start_angle
start_angle = end_angle
end_angle = tmp
# Inside / Outside
if base.isInside(edge.Vertexes[0].Point, offset/2, True):
if base.isInside(edge.Vertexes[0].Point, offset / 2, True):
offset *= -1
if Side:
Side[0] = "Inside"
# Create new arc
if curve.AngleXU > 0:
edge = Part.ArcOfCircle(Part.Circle(curve.Center, FreeCAD.Vector(0,0,1), curve.Radius+offset), start_angle, end_angle).toShape()
edge = Part.ArcOfCircle(
Part.Circle(
curve.Center, FreeCAD.Vector(0, 0, 1), curve.Radius + offset
),
start_angle,
end_angle,
).toShape()
else:
edge = Part.ArcOfCircle(Part.Circle(curve.Center, FreeCAD.Vector(0,0,1), curve.Radius-offset), start_angle, end_angle).toShape()
edge = Part.ArcOfCircle(
Part.Circle(
curve.Center, FreeCAD.Vector(0, 0, 1), curve.Radius - offset
),
start_angle,
end_angle,
).toShape()
return Part.Wire([edge])
if Part.Line == type(curve) or Part.LineSegment == type(curve):
# offsetting a single edge doesn't work because there is an infinite
# possible planes into which the edge could be offset
@@ -217,7 +268,11 @@ def offsetWire(wire, base, offset, forward, Side = None):
edge.translate(o)
# offset edde the other way if the result is inside
if base.isInside(edge.valueAt((edge.FirstParameter + edge.LastParameter) / 2), offset / 2, True):
if base.isInside(
edge.valueAt((edge.FirstParameter + edge.LastParameter) / 2),
offset / 2,
True,
):
edge.translate(-2 * o)
# flip the edge if it's not on the right side of the original edge
@@ -229,23 +284,23 @@ def offsetWire(wire, base, offset, forward, Side = None):
return Part.Wire([edge])
# if we get to this point the assumption is that makeOffset2D can deal with the edge
pass # pylint: disable=unnecessary-pass
pass # pylint: disable=unnecessary-pass
owire = orientWire(wire.makeOffset2D(offset), True)
debugWire('makeOffset2D_%d' % len(wire.Edges), owire)
debugWire("makeOffset2D_%d" % len(wire.Edges), owire)
if wire.isClosed():
if not base.isInside(owire.Edges[0].Vertexes[0].Point, offset/2, True):
PathLog.track('closed - outside')
if not base.isInside(owire.Edges[0].Vertexes[0].Point, offset / 2, True):
PathLog.track("closed - outside")
if Side:
Side[0] = "Outside"
return orientWire(owire, forward)
PathLog.track('closed - inside')
PathLog.track("closed - inside")
if Side:
Side[0] = "Inside"
try:
owire = wire.makeOffset2D(-offset)
except Exception: # pylint: disable=broad-except
except Exception: # pylint: disable=broad-except
# most likely offsetting didn't work because the wire is a hole
# and the offset is too big - making the hole vanish
return None
@@ -269,8 +324,8 @@ def offsetWire(wire, base, offset, forward, Side = None):
# determine the start and end point
start = edges[0].firstVertex().Point
end = edges[-1].lastVertex().Point
debugWire('wire', wire)
debugWire('wedges', Part.Wire(edges))
debugWire("wire", wire)
debugWire("wedges", Part.Wire(edges))
# find edges that are not inside the shape
common = base.common(owire)
@@ -281,7 +336,9 @@ def offsetWire(wire, base, offset, forward, Side = None):
p0 = edge.firstVertex().Point
p1 = edge.lastVertex().Point
for p in insideEndpoints:
if PathGeom.pointsCoincide(p, p0, 0.01) or PathGeom.pointsCoincide(p, p1, 0.01):
if PathGeom.pointsCoincide(p, p0, 0.01) or PathGeom.pointsCoincide(
p, p1, 0.01
):
return True
return False
@@ -292,16 +349,15 @@ def offsetWire(wire, base, offset, forward, Side = None):
if not longestWire or longestWire.Length < w.Length:
longestWire = w
debugWire('outside', Part.Wire(outside))
debugWire('longest', longestWire)
debugWire("outside", Part.Wire(outside))
debugWire("longest", longestWire)
def isCircleAt(edge, center):
'''isCircleAt(edge, center) ... helper function returns True if edge is a circle at the given center.'''
"""isCircleAt(edge, center) ... helper function returns True if edge is a circle at the given center."""
if Part.Circle == type(edge.Curve) or Part.ArcOfCircle == type(edge.Curve):
return PathGeom.pointsCoincide(edge.Curve.Center, center)
return False
# split offset wire into edges to the left side and edges to the right side
collectLeft = False
collectRight = False
@@ -312,7 +368,7 @@ def offsetWire(wire, base, offset, forward, Side = None):
# an end point (circle centered at one of the end points of the original wire).
# should we come to an end point and determine that we've already collected the
# next side, we're done
for e in (owire.Edges + owire.Edges):
for e in owire.Edges + owire.Edges:
if isCircleAt(e, start):
if PathGeom.pointsCoincide(e.Curve.Axis, FreeCAD.Vector(0, 0, 1)):
if not collectLeft and leftSideEdges:
@@ -340,8 +396,8 @@ def offsetWire(wire, base, offset, forward, Side = None):
elif collectRight:
rightSideEdges.append(e)
debugWire('left', Part.Wire(leftSideEdges))
debugWire('right', Part.Wire(rightSideEdges))
debugWire("left", Part.Wire(leftSideEdges))
debugWire("right", Part.Wire(rightSideEdges))
# figure out if all the left sided edges or the right sided edges are the ones
# that are 'outside'. However, we return the full side.
@@ -365,4 +421,3 @@ def offsetWire(wire, base, offset, forward, Side = None):
edges.reverse()
return orientWire(Part.Wire(edges), None)

View File

@@ -20,31 +20,39 @@
# * *
# ***************************************************************************
from PySide.QtCore import QT_TRANSLATE_NOOP
import FreeCAD
import PySide
import re
import PathScripts.PathLog as PathLog
__title__ = 'Generic property container to store some values.'
__author__ = 'sliptonic (Brad Collette)'
__url__ = 'https://www.freecadweb.org'
__doc__ = 'A generic container for typed properties in arbitrary categories.'
__title__ = "Generic property container to store some values."
__author__ = "sliptonic (Brad Collette)"
__url__ = "https://www.freecadweb.org"
__doc__ = "A generic container for typed properties in arbitrary categories."
def translate(context, text, disambig=None):
return PySide.QtCore.QCoreApplication.translate(context, text, disambig)
if False:
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
PathLog.trackModule(PathLog.thisModule())
else:
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
translate = FreeCAD.Qt.translate
SupportedPropertyType = {
'Angle' : 'App::PropertyAngle',
'Bool' : 'App::PropertyBool',
'Distance' : 'App::PropertyDistance',
'Enumeration' : 'App::PropertyEnumeration',
'File' : 'App::PropertyFile',
'Float' : 'App::PropertyFloat',
'Integer' : 'App::PropertyInteger',
'Length' : 'App::PropertyLength',
'Percent' : 'App::PropertyPercent',
'String' : 'App::PropertyString',
}
"Angle": "App::PropertyAngle",
"Bool": "App::PropertyBool",
"Distance": "App::PropertyDistance",
"Enumeration": "App::PropertyEnumeration",
"File": "App::PropertyFile",
"Float": "App::PropertyFloat",
"Integer": "App::PropertyInteger",
"Length": "App::PropertyLength",
"Percent": "App::PropertyPercent",
"String": "App::PropertyString",
}
def getPropertyTypeName(o):
for typ in SupportedPropertyType:
@@ -52,14 +60,22 @@ def getPropertyTypeName(o):
return typ
raise IndexError()
class PropertyBag(object):
'''Property container object.'''
CustomPropertyGroups = 'CustomPropertyGroups'
CustomPropertyGroupDefault = 'User'
class PropertyBag(object):
"""Property container object."""
CustomPropertyGroups = "CustomPropertyGroups"
CustomPropertyGroupDefault = "User"
def __init__(self, obj):
obj.addProperty('App::PropertyStringList', self.CustomPropertyGroups, 'Base', PySide.QtCore.QT_TRANSLATE_NOOP('PathPropertyBag', 'List of custom property groups'))
obj.addProperty(
"App::PropertyStringList",
self.CustomPropertyGroups,
"Base",
QT_TRANSLATE_NOOP(
"App::Property", "List of custom property groups"
),
)
self.onDocumentRestored(obj)
def __getstate__(self):
@@ -69,14 +85,14 @@ class PropertyBag(object):
return None
def __sanitizePropertyName(self, name):
if(len(name) == 0):
if len(name) == 0:
return
clean = name[0]
for i in range(1, len(name)):
if (name[i] == ' '):
if name[i] == " ":
clean += name[i + 1].upper()
i += 1
elif(name[i - 1] != ' '):
elif name[i - 1] != " ":
clean += name[i]
return clean
@@ -85,20 +101,24 @@ class PropertyBag(object):
obj.setEditorMode(self.CustomPropertyGroups, 2) # hide
def getCustomProperties(self):
'''getCustomProperties() ... Return a list of all custom properties created in this container.'''
return [p for p in self.obj.PropertiesList if self.obj.getGroupOfProperty(p) in self.obj.CustomPropertyGroups]
"""getCustomProperties() ... Return a list of all custom properties created in this container."""
return [
p
for p in self.obj.PropertiesList
if self.obj.getGroupOfProperty(p) in self.obj.CustomPropertyGroups
]
def addCustomProperty(self, propertyType, name, group=None, desc=None):
'''addCustomProperty(propertyType, name, group=None, desc=None) ... adds a custom property and tracks its group.'''
"""addCustomProperty(propertyType, name, group=None, desc=None) ... adds a custom property and tracks its group."""
if desc is None:
desc = ''
desc = ""
if group is None:
group = self.CustomPropertyGroupDefault
groups = self.obj.CustomPropertyGroups
name = self.__sanitizePropertyName(name)
if not re.match("^[A-Za-z0-9_]*$", name):
raise ValueError('Property Name can only contain letters and numbers')
raise ValueError("Property Name can only contain letters and numbers")
if not group in groups:
groups.append(group)
@@ -107,7 +127,7 @@ class PropertyBag(object):
return name
def refreshCustomPropertyGroups(self):
'''refreshCustomPropertyGroups() ... removes empty property groups, should be called after deleting properties.'''
"""refreshCustomPropertyGroups() ... removes empty property groups, should be called after deleting properties."""
customGroups = []
for p in self.obj.PropertiesList:
group = self.obj.getGroupOfProperty(p)
@@ -116,17 +136,17 @@ class PropertyBag(object):
self.obj.CustomPropertyGroups = customGroups
def Create(name = 'PropertyBag'):
obj = FreeCAD.ActiveDocument.addObject('App::FeaturePython', name)
def Create(name="PropertyBag"):
obj = FreeCAD.ActiveDocument.addObject("App::FeaturePython", name)
obj.Proxy = PropertyBag(obj)
return obj
def IsPropertyBag(obj):
'''Returns True if the supplied object is a property container (or its Proxy).'''
"""Returns True if the supplied object is a property container (or its Proxy)."""
if type(obj) == PropertyBag:
return True
if hasattr(obj, 'Proxy'):
if hasattr(obj, "Proxy"):
return IsPropertyBag(obj.Proxy)
return False

View File

@@ -20,9 +20,9 @@
# * *
# ***************************************************************************
from PySide import QtCore, QtGui
import FreeCAD
import FreeCADGui
#import PathGui
import PathScripts.PathIconViewProvider as PathIconViewProvider
import PathScripts.PathLog as PathLog
import PathScripts.PathPropertyBag as PathPropertyBag
@@ -30,23 +30,24 @@ import PathScripts.PathPropertyEditor as PathPropertyEditor
import PathScripts.PathUtil as PathUtil
import re
from PySide import QtCore, QtGui
__title__ = "Property Bag Editor"
__author__ = "sliptonic (Brad Collette)"
__url__ = "https://www.freecadweb.org"
__doc__ = "Task panel editor for a PropertyBag"
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
#PathLog.trackModule(PathLog.thisModule())
if False:
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
PathLog.trackModule(PathLog.thisModule())
else:
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
translate = FreeCAD.Qt.translate
# Qt translation handling
def translate(context, text, disambig=None):
return QtCore.QCoreApplication.translate(context, text, disambig)
class ViewProvider(object):
'''ViewProvider for a PropertyBag.
It's sole job is to provide an icon and invoke the TaskPanel on edit.'''
"""ViewProvider for a PropertyBag.
It's sole job is to provide an icon and invoke the TaskPanel on edit."""
def __init__(self, vobj, name):
PathLog.track(name)
@@ -73,7 +74,7 @@ class ViewProvider(object):
def getDisplayMode(self, mode):
# pylint: disable=unused-argument
return 'Default'
return "Default"
def setEdit(self, vobj, mode=0):
# pylint: disable=unused-argument
@@ -95,18 +96,20 @@ class ViewProvider(object):
def doubleClicked(self, vobj):
self.setEdit(vobj)
class Delegate(QtGui.QStyledItemDelegate):
RoleObject = QtCore.Qt.UserRole + 1
RoleObject = QtCore.Qt.UserRole + 1
RoleProperty = QtCore.Qt.UserRole + 2
RoleEditor = QtCore.Qt.UserRole + 3
RoleEditor = QtCore.Qt.UserRole + 3
#def paint(self, painter, option, index):
# def paint(self, painter, option, index):
# #PathLog.track(index.column(), type(option))
def createEditor(self, parent, option, index):
# pylint: disable=unused-argument
editor = PathPropertyEditor.Editor(index.data(self.RoleObject), index.data(self.RoleProperty))
editor = PathPropertyEditor.Editor(
index.data(self.RoleObject), index.data(self.RoleProperty)
)
index.model().setData(index, editor, self.RoleEditor)
return editor.widget(parent)
@@ -125,8 +128,8 @@ class Delegate(QtGui.QStyledItemDelegate):
# pylint: disable=unused-argument
widget.setGeometry(option.rect)
class PropertyCreate(object):
class PropertyCreate(object):
def __init__(self, obj, grp, typ, another):
self.obj = obj
self.form = FreeCADGui.PySideUic.loadUi(":panels/PropertyCreate.ui")
@@ -144,7 +147,7 @@ class PropertyCreate(object):
if typ:
self.form.propertyType.setCurrentText(typ)
else:
self.form.propertyType.setCurrentText('String')
self.form.propertyType.setCurrentText("String")
self.form.createAnother.setChecked(another)
self.form.propertyGroup.currentTextChanged.connect(self.updateUI)
@@ -159,12 +162,12 @@ class PropertyCreate(object):
if self.propertyIsEnumeration():
self.form.labelEnum.setEnabled(True)
self.form.propertyEnum.setEnabled(True)
typeSet = self.form.propertyEnum.text().strip() != ''
typeSet = self.form.propertyEnum.text().strip() != ""
else:
self.form.labelEnum.setEnabled(False)
self.form.propertyEnum.setEnabled(False)
if self.form.propertyEnum.text().strip():
self.form.propertyEnum.setText('')
self.form.propertyEnum.setText("")
ok = self.form.buttonBox.button(QtGui.QDialogButtonBox.Ok)
@@ -178,25 +181,35 @@ class PropertyCreate(object):
def propertyName(self):
return self.form.propertyName.text().strip()
def propertyGroup(self):
return self.form.propertyGroup.currentText().strip()
def propertyType(self):
return PathPropertyBag.SupportedPropertyType[self.form.propertyType.currentText()].strip()
return PathPropertyBag.SupportedPropertyType[
self.form.propertyType.currentText()
].strip()
def propertyInfo(self):
return self.form.propertyInfo.toPlainText().strip()
def createAnother(self):
return self.form.createAnother.isChecked()
def propertyEnumerations(self):
return [s.strip() for s in self.form.propertyEnum.text().strip().split(',')]
return [s.strip() for s in self.form.propertyEnum.text().strip().split(",")]
def propertyIsEnumeration(self):
return self.propertyType() == 'App::PropertyEnumeration'
return self.propertyType() == "App::PropertyEnumeration"
def exec_(self, name):
if name:
# property exists - this is an edit operation
self.form.propertyName.setText(name)
if self.propertyIsEnumeration():
self.form.propertyEnum.setText(','.join(self.obj.getEnumerationsOfProperty(name)))
self.form.propertyEnum.setText(
",".join(self.obj.getEnumerationsOfProperty(name))
)
self.form.propertyInfo.setText(self.obj.getDocumentationOfProperty(name))
self.form.labelName.setEnabled(False)
@@ -206,65 +219,80 @@ class PropertyCreate(object):
self.form.createAnother.setEnabled(False)
else:
self.form.propertyName.setText('')
self.form.propertyInfo.setText('')
self.form.propertyEnum.setText('')
#self.form.propertyName.setFocus()
self.form.propertyName.setText("")
self.form.propertyInfo.setText("")
self.form.propertyEnum.setText("")
# self.form.propertyName.setFocus()
self.updateUI()
return self.form.exec_()
Panel = []
class TaskPanel(object):
ColumnName = 0
#ColumnType = 1
ColumnVal = 1
#TableHeaders = ['Property', 'Type', 'Value']
TableHeaders = ['Property', 'Value']
# ColumnType = 1
ColumnVal = 1
# TableHeaders = ['Property', 'Type', 'Value']
TableHeaders = ["Property", "Value"]
def __init__(self, vobj):
self.obj = vobj.Object
self.obj = vobj.Object
self.props = sorted(self.obj.Proxy.getCustomProperties())
self.form = FreeCADGui.PySideUic.loadUi(":panels/PropertyBag.ui")
self.form = FreeCADGui.PySideUic.loadUi(":panels/PropertyBag.ui")
# initialized later
self.model = None
self.model = None
self.delegate = None
FreeCAD.ActiveDocument.openTransaction(translate("PathPropertyBag", "Edit PropertyBag"))
FreeCAD.ActiveDocument.openTransaction("Edit PropertyBag")
Panel.append(self)
def updateData(self, topLeft, bottomRight):
pass
def _setupProperty(self, i, name):
typ = PathPropertyBag.getPropertyTypeName(self.obj.getTypeIdOfProperty(name))
val = PathUtil.getPropertyValueString(self.obj, name)
val = PathUtil.getPropertyValueString(self.obj, name)
info = self.obj.getDocumentationOfProperty(name)
self.model.setData(self.model.index(i, self.ColumnName), name, QtCore.Qt.EditRole)
#self.model.setData(self.model.index(i, self.ColumnType), typ, QtCore.Qt.EditRole)
self.model.setData(self.model.index(i, self.ColumnVal), self.obj, Delegate.RoleObject)
self.model.setData(self.model.index(i, self.ColumnVal), name, Delegate.RoleProperty)
self.model.setData(self.model.index(i, self.ColumnVal), val, QtCore.Qt.DisplayRole)
self.model.setData(
self.model.index(i, self.ColumnName), name, QtCore.Qt.EditRole
)
# self.model.setData(self.model.index(i, self.ColumnType), typ, QtCore.Qt.EditRole)
self.model.setData(
self.model.index(i, self.ColumnVal), self.obj, Delegate.RoleObject
)
self.model.setData(
self.model.index(i, self.ColumnVal), name, Delegate.RoleProperty
)
self.model.setData(
self.model.index(i, self.ColumnVal), val, QtCore.Qt.DisplayRole
)
self.model.setData(self.model.index(i, self.ColumnName), typ, QtCore.Qt.ToolTipRole)
#self.model.setData(self.model.index(i, self.ColumnType), info, QtCore.Qt.ToolTipRole)
self.model.setData(self.model.index(i, self.ColumnVal), info, QtCore.Qt.ToolTipRole)
self.model.setData(
self.model.index(i, self.ColumnName), typ, QtCore.Qt.ToolTipRole
)
# self.model.setData(self.model.index(i, self.ColumnType), info, QtCore.Qt.ToolTipRole)
self.model.setData(
self.model.index(i, self.ColumnVal), info, QtCore.Qt.ToolTipRole
)
self.model.item(i, self.ColumnName).setEditable(False)
#self.model.item(i, self.ColumnType).setEditable(False)
# self.model.item(i, self.ColumnType).setEditable(False)
def setupUi(self):
PathLog.track()
self.delegate = Delegate(self.form)
self.model = QtGui.QStandardItemModel(len(self.props), len(self.TableHeaders), self.form)
self.model = QtGui.QStandardItemModel(
len(self.props), len(self.TableHeaders), self.form
)
self.model.setHorizontalHeaderLabels(self.TableHeaders)
for i,name in enumerate(self.props):
for i, name in enumerate(self.props):
self._setupProperty(i, name)
self.form.table.setModel(self.model)
@@ -301,8 +329,8 @@ class TaskPanel(object):
def addCustomProperty(self, obj, dialog):
name = dialog.propertyName()
typ = dialog.propertyType()
grp = dialog.propertyGroup()
typ = dialog.propertyType()
grp = dialog.propertyGroup()
info = dialog.propertyInfo()
propname = self.obj.Proxy.addCustomProperty(typ, name, grp, info)
if dialog.propertyIsEnumeration():
@@ -318,17 +346,22 @@ class TaskPanel(object):
dialog = PropertyCreate(self.obj, grp, typ, more)
if dialog.exec_(None):
# if we block signals the view doesn't get updated, surprise, surprise
#self.model.blockSignals(True)
# self.model.blockSignals(True)
name, info = self.addCustomProperty(self.obj, dialog)
index = 0
for i in range(self.model.rowCount()):
index = i
if self.model.item(i, self.ColumnName).data(QtCore.Qt.EditRole) > dialog.propertyName():
if (
self.model.item(i, self.ColumnName).data(QtCore.Qt.EditRole)
> dialog.propertyName()
):
break
self.model.insertRows(index, 1)
self._setupProperty(index, name)
self.form.table.selectionModel().setCurrentIndex(self.model.index(index, 0), QtCore.QItemSelectionModel.Rows)
#self.model.blockSignals(False)
self.form.table.selectionModel().setCurrentIndex(
self.model.index(index, 0), QtCore.QItemSelectionModel.Rows
)
# self.model.blockSignals(False)
more = dialog.createAnother()
else:
more = False
@@ -355,10 +388,14 @@ class TaskPanel(object):
# this can happen if the old enumeration value doesn't exist anymore
pass
newVal = PathUtil.getPropertyValueString(obj, nam)
self.model.setData(self.model.index(row, self.ColumnVal), newVal, QtCore.Qt.DisplayRole)
self.model.setData(
self.model.index(row, self.ColumnVal), newVal, QtCore.Qt.DisplayRole
)
#self.model.setData(self.model.index(row, self.ColumnType), info, QtCore.Qt.ToolTipRole)
self.model.setData(self.model.index(row, self.ColumnVal), info, QtCore.Qt.ToolTipRole)
# self.model.setData(self.model.index(row, self.ColumnType), info, QtCore.Qt.ToolTipRole)
self.model.setData(
self.model.index(row, self.ColumnVal), info, QtCore.Qt.ToolTipRole
)
def propertyModify(self):
PathLog.track()
@@ -371,7 +408,6 @@ class TaskPanel(object):
self.propertyModifyIndex(index)
def propertyRemove(self):
PathLog.track()
# first find all rows which need to be removed
@@ -387,24 +423,31 @@ class TaskPanel(object):
self.model.removeRow(row)
def Create(name = 'PropertyBag'):
'''Create(name = 'PropertyBag') ... creates a new setup sheet'''
FreeCAD.ActiveDocument.openTransaction(translate("PathPropertyBag", "Create PropertyBag"))
def Create(name="PropertyBag"):
"""Create(name = 'PropertyBag') ... creates a new setup sheet"""
FreeCAD.ActiveDocument.openTransaction("Create PropertyBag")
pcont = PathPropertyBag.Create(name)
PathIconViewProvider.Attach(pcont.ViewObject, name)
return pcont
PathIconViewProvider.RegisterViewProvider('PropertyBag', ViewProvider)
PathIconViewProvider.RegisterViewProvider("PropertyBag", ViewProvider)
class PropertyBagCreateCommand(object):
'''Command to create a property container object'''
"""Command to create a property container object"""
def __init__(self):
pass
def GetResources(self):
return {'MenuText': translate('PathPropertyBag', 'PropertyBag'),
'ToolTip': translate('PathPropertyBag', 'Creates an object which can be used to store reference properties.')}
return {
"MenuText": translate("Path_PropertyBag", "PropertyBag"),
"ToolTip": translate(
"Path_PropertyBag",
"Creates an object which can be used to store reference properties.",
),
}
def IsActive(self):
return not FreeCAD.ActiveDocument is None
@@ -414,17 +457,18 @@ class PropertyBagCreateCommand(object):
obj = Create()
body = None
if sel:
if 'PartDesign::Body' == sel[0].Object.TypeId:
if "PartDesign::Body" == sel[0].Object.TypeId:
body = sel[0].Object
elif hasattr(sel[0].Object, 'getParentGeoFeatureGroup'):
elif hasattr(sel[0].Object, "getParentGeoFeatureGroup"):
body = sel[0].Object.getParentGeoFeatureGroup()
if body:
obj.Label = 'Attributes'
obj.Label = "Attributes"
group = body.Group
group.append(obj)
body.Group = group
if FreeCAD.GuiUp:
FreeCADGui.addCommand('Path_PropertyBag', PropertyBagCreateCommand())
FreeCADGui.addCommand("Path_PropertyBag", PropertyBagCreateCommand())
FreeCAD.Console.PrintLog("Loading PathPropertyBagGui ... done\n")

View File

@@ -44,13 +44,14 @@ except ImportError:
# import sys
# sys.exit(msg)
from PySide.QtCore import QT_TRANSLATE_NOOP
import Path
import PathScripts.PathLog as PathLog
import PathScripts.PathUtils as PathUtils
import PathScripts.PathOp as PathOp
import PathScripts.PathSurfaceSupport as PathSurfaceSupport
import time
import PathScripts.PathUtils as PathUtils
import math
import time
# lazily loaded modules
from lazy_loader.lazy_loader import LazyLoader
@@ -60,13 +61,14 @@ Part = LazyLoader("Part", globals(), "Part")
if FreeCAD.GuiUp:
import FreeCADGui
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
# PathLog.trackModule(PathLog.thisModule())
translate = FreeCAD.Qt.translate
# Qt translation handling
def translate(context, text, disambig=None):
return QtCore.QCoreApplication.translate(context, text, disambig)
if False:
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
PathLog.trackModule(PathLog.thisModule())
else:
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
class ObjectSurface(PathOp.ObjectOp):
@@ -99,7 +101,7 @@ class ObjectSurface(PathOp.ObjectOp):
def initOpProperties(self, obj, warn=False):
"""initOpProperties(obj) ... create operation specific properties"""
self.addNewProps = list()
self.addNewProps = []
for (prtyp, nm, grp, tt) in self.opPropertyDefinitions():
if not hasattr(obj, nm):
@@ -107,17 +109,14 @@ class ObjectSurface(PathOp.ObjectOp):
self.addNewProps.append(nm)
# Set enumeration lists for enumeration properties
if len(self.addNewProps) > 0:
ENUMS = self.opPropertyEnumerations()
for n in ENUMS:
if n in self.addNewProps:
setattr(obj, n, ENUMS[n])
for n in self.propertyEnumerations():
setattr(obj, n[0], n[1])
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")
# 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")
self.propertiesReady = True
@@ -129,7 +128,7 @@ class ObjectSurface(PathOp.ObjectOp):
"App::PropertyBool",
"ShowTempObjects",
"Debug",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"Show the temporary path construction objects when module is in DEBUG mode.",
),
@@ -138,7 +137,7 @@ class ObjectSurface(PathOp.ObjectOp):
"App::PropertyDistance",
"AngularDeflection",
"Mesh Conversion",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"Smaller values yield a finer, more accurate mesh. Smaller values increase processing time a lot.",
),
@@ -147,7 +146,7 @@ class ObjectSurface(PathOp.ObjectOp):
"App::PropertyDistance",
"LinearDeflection",
"Mesh Conversion",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"Smaller values yield a finer, more accurate mesh. Smaller values do not increase processing time much.",
),
@@ -156,7 +155,7 @@ class ObjectSurface(PathOp.ObjectOp):
"App::PropertyFloat",
"CutterTilt",
"Rotation",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property", "Stop index(angle) for rotational scan"
),
),
@@ -164,7 +163,7 @@ class ObjectSurface(PathOp.ObjectOp):
"App::PropertyEnumeration",
"DropCutterDir",
"Rotation",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"Dropcutter lines are created parallel to this axis.",
),
@@ -173,7 +172,7 @@ class ObjectSurface(PathOp.ObjectOp):
"App::PropertyVectorDistance",
"DropCutterExtraOffset",
"Rotation",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property", "Additional offset to the selected bounding box"
),
),
@@ -181,7 +180,7 @@ class ObjectSurface(PathOp.ObjectOp):
"App::PropertyEnumeration",
"RotationAxis",
"Rotation",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property", "The model will be rotated around this axis."
),
),
@@ -189,7 +188,7 @@ class ObjectSurface(PathOp.ObjectOp):
"App::PropertyFloat",
"StartIndex",
"Rotation",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property", "Start index(angle) for rotational scan"
),
),
@@ -197,7 +196,7 @@ class ObjectSurface(PathOp.ObjectOp):
"App::PropertyFloat",
"StopIndex",
"Rotation",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property", "Stop index(angle) for rotational scan"
),
),
@@ -205,7 +204,7 @@ class ObjectSurface(PathOp.ObjectOp):
"App::PropertyEnumeration",
"ScanType",
"Surface",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"Planar: Flat, 3D surface scan. Rotational: 4th-axis rotational scan.",
),
@@ -214,7 +213,7 @@ class ObjectSurface(PathOp.ObjectOp):
"App::PropertyInteger",
"AvoidLastX_Faces",
"Selected Geometry Settings",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"Avoid cutting the last 'N' faces in the Base Geometry list of selected faces.",
),
@@ -223,7 +222,7 @@ class ObjectSurface(PathOp.ObjectOp):
"App::PropertyBool",
"AvoidLastX_InternalFeatures",
"Selected Geometry Settings",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property", "Do not cut internal features on avoided faces."
),
),
@@ -231,7 +230,7 @@ class ObjectSurface(PathOp.ObjectOp):
"App::PropertyDistance",
"BoundaryAdjustment",
"Selected Geometry Settings",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"Positive values push the cutter toward, or beyond, the boundary. Negative values retract the cutter away from the boundary.",
),
@@ -240,7 +239,7 @@ class ObjectSurface(PathOp.ObjectOp):
"App::PropertyBool",
"BoundaryEnforcement",
"Selected Geometry Settings",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"If true, the cutter will remain inside the boundaries of the model or selected face(s).",
),
@@ -249,7 +248,7 @@ class ObjectSurface(PathOp.ObjectOp):
"App::PropertyEnumeration",
"HandleMultipleFeatures",
"Selected Geometry Settings",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"Choose how to process multiple Base Geometry features.",
),
@@ -258,7 +257,7 @@ class ObjectSurface(PathOp.ObjectOp):
"App::PropertyDistance",
"InternalFeaturesAdjustment",
"Selected Geometry Settings",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"Positive values push the cutter toward, or into, the feature. Negative values retract the cutter away from the feature.",
),
@@ -267,7 +266,7 @@ class ObjectSurface(PathOp.ObjectOp):
"App::PropertyBool",
"InternalFeaturesCut",
"Selected Geometry Settings",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"Cut internal feature areas within a larger selected face.",
),
@@ -276,7 +275,7 @@ class ObjectSurface(PathOp.ObjectOp):
"App::PropertyEnumeration",
"BoundBox",
"Clearing Options",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property", "Select the overall boundary for the operation."
),
),
@@ -284,7 +283,7 @@ class ObjectSurface(PathOp.ObjectOp):
"App::PropertyEnumeration",
"CutMode",
"Clearing Options",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"Set the direction for the cutting tool to engage the material: Climb (ClockWise) or Conventional (CounterClockWise)",
),
@@ -293,7 +292,7 @@ class ObjectSurface(PathOp.ObjectOp):
"App::PropertyEnumeration",
"CutPattern",
"Clearing Options",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"Set the geometric clearing pattern to use for the operation.",
),
@@ -302,7 +301,7 @@ class ObjectSurface(PathOp.ObjectOp):
"App::PropertyFloat",
"CutPatternAngle",
"Clearing Options",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property", "The yaw angle used for certain clearing patterns"
),
),
@@ -310,7 +309,7 @@ class ObjectSurface(PathOp.ObjectOp):
"App::PropertyBool",
"CutPatternReversed",
"Clearing Options",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"Reverse the cut order of the stepover paths. For circular cut patterns, begin at the outside and work toward the center.",
),
@@ -319,7 +318,7 @@ class ObjectSurface(PathOp.ObjectOp):
"App::PropertyDistance",
"DepthOffset",
"Clearing Options",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"Set the Z-axis depth offset from the target surface.",
),
@@ -328,7 +327,7 @@ class ObjectSurface(PathOp.ObjectOp):
"App::PropertyEnumeration",
"LayerMode",
"Clearing Options",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"Complete the operation in a single pass at depth, or mulitiple passes to final depth.",
),
@@ -337,7 +336,7 @@ class ObjectSurface(PathOp.ObjectOp):
"App::PropertyVectorDistance",
"PatternCenterCustom",
"Clearing Options",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property", "Set the start point for the cut pattern."
),
),
@@ -345,7 +344,7 @@ class ObjectSurface(PathOp.ObjectOp):
"App::PropertyEnumeration",
"PatternCenterAt",
"Clearing Options",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"Choose location of the center point for starting the cut pattern.",
),
@@ -354,7 +353,7 @@ class ObjectSurface(PathOp.ObjectOp):
"App::PropertyEnumeration",
"ProfileEdges",
"Clearing Options",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property", "Profile the edges of the selection."
),
),
@@ -362,7 +361,7 @@ class ObjectSurface(PathOp.ObjectOp):
"App::PropertyDistance",
"SampleInterval",
"Clearing Options",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"Set the sampling resolution. Smaller values quickly increase processing time.",
),
@@ -371,7 +370,7 @@ class ObjectSurface(PathOp.ObjectOp):
"App::PropertyFloat",
"StepOver",
"Clearing Options",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"Set the stepover percentage, based on the tool's diameter.",
),
@@ -380,7 +379,7 @@ class ObjectSurface(PathOp.ObjectOp):
"App::PropertyBool",
"OptimizeLinearPaths",
"Optimization",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"Enable optimization of linear paths (co-linear points). Removes unnecessary co-linear points from G-Code output.",
),
@@ -389,7 +388,7 @@ class ObjectSurface(PathOp.ObjectOp):
"App::PropertyBool",
"OptimizeStepOverTransitions",
"Optimization",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"Enable separate optimization of transitions between, and breaks within, each step over path.",
),
@@ -398,7 +397,7 @@ class ObjectSurface(PathOp.ObjectOp):
"App::PropertyBool",
"CircularUseG2G3",
"Optimization",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"Convert co-planar arcs to G2/G3 gcode commands for `Circular` and `CircularZigZag` cut patterns.",
),
@@ -407,7 +406,7 @@ class ObjectSurface(PathOp.ObjectOp):
"App::PropertyDistance",
"GapThreshold",
"Optimization",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"Collinear and co-radial artifact gaps that are smaller than this threshold are closed in the path.",
),
@@ -416,7 +415,7 @@ class ObjectSurface(PathOp.ObjectOp):
"App::PropertyString",
"GapSizes",
"Optimization",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"Feedback: three smallest gaps identified in the path geometry.",
),
@@ -425,7 +424,7 @@ class ObjectSurface(PathOp.ObjectOp):
"App::PropertyVectorDistance",
"StartPoint",
"Start Point",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"The custom start point for the path of this operation",
),
@@ -434,39 +433,86 @@ class ObjectSurface(PathOp.ObjectOp):
"App::PropertyBool",
"UseStartPoint",
"Start Point",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property", "Make True, if specifying a Start Point"
),
),
]
def opPropertyEnumerations(self):
@classmethod
def propertyEnumerations(self, dataType="data"):
"""propertyEnumerations(dataType="data")... return property enumeration lists of specified dataType.
Args:
dataType = 'data', 'raw', 'translated'
Notes:
'data' is list of internal string literals used in code
'raw' is list of (translated_text, data_string) tuples
'translated' is list of translated string literals
"""
# Enumeration lists for App::PropertyEnumeration properties
return {
"BoundBox": ["BaseBoundBox", "Stock"],
"PatternCenterAt": [
"CenterOfMass",
"CenterOfBoundBox",
"XminYmin",
"Custom",
enums = {
"BoundBox": [
(translate("Path_Surface", "BaseBoundBox"), "BaseBoundBox"),
(translate("Path_Surface", "Stock"), "Stock"),
],
"PatternCenterAt": [
(translate("Path_Surface", "CenterOfMass"), "CenterOfMass"),
(translate("Path_Surface", "CenterOfBoundBox"), "CenterOfBoundBox"),
(translate("Path_Surface", "XminYmin"), "XminYmin"),
(translate("Path_Surface", "Custom"), "Custom"),
],
"CutMode": [
(translate("Path_Surface", "Conventional"), "Conventional"),
(translate("Path_Surface", "Climb"), "Climb"),
],
"CutMode": ["Conventional", "Climb"],
"CutPattern": [
"Circular",
"CircularZigZag",
"Line",
"Offset",
"Spiral",
"ZigZag",
], # Additional goals ['Offset', 'ZigZagOffset', 'Grid', 'Triangle']
"DropCutterDir": ["X", "Y"],
"HandleMultipleFeatures": ["Collectively", "Individually"],
"LayerMode": ["Single-pass", "Multi-pass"],
"ProfileEdges": ["None", "Only", "First", "Last"],
"RotationAxis": ["X", "Y"],
"ScanType": ["Planar", "Rotational"],
(translate("Path_Surface", "Circular"), "Circular"),
(translate("Path_Surface", "CircularZigZag"), "CircularZigZag"),
(translate("Path_Surface", "Line"), "Line"),
(translate("Path_Surface", "Offset"), "Offset"),
(translate("Path_Surface", "Spiral"), "Spiral"),
(translate("Path_Surface", "ZigZag"), "ZigZag"),
],
"DropCutterDir": [
(translate("Path_Surface", "X"), "X"),
(translate("Path_Surface", "Y"), "Y"),
],
"HandleMultipleFeatures": [
(translate("Path_Surface", "Collectively"), "Collectively"),
(translate("Path_Surface", "Individually"), "Individually"),
],
"LayerMode": [
(translate("Path_Surface", "Single-pass"), "Single-pass"),
(translate("Path_Surface", "Multi-pass"), "Multi-pass"),
],
"ProfileEdges": [
(translate("Path_Surface", "None"), "None"),
(translate("Path_Surface", "Only"), "Only"),
(translate("Path_Surface", "First"), "First"),
(translate("Path_Surface", "Last"), "Last"),
],
"RotationAxis": [
(translate("Path_Surface", "X"), "X"),
(translate("Path_Surface", "Y"), "Y"),
],
"ScanType": [
(translate("Path_Surface", "Planar"), "Planar"),
(translate("Path_Surface", "Rotational"), "Rotational"),
],
}
if dataType == "raw":
return enums
data = []
idx = 0 if dataType == "translated" else 1
for k, v in enumerate(enums):
data.append((v, [tup[idx] for tup in enums[v]]))
return data
def opPropertyDefaults(self, obj, job):
"""opPropertyDefaults(obj, job) ... returns a dictionary of default values
for the operation's properties."""
@@ -646,32 +692,20 @@ class ObjectSurface(PathOp.ObjectOp):
# Limit sample interval
if obj.SampleInterval.Value < 0.0001:
obj.SampleInterval.Value = 0.0001
PathLog.error(
translate(
"PathSurface",
"Sample interval limits are 0.001 to 25.4 millimeters.",
)
)
PathLog.error("Sample interval limits are 0.001 to 25.4 millimeters.")
if obj.SampleInterval.Value > 25.4:
obj.SampleInterval.Value = 25.4
PathLog.error(
translate(
"PathSurface",
"Sample interval limits are 0.001 to 25.4 millimeters.",
)
)
PathLog.error("Sample interval limits are 0.001 to 25.4 millimeters.")
# Limit cut pattern angle
if obj.CutPatternAngle < -360.0:
obj.CutPatternAngle = 0.0
PathLog.error(
translate("PathSurface", "Cut pattern angle limits are +-360 degrees.")
)
PathLog.error("Cut pattern angle limits are +-360 degrees.")
if obj.CutPatternAngle >= 360.0:
obj.CutPatternAngle = 0.0
PathLog.error(
translate("PathSurface", "Cut pattern angle limits are +- 360 degrees.")
)
PathLog.error("Cut pattern angle limits are +- 360 degrees.")
# Limit StepOver to natural number percentage
if obj.StepOver > 100.0:
@@ -682,20 +716,11 @@ class ObjectSurface(PathOp.ObjectOp):
# Limit AvoidLastX_Faces to zero and positive values
if obj.AvoidLastX_Faces < 0:
obj.AvoidLastX_Faces = 0
PathLog.error(
translate(
"PathSurface",
"AvoidLastX_Faces: Only zero or positive values permitted.",
)
)
PathLog.error("AvoidLastX_Faces: Only zero or positive values permitted.")
if obj.AvoidLastX_Faces > 100:
obj.AvoidLastX_Faces = 100
PathLog.error(
translate(
"PathSurface",
"AvoidLastX_Faces: Avoid last X faces count limited to 100.",
)
)
PathLog.error("AvoidLastX_Faces: Avoid last X faces count limited to 100.")
def opUpdateDepths(self, obj):
if hasattr(obj, "Base") and obj.Base:
@@ -726,22 +751,22 @@ class ObjectSurface(PathOp.ObjectOp):
"""opExecute(obj) ... process surface operation"""
PathLog.track()
self.modelSTLs = list()
self.safeSTLs = list()
self.modelTypes = list()
self.boundBoxes = list()
self.profileShapes = list()
self.collectiveShapes = list()
self.individualShapes = list()
self.avoidShapes = list()
self.modelSTLs = []
self.safeSTLs = []
self.modelTypes = []
self.boundBoxes = []
self.profileShapes = []
self.collectiveShapes = []
self.individualShapes = []
self.avoidShapes = []
self.tempGroup = None
self.CutClimb = False
self.closedGap = False
self.tmpCOM = None
self.gaps = [0.1, 0.2, 0.3]
self.cancelOperation = False
CMDS = list()
modelVisibility = list()
CMDS = []
modelVisibility = []
FCAD = FreeCAD.ActiveDocument
try:
@@ -876,26 +901,25 @@ class ObjectSurface(PathOp.ObjectOp):
# Save model visibilities for restoration
if FreeCAD.GuiUp:
for m in range(0, len(JOB.Model.Group)):
mNm = JOB.Model.Group[m].Name
for model in JOB.Model.Group:
mNm = model.Name
modelVisibility.append(
FreeCADGui.ActiveDocument.getObject(mNm).Visibility
)
# Setup STL, model type, and bound box containers for each model in Job
for m in range(0, len(JOB.Model.Group)):
M = JOB.Model.Group[m]
for model in JOB.Model.Group:
self.modelSTLs.append(False)
self.safeSTLs.append(False)
self.profileShapes.append(False)
# Set bound box
if obj.BoundBox == "BaseBoundBox":
if M.TypeId.startswith("Mesh"):
if model.TypeId.startswith("Mesh"):
self.modelTypes.append("M") # Mesh
self.boundBoxes.append(M.Mesh.BoundBox)
self.boundBoxes.append(model.Mesh.BoundBox)
else:
self.modelTypes.append("S") # Solid
self.boundBoxes.append(M.Shape.BoundBox)
self.boundBoxes.append(model.Shape.BoundBox)
elif obj.BoundBox == "Stock":
self.modelTypes.append("S") # Solid
self.boundBoxes.append(JOB.Stock.Shape.BoundBox)
@@ -916,18 +940,20 @@ class ObjectSurface(PathOp.ObjectOp):
self.modelSTLs = PSF.modelSTLs
self.profileShapes = PSF.profileShapes
for m in range(0, len(JOB.Model.Group)):
for idx, model in enumerate(JOB.Model.Group):
PathLog.debug(idx)
# Create OCL.stl model objects
PathSurfaceSupport._prepareModelSTLs(self, JOB, obj, m, ocl)
PathSurfaceSupport._prepareModelSTLs(self, JOB, obj, idx, ocl)
Mdl = JOB.Model.Group[m]
if FACES[m]:
PathLog.debug("Working on Model.Group[{}]: {}".format(m, Mdl.Label))
if m > 0:
if FACES[idx]:
PathLog.debug(
"Working on Model.Group[{}]: {}".format(idx, model.Label)
)
if idx > 0:
# Raise to clearance between models
CMDS.append(
Path.Command(
"N (Transition to base: {}.)".format(Mdl.Label)
"N (Transition to base: {}.)".format(model.Label)
)
)
CMDS.append(
@@ -938,14 +964,14 @@ class ObjectSurface(PathOp.ObjectOp):
)
# make stock-model-voidShapes STL model for avoidance detection on transitions
PathSurfaceSupport._makeSafeSTL(
self, JOB, obj, m, FACES[m], VOIDS[m], ocl
self, JOB, obj, idx, FACES[idx], VOIDS[idx], ocl
)
# Process model/faces - OCL objects must be ready
CMDS.extend(self._processCutAreas(JOB, obj, m, FACES[m], VOIDS[m]))
else:
PathLog.debug(
"No data for model base: {}".format(JOB.Model.Group[m].Label)
CMDS.extend(
self._processCutAreas(JOB, obj, idx, FACES[idx], VOIDS[idx])
)
else:
PathLog.debug("No data for model base: {}".format(model.Label))
# Save gcode produced
self.commandlist.extend(CMDS)
@@ -976,7 +1002,7 @@ class ObjectSurface(PathOp.ObjectOp):
tempGroup.purgeTouched()
# Provide user feedback for gap sizes
gaps = list()
gaps = []
for g in self.gaps:
if g != self.toolDiam:
gaps.append(g)
@@ -1039,7 +1065,7 @@ class ObjectSurface(PathOp.ObjectOp):
It then calls the correct scan method depending on the ScanType property."""
PathLog.debug("_processCutAreas()")
final = list()
final = []
# Process faces Collectively or Individually
if obj.HandleMultipleFeatures == "Collectively":
@@ -1091,8 +1117,8 @@ class ObjectSurface(PathOp.ObjectOp):
It calls the correct Single or Multi-pass method as needed.
It returns the gcode for the operation."""
PathLog.debug("_processPlanarOp()")
final = list()
SCANDATA = list()
final = []
SCANDATA = []
def getTransition(two):
first = two[0][0][0] # [step][item][point]
@@ -1121,23 +1147,23 @@ class ObjectSurface(PathOp.ObjectOp):
self.cutter,
)
profScan = list()
profScan = []
if obj.ProfileEdges != "None":
prflShp = self.profileShapes[mdlIdx][fsi]
if prflShp is False:
msg = translate("PathSurface", "No profile geometry shape returned.")
PathLog.error(msg)
return list()
return []
self.showDebugObject(prflShp, "NewProfileShape")
# get offset path geometry and perform OCL scan with that geometry
pathOffsetGeom = self._offsetFacesToPointData(obj, prflShp)
if pathOffsetGeom is False:
msg = translate("PathSurface", "No profile path geometry returned.")
PathLog.error(msg)
return list()
return []
profScan = [self._planarPerformOclScan(obj, pdc, pathOffsetGeom, True)]
geoScan = list()
geoScan = []
if obj.ProfileEdges != "Only":
self.showDebugObject(cmpdShp, "CutArea")
# get internal path geometry and perform OCL scan with that geometry
@@ -1149,7 +1175,7 @@ class ObjectSurface(PathOp.ObjectOp):
if pathGeom is False:
msg = translate("PathSurface", "No clearing shape returned.")
PathLog.error(msg)
return list()
return []
if obj.CutPattern == "Offset":
useGeom = self._offsetFacesToPointData(obj, pathGeom, profile=False)
if useGeom is False:
@@ -1157,7 +1183,7 @@ class ObjectSurface(PathOp.ObjectOp):
"PathSurface", "No clearing path geometry returned."
)
PathLog.error(msg)
return list()
return []
geoScan = [self._planarPerformOclScan(obj, pdc, useGeom, True)]
else:
geoScan = self._planarPerformOclScan(obj, pdc, pathGeom, False)
@@ -1177,7 +1203,7 @@ class ObjectSurface(PathOp.ObjectOp):
if len(SCANDATA) == 0:
msg = translate("PathSurface", "No scan data to convert to Gcode.")
PathLog.error(msg)
return list()
return []
# Apply depth offset
if obj.DepthOffset.Value != 0.0:
@@ -1214,7 +1240,7 @@ class ObjectSurface(PathOp.ObjectOp):
def _offsetFacesToPointData(self, obj, subShp, profile=True):
PathLog.debug("_offsetFacesToPointData()")
offsetLists = list()
offsetLists = []
dist = obj.SampleInterval.Value / 5.0
# defl = obj.SampleInterval.Value / 5.0
@@ -1246,18 +1272,18 @@ class ObjectSurface(PathOp.ObjectOp):
Switching function for calling the appropriate path-geometry to OCL points conversion function
for the various cut patterns."""
PathLog.debug("_planarPerformOclScan()")
SCANS = list()
SCANS = []
if offsetPoints or obj.CutPattern == "Offset":
PNTSET = PathSurfaceSupport.pathGeomToOffsetPointSet(obj, pathGeom)
for D in PNTSET:
stpOvr = list()
ofst = list()
stpOvr = []
ofst = []
for I in D:
if I == "BRK":
stpOvr.append(ofst)
stpOvr.append(I)
ofst = list()
ofst = []
else:
# D format is ((p1, p2), (p3, p4))
(A, B) = I
@@ -1266,7 +1292,7 @@ class ObjectSurface(PathOp.ObjectOp):
stpOvr.append(ofst)
SCANS.extend(stpOvr)
elif obj.CutPattern in ["Line", "Spiral", "ZigZag"]:
stpOvr = list()
stpOvr = []
if obj.CutPattern == "Line":
# PNTSET = PathSurfaceSupport.pathGeomToLinesPointSet(obj, pathGeom, self.CutClimb, self.toolDiam, self.closedGap, self.gaps)
PNTSET = PathSurfaceSupport.pathGeomToLinesPointSet(self, obj, pathGeom)
@@ -1287,7 +1313,7 @@ class ObjectSurface(PathOp.ObjectOp):
(A, B) = LN
stpOvr.append(self._planarDropCutScan(pdc, A, B))
SCANS.append(stpOvr)
stpOvr = list()
stpOvr = []
elif obj.CutPattern in ["Circular", "CircularZigZag"]:
# PNTSET is list, by stepover.
# Each stepover is a list containing arc/loop descriptions, (sp, ep, cp)
@@ -1295,7 +1321,7 @@ class ObjectSurface(PathOp.ObjectOp):
PNTSET = PathSurfaceSupport.pathGeomToCircularPointSet(self, obj, pathGeom)
for so in range(0, len(PNTSET)):
stpOvr = list()
stpOvr = []
erFlg = False
(aTyp, dirFlg, ARCS) = PNTSET[so]
@@ -1386,7 +1412,7 @@ class ObjectSurface(PathOp.ObjectOp):
odd = True
lstStpEnd = None
for so in range(0, lenSCANDATA):
cmds = list()
cmds = []
PRTS = SCANDATA[so]
lenPRTS = len(PRTS)
first = PRTS[0][0] # first point of arc/line stepover group
@@ -1489,7 +1515,7 @@ class ObjectSurface(PathOp.ObjectOp):
odd = True # ZigZag directional switch
lyrHasCmds = False
actvSteps = 0
LYR = list()
LYR = []
# if lyr > 0:
# if prvStpLast is not None:
# lastPrvStpLast = prvStpLast
@@ -1503,8 +1529,8 @@ class ObjectSurface(PathOp.ObjectOp):
lenSO = len(SO)
# Pre-process step-over parts for layer depth and holds
ADJPRTS = list()
LMAX = list()
ADJPRTS = []
LMAX = []
soHasPnts = False
brkFlg = False
for i in range(0, lenSO):
@@ -1530,9 +1556,9 @@ class ObjectSurface(PathOp.ObjectOp):
# Process existing parts within current step over
prtsHasCmds = False
stepHasCmds = False
prtsCmds = list()
stpOvrCmds = list()
transCmds = list()
prtsCmds = []
stpOvrCmds = []
transCmds = []
if soHasPnts is True:
first = ADJPRTS[0][0] # first point of arc/line stepover group
last = None
@@ -1657,8 +1683,8 @@ class ObjectSurface(PathOp.ObjectOp):
return GCODE
def _planarMultipassPreProcess(self, obj, LN, prvDep, layDep):
ALL = list()
PTS = list()
ALL = []
PTS = []
optLinTrans = obj.OptimizeStepOverTransitions
safe = math.ceil(obj.SafeHeight.Value)
@@ -1685,7 +1711,7 @@ class ObjectSurface(PathOp.ObjectOp):
if optLinTrans is True:
# Remove leading and trailing Hold Points
popList = list()
popList = []
for i in range(0, len(PTS)): # identify leading string
if PTS[i].z == safe:
popList.append(i)
@@ -1695,7 +1721,7 @@ class ObjectSurface(PathOp.ObjectOp):
for p in popList: # Remove hold points
PTS.pop(p)
ALL.pop(p)
popList = list()
popList = []
for i in range(len(PTS) - 1, -1, -1): # identify trailing string
if PTS[i].z == safe:
popList.append(i)
@@ -1717,7 +1743,7 @@ class ObjectSurface(PathOp.ObjectOp):
return (PTS, lMax)
def _planarMultipassProcess(self, obj, PNTS, lMax):
output = list()
output = []
optimize = obj.OptimizeLinearPaths
safe = math.ceil(obj.SafeHeight.Value)
lenPNTS = len(PNTS)
@@ -1800,7 +1826,7 @@ class ObjectSurface(PathOp.ObjectOp):
passes, as well as other kinds of breaks. When
OptimizeStepOverTransitions is enabled, uses safePDC to safely optimize
short (~order of cutter diameter) transitions."""
cmds = list()
cmds = []
rtpd = False
height = obj.SafeHeight.Value
# Allow cutter-down transitions with a distance up to 2x cutter
@@ -1869,7 +1895,7 @@ class ObjectSurface(PathOp.ObjectOp):
return cmds
def _arcsToG2G3(self, LN, numPts, odd, gDIR, tolrnc):
cmds = list()
cmds = []
strtPnt = LN[0]
endPnt = LN[numPts - 1]
strtHght = strtPnt.z

View File

@@ -20,47 +20,69 @@
# * *
# ***************************************************************************
from PySide import QtCore
import FreeCAD
import FreeCADGui
import PathGui as PGui # ensure Path/Gui/Resources are loaded
import PathScripts.PathSurface as PathSurface
import PathScripts.PathLog as PathLog
import PathGui as PGui # ensure Path/Gui/Resources are loaded
import PathScripts.PathGui as PathGui
import PathScripts.PathOpGui as PathOpGui
import PathScripts.PathSurface as PathSurface
from PySide import QtCore
__title__ = "Path Surface Operation UI"
__author__ = "sliptonic (Brad Collette)"
__url__ = "https://www.freecadweb.org"
__doc__ = "Surface operation page controller and command implementation."
translate = FreeCAD.Qt.translate
if False:
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
PathLog.trackModule(PathLog.thisModule())
else:
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
class TaskPanelOpPage(PathOpGui.TaskPanelPage):
'''Page controller class for the Surface operation.'''
"""Page controller class for the Surface operation."""
def initPage(self, obj):
self.setTitle("3D Surface - " + obj.Label)
# self.updateVisibility()
# retrieve property enumerations
self.propEnums = PathSurface.ObjectSurface.opPropertyEnumerations(False)
# self.propEnums = PathSurface.ObjectSurface.opPropertyEnumerations(False)
self.propEnums = PathSurface.ObjectSurface.propertyEnumerations(False)
def getForm(self):
'''getForm() ... returns UI'''
return FreeCADGui.PySideUic.loadUi(":/panels/PageOpSurfaceEdit.ui")
"""getForm() ... returns UI"""
form = FreeCADGui.PySideUic.loadUi(":/panels/PageOpSurfaceEdit.ui")
comboToPropertyMap = [
("boundBoxSelect", "BoundBox"),
("scanType", "ScanType"),
("cutPattern", "CutPattern"),
("profileEdges", "ProfileEdges"),
("layerMode", "LayerMode"),
("dropCutterDirSelect", "DropCutterDir"),
]
enumTups = PathSurface.ObjectSurface.propertyEnumerations(dataType="raw")
PathGui.populateCombobox(form, enumTups, comboToPropertyMap)
return form
def getFields(self, obj):
'''getFields(obj) ... transfers values from UI to obj's proprties'''
"""getFields(obj) ... transfers values from UI to obj's proprties"""
self.updateToolController(obj, self.form.toolController)
self.updateCoolant(obj, self.form.coolantController)
if obj.BoundBox != str(self.form.boundBoxSelect.currentText()):
obj.BoundBox = str(self.form.boundBoxSelect.currentText())
if obj.BoundBox != str(self.form.boundBoxSelect.currentData()):
obj.BoundBox = str(self.form.boundBoxSelect.currentData())
if obj.ScanType != str(self.form.scanType.currentText()):
obj.ScanType = str(self.form.scanType.currentText())
if obj.ScanType != str(self.form.scanType.currentData()):
obj.ScanType = str(self.form.scanType.currentData())
if obj.LayerMode != str(self.form.layerMode.currentText()):
obj.LayerMode = str(self.form.layerMode.currentText())
if obj.LayerMode != str(self.form.layerMode.currentData()):
obj.LayerMode = str(self.form.layerMode.currentData())
"""
The following method of getting values from the UI form
@@ -74,29 +96,36 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
This type of dynamic combobox population is done for the
Tool Controller selection.
"""
val = self.propEnums['CutPattern'][self.form.cutPattern.currentIndex()]
if obj.CutPattern != val:
obj.CutPattern = val
# val = self.propEnums["CutPattern"][self.form.cutPattern.currentIndex()]
# if obj.CutPattern != val:
# obj.CutPattern = val
val = self.propEnums['ProfileEdges'][self.form.profileEdges.currentIndex()]
if obj.ProfileEdges != val:
obj.ProfileEdges = val
# val = self.propEnums["ProfileEdges"][self.form.profileEdges.currentIndex()]
# if obj.ProfileEdges != val:
# obj.ProfileEdges = val
obj.CutPattern = self.form.cutPattern.currentData()
obj.ProfileEdges = self.form.profileEdges.currentData()
if obj.AvoidLastX_Faces != self.form.avoidLastX_Faces.value():
obj.AvoidLastX_Faces = self.form.avoidLastX_Faces.value()
obj.DropCutterExtraOffset.x = FreeCAD.Units.Quantity(self.form.boundBoxExtraOffsetX.text()).Value
obj.DropCutterExtraOffset.y = FreeCAD.Units.Quantity(self.form.boundBoxExtraOffsetY.text()).Value
obj.DropCutterExtraOffset.x = FreeCAD.Units.Quantity(
self.form.boundBoxExtraOffsetX.text()
).Value
obj.DropCutterExtraOffset.y = FreeCAD.Units.Quantity(
self.form.boundBoxExtraOffsetY.text()
).Value
if obj.DropCutterDir != str(self.form.dropCutterDirSelect.currentText()):
obj.DropCutterDir = str(self.form.dropCutterDirSelect.currentText())
if obj.DropCutterDir != str(self.form.dropCutterDirSelect.currentData()):
obj.DropCutterDir = str(self.form.dropCutterDirSelect.currentData())
PathGui.updateInputField(obj, 'DepthOffset', self.form.depthOffset)
PathGui.updateInputField(obj, "DepthOffset", self.form.depthOffset)
if obj.StepOver != self.form.stepOver.value():
obj.StepOver = self.form.stepOver.value()
PathGui.updateInputField(obj, 'SampleInterval', self.form.sampleInterval)
PathGui.updateInputField(obj, "SampleInterval", self.form.sampleInterval)
if obj.UseStartPoint != self.form.useStartPoint.isChecked():
obj.UseStartPoint = self.form.useStartPoint.isChecked()
@@ -107,11 +136,16 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
if obj.OptimizeLinearPaths != self.form.optimizeEnabled.isChecked():
obj.OptimizeLinearPaths = self.form.optimizeEnabled.isChecked()
if obj.OptimizeStepOverTransitions != self.form.optimizeStepOverTransitions.isChecked():
obj.OptimizeStepOverTransitions = self.form.optimizeStepOverTransitions.isChecked()
if (
obj.OptimizeStepOverTransitions
!= self.form.optimizeStepOverTransitions.isChecked()
):
obj.OptimizeStepOverTransitions = (
self.form.optimizeStepOverTransitions.isChecked()
)
def setFields(self, obj):
'''setFields(obj) ... transfers obj's property values to UI'''
"""setFields(obj) ... transfers obj's property values to UI"""
self.setupToolController(obj, self.form.toolController)
self.setupCoolant(obj, self.form.coolantController)
self.selectInComboBox(obj.BoundBox, self.form.boundBoxSelect)
@@ -126,20 +160,36 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
and the UI panel QComboBox list.
The original method is commented out below.
"""
idx = self.propEnums['CutPattern'].index(obj.CutPattern)
self.form.cutPattern.setCurrentIndex(idx)
idx = self.propEnums['ProfileEdges'].index(obj.ProfileEdges)
self.form.profileEdges.setCurrentIndex(idx)
# self.selectInComboBox(obj.CutPattern, self.form.cutPattern)
# self.selectInComboBox(obj.ProfileEdges, self.form.profileEdges)
# idx = self.propEnums["CutPattern"].index(obj.CutPattern)
# self.form.cutPattern.setCurrentIndex(idx)
# idx = self.propEnums["ProfileEdges"].index(obj.ProfileEdges)
# self.form.profileEdges.setCurrentIndex(idx)
self.selectInComboBox(obj.CutPattern, self.form.cutPattern)
self.selectInComboBox(obj.ProfileEdges, self.form.profileEdges)
self.form.avoidLastX_Faces.setValue(obj.AvoidLastX_Faces)
self.form.boundBoxExtraOffsetX.setText(FreeCAD.Units.Quantity(obj.DropCutterExtraOffset.x, FreeCAD.Units.Length).UserString)
self.form.boundBoxExtraOffsetY.setText(FreeCAD.Units.Quantity(obj.DropCutterExtraOffset.y, FreeCAD.Units.Length).UserString)
self.form.boundBoxExtraOffsetX.setText(
FreeCAD.Units.Quantity(
obj.DropCutterExtraOffset.x, FreeCAD.Units.Length
).UserString
)
self.form.boundBoxExtraOffsetY.setText(
FreeCAD.Units.Quantity(
obj.DropCutterExtraOffset.y, FreeCAD.Units.Length
).UserString
)
self.selectInComboBox(obj.DropCutterDir, self.form.dropCutterDirSelect)
self.form.depthOffset.setText(FreeCAD.Units.Quantity(obj.DepthOffset.Value, FreeCAD.Units.Length).UserString)
self.form.depthOffset.setText(
FreeCAD.Units.Quantity(
obj.DepthOffset.Value, FreeCAD.Units.Length
).UserString
)
self.form.stepOver.setValue(obj.StepOver)
self.form.sampleInterval.setText(FreeCAD.Units.Quantity(obj.SampleInterval.Value, FreeCAD.Units.Length).UserString)
self.form.sampleInterval.setText(
FreeCAD.Units.Quantity(
obj.SampleInterval.Value, FreeCAD.Units.Length
).UserString
)
if obj.UseStartPoint:
self.form.useStartPoint.setCheckState(QtCore.Qt.Checked)
@@ -164,7 +214,7 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
self.updateVisibility()
def getSignalsForUpdate(self, obj):
'''getSignalsForUpdate(obj) ... return list of signals for updating obj'''
"""getSignalsForUpdate(obj) ... return list of signals for updating obj"""
signals = []
signals.append(self.form.toolController.currentIndexChanged)
signals.append(self.form.coolantController.currentIndexChanged)
@@ -188,12 +238,12 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
return signals
def updateVisibility(self, sentObj=None):
'''updateVisibility(sentObj=None)... Updates visibility of Tasks panel objects.'''
if self.form.scanType.currentText() == 'Planar':
"""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'):
if hasattr(self.form, "profileEdges"):
self.form.profileEdges.show()
self.form.profileEdges_label.show()
self.form.avoidLastX_Faces.show()
@@ -204,11 +254,11 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
self.form.boundBoxExtraOffset_label.hide()
self.form.dropCutterDirSelect.hide()
self.form.dropCutterDirSelect_label.hide()
elif self.form.scanType.currentText() == 'Rotational':
elif self.form.scanType.currentText() == "Rotational":
self.form.cutPattern.hide()
self.form.cutPattern_label.hide()
self.form.optimizeStepOverTransitions.hide()
if hasattr(self.form, 'profileEdges'):
if hasattr(self.form, "profileEdges"):
self.form.profileEdges.hide()
self.form.profileEdges_label.hide()
self.form.avoidLastX_Faces.hide()
@@ -224,12 +274,16 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
self.form.scanType.currentIndexChanged.connect(self.updateVisibility)
Command = PathOpGui.SetupOperation('Surface',
PathSurface.Create,
TaskPanelOpPage,
'Path_3DSurface',
QtCore.QT_TRANSLATE_NOOP("Path_Surface", "3D Surface"),
QtCore.QT_TRANSLATE_NOOP("Path_Surface", "Create a 3D Surface Operation from a model"),
PathSurface.SetupProperties)
Command = PathOpGui.SetupOperation(
"Surface",
PathSurface.Create,
TaskPanelOpPage,
"Path_3DSurface",
QtCore.QT_TRANSLATE_NOOP("Path_Surface", "3D Surface"),
QtCore.QT_TRANSLATE_NOOP(
"Path_Surface", "Create a 3D Surface Operation from a model"
),
PathSurface.SetupProperties,
)
FreeCAD.Console.PrintLog("Loading PathSurfaceGui... done\n")

File diff suppressed because it is too large Load Diff

View File

@@ -22,6 +22,8 @@
# ***************************************************************************
from __future__ import print_function
from PySide import QtCore
import FreeCAD
__title__ = "Path Waterline Operation"
__author__ = "russ4262 (Russell Johnson), sliptonic (Brad Collette)"
@@ -29,28 +31,24 @@ __url__ = "http://www.freecadweb.org"
__doc__ = "Class and implementation of Waterline operation."
__contributors__ = ""
import FreeCAD
from PySide import QtCore
# OCL must be installed
try:
import ocl
except ImportError:
msg = QtCore.QCoreApplication.translate(
"PathWaterline", "This operation requires OpenCamLib to be installed."
"path_waterline", "This operation requires OpenCamLib to be installed."
)
FreeCAD.Console.PrintError(msg + "\n")
raise ImportError
# import sys
# sys.exit(msg)
import Path
import PathScripts.PathLog as PathLog
import PathScripts.PathUtils as PathUtils
import PathScripts.PathOp as PathOp
import PathScripts.PathSurfaceSupport as PathSurfaceSupport
import time
import PathScripts.PathUtils as PathUtils
import math
import time
from PySide.QtCore import QT_TRANSLATE_NOOP
# lazily loaded modules
from lazy_loader.lazy_loader import LazyLoader
@@ -60,13 +58,13 @@ Part = LazyLoader("Part", globals(), "Part")
if FreeCAD.GuiUp:
import FreeCADGui
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
# PathLog.trackModule(PathLog.thisModule())
if False:
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
PathLog.trackModule(PathLog.thisModule())
else:
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
# Qt translation handling
def translate(context, text, disambig=None):
return QtCore.QCoreApplication.translate(context, text, disambig)
translate = FreeCAD.Qt.translate
class ObjectWaterline(PathOp.ObjectOp):
@@ -83,6 +81,79 @@ class ObjectWaterline(PathOp.ObjectOp):
| PathOp.FeatureBaseFaces
)
@classmethod
def propertyEnumerations(self, dataType="data"):
"""propertyEnumerations(dataType="data")... return property enumeration lists of specified dataType.
Args:
dataType = 'data', 'raw', 'translated'
Notes:
'data' is list of internal string literals used in code
'raw' is list of (translated_text, data_string) tuples
'translated' is list of translated string literals
"""
# Enumeration lists for App::PropertyEnumeration properties
enums = {
"Algorithm": [
(translate("path_waterline", "OCL Dropcutter"), "OCL Dropcutter"),
(translate("path_waterline", "Experimental"), "Experimental"),
],
"BoundBox": [
(translate("path_waterline", "BaseBoundBox"), "BaseBoundBox"),
(translate("path_waterline", "Stock"), "Stock"),
],
"PatternCenterAt": [
(translate("path_waterline", "CenterOfMass"), "CenterOfMass"),
(translate("path_waterline", "CenterOfBoundBox"), "CenterOfBoundBox"),
(translate("path_waterline", "XminYmin"), "XminYmin"),
(translate("path_waterline", "Custom"), "Custom"),
],
"ClearLastLayer": [
(translate("path_waterline", "Off"), "Off"),
(translate("path_waterline", "Circular"), "Circular"),
(translate("path_waterline", "CircularZigZag"), "CircularZigZag"),
(translate("path_waterline", "Line"), "Line"),
(translate("path_waterline", "Offset"), "Offset"),
(translate("path_waterline", "Spiral"), "Spiral"),
(translate("path_waterline", "ZigZag"), "ZigZag"),
],
"CutMode": [
(translate("path_waterline", "Conventional"), "Conventional"),
(translate("path_waterline", "Climb"), "Climb"),
],
"CutPattern": [
(translate("path_waterline", "None"), "None"),
(translate("path_waterline", "Circular"), "Circular"),
(translate("path_waterline", "CircularZigZag"), "CircularZigZag"),
(translate("path_waterline", "Line"), "Line"),
(translate("path_waterline", "Offset"), "Offset"),
(translate("path_waterline", "Spiral"), "Spiral"),
(translate("path_waterline", "ZigZag"), "ZigZag"),
],
"HandleMultipleFeatures": [
(translate("path_waterline", "Collectively"), "Collectively"),
(translate("path_waterline", "Individually"), "Individually"),
],
"LayerMode": [
(translate("path_waterline", "Single-pass"), "Single-pass"),
(translate("path_waterline", "Multi-pass"), "Multi-pass"),
],
}
if dataType == "raw":
return enums
data = list()
idx = 0 if dataType == "translated" else 1
PathLog.debug(enums)
for k, v in enumerate(enums):
data.append((v, [tup[idx] for tup in enums[v]]))
PathLog.debug(data)
return data
def initOperation(self, obj):
"""initOperation(obj) ... Initialize the operation by
managing property creation and property editor status."""
@@ -108,10 +179,10 @@ class ObjectWaterline(PathOp.ObjectOp):
# Set enumeration lists for enumeration properties
if len(self.addNewProps) > 0:
ENUMS = self.opPropertyEnumerations()
ENUMS = self.propertyEnumerations()
for n in ENUMS:
if n in self.addNewProps:
setattr(obj, n, ENUMS[n])
if n[0] in self.addNewProps:
setattr(obj, n[0], n[1])
if warn:
newPropMsg = translate("PathWaterline", "New property added to")
@@ -128,7 +199,7 @@ class ObjectWaterline(PathOp.ObjectOp):
"App::PropertyBool",
"ShowTempObjects",
"Debug",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"Show the temporary path construction objects when module is in DEBUG mode.",
),
@@ -137,7 +208,7 @@ class ObjectWaterline(PathOp.ObjectOp):
"App::PropertyDistance",
"AngularDeflection",
"Mesh Conversion",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"Smaller values yield a finer, more accurate the mesh. Smaller values increase processing time a lot.",
),
@@ -146,7 +217,7 @@ class ObjectWaterline(PathOp.ObjectOp):
"App::PropertyDistance",
"LinearDeflection",
"Mesh Conversion",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"Smaller values yield a finer, more accurate the mesh. Smaller values do not increase processing time much.",
),
@@ -155,7 +226,7 @@ class ObjectWaterline(PathOp.ObjectOp):
"App::PropertyInteger",
"AvoidLastX_Faces",
"Selected Geometry Settings",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"Avoid cutting the last 'N' faces in the Base Geometry list of selected faces.",
),
@@ -164,7 +235,7 @@ class ObjectWaterline(PathOp.ObjectOp):
"App::PropertyBool",
"AvoidLastX_InternalFeatures",
"Selected Geometry Settings",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property", "Do not cut internal features on avoided faces."
),
),
@@ -172,7 +243,7 @@ class ObjectWaterline(PathOp.ObjectOp):
"App::PropertyDistance",
"BoundaryAdjustment",
"Selected Geometry Settings",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"Positive values push the cutter toward, or beyond, the boundary. Negative values retract the cutter away from the boundary.",
),
@@ -181,7 +252,7 @@ class ObjectWaterline(PathOp.ObjectOp):
"App::PropertyBool",
"BoundaryEnforcement",
"Selected Geometry Settings",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"If true, the cutter will remain inside the boundaries of the model or selected face(s).",
),
@@ -190,7 +261,7 @@ class ObjectWaterline(PathOp.ObjectOp):
"App::PropertyEnumeration",
"HandleMultipleFeatures",
"Selected Geometry Settings",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"Choose how to process multiple Base Geometry features.",
),
@@ -199,7 +270,7 @@ class ObjectWaterline(PathOp.ObjectOp):
"App::PropertyDistance",
"InternalFeaturesAdjustment",
"Selected Geometry Settings",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"Positive values push the cutter toward, or into, the feature. Negative values retract the cutter away from the feature.",
),
@@ -208,7 +279,7 @@ class ObjectWaterline(PathOp.ObjectOp):
"App::PropertyBool",
"InternalFeaturesCut",
"Selected Geometry Settings",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"Cut internal feature areas within a larger selected face.",
),
@@ -217,7 +288,7 @@ class ObjectWaterline(PathOp.ObjectOp):
"App::PropertyEnumeration",
"Algorithm",
"Clearing Options",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"Select the algorithm to use: OCL Dropcutter*, or Experimental (Not OCL based).",
),
@@ -226,7 +297,7 @@ class ObjectWaterline(PathOp.ObjectOp):
"App::PropertyEnumeration",
"BoundBox",
"Clearing Options",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property", "Select the overall boundary for the operation."
),
),
@@ -234,7 +305,7 @@ class ObjectWaterline(PathOp.ObjectOp):
"App::PropertyEnumeration",
"ClearLastLayer",
"Clearing Options",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"Set to clear last layer in a `Multi-pass` operation.",
),
@@ -243,7 +314,7 @@ class ObjectWaterline(PathOp.ObjectOp):
"App::PropertyEnumeration",
"CutMode",
"Clearing Options",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"Set the direction for the cutting tool to engage the material: Climb (ClockWise) or Conventional (CounterClockWise)",
),
@@ -252,7 +323,7 @@ class ObjectWaterline(PathOp.ObjectOp):
"App::PropertyEnumeration",
"CutPattern",
"Clearing Options",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"Set the geometric clearing pattern to use for the operation.",
),
@@ -261,7 +332,7 @@ class ObjectWaterline(PathOp.ObjectOp):
"App::PropertyFloat",
"CutPatternAngle",
"Clearing Options",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property", "The yaw angle used for certain clearing patterns"
),
),
@@ -269,7 +340,7 @@ class ObjectWaterline(PathOp.ObjectOp):
"App::PropertyBool",
"CutPatternReversed",
"Clearing Options",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"Reverse the cut order of the stepover paths. For circular cut patterns, begin at the outside and work toward the center.",
),
@@ -278,7 +349,7 @@ class ObjectWaterline(PathOp.ObjectOp):
"App::PropertyDistance",
"DepthOffset",
"Clearing Options",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"Set the Z-axis depth offset from the target surface.",
),
@@ -287,7 +358,7 @@ class ObjectWaterline(PathOp.ObjectOp):
"App::PropertyDistance",
"IgnoreOuterAbove",
"Clearing Options",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property", "Ignore outer waterlines above this height."
),
),
@@ -295,7 +366,7 @@ class ObjectWaterline(PathOp.ObjectOp):
"App::PropertyEnumeration",
"LayerMode",
"Clearing Options",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"Complete the operation in a single pass at depth, or mulitiple passes to final depth.",
),
@@ -304,7 +375,7 @@ class ObjectWaterline(PathOp.ObjectOp):
"App::PropertyVectorDistance",
"PatternCenterCustom",
"Clearing Options",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property", "Set the start point for the cut pattern."
),
),
@@ -312,7 +383,7 @@ class ObjectWaterline(PathOp.ObjectOp):
"App::PropertyEnumeration",
"PatternCenterAt",
"Clearing Options",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"Choose location of the center point for starting the cut pattern.",
),
@@ -321,7 +392,7 @@ class ObjectWaterline(PathOp.ObjectOp):
"App::PropertyDistance",
"SampleInterval",
"Clearing Options",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"Set the sampling resolution. Smaller values quickly increase processing time.",
),
@@ -330,7 +401,7 @@ class ObjectWaterline(PathOp.ObjectOp):
"App::PropertyFloat",
"StepOver",
"Clearing Options",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"Set the stepover percentage, based on the tool's diameter.",
),
@@ -339,7 +410,7 @@ class ObjectWaterline(PathOp.ObjectOp):
"App::PropertyBool",
"OptimizeLinearPaths",
"Optimization",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"Enable optimization of linear paths (co-linear points). Removes unnecessary co-linear points from G-Code output.",
),
@@ -348,7 +419,7 @@ class ObjectWaterline(PathOp.ObjectOp):
"App::PropertyBool",
"OptimizeStepOverTransitions",
"Optimization",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"Enable separate optimization of transitions between, and breaks within, each step over path.",
),
@@ -357,7 +428,7 @@ class ObjectWaterline(PathOp.ObjectOp):
"App::PropertyDistance",
"GapThreshold",
"Optimization",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"Collinear and co-radial artifact gaps that are smaller than this threshold are closed in the path.",
),
@@ -366,7 +437,7 @@ class ObjectWaterline(PathOp.ObjectOp):
"App::PropertyString",
"GapSizes",
"Optimization",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"Feedback: three smallest gaps identified in the path geometry.",
),
@@ -375,7 +446,7 @@ class ObjectWaterline(PathOp.ObjectOp):
"App::PropertyVectorDistance",
"StartPoint",
"Start Point",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"The custom start point for the path of this operation",
),
@@ -384,46 +455,12 @@ class ObjectWaterline(PathOp.ObjectOp):
"App::PropertyBool",
"UseStartPoint",
"Start Point",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property", "Make True, if specifying a Start Point"
),
),
]
def opPropertyEnumerations(self):
# Enumeration lists for App::PropertyEnumeration properties
return {
"Algorithm": ["OCL Dropcutter", "Experimental"],
"BoundBox": ["BaseBoundBox", "Stock"],
"PatternCenterAt": [
"CenterOfMass",
"CenterOfBoundBox",
"XminYmin",
"Custom",
],
"ClearLastLayer": [
"Off",
"Circular",
"CircularZigZag",
"Line",
"Offset",
"Spiral",
"ZigZag",
],
"CutMode": ["Conventional", "Climb"],
"CutPattern": [
"None",
"Circular",
"CircularZigZag",
"Line",
"Offset",
"Spiral",
"ZigZag",
], # Additional goals ['Offset', 'Spiral', 'ZigZagOffset', 'Grid', 'Triangle']
"HandleMultipleFeatures": ["Collectively", "Individually"],
"LayerMode": ["Single-pass", "Multi-pass"],
}
def opPropertyDefaults(self, obj, job):
"""opPropertyDefaults(obj, job) ... returns a dictionary
of default values for the operation's properties."""
@@ -543,15 +580,16 @@ class ObjectWaterline(PathOp.ObjectOp):
obj.setEditorMode("ShowTempObjects", mode)
# Repopulate enumerations in case of changes
ENUMS = self.opPropertyEnumerations()
ENUMS = self.propertyEnumerations()
for n in ENUMS:
restore = False
if hasattr(obj, n):
val = obj.getPropertyByName(n)
if hasattr(obj, n[0]):
val = obj.getPropertyByName(n[0])
restore = True
setattr(obj, n, ENUMS[n])
setattr(obj, n[0], n[1])
if restore:
setattr(obj, n, val)
setattr(obj, n[0], val)
self.setEditorProperties(obj)

View File

@@ -21,70 +21,97 @@
# * *
# ***************************************************************************
from PySide import QtCore
from PySide.QtCore import QT_TRANSLATE_NOOP
import FreeCAD
import FreeCADGui
import PathGui as PGui # ensure Path/Gui/Resources are loaded
import PathScripts.PathWaterline as PathWaterline
import PathScripts.PathLog as PathLog
import PathScripts.PathGui as PathGui
import PathScripts.PathOpGui as PathOpGui
from PySide import QtCore
import PathScripts.PathWaterline as PathWaterline
__title__ = "Path Waterline Operation UI"
__author__ = "sliptonic (Brad Collette), russ4262 (Russell Johnson)"
__url__ = "http://www.freecadweb.org"
__doc__ = "Waterline operation page controller and command implementation."
translate = FreeCAD.Qt.translate
if False:
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
PathLog.trackModule(PathLog.thisModule())
else:
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
class TaskPanelOpPage(PathOpGui.TaskPanelPage):
'''Page controller class for the Waterline operation.'''
"""Page controller class for the Waterline operation."""
def initPage(self, obj):
self.setTitle("Waterline - " + obj.Label)
self.updateVisibility()
def getForm(self):
'''getForm() ... returns UI'''
return FreeCADGui.PySideUic.loadUi(":/panels/PageOpWaterlineEdit.ui")
"""getForm() ... returns UI"""
form = FreeCADGui.PySideUic.loadUi(":/panels/PageOpWaterlineEdit.ui")
comboToPropertyMap = [
("algorithmSelect", "Algorithm"),
("boundBoxSelect", "BoundBox"),
("layerMode", "LayerMode"),
("cutPattern", "CutPattern"),
]
enumTups = PathWaterline.ObjectWaterline.propertyEnumerations(dataType="raw")
PathGui.populateCombobox(form, enumTups, comboToPropertyMap)
return form
def getFields(self, obj):
'''getFields(obj) ... transfers values from UI to obj's proprties'''
"""getFields(obj) ... transfers values from UI to obj's proprties"""
self.updateToolController(obj, self.form.toolController)
self.updateCoolant(obj, self.form.coolantController)
if obj.Algorithm != str(self.form.algorithmSelect.currentText()):
obj.Algorithm = str(self.form.algorithmSelect.currentText())
if obj.Algorithm != str(self.form.algorithmSelect.currentData()):
obj.Algorithm = str(self.form.algorithmSelect.currentData())
if obj.BoundBox != str(self.form.boundBoxSelect.currentText()):
obj.BoundBox = str(self.form.boundBoxSelect.currentText())
if obj.BoundBox != str(self.form.boundBoxSelect.currentData()):
obj.BoundBox = str(self.form.boundBoxSelect.currentData())
if obj.LayerMode != str(self.form.layerMode.currentText()):
obj.LayerMode = str(self.form.layerMode.currentText())
if obj.LayerMode != str(self.form.layerMode.currentData()):
obj.LayerMode = str(self.form.layerMode.currentData())
if obj.CutPattern != str(self.form.cutPattern.currentText()):
obj.CutPattern = str(self.form.cutPattern.currentText())
if obj.CutPattern != str(self.form.cutPattern.currentData()):
obj.CutPattern = str(self.form.cutPattern.currentData())
PathGui.updateInputField(obj, 'BoundaryAdjustment', self.form.boundaryAdjustment)
PathGui.updateInputField(
obj, "BoundaryAdjustment", self.form.boundaryAdjustment
)
if obj.StepOver != self.form.stepOver.value():
obj.StepOver = self.form.stepOver.value()
PathGui.updateInputField(obj, 'SampleInterval', self.form.sampleInterval)
PathGui.updateInputField(obj, "SampleInterval", self.form.sampleInterval)
if obj.OptimizeLinearPaths != self.form.optimizeEnabled.isChecked():
obj.OptimizeLinearPaths = self.form.optimizeEnabled.isChecked()
def setFields(self, obj):
'''setFields(obj) ... transfers obj's property values to UI'''
"""setFields(obj) ... transfers obj's property values to UI"""
self.setupToolController(obj, self.form.toolController)
self.setupCoolant(obj, self.form.coolantController)
self.selectInComboBox(obj.Algorithm, self.form.algorithmSelect)
self.selectInComboBox(obj.BoundBox, self.form.boundBoxSelect)
self.selectInComboBox(obj.LayerMode, self.form.layerMode)
self.selectInComboBox(obj.CutPattern, self.form.cutPattern)
self.form.boundaryAdjustment.setText(FreeCAD.Units.Quantity(obj.BoundaryAdjustment.Value, FreeCAD.Units.Length).UserString)
self.form.boundaryAdjustment.setText(
FreeCAD.Units.Quantity(
obj.BoundaryAdjustment.Value, FreeCAD.Units.Length
).UserString
)
self.form.stepOver.setValue(obj.StepOver)
self.form.sampleInterval.setText(FreeCAD.Units.Quantity(obj.SampleInterval.Value, FreeCAD.Units.Length).UserString)
self.form.sampleInterval.setText(
FreeCAD.Units.Quantity(
obj.SampleInterval.Value, FreeCAD.Units.Length
).UserString
)
if obj.OptimizeLinearPaths:
self.form.optimizeEnabled.setCheckState(QtCore.Qt.Checked)
@@ -94,7 +121,7 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
self.updateVisibility()
def getSignalsForUpdate(self, obj):
'''getSignalsForUpdate(obj) ... return list of signals for updating obj'''
"""getSignalsForUpdate(obj) ... return list of signals for updating obj"""
signals = []
signals.append(self.form.toolController.currentIndexChanged)
signals.append(self.form.coolantController.currentIndexChanged)
@@ -110,11 +137,11 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
return signals
def updateVisibility(self, sentObj=None):
'''updateVisibility(sentObj=None)... Updates visibility of Tasks panel objects.'''
Algorithm = self.form.algorithmSelect.currentText()
"""updateVisibility(sentObj=None)... Updates visibility of Tasks panel objects."""
Algorithm = self.form.algorithmSelect.currentData()
self.form.optimizeEnabled.hide() # Has no independent QLabel object
if Algorithm == 'OCL Dropcutter':
if Algorithm == "OCL Dropcutter":
self.form.cutPattern.hide()
self.form.cutPattern_label.hide()
self.form.boundaryAdjustment.hide()
@@ -123,12 +150,12 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
self.form.stepOver_label.hide()
self.form.sampleInterval.show()
self.form.sampleInterval_label.show()
elif Algorithm == 'Experimental':
elif Algorithm == "Experimental":
self.form.cutPattern.show()
self.form.boundaryAdjustment.show()
self.form.cutPattern_label.show()
self.form.boundaryAdjustment_label.show()
if self.form.cutPattern.currentText() == 'None':
if self.form.cutPattern.currentData() == "None":
self.form.stepOver.hide()
self.form.stepOver_label.hide()
else:
@@ -142,12 +169,14 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
self.form.cutPattern.currentIndexChanged.connect(self.updateVisibility)
Command = PathOpGui.SetupOperation('Waterline',
PathWaterline.Create,
TaskPanelOpPage,
'Path_Waterline',
QtCore.QT_TRANSLATE_NOOP("Path_Waterline", "Waterline"),
QtCore.QT_TRANSLATE_NOOP("Path_Waterline", "Create a Waterline Operation from a model"),
PathWaterline.SetupProperties)
Command = PathOpGui.SetupOperation(
"Waterline",
PathWaterline.Create,
TaskPanelOpPage,
"Path_Waterline",
QT_TRANSLATE_NOOP("Path_Waterline", "Waterline"),
QT_TRANSLATE_NOOP("Path_Waterline", "Create a Waterline Operation from a model"),
PathWaterline.SetupProperties,
)
FreeCAD.Console.PrintLog("Loading PathWaterlineGui... done\n")