Black
Black (surface) waterline black propertybag black
This commit is contained in:
@@ -31,8 +31,9 @@ from PySide import QtCore
|
||||
|
||||
# lazily loaded modules
|
||||
from lazy_loader.lazy_loader import LazyLoader
|
||||
Draft = LazyLoader('Draft', globals(), 'Draft')
|
||||
Part = LazyLoader('Part', globals(), 'Part')
|
||||
|
||||
Draft = LazyLoader("Draft", globals(), "Draft")
|
||||
Part = LazyLoader("Part", globals(), "Part")
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
import FreeCADGui
|
||||
@@ -54,85 +55,104 @@ def translate(context, text, disambig=None):
|
||||
|
||||
|
||||
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",
|
||||
QtCore.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 +161,36 @@ 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",
|
||||
QtCore.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 +209,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 +368,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 +376,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 +384,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 +436,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 +492,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
|
||||
|
||||
@@ -30,7 +30,8 @@ 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)"
|
||||
@@ -38,7 +39,7 @@ __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())
|
||||
# PathLog.trackModule(PathLog.thisModule())
|
||||
|
||||
PrintWireDebug = False
|
||||
|
||||
@@ -46,15 +47,19 @@ PrintWireDebug = False
|
||||
def translate(context, text, disambig=None):
|
||||
return QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
|
||||
|
||||
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 +70,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 +108,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 +148,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 +184,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 +221,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 +267,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 +283,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 +323,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 +335,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 +348,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 +367,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 +395,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 +420,3 @@ def offsetWire(wire, base, offset, forward, Side = None):
|
||||
edges.reverse()
|
||||
|
||||
return orientWire(Part.Wire(edges), None)
|
||||
|
||||
|
||||
@@ -24,27 +24,29 @@ import FreeCAD
|
||||
import PySide
|
||||
import re
|
||||
|
||||
__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)
|
||||
|
||||
|
||||
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 +54,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",
|
||||
PySide.QtCore.QT_TRANSLATE_NOOP(
|
||||
"PathPropertyBag", "List of custom property groups"
|
||||
),
|
||||
)
|
||||
self.onDocumentRestored(obj)
|
||||
|
||||
def __getstate__(self):
|
||||
@@ -69,14 +79,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 +95,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 +121,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 +130,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
|
||||
|
||||
|
||||
@@ -22,7 +22,8 @@
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
#import PathGui
|
||||
|
||||
# import PathGui
|
||||
import PathScripts.PathIconViewProvider as PathIconViewProvider
|
||||
import PathScripts.PathLog as PathLog
|
||||
import PathScripts.PathPropertyBag as PathPropertyBag
|
||||
@@ -38,15 +39,16 @@ __url__ = "https://www.freecadweb.org"
|
||||
__doc__ = "Task panel editor for a PropertyBag"
|
||||
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
#PathLog.trackModule(PathLog.thisModule())
|
||||
# PathLog.trackModule(PathLog.thisModule())
|
||||
|
||||
# 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 +75,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 +97,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 +129,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 +148,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 +163,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 +182,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 +220,82 @@ 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(
|
||||
translate("PathPropertyBag", "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 +332,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 +349,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 +391,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 +411,6 @@ class TaskPanel(object):
|
||||
|
||||
self.propertyModifyIndex(index)
|
||||
|
||||
|
||||
def propertyRemove(self):
|
||||
PathLog.track()
|
||||
# first find all rows which need to be removed
|
||||
@@ -387,24 +426,33 @@ 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(
|
||||
translate("PathPropertyBag", "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("PathPropertyBag", "PropertyBag"),
|
||||
"ToolTip": translate(
|
||||
"PathPropertyBag",
|
||||
"Creates an object which can be used to store reference properties.",
|
||||
),
|
||||
}
|
||||
|
||||
def IsActive(self):
|
||||
return not FreeCAD.ActiveDocument is None
|
||||
@@ -414,17 +462,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")
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
import PathGui as PGui # ensure Path/Gui/Resources are loaded
|
||||
import PathGui as PGui # ensure Path/Gui/Resources are loaded
|
||||
import PathScripts.PathSurface as PathSurface
|
||||
import PathScripts.PathGui as PathGui
|
||||
import PathScripts.PathOpGui as PathOpGui
|
||||
@@ -36,7 +36,7 @@ __doc__ = "Surface operation page controller and command implementation."
|
||||
|
||||
|
||||
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)
|
||||
@@ -45,11 +45,11 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
|
||||
self.propEnums = PathSurface.ObjectSurface.opPropertyEnumerations(False)
|
||||
|
||||
def getForm(self):
|
||||
'''getForm() ... returns UI'''
|
||||
"""getForm() ... returns UI"""
|
||||
return FreeCADGui.PySideUic.loadUi(":/panels/PageOpSurfaceEdit.ui")
|
||||
|
||||
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)
|
||||
|
||||
@@ -74,29 +74,33 @@ 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()]
|
||||
val = self.propEnums["CutPattern"][self.form.cutPattern.currentIndex()]
|
||||
if obj.CutPattern != val:
|
||||
obj.CutPattern = val
|
||||
|
||||
val = self.propEnums['ProfileEdges'][self.form.profileEdges.currentIndex()]
|
||||
val = self.propEnums["ProfileEdges"][self.form.profileEdges.currentIndex()]
|
||||
if obj.ProfileEdges != val:
|
||||
obj.ProfileEdges = val
|
||||
|
||||
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())
|
||||
|
||||
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 +111,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 +135,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)
|
||||
idx = self.propEnums["CutPattern"].index(obj.CutPattern)
|
||||
self.form.cutPattern.setCurrentIndex(idx)
|
||||
idx = self.propEnums['ProfileEdges'].index(obj.ProfileEdges)
|
||||
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 +189,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 +213,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 +229,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 +249,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
@@ -23,7 +23,7 @@
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
import PathGui as PGui # ensure Path/Gui/Resources are loaded
|
||||
import PathGui as PGui # ensure Path/Gui/Resources are loaded
|
||||
import PathScripts.PathWaterline as PathWaterline
|
||||
import PathScripts.PathGui as PathGui
|
||||
import PathScripts.PathOpGui as PathOpGui
|
||||
@@ -37,18 +37,18 @@ __doc__ = "Waterline operation page controller and command implementation."
|
||||
|
||||
|
||||
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'''
|
||||
"""getForm() ... returns UI"""
|
||||
return FreeCADGui.PySideUic.loadUi(":/panels/PageOpWaterlineEdit.ui")
|
||||
|
||||
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)
|
||||
|
||||
@@ -64,27 +64,37 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
|
||||
if obj.CutPattern != str(self.form.cutPattern.currentText()):
|
||||
obj.CutPattern = str(self.form.cutPattern.currentText())
|
||||
|
||||
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 +104,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 +120,11 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
|
||||
return signals
|
||||
|
||||
def updateVisibility(self, sentObj=None):
|
||||
'''updateVisibility(sentObj=None)... Updates visibility of Tasks panel objects.'''
|
||||
"""updateVisibility(sentObj=None)... Updates visibility of Tasks panel objects."""
|
||||
Algorithm = self.form.algorithmSelect.currentText()
|
||||
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 +133,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.currentText() == "None":
|
||||
self.form.stepOver.hide()
|
||||
self.form.stepOver_label.hide()
|
||||
else:
|
||||
@@ -142,12 +152,16 @@ 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",
|
||||
QtCore.QT_TRANSLATE_NOOP("Path_Waterline", "Waterline"),
|
||||
QtCore.QT_TRANSLATE_NOOP(
|
||||
"Path_Waterline", "Create a Waterline Operation from a model"
|
||||
),
|
||||
PathWaterline.SetupProperties,
|
||||
)
|
||||
|
||||
FreeCAD.Console.PrintLog("Loading PathWaterlineGui... done\n")
|
||||
|
||||
Reference in New Issue
Block a user