Merge pull request #5421 from sliptonic/bug/translationEngrave

[PATH] translation cleanup (engrave, millface, pocket)
This commit is contained in:
sliptonic
2022-01-25 09:54:08 -06:00
committed by GitHub
14 changed files with 846 additions and 383 deletions

View File

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

View File

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

View File

@@ -22,7 +22,7 @@
import FreeCAD
import FreeCADGui
import PathGui as PGui # ensure Path/Gui/Resources are loaded
import PathGui as PGui # ensure Path/Gui/Resources are loaded
import PathScripts.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")

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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