diff --git a/src/Mod/Path/PathScripts/PathDeburr.py b/src/Mod/Path/PathScripts/PathDeburr.py index a6c24241f4..51bbb96207 100644 --- a/src/Mod/Path/PathScripts/PathDeburr.py +++ b/src/Mod/Path/PathScripts/PathDeburr.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # *************************************************************************** # * Copyright (c) 2018 sliptonic * -# * Copyright (c) 2020 Schildkroet * +# * Copyright (c) 2020-2021 Schildkroet * # * * # * This program is free software; you can redistribute it and/or modify * # * it under the terms of the GNU Lesser General Public License (LGPL) * @@ -86,14 +86,14 @@ def toolDepthAndOffset(width, extraDepth, tool, printInfo): extraOffset = -width if angle == 180 else (extraDepth / tan) offset = toolOffset + extraOffset - return (depth, offset, suppressInfo) + return (depth, offset, extraOffset, suppressInfo) 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 | PathOp.FeatureBaseGeometry def initOperation(self, obj): PathLog.track(obj.Label) @@ -123,7 +123,7 @@ class ObjectDeburr(PathEngraveBase.ObjectOp): if not hasattr(self, 'printInfo'): self.printInfo = True try: - (depth, offset, suppressInfo) = toolDepthAndOffset(obj.Width.Value, obj.ExtraDepth.Value, self.tool, self.printInfo) + (depth, offset, extraOffset, suppressInfo) = toolDepthAndOffset(obj.Width.Value, obj.ExtraDepth.Value, self.tool, self.printInfo) self.printInfo = not suppressInfo except ValueError as e: msg = "{} \n No path will be generated".format(e) @@ -136,42 +136,129 @@ class ObjectDeburr(PathEngraveBase.ObjectOp): 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 = [] basewires = [] + max_h = -99999 + radius_top = 0 + radius_bottom = 0 + for f in subs: sub = base.Shape.getElement(f) - if type(sub) == Part.Edge: + + if type(sub) == Part.Edge: # Edge edges.append(sub) + + elif type(sub) == Part.Face and sub.normalAt(0, 0) != FreeCAD.Vector(0, 0, 1): # Angled face + # If an angled face is selected, the lower edge is projected to the height of the upper edge, + # to simulate an edge + + # Find z value of upper edge + for edge in sub.Edges: + for p0 in edge.Vertexes: + if p0.Point.z > max_h: + max_h = p0.Point.z + + # Find biggest radius for top/bottom + for edge in sub.Edges: + if Part.Circle == type(edge.Curve): + if edge.Vertexes[0].Point.z == max_h: + if edge.Curve.Radius > radius_top: + radius_top = edge.Curve.Radius + else: + if edge.Curve.Radius > radius_bottom: + radius_bottom = edge.Curve.Radius + + # Search for lower edge and raise it to height of upper edge + for edge in sub.Edges: + if Part.Circle == type(edge.Curve): # Edge is a circle + if edge.Vertexes[0].Point.z < max_h: + + if edge.Closed: # Circle + # New center + center = FreeCAD.Vector(edge.Curve.Center.x, edge.Curve.Center.y, max_h) + new_edge = Part.makeCircle(edge.Curve.Radius, center, FreeCAD.Vector(0, 0, 1)) + edges.append(new_edge) + + # Modify offset for inner angled faces + if radius_bottom < radius_top: + offset -= 2 * extraOffset + + break + + else: # Arc + if edge.Vertexes[0].Point.z == edge.Vertexes[1].Point.z: + # Arc vertexes are on same layer + l1 = math.sqrt((edge.Vertexes[0].Point.x - edge.Curve.Center.x)**2 + (edge.Vertexes[0].Point.y - edge.Curve.Center.y)**2) + l2 = math.sqrt((edge.Vertexes[1].Point.x - edge.Curve.Center.x)**2 + (edge.Vertexes[1].Point.y - edge.Curve.Center.y)**2) + + # New center + center = FreeCAD.Vector(edge.Curve.Center.x, edge.Curve.Center.y, max_h) + + # Calculate angles based on x-axis (0 - PI/2) + start_angle = math.acos((edge.Vertexes[0].Point.x - edge.Curve.Center.x) / l1) + end_angle = math.acos((edge.Vertexes[1].Point.x - edge.Curve.Center.x) / l2) + + # Angles are based on x-axis (Mirrored on x-axis) -> negative y value means negative angle + if edge.Vertexes[0].Point.y < edge.Curve.Center.y: + start_angle *= -1 + if edge.Vertexes[1].Point.y < edge.Curve.Center.y: + end_angle *= -1 + + # Create new arc + new_edge = Part.ArcOfCircle(Part.Circle(center, FreeCAD.Vector(0,0,1), edge.Curve.Radius), start_angle, end_angle).toShape() + edges.append(new_edge) + + # Modify offset for inner angled faces + if radius_bottom < radius_top: + offset -= 2 * extraOffset + + break + + else: # Line + if edge.Vertexes[0].Point.z == edge.Vertexes[1].Point.z and edge.Vertexes[0].Point.z < max_h: + new_edge = Part.Edge(Part.LineSegment(FreeCAD.Vector(edge.Vertexes[0].Point.x, edge.Vertexes[0].Point.y, max_h), FreeCAD.Vector(edge.Vertexes[1].Point.x, edge.Vertexes[1].Point.y, max_h))) + edges.append(new_edge) + + elif sub.Wires: basewires.extend(sub.Wires) - else: + + else: # Flat face basewires.append(Part.Wire(sub.Edges)) + 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 side + side = ["Outside"] for w in basewires: self.adjusted_basewires.append(w) - wire = PathOpTools.offsetWire(w, base.Shape, offset, True) #, obj.Side) + wire = PathOpTools.offsetWire(w, base.Shape, offset, True, side) if wire: wires.append(wire) - # # Save Outside or Inside - # obj.Side = side[0] - # Set direction of op forward = (obj.Direction == 'CW') + # Set value of side + obj.Side = side[0] + # Check side extra for angled faces + if radius_top > radius_bottom: + obj.Side = "Inside" + zValues = [] z = 0 if obj.StepDown.Value != 0: while z + obj.StepDown.Value < depth: z = z + obj.StepDown.Value zValues.append(z) + zValues.append(depth) PathLog.track(obj.Label, depth, zValues) @@ -208,5 +295,6 @@ def Create(name, obj=None): '''Create(name) ... Creates and returns a Deburr operation.''' if obj is None: obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) + obj.Proxy = ObjectDeburr(obj, name) return obj diff --git a/src/Mod/Path/PathScripts/PathDeburrGui.py b/src/Mod/Path/PathScripts/PathDeburrGui.py index d98642f4dd..76e5f405e6 100644 --- a/src/Mod/Path/PathScripts/PathDeburrGui.py +++ b/src/Mod/Path/PathScripts/PathDeburrGui.py @@ -55,15 +55,6 @@ class TaskPanelBaseGeometryPage(PathOpGui.TaskPanelBaseGeometryPage): 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) diff --git a/src/Mod/Path/PathScripts/PathDressupLeadInOut.py b/src/Mod/Path/PathScripts/PathDressupLeadInOut.py index 64fc18e1ac..03f24a277f 100644 --- a/src/Mod/Path/PathScripts/PathDressupLeadInOut.py +++ b/src/Mod/Path/PathScripts/PathDressupLeadInOut.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # *************************************************************************** # * Copyright (c) 2017 LTS under LGPL * -# * Copyright (c) 2020 Schildkroet * +# * Copyright (c) 2020-2021 Schildkroet * # * * # * This program is free software; you can redistribute it and/or modify * # * it under the terms of the GNU Lesser General Public License (LGPL) * @@ -113,6 +113,7 @@ class ObjectDressup: def getDirectionOfPath(self, obj): op = PathDressup.baseOp(obj.Base) + if hasattr(op, 'Side') and op.Side == 'Outside': if hasattr(op, 'Direction') and op.Direction == 'CW': return 'left' @@ -131,12 +132,16 @@ class ObjectDressup: return '' def normalize(self, Vector): + vx = 0 + vy = 0 + x = Vector.x y = Vector.y length = math.sqrt(x*x + y*y) if((math.fabs(length)) > 0.0000000000001): vx = round(x / length, 3) vy = round(y / length, 3) + return FreeCAD.Vector(vx, vy, 0) def invert(self, Vector): diff --git a/src/Mod/Path/PathScripts/PathGeom.py b/src/Mod/Path/PathScripts/PathGeom.py index 5eff971035..2d8d54ecfd 100644 --- a/src/Mod/Path/PathScripts/PathGeom.py +++ b/src/Mod/Path/PathScripts/PathGeom.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # *************************************************************************** # * Copyright (c) 2016 sliptonic * +# * Copyright (c) 2021 Schildkroet * # * * # * This program is free software; you can redistribute it and/or modify * # * it under the terms of the GNU Lesser General Public License (LGPL) * @@ -342,8 +343,11 @@ def edgeForCmd(cmd, startPoint): PathLog.debug("StartPoint:{}".format(startPoint)) PathLog.debug("MidPoint:{}".format(midPoint)) PathLog.debug("EndPoint:{}".format(endPoint)) - - return Part.Edge(Part.Arc(startPoint, midPoint, endPoint)) + + if pointsCoincide(startPoint, endPoint, 0.001): + return Part.makeCircle(R, center, FreeCAD.Vector(0, 0, 1)) + else: + return Part.Edge(Part.Arc(startPoint, midPoint, endPoint)) # It's a Helix #print('angle: A=%.2f B=%.2f' % (getAngle(A)/math.pi, getAngle(B)/math.pi)) diff --git a/src/Mod/Path/PathScripts/PathOpTools.py b/src/Mod/Path/PathScripts/PathOpTools.py index 5bdb3a9ba4..4f66953b02 100644 --- a/src/Mod/Path/PathScripts/PathOpTools.py +++ b/src/Mod/Path/PathScripts/PathOpTools.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # *************************************************************************** # * Copyright (c) 2018 sliptonic * +# * Copyright (c) 2021 Schildkroet * # * * # * This program is free software; you can redistribute it and/or modify * # * it under the terms of the GNU Lesser General Public License (LGPL) * @@ -143,7 +144,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. @@ -158,14 +159,53 @@ def offsetWire(wire, base, offset, forward):#, Side = None): # https://www.freecadweb.org/wiki/Part%20Offset2D # it's easy to construct them manually though z = -1 if forward else 1 - edge = Part.makeCircle(curve.Radius + offset, curve.Center, FreeCAD.Vector(0, 0, z)) - if base.isInside(edge.Vertexes[0].Point, offset/2, True): + new_edge = Part.makeCircle(curve.Radius + offset, curve.Center, FreeCAD.Vector(0, 0, z)) + if base.isInside(new_edge.Vertexes[0].Point, offset/2, True): if offset > curve.Radius or PathGeom.isRoughly(offset, curve.Radius): # offsetting a hole by its own radius (or more) makes the hole vanish return None - edge = Part.makeCircle(curve.Radius - offset, curve.Center, FreeCAD.Vector(0, 0, -z)) - w = Part.Wire([edge]) - return w + if Side: + Side[0] = "Inside" + print("inside") + new_edge = Part.makeCircle(curve.Radius - offset, curve.Center, FreeCAD.Vector(0, 0, -z)) + + return Part.Wire([new_edge]) + + if Part.Circle == type(curve) and not wire.isClosed(): + # Process arc segment + z = -1 if forward else 1 + l1 = math.sqrt((edge.Vertexes[0].Point.x - curve.Center.x)**2 + (edge.Vertexes[0].Point.y - curve.Center.y)**2) + l2 = math.sqrt((edge.Vertexes[1].Point.x - curve.Center.x)**2 + (edge.Vertexes[1].Point.y - curve.Center.y)**2) + + # Calculate angles based on x-axis (0 - PI/2) + start_angle = math.acos((edge.Vertexes[0].Point.x - curve.Center.x) / l1) + end_angle = math.acos((edge.Vertexes[1].Point.x - curve.Center.x) / l2) + + # Angles are based on x-axis (Mirrored on x-axis) -> negative y value means negative angle + if edge.Vertexes[0].Point.y < curve.Center.y: + start_angle *= -1 + if edge.Vertexes[1].Point.y < curve.Center.y: + end_angle *= -1 + + if (edge.Vertexes[0].Point.x > curve.Center.x or edge.Vertexes[1].Point.x > curve.Center.x) and curve.AngleXU < 0: + tmp = start_angle + start_angle = end_angle + end_angle = tmp + + # Inside / Outside + if base.isInside(edge.Vertexes[0].Point, offset/2, True): + offset *= -1 + if Side: + Side[0] = "Inside" + + # Create new arc + if curve.AngleXU > 0: + edge = Part.ArcOfCircle(Part.Circle(curve.Center, FreeCAD.Vector(0,0,1), curve.Radius+offset), start_angle, end_angle).toShape() + else: + edge = Part.ArcOfCircle(Part.Circle(curve.Center, FreeCAD.Vector(0,0,1), curve.Radius-offset), start_angle, end_angle).toShape() + + return Part.Wire([edge]) + if Part.Line == type(curve) or Part.LineSegment == type(curve): # offsetting a single edge doesn't work because there is an infinite # possible planes into which the edge could be offset @@ -197,12 +237,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 055bfb1757..8f0725407b 100644 --- a/src/Mod/Path/PathScripts/PathSelection.py +++ b/src/Mod/Path/PathScripts/PathSelection.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # *************************************************************************** # * Copyright (c) 2015 Dan Falck * +# * Copyright (c) 2021 Schildkroet * # * * # * This program is free software; you can redistribute it and/or modify * # * it under the terms of the GNU Lesser General Public License (LGPL) * @@ -115,7 +116,7 @@ class CHAMFERGate(PathBaseGate): subShape = shape.getElement(sub) if subShape.ShapeType == 'Edge': return True - elif (subShape.ShapeType == 'Face' and subShape.normalAt(0, 0) == FreeCAD.Vector(0, 0, 1)): + elif (subShape.ShapeType == 'Face'): return True return False diff --git a/src/Mod/Path/PathTests/TestPathDeburr.py b/src/Mod/Path/PathTests/TestPathDeburr.py index 901851143b..2dcf446018 100644 --- a/src/Mod/Path/PathTests/TestPathDeburr.py +++ b/src/Mod/Path/PathTests/TestPathDeburr.py @@ -38,7 +38,7 @@ class TestPathDeburr(PathTestUtils.PathTestBase): tool.FlatRadius = 0 tool.CuttingEdgeAngle = 180 - (depth, offset, info) = PathDeburr.toolDepthAndOffset(1, 0.01, tool, True) + (depth, offset, __, info) = PathDeburr.toolDepthAndOffset(1, 0.01, tool, True) self.assertRoughly(0.01, depth) self.assertRoughly(9, offset) self.assertFalse(info) @@ -46,7 +46,7 @@ class TestPathDeburr(PathTestUtils.PathTestBase): # legacy tools - no problem, same result tool.CuttingEdgeAngle = 0 - (depth, offset, info) = PathDeburr.toolDepthAndOffset(1, 0.01, tool, True) + (depth, offset, __, info) = PathDeburr.toolDepthAndOffset(1, 0.01, tool, True) self.assertRoughly(0.01, depth) self.assertRoughly(9, offset) self.assertFalse(info) @@ -57,12 +57,12 @@ class TestPathDeburr(PathTestUtils.PathTestBase): tool.FlatRadius = 0 tool.CuttingEdgeAngle = 90 - (depth, offset, info) = PathDeburr.toolDepthAndOffset(1, 0, tool, True) + (depth, offset, __, info) = PathDeburr.toolDepthAndOffset(1, 0, tool, True) self.assertRoughly(1, depth) self.assertRoughly(0, offset) self.assertFalse(info) - (depth, offset, info) = PathDeburr.toolDepthAndOffset(1, 0.2, tool, True) + (depth, offset, __, info) = PathDeburr.toolDepthAndOffset(1, 0.2, tool, True) self.assertRoughly(1.2, depth) self.assertRoughly(0.2, offset) self.assertFalse(info) @@ -73,12 +73,12 @@ class TestPathDeburr(PathTestUtils.PathTestBase): tool.FlatRadius = 0.3 tool.CuttingEdgeAngle = 90 - (depth, offset, info) = PathDeburr.toolDepthAndOffset(1, 0, tool, True) + (depth, offset, __, info) = PathDeburr.toolDepthAndOffset(1, 0, tool, True) self.assertRoughly(1, depth) self.assertRoughly(0.3, offset) self.assertFalse(info) - (depth, offset, info) = PathDeburr.toolDepthAndOffset(2, 0.2, tool, True) + (depth, offset, __, info) = PathDeburr.toolDepthAndOffset(2, 0.2, tool, True) self.assertRoughly(2.2, depth) self.assertRoughly(0.5, offset) self.assertFalse(info) @@ -91,12 +91,12 @@ class TestPathDeburr(PathTestUtils.PathTestBase): td = 1.73205 - (depth, offset, info) = PathDeburr.toolDepthAndOffset(1, 0, tool, True) + (depth, offset, __, info) = PathDeburr.toolDepthAndOffset(1, 0, tool, True) self.assertRoughly(td, depth) self.assertRoughly(10, offset) self.assertFalse(info) - (depth, offset, info) = PathDeburr.toolDepthAndOffset(3, 1, tool, True) + (depth, offset, __, info) = PathDeburr.toolDepthAndOffset(3, 1, tool, True) self.assertRoughly(td * 3 + 1, depth) self.assertRoughly(10 + td, offset) self.assertFalse(info) @@ -109,15 +109,15 @@ class TestPathDeburr(PathTestUtils.PathTestBase): self.Diameter = dia tool = FakeEndmill(10) - (depth, offset, info) = PathDeburr.toolDepthAndOffset(1, 0.1, tool, True) + (depth, offset, __, info) = PathDeburr.toolDepthAndOffset(1, 0.1, tool, True) self.assertRoughly(0.1, depth) self.assertRoughly(4, offset) self.assertTrue(info) - (depth, offset, info) = PathDeburr.toolDepthAndOffset(1, 0.1, tool, not info) + (depth, offset, __, info) = PathDeburr.toolDepthAndOffset(1, 0.1, tool, not info) self.assertRoughly(0.1, depth) self.assertRoughly(4, offset) self.assertTrue(info) - (depth, offset, info) = PathDeburr.toolDepthAndOffset(1, 0.1, tool, not info) + (depth, offset, __, info) = PathDeburr.toolDepthAndOffset(1, 0.1, tool, not info) self.assertRoughly(0.1, depth) self.assertRoughly(4, offset) self.assertTrue(info) @@ -131,15 +131,15 @@ class TestPathDeburr(PathTestUtils.PathTestBase): self.CuttingEdgeAngle = angle tool = FakePointyBit(10, 90) - (depth, offset, info) = PathDeburr.toolDepthAndOffset(1, 0.1, tool, True) + (depth, offset, __, info) = PathDeburr.toolDepthAndOffset(1, 0.1, tool, True) self.assertRoughly(1.1, depth) self.assertRoughly(0.1, offset) self.assertTrue(info) - (depth, offset, info) = PathDeburr.toolDepthAndOffset(1, 0.1, tool, not info) + (depth, offset, __, info) = PathDeburr.toolDepthAndOffset(1, 0.1, tool, not info) self.assertRoughly(1.1, depth) self.assertRoughly(0.1, offset) self.assertTrue(info) - (depth, offset, info) = PathDeburr.toolDepthAndOffset(1, 0.1, tool, not info) + (depth, offset, __, info) = PathDeburr.toolDepthAndOffset(1, 0.1, tool, not info) self.assertRoughly(1.1, depth) self.assertRoughly(0.1, offset) self.assertTrue(info)