Merge pull request #3941 from sliptonic/bug/deburr

[Path] Deburr bug fixes
This commit is contained in:
sliptonic
2020-10-12 16:04:55 -05:00
committed by GitHub
5 changed files with 111 additions and 53 deletions

View File

@@ -42,7 +42,7 @@ __url__ = "http://www.freecadweb.org"
__doc__ = "Deburr operation."
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
#PathLog.trackModule(PathLog.thisModule())
# PathLog.trackModule(PathLog.thisModule())
# Qt translation handling
@@ -51,18 +51,33 @@ def translate(context, text, disambig=None):
def toolDepthAndOffset(width, extraDepth, tool):
'''toolDepthAndOffset(width, extraDepth, tool) ... return tuple for given parameters.'''
angle = float(tool.CuttingEdgeAngle)
if 0 == angle:
'''toolDepthAndOffset(width, extraDepth, tool) ... return tuple for given\n
parameters.'''
if not hasattr(tool, 'Diameter'):
raise ValueError('Deburr requires tool with diameter\n')
if not hasattr(tool, 'CuttingEdgeAngle'):
angle = 180
FreeCAD.Console.PrintMessage('The selected tool has No CuttingEdgeAngle property. Assuming Endmill\n')
else:
angle = float(tool.CuttingEdgeAngle)
if not hasattr(tool, 'FlatRadius'):
toolOffset = float(tool.Diameter / 2)
FreeCAD.Console.PrintMessage('The selected tool has no FlatRadius property. Using Diameter\n')
else:
toolOffset = float(tool.FlatRadius)
if angle == 0:
angle = 180
tan = math.tan(math.radians(angle / 2))
toolDepth = 0 if 0 == tan else width / tan
depth = toolDepth + extraDepth
toolOffset = float(tool.FlatRadius)
extraOffset = float(tool.Diameter) / 2 - width if 180 == angle else extraDepth / tan
extraOffset = float(tool.Diameter) / 2 - width if angle == 180 else extraDepth / tan
offset = toolOffset + extraOffset
return (depth, offset)
@@ -70,32 +85,45 @@ class ObjectDeburr(PathEngraveBase.ObjectOp):
'''Proxy class for Deburr operation.'''
def opFeatures(self, obj):
return PathOp.FeatureTool | PathOp.FeatureHeights | PathOp.FeatureStepDown | PathOp.FeatureBaseEdges | PathOp.FeatureBaseFaces | PathOp.FeatureCoolant
return PathOp.FeatureTool | PathOp.FeatureHeights | PathOp.FeatureStepDown | PathOp.FeatureBaseEdges | PathOp.FeatureBaseFaces | PathOp.FeatureCoolant
def initOperation(self, obj):
PathLog.track(obj.Label)
obj.addProperty('App::PropertyDistance', 'Width', 'Deburr', QtCore.QT_TRANSLATE_NOOP('PathDeburr', 'The desired width of the chamfer'))
obj.addProperty('App::PropertyDistance', 'ExtraDepth', 'Deburr', QtCore.QT_TRANSLATE_NOOP('PathDeburr', 'The additional depth of the tool path'))
obj.addProperty('App::PropertyEnumeration', 'Join', 'Deburr', QtCore.QT_TRANSLATE_NOOP('PathDeburr', 'How to join chamfer segments'))
obj.addProperty('App::PropertyDistance', 'Width', 'Deburr',
QtCore.QT_TRANSLATE_NOOP('PathDeburr', 'The desired width of the chamfer'))
obj.addProperty('App::PropertyDistance', 'ExtraDepth', 'Deburr',
QtCore.QT_TRANSLATE_NOOP('PathDeburr', 'The additional depth of the tool path'))
obj.addProperty('App::PropertyEnumeration', 'Join', 'Deburr',
QtCore.QT_TRANSLATE_NOOP('PathDeburr', 'How to join chamfer segments'))
obj.Join = ['Round', 'Miter']
obj.setEditorMode('Join', 2) # hide for now
obj.addProperty('App::PropertyEnumeration', 'Direction', 'Deburr', QtCore.QT_TRANSLATE_NOOP('PathDeburr', 'Direction of Operation'))
obj.addProperty('App::PropertyEnumeration', 'Direction', 'Deburr',
QtCore.QT_TRANSLATE_NOOP('PathDeburr', 'Direction of Operation'))
obj.Direction = ['CW', 'CCW']
obj.addProperty('App::PropertyEnumeration', 'Side', 'Deburr', QtCore.QT_TRANSLATE_NOOP('PathDeburr', 'Side of Operation'))
obj.addProperty('App::PropertyEnumeration', 'Side', 'Deburr',
QtCore.QT_TRANSLATE_NOOP('PathDeburr', 'Side of Operation'))
obj.Side = ['Outside', 'Inside']
obj.setEditorMode('Side', 2) # Hide property, it's calculated by op
obj.addProperty('App::PropertyInteger', 'EntryPoint', 'Deburr', QtCore.QT_TRANSLATE_NOOP('PathDeburr', 'Select the segment, there the operations starts'))
obj.setEditorMode('Side', 2) # Hide property, it's calculated by op
obj.addProperty('App::PropertyInteger', 'EntryPoint', 'Deburr',
QtCore.QT_TRANSLATE_NOOP('PathDeburr', 'Select the segment, there the operations starts'))
def opOnDocumentRestored(self, obj):
obj.setEditorMode('Join', 2) # hide for now
def opExecute(self, obj):
PathLog.track(obj.Label)
(depth, offset) = toolDepthAndOffset(obj.Width.Value, obj.ExtraDepth.Value, self.tool)
try:
(depth, offset) = toolDepthAndOffset(obj.Width.Value, obj.ExtraDepth.Value, self.tool)
except ValueError as e:
msg = "{} \n No path will be generated".format(e)
raise ValueError(msg)
# QtGui.QMessageBox.information(None, "Tool Error", msg)
# return
PathLog.track(obj.Label, depth, offset)
self.basewires = [] # pylint: disable=attribute-defined-outside-init
self.adjusted_basewires = [] # pylint: disable=attribute-defined-outside-init
self.basewires = [] # pylint: disable=attribute-defined-outside-init
self.adjusted_basewires = [] # pylint: disable=attribute-defined-outside-init
wires = []
for base, subs in obj.Base:
edges = []
@@ -108,29 +136,25 @@ class ObjectDeburr(PathEngraveBase.ObjectOp):
basewires.extend(sub.Wires)
else:
basewires.append(Part.Wire(sub.Edges))
self.edges = edges # pylint: disable=attribute-defined-outside-init
self.edges = edges # pylint: disable=attribute-defined-outside-init
for edgelist in Part.sortEdges(edges):
basewires.append(Part.Wire(edgelist))
self.basewires.extend(basewires)
# Set default value
side = ["Outside"]
for w in basewires:
self.adjusted_basewires.append(w)
wire = PathOpTools.offsetWire(w, base.Shape, offset, True, side)
wire = PathOpTools.offsetWire(w, base.Shape, offset, True) #, obj.Side)
if wire:
wires.append(wire)
# Save Outside or Inside
obj.Side = side[0]
# # Save Outside or Inside
# obj.Side = side[0]
# Set direction of op
forward = True
if obj.Direction == 'CCW':
forward = False
forward = (obj.Direction == 'CCW')
zValues = []
z = 0
if obj.StepDown.Value != 0:
@@ -139,13 +163,12 @@ class ObjectDeburr(PathEngraveBase.ObjectOp):
zValues.append(z)
zValues.append(depth)
PathLog.track(obj.Label, depth, zValues)
if obj.EntryPoint < 0:
obj.EntryPoint = 0;
self.wires = wires # pylint: disable=attribute-defined-outside-init
obj.EntryPoint = 0
self.wires = wires # pylint: disable=attribute-defined-outside-init
self.buildpathocc(obj, wires, zValues, True, forward, obj.EntryPoint)
def opRejectAddBase(self, obj, base, sub):
'''The chamfer op can only deal with features of the base model, all others are rejected.'''
@@ -160,7 +183,7 @@ class ObjectDeburr(PathEngraveBase.ObjectOp):
obj.StepDown = '0 mm'
obj.Direction = 'CW'
obj.Side = "Outside"
obj.EntryPoint = 0;
obj.EntryPoint = 0
def SetupProperties():

View File

@@ -28,7 +28,7 @@ import PathScripts.PathDeburr as PathDeburr
import PathScripts.PathGui as PathGui
import PathScripts.PathLog as PathLog
import PathScripts.PathOpGui as PathOpGui
import Part
from PySide import QtCore, QtGui
__title__ = "Path Deburr Operation UI"
@@ -44,10 +44,30 @@ if LOGLEVEL:
else:
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
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.'''
def super(self):
return super(TaskPanelBaseGeometryPage, self)
def addBaseGeometry(self, selection):
for sel in selection:
if sel.HasSubObjects:
# selectively add some elements of the drawing to the Base
for sub in sel.SubObjects:
if isinstance(sub, Part.Face):
if sub.normalAt(0, 0) != FreeCAD.Vector(0, 0, 1):
PathLog.info(translate("Path", "Ignoring non-horizontal Face"))
return
self.super().addBaseGeometry(selection)
class TaskPanelOpPage(PathOpGui.TaskPanelPage):
'''Page controller class for the Deburr operation.'''
@@ -55,8 +75,8 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
return FreeCADGui.PySideUic.loadUi(":/panels/PageOpDeburrEdit.ui")
def initPage(self, obj):
self.opImagePath = "{}Mod/Path/Images/Ops/{}".format(FreeCAD.getHomePath(), 'chamfer.svg') # pylint: disable=attribute-defined-outside-init
self.opImage = QtGui.QPixmap(self.opImagePath) # pylint: disable=attribute-defined-outside-init
self.opImagePath = "{}Mod/Path/Images/Ops/{}".format(FreeCAD.getHomePath(), 'chamfer.svg') # pylint: disable=attribute-defined-outside-init
self.opImage = QtGui.QPixmap(self.opImagePath) # pylint: disable=attribute-defined-outside-init
self.form.opImage.setPixmap(self.opImage)
iconMiter = QtGui.QIcon(':/icons/edge-join-miter-not.svg')
iconMiter.addFile(':/icons/edge-join-miter.svg', state=QtGui.QIcon.On)
@@ -72,7 +92,7 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
obj.Join = 'Round'
elif self.form.joinMiter.isChecked():
obj.Join = 'Miter'
if obj.Direction != str(self.form.direction.currentText()):
obj.Direction = str(self.form.direction.currentText())
@@ -109,6 +129,10 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
self.form.value_W.editingFinished.connect(self.updateWidth)
self.form.value_h.editingFinished.connect(self.updateExtraDepth)
def taskPanelBaseGeometryPage(self, obj, features):
'''taskPanelBaseGeometryPage(obj, features) ... return page for adding base geometries.'''
return TaskPanelBaseGeometryPage(obj, features)
Command = PathOpGui.SetupOperation('Deburr',
PathDeburr.Create,
@@ -119,4 +143,3 @@ Command = PathOpGui.SetupOperation('Deburr',
PathDeburr.SetupProperties)
FreeCAD.Console.PrintLog("Loading PathDeburrGui... done\n")

View File

@@ -77,16 +77,20 @@ class ObjectOp(PathOp.ObjectOp):
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)
first = True
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)))
if first and (not last or not wire.isClosed()):
PathLog.debug('processing first edge entry')
# we set the first move to our first point
last = edge.Vertexes[0].Point
@@ -96,7 +100,8 @@ class ObjectOp(PathOp.ObjectOp):
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.Vertexes[0].Point):
if PathGeom.pointsCoincide(last, edge.valueAt(edge.FirstParameter)):
#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

View File

@@ -136,6 +136,7 @@ def orientWire(w, forward=True):
If forward = True (the default) the wire is oriented clockwise, looking down the negative Z axis.
If forward = False the wire is oriented counter clockwise.
If forward = None the orientation is determined by the order in which the edges appear in the wire.'''
PathLog.debug('orienting forward: {}'.format(forward))
wire = Part.Wire(_orientEdges(w.Edges))
if forward is not None:
if forward != _isWireClockwise(wire):
@@ -144,7 +145,7 @@ def orientWire(w, forward=True):
PathLog.track('orientWire - ok')
return wire
def offsetWire(wire, base, offset, forward, Side = None):
def offsetWire(wire, base, offset, forward):#, Side = None):
'''offsetWire(wire, base, offset, forward) ... offsets the wire away from base and orients the wire accordingly.
The function tries to avoid most of the pitfalls of Part.makeOffset2D which is possible because all offsetting
happens in the XY plane.
@@ -198,12 +199,12 @@ def offsetWire(wire, base, offset, forward, Side = None):
if wire.isClosed():
if not base.isInside(owire.Edges[0].Vertexes[0].Point, offset/2, True):
PathLog.track('closed - outside')
if Side:
Side[0] = "Outside"
# if Side:
# Side[0] = "Outside"
return orientWire(owire, forward)
PathLog.track('closed - inside')
if Side:
Side[0] = "Inside"
# if Side:
# Side[0] = "Inside"
try:
owire = wire.makeOffset2D(-offset)
except Exception: # pylint: disable=broad-except

View File

@@ -107,15 +107,21 @@ class CHAMFERGate(PathBaseGate):
if math.fabs(shape.Volume) < 1e-9 and len(shape.Wires) > 0:
return True
if 'Edge' == shape.ShapeType or 'Face' == shape.ShapeType:
if shape.ShapeType == 'Edge':
return True
if (shape.ShapeType == 'Face'
and shape.normalAt(0,0) == FreeCAD.Vector(0,0,1)):
return True
if sub:
subShape = shape.getElement(sub)
if 'Edge' == subShape.ShapeType or 'Face' == subShape.ShapeType:
if subShape.ShapeType == 'Edge':
return True
elif (subShape.ShapeType == 'Face'
and subShape.normalAt(0,0) == FreeCAD.Vector(0,0,1)):
return True
print(shape.ShapeType)
return False