Black (surface)

waterline black

propertybag black
This commit is contained in:
sliptonic
2022-01-24 18:06:59 -06:00
parent efaebbb7fc
commit 9c5e4e2340
7 changed files with 1106 additions and 703 deletions

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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")

View File

@@ -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

View File

@@ -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")