Merge pull request #5421 from sliptonic/bug/translationEngrave
[PATH] translation cleanup (engrave, millface, pocket)
This commit is contained in:
@@ -27,23 +27,21 @@ import PathScripts.PathLog as PathLog
|
||||
import PathScripts.PathOp as PathOp
|
||||
import PathScripts.PathUtils as PathUtils
|
||||
|
||||
from PySide import QtCore
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
|
||||
__doc__ = "Class and implementation of Path Engrave operation"
|
||||
|
||||
if False:
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
|
||||
PathLog.trackModule(PathLog.thisModule())
|
||||
else:
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
|
||||
# lazily loaded modules
|
||||
from lazy_loader.lazy_loader import LazyLoader
|
||||
|
||||
Part = LazyLoader("Part", globals(), "Part")
|
||||
|
||||
__doc__ = "Class and implementation of Path Engrave operation"
|
||||
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
# PathLog.trackModule(PathLog.thisModule())
|
||||
|
||||
|
||||
# Qt translation handling
|
||||
def translate(context, text, disambig=None):
|
||||
return QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
|
||||
|
||||
class ObjectEngrave(PathEngraveBase.ObjectOp):
|
||||
"""Proxy class for Engrave operation."""
|
||||
@@ -69,8 +67,8 @@ class ObjectEngrave(PathEngraveBase.ObjectOp):
|
||||
"App::PropertyLinkList",
|
||||
"BaseShapes",
|
||||
"Path",
|
||||
QtCore.QT_TRANSLATE_NOOP(
|
||||
"PathEngrave", "Additional base objects to be engraved"
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property", "Additional base objects to be engraved"
|
||||
),
|
||||
)
|
||||
obj.setEditorMode("BaseShapes", 2) # hide
|
||||
@@ -79,8 +77,8 @@ class ObjectEngrave(PathEngraveBase.ObjectOp):
|
||||
"App::PropertyLink",
|
||||
"BaseObject",
|
||||
"Path",
|
||||
QtCore.QT_TRANSLATE_NOOP(
|
||||
"PathEngrave", "Additional base objects to be engraved"
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property", "Additional base objects to be engraved"
|
||||
),
|
||||
)
|
||||
obj.setEditorMode("BaseObject", 2) # hide
|
||||
@@ -91,8 +89,8 @@ class ObjectEngrave(PathEngraveBase.ObjectOp):
|
||||
"App::PropertyInteger",
|
||||
"StartVertex",
|
||||
"Path",
|
||||
QtCore.QT_TRANSLATE_NOOP(
|
||||
"PathEngrave", "The vertex index to start the path from"
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property", "The vertex index to start the path from"
|
||||
),
|
||||
)
|
||||
self.setupAdditionalProperties(obj)
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
from lazy_loader.lazy_loader import LazyLoader
|
||||
import Path
|
||||
import PathScripts.PathGeom as PathGeom
|
||||
import PathScripts.PathLog as PathLog
|
||||
@@ -27,24 +28,21 @@ import PathScripts.PathOp as PathOp
|
||||
import PathScripts.PathOpTools as PathOpTools
|
||||
import copy
|
||||
|
||||
# lazily loaded modules
|
||||
from lazy_loader.lazy_loader import LazyLoader
|
||||
DraftGeomUtils = LazyLoader('DraftGeomUtils', globals(), 'DraftGeomUtils')
|
||||
Part = LazyLoader('Part', globals(), 'Part')
|
||||
|
||||
from PySide import QtCore
|
||||
|
||||
__doc__ = "Base class for all ops in the engrave family."
|
||||
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
#PathLog.trackModule(PathLog.thisModule())
|
||||
# lazily loaded modules
|
||||
DraftGeomUtils = LazyLoader("DraftGeomUtils", globals(), "DraftGeomUtils")
|
||||
Part = LazyLoader("Part", globals(), "Part")
|
||||
|
||||
if False:
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
|
||||
PathLog.trackModule(PathLog.thisModule())
|
||||
else:
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
|
||||
# Qt translation handling
|
||||
def translate(context, text, disambig=None):
|
||||
return QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
|
||||
class ObjectOp(PathOp.ObjectOp):
|
||||
'''Proxy base class for engrave operations.'''
|
||||
"""Proxy base class for engrave operations."""
|
||||
|
||||
def getZValues(self, obj):
|
||||
zValues = []
|
||||
@@ -62,47 +60,79 @@ class ObjectOp(PathOp.ObjectOp):
|
||||
return zValues
|
||||
|
||||
def buildpathocc(self, obj, wires, zValues, relZ=False, forward=True, start_idx=0):
|
||||
'''buildpathocc(obj, wires, zValues, relZ=False) ... internal helper function to generate engraving commands.'''
|
||||
"""buildpathocc(obj, wires, zValues, relZ=False) ... internal helper function to generate engraving commands."""
|
||||
PathLog.track(obj.Label, len(wires), zValues)
|
||||
|
||||
for wire in wires:
|
||||
offset = wire
|
||||
|
||||
# reorder the wire
|
||||
if hasattr(obj, 'StartVertex'):
|
||||
if hasattr(obj, "StartVertex"):
|
||||
start_idx = obj.StartVertex
|
||||
|
||||
edges = copy.copy(PathOpTools.orientWire(offset, forward).Edges)
|
||||
edges = Part.sortEdges(edges)[0];
|
||||
edges = Part.sortEdges(edges)[0]
|
||||
|
||||
last = None
|
||||
|
||||
for z in zValues:
|
||||
PathLog.debug(z)
|
||||
if last:
|
||||
self.appendCommand(Path.Command('G1', {'X': last.x, 'Y': last.y, 'Z': last.z}), z, relZ, self.vertFeed)
|
||||
self.appendCommand(
|
||||
Path.Command("G1", {"X": last.x, "Y": last.y, "Z": last.z}),
|
||||
z,
|
||||
relZ,
|
||||
self.vertFeed,
|
||||
)
|
||||
|
||||
first = True
|
||||
if start_idx > len(edges)-1:
|
||||
start_idx = len(edges)-1
|
||||
if start_idx > len(edges) - 1:
|
||||
start_idx = len(edges) - 1
|
||||
|
||||
edges = edges[start_idx:] + edges[:start_idx]
|
||||
for edge in edges:
|
||||
PathLog.debug("points: {} -> {}".format(edge.Vertexes[0].Point, edge.Vertexes[-1].Point))
|
||||
PathLog.debug("valueat {} -> {}".format(edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter)))
|
||||
PathLog.debug(
|
||||
"points: {} -> {}".format(
|
||||
edge.Vertexes[0].Point, edge.Vertexes[-1].Point
|
||||
)
|
||||
)
|
||||
PathLog.debug(
|
||||
"valueat {} -> {}".format(
|
||||
edge.valueAt(edge.FirstParameter),
|
||||
edge.valueAt(edge.LastParameter),
|
||||
)
|
||||
)
|
||||
if first and (not last or not wire.isClosed()):
|
||||
PathLog.debug('processing first edge entry')
|
||||
PathLog.debug("processing first edge entry")
|
||||
# we set the first move to our first point
|
||||
last = edge.Vertexes[0].Point
|
||||
|
||||
self.commandlist.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid}))
|
||||
self.commandlist.append(Path.Command('G0', {'X': last.x, 'Y': last.y, 'F': self.horizRapid}))
|
||||
self.commandlist.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid}))
|
||||
self.appendCommand(Path.Command('G1', {'X': last.x, 'Y': last.y, 'Z': last.z}), z, relZ, self.vertFeed)
|
||||
self.commandlist.append(
|
||||
Path.Command(
|
||||
"G0",
|
||||
{"Z": obj.ClearanceHeight.Value, "F": self.vertRapid},
|
||||
)
|
||||
)
|
||||
self.commandlist.append(
|
||||
Path.Command(
|
||||
"G0", {"X": last.x, "Y": last.y, "F": self.horizRapid}
|
||||
)
|
||||
)
|
||||
self.commandlist.append(
|
||||
Path.Command(
|
||||
"G0", {"Z": obj.SafeHeight.Value, "F": self.vertRapid}
|
||||
)
|
||||
)
|
||||
self.appendCommand(
|
||||
Path.Command("G1", {"X": last.x, "Y": last.y, "Z": last.z}),
|
||||
z,
|
||||
relZ,
|
||||
self.vertFeed,
|
||||
)
|
||||
first = False
|
||||
|
||||
if PathGeom.pointsCoincide(last, edge.valueAt(edge.FirstParameter)):
|
||||
#if PathGeom.pointsCoincide(last, edge.Vertexes[0].Point):
|
||||
# if PathGeom.pointsCoincide(last, edge.Vertexes[0].Point):
|
||||
for cmd in PathGeom.cmdsForEdge(edge):
|
||||
self.appendCommand(cmd, z, relZ, self.horizFeed)
|
||||
last = edge.Vertexes[-1].Point
|
||||
@@ -110,17 +140,21 @@ class ObjectOp(PathOp.ObjectOp):
|
||||
for cmd in PathGeom.cmdsForEdge(edge, True):
|
||||
self.appendCommand(cmd, z, relZ, self.horizFeed)
|
||||
last = edge.Vertexes[0].Point
|
||||
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}
|
||||
)
|
||||
)
|
||||
|
||||
def appendCommand(self, cmd, z, relZ, feed):
|
||||
params = cmd.Parameters
|
||||
if relZ:
|
||||
z = params['Z'] - z
|
||||
params.update({'Z': z, 'F': feed})
|
||||
z = params["Z"] - z
|
||||
params.update({"Z": z, "F": feed})
|
||||
self.commandlist.append(Path.Command(cmd.Name, params))
|
||||
|
||||
def opSetDefaultValues(self, obj, job):
|
||||
'''opSetDefaultValues(obj) ... set depths for engraving'''
|
||||
"""opSetDefaultValues(obj) ... set depths for engraving"""
|
||||
if PathOp.FeatureDepths & self.opFeatures(obj):
|
||||
if job and len(job.Model.Group) > 0:
|
||||
bb = job.Proxy.modelBoundBox(job)
|
||||
@@ -128,4 +162,3 @@ class ObjectOp(PathOp.ObjectOp):
|
||||
obj.OpFinalDepth = bb.ZMax - max(obj.StepDown.Value, 0.1)
|
||||
else:
|
||||
obj.OpFinalDepth = -0.1
|
||||
|
||||
|
||||
@@ -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.PathEngrave as PathEngrave
|
||||
import PathScripts.PathLog as PathLog
|
||||
import PathScripts.PathOpGui as PathOpGui
|
||||
@@ -30,26 +30,35 @@ import PathScripts.PathUtils as PathUtils
|
||||
|
||||
from PySide import QtCore, QtGui
|
||||
|
||||
|
||||
__title__ = "Path Engrave Operation UI"
|
||||
__author__ = "sliptonic (Brad Collette)"
|
||||
__url__ = "https://www.freecadweb.org"
|
||||
__doc__ = "Engrave operation page controller and command implementation."
|
||||
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
#PathLog.trackModule(PathLog.thisModule())
|
||||
if False:
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
|
||||
PathLog.trackModule(PathLog.thisModule())
|
||||
else:
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
|
||||
|
||||
translate = FreeCAD.Qt.translate
|
||||
|
||||
def translate(context, text, disambig=None):
|
||||
return QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
|
||||
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)
|
||||
|
||||
def selectionSupportedAsBaseGeometry(self, selection, ignoreErrors):
|
||||
# allow selection of an entire 2D object, which is generally not the case
|
||||
if len(selection) == 1 and not selection[0].HasSubObjects and selection[0].Object.isDerivedFrom('Part::Part2DObject'):
|
||||
if (
|
||||
len(selection) == 1
|
||||
and not selection[0].HasSubObjects
|
||||
and selection[0].Object.isDerivedFrom("Part::Part2DObject")
|
||||
):
|
||||
return True
|
||||
# Let general logic handle all other cases.
|
||||
return self.super().selectionSupportedAsBaseGeometry(selection, ignoreErrors)
|
||||
@@ -61,22 +70,31 @@ 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:
|
||||
PathLog.info(translate("Path", "Ignoring vertex"))
|
||||
if "Vertex" in sub:
|
||||
PathLog.info("Ignoring vertex")
|
||||
else:
|
||||
self.obj.Proxy.addBase(self.obj, base, sub)
|
||||
else:
|
||||
# when adding an entire shape to BaseShapes we can take its sub shapes out of Base
|
||||
self.obj.Base = [(p,el) for p,el in self.obj.Base if p != base]
|
||||
self.obj.Base = [(p, el) for p, el in self.obj.Base if p != base]
|
||||
shapes.append(base)
|
||||
self.obj.BaseShapes = shapes
|
||||
added = True
|
||||
@@ -106,32 +124,35 @@ 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 Engrave operation.'''
|
||||
"""Page controller class for the Engrave operation."""
|
||||
|
||||
def getForm(self):
|
||||
'''getForm() ... returns UI'''
|
||||
"""getForm() ... returns UI"""
|
||||
return FreeCADGui.PySideUic.loadUi(":/panels/PageOpEngraveEdit.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.StartVertex != self.form.startVertex.value():
|
||||
obj.StartVertex = self.form.startVertex.value()
|
||||
self.updateToolController(obj, self.form.toolController)
|
||||
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.startVertex.setValue(obj.StartVertex)
|
||||
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.startVertex.editingFinished)
|
||||
signals.append(self.form.toolController.currentIndexChanged)
|
||||
@@ -139,15 +160,20 @@ 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('Engrave',
|
||||
PathEngrave.Create,
|
||||
TaskPanelOpPage,
|
||||
'Path_Engrave',
|
||||
QtCore.QT_TRANSLATE_NOOP("PathEngrave", "Engrave"),
|
||||
QtCore.QT_TRANSLATE_NOOP("PathEngrave", "Creates an Engraving Path around a Draft ShapeString"),
|
||||
PathEngrave.SetupProperties)
|
||||
|
||||
Command = PathOpGui.SetupOperation(
|
||||
"Engrave",
|
||||
PathEngrave.Create,
|
||||
TaskPanelOpPage,
|
||||
"Path_Engrave",
|
||||
QtCore.QT_TRANSLATE_NOOP("Path_Engrave", "Engrave"),
|
||||
QtCore.QT_TRANSLATE_NOOP(
|
||||
"Path_Engrave", "Creates an Engraving Path around a Draft ShapeString"
|
||||
),
|
||||
PathEngrave.SetupProperties,
|
||||
)
|
||||
|
||||
FreeCAD.Console.PrintLog("Loading PathEngraveGui... done\n")
|
||||
|
||||
@@ -26,13 +26,13 @@ import FreeCAD
|
||||
import PathScripts.PathLog as PathLog
|
||||
import PathScripts.PathPocketBase as PathPocketBase
|
||||
import PathScripts.PathUtils as PathUtils
|
||||
|
||||
from PySide import QtCore
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
import numpy
|
||||
|
||||
# lazily loaded modules
|
||||
from lazy_loader.lazy_loader import LazyLoader
|
||||
Part = LazyLoader('Part', globals(), 'Part')
|
||||
|
||||
Part = LazyLoader("Part", globals(), "Part")
|
||||
|
||||
__title__ = "Path Mill Face Operation"
|
||||
__author__ = "sliptonic (Brad Collette)"
|
||||
@@ -41,32 +41,87 @@ __doc__ = "Class and implementation of Mill Facing operation."
|
||||
__contributors__ = "russ4262 (Russell Johnson)"
|
||||
|
||||
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
# PathLog.trackModule()
|
||||
if False:
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
|
||||
PathLog.trackModule(PathLog.thisModule())
|
||||
else:
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
|
||||
|
||||
# Qt translation handling
|
||||
def translate(context, text, disambig=None):
|
||||
return QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
translate = FreeCAD.Qt.translate
|
||||
|
||||
|
||||
class ObjectFace(PathPocketBase.ObjectPocket):
|
||||
'''Proxy object for Mill Facing operation.'''
|
||||
"""Proxy object for Mill Facing operation."""
|
||||
|
||||
@classmethod
|
||||
def propertyEnumerations(self, dataType="data"):
|
||||
"""helixOpPropertyEnumerations(dataType="data")... return property enumeration lists of specified dataType.
|
||||
Args:
|
||||
dataType = 'data', 'raw', 'translated'
|
||||
Notes:
|
||||
'data' is list of internal string literals used in code
|
||||
'raw' is list of (translated_text, data_string) tuples
|
||||
'translated' is list of translated string literals
|
||||
"""
|
||||
|
||||
enums = {
|
||||
"BoundaryShape": [
|
||||
(translate("Path_Pocket", "Boundbox"), "Boundbox"),
|
||||
(translate("Path_Pocket", "Face Region"), "Face Region"),
|
||||
(translate("Path_Pocket", "Perimeter"), "Perimeter"),
|
||||
(translate("Path_Pocket", "Stock"), "Stock"),
|
||||
],
|
||||
}
|
||||
|
||||
if dataType == "raw":
|
||||
return enums
|
||||
|
||||
data = list()
|
||||
idx = 0 if dataType == "translated" else 1
|
||||
|
||||
PathLog.debug(enums)
|
||||
|
||||
for k, v in enumerate(enums):
|
||||
data.append((v, [tup[idx] for tup in enums[v]]))
|
||||
PathLog.debug(data)
|
||||
|
||||
return data
|
||||
|
||||
def initPocketOp(self, obj):
|
||||
'''initPocketOp(obj) ... create facing specific properties'''
|
||||
obj.addProperty("App::PropertyEnumeration", "BoundaryShape", "Face", QtCore.QT_TRANSLATE_NOOP("App::Property", "Shape to use for calculating Boundary"))
|
||||
obj.addProperty("App::PropertyBool", "ClearEdges", "Face", QtCore.QT_TRANSLATE_NOOP("App::Property", "Clear edges of surface (Only applicable to BoundBox)"))
|
||||
if not hasattr(obj, 'ExcludeRaisedAreas'):
|
||||
obj.addProperty("App::PropertyBool", "ExcludeRaisedAreas", "Face", QtCore.QT_TRANSLATE_NOOP("App::Property", "Exclude milling raised areas inside the face."))
|
||||
PathLog.track()
|
||||
"""initPocketOp(obj) ... create facing specific properties"""
|
||||
obj.addProperty(
|
||||
"App::PropertyEnumeration",
|
||||
"BoundaryShape",
|
||||
"Face",
|
||||
QT_TRANSLATE_NOOP("App::Property", "Shape to use for calculating Boundary"),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyBool",
|
||||
"ClearEdges",
|
||||
"Face",
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property", "Clear edges of surface (Only applicable to BoundBox)"
|
||||
),
|
||||
)
|
||||
if not hasattr(obj, "ExcludeRaisedAreas"):
|
||||
obj.addProperty(
|
||||
"App::PropertyBool",
|
||||
"ExcludeRaisedAreas",
|
||||
"Face",
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property", "Exclude milling raised areas inside the face."
|
||||
),
|
||||
)
|
||||
|
||||
obj.BoundaryShape = ['Boundbox', 'Face Region', 'Perimeter', 'Stock']
|
||||
for n in self.propertyEnumerations():
|
||||
setattr(obj, n[0], n[1])
|
||||
|
||||
def pocketInvertExtraOffset(self):
|
||||
return True
|
||||
|
||||
def areaOpOnChanged(self, obj, prop):
|
||||
'''areaOpOnChanged(obj, prop) ... facing specific depths calculation.'''
|
||||
"""areaOpOnChanged(obj, prop) ... facing specific depths calculation."""
|
||||
PathLog.track(prop)
|
||||
if prop == "StepOver" and obj.StepOver == 0:
|
||||
obj.StepOver = 1
|
||||
@@ -78,27 +133,27 @@ class ObjectFace(PathPocketBase.ObjectPocket):
|
||||
obj.OpStartDepth = job.Stock.Shape.BoundBox.ZMax
|
||||
|
||||
if len(obj.Base) >= 1:
|
||||
PathLog.debug('processing')
|
||||
PathLog.debug("processing")
|
||||
sublist = []
|
||||
for i in obj.Base:
|
||||
o = i[0]
|
||||
for s in i[1]:
|
||||
sublist.append(o.Shape.getElement(s))
|
||||
|
||||
# If the operation has a geometry identified the Finaldepth
|
||||
# is the top of the bboundbox which includes all features.
|
||||
# Otherwise, top of part.
|
||||
# If the operation has a geometry identified the Finaldepth
|
||||
# is the top of the bboundbox which includes all features.
|
||||
# Otherwise, top of part.
|
||||
|
||||
obj.OpFinalDepth = Part.makeCompound(sublist).BoundBox.ZMax
|
||||
elif job:
|
||||
obj.OpFinalDepth = job.Proxy.modelBoundBox(job).ZMax
|
||||
|
||||
def areaOpShapes(self, obj):
|
||||
'''areaOpShapes(obj) ... return top face'''
|
||||
"""areaOpShapes(obj) ... return top face"""
|
||||
# Facing is done either against base objects
|
||||
holeShape = None
|
||||
|
||||
PathLog.debug('depthparams: {}'.format([i for i in self.depthparams]))
|
||||
PathLog.debug("depthparams: {}".format([i for i in self.depthparams]))
|
||||
|
||||
if obj.Base:
|
||||
PathLog.debug("obj.Base: {}".format(obj.Base))
|
||||
@@ -120,7 +175,9 @@ class ObjectFace(PathPocketBase.ObjectPocket):
|
||||
# Limit to one model base per operation
|
||||
if oneBase[0] is not b[0]:
|
||||
oneBase[1] = False
|
||||
if numpy.isclose(abs(shape.normalAt(0, 0).z), 1): # horizontal face
|
||||
if numpy.isclose(
|
||||
abs(shape.normalAt(0, 0).z), 1
|
||||
): # horizontal face
|
||||
# Analyze internal closed wires to determine if raised or a recess
|
||||
for wire in shape.Wires[1:]:
|
||||
if obj.ExcludeRaisedAreas:
|
||||
@@ -130,12 +187,18 @@ class ObjectFace(PathPocketBase.ObjectPocket):
|
||||
else:
|
||||
holes.append((b[0].Shape, wire))
|
||||
else:
|
||||
PathLog.warning('The base subobject, "{0}," is not a face. Ignoring "{0}."'.format(sub))
|
||||
PathLog.warning(
|
||||
'The base subobject, "{0}," is not a face. Ignoring "{0}."'.format(
|
||||
sub
|
||||
)
|
||||
)
|
||||
|
||||
if obj.ExcludeRaisedAreas and len(holes) > 0:
|
||||
for shape, wire in holes:
|
||||
f = Part.makeFace(wire, 'Part::FaceMakerSimple')
|
||||
env = PathUtils.getEnvelope(shape, subshape=f, depthparams=self.depthparams)
|
||||
f = Part.makeFace(wire, "Part::FaceMakerSimple")
|
||||
env = PathUtils.getEnvelope(
|
||||
shape, subshape=f, depthparams=self.depthparams
|
||||
)
|
||||
holeEnvs.append(env)
|
||||
holeShape = Part.makeCompound(holeEnvs)
|
||||
|
||||
@@ -161,62 +224,98 @@ class ObjectFace(PathPocketBase.ObjectPocket):
|
||||
bb.XMax = bb.XMax + offset
|
||||
bb.YMax = bb.YMax + offset
|
||||
|
||||
if obj.BoundaryShape == 'Boundbox':
|
||||
bbperim = Part.makeBox(bb.XLength, bb.YLength, 1, FreeCAD.Vector(bb.XMin, bb.YMin, bb.ZMin), FreeCAD.Vector(0, 0, 1))
|
||||
if obj.BoundaryShape == "Boundbox":
|
||||
bbperim = Part.makeBox(
|
||||
bb.XLength,
|
||||
bb.YLength,
|
||||
1,
|
||||
FreeCAD.Vector(bb.XMin, bb.YMin, bb.ZMin),
|
||||
FreeCAD.Vector(0, 0, 1),
|
||||
)
|
||||
env = PathUtils.getEnvelope(partshape=bbperim, depthparams=self.depthparams)
|
||||
if obj.ExcludeRaisedAreas and oneBase[1]:
|
||||
includedFaces = self.getAllIncludedFaces(oneBase[0], env, faceZ=minHeight)
|
||||
includedFaces = self.getAllIncludedFaces(
|
||||
oneBase[0], env, faceZ=minHeight
|
||||
)
|
||||
if len(includedFaces) > 0:
|
||||
includedShape = Part.makeCompound(includedFaces)
|
||||
includedEnv = PathUtils.getEnvelope(oneBase[0].Shape, subshape=includedShape, depthparams=self.depthparams)
|
||||
includedEnv = PathUtils.getEnvelope(
|
||||
oneBase[0].Shape,
|
||||
subshape=includedShape,
|
||||
depthparams=self.depthparams,
|
||||
)
|
||||
env = env.cut(includedEnv)
|
||||
elif obj.BoundaryShape == 'Stock':
|
||||
elif obj.BoundaryShape == "Stock":
|
||||
stock = PathUtils.findParentJob(obj).Stock.Shape
|
||||
env = stock
|
||||
|
||||
if obj.ExcludeRaisedAreas and oneBase[1]:
|
||||
includedFaces = self.getAllIncludedFaces(oneBase[0], stock, faceZ=minHeight)
|
||||
includedFaces = self.getAllIncludedFaces(
|
||||
oneBase[0], stock, faceZ=minHeight
|
||||
)
|
||||
if len(includedFaces) > 0:
|
||||
stockEnv = PathUtils.getEnvelope(partshape=stock, depthparams=self.depthparams)
|
||||
stockEnv = PathUtils.getEnvelope(
|
||||
partshape=stock, depthparams=self.depthparams
|
||||
)
|
||||
includedShape = Part.makeCompound(includedFaces)
|
||||
includedEnv = PathUtils.getEnvelope(oneBase[0].Shape, subshape=includedShape, depthparams=self.depthparams)
|
||||
includedEnv = PathUtils.getEnvelope(
|
||||
oneBase[0].Shape,
|
||||
subshape=includedShape,
|
||||
depthparams=self.depthparams,
|
||||
)
|
||||
env = stockEnv.cut(includedEnv)
|
||||
elif obj.BoundaryShape == 'Perimeter':
|
||||
elif obj.BoundaryShape == "Perimeter":
|
||||
if obj.ClearEdges:
|
||||
psZMin = planeshape.BoundBox.ZMin
|
||||
ofstShape = PathUtils.getOffsetArea(planeshape,
|
||||
self.radius * 1.25,
|
||||
plane=planeshape)
|
||||
ofstShape.translate(FreeCAD.Vector(0.0, 0.0, psZMin - ofstShape.BoundBox.ZMin))
|
||||
env = PathUtils.getEnvelope(partshape=ofstShape, depthparams=self.depthparams)
|
||||
ofstShape = PathUtils.getOffsetArea(
|
||||
planeshape, self.radius * 1.25, plane=planeshape
|
||||
)
|
||||
ofstShape.translate(
|
||||
FreeCAD.Vector(0.0, 0.0, psZMin - ofstShape.BoundBox.ZMin)
|
||||
)
|
||||
env = PathUtils.getEnvelope(
|
||||
partshape=ofstShape, depthparams=self.depthparams
|
||||
)
|
||||
else:
|
||||
env = PathUtils.getEnvelope(partshape=planeshape, depthparams=self.depthparams)
|
||||
elif obj.BoundaryShape == 'Face Region':
|
||||
env = PathUtils.getEnvelope(
|
||||
partshape=planeshape, depthparams=self.depthparams
|
||||
)
|
||||
elif obj.BoundaryShape == "Face Region":
|
||||
baseShape = oneBase[0].Shape
|
||||
psZMin = planeshape.BoundBox.ZMin
|
||||
ofst = 0.0
|
||||
if obj.ClearEdges:
|
||||
ofst = self.tool.Diameter * 0.51
|
||||
ofstShape = PathUtils.getOffsetArea(planeshape, ofst, plane=planeshape)
|
||||
ofstShape.translate(FreeCAD.Vector(0.0, 0.0, psZMin - ofstShape.BoundBox.ZMin))
|
||||
ofstShape.translate(
|
||||
FreeCAD.Vector(0.0, 0.0, psZMin - ofstShape.BoundBox.ZMin)
|
||||
)
|
||||
|
||||
# Calculate custom depth params for removal shape envelope, with start and final depth buffers
|
||||
custDepthparams = self._customDepthParams(obj, obj.StartDepth.Value + 0.2, obj.FinalDepth.Value - 0.1) # only an envelope
|
||||
ofstShapeEnv = PathUtils.getEnvelope(partshape=ofstShape, depthparams=custDepthparams)
|
||||
if obj.ExcludeRaisedAreas:
|
||||
custDepthparams = self._customDepthParams(
|
||||
obj, obj.StartDepth.Value + 0.2, obj.FinalDepth.Value - 0.1
|
||||
) # only an envelope
|
||||
ofstShapeEnv = PathUtils.getEnvelope(
|
||||
partshape=ofstShape, depthparams=custDepthparams
|
||||
)
|
||||
if obj.ExcludeRaisedAreas:
|
||||
env = ofstShapeEnv.cut(baseShape)
|
||||
env.translate(FreeCAD.Vector(0.0, 0.0, -0.00001)) # lower removal shape into buffer zone
|
||||
env.translate(
|
||||
FreeCAD.Vector(0.0, 0.0, -0.00001)
|
||||
) # lower removal shape into buffer zone
|
||||
else:
|
||||
env = ofstShapeEnv
|
||||
|
||||
if holeShape:
|
||||
PathLog.debug("Processing holes and face ...")
|
||||
holeEnv = PathUtils.getEnvelope(partshape=holeShape, depthparams=self.depthparams)
|
||||
holeEnv = PathUtils.getEnvelope(
|
||||
partshape=holeShape, depthparams=self.depthparams
|
||||
)
|
||||
newEnv = env.cut(holeEnv)
|
||||
tup = newEnv, False, 'pathMillFace'
|
||||
tup = newEnv, False, "pathMillFace"
|
||||
else:
|
||||
PathLog.debug("Processing solid face ...")
|
||||
tup = env, False, 'pathMillFace'
|
||||
tup = env, False, "pathMillFace"
|
||||
|
||||
self.removalshapes.append(tup)
|
||||
obj.removalshape = self.removalshapes[0][0] # save removal shape
|
||||
@@ -224,7 +323,7 @@ class ObjectFace(PathPocketBase.ObjectPocket):
|
||||
return self.removalshapes
|
||||
|
||||
def areaOpSetDefaultValues(self, obj, job):
|
||||
'''areaOpSetDefaultValues(obj, job) ... initialize mill facing properties'''
|
||||
"""areaOpSetDefaultValues(obj, job) ... initialize mill facing properties"""
|
||||
obj.StepOver = 50
|
||||
obj.ZigZagAngle = 45.0
|
||||
obj.ExcludeRaisedAreas = False
|
||||
@@ -260,8 +359,8 @@ class ObjectFace(PathPocketBase.ObjectPocket):
|
||||
return False
|
||||
|
||||
def getAllIncludedFaces(self, base, env, faceZ):
|
||||
'''getAllIncludedFaces(base, env, faceZ)...
|
||||
Return all `base` faces extending above `faceZ` whose boundboxes overlap with the `env` boundbox.'''
|
||||
"""getAllIncludedFaces(base, env, faceZ)...
|
||||
Return all `base` faces extending above `faceZ` whose boundboxes overlap with the `env` boundbox."""
|
||||
included = []
|
||||
|
||||
eXMin = env.BoundBox.XMin
|
||||
@@ -305,7 +404,7 @@ def SetupProperties():
|
||||
|
||||
|
||||
def Create(name, obj=None, parentJob=None):
|
||||
'''Create(name) ... Creates and returns a Mill Facing operation.'''
|
||||
"""Create(name) ... Creates and returns a Mill Facing operation."""
|
||||
if obj is None:
|
||||
obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name)
|
||||
obj.Proxy = ObjectFace(obj, name, parentJob)
|
||||
|
||||
@@ -20,32 +20,79 @@
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
import FreeCAD
|
||||
import PathScripts.PathLog as PathLog
|
||||
import PathScripts.PathMillFace as PathMillFace
|
||||
import PathScripts.PathOpGui as PathOpGui
|
||||
import PathScripts.PathPocketBaseGui as PathPocketBaseGui
|
||||
|
||||
from PySide import QtCore
|
||||
import PathScripts.PathPocketShape as PathPocketShape
|
||||
import FreeCADGui
|
||||
|
||||
__title__ = "Path Face Mill Operation UI"
|
||||
__author__ = "sliptonic (Brad Collette)"
|
||||
__url__ = "https://www.freecadweb.org"
|
||||
__doc__ = "Face Mill operation page controller and command implementation."
|
||||
|
||||
if False:
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
|
||||
PathLog.trackModule(PathLog.thisModule())
|
||||
else:
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
|
||||
|
||||
class TaskPanelOpPage(PathPocketBaseGui.TaskPanelOpPage):
|
||||
'''Page controller class for the face milling operation.'''
|
||||
"""Page controller class for the face milling operation."""
|
||||
|
||||
def getForm(self):
|
||||
PathLog.track()
|
||||
"""getForm() ... return UI"""
|
||||
|
||||
form = FreeCADGui.PySideUic.loadUi(":/panels/PageOpPocketFullEdit.ui")
|
||||
comboToPropertyMap = [
|
||||
("cutMode", "CutMode"),
|
||||
("offsetPattern", "OffsetPattern"),
|
||||
("boundaryShape", "BoundaryShape"),
|
||||
]
|
||||
|
||||
enumTups = PathMillFace.ObjectFace.propertyEnumerations(dataType="raw")
|
||||
enumTups.update(
|
||||
PathPocketShape.ObjectPocket.pocketPropertyEnumerations(dataType="raw")
|
||||
)
|
||||
|
||||
self.populateCombobox(form, enumTups, comboToPropertyMap)
|
||||
return form
|
||||
|
||||
def populateCombobox(self, form, enumTups, comboBoxesPropertyMap):
|
||||
"""fillComboboxes(form, comboBoxesPropertyMap) ... populate comboboxes with translated enumerations
|
||||
** comboBoxesPropertyMap will be unnecessary if UI files use strict combobox naming protocol.
|
||||
Args:
|
||||
form = UI form
|
||||
enumTups = list of (translated_text, data_string) tuples
|
||||
comboBoxesPropertyMap = list of (translated_text, data_string) tuples
|
||||
"""
|
||||
# Load appropriate enumerations in each combobox
|
||||
for cb, prop in comboBoxesPropertyMap:
|
||||
box = getattr(form, cb) # Get the combobox
|
||||
box.clear() # clear the combobox
|
||||
for text, data in enumTups[prop]: # load enumerations
|
||||
box.addItem(text, data)
|
||||
|
||||
def pocketFeatures(self):
|
||||
'''pocketFeatures() ... return FeatureFacing (see PathPocketBaseGui)'''
|
||||
"""pocketFeatures() ... return FeatureFacing (see PathPocketBaseGui)"""
|
||||
return PathPocketBaseGui.FeatureFacing
|
||||
|
||||
Command = PathOpGui.SetupOperation('MillFace',
|
||||
PathMillFace.Create,
|
||||
TaskPanelOpPage,
|
||||
'Path_Face',
|
||||
QtCore.QT_TRANSLATE_NOOP("Path_Face", "Face"),
|
||||
QtCore.QT_TRANSLATE_NOOP("Path_Face", "Create a Facing Operation from a model or face"),
|
||||
PathMillFace.SetupProperties)
|
||||
|
||||
Command = PathOpGui.SetupOperation(
|
||||
"MillFace",
|
||||
PathMillFace.Create,
|
||||
TaskPanelOpPage,
|
||||
"Path_Face",
|
||||
QT_TRANSLATE_NOOP("Path_MillFace", "Face"),
|
||||
QT_TRANSLATE_NOOP(
|
||||
"Path_MillFace", "Create a Facing Operation from a model or face"
|
||||
),
|
||||
PathMillFace.SetupProperties,
|
||||
)
|
||||
|
||||
FreeCAD.Console.PrintLog("Loading PathMillFaceGui... done\n")
|
||||
|
||||
|
||||
@@ -20,17 +20,17 @@
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
import FreeCAD
|
||||
import PathScripts.PathLog as PathLog
|
||||
import PathScripts.PathOp as PathOp
|
||||
import PathScripts.PathPocketBase as PathPocketBase
|
||||
import PathScripts.PathUtils as PathUtils
|
||||
|
||||
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__ = "Path 3D Pocket Operation"
|
||||
__author__ = "Yorik van Havre <yorik@uncreated.net>"
|
||||
@@ -41,42 +41,110 @@ __created__ = "2014"
|
||||
__scriptVersion__ = "2e"
|
||||
__lastModified__ = "2020-02-13 17:22 CST"
|
||||
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
# PathLog.trackModule(PathLog.thisModule())
|
||||
if False:
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
|
||||
PathLog.trackModule(PathLog.thisModule())
|
||||
else:
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
|
||||
|
||||
# Qt translation handling
|
||||
def translate(context, text, disambig=None):
|
||||
return QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
translate = FreeCAD.Qt.translate
|
||||
|
||||
|
||||
class ObjectPocket(PathPocketBase.ObjectPocket):
|
||||
'''Proxy object for Pocket operation.'''
|
||||
"""Proxy object for Pocket operation."""
|
||||
|
||||
def pocketOpFeatures(self, obj):
|
||||
return PathOp.FeatureNoFinalDepth
|
||||
|
||||
def initPocketOp(self, obj):
|
||||
'''initPocketOp(obj) ... setup receiver'''
|
||||
if not hasattr(obj, 'HandleMultipleFeatures'):
|
||||
obj.addProperty('App::PropertyEnumeration', 'HandleMultipleFeatures', 'Pocket', QtCore.QT_TRANSLATE_NOOP('PathPocket', 'Choose how to process multiple Base Geometry features.'))
|
||||
obj.HandleMultipleFeatures = ['Collectively', 'Individually']
|
||||
if not hasattr(obj, 'AdaptivePocketStart'):
|
||||
obj.addProperty('App::PropertyBool', 'AdaptivePocketStart', 'Pocket', QtCore.QT_TRANSLATE_NOOP('App::Property', 'Use adaptive algorithm to eliminate excessive air milling above planar pocket top.'))
|
||||
if not hasattr(obj, 'AdaptivePocketFinish'):
|
||||
obj.addProperty('App::PropertyBool', 'AdaptivePocketFinish', 'Pocket', QtCore.QT_TRANSLATE_NOOP('App::Property', 'Use adaptive algorithm to eliminate excessive air milling below planar pocket bottom.'))
|
||||
if not hasattr(obj, 'ProcessStockArea'):
|
||||
obj.addProperty('App::PropertyBool', 'ProcessStockArea', 'Pocket', QtCore.QT_TRANSLATE_NOOP('App::Property', 'Process the model and stock in an operation with no Base Geometry selected.'))
|
||||
"""initPocketOp(obj) ... setup receiver"""
|
||||
if not hasattr(obj, "HandleMultipleFeatures"):
|
||||
obj.addProperty(
|
||||
"App::PropertyEnumeration",
|
||||
"HandleMultipleFeatures",
|
||||
"Pocket",
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property",
|
||||
"Choose how to process multiple Base Geometry features.",
|
||||
),
|
||||
)
|
||||
if not hasattr(obj, "AdaptivePocketStart"):
|
||||
obj.addProperty(
|
||||
"App::PropertyBool",
|
||||
"AdaptivePocketStart",
|
||||
"Pocket",
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property",
|
||||
"Use adaptive algorithm to eliminate excessive air milling above planar pocket top.",
|
||||
),
|
||||
)
|
||||
if not hasattr(obj, "AdaptivePocketFinish"):
|
||||
obj.addProperty(
|
||||
"App::PropertyBool",
|
||||
"AdaptivePocketFinish",
|
||||
"Pocket",
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property",
|
||||
"Use adaptive algorithm to eliminate excessive air milling below planar pocket bottom.",
|
||||
),
|
||||
)
|
||||
if not hasattr(obj, "ProcessStockArea"):
|
||||
obj.addProperty(
|
||||
"App::PropertyBool",
|
||||
"ProcessStockArea",
|
||||
"Pocket",
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property",
|
||||
"Process the model and stock in an operation with no Base Geometry selected.",
|
||||
),
|
||||
)
|
||||
|
||||
# populate the property enumerations
|
||||
for n in self.propertyEnumerations():
|
||||
setattr(obj, n[0], n[1])
|
||||
|
||||
@classmethod
|
||||
def propertyEnumerations(self, dataType="data"):
|
||||
"""propertyEnumerations(dataType="data")... return property enumeration lists of specified dataType.
|
||||
Args:
|
||||
dataType = 'data', 'raw', 'translated'
|
||||
Notes:
|
||||
'data' is list of internal string literals used in code
|
||||
'raw' is list of (translated_text, data_string) tuples
|
||||
'translated' is list of translated string literals
|
||||
"""
|
||||
|
||||
enums = {
|
||||
"HandleMultipleFeatures": [
|
||||
(translate("Path_Pocket", "Collectively"), "Collectively"),
|
||||
(translate("Path_Pocket", "Individually"), "Individually"),
|
||||
],
|
||||
}
|
||||
|
||||
if dataType == "raw":
|
||||
return enums
|
||||
|
||||
data = list()
|
||||
idx = 0 if dataType == "translated" else 1
|
||||
|
||||
PathLog.debug(enums)
|
||||
|
||||
for k, v in enumerate(enums):
|
||||
data.append((v, [tup[idx] for tup in enums[v]]))
|
||||
PathLog.debug(data)
|
||||
|
||||
return data
|
||||
|
||||
def opOnDocumentRestored(self, obj):
|
||||
'''opOnDocumentRestored(obj) ... adds the properties if they doesn't exist.'''
|
||||
"""opOnDocumentRestored(obj) ... adds the properties if they doesn't exist."""
|
||||
self.initPocketOp(obj)
|
||||
|
||||
def pocketInvertExtraOffset(self):
|
||||
return False
|
||||
|
||||
def areaOpShapes(self, obj):
|
||||
'''areaOpShapes(obj) ... return shapes representing the solids to be removed.'''
|
||||
"""areaOpShapes(obj) ... return shapes representing the solids to be removed."""
|
||||
PathLog.track()
|
||||
|
||||
subObjTups = []
|
||||
@@ -102,34 +170,51 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
|
||||
if len(Faces) == 0:
|
||||
allSubsFaceType = False
|
||||
|
||||
if allSubsFaceType is True and obj.HandleMultipleFeatures == 'Collectively':
|
||||
if (
|
||||
allSubsFaceType is True
|
||||
and obj.HandleMultipleFeatures == "Collectively"
|
||||
):
|
||||
(fzmin, fzmax) = self.getMinMaxOfFaces(Faces)
|
||||
if obj.FinalDepth.Value < fzmin:
|
||||
PathLog.warning(translate('PathPocket', 'Final depth set below ZMin of face(s) selected.'))
|
||||
PathLog.warning(
|
||||
translate(
|
||||
"PathPocket",
|
||||
"Final depth set below ZMin of face(s) selected.",
|
||||
)
|
||||
)
|
||||
|
||||
if obj.AdaptivePocketStart is True or obj.AdaptivePocketFinish is True:
|
||||
if (
|
||||
obj.AdaptivePocketStart is True
|
||||
or obj.AdaptivePocketFinish is True
|
||||
):
|
||||
pocketTup = self.calculateAdaptivePocket(obj, base, subObjTups)
|
||||
if pocketTup is not False:
|
||||
obj.removalshape = pocketTup[0]
|
||||
removalshapes.append(pocketTup) # (shape, isHole, detail)
|
||||
else:
|
||||
shape = Part.makeCompound(Faces)
|
||||
env = PathUtils.getEnvelope(base[0].Shape, subshape=shape, depthparams=self.depthparams)
|
||||
env = PathUtils.getEnvelope(
|
||||
base[0].Shape, subshape=shape, depthparams=self.depthparams
|
||||
)
|
||||
obj.removalshape = env.cut(base[0].Shape)
|
||||
# obj.removalshape.tessellate(0.1)
|
||||
removalshapes.append((obj.removalshape, False, '3DPocket')) # (shape, isHole, detail)
|
||||
removalshapes.append(
|
||||
(obj.removalshape, False, "3DPocket")
|
||||
) # (shape, isHole, detail)
|
||||
else:
|
||||
for sub in base[1]:
|
||||
if "Face" in sub:
|
||||
shape = Part.makeCompound([getattr(base[0].Shape, sub)])
|
||||
else:
|
||||
edges = [getattr(base[0].Shape, sub) for sub in base[1]]
|
||||
shape = Part.makeFace(edges, 'Part::FaceMakerSimple')
|
||||
shape = Part.makeFace(edges, "Part::FaceMakerSimple")
|
||||
|
||||
env = PathUtils.getEnvelope(base[0].Shape, subshape=shape, depthparams=self.depthparams)
|
||||
env = PathUtils.getEnvelope(
|
||||
base[0].Shape, subshape=shape, depthparams=self.depthparams
|
||||
)
|
||||
obj.removalshape = env.cut(base[0].Shape)
|
||||
# obj.removalshape.tessellate(0.1)
|
||||
removalshapes.append((obj.removalshape, False, '3DPocket'))
|
||||
removalshapes.append((obj.removalshape, False, "3DPocket"))
|
||||
|
||||
else: # process the job base object as a whole
|
||||
PathLog.debug("processing the whole job base object")
|
||||
@@ -137,36 +222,40 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
|
||||
if obj.ProcessStockArea is True:
|
||||
job = PathUtils.findParentJob(obj)
|
||||
|
||||
stockEnvShape = PathUtils.getEnvelope(job.Stock.Shape, subshape=None, depthparams=self.depthparams)
|
||||
stockEnvShape = PathUtils.getEnvelope(
|
||||
job.Stock.Shape, subshape=None, depthparams=self.depthparams
|
||||
)
|
||||
|
||||
obj.removalshape = stockEnvShape.cut(base.Shape)
|
||||
# obj.removalshape.tessellate(0.1)
|
||||
else:
|
||||
env = PathUtils.getEnvelope(base.Shape, subshape=None, depthparams=self.depthparams)
|
||||
env = PathUtils.getEnvelope(
|
||||
base.Shape, subshape=None, depthparams=self.depthparams
|
||||
)
|
||||
obj.removalshape = env.cut(base.Shape)
|
||||
# obj.removalshape.tessellate(0.1)
|
||||
|
||||
removalshapes.append((obj.removalshape, False, '3DPocket'))
|
||||
removalshapes.append((obj.removalshape, False, "3DPocket"))
|
||||
|
||||
return removalshapes
|
||||
|
||||
def areaOpSetDefaultValues(self, obj, job):
|
||||
'''areaOpSetDefaultValues(obj, job) ... set default values'''
|
||||
"""areaOpSetDefaultValues(obj, job) ... set default values"""
|
||||
obj.StepOver = 100
|
||||
obj.ZigZagAngle = 45
|
||||
obj.HandleMultipleFeatures = 'Collectively'
|
||||
obj.HandleMultipleFeatures = "Collectively"
|
||||
obj.AdaptivePocketStart = False
|
||||
obj.AdaptivePocketFinish = False
|
||||
obj.ProcessStockArea = False
|
||||
|
||||
# methods for eliminating air milling with some pockets: adpative start and finish
|
||||
def calculateAdaptivePocket(self, obj, base, subObjTups):
|
||||
'''calculateAdaptivePocket(obj, base, subObjTups)
|
||||
"""calculateAdaptivePocket(obj, base, subObjTups)
|
||||
Orient multiple faces around common facial center of mass.
|
||||
Identify edges that are connections for adjacent faces.
|
||||
Attempt to separate unconnected edges into top and bottom loops of the pocket.
|
||||
Trim the top and bottom of the pocket if available and requested.
|
||||
return: tuple with pocket shape information'''
|
||||
return: tuple with pocket shape information"""
|
||||
low = []
|
||||
high = []
|
||||
removeList = []
|
||||
@@ -203,23 +292,27 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
|
||||
highFaceShape = Part.Face(Part.Wire(Part.__sortEdges__(allEdges)))
|
||||
except Exception as ee:
|
||||
PathLog.warning(ee)
|
||||
PathLog.error(translate("Path", "A planar adaptive start is unavailable. The non-planar will be attempted."))
|
||||
PathLog.error(
|
||||
"A planar adaptive start is unavailable. The non-planar will be attempted."
|
||||
)
|
||||
tryNonPlanar = True
|
||||
else:
|
||||
makeHighFace = 1
|
||||
|
||||
if tryNonPlanar is True:
|
||||
try:
|
||||
highFaceShape = Part.makeFilledFace(Part.__sortEdges__(allEdges)) # NON-planar face method
|
||||
highFaceShape = Part.makeFilledFace(
|
||||
Part.__sortEdges__(allEdges)
|
||||
) # NON-planar face method
|
||||
except Exception as eee:
|
||||
PathLog.warning(eee)
|
||||
PathLog.error(translate("Path", "The non-planar adaptive start is also unavailable.") + "(1)")
|
||||
PathLog.error("The non-planar adaptive start is also unavailable.")
|
||||
isHighFacePlanar = False
|
||||
else:
|
||||
makeHighFace = 2
|
||||
|
||||
if makeHighFace > 0:
|
||||
FreeCAD.ActiveDocument.addObject('Part::Feature', 'topEdgeFace')
|
||||
FreeCAD.ActiveDocument.addObject("Part::Feature", "topEdgeFace")
|
||||
highFace = FreeCAD.ActiveDocument.ActiveObject
|
||||
highFace.Shape = highFaceShape
|
||||
removeList.append(highFace.Name)
|
||||
@@ -228,9 +321,17 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
|
||||
if makeHighFace == 2:
|
||||
mx = hzmax + obj.StepDown.Value
|
||||
mn = hzmin - obj.StepDown.Value
|
||||
if highFace.Shape.BoundBox.ZMax > mx or highFace.Shape.BoundBox.ZMin < mn:
|
||||
PathLog.warning("ZMaxDiff: {}; ZMinDiff: {}".format(highFace.Shape.BoundBox.ZMax - mx, highFace.Shape.BoundBox.ZMin - mn))
|
||||
PathLog.error(translate("Path", "The non-planar adaptive start is also unavailable.") + "(2)")
|
||||
if (
|
||||
highFace.Shape.BoundBox.ZMax > mx
|
||||
or highFace.Shape.BoundBox.ZMin < mn
|
||||
):
|
||||
PathLog.warning(
|
||||
"ZMaxDiff: {}; ZMinDiff: {}".format(
|
||||
highFace.Shape.BoundBox.ZMax - mx,
|
||||
highFace.Shape.BoundBox.ZMin - mn,
|
||||
)
|
||||
)
|
||||
PathLog.error("The non-planar adaptive start is also unavailable.")
|
||||
isHighFacePlanar = False
|
||||
makeHighFace = 0
|
||||
else:
|
||||
@@ -252,7 +353,7 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
|
||||
PathLog.error("An adaptive finish is unavailable.")
|
||||
isLowFacePlanar = False
|
||||
else:
|
||||
FreeCAD.ActiveDocument.addObject('Part::Feature', 'bottomEdgeFace')
|
||||
FreeCAD.ActiveDocument.addObject("Part::Feature", "bottomEdgeFace")
|
||||
lowFace = FreeCAD.ActiveDocument.ActiveObject
|
||||
lowFace.Shape = lowFaceShape
|
||||
removeList.append(lowFace.Name)
|
||||
@@ -279,9 +380,12 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
|
||||
step_down=obj.StepDown.Value,
|
||||
z_finish_step=finish_step,
|
||||
final_depth=finDep,
|
||||
user_depths=None)
|
||||
user_depths=None,
|
||||
)
|
||||
shape = Part.makeCompound(Faces)
|
||||
env = PathUtils.getEnvelope(base[0].Shape, subshape=shape, depthparams=depthparams)
|
||||
env = PathUtils.getEnvelope(
|
||||
base[0].Shape, subshape=shape, depthparams=depthparams
|
||||
)
|
||||
cuts.append(env.cut(base[0].Shape))
|
||||
|
||||
# Might need to change to .cut(job.Stock.Shape) if pocket has no bottom
|
||||
@@ -305,8 +409,11 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
|
||||
step_down=obj.StepDown.Value,
|
||||
z_finish_step=finish_step,
|
||||
final_depth=finDep1,
|
||||
user_depths=None)
|
||||
envTop = PathUtils.getEnvelope(base[0].Shape, subshape=highFace.Shape, depthparams=depthparams1)
|
||||
user_depths=None,
|
||||
)
|
||||
envTop = PathUtils.getEnvelope(
|
||||
base[0].Shape, subshape=highFace.Shape, depthparams=depthparams1
|
||||
)
|
||||
cbi = len(cuts) - 1
|
||||
cuts.append(cuts[cbi].cut(envTop))
|
||||
|
||||
@@ -326,18 +433,20 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
|
||||
step_down=obj.StepDown.Value,
|
||||
z_finish_step=finish_step,
|
||||
final_depth=finDep2,
|
||||
user_depths=None)
|
||||
envBottom = PathUtils.getEnvelope(base[0].Shape, subshape=lowFace.Shape, depthparams=depthparams2)
|
||||
user_depths=None,
|
||||
)
|
||||
envBottom = PathUtils.getEnvelope(
|
||||
base[0].Shape, subshape=lowFace.Shape, depthparams=depthparams2
|
||||
)
|
||||
cbi = len(cuts) - 1
|
||||
cuts.append(cuts[cbi].cut(envBottom))
|
||||
|
||||
# package pocket details into tuple
|
||||
sdi = len(starts) - 1
|
||||
fdi = len(finals) - 1
|
||||
cbi = len(cuts) - 1
|
||||
pocket = (cuts[cbi], False, '3DPocket')
|
||||
pocket = (cuts[cbi], False, "3DPocket")
|
||||
if FreeCAD.GuiUp:
|
||||
import FreeCADGui
|
||||
|
||||
for rn in removeList:
|
||||
FreeCADGui.ActiveDocument.getObject(rn).Visibility = False
|
||||
|
||||
@@ -347,11 +456,12 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
|
||||
return pocket
|
||||
|
||||
def orderFacesAroundCenterOfMass(self, subObjTups):
|
||||
'''orderFacesAroundCenterOfMass(subObjTups)
|
||||
"""orderFacesAroundCenterOfMass(subObjTups)
|
||||
Order list of faces by center of mass in angular order around
|
||||
average center of mass for all faces. Positive X-axis is zero degrees.
|
||||
return: subObjTups [ordered/sorted]'''
|
||||
return: subObjTups [ordered/sorted]"""
|
||||
import math
|
||||
|
||||
newList = []
|
||||
vectList = []
|
||||
comList = []
|
||||
@@ -364,7 +474,7 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
|
||||
return vectItem[3]
|
||||
|
||||
def getFaceIdx(sub):
|
||||
return int(sub.replace('Face', '')) - 1
|
||||
return int(sub.replace("Face", "")) - 1
|
||||
|
||||
# get CenterOfMass for each face and add to sumCenterOfMass for average calculation
|
||||
for (sub, face) in subObjTups:
|
||||
@@ -382,22 +492,26 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
|
||||
|
||||
# calculate vector (mag, direct) for each face from avgCom
|
||||
for (sub, face, com) in comList:
|
||||
adjCom = com.sub(avgCom) # effectively treats avgCom as origin for each face.
|
||||
mag = math.sqrt(adjCom.x**2 + adjCom.y**2) # adjCom.Length without Z values
|
||||
adjCom = com.sub(
|
||||
avgCom
|
||||
) # effectively treats avgCom as origin for each face.
|
||||
mag = math.sqrt(
|
||||
adjCom.x ** 2 + adjCom.y ** 2
|
||||
) # adjCom.Length without Z values
|
||||
drctn = 0.0
|
||||
# Determine direction of vector
|
||||
if adjCom.x > 0.0:
|
||||
if adjCom.y > 0.0: # Q1
|
||||
drctn = math.degrees(math.atan(adjCom.y/adjCom.x))
|
||||
drctn = math.degrees(math.atan(adjCom.y / adjCom.x))
|
||||
elif adjCom.y < 0.0:
|
||||
drctn = -math.degrees(math.atan(adjCom.x/adjCom.y)) + 270.0
|
||||
drctn = -math.degrees(math.atan(adjCom.x / adjCom.y)) + 270.0
|
||||
elif adjCom.y == 0.0:
|
||||
drctn = 0.0
|
||||
elif adjCom.x < 0.0:
|
||||
if adjCom.y < 0.0:
|
||||
drctn = math.degrees(math.atan(adjCom.y/adjCom.x)) + 180.0
|
||||
drctn = math.degrees(math.atan(adjCom.y / adjCom.x)) + 180.0
|
||||
elif adjCom.y > 0.0:
|
||||
drctn = -math.degrees(math.atan(adjCom.x/adjCom.y)) + 90.0
|
||||
drctn = -math.degrees(math.atan(adjCom.x / adjCom.y)) + 90.0
|
||||
elif adjCom.y == 0.0:
|
||||
drctn = 180.0
|
||||
elif adjCom.x == 0.0:
|
||||
@@ -434,8 +548,8 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
|
||||
return newList
|
||||
|
||||
def findSharedEdges(self, subObjTups):
|
||||
'''findSharedEdges(self, subObjTups)
|
||||
Find connected edges given a group of faces'''
|
||||
"""findSharedEdges(self, subObjTups)
|
||||
Find connected edges given a group of faces"""
|
||||
checkoutList = []
|
||||
searchedList = []
|
||||
shared = []
|
||||
@@ -471,7 +585,11 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
|
||||
for ei2 in range(0, len(face2.Edges)):
|
||||
edg2 = face2.Edges[ei2]
|
||||
if edg1.isSame(edg2) is True:
|
||||
PathLog.debug("{}.Edges[{}] connects at {}.Edges[{}]".format(sub1, ei1, sub2, ei2))
|
||||
PathLog.debug(
|
||||
"{}.Edges[{}] connects at {}.Edges[{}]".format(
|
||||
sub1, ei1, sub2, ei2
|
||||
)
|
||||
)
|
||||
shared.append((sub1, face1, ei1))
|
||||
touching[sub1].append(ei1)
|
||||
touching[sub2].append(ei2)
|
||||
@@ -486,8 +604,8 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
|
||||
return (shared, touchingCleaned)
|
||||
|
||||
def identifyUnconnectedEdges(self, subObjTups, touching):
|
||||
'''identifyUnconnectedEdges(subObjTups, touching)
|
||||
Categorize unconnected edges into two groups, if possible: low and high'''
|
||||
"""identifyUnconnectedEdges(subObjTups, touching)
|
||||
Categorize unconnected edges into two groups, if possible: low and high"""
|
||||
# Identify unconnected edges
|
||||
# (should be top edge loop if all faces form loop with bottom face(s) included)
|
||||
high = []
|
||||
@@ -518,7 +636,7 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
|
||||
com = FreeCAD.Vector(0, 0, 0)
|
||||
com.add(edg0.CenterOfMass)
|
||||
com.add(edg1.CenterOfMass)
|
||||
avgCom = FreeCAD.Vector(com.x/2.0, com.y/2.0, com.z/2.0)
|
||||
avgCom = FreeCAD.Vector(com.x / 2.0, com.y / 2.0, com.z / 2.0)
|
||||
if avgCom.z > face.CenterOfMass.z:
|
||||
high.extend(holding)
|
||||
else:
|
||||
@@ -534,9 +652,9 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
|
||||
return (low, high)
|
||||
|
||||
def hasCommonVertex(self, edge1, edge2, show=False):
|
||||
'''findCommonVertexIndexes(edge1, edge2, show=False)
|
||||
"""findCommonVertexIndexes(edge1, edge2, show=False)
|
||||
Compare vertexes of two edges to identify a common vertex.
|
||||
Returns the vertex index of edge1 to which edge2 is connected'''
|
||||
Returns the vertex index of edge1 to which edge2 is connected"""
|
||||
if show is True:
|
||||
PathLog.info("New findCommonVertex()... ")
|
||||
|
||||
@@ -561,9 +679,9 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
|
||||
return -1
|
||||
|
||||
def groupConnectedEdges(self, holding):
|
||||
'''groupConnectedEdges(self, holding)
|
||||
"""groupConnectedEdges(self, holding)
|
||||
Take edges and determine which are connected.
|
||||
Group connected chains/loops into: low and high'''
|
||||
Group connected chains/loops into: low and high"""
|
||||
holds = []
|
||||
grps = []
|
||||
searched = []
|
||||
@@ -607,7 +725,7 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
|
||||
|
||||
while len(holds) > 0:
|
||||
if loops > 500:
|
||||
PathLog.error('BREAK --- LOOPS LIMIT of 500 ---')
|
||||
PathLog.error("BREAK --- LOOPS LIMIT of 500 ---")
|
||||
break
|
||||
save = False
|
||||
|
||||
@@ -701,8 +819,8 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
|
||||
return (low, high)
|
||||
|
||||
def getMinMaxOfFaces(self, Faces):
|
||||
'''getMinMaxOfFaces(Faces)
|
||||
return the zmin and zmax values for given set of faces or edges.'''
|
||||
"""getMinMaxOfFaces(Faces)
|
||||
return the zmin and zmax values for given set of faces or edges."""
|
||||
zmin = Faces[0].BoundBox.ZMax
|
||||
zmax = Faces[0].BoundBox.ZMin
|
||||
for f in Faces:
|
||||
@@ -718,7 +836,7 @@ def SetupProperties():
|
||||
|
||||
|
||||
def Create(name, obj=None, parentJob=None):
|
||||
'''Create(name) ... Creates and returns a Pocket operation.'''
|
||||
"""Create(name) ... Creates and returns a Pocket operation."""
|
||||
if obj is None:
|
||||
obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name)
|
||||
obj.Proxy = ObjectPocket(obj, name, parentJob)
|
||||
|
||||
@@ -21,65 +21,164 @@
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
import FreeCAD
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
import PathScripts.PathAreaOp as PathAreaOp
|
||||
import PathScripts.PathLog as PathLog
|
||||
import PathScripts.PathOp as PathOp
|
||||
|
||||
from PySide import QtCore
|
||||
|
||||
__title__ = "Base Path Pocket Operation"
|
||||
__author__ = "sliptonic (Brad Collette)"
|
||||
__url__ = "https://www.freecadweb.org"
|
||||
__doc__ = "Base class and implementation for Path pocket operations."
|
||||
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
# PathLog.trackModule(PathLog.thisModule())
|
||||
if False:
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
|
||||
PathLog.trackModule(PathLog.thisModule())
|
||||
else:
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
|
||||
|
||||
# Qt translation handling
|
||||
def translate(context, text, disambig=None):
|
||||
return QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
translate = FreeCAD.Qt.translate
|
||||
|
||||
|
||||
class ObjectPocket(PathAreaOp.ObjectOp):
|
||||
'''Base class for proxy objects of all pocket operations.'''
|
||||
"""Base class for proxy objects of all pocket operations."""
|
||||
|
||||
@classmethod
|
||||
def pocketPropertyEnumerations(self, dataType="data"):
|
||||
"""helixOpPropertyEnumerations(dataType="data")... return property enumeration lists of specified dataType.
|
||||
Args:
|
||||
dataType = 'data', 'raw', 'translated'
|
||||
Notes:
|
||||
'data' is list of internal string literals used in code
|
||||
'raw' is list of (translated_text, data_string) tuples
|
||||
'translated' is list of translated string literals
|
||||
"""
|
||||
|
||||
enums = {
|
||||
"CutMode": [
|
||||
(translate("Path_Pocket", "Climb"), "Climb"),
|
||||
(translate("Path_Pocket", "Conventional"), "Conventional"),
|
||||
], # this is the direction that the profile runs
|
||||
"StartAt": [
|
||||
(translate("Path_Pocket", "Center"), "Center"),
|
||||
(translate("Path_Pocket", "Edge"), "Edge"),
|
||||
],
|
||||
"OffsetPattern": [
|
||||
(translate("Path_Pocket", "ZigZag"), "ZigZag"),
|
||||
(translate("Path_Pocket", "Offset"), "Offset"),
|
||||
(translate("Path_Pocket", "Spiral"), "Spiral"),
|
||||
(translate("Path_Pocket", "ZigZagOffset"), "ZigZagOffset"),
|
||||
(translate("Path_Pocket", "Line"), "Line"),
|
||||
(translate("Path_Pocket", "Grid"), "Grid"),
|
||||
(translate("Path_Pocket", "Triangle"), "Triangle"),
|
||||
], # Fill Pattern
|
||||
}
|
||||
|
||||
if dataType == "raw":
|
||||
return enums
|
||||
|
||||
data = list()
|
||||
idx = 0 if dataType == "translated" else 1
|
||||
|
||||
PathLog.debug(enums)
|
||||
|
||||
for k, v in enumerate(enums):
|
||||
data.append((v, [tup[idx] for tup in enums[v]]))
|
||||
PathLog.debug(data)
|
||||
|
||||
return data
|
||||
|
||||
def areaOpFeatures(self, obj):
|
||||
'''areaOpFeatures(obj) ... Pockets have a FinishDepth and work on Faces'''
|
||||
return PathOp.FeatureBaseFaces | PathOp.FeatureFinishDepth | self.pocketOpFeatures(obj)
|
||||
"""areaOpFeatures(obj) ... Pockets have a FinishDepth and work on Faces"""
|
||||
return (
|
||||
PathOp.FeatureBaseFaces
|
||||
| PathOp.FeatureFinishDepth
|
||||
| self.pocketOpFeatures(obj)
|
||||
)
|
||||
|
||||
def pocketOpFeatures(self, obj):
|
||||
# pylint: disable=unused-argument
|
||||
return 0
|
||||
|
||||
def initPocketOp(self, obj):
|
||||
'''initPocketOp(obj) ... overwrite to initialize subclass.
|
||||
Can safely be overwritten by subclass.'''
|
||||
pass # pylint: disable=unnecessary-pass
|
||||
"""initPocketOp(obj) ... overwrite to initialize subclass.
|
||||
Can safely be overwritten by subclass."""
|
||||
pass # pylint: disable=unnecessary-pass
|
||||
|
||||
def pocketInvertExtraOffset(self):
|
||||
'''pocketInvertExtraOffset() ... return True if ExtraOffset's direction is inward.
|
||||
Can safely be overwritten by subclass.'''
|
||||
"""pocketInvertExtraOffset() ... return True if ExtraOffset's direction is inward.
|
||||
Can safely be overwritten by subclass."""
|
||||
return False
|
||||
|
||||
def initAreaOp(self, obj):
|
||||
'''initAreaOp(obj) ... create pocket specific properties.
|
||||
Do not overwrite, implement initPocketOp(obj) instead.'''
|
||||
"""initAreaOp(obj) ... create pocket specific properties.
|
||||
Do not overwrite, implement initPocketOp(obj) instead."""
|
||||
PathLog.track()
|
||||
|
||||
# Pocket Properties
|
||||
obj.addProperty("App::PropertyEnumeration", "CutMode", "Pocket", QtCore.QT_TRANSLATE_NOOP("App::Property", "The direction that the toolpath should go around the part ClockWise (CW) or CounterClockWise (CCW)"))
|
||||
obj.addProperty("App::PropertyDistance", "ExtraOffset", "Pocket", QtCore.QT_TRANSLATE_NOOP("App::Property", "Extra offset to apply to the operation. Direction is operation dependent."))
|
||||
obj.addProperty("App::PropertyEnumeration", "StartAt", "Pocket", QtCore.QT_TRANSLATE_NOOP("App::Property", "Start pocketing at center or boundary"))
|
||||
obj.addProperty("App::PropertyPercent", "StepOver", "Pocket", QtCore.QT_TRANSLATE_NOOP("App::Property", "Percent of cutter diameter to step over on each pass"))
|
||||
obj.addProperty("App::PropertyFloat", "ZigZagAngle", "Pocket", QtCore.QT_TRANSLATE_NOOP("App::Property", "Angle of the zigzag pattern"))
|
||||
obj.addProperty("App::PropertyEnumeration", "OffsetPattern", "Face", QtCore.QT_TRANSLATE_NOOP("App::Property", "Clearing pattern to use"))
|
||||
obj.addProperty("App::PropertyBool", "MinTravel", "Pocket", QtCore.QT_TRANSLATE_NOOP("App::Property", "Use 3D Sorting of Path"))
|
||||
obj.addProperty("App::PropertyBool", "KeepToolDown", "Pocket", QtCore.QT_TRANSLATE_NOOP("App::Property", "Attempts to avoid unnecessary retractions."))
|
||||
obj.addProperty(
|
||||
"App::PropertyEnumeration",
|
||||
"CutMode",
|
||||
"Pocket",
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property",
|
||||
"The direction that the toolpath should go around the part ClockWise (CW) or CounterClockWise (CCW)",
|
||||
),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyDistance",
|
||||
"ExtraOffset",
|
||||
"Pocket",
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property",
|
||||
"Extra offset to apply to the operation. Direction is operation dependent.",
|
||||
),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyEnumeration",
|
||||
"StartAt",
|
||||
"Pocket",
|
||||
QT_TRANSLATE_NOOP("App::Property", "Start pocketing at center or boundary"),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyPercent",
|
||||
"StepOver",
|
||||
"Pocket",
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property", "Percent of cutter diameter to step over on each pass"
|
||||
),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyFloat",
|
||||
"ZigZagAngle",
|
||||
"Pocket",
|
||||
QT_TRANSLATE_NOOP("App::Property", "Angle of the zigzag pattern"),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyEnumeration",
|
||||
"OffsetPattern",
|
||||
"Face",
|
||||
QT_TRANSLATE_NOOP("App::Property", "Clearing pattern to use"),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyBool",
|
||||
"MinTravel",
|
||||
"Pocket",
|
||||
QT_TRANSLATE_NOOP("App::Property", "Use 3D Sorting of Path"),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyBool",
|
||||
"KeepToolDown",
|
||||
"Pocket",
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property", "Attempts to avoid unnecessary retractions."
|
||||
),
|
||||
)
|
||||
|
||||
obj.CutMode = ['Climb', 'Conventional']
|
||||
obj.StartAt = ['Center', 'Edge']
|
||||
obj.OffsetPattern = ['ZigZag', 'Offset', 'Spiral', 'ZigZagOffset', 'Line', 'Grid', 'Triangle']
|
||||
for n in self.pocketPropertyEnumerations():
|
||||
setattr(obj, n[0], n[1])
|
||||
|
||||
self.initPocketOp(obj)
|
||||
|
||||
@@ -88,40 +187,48 @@ class ObjectPocket(PathAreaOp.ObjectOp):
|
||||
return not obj.KeepToolDown
|
||||
|
||||
def areaOpUseProjection(self, obj):
|
||||
'''areaOpUseProjection(obj) ... return False'''
|
||||
"""areaOpUseProjection(obj) ... return False"""
|
||||
return False
|
||||
|
||||
def areaOpAreaParams(self, obj, isHole):
|
||||
'''areaOpAreaParams(obj, isHole) ... return dictionary with pocket's area parameters'''
|
||||
"""areaOpAreaParams(obj, isHole) ... return dictionary with pocket's area parameters"""
|
||||
params = {}
|
||||
params['Fill'] = 0
|
||||
params['Coplanar'] = 0
|
||||
params['PocketMode'] = 1
|
||||
params['SectionCount'] = -1
|
||||
params['Angle'] = obj.ZigZagAngle
|
||||
params['FromCenter'] = (obj.StartAt == "Center")
|
||||
params['PocketStepover'] = (self.radius * 2) * (float(obj.StepOver)/100)
|
||||
params["Fill"] = 0
|
||||
params["Coplanar"] = 0
|
||||
params["PocketMode"] = 1
|
||||
params["SectionCount"] = -1
|
||||
params["Angle"] = obj.ZigZagAngle
|
||||
params["FromCenter"] = obj.StartAt == "Center"
|
||||
params["PocketStepover"] = (self.radius * 2) * (float(obj.StepOver) / 100)
|
||||
extraOffset = obj.ExtraOffset.Value
|
||||
if self.pocketInvertExtraOffset():
|
||||
extraOffset = 0 - extraOffset
|
||||
params['PocketExtraOffset'] = extraOffset
|
||||
params['ToolRadius'] = self.radius
|
||||
params["PocketExtraOffset"] = extraOffset
|
||||
params["ToolRadius"] = self.radius
|
||||
|
||||
Pattern = ['ZigZag', 'Offset', 'Spiral', 'ZigZagOffset', 'Line', 'Grid', 'Triangle']
|
||||
params['PocketMode'] = Pattern.index(obj.OffsetPattern) + 1
|
||||
Pattern = [
|
||||
"ZigZag",
|
||||
"Offset",
|
||||
"Spiral",
|
||||
"ZigZagOffset",
|
||||
"Line",
|
||||
"Grid",
|
||||
"Triangle",
|
||||
]
|
||||
params["PocketMode"] = Pattern.index(obj.OffsetPattern) + 1
|
||||
|
||||
if obj.SplitArcs:
|
||||
params['Explode'] = True
|
||||
params['FitArcs'] = False
|
||||
params["Explode"] = True
|
||||
params["FitArcs"] = False
|
||||
|
||||
return params
|
||||
|
||||
def areaOpPathParams(self, obj, isHole):
|
||||
'''areaOpAreaParams(obj, isHole) ... return dictionary with pocket's path parameters'''
|
||||
"""areaOpAreaParams(obj, isHole) ... return dictionary with pocket's path parameters"""
|
||||
params = {}
|
||||
|
||||
CutMode = ['Conventional', 'Climb']
|
||||
params['orientation'] = CutMode.index(obj.CutMode)
|
||||
CutMode = ["Conventional", "Climb"]
|
||||
params["orientation"] = CutMode.index(obj.CutMode)
|
||||
|
||||
# if MinTravel is turned on, set path sorting to 3DSort
|
||||
# 3DSort shouldn't be used without a valid start point. Can cause
|
||||
@@ -133,19 +240,19 @@ class ObjectPocket(PathAreaOp.ObjectOp):
|
||||
# any problem
|
||||
#
|
||||
if obj.MinTravel and obj.UseStartPoint and obj.StartPoint is not None:
|
||||
params['sort_mode'] = 3
|
||||
params['threshold'] = self.radius * 2
|
||||
params["sort_mode"] = 3
|
||||
params["threshold"] = self.radius * 2
|
||||
return params
|
||||
|
||||
|
||||
def SetupProperties():
|
||||
setup = PathAreaOp.SetupProperties()
|
||||
setup.append('CutMode')
|
||||
setup.append('ExtraOffset')
|
||||
setup.append('StepOver')
|
||||
setup.append('ZigZagAngle')
|
||||
setup.append('OffsetPattern')
|
||||
setup.append('StartAt')
|
||||
setup.append('MinTravel')
|
||||
setup.append('KeepToolDown')
|
||||
setup.append("CutMode")
|
||||
setup.append("ExtraOffset")
|
||||
setup.append("StepOver")
|
||||
setup.append("ZigZagAngle")
|
||||
setup.append("OffsetPattern")
|
||||
setup.append("StartAt")
|
||||
setup.append("MinTravel")
|
||||
setup.append("KeepToolDown")
|
||||
return setup
|
||||
|
||||
@@ -22,50 +22,69 @@
|
||||
|
||||
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.PathGui as PathGui
|
||||
import PathScripts.PathOpGui as PathOpGui
|
||||
|
||||
from PySide import QtCore #, QtGui
|
||||
import PathScripts.PathPocket as PathPocket
|
||||
import PathScripts.PathLog as PathLog
|
||||
|
||||
__title__ = "Path Pocket Base Operation UI"
|
||||
__author__ = "sliptonic (Brad Collette)"
|
||||
__url__ = "https://www.freecadweb.org"
|
||||
__doc__ = "Base page controller and command implementation for path pocket operations."
|
||||
|
||||
def translate(context, text, disambig=None):
|
||||
return QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
if False:
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
|
||||
PathLog.trackModule(PathLog.thisModule())
|
||||
else:
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
|
||||
translate = FreeCAD.Qt.translate
|
||||
|
||||
FeaturePocket = 0x01
|
||||
FeatureFacing = 0x02
|
||||
FeatureOutline = 0x04
|
||||
|
||||
FeaturePocket = 0x01
|
||||
FeatureFacing = 0x02
|
||||
FeatureOutline = 0x04
|
||||
|
||||
class TaskPanelOpPage(PathOpGui.TaskPanelPage):
|
||||
'''Page controller class for pocket operations, supports:
|
||||
FeaturePocket ... used for pocketing operation
|
||||
FeatureFacing ... used for face milling operation
|
||||
FeatureOutline ... used for pocket-shape operation
|
||||
'''
|
||||
"""Page controller class for pocket operations, supports:
|
||||
FeaturePocket ... used for pocketing operation
|
||||
FeatureFacing ... used for face milling operation
|
||||
FeatureOutline ... used for pocket-shape operation
|
||||
"""
|
||||
|
||||
def pocketFeatures(self):
|
||||
'''pocketFeatures() ... return which features of the UI are supported by the operation.
|
||||
"""pocketFeatures() ... return which features of the UI are supported by the operation.
|
||||
FeaturePocket ... used for pocketing operation
|
||||
FeatureFacing ... used for face milling operation
|
||||
FeatureOutline ... used for pocket-shape operation
|
||||
Must be overwritten by subclasses'''
|
||||
pass # pylint: disable=unnecessary-pass
|
||||
Must be overwritten by subclasses"""
|
||||
pass # pylint: disable=unnecessary-pass
|
||||
|
||||
def getForm(self):
|
||||
'''getForm() ... returns UI, adapted to the results from pocketFeatures()'''
|
||||
"""getForm() ... returns UI, adapted to the results from pocketFeatures()"""
|
||||
form = FreeCADGui.PySideUic.loadUi(":/panels/PageOpPocketFullEdit.ui")
|
||||
|
||||
comboToPropertyMap = [
|
||||
("cutMode", "CutMode"),
|
||||
("offsetPattern", "OffsetPattern"),
|
||||
]
|
||||
enumTups = PathPocket.ObjectPocket.pocketPropertyEnumerations(dataType="raw")
|
||||
|
||||
self.populateCombobox(form, enumTups, comboToPropertyMap)
|
||||
|
||||
if not FeatureFacing & self.pocketFeatures():
|
||||
form.facingWidget.hide()
|
||||
form.clearEdges.hide()
|
||||
|
||||
if FeaturePocket & self.pocketFeatures():
|
||||
form.extraOffset_label.setText(translate("PathPocket", "Pass Extension"))
|
||||
form.extraOffset.setToolTip(translate("PathPocket", "The distance the facing operation will extend beyond the boundary shape."))
|
||||
form.extraOffset.setToolTip(
|
||||
translate(
|
||||
"PathPocket",
|
||||
"The distance the facing operation will extend beyond the boundary shape.",
|
||||
)
|
||||
)
|
||||
|
||||
if not (FeatureOutline & self.pocketFeatures()):
|
||||
form.useOutline.hide()
|
||||
@@ -76,6 +95,21 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
|
||||
|
||||
return form
|
||||
|
||||
def populateCombobox(self, form, enumTups, comboBoxesPropertyMap):
|
||||
"""fillComboboxes(form, comboBoxesPropertyMap) ... populate comboboxes with translated enumerations
|
||||
** comboBoxesPropertyMap will be unnecessary if UI files use strict combobox naming protocol.
|
||||
Args:
|
||||
form = UI form
|
||||
enumTups = list of (translated_text, data_string) tuples
|
||||
comboBoxesPropertyMap = list of (translated_text, data_string) tuples
|
||||
"""
|
||||
# Load appropriate enumerations in each combobox
|
||||
for cb, prop in comboBoxesPropertyMap:
|
||||
box = getattr(form, cb) # Get the combobox
|
||||
box.clear() # clear the combobox
|
||||
for text, data in enumTups[prop]: # load enumerations
|
||||
box.addItem(text, data)
|
||||
|
||||
def updateMinTravel(self, obj, setModel=True):
|
||||
if obj.UseStartPoint:
|
||||
self.form.minTravel.setEnabled(True)
|
||||
@@ -87,24 +121,24 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
|
||||
obj.MinTravel = self.form.minTravel.isChecked()
|
||||
|
||||
def updateZigZagAngle(self, obj, setModel=True):
|
||||
if obj.OffsetPattern in ['Offset', 'Spiral']:
|
||||
if obj.OffsetPattern in ["Offset", "Spiral"]:
|
||||
self.form.zigZagAngle.setEnabled(False)
|
||||
else:
|
||||
self.form.zigZagAngle.setEnabled(True)
|
||||
|
||||
if setModel:
|
||||
PathGui.updateInputField(obj, 'ZigZagAngle', self.form.zigZagAngle)
|
||||
PathGui.updateInputField(obj, "ZigZagAngle", self.form.zigZagAngle)
|
||||
|
||||
def getFields(self, obj):
|
||||
'''getFields(obj) ... transfers values from UI to obj's proprties'''
|
||||
if obj.CutMode != str(self.form.cutMode.currentText()):
|
||||
obj.CutMode = str(self.form.cutMode.currentText())
|
||||
"""getFields(obj) ... transfers values from UI to obj's proprties"""
|
||||
if obj.CutMode != str(self.form.cutMode.currentData()):
|
||||
obj.CutMode = str(self.form.cutMode.currentData())
|
||||
if obj.StepOver != self.form.stepOverPercent.value():
|
||||
obj.StepOver = self.form.stepOverPercent.value()
|
||||
if obj.OffsetPattern != str(self.form.offsetPattern.currentText()):
|
||||
obj.OffsetPattern = str(self.form.offsetPattern.currentText())
|
||||
if obj.OffsetPattern != str(self.form.offsetPattern.currentData()):
|
||||
obj.OffsetPattern = str(self.form.offsetPattern.currentData())
|
||||
|
||||
PathGui.updateInputField(obj, 'ExtraOffset', self.form.extraOffset)
|
||||
PathGui.updateInputField(obj, "ExtraOffset", self.form.extraOffset)
|
||||
self.updateToolController(obj, self.form.toolController)
|
||||
self.updateCoolant(obj, self.form.coolantController)
|
||||
self.updateZigZagAngle(obj)
|
||||
@@ -119,20 +153,29 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
|
||||
self.updateMinTravel(obj)
|
||||
|
||||
if FeatureFacing & self.pocketFeatures():
|
||||
if obj.BoundaryShape != str(self.form.boundaryShape.currentText()):
|
||||
obj.BoundaryShape = str(self.form.boundaryShape.currentText())
|
||||
print(obj.BoundaryShape)
|
||||
print(self.form.boundaryShape.currentText())
|
||||
print(self.form.boundaryShape.currentData())
|
||||
if obj.BoundaryShape != str(self.form.boundaryShape.currentData()):
|
||||
obj.BoundaryShape = str(self.form.boundaryShape.currentData())
|
||||
if obj.ClearEdges != self.form.clearEdges.isChecked():
|
||||
obj.ClearEdges = self.form.clearEdges.isChecked()
|
||||
|
||||
def setFields(self, obj):
|
||||
'''setFields(obj) ... transfers obj's property values to UI'''
|
||||
"""setFields(obj) ... transfers obj's property values to UI"""
|
||||
self.form.stepOverPercent.setValue(obj.StepOver)
|
||||
self.form.extraOffset.setText(FreeCAD.Units.Quantity(obj.ExtraOffset.Value, FreeCAD.Units.Length).UserString)
|
||||
self.form.extraOffset.setText(
|
||||
FreeCAD.Units.Quantity(
|
||||
obj.ExtraOffset.Value, FreeCAD.Units.Length
|
||||
).UserString
|
||||
)
|
||||
self.form.useStartPoint.setChecked(obj.UseStartPoint)
|
||||
if FeatureOutline & self.pocketFeatures():
|
||||
self.form.useOutline.setChecked(obj.UseOutline)
|
||||
|
||||
self.form.zigZagAngle.setText(FreeCAD.Units.Quantity(obj.ZigZagAngle, FreeCAD.Units.Angle).UserString)
|
||||
self.form.zigZagAngle.setText(
|
||||
FreeCAD.Units.Quantity(obj.ZigZagAngle, FreeCAD.Units.Angle).UserString
|
||||
)
|
||||
self.updateZigZagAngle(obj, False)
|
||||
|
||||
self.form.minTravel.setChecked(obj.MinTravel)
|
||||
@@ -148,7 +191,7 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
|
||||
self.form.clearEdges.setChecked(obj.ClearEdges)
|
||||
|
||||
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.cutMode.currentIndexChanged)
|
||||
|
||||
@@ -24,27 +24,41 @@ import FreeCAD
|
||||
import PathScripts.PathOpGui as PathOpGui
|
||||
import PathScripts.PathPocket as PathPocket
|
||||
import PathScripts.PathPocketBaseGui as PathPocketBaseGui
|
||||
import PathScripts.PathLog as PathLog
|
||||
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
|
||||
if False:
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
|
||||
PathLog.trackModule(PathLog.thisModule())
|
||||
else:
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
|
||||
from PySide import QtCore
|
||||
|
||||
__title__ = "Path Pocket Operation UI"
|
||||
__author__ = "sliptonic (Brad Collette)"
|
||||
__url__ = "https://www.freecadweb.org"
|
||||
__doc__ = "Pocket operation page controller and command implementation."
|
||||
|
||||
|
||||
class TaskPanelOpPage(PathPocketBaseGui.TaskPanelOpPage):
|
||||
'''Page controller class for Pocket operation'''
|
||||
"""Page controller class for Pocket operation"""
|
||||
|
||||
def pocketFeatures(self):
|
||||
'''pocketFeatures() ... return FeaturePocket (see PathPocketBaseGui)'''
|
||||
"""pocketFeatures() ... return FeaturePocket (see PathPocketBaseGui)"""
|
||||
return PathPocketBaseGui.FeaturePocket
|
||||
|
||||
Command = PathOpGui.SetupOperation('Pocket 3D',
|
||||
PathPocket.Create,
|
||||
TaskPanelOpPage,
|
||||
'Path_3DPocket',
|
||||
QtCore.QT_TRANSLATE_NOOP("Path_Pocket", "3D Pocket"),
|
||||
QtCore.QT_TRANSLATE_NOOP("Path_Pocket", "Creates a Path 3D Pocket object from a face or faces"),
|
||||
PathPocket.SetupProperties)
|
||||
|
||||
Command = PathOpGui.SetupOperation(
|
||||
"Pocket3D",
|
||||
PathPocket.Create,
|
||||
TaskPanelOpPage,
|
||||
"Path_3DPocket",
|
||||
QT_TRANSLATE_NOOP("Path_Pocket3D", "3D Pocket"),
|
||||
QT_TRANSLATE_NOOP(
|
||||
"Path_Pocket3D", "Creates a Path 3D Pocket object from a face or faces"
|
||||
),
|
||||
PathPocket.SetupProperties,
|
||||
)
|
||||
|
||||
FreeCAD.Console.PrintLog("Loading PathPocketGui... done\n")
|
||||
|
||||
@@ -20,13 +20,13 @@
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
import FreeCAD
|
||||
import PathScripts.PathGeom as PathGeom
|
||||
import PathScripts.PathLog as PathLog
|
||||
import PathScripts.PathOp as PathOp
|
||||
import PathScripts.PathPocketBase as PathPocketBase
|
||||
|
||||
from PySide import QtCore
|
||||
|
||||
# lazily loaded modules
|
||||
from lazy_loader.lazy_loader import LazyLoader
|
||||
@@ -46,13 +46,11 @@ __url__ = "https://www.freecadweb.org"
|
||||
__doc__ = "Class and implementation of shape based Pocket operation."
|
||||
|
||||
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
# PathLog.trackModule(PathLog.thisModule())
|
||||
|
||||
|
||||
# Qt translation handling
|
||||
def translate(context, text, disambig=None):
|
||||
return QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
if False:
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
|
||||
PathLog.trackModule(PathLog.thisModule())
|
||||
else:
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
|
||||
|
||||
class ObjectPocket(PathPocketBase.ObjectPocket):
|
||||
@@ -68,8 +66,8 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
|
||||
"App::PropertyBool",
|
||||
"UseOutline",
|
||||
"Pocket",
|
||||
QtCore.QT_TRANSLATE_NOOP(
|
||||
"PathPocketShape", "Uses the outline of the base geometry."
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property", "Uses the outline of the base geometry."
|
||||
),
|
||||
)
|
||||
|
||||
@@ -113,10 +111,9 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
|
||||
if "Face" in sub:
|
||||
if sub not in avoidFeatures and not self.clasifySub(base, sub):
|
||||
PathLog.error(
|
||||
translate(
|
||||
"PathPocket", "Pocket does not support shape %s.%s"
|
||||
"Pocket does not support shape {}.{}".format(
|
||||
base.Label, sub
|
||||
)
|
||||
% (base.Label, sub)
|
||||
)
|
||||
|
||||
# Convert horizontal faces to use outline only if requested
|
||||
@@ -136,12 +133,7 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
|
||||
face = Part.Face(w)
|
||||
# face.tessellate(0.1)
|
||||
if PathGeom.isRoughly(face.Area, 0):
|
||||
PathLog.error(
|
||||
translate(
|
||||
"PathPocket",
|
||||
"Vertical faces do not form a loop - ignoring",
|
||||
)
|
||||
)
|
||||
PathLog.error("Vertical faces do not form a loop - ignoring")
|
||||
else:
|
||||
self.horiz.append(face)
|
||||
|
||||
@@ -271,11 +263,7 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
|
||||
self.vert.append(face)
|
||||
return True
|
||||
else:
|
||||
PathLog.error(
|
||||
translate(
|
||||
"Path", "Failed to identify vertical face from {}.".format(sub)
|
||||
)
|
||||
)
|
||||
PathLog.error("Failed to identify vertical face from {}".format(sub))
|
||||
|
||||
else:
|
||||
PathLog.debug(" -type(face.Surface): {}".format(type(face.Surface)))
|
||||
|
||||
@@ -26,42 +26,52 @@ import PathScripts.PathOpGui as PathOpGui
|
||||
import PathScripts.PathPocketShape as PathPocketShape
|
||||
import PathScripts.PathPocketBaseGui as PathPocketBaseGui
|
||||
import PathScripts.PathFeatureExtensionsGui as PathFeatureExtensionsGui
|
||||
|
||||
from PySide import QtCore
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
|
||||
# lazily loaded modules
|
||||
from lazy_loader.lazy_loader import LazyLoader
|
||||
Part = LazyLoader('Part', globals(), 'Part')
|
||||
|
||||
Part = LazyLoader("Part", globals(), "Part")
|
||||
|
||||
__title__ = "Path Pocket Shape Operation UI"
|
||||
__author__ = "sliptonic (Brad Collette)"
|
||||
__url__ = "https://www.freecadweb.org"
|
||||
__doc__ = "Pocket Shape operation page controller and command implementation."
|
||||
|
||||
def translate(context, text, disambig=None):
|
||||
return QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
if False:
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
|
||||
PathLog.trackModule(PathLog.thisModule())
|
||||
else:
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
|
||||
translate = FreeCAD.Qt.translate
|
||||
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
#PathLog.trackModule(PathLog.thisModule())
|
||||
|
||||
class TaskPanelOpPage(PathPocketBaseGui.TaskPanelOpPage):
|
||||
'''Page controller class for Pocket operation'''
|
||||
"""Page controller class for Pocket operation"""
|
||||
|
||||
def pocketFeatures(self):
|
||||
'''pocketFeatures() ... return FeaturePocket (see PathPocketBaseGui)'''
|
||||
"""pocketFeatures() ... return FeaturePocket (see PathPocketBaseGui)"""
|
||||
return PathPocketBaseGui.FeaturePocket | PathPocketBaseGui.FeatureOutline
|
||||
|
||||
def taskPanelBaseLocationPage(self, obj, features):
|
||||
if not hasattr(self, 'extensionsPanel'):
|
||||
self.extensionsPanel = PathFeatureExtensionsGui.TaskPanelExtensionPage(obj, features) # pylint: disable=attribute-defined-outside-init
|
||||
if not hasattr(self, "extensionsPanel"):
|
||||
self.extensionsPanel = PathFeatureExtensionsGui.TaskPanelExtensionPage(
|
||||
obj, features
|
||||
) # pylint: disable=attribute-defined-outside-init
|
||||
return self.extensionsPanel
|
||||
|
||||
Command = PathOpGui.SetupOperation('Pocket Shape',
|
||||
PathPocketShape.Create,
|
||||
TaskPanelOpPage,
|
||||
'Path_Pocket',
|
||||
QtCore.QT_TRANSLATE_NOOP("Path_Pocket", "Pocket Shape"),
|
||||
QtCore.QT_TRANSLATE_NOOP("Path_Pocket", "Creates a Path Pocket object from a face or faces"),
|
||||
PathPocketShape.SetupProperties)
|
||||
|
||||
Command = PathOpGui.SetupOperation(
|
||||
"Pocket Shape",
|
||||
PathPocketShape.Create,
|
||||
TaskPanelOpPage,
|
||||
"Path_Pocket",
|
||||
QT_TRANSLATE_NOOP("Path_Pocket_Shape", "Pocket Shape"),
|
||||
QT_TRANSLATE_NOOP(
|
||||
"Path_Pocket_Shape", "Creates a Path Pocket object from a face or faces"
|
||||
),
|
||||
PathPocketShape.SetupProperties,
|
||||
)
|
||||
|
||||
FreeCAD.Console.PrintLog("Loading PathPocketShapeGui... done\n")
|
||||
|
||||
@@ -390,7 +390,7 @@ def select(op):
|
||||
opsel["Helix"] = drillselect
|
||||
opsel["MillFace"] = pocketselect
|
||||
opsel["Pocket"] = pocketselect
|
||||
opsel["Pocket 3D"] = pocketselect
|
||||
opsel["Pocket3D"] = pocketselect
|
||||
opsel["Pocket Shape"] = pocketselect
|
||||
opsel["Profile Edges"] = eselect # (depreciated)
|
||||
opsel["Profile Faces"] = fselect # (depreciated)
|
||||
|
||||
Reference in New Issue
Block a user