Added basic chamfer test case for offsetting a wire.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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)
|
||||
|
||||
113
src/Mod/Path/PathTests/TestPathChamfer.py
Normal file
113
src/Mod/Path/PathTests/TestPathChamfer.py
Normal 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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
BIN
src/Mod/Path/PathTests/test_chamfer.fcstd
Normal file
BIN
src/Mod/Path/PathTests/test_chamfer.fcstd
Normal file
Binary file not shown.
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user