Merge pull request #4446 from Schildkroet/deburr_improve

[0.20][PATH] Deburr improve
This commit is contained in:
sliptonic
2021-07-20 12:29:31 -05:00
committed by GitHub
7 changed files with 176 additions and 47 deletions

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * Copyright (c) 2018 sliptonic <shopinthewoods@gmail.com> *
# * 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

View File

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

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * Copyright (c) 2017 LTS <SammelLothar@gmx.de> 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):

View File

@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * Copyright (c) 2016 sliptonic <shopinthewoods@gmail.com> *
# * 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))

View File

@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * Copyright (c) 2018 sliptonic <shopinthewoods@gmail.com> *
# * 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

View File

@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * Copyright (c) 2015 Dan Falck <ddfalck@gmail.com> *
# * 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

View File

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