930 lines
37 KiB
Python
930 lines
37 KiB
Python
# -*- 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 Path.Op.Util as PathOpUtil
|
|
import CAMTests.PathTestUtils as PathTestUtils
|
|
import math
|
|
|
|
from FreeCAD import Vector
|
|
|
|
Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule())
|
|
# Path.Log.trackModule(Path.Log.thisModule())
|
|
|
|
DOC = FreeCAD.getHomePath() + "Mod/CAM/CAMTests/test_geomop.fcstd"
|
|
|
|
|
|
def getWire(obj, nr=0):
|
|
return obj.Tip.Profile[0].Shape.Wires[nr]
|
|
|
|
|
|
def getWireInside(obj):
|
|
w1 = getWire(obj, 0)
|
|
w2 = getWire(obj, 1)
|
|
if w2.BoundBox.isInside(w1.BoundBox):
|
|
return w1
|
|
return w2
|
|
|
|
|
|
def getWireOutside(obj):
|
|
w1 = getWire(obj, 0)
|
|
w2 = getWire(obj, 1)
|
|
if w2.BoundBox.isInside(w1.BoundBox):
|
|
return w2
|
|
return w1
|
|
|
|
|
|
def getPositiveShape(obj):
|
|
return obj.Tool.Shape
|
|
|
|
|
|
def getNegativeShape(obj):
|
|
return obj.Shape
|
|
|
|
|
|
def makeWire(pts):
|
|
edges = []
|
|
first = pts[0]
|
|
last = pts[0]
|
|
for p in pts[1:]:
|
|
edges.append(Part.Edge(Part.LineSegment(last, p)))
|
|
last = p
|
|
edges.append(Part.Edge(Part.LineSegment(last, first)))
|
|
return Part.Wire(edges)
|
|
|
|
|
|
def wireMarkers(wire):
|
|
pts = [wire.Edges[0].valueAt(wire.Edges[0].FirstParameter)]
|
|
for edge in wire.Edges:
|
|
pts.append(edge.valueAt(edge.LastParameter))
|
|
return pts
|
|
|
|
|
|
class TestPathOpUtil(PathTestUtils.PathTestBase):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
FreeCAD.ConfigSet("SuppressRecomputeRequiredDialog", "True")
|
|
cls.doc = FreeCAD.openDocument(DOC)
|
|
FreeCAD.ConfigSet("SuppressRecomputeRequiredDialog", "")
|
|
|
|
@classmethod
|
|
def tearDownClass(cls):
|
|
FreeCAD.closeDocument(cls.doc.Name)
|
|
|
|
def test00(self):
|
|
"""Verify isWireClockwise for polygon wires."""
|
|
pa = Vector(1, 1, 0)
|
|
pb = Vector(1, 5, 0)
|
|
pc = Vector(5, 5, 0)
|
|
pd = Vector(5, 1, 0)
|
|
|
|
self.assertTrue(PathOpUtil.isWireClockwise(makeWire([pa, pb, pc, pd])))
|
|
self.assertFalse(PathOpUtil.isWireClockwise(makeWire([pa, pd, pc, pb])))
|
|
|
|
def test01(self):
|
|
"""Verify isWireClockwise for single edge circle wires."""
|
|
self.assertTrue(
|
|
PathOpUtil.isWireClockwise(Part.makeCircle(5, Vector(1, 2, 3), Vector(0, 0, -1)))
|
|
)
|
|
self.assertFalse(
|
|
PathOpUtil.isWireClockwise(Part.makeCircle(5, Vector(1, 2, 3), Vector(0, 0, +1)))
|
|
)
|
|
|
|
def test02(self):
|
|
"""Verify isWireClockwise for two half circle wires."""
|
|
e0 = Part.makeCircle(5, Vector(1, 2, 3), Vector(0, 0, -1), 0, 180)
|
|
e1 = Part.makeCircle(5, Vector(1, 2, 3), Vector(0, 0, -1), 180, 360)
|
|
self.assertTrue(PathOpUtil.isWireClockwise(Part.Wire([e0, e1])))
|
|
|
|
e0 = Part.makeCircle(5, Vector(1, 2, 3), Vector(0, 0, +1), 0, 180)
|
|
e1 = Part.makeCircle(5, Vector(1, 2, 3), Vector(0, 0, +1), 180, 360)
|
|
self.assertFalse(PathOpUtil.isWireClockwise(Part.Wire([e0, e1])))
|
|
|
|
def test03(self):
|
|
"""Verify isWireClockwise for two edge wires with an arc."""
|
|
e0 = Part.makeCircle(5, Vector(1, 2, 3), Vector(0, 0, -1), 0, 180)
|
|
e2 = Part.makeLine(e0.valueAt(e0.LastParameter), e0.valueAt(e0.FirstParameter))
|
|
self.assertTrue(PathOpUtil.isWireClockwise(Part.Wire([e0, e2])))
|
|
|
|
e0 = Part.makeCircle(5, Vector(1, 2, 3), Vector(0, 0, +1), 0, 180)
|
|
e2 = Part.makeLine(e0.valueAt(e0.LastParameter), e0.valueAt(e0.FirstParameter))
|
|
self.assertFalse(PathOpUtil.isWireClockwise(Part.Wire([e0, e2])))
|
|
|
|
def test04(self):
|
|
"""Verify isWireClockwise for unoriented wires."""
|
|
e0 = Part.makeCircle(5, Vector(1, 2, 3), Vector(0, 0, -1), 0, 180)
|
|
e3 = Part.makeLine(e0.valueAt(e0.FirstParameter), e0.valueAt(e0.LastParameter))
|
|
self.assertTrue(PathOpUtil.isWireClockwise(Part.Wire([e0, e3])))
|
|
|
|
e0 = Part.makeCircle(5, Vector(1, 2, 3), Vector(0, 0, +1), 0, 180)
|
|
e3 = Part.makeLine(e0.valueAt(e0.FirstParameter), e0.valueAt(e0.LastParameter))
|
|
self.assertFalse(PathOpUtil.isWireClockwise(Part.Wire([e0, e3])))
|
|
|
|
def test11(self):
|
|
"""Check offsetting a circular hole."""
|
|
obj = self.doc.getObjectsByLabel("offset-circle")[0]
|
|
|
|
small = getWireInside(obj)
|
|
self.assertRoughly(10, small.Edges[0].Curve.Radius)
|
|
|
|
wire = PathOpUtil.offsetWire(small, obj.Shape, 3, True)
|
|
self.assertIsNotNone(wire)
|
|
self.assertEqual(1, len(wire.Edges))
|
|
self.assertRoughly(7, wire.Edges[0].Curve.Radius)
|
|
self.assertCoincide(Vector(0, 0, 1), wire.Edges[0].Curve.Axis)
|
|
|
|
wire = PathOpUtil.offsetWire(small, obj.Shape, 9.9, True)
|
|
self.assertIsNotNone(wire)
|
|
self.assertEqual(1, len(wire.Edges))
|
|
self.assertRoughly(0.1, wire.Edges[0].Curve.Radius)
|
|
self.assertCoincide(Vector(0, 0, 1), wire.Edges[0].Curve.Axis)
|
|
|
|
def test12(self):
|
|
"""Check offsetting a circular hole by the radius or more makes the hole vanish."""
|
|
obj = self.doc.getObjectsByLabel("offset-circle")[0]
|
|
|
|
small = getWireInside(obj)
|
|
self.assertRoughly(10, small.Edges[0].Curve.Radius)
|
|
wire = PathOpUtil.offsetWire(small, obj.Shape, 10, True)
|
|
self.assertIsNone(wire)
|
|
|
|
wire = PathOpUtil.offsetWire(small, obj.Shape, 15, True)
|
|
self.assertIsNone(wire)
|
|
|
|
def test13(self):
|
|
"""Check offsetting a cylinder succeeds."""
|
|
obj = self.doc.getObjectsByLabel("offset-circle")[0]
|
|
|
|
big = getWireOutside(obj)
|
|
self.assertRoughly(20, big.Edges[0].Curve.Radius)
|
|
|
|
wire = PathOpUtil.offsetWire(big, obj.Shape, 10, True)
|
|
self.assertIsNotNone(wire)
|
|
self.assertEqual(1, len(wire.Edges))
|
|
self.assertRoughly(30, wire.Edges[0].Curve.Radius)
|
|
self.assertCoincide(Vector(0, 0, -1), wire.Edges[0].Curve.Axis)
|
|
|
|
wire = PathOpUtil.offsetWire(big, obj.Shape, 20, True)
|
|
self.assertIsNotNone(wire)
|
|
self.assertEqual(1, len(wire.Edges))
|
|
self.assertRoughly(40, wire.Edges[0].Curve.Radius)
|
|
self.assertCoincide(Vector(0, 0, -1), wire.Edges[0].Curve.Axis)
|
|
|
|
def test14(self):
|
|
"""Check offsetting a hole with Placement."""
|
|
obj = self.doc.getObjectsByLabel("offset-placement")[0]
|
|
|
|
wires = [
|
|
w
|
|
for w in obj.Shape.Wires
|
|
if 1 == len(w.Edges) and Path.Geom.isRoughly(0, w.Edges[0].Vertexes[0].Point.z)
|
|
]
|
|
self.assertEqual(2, len(wires))
|
|
w = wires[1] if wires[0].BoundBox.isInside(wires[1].BoundBox) else wires[0]
|
|
|
|
self.assertRoughly(10, w.Edges[0].Curve.Radius)
|
|
# make sure there is a placement and I didn't mess up the model
|
|
self.assertFalse(Path.Geom.pointsCoincide(Vector(), w.Edges[0].Placement.Base))
|
|
|
|
wire = PathOpUtil.offsetWire(w, obj.Shape, 2, True)
|
|
self.assertIsNotNone(wire)
|
|
self.assertEqual(1, len(wire.Edges))
|
|
self.assertRoughly(8, wire.Edges[0].Curve.Radius)
|
|
self.assertCoincide(Vector(0, 0, 0), wire.Edges[0].Curve.Center)
|
|
self.assertCoincide(Vector(0, 0, 1), wire.Edges[0].Curve.Axis)
|
|
|
|
def test15(self):
|
|
"""Check offsetting a cylinder with Placement."""
|
|
obj = self.doc.getObjectsByLabel("offset-placement")[0]
|
|
|
|
wires = [
|
|
w
|
|
for w in obj.Shape.Wires
|
|
if 1 == len(w.Edges) and Path.Geom.isRoughly(0, w.Edges[0].Vertexes[0].Point.z)
|
|
]
|
|
self.assertEqual(2, len(wires))
|
|
w = wires[0] if wires[0].BoundBox.isInside(wires[1].BoundBox) else wires[1]
|
|
|
|
self.assertRoughly(20, w.Edges[0].Curve.Radius)
|
|
# make sure there is a placement and I didn't mess up the model
|
|
self.assertFalse(Path.Geom.pointsCoincide(Vector(), w.Edges[0].Placement.Base))
|
|
|
|
wire = PathOpUtil.offsetWire(w, obj.Shape, 2, True)
|
|
self.assertIsNotNone(wire)
|
|
self.assertEqual(1, len(wire.Edges))
|
|
self.assertRoughly(22, wire.Edges[0].Curve.Radius)
|
|
self.assertCoincide(Vector(0, 0, 0), wire.Edges[0].Curve.Center)
|
|
self.assertCoincide(Vector(0, 0, -1), wire.Edges[0].Curve.Axis)
|
|
|
|
def test20(self):
|
|
"""Check offsetting hole wire succeeds."""
|
|
obj = self.doc.getObjectsByLabel("offset-edge")[0]
|
|
|
|
small = getWireInside(obj)
|
|
# sanity check
|
|
y = 10
|
|
x = 10 * math.cos(math.pi / 6)
|
|
self.assertLines(
|
|
small.Edges,
|
|
False,
|
|
[
|
|
Vector(0, y, 0),
|
|
Vector(-x, -y / 2, 0),
|
|
Vector(x, -y / 2, 0),
|
|
Vector(0, y, 0),
|
|
],
|
|
)
|
|
|
|
wire = PathOpUtil.offsetWire(small, obj.Shape, 3, True)
|
|
self.assertIsNotNone(wire)
|
|
self.assertEqual(3, len(wire.Edges))
|
|
self.assertTrue(wire.isClosed())
|
|
# for holes processing "forward" means CCW
|
|
self.assertFalse(PathOpUtil.isWireClockwise(wire))
|
|
y = 4 # offset works in both directions
|
|
x = 4 * math.cos(math.pi / 6)
|
|
self.assertLines(
|
|
wire.Edges,
|
|
False,
|
|
[Vector(0, 4, 0), Vector(-x, -2, 0), Vector(x, -2, 0), Vector(0, 4, 0)],
|
|
)
|
|
|
|
def test21(self):
|
|
"""Check offsetting hole wire for more than it's size makes hole vanish."""
|
|
obj = self.doc.getObjectsByLabel("offset-edge")[0]
|
|
|
|
small = getWireInside(obj)
|
|
# sanity check
|
|
y = 10
|
|
x = 10 * math.cos(math.pi / 6)
|
|
self.assertLines(
|
|
small.Edges,
|
|
False,
|
|
[
|
|
Vector(0, y, 0),
|
|
Vector(-x, -y / 2, 0),
|
|
Vector(x, -y / 2, 0),
|
|
Vector(0, y, 0),
|
|
],
|
|
)
|
|
wire = PathOpUtil.offsetWire(small, obj.Shape, 5, True)
|
|
self.assertIsNone(wire)
|
|
|
|
def test22(self):
|
|
"""Check offsetting a body wire succeeds."""
|
|
obj = self.doc.getObjectsByLabel("offset-edge")[0]
|
|
|
|
big = getWireOutside(obj)
|
|
# sanity check
|
|
y = 20
|
|
x = 20 * math.cos(math.pi / 6)
|
|
self.assertLines(
|
|
big.Edges,
|
|
False,
|
|
[
|
|
Vector(0, y, 0),
|
|
Vector(-x, -y / 2, 0),
|
|
Vector(x, -y / 2, 0),
|
|
Vector(0, y, 0),
|
|
],
|
|
)
|
|
|
|
wire = PathOpUtil.offsetWire(big, obj.Shape, 5, True)
|
|
self.assertIsNotNone(wire)
|
|
self.assertEqual(6, len(wire.Edges))
|
|
lastAngle = None
|
|
refAngle = math.pi / 3
|
|
for e in wire.Edges:
|
|
if Part.Circle == type(e.Curve):
|
|
self.assertRoughly(5, e.Curve.Radius)
|
|
self.assertCoincide(Vector(0, 0, -1), e.Curve.Axis)
|
|
else:
|
|
self.assertRoughly(34.641, e.Length, 0.001)
|
|
begin = e.Vertexes[0].Point
|
|
end = e.Vertexes[1].Point
|
|
v = end - begin
|
|
angle = Path.Geom.getAngle(v)
|
|
if Path.Geom.isRoughly(0, angle) or Path.Geom.isRoughly(math.pi, math.fabs(angle)):
|
|
if lastAngle:
|
|
self.assertRoughly(-refAngle, lastAngle)
|
|
elif Path.Geom.isRoughly(+refAngle, angle):
|
|
if lastAngle:
|
|
self.assertRoughly(math.pi, math.fabs(lastAngle))
|
|
elif Path.Geom.isRoughly(-refAngle, angle):
|
|
if lastAngle:
|
|
self.assertRoughly(+refAngle, lastAngle)
|
|
else:
|
|
self.assertIsNone("%s: angle=%s" % (type(e.Curve), angle))
|
|
lastAngle = angle
|
|
self.assertTrue(PathOpUtil.isWireClockwise(wire))
|
|
|
|
def test31(self):
|
|
"""Check offsetting a cylinder."""
|
|
obj = self.doc.getObjectsByLabel("circle-cut")[0]
|
|
|
|
wire = PathOpUtil.offsetWire(getWire(obj.Tool), 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 = PathOpUtil.offsetWire(getWire(obj.Tool), 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 test32(self):
|
|
"""Check offsetting a box."""
|
|
obj = self.doc.getObjectsByLabel("square-cut")[0]
|
|
|
|
wire = PathOpUtil.offsetWire(getWire(obj.Tool), 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 Path.Geom.isRoughly(e.Vertexes[0].Point.x, e.Vertexes[1].Point.x):
|
|
self.assertEqual(40, e.Length)
|
|
if Path.Geom.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)
|
|
self.assertCoincide(Vector(0, 0, -1), e.Curve.Axis)
|
|
self.assertTrue(PathOpUtil.isWireClockwise(wire))
|
|
|
|
# change offset orientation
|
|
wire = PathOpUtil.offsetWire(getWire(obj.Tool), 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 Path.Geom.isRoughly(e.Vertexes[0].Point.x, e.Vertexes[1].Point.x):
|
|
self.assertEqual(40, e.Length)
|
|
if Path.Geom.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)
|
|
self.assertCoincide(Vector(0, 0, +1), e.Curve.Axis)
|
|
self.assertFalse(PathOpUtil.isWireClockwise(wire))
|
|
|
|
def test33(self):
|
|
"""Check offsetting a triangle."""
|
|
obj = self.doc.getObjectsByLabel("triangle-cut")[0]
|
|
|
|
wire = PathOpUtil.offsetWire(getWire(obj.Tool), getPositiveShape(obj), 3, True)
|
|
self.assertEqual(6, len(wire.Edges))
|
|
self.assertEqual(3, len([e for e in wire.Edges if Part.Line == type(e.Curve)]))
|
|
self.assertEqual(3, len([e for e in wire.Edges if Part.Circle == type(e.Curve)]))
|
|
length = 60 * math.sin(math.radians(60))
|
|
for e in wire.Edges:
|
|
if Part.Line == type(e.Curve):
|
|
self.assertRoughly(length, e.Length)
|
|
if Part.Circle == type(e.Curve):
|
|
self.assertRoughly(3, e.Curve.Radius)
|
|
self.assertCoincide(Vector(0, 0, -1), e.Curve.Axis)
|
|
|
|
# change offset orientation
|
|
wire = PathOpUtil.offsetWire(getWire(obj.Tool), getPositiveShape(obj), 3, False)
|
|
self.assertEqual(6, len(wire.Edges))
|
|
self.assertEqual(3, len([e for e in wire.Edges if Part.Line == type(e.Curve)]))
|
|
self.assertEqual(3, len([e for e in wire.Edges if Part.Circle == type(e.Curve)]))
|
|
for e in wire.Edges:
|
|
if Part.Line == type(e.Curve):
|
|
self.assertRoughly(length, e.Length)
|
|
if Part.Circle == type(e.Curve):
|
|
self.assertRoughly(3, e.Curve.Radius)
|
|
self.assertCoincide(Vector(0, 0, +1), e.Curve.Axis)
|
|
|
|
def test34(self):
|
|
"""Check offsetting a shape."""
|
|
obj = self.doc.getObjectsByLabel("shape-cut")[0]
|
|
|
|
wire = PathOpUtil.offsetWire(getWire(obj.Tool), getPositiveShape(obj), 3, True)
|
|
self.assertEqual(6, len(wire.Edges))
|
|
self.assertEqual(3, len([e for e in wire.Edges if Part.Line == type(e.Curve)]))
|
|
self.assertEqual(3, len([e for e in wire.Edges if Part.Circle == type(e.Curve)]))
|
|
length = 40
|
|
radius = 20 + 3
|
|
for e in wire.Edges:
|
|
if Part.Line == type(e.Curve):
|
|
self.assertRoughly(length, e.Length)
|
|
if Part.Circle == type(e.Curve):
|
|
self.assertRoughly(radius, e.Curve.Radius)
|
|
self.assertCoincide(Vector(0, 0, -1), e.Curve.Axis)
|
|
|
|
# change offset orientation
|
|
wire = PathOpUtil.offsetWire(getWire(obj.Tool), getPositiveShape(obj), 3, False)
|
|
self.assertEqual(6, len(wire.Edges))
|
|
self.assertEqual(3, len([e for e in wire.Edges if Part.Line == type(e.Curve)]))
|
|
self.assertEqual(3, len([e for e in wire.Edges if Part.Circle == type(e.Curve)]))
|
|
for e in wire.Edges:
|
|
if Part.Line == type(e.Curve):
|
|
self.assertRoughly(length, e.Length)
|
|
if Part.Circle == type(e.Curve):
|
|
self.assertRoughly(radius, e.Curve.Radius)
|
|
self.assertCoincide(Vector(0, 0, +1), e.Curve.Axis)
|
|
|
|
def test35(self):
|
|
"""Check offsetting a cylindrical hole."""
|
|
obj = self.doc.getObjectsByLabel("circle-cut")[0]
|
|
|
|
wire = PathOpUtil.offsetWire(getWire(obj.Tool), getNegativeShape(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(27, edge.Curve.Radius)
|
|
|
|
# the other way around everything's the same except the axis is negative
|
|
wire = PathOpUtil.offsetWire(getWire(obj.Tool), getNegativeShape(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(27, edge.Curve.Radius)
|
|
|
|
def test36(self):
|
|
"""Check offsetting a square hole."""
|
|
obj = self.doc.getObjectsByLabel("square-cut")[0]
|
|
|
|
wire = PathOpUtil.offsetWire(getWire(obj.Tool), getNegativeShape(obj), 3, True)
|
|
self.assertEqual(4, len(wire.Edges))
|
|
self.assertEqual(4, len([e for e in wire.Edges if Part.Line == type(e.Curve)]))
|
|
for e in wire.Edges:
|
|
if Path.Geom.isRoughly(e.Vertexes[0].Point.x, e.Vertexes[1].Point.x):
|
|
self.assertRoughly(34, e.Length)
|
|
if Path.Geom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y):
|
|
self.assertRoughly(54, e.Length)
|
|
self.assertFalse(PathOpUtil.isWireClockwise(wire))
|
|
|
|
# change offset orientation
|
|
wire = PathOpUtil.offsetWire(getWire(obj.Tool), getNegativeShape(obj), 3, False)
|
|
self.assertEqual(4, len(wire.Edges))
|
|
self.assertEqual(4, len([e for e in wire.Edges if Part.Line == type(e.Curve)]))
|
|
for e in wire.Edges:
|
|
if Path.Geom.isRoughly(e.Vertexes[0].Point.x, e.Vertexes[1].Point.x):
|
|
self.assertRoughly(34, e.Length)
|
|
if Path.Geom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y):
|
|
self.assertRoughly(54, e.Length)
|
|
self.assertTrue(PathOpUtil.isWireClockwise(wire))
|
|
|
|
def test37(self):
|
|
"""Check offsetting a triangular holee."""
|
|
obj = self.doc.getObjectsByLabel("triangle-cut")[0]
|
|
|
|
wire = PathOpUtil.offsetWire(getWire(obj.Tool), getNegativeShape(obj), 3, True)
|
|
self.assertEqual(3, len(wire.Edges))
|
|
self.assertEqual(3, len([e for e in wire.Edges if Part.Line == type(e.Curve)]))
|
|
length = 48 * math.sin(math.radians(60))
|
|
for e in wire.Edges:
|
|
self.assertRoughly(length, e.Length)
|
|
self.assertFalse(PathOpUtil.isWireClockwise(wire))
|
|
|
|
# change offset orientation
|
|
wire = PathOpUtil.offsetWire(getWire(obj.Tool), getNegativeShape(obj), 3, False)
|
|
self.assertEqual(3, len(wire.Edges))
|
|
self.assertEqual(3, len([e for e in wire.Edges if Part.Line == type(e.Curve)]))
|
|
for e in wire.Edges:
|
|
self.assertRoughly(length, e.Length)
|
|
self.assertTrue(PathOpUtil.isWireClockwise(wire))
|
|
|
|
def test38(self):
|
|
"""Check offsetting a shape hole."""
|
|
obj = self.doc.getObjectsByLabel("shape-cut")[0]
|
|
|
|
wire = PathOpUtil.offsetWire(getWire(obj.Tool), getNegativeShape(obj), 3, True)
|
|
self.assertEqual(6, len(wire.Edges))
|
|
self.assertEqual(3, len([e for e in wire.Edges if Part.Line == type(e.Curve)]))
|
|
self.assertEqual(3, len([e for e in wire.Edges if Part.Circle == type(e.Curve)]))
|
|
length = 40
|
|
radius = 20 - 3
|
|
for e in wire.Edges:
|
|
if Part.Line == type(e.Curve):
|
|
self.assertRoughly(length, e.Length)
|
|
if Part.Circle == type(e.Curve):
|
|
self.assertRoughly(radius, e.Curve.Radius)
|
|
self.assertCoincide(Vector(0, 0, +1), e.Curve.Axis)
|
|
|
|
# change offset orientation
|
|
wire = PathOpUtil.offsetWire(getWire(obj.Tool), getNegativeShape(obj), 3, False)
|
|
self.assertEqual(6, len(wire.Edges))
|
|
self.assertEqual(3, len([e for e in wire.Edges if Part.Line == type(e.Curve)]))
|
|
self.assertEqual(3, len([e for e in wire.Edges if Part.Circle == type(e.Curve)]))
|
|
for e in wire.Edges:
|
|
if Part.Line == type(e.Curve):
|
|
self.assertRoughly(length, e.Length)
|
|
if Part.Circle == type(e.Curve):
|
|
self.assertRoughly(radius, e.Curve.Radius)
|
|
self.assertCoincide(Vector(0, 0, -1), e.Curve.Axis)
|
|
|
|
def test40(self):
|
|
"""Check offsetting a single outside edge forward."""
|
|
obj = self.doc.getObjectsByLabel("offset-edge")[0]
|
|
|
|
w = getWireOutside(obj)
|
|
length = 40 * math.cos(math.pi / 6)
|
|
for e in w.Edges:
|
|
self.assertRoughly(length, e.Length)
|
|
|
|
# let's offset the horizontal edge for starters
|
|
hEdges = [
|
|
e for e in w.Edges if Path.Geom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y)
|
|
]
|
|
|
|
x = length / 2
|
|
y = -10
|
|
self.assertEqual(1, len(hEdges))
|
|
edge = hEdges[0]
|
|
|
|
self.assertCoincide(Vector(-x, y, 0), edge.Vertexes[0].Point)
|
|
self.assertCoincide(Vector(+x, y, 0), edge.Vertexes[1].Point)
|
|
|
|
wire = PathOpUtil.offsetWire(Part.Wire([edge]), obj.Shape, 5, True)
|
|
self.assertEqual(1, len(wire.Edges))
|
|
|
|
y = y - 5
|
|
self.assertCoincide(Vector(+x, y, 0), wire.Edges[0].Vertexes[0].Point)
|
|
self.assertCoincide(Vector(-x, y, 0), wire.Edges[0].Vertexes[1].Point)
|
|
|
|
# make sure we get the same result even if the edge is oriented the other way
|
|
edge = Path.Geom.flipEdge(edge)
|
|
wire = PathOpUtil.offsetWire(Part.Wire([edge]), obj.Shape, 5, True)
|
|
self.assertEqual(1, len(wire.Edges))
|
|
|
|
self.assertCoincide(Vector(+x, y, 0), wire.Edges[0].Vertexes[0].Point)
|
|
self.assertCoincide(Vector(-x, y, 0), wire.Edges[0].Vertexes[1].Point)
|
|
|
|
def test41(self):
|
|
"""Check offsetting a single outside edge not forward."""
|
|
obj = self.doc.getObjectsByLabel("offset-edge")[0]
|
|
|
|
w = getWireOutside(obj)
|
|
length = 40 * math.cos(math.pi / 6)
|
|
for e in w.Edges:
|
|
self.assertRoughly(length, e.Length)
|
|
|
|
# let's offset the horizontal edge for starters
|
|
hEdges = [
|
|
e for e in w.Edges if Path.Geom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y)
|
|
]
|
|
|
|
x = length / 2
|
|
y = -10
|
|
self.assertEqual(1, len(hEdges))
|
|
edge = hEdges[0]
|
|
self.assertCoincide(Vector(-x, y, 0), edge.Vertexes[0].Point)
|
|
self.assertCoincide(Vector(+x, y, 0), edge.Vertexes[1].Point)
|
|
|
|
wire = PathOpUtil.offsetWire(Part.Wire([edge]), obj.Shape, 5, False)
|
|
self.assertEqual(1, len(wire.Edges))
|
|
|
|
y = y - 5
|
|
self.assertCoincide(Vector(-x, y, 0), wire.Edges[0].Vertexes[0].Point)
|
|
self.assertCoincide(Vector(+x, y, 0), wire.Edges[0].Vertexes[1].Point)
|
|
|
|
# make sure we get the same result on a reversed edge
|
|
edge = Path.Geom.flipEdge(edge)
|
|
wire = PathOpUtil.offsetWire(Part.Wire([edge]), obj.Shape, 5, False)
|
|
self.assertEqual(1, len(wire.Edges))
|
|
|
|
self.assertCoincide(Vector(-x, y, 0), wire.Edges[0].Vertexes[0].Point)
|
|
self.assertCoincide(Vector(+x, y, 0), wire.Edges[0].Vertexes[1].Point)
|
|
|
|
def test42(self):
|
|
"""Check offsetting multiple outside edges."""
|
|
obj = self.doc.getObjectsByLabel("offset-edge")[0]
|
|
obj.Shape.tessellate(0.01)
|
|
self.doc.recompute()
|
|
|
|
w = getWireOutside(obj)
|
|
length = 40 * math.cos(math.pi / 6)
|
|
|
|
# let's offset the other two legs
|
|
lEdges = [
|
|
e
|
|
for e in w.Edges
|
|
if not Path.Geom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y)
|
|
]
|
|
self.assertEqual(2, len(lEdges))
|
|
|
|
wire = PathOpUtil.offsetWire(Part.Wire(lEdges), obj.Shape, 2, True)
|
|
|
|
x = length / 2 + 2 * math.cos(math.pi / 6)
|
|
y = -10 + 2 * math.sin(math.pi / 6)
|
|
|
|
self.assertCoincide(Vector(-x, y, 0), wire.Edges[0].Vertexes[0].Point)
|
|
self.assertCoincide(Vector(+x, y, 0), wire.Edges[-1].Vertexes[1].Point)
|
|
|
|
rEdges = [e for e in wire.Edges if Part.Circle == type(e.Curve)]
|
|
|
|
self.assertEqual(1, len(rEdges))
|
|
self.assertCoincide(Vector(0, 20, 0), rEdges[0].Curve.Center)
|
|
self.assertCoincide(Vector(0, 0, -1), rEdges[0].Curve.Axis)
|
|
|
|
# offset the other way
|
|
wire = PathOpUtil.offsetWire(Part.Wire(lEdges), obj.Shape, 2, False)
|
|
|
|
self.assertCoincide(Vector(+x, y, 0), wire.Edges[0].Vertexes[0].Point)
|
|
self.assertCoincide(Vector(-x, y, 0), wire.Edges[-1].Vertexes[1].Point)
|
|
|
|
rEdges = [e for e in wire.Edges if Part.Circle == type(e.Curve)]
|
|
|
|
self.assertEqual(1, len(rEdges))
|
|
self.assertCoincide(Vector(0, 20, 0), rEdges[0].Curve.Center)
|
|
self.assertCoincide(Vector(0, 0, +1), rEdges[0].Curve.Axis)
|
|
|
|
def test43(self):
|
|
"""Check offsetting multiple backwards outside edges."""
|
|
# This is exactly the same as test32, except that the wire is flipped to make
|
|
# sure the input orientation doesn't matter
|
|
obj = self.doc.getObjectsByLabel("offset-edge")[0]
|
|
|
|
w = getWireOutside(obj)
|
|
length = 40 * math.cos(math.pi / 6)
|
|
|
|
# let's offset the other two legs
|
|
lEdges = [
|
|
e
|
|
for e in w.Edges
|
|
if not Path.Geom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y)
|
|
]
|
|
self.assertEqual(2, len(lEdges))
|
|
|
|
w = Path.Geom.flipWire(Part.Wire(lEdges))
|
|
wire = PathOpUtil.offsetWire(w, obj.Shape, 2, True)
|
|
|
|
x = length / 2 + 2 * math.cos(math.pi / 6)
|
|
y = -10 + 2 * math.sin(math.pi / 6)
|
|
|
|
self.assertCoincide(Vector(-x, y, 0), wire.Edges[0].Vertexes[0].Point)
|
|
self.assertCoincide(Vector(+x, y, 0), wire.Edges[-1].Vertexes[1].Point)
|
|
|
|
rEdges = [e for e in wire.Edges if Part.Circle == type(e.Curve)]
|
|
|
|
self.assertEqual(1, len(rEdges))
|
|
self.assertCoincide(Vector(0, 20, 0), rEdges[0].Curve.Center)
|
|
self.assertCoincide(Vector(0, 0, -1), rEdges[0].Curve.Axis)
|
|
|
|
# offset the other way
|
|
wire = PathOpUtil.offsetWire(Part.Wire(lEdges), obj.Shape, 2, False)
|
|
|
|
self.assertCoincide(Vector(+x, y, 0), wire.Edges[0].Vertexes[0].Point)
|
|
self.assertCoincide(Vector(-x, y, 0), wire.Edges[-1].Vertexes[1].Point)
|
|
|
|
rEdges = [e for e in wire.Edges if Part.Circle == type(e.Curve)]
|
|
|
|
self.assertEqual(1, len(rEdges))
|
|
self.assertCoincide(Vector(0, 20, 0), rEdges[0].Curve.Center)
|
|
self.assertCoincide(Vector(0, 0, +1), rEdges[0].Curve.Axis)
|
|
|
|
def test44(self):
|
|
"""Check offsetting a single inside edge forward."""
|
|
obj = self.doc.getObjectsByLabel("offset-edge")[0]
|
|
|
|
w = getWireInside(obj)
|
|
length = 20 * math.cos(math.pi / 6)
|
|
for e in w.Edges:
|
|
self.assertRoughly(length, e.Length)
|
|
|
|
# let's offset the horizontal edge for starters
|
|
hEdges = [
|
|
e for e in w.Edges if Path.Geom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y)
|
|
]
|
|
|
|
x = length / 2
|
|
y = -5
|
|
self.assertEqual(1, len(hEdges))
|
|
edge = hEdges[0]
|
|
|
|
self.assertCoincide(Vector(-x, y, 0), edge.Vertexes[0].Point)
|
|
self.assertCoincide(Vector(+x, y, 0), edge.Vertexes[1].Point)
|
|
|
|
wire = PathOpUtil.offsetWire(Part.Wire([edge]), obj.Shape, 2, True)
|
|
self.assertEqual(1, len(wire.Edges))
|
|
|
|
y = y + 2
|
|
self.assertCoincide(Vector(-x, y, 0), wire.Edges[0].Vertexes[0].Point)
|
|
self.assertCoincide(Vector(+x, y, 0), wire.Edges[0].Vertexes[1].Point)
|
|
|
|
# make sure we get the same result even if the edge is oriented the other way
|
|
edge = Path.Geom.flipEdge(edge)
|
|
wire = PathOpUtil.offsetWire(Part.Wire([edge]), obj.Shape, 2, True)
|
|
self.assertEqual(1, len(wire.Edges))
|
|
|
|
self.assertCoincide(Vector(-x, y, 0), wire.Edges[0].Vertexes[0].Point)
|
|
self.assertCoincide(Vector(+x, y, 0), wire.Edges[0].Vertexes[1].Point)
|
|
|
|
def test45(self):
|
|
"""Check offsetting a single inside edge not forward."""
|
|
obj = self.doc.getObjectsByLabel("offset-edge")[0]
|
|
|
|
w = getWireInside(obj)
|
|
length = 20 * math.cos(math.pi / 6)
|
|
for e in w.Edges:
|
|
self.assertRoughly(length, e.Length)
|
|
|
|
# let's offset the horizontal edge for starters
|
|
hEdges = [
|
|
e for e in w.Edges if Path.Geom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y)
|
|
]
|
|
|
|
x = length / 2
|
|
y = -5
|
|
self.assertEqual(1, len(hEdges))
|
|
edge = hEdges[0]
|
|
|
|
self.assertCoincide(Vector(-x, y, 0), edge.Vertexes[0].Point)
|
|
self.assertCoincide(Vector(+x, y, 0), edge.Vertexes[1].Point)
|
|
|
|
wire = PathOpUtil.offsetWire(Part.Wire([edge]), obj.Shape, 2, False)
|
|
self.assertEqual(1, len(wire.Edges))
|
|
|
|
y = y + 2
|
|
self.assertCoincide(Vector(+x, y, 0), wire.Edges[0].Vertexes[0].Point)
|
|
self.assertCoincide(Vector(-x, y, 0), wire.Edges[0].Vertexes[1].Point)
|
|
|
|
# make sure we get the same result even if the edge is oriented the other way
|
|
edge = Path.Geom.flipEdge(edge)
|
|
wire = PathOpUtil.offsetWire(Part.Wire([edge]), obj.Shape, 2, False)
|
|
self.assertEqual(1, len(wire.Edges))
|
|
|
|
self.assertCoincide(Vector(+x, y, 0), wire.Edges[0].Vertexes[0].Point)
|
|
self.assertCoincide(Vector(-x, y, 0), wire.Edges[0].Vertexes[1].Point)
|
|
|
|
def test46(self):
|
|
"""Check offsetting multiple inside edges."""
|
|
obj = self.doc.getObjectsByLabel("offset-edge")[0]
|
|
|
|
w = getWireInside(obj)
|
|
length = 20 * math.cos(math.pi / 6)
|
|
|
|
# let's offset the other two legs
|
|
lEdges = [
|
|
e
|
|
for e in w.Edges
|
|
if not Path.Geom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y)
|
|
]
|
|
self.assertEqual(2, len(lEdges))
|
|
|
|
wire = PathOpUtil.offsetWire(Part.Wire(lEdges), obj.Shape, 2, True)
|
|
|
|
x = length / 2 - 2 * math.cos(math.pi / 6)
|
|
y = -5 - 2 * math.sin(math.pi / 6)
|
|
|
|
self.assertCoincide(Vector(+x, y, 0), wire.Edges[0].Vertexes[0].Point)
|
|
self.assertCoincide(Vector(-x, y, 0), wire.Edges[-1].Vertexes[1].Point)
|
|
|
|
rEdges = [e for e in wire.Edges if Part.Circle == type(e.Curve)]
|
|
self.assertEqual(0, len(rEdges))
|
|
|
|
# offset the other way
|
|
wire = PathOpUtil.offsetWire(Part.Wire(lEdges), obj.Shape, 2, False)
|
|
|
|
self.assertCoincide(Vector(-x, y, 0), wire.Edges[0].Vertexes[0].Point)
|
|
self.assertCoincide(Vector(+x, y, 0), wire.Edges[-1].Vertexes[1].Point)
|
|
|
|
rEdges = [e for e in wire.Edges if Part.Circle == type(e.Curve)]
|
|
self.assertEqual(0, len(rEdges))
|
|
|
|
def test47(self):
|
|
"""Check offsetting multiple backwards inside edges."""
|
|
# This is exactly the same as test36 except that the wire is flipped to make
|
|
# sure it's orientation doesn't matter
|
|
obj = self.doc.getObjectsByLabel("offset-edge")[0]
|
|
|
|
w = getWireInside(obj)
|
|
length = 20 * math.cos(math.pi / 6)
|
|
|
|
# let's offset the other two legs
|
|
lEdges = [
|
|
e
|
|
for e in w.Edges
|
|
if not Path.Geom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y)
|
|
]
|
|
self.assertEqual(2, len(lEdges))
|
|
|
|
w = Path.Geom.flipWire(Part.Wire(lEdges))
|
|
wire = PathOpUtil.offsetWire(w, obj.Shape, 2, True)
|
|
|
|
x = length / 2 - 2 * math.cos(math.pi / 6)
|
|
y = -5 - 2 * math.sin(math.pi / 6)
|
|
|
|
self.assertCoincide(Vector(+x, y, 0), wire.Edges[0].Vertexes[0].Point)
|
|
self.assertCoincide(Vector(-x, y, 0), wire.Edges[-1].Vertexes[1].Point)
|
|
|
|
rEdges = [e for e in wire.Edges if Part.Circle == type(e.Curve)]
|
|
self.assertEqual(0, len(rEdges))
|
|
|
|
# offset the other way
|
|
wire = PathOpUtil.offsetWire(Part.Wire(lEdges), obj.Shape, 2, False)
|
|
|
|
self.assertCoincide(Vector(-x, y, 0), wire.Edges[0].Vertexes[0].Point)
|
|
self.assertCoincide(Vector(+x, y, 0), wire.Edges[-1].Vertexes[1].Point)
|
|
|
|
rEdges = [e for e in wire.Edges if Part.Circle == type(e.Curve)]
|
|
self.assertEqual(0, len(rEdges))
|
|
|
|
def test50(self):
|
|
"""Orient an already oriented wire"""
|
|
p0 = Vector()
|
|
p1 = Vector(1, 2, 3)
|
|
p2 = Vector(2, 3, 4)
|
|
pts = [p0, p1, p2]
|
|
|
|
e0 = Part.Edge(Part.LineSegment(p0, p1))
|
|
e1 = Part.Edge(Part.LineSegment(p1, p2))
|
|
|
|
wire = PathOpUtil.orientWire(Part.Wire([e0, e1]))
|
|
wirePts = wireMarkers(wire)
|
|
|
|
self.assertPointsMatch(wirePts, pts)
|
|
|
|
def test51(self):
|
|
"""Orient a potentially misoriented wire"""
|
|
p0 = Vector()
|
|
p1 = Vector(1, 2, 3)
|
|
p2 = Vector(2, 3, 4)
|
|
pts = [p0, p1, p2]
|
|
|
|
e0p = Part.Edge(Part.LineSegment(p0, p1))
|
|
e0m = Part.Edge(Part.LineSegment(p1, p0))
|
|
e1p = Part.Edge(Part.LineSegment(p1, p2))
|
|
e1m = Part.Edge(Part.LineSegment(p2, p1))
|
|
|
|
wire = PathOpUtil.orientWire(Part.Wire([e0p, e1p]))
|
|
self.assertPointsMatch(wireMarkers(wire), pts)
|
|
|
|
wire = PathOpUtil.orientWire(Part.Wire([e0p, e1m]))
|
|
self.assertPointsMatch(wireMarkers(wire), pts)
|
|
|
|
wire = PathOpUtil.orientWire(Part.Wire([e0m, e1p]))
|
|
self.assertPointsMatch(wireMarkers(wire), pts)
|
|
|
|
wire = PathOpUtil.orientWire(Part.Wire([e0m, e1m]))
|
|
self.assertPointsMatch(wireMarkers(wire), pts)
|
|
|
|
def test52(self):
|
|
"""Orient a potentially misoriented longer wire"""
|
|
p0 = Vector()
|
|
p1 = Vector(1, 2, 3)
|
|
p2 = Vector(4, 5, 6)
|
|
p3 = Vector(7, 8, 9)
|
|
pts = [p0, p1, p2, p3]
|
|
|
|
e0p = Part.Edge(Part.LineSegment(p0, p1))
|
|
e0m = Part.Edge(Part.LineSegment(p1, p0))
|
|
e1p = Part.Edge(Part.LineSegment(p1, p2))
|
|
e1m = Part.Edge(Part.LineSegment(p2, p1))
|
|
e2p = Part.Edge(Part.LineSegment(p2, p3))
|
|
e2m = Part.Edge(Part.LineSegment(p3, p2))
|
|
|
|
wire = PathOpUtil.orientWire(Part.Wire([e0p, e1p, e2p]))
|
|
self.assertPointsMatch(wireMarkers(wire), pts)
|
|
|
|
wire = PathOpUtil.orientWire(Part.Wire([e0p, e1m, e2p]))
|
|
self.assertPointsMatch(wireMarkers(wire), pts)
|
|
|
|
wire = PathOpUtil.orientWire(Part.Wire([e0m, e1p, e2p]))
|
|
self.assertPointsMatch(wireMarkers(wire), pts)
|
|
|
|
wire = PathOpUtil.orientWire(Part.Wire([e0m, e1m, e2p]))
|
|
self.assertPointsMatch(wireMarkers(wire), pts)
|
|
|
|
wire = PathOpUtil.orientWire(Part.Wire([e0p, e1p, e2m]))
|
|
self.assertPointsMatch(wireMarkers(wire), pts)
|
|
|
|
wire = PathOpUtil.orientWire(Part.Wire([e0p, e1m, e2m]))
|
|
self.assertPointsMatch(wireMarkers(wire), pts)
|
|
|
|
wire = PathOpUtil.orientWire(Part.Wire([e0m, e1p, e2m]))
|
|
self.assertPointsMatch(wireMarkers(wire), pts)
|
|
|
|
wire = PathOpUtil.orientWire(Part.Wire([e0m, e1m, e2m]))
|
|
self.assertPointsMatch(wireMarkers(wire), pts)
|