Added basic chamfer test case for offsetting a wire.

This commit is contained in:
Markus Lampert
2018-06-22 17:58:47 -07:00
parent cfd2c5ad96
commit e1af026933
8 changed files with 223 additions and 42 deletions

View File

@@ -116,10 +116,8 @@ SET(PathScripts_post_SRCS
SET(PathTests_SRCS
PathTests/__init__.py
PathTests/boxtest.fcstd
PathTests/PathTestUtils.py
PathTests/test_centroid_00.ngc
PathTests/test_linuxcnc_00.ngc
PathTests/TestPathChamfer.py
PathTests/TestPathCore.py
PathTests/TestPathDepthParams.py
PathTests/TestPathDressupDogbone.py
@@ -133,6 +131,10 @@ SET(PathTests_SRCS
PathTests/TestPathToolController.py
PathTests/TestPathTooltable.py
PathTests/TestPathUtil.py
PathTests/boxtest.fcstd
PathTests/test_centroid_00.ngc
PathTests/test_chamfer.fcstd
PathTests/test_linuxcnc_00.ngc
)
SET(PathImages_Ops

View File

@@ -26,11 +26,11 @@ import FreeCAD
import Part
import Path
import PathScripts.PathEngraveBase as PathEngraveBase
import PathScripts.PathGeom as PathGeom
import PathScripts.PathLog as PathLog
import PathScripts.PathOp as PathOp
import math
from PathScripts.PathGeom import PathGeom
from PySide import QtCore
if False:
@@ -43,7 +43,32 @@ else:
def translate(context, text, disambig=None):
return QtCore.QCoreApplication.translate(context, text, disambig)
def removeInsideEdges(edges, shape, offset):
def orientWire(w, forward=True):
face = Part.Face(w)
cw = 0 < face.Surface.Axis.z
if forward != cw:
PathLog.track('orientWire - needs flipping')
return PathGeom.flipWire(w)
PathLog.track('orientWire - ok')
return w
def isCircleAt(edge, center):
if Circel == type(edge.Curve) or ArcOfCircle == type(edge.Curve):
return PathGeom.pointsCoincide(edge.Curve.Center, center)
return False
def offsetWire(wire, base, offset, forward):
PathLog.track('offsetWire')
w = wire.makeOffset2D(offset)
if wire.isClosed():
if not base.isInside(w.Edges[0].Vertexes[0].Point, offset/2, True):
PathLog.track('closed - outside')
return orientWire(w, forward)
PathLog.track('closed - inside')
w = wire.makeOffset2D(-offset)
return orientWire(w, forward)
# An edge is considered to be inside of shape if the mid point is inside
# Of the remaining edges we take the longest wire to be the engraving side
# Looking for a circle with the start vertex as center marks and end
@@ -52,48 +77,66 @@ def removeInsideEdges(edges, shape, offset):
# this is to also include edges which might partially be inside shape
# if they need to be discarded, split, that should happen in a post process
# Depending on the Axis of the circle, and which side remains we know if the wire needs to be flipped
# find edges that are not inside the shape
def isInside(edge):
if shape.Shape.isInside(edge.Vertexes[0].Point, offset/2, True) and shape.Shape.isInside(edge.Vertexes[-1].Point, offset/2, True):
return True
return False
remaining = [e for e in edges if not isInside(e)]
# of the ones remaining, the first and the last are the end offsets
allFirst = [e.firstVertex().Point for e in remaining]
allLast = [e.lastVertex().Point for e in remaining]
first = [f for f in allFirst if not f in allLast][0]
last = [l for l in allLast if not l in allFirst][0]
#return [e for e in remaining if not PathGeom.pointsCoincide(e.firstVertex().Point, first) and not PathGeom.pointsCoincide(e.lastVertex().Point, last)]
return remaining
outside = [e for e in edges if not isInside(e)]
# discard all edges that are not part of the longest wire
longestWire = None
for w in [Part.Wire(el) for el in Part.sortEdges(outside)]:
if not longestWire or longestWire.Length < w.Length:
longestWire = w
def orientWireForClimbMilling(w):
face = Part.Face(w)
cw = 'Forward' == obj.ToolController.SpindleDir
wcw = 0 < face.Surface.Axis.z
if cw != wcw:
PathLog.track('flip wire')
# This works because Path creation will flip the edges accordingly
return Part.Wire([e for e in reversed(w.Edges)])
PathLog.track('no flip', cw, wcw)
return w
# find the start and end point
start = wire.Vertexes[0].Point
end = wire.Vertexes[-1].Point
def offsetWire(obj, wire, base, offset):
PathLog.track(obj.Label)
collectLeft = False
collectRight = False
leftSideEdges = []
rightSideEdges = []
w = wire.makeOffset2D(offset)
if wire.isClosed():
if not base.Shape.isInside(w.Edges[0].Vertexes[0].Point, offset/2, True):
return orientWireForClimbMilling(w)
w = wire.makeOffset2D(-offset)
return orientWireForClimbMilling(w)
for e in (w.Edges + w.Edges):
if isCircleAt(e, start):
if PathGeom.pointsCoincide(e.Curve.Axis, FreeCAD.Vector(0, 0, 1)):
if not collectLeft and leftSideEdges:
break
collectLeft = True
collectRight = False
else:
if not collectRight and rightSideEdges:
break
collectLeft = False
collectRight = True
elif isCircleAt(e, end):
if PathGeom.pointsCoincide(e.Curve.Axis, FreeCAD.Vector(0, 0, 1)):
if not collectRight and rightSideEdges:
break
collectLeft = False
collectRight = True
else:
if not collectLeft and leftSideEdges:
break
collectLeft = True
collectRight = False
elif collectLeft:
leftSideEdges.append(e)
elif collectRight:
rightSideEdges.append(e)
edges = removeInsideEdges(w.Edges, base, offset)
if not edges:
w = wire.makeOffset2D(-offset)
edges = removeInsideEdges(w.Edges, base, offset)
points = []
for e in edges:
points
# determine the start point
edges = leftSideEdges
for e in longestWire.Edges:
for e0 in rightSideEdges:
if PathGeom.edgesMatch(e, e0):
if forward:
edges = [PathGeom.flipEdge(edge) for edge in rightSideEdges]
return Part.Wire(edges)
if not forward:
edges = [PathGeom.flipEdge(edge) for edge in rightSideEdges]
return Part.Wire(edges)
class ObjectChamfer(PathEngraveBase.ObjectOp):
@@ -138,7 +181,7 @@ class ObjectChamfer(PathEngraveBase.ObjectOp):
basewires.append(Part.Wire(edgelist))
for w in self.adjustWirePlacement(obj, base, basewires):
wires.append(offsetWire(obj, w, base, offset))
wires.append(offsetWire(obj, w, base.Shape, offset))
self.wires = wires
self.buildpathocc(obj, wires, [depth], True)

View File

@@ -26,12 +26,12 @@ import DraftGeomUtils
import FreeCAD
import Part
import Path
import PathScripts.PathGeom as PathGeom
import PathScripts.PathLog as PathLog
import PathScripts.PathOp as PathOp
import PathScripts.PathUtils as PathUtils
import copy
from PathScripts.PathGeom import PathGeom
from PySide import QtCore
__doc__ = "Base class for all ops in the engrave family."

View File

@@ -465,7 +465,17 @@ def flipEdge(edge):
elif Part.Line == type(edge.Curve) or Part.LineSegment == type(edge.Curve):
return Part.Edge(Part.LineSegment(edge.Vertexes[-1].Point, edge.Vertexes[0].Point))
elif Part.Circle == type(edge.Curve):
return Part.makeCircle(edge.Curve.Radius, edge.Curve.Center, -edge.Curve.Axis, -math.degrees(edge.LastParameter), -math.degrees(edge.FirstParameter))
r = edge.Curve.Radius
c = edge.Curve.Center
d = edge.Curve.Axis
a = math.degrees(edge.Curve.AngleXU)
f = math.degrees(edge.FirstParameter)
l = math.degrees(edge.LastParameter)
if 0 > d.z:
a = a + 180
PathLog.track(r, c, d, a, f, l)
arc = Part.makeCircle(r, c, -d, -l-a, -f-a)
return arc
elif Part.BSplineCurve == type(edge.Curve):
spline = edge.Curve
@@ -491,3 +501,8 @@ def flipEdge(edge):
return Part.Edge(flipped)
def flipWire(wire):
'''Flip the entire wire and all its edges so it is being processed the other way around.'''
edges = [flipEdge(e) for e in wire.Edges]
edges.reverse()
return Part.Wire(edges)

View File

@@ -0,0 +1,113 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * *
# * Copyright (c) 2018 sliptonic <shopinthewoods@gmail.com> *
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU Lesser General Public License (LGPL) *
# * as published by the Free Software Foundation; either version 2 of *
# * the License, or (at your option) any later version. *
# * for detail see the LICENCE text file. *
# * *
# * This program is distributed in the hope that it will be useful, *
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
# * GNU Library General Public License for more details. *
# * *
# * You should have received a copy of the GNU Library General Public *
# * License along with this program; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************
import FreeCAD
import Part
import Path
import PathScripts.PathChamfer as PathChamfer
import PathScripts.PathGeom as PathGeom
import PathScripts.PathLog as PathLog
import PathTests.PathTestUtils as PathTestUtils
from FreeCAD import Vector
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
#PathLog.trackModule(PathLog.thisModule())
def getWire(obj):
return obj.Tool.Tip.Profile[0].Shape.Wires[0]
def getPositiveShape(obj):
return obj.Tool.Shape
def getNegativeShape(obj):
return obj.Shape
class TestPathChamfer(PathTestUtils.PathTestBase):
def setUp(self):
self.doc = FreeCAD.open(FreeCAD.getHomePath() + 'Mod/Path/PathTests/test_chamfer.fcstd')
self.circle = self.doc.getObjectsByLabel('circle-cut')[0]
self.square = self.doc.getObjectsByLabel('square-cut')[0]
self.triangle = self.doc.getObjectsByLabel('triangle-cut')[0]
self.shape = self.doc.getObjectsByLabel('shape-cut')[0]
def tearDown(self):
FreeCAD.closeDocument("test_chamfer")
def test01(self):
'''Check offsetting a cylinder.'''
obj = self.circle
wire = PathChamfer.offsetWire(getWire(obj), getPositiveShape(obj), 3, True)
self.assertEqual(1, len(wire.Edges))
edge = wire.Edges[0]
self.assertCoincide(Vector(), edge.Curve.Center)
self.assertCoincide(Vector(0, 0, -1), edge.Curve.Axis)
self.assertRoughly(33, edge.Curve.Radius)
# the other way around everything's the same except the axis is negative
wire = PathChamfer.offsetWire(getWire(obj), getPositiveShape(obj), 3, False)
self.assertEqual(1, len(wire.Edges))
edge = wire.Edges[0]
self.assertCoincide(Vector(), edge.Curve.Center)
self.assertCoincide(Vector(0, 0, +1), edge.Curve.Axis)
self.assertRoughly(33, edge.Curve.Radius)
def test02(self):
'''Check offsetting a box.'''
obj = self.square
wire = PathChamfer.offsetWire(getWire(obj), getPositiveShape(obj), 3, True)
self.assertEqual(8, len(wire.Edges))
self.assertEqual(4, len([e for e in wire.Edges if Part.Line == type(e.Curve)]))
self.assertEqual(4, len([e for e in wire.Edges if Part.Circle == type(e.Curve)]))
for e in wire.Edges:
if Part.Line == type(e.Curve):
if PathGeom.isRoughly(e.Vertexes[0].Point.x, e.Vertexes[1].Point.x):
self.assertEqual(40, e.Length)
if PathGeom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y):
self.assertEqual(60, e.Length)
if Part.Circle == type(e.Curve):
self.assertRoughly(3, e.Curve.Radius)
# As it turns out the arcs are oriented the wrong way
self.assertCoincide(Vector(0, 0, +1), e.Curve.Axis)
wire = PathChamfer.offsetWire(getWire(obj), getPositiveShape(obj), 3, False)
self.assertEqual(8, len(wire.Edges))
self.assertEqual(4, len([e for e in wire.Edges if Part.Line == type(e.Curve)]))
self.assertEqual(4, len([e for e in wire.Edges if Part.Circle == type(e.Curve)]))
for e in wire.Edges:
if Part.Line == type(e.Curve):
if PathGeom.isRoughly(e.Vertexes[0].Point.x, e.Vertexes[1].Point.x):
self.assertEqual(40, e.Length)
if PathGeom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y):
self.assertEqual(60, e.Length)
if Part.Circle == type(e.Curve):
self.assertRoughly(3, e.Curve.Radius)
# As it turns out the arcs are oriented the wrong way
self.assertCoincide(Vector(0, 0, -1), e.Curve.Axis)

View File

@@ -427,6 +427,12 @@ class TestPathGeom(PathTestBase):
edge = Part.makeCircle(3, Vector(1, 3, 2), Vector(0, 0, -1), 300, 340)
self.assertEdgeShapesMatch(edge, PathGeom.flipEdge(edge))
def test74(self):
'''Flip a rotated arc'''
# oh yes ...
edge = Part.makeCircle(3, Vector(1, 3, 2), Vector(0, 0, 1), 45, 90)
edge.rotate(edge.Curve.Center, Vector(0, 0, 1), -90)
self.assertEdgeShapesMatch(edge, PathGeom.flipEdge(edge))
def test75(self):
'''Flip a b-spline'''
@@ -440,3 +446,4 @@ class TestPathGeom(PathTestBase):

Binary file not shown.

View File

@@ -37,4 +37,5 @@ from PathTests.TestPathTool import TestPathTool
from PathTests.TestPathTooltable import TestPathTooltable
from PathTests.TestPathToolController import TestPathToolController
from PathTests.TestPathSetupSheet import TestPathSetupSheet
from PathTests.TestPathChamfer import TestPathChamfer