diff --git a/src/Mod/Path/PathScripts/PathDeburr.py b/src/Mod/Path/PathScripts/PathDeburr.py index 0e45c72658..b60fda27da 100644 --- a/src/Mod/Path/PathScripts/PathDeburr.py +++ b/src/Mod/Path/PathScripts/PathDeburr.py @@ -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(): diff --git a/src/Mod/Path/PathScripts/PathDeburrGui.py b/src/Mod/Path/PathScripts/PathDeburrGui.py index 6334b6e9fc..1d84135d07 100644 --- a/src/Mod/Path/PathScripts/PathDeburrGui.py +++ b/src/Mod/Path/PathScripts/PathDeburrGui.py @@ -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") - diff --git a/src/Mod/Path/PathScripts/PathEngraveBase.py b/src/Mod/Path/PathScripts/PathEngraveBase.py index 7606ec9b58..dc8689aa91 100644 --- a/src/Mod/Path/PathScripts/PathEngraveBase.py +++ b/src/Mod/Path/PathScripts/PathEngraveBase.py @@ -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 diff --git a/src/Mod/Path/PathScripts/PathOpTools.py b/src/Mod/Path/PathScripts/PathOpTools.py index 40e7105ca5..c7c4baf540 100644 --- a/src/Mod/Path/PathScripts/PathOpTools.py +++ b/src/Mod/Path/PathScripts/PathOpTools.py @@ -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 diff --git a/src/Mod/Path/PathScripts/PathSelection.py b/src/Mod/Path/PathScripts/PathSelection.py index d124d2b139..1263f95cb3 100644 --- a/src/Mod/Path/PathScripts/PathSelection.py +++ b/src/Mod/Path/PathScripts/PathSelection.py @@ -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