Black
This commit is contained in:
@@ -30,20 +30,18 @@ import PathScripts.PathUtils as PathUtils
|
||||
import PathScripts.PathGeom as PathGeom
|
||||
import PathScripts.PathPreferences as PathPreferences
|
||||
|
||||
import traceback
|
||||
|
||||
import math
|
||||
|
||||
from PySide import QtCore
|
||||
|
||||
__doc__ = "Class and implementation of Path Vcarve operation"
|
||||
|
||||
PRIMARY = 0
|
||||
PRIMARY = 0
|
||||
SECONDARY = 1
|
||||
EXTERIOR1 = 2
|
||||
EXTERIOR2 = 3
|
||||
COLINEAR = 4
|
||||
TWIN = 5
|
||||
COLINEAR = 4
|
||||
TWIN = 5
|
||||
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
# PathLog.trackModule(PathLog.thisModule())
|
||||
@@ -57,7 +55,7 @@ def translate(context, text, disambig=None):
|
||||
VD = []
|
||||
Vertex = {}
|
||||
|
||||
_sorting = 'global'
|
||||
_sorting = "global"
|
||||
|
||||
|
||||
def _collectVoronoiWires(vd):
|
||||
@@ -104,13 +102,13 @@ def _collectVoronoiWires(vd):
|
||||
we = []
|
||||
vFirst = knots[0]
|
||||
vStart = vFirst
|
||||
vLast = vFirst
|
||||
vLast = vFirst
|
||||
if len(vertex[vStart]):
|
||||
while vStart is not None:
|
||||
vLast = vStart
|
||||
edges = vertex[vStart]
|
||||
vLast = vStart
|
||||
edges = vertex[vStart]
|
||||
if len(edges) > 0:
|
||||
edge = edges[0]
|
||||
edge = edges[0]
|
||||
vStart = traverse(vStart, edge, we)
|
||||
else:
|
||||
vStart = None
|
||||
@@ -133,11 +131,11 @@ def _sortVoronoiWires(wires, start=FreeCAD.Vector(0, 0, 0)):
|
||||
return (p, l)
|
||||
|
||||
begin = {}
|
||||
end = {}
|
||||
end = {}
|
||||
|
||||
for i, w in enumerate(wires):
|
||||
begin[i] = w[0].Vertices[0].toPoint()
|
||||
end[i] = w[-1].Vertices[1].toPoint()
|
||||
end[i] = w[-1].Vertices[1].toPoint()
|
||||
|
||||
result = []
|
||||
while begin:
|
||||
@@ -145,23 +143,24 @@ def _sortVoronoiWires(wires, start=FreeCAD.Vector(0, 0, 0)):
|
||||
(eIdx, eLen) = closestTo(start, end)
|
||||
if bLen < eLen:
|
||||
result.append(wires[bIdx])
|
||||
start = end[bIdx]
|
||||
del begin[bIdx]
|
||||
del end[bIdx]
|
||||
start = end[bIdx]
|
||||
del begin[bIdx]
|
||||
del end[bIdx]
|
||||
else:
|
||||
result.append([e.Twin for e in reversed(wires[eIdx])])
|
||||
start = begin[eIdx]
|
||||
del begin[eIdx]
|
||||
del end[eIdx]
|
||||
del begin[eIdx]
|
||||
del end[eIdx]
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class _Geometry(object):
|
||||
'''POD class so the limits only have to be calculated once.'''
|
||||
"""POD class so the limits only have to be calculated once."""
|
||||
|
||||
def __init__(self, zStart, zStop, zScale):
|
||||
self.start = zStart
|
||||
self.stop = zStop
|
||||
self.stop = zStop
|
||||
self.scale = zScale
|
||||
|
||||
@classmethod
|
||||
@@ -170,8 +169,8 @@ class _Geometry(object):
|
||||
rMin = float(tool.TipDiameter) / 2.0
|
||||
toolangle = math.tan(math.radians(tool.CuttingEdgeAngle.Value / 2.0))
|
||||
zScale = 1.0 / toolangle
|
||||
zStop = zStart - rMax * zScale
|
||||
zOff = rMin * zScale
|
||||
zStop = zStart - rMax * zScale
|
||||
zOff = rMin * zScale
|
||||
|
||||
return _Geometry(zStart + zOff, max(zStop + zOff, zFinal), zScale)
|
||||
|
||||
@@ -182,45 +181,74 @@ class _Geometry(object):
|
||||
|
||||
return cls.FromTool(obj.ToolController.Tool, zStart, finalDepth)
|
||||
|
||||
|
||||
def _calculate_depth(MIC, geom):
|
||||
# given a maximum inscribed circle (MIC) and tool angle,
|
||||
# return depth of cut relative to zStart.
|
||||
depth = geom.start - round(MIC * geom.scale, 4)
|
||||
PathLog.debug('zStart value: {} depth: {}'.format(geom.start, depth))
|
||||
PathLog.debug("zStart value: {} depth: {}".format(geom.start, depth))
|
||||
|
||||
return max(depth, geom.stop)
|
||||
|
||||
|
||||
def _getPartEdge(edge, depths):
|
||||
dist = edge.getDistances()
|
||||
zBegin = _calculate_depth(dist[0], depths)
|
||||
zEnd = _calculate_depth(dist[1], depths)
|
||||
zEnd = _calculate_depth(dist[1], depths)
|
||||
return edge.toShape(zBegin, zEnd)
|
||||
|
||||
|
||||
class ObjectVcarve(PathEngraveBase.ObjectOp):
|
||||
'''Proxy class for Vcarve operation.'''
|
||||
"""Proxy class for Vcarve operation."""
|
||||
|
||||
def opFeatures(self, obj):
|
||||
'''opFeatures(obj) ... return all standard features and edges based geomtries'''
|
||||
return PathOp.FeatureTool | PathOp.FeatureHeights | PathOp.FeatureDepths | PathOp.FeatureBaseFaces | PathOp.FeatureCoolant
|
||||
"""opFeatures(obj) ... return all standard features and edges based geomtries"""
|
||||
return (
|
||||
PathOp.FeatureTool
|
||||
| PathOp.FeatureHeights
|
||||
| PathOp.FeatureDepths
|
||||
| PathOp.FeatureBaseFaces
|
||||
| PathOp.FeatureCoolant
|
||||
)
|
||||
|
||||
def setupAdditionalProperties(self, obj):
|
||||
if not hasattr(obj, 'BaseShapes'):
|
||||
obj.addProperty("App::PropertyLinkList", "BaseShapes", "Path",
|
||||
QtCore.QT_TRANSLATE_NOOP("PathVcarve",
|
||||
"Additional base objects to be engraved"))
|
||||
obj.setEditorMode('BaseShapes', 2) # hide
|
||||
if not hasattr(obj, "BaseShapes"):
|
||||
obj.addProperty(
|
||||
"App::PropertyLinkList",
|
||||
"BaseShapes",
|
||||
"Path",
|
||||
QtCore.QT_TRANSLATE_NOOP(
|
||||
"PathVcarve", "Additional base objects to be engraved"
|
||||
),
|
||||
)
|
||||
obj.setEditorMode("BaseShapes", 2) # hide
|
||||
|
||||
def initOperation(self, obj):
|
||||
'''initOperation(obj) ... create vcarve specific properties.'''
|
||||
obj.addProperty("App::PropertyFloat", "Discretize", "Path",
|
||||
QtCore.QT_TRANSLATE_NOOP("PathVcarve",
|
||||
"The deflection value for discretizing arcs"))
|
||||
obj.addProperty("App::PropertyFloat", "Colinear", "Path",
|
||||
QtCore.QT_TRANSLATE_NOOP("PathVcarve",
|
||||
"Cutoff for removing colinear segments (degrees). \
|
||||
default=10.0."))
|
||||
obj.addProperty("App::PropertyFloat", "Tolerance", "Path",
|
||||
QtCore.QT_TRANSLATE_NOOP("PathVcarve", ""))
|
||||
"""initOperation(obj) ... create vcarve specific properties."""
|
||||
obj.addProperty(
|
||||
"App::PropertyFloat",
|
||||
"Discretize",
|
||||
"Path",
|
||||
QtCore.QT_TRANSLATE_NOOP(
|
||||
"PathVcarve", "The deflection value for discretizing arcs"
|
||||
),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyFloat",
|
||||
"Colinear",
|
||||
"Path",
|
||||
QtCore.QT_TRANSLATE_NOOP(
|
||||
"PathVcarve",
|
||||
"Cutoff for removing colinear segments (degrees). \
|
||||
default=10.0.",
|
||||
),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyFloat",
|
||||
"Tolerance",
|
||||
"Path",
|
||||
QtCore.QT_TRANSLATE_NOOP("PathVcarve", ""),
|
||||
)
|
||||
obj.Colinear = 10.0
|
||||
obj.Discretize = 0.01
|
||||
obj.Tolerance = PathPreferences.defaultGeometryTolerance()
|
||||
@@ -237,27 +265,31 @@ class ObjectVcarve(PathEngraveBase.ObjectOp):
|
||||
return edges
|
||||
|
||||
def buildPathMedial(self, obj, faces):
|
||||
'''constructs a medial axis path using openvoronoi'''
|
||||
"""constructs a medial axis path using openvoronoi"""
|
||||
|
||||
def insert_many_wires(vd, wires):
|
||||
for wire in wires:
|
||||
PathLog.debug('discretize value: {}'.format(obj.Discretize))
|
||||
PathLog.debug("discretize value: {}".format(obj.Discretize))
|
||||
pts = wire.discretize(QuasiDeflection=obj.Discretize)
|
||||
ptv = [FreeCAD.Vector(p.x, p.y) for p in pts]
|
||||
ptv.append(ptv[0])
|
||||
|
||||
for i in range(len(pts)):
|
||||
vd.addSegment(ptv[i], ptv[i+1])
|
||||
vd.addSegment(ptv[i], ptv[i + 1])
|
||||
|
||||
def cutWire(edges):
|
||||
path = []
|
||||
path.append(Path.Command("G0 Z{}".format(obj.SafeHeight.Value)))
|
||||
e = edges[0]
|
||||
p = e.valueAt(e.FirstParameter)
|
||||
path.append(Path.Command("G0 X{} Y{} Z{}".format(p.x, p.y, obj.SafeHeight.Value)))
|
||||
path.append(
|
||||
Path.Command("G0 X{} Y{} Z{}".format(p.x, p.y, obj.SafeHeight.Value))
|
||||
)
|
||||
hSpeed = obj.ToolController.HorizFeed.Value
|
||||
vSpeed = obj.ToolController.VertFeed.Value
|
||||
path.append(Path.Command("G1 X{} Y{} Z{} F{}".format(p.x, p.y, p.z, vSpeed)))
|
||||
path.append(
|
||||
Path.Command("G1 X{} Y{} Z{} F{}".format(p.x, p.y, p.z, vSpeed))
|
||||
)
|
||||
for e in edges:
|
||||
path.extend(PathGeom.cmdsForEdge(e, hSpeed=hSpeed, vSpeed=vSpeed))
|
||||
|
||||
@@ -274,19 +306,22 @@ class ObjectVcarve(PathEngraveBase.ObjectOp):
|
||||
for e in vd.Edges:
|
||||
e.Color = PRIMARY if e.isPrimary() else SECONDARY
|
||||
vd.colorExterior(EXTERIOR1)
|
||||
vd.colorExterior(EXTERIOR2,
|
||||
lambda v: not f.isInside(v.toPoint(f.BoundBox.ZMin),
|
||||
obj.Tolerance, True))
|
||||
vd.colorExterior(
|
||||
EXTERIOR2,
|
||||
lambda v: not f.isInside(
|
||||
v.toPoint(f.BoundBox.ZMin), obj.Tolerance, True
|
||||
),
|
||||
)
|
||||
vd.colorColinear(COLINEAR, obj.Colinear)
|
||||
vd.colorTwins(TWIN)
|
||||
|
||||
wires = _collectVoronoiWires(vd)
|
||||
if _sorting != 'global':
|
||||
if _sorting != "global":
|
||||
wires = _sortVoronoiWires(wires)
|
||||
voronoiWires.extend(wires)
|
||||
VD.append((f, vd, wires))
|
||||
|
||||
if _sorting == 'global':
|
||||
if _sorting == "global":
|
||||
voronoiWires = _sortVoronoiWires(voronoiWires)
|
||||
|
||||
geom = _Geometry.FromObj(obj, self.model[0])
|
||||
@@ -301,14 +336,23 @@ class ObjectVcarve(PathEngraveBase.ObjectOp):
|
||||
self.commandlist = pathlist
|
||||
|
||||
def opExecute(self, obj):
|
||||
'''opExecute(obj) ... process engraving operation'''
|
||||
"""opExecute(obj) ... process engraving operation"""
|
||||
PathLog.track()
|
||||
|
||||
if not hasattr(obj.ToolController.Tool, "CuttingEdgeAngle"):
|
||||
PathLog.error(translate("Path_Vcarve", "VCarve requires an engraving cutter with CuttingEdgeAngle"))
|
||||
PathLog.error(
|
||||
translate(
|
||||
"Path_Vcarve",
|
||||
"VCarve requires an engraving cutter with CuttingEdgeAngle",
|
||||
)
|
||||
)
|
||||
|
||||
if obj.ToolController.Tool.CuttingEdgeAngle >= 180.0:
|
||||
PathLog.error(translate("Path_Vcarve", "Engraver Cutting Edge Angle must be < 180 degrees."))
|
||||
PathLog.error(
|
||||
translate(
|
||||
"Path_Vcarve", "Engraver Cutting Edge Angle must be < 180 degrees."
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
try:
|
||||
@@ -325,27 +369,39 @@ class ObjectVcarve(PathEngraveBase.ObjectOp):
|
||||
|
||||
if not faces:
|
||||
for model in self.model:
|
||||
if model.isDerivedFrom('Sketcher::SketchObject') or model.isDerivedFrom('Part::Part2DObject'):
|
||||
if model.isDerivedFrom(
|
||||
"Sketcher::SketchObject"
|
||||
) or model.isDerivedFrom("Part::Part2DObject"):
|
||||
faces.extend(model.Shape.Faces)
|
||||
|
||||
if faces:
|
||||
self.buildPathMedial(obj, faces)
|
||||
else:
|
||||
PathLog.error(translate('PathVcarve', 'The Job Base Object has no engraveable element. Engraving operation will produce no output.'))
|
||||
PathLog.error(
|
||||
translate(
|
||||
"PathVcarve",
|
||||
"The Job Base Object has no engraveable element. Engraving operation will produce no output.",
|
||||
)
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
#PathLog.error(e)
|
||||
#traceback.print_exc()
|
||||
PathLog.error(translate('PathVcarve', 'Error processing Base object. Engraving operation will produce no output.'))
|
||||
#raise e
|
||||
# PathLog.error(e)
|
||||
# traceback.print_exc()
|
||||
PathLog.error(
|
||||
translate(
|
||||
"PathVcarve",
|
||||
"Error processing Base object. Engraving operation will produce no output.",
|
||||
)
|
||||
)
|
||||
# raise e
|
||||
|
||||
def opUpdateDepths(self, obj, ignoreErrors=False):
|
||||
'''updateDepths(obj) ... engraving is always done at the top most z-value'''
|
||||
"""updateDepths(obj) ... engraving is always done at the top most z-value"""
|
||||
job = PathUtils.findParentJob(obj)
|
||||
self.opSetDefaultValues(obj, job)
|
||||
|
||||
def opSetDefaultValues(self, obj, job):
|
||||
'''opSetDefaultValues(obj) ... set depths for vcarving'''
|
||||
"""opSetDefaultValues(obj) ... set depths for vcarving"""
|
||||
if PathOp.FeatureDepths & self.opFeatures(obj):
|
||||
if job and len(job.Model.Group) > 0:
|
||||
bb = job.Proxy.modelBoundBox(job)
|
||||
@@ -355,15 +411,20 @@ class ObjectVcarve(PathEngraveBase.ObjectOp):
|
||||
obj.OpFinalDepth = -0.1
|
||||
|
||||
def isToolSupported(self, obj, tool):
|
||||
'''isToolSupported(obj, tool) ... returns True if v-carve op can work with tool.'''
|
||||
return hasattr(tool, 'Diameter') and hasattr(tool, 'CuttingEdgeAngle') and hasattr(tool, 'TipDiameter')
|
||||
"""isToolSupported(obj, tool) ... returns True if v-carve op can work with tool."""
|
||||
return (
|
||||
hasattr(tool, "Diameter")
|
||||
and hasattr(tool, "CuttingEdgeAngle")
|
||||
and hasattr(tool, "TipDiameter")
|
||||
)
|
||||
|
||||
|
||||
def SetupProperties():
|
||||
return ["Discretize"]
|
||||
|
||||
|
||||
def Create(name, obj=None, parentJob=None):
|
||||
'''Create(name) ... Creates and returns a Vcarve operation.'''
|
||||
"""Create(name) ... Creates and returns a Vcarve operation."""
|
||||
if obj is None:
|
||||
obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name)
|
||||
obj.Proxy = ObjectVcarve(obj, name, parentJob)
|
||||
|
||||
@@ -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.PathVcarve as PathVcarve
|
||||
import PathScripts.PathLog as PathLog
|
||||
import PathScripts.PathOpGui as PathOpGui
|
||||
@@ -47,7 +47,7 @@ def translate(context, text, disambig=None):
|
||||
|
||||
|
||||
class TaskPanelBaseGeometryPage(PathOpGui.TaskPanelBaseGeometryPage):
|
||||
'''Enhanced base geometry page to also allow special base objects.'''
|
||||
"""Enhanced base geometry page to also allow special base objects."""
|
||||
|
||||
def super(self):
|
||||
return super(TaskPanelBaseGeometryPage, self)
|
||||
@@ -60,16 +60,25 @@ class TaskPanelBaseGeometryPage(PathOpGui.TaskPanelBaseGeometryPage):
|
||||
job = PathUtils.findParentJob(self.obj)
|
||||
base = job.Proxy.resourceClone(job, sel.Object)
|
||||
if not base:
|
||||
PathLog.notice((translate("Path", "%s is not a Base Model object of the job %s") + "\n") % (sel.Object.Label, job.Label))
|
||||
PathLog.notice(
|
||||
(
|
||||
translate("Path", "%s is not a Base Model object of the job %s")
|
||||
+ "\n"
|
||||
)
|
||||
% (sel.Object.Label, job.Label)
|
||||
)
|
||||
continue
|
||||
if base in shapes:
|
||||
PathLog.notice((translate("Path", "Base shape %s already in the list") + "\n") % (sel.Object.Label))
|
||||
PathLog.notice(
|
||||
(translate("Path", "Base shape %s already in the list") + "\n")
|
||||
% (sel.Object.Label)
|
||||
)
|
||||
continue
|
||||
if base.isDerivedFrom('Part::Part2DObject'):
|
||||
if base.isDerivedFrom("Part::Part2DObject"):
|
||||
if sel.HasSubObjects:
|
||||
# selectively add some elements of the drawing to the Base
|
||||
for sub in sel.SubElementNames:
|
||||
if 'Vertex' in sub:
|
||||
if "Vertex" in sub:
|
||||
PathLog.info(translate("Path", "Ignoring vertex"))
|
||||
else:
|
||||
self.obj.Proxy.addBase(self.obj, base, sub)
|
||||
@@ -107,20 +116,22 @@ class TaskPanelBaseGeometryPage(PathOpGui.TaskPanelBaseGeometryPage):
|
||||
sub = item.data(self.super().DataObjectSub)
|
||||
if not sub:
|
||||
shapes.append(obj)
|
||||
PathLog.debug("Setting new base shapes: %s -> %s" % (self.obj.BaseShapes, shapes))
|
||||
PathLog.debug(
|
||||
"Setting new base shapes: %s -> %s" % (self.obj.BaseShapes, shapes)
|
||||
)
|
||||
self.obj.BaseShapes = shapes
|
||||
return self.super().updateBase()
|
||||
|
||||
|
||||
class TaskPanelOpPage(PathOpGui.TaskPanelPage):
|
||||
'''Page controller class for the Vcarve operation.'''
|
||||
"""Page controller class for the Vcarve operation."""
|
||||
|
||||
def getForm(self):
|
||||
'''getForm() ... returns UI'''
|
||||
"""getForm() ... returns UI"""
|
||||
return FreeCADGui.PySideUic.loadUi(":/panels/PageOpVcarveEdit.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"""
|
||||
if obj.Discretize != self.form.discretize.value():
|
||||
obj.Discretize = self.form.discretize.value()
|
||||
if obj.Colinear != self.form.colinearFilter.value():
|
||||
@@ -129,14 +140,14 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
|
||||
self.updateCoolant(obj, self.form.coolantController)
|
||||
|
||||
def setFields(self, obj):
|
||||
'''setFields(obj) ... transfers obj's property values to UI'''
|
||||
"""setFields(obj) ... transfers obj's property values to UI"""
|
||||
self.form.discretize.setValue(obj.Discretize)
|
||||
self.form.colinearFilter.setValue(obj.Colinear)
|
||||
self.setupToolController(obj, self.form.toolController)
|
||||
self.setupCoolant(obj, self.form.coolantController)
|
||||
|
||||
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.discretize.editingFinished)
|
||||
signals.append(self.form.colinearFilter.editingFinished)
|
||||
@@ -145,16 +156,18 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
|
||||
return signals
|
||||
|
||||
def taskPanelBaseGeometryPage(self, obj, features):
|
||||
'''taskPanelBaseGeometryPage(obj, features) ... return page for adding base geometries.'''
|
||||
"""taskPanelBaseGeometryPage(obj, features) ... return page for adding base geometries."""
|
||||
return TaskPanelBaseGeometryPage(obj, features)
|
||||
|
||||
|
||||
Command = PathOpGui.SetupOperation('Vcarve',
|
||||
PathVcarve.Create,
|
||||
TaskPanelOpPage,
|
||||
'Path_Vcarve',
|
||||
QtCore.QT_TRANSLATE_NOOP("Path_Vcarve", "Vcarve"),
|
||||
QtCore.QT_TRANSLATE_NOOP("Path_Vcarve", "Creates a medial line engraving path"),
|
||||
PathVcarve.SetupProperties)
|
||||
Command = PathOpGui.SetupOperation(
|
||||
"Vcarve",
|
||||
PathVcarve.Create,
|
||||
TaskPanelOpPage,
|
||||
"Path_Vcarve",
|
||||
QtCore.QT_TRANSLATE_NOOP("Path_Vcarve", "Vcarve"),
|
||||
QtCore.QT_TRANSLATE_NOOP("Path_Vcarve", "Creates a medial line engraving path"),
|
||||
PathVcarve.SetupProperties,
|
||||
)
|
||||
|
||||
FreeCAD.Console.PrintLog("Loading PathVcarveGui... done\n")
|
||||
|
||||
Reference in New Issue
Block a user