Fixed Issue #843 added is_forward to Edge parameter methods

This commit is contained in:
gumyr 2025-04-23 20:17:02 -04:00
parent 739368c417
commit 6590df1e65
4 changed files with 141 additions and 27 deletions

View file

@ -506,7 +506,7 @@ class Mixin1D(Shape):
Note that circles may have identical start and end points.
"""
curve = self.geom_adaptor()
umax = curve.LastParameter()
umax = curve.LastParameter() if self.is_forward else curve.FirstParameter()
return Vector(curve.Value(umax))
@ -546,6 +546,12 @@ class Mixin1D(Shape):
"""
curve = self.geom_adaptor()
if not self.is_forward:
if position_mode == PositionMode.PARAMETER:
distance = 1 - distance
else:
distance = self.length - distance
if position_mode == PositionMode.PARAMETER:
param = self.param_at(distance)
else:
@ -595,7 +601,9 @@ class Mixin1D(Shape):
)
loc = Location(TopLoc_Location(transformation))
if self.is_forward:
return loc
return -loc
def locations(
self,
@ -822,8 +830,12 @@ class Mixin1D(Shape):
curve = self.geom_adaptor()
if position_mode == PositionMode.PARAMETER:
if not self.is_forward:
distance = 1 - distance
param = self.param_at(distance)
else:
if not self.is_forward:
distance = self.length - distance
param = self.param_at(distance / self.length)
return Vector(curve.Value(param))
@ -1120,7 +1132,7 @@ class Mixin1D(Shape):
Note that circles may have identical start and end points.
"""
curve = self.geom_adaptor()
umin = curve.FirstParameter()
umin = curve.FirstParameter() if self.is_forward else curve.LastParameter()
return Vector(curve.Value(umin))
@ -1169,6 +1181,12 @@ class Mixin1D(Shape):
Returns:
Vector: tangent value
"""
if not self.is_forward:
if position_mode == PositionMode.PARAMETER:
position = 1 - position
else:
position = self.length - position
if isinstance(position, (float, int)):
curve = self.geom_adaptor()
if position_mode == PositionMode.PARAMETER:
@ -1198,7 +1216,9 @@ class Mixin1D(Shape):
res = gp_Vec()
curve.D1(parameter, tmp, res)
if self.is_forward:
return Vector(gp_Dir(res))
return Vector(gp_Dir(res)) * -1
def vertex(self) -> Vertex | None:
"""Return the Vertex"""
@ -2089,9 +2109,20 @@ class Edge(Mixin1D, Shape[TopoDS_Edge]):
projected_edges = [w.edges()[0] for w in projected_wires]
return projected_edges
def reversed(self) -> Edge:
"""Return a copy of self with the opposite orientation"""
def reversed(self, reconstruct: bool = False) -> Edge:
"""reversed
Return a copy of self with the opposite orientation.
Args:
reconstruct (bool, optional): rebuild edge instead of setting OCCT flag.
Defaults to False.
Returns:
Edge: reversed
"""
reversed_edge = copy.deepcopy(self)
if reconstruct:
first: float = self.param_at(0)
last: float = self.param_at(1)
curve = BRep_Tool.Curve_s(self.wrapped, first, last)
@ -2099,6 +2130,8 @@ class Edge(Mixin1D, Shape[TopoDS_Edge]):
last = curve.ReversedParameter(last)
topods_edge = BRepBuilderAPI_MakeEdge(curve.Reversed(), last, first).Edge()
reversed_edge.wrapped = topods_edge
else:
reversed_edge.wrapped = downcast(self.wrapped.Reversed())
return reversed_edge
def to_axis(self) -> Axis:
@ -2805,10 +2838,18 @@ class Wire(Mixin1D, Shape[TopoDS_Wire]):
def order_edges(self) -> ShapeList[Edge]:
"""Return the edges in self ordered by wire direction and orientation"""
ordered_edges = [
e if e.is_forward else e.reversed() for e in self.edges().sort_by(self)
]
return ShapeList(ordered_edges)
sorted_edges = self.edges().sort_by(self)
ordered_edges = ShapeList([sorted_edges[0]])
for edge in sorted_edges[1:]:
last_edge = ordered_edges[-1]
if abs(last_edge @ 1 - edge @ 0) < TOLERANCE:
ordered_edges.append(edge)
else:
ordered_edges.append(edge.reversed())
return ordered_edges
def param_at_point(self, point: VectorLike) -> float:
"""Parameter at point on Wire"""

View file

@ -415,20 +415,32 @@ class TestRevolve(unittest.TestCase):
def test_revolve_size(self):
"""Verify revolution result matches revolution_arc size and direction"""
ax = Axis.X
profile = RegularPolygon(10, 4, align=(Align.CENTER, Align.MIN))
full_volume = revolve(profile, ax, 360).volume
sizes = [30, 90, 150, 180, 200, 360, 500, 720, 750]
sizes = [x * -1 for x in sizes[::-1]] + [0] + sizes
for size in sizes:
profile = RegularPolygon(10, 4, align=(Align.CENTER, Align.MIN))
solid = revolve(profile, axis=ax, revolution_arc=size)
# Find any rotation edge and and the start tangent normal to the profile
edge = solid.edges().filter_by(GeomType.CIRCLE).sort_by(Edge.length)[-1]
# Create a rotation edge and and the start tangent normal to the profile
edge = Edge.make_circle(
1,
Plane.YZ,
0,
size % 360,
(
AngularDirection.COUNTER_CLOCKWISE
if size > 0
else AngularDirection.CLOCKWISE
),
)
sign = (edge % 0).Z
expected = size % (sign * 360)
expected = sign * 360 if expected == 0 else expected
result = edge.length / edge.radius / pi * 180 * sign
self.assertAlmostEqual(solid.volume, full_volume * abs(expected) / 360)
self.assertAlmostEqual(expected, result)
# Invalid test
@ -506,10 +518,10 @@ class TestThicken(unittest.TestCase):
outer_sphere = thicken(non_planar, amount=0.1)
self.assertAlmostEqual(outer_sphere.volume, (4 / 3) * pi * (1.1**3 - 1**3), 5)
wire = JernArc((0, 0), (-1, 0), 1, 180).edge().reversed() + JernArc(
(0, 0), (1, 0), 2, -90
)
part = thicken(sweep((wire ^ 0) * RadiusArc((0, 0), (0, -1), 1), wire), 0.4)
wire = JernArc((0, -2), (-1, 0), 1, -180) + JernArc((0, 0), (1, 0), 2, -90)
surface = sweep((wire ^ 0) * RadiusArc((0, 0), (0, -1), 1), wire)
part = thicken(surface, 0.4)
self.assertAlmostEqual(part.volume, 2.241583787221904, 5)
part = thicken(

View file

@ -29,7 +29,7 @@ license:
import math
import unittest
from build123d.build_enums import AngularDirection, GeomType, Transition
from build123d.build_enums import AngularDirection, GeomType, PositionMode, Transition
from build123d.geometry import Axis, Plane, Vector
from build123d.objects_curve import CenterArc, EllipticalCenterArc
from build123d.objects_sketch import Circle, Rectangle, RegularPolygon
@ -282,6 +282,9 @@ class TestEdge(unittest.TestCase):
e2r = e2.reversed()
self.assertAlmostEqual((e2 @ 0.1).X, -(e2r @ 0.1).X, 5)
e2r = e2.reversed(reconstruct=True)
self.assertAlmostEqual((e2 @ 0.1).X, -(e2r @ 0.1).X, 5)
def test_init(self):
with self.assertRaises(TypeError):
Edge(direction=(1, 0, 0))
@ -294,6 +297,66 @@ class TestEdge(unittest.TestCase):
self.assertEqual(len(inside_edges), 5)
self.assertTrue(all(e.geom_type == GeomType.ELLIPSE for e in inside_edges))
def test_position_at(self):
line = Edge.make_line((1, 1), (2, 2))
self.assertEqual(line @ 0, Vector(1, 1, 0))
self.assertEqual(line @ 1, Vector(2, 2, 0))
self.assertEqual(line.reversed() @ 0, Vector(2, 2, 0))
self.assertEqual(line.reversed() @ 1, Vector(1, 1, 0))
self.assertEqual(
line.position_at(1, position_mode=PositionMode.LENGTH),
Vector(1, 1) + Vector(math.sqrt(2) / 2, math.sqrt(2) / 2),
)
self.assertEqual(
line.reversed().position_at(1, position_mode=PositionMode.LENGTH),
Vector(2, 2) - Vector(math.sqrt(2) / 2, math.sqrt(2) / 2),
)
def test_tangent_at(self):
arc = Edge.make_circle(1, start_angle=0, end_angle=180)
self.assertEqual(arc % 0, Vector(0, 1, 0))
self.assertEqual(arc % 1, Vector(0, -1, 0))
self.assertEqual(arc.reversed() % 0, Vector(0, 1, 0))
self.assertEqual(arc.reversed() % 1, Vector(0, -1, 0))
self.assertEqual(arc.reversed() @ 0, Vector(-1, 0, 0))
self.assertEqual(arc.reversed() @ 1, Vector(1, 0, 0))
self.assertEqual(
arc.tangent_at(math.pi, position_mode=PositionMode.LENGTH), Vector(0, -1, 0)
)
self.assertEqual(
arc.reversed().tangent_at(math.pi / 2, position_mode=PositionMode.LENGTH),
Vector(1, 0, 0),
)
def test_location_at(self):
arc = Edge.make_circle(1, start_angle=0, end_angle=180)
self.assertEqual(arc.location_at(0).position, Vector(1, 0, 0))
self.assertEqual(arc.location_at(1).position, Vector(-1, 0, 0))
self.assertEqual(arc.location_at(0).z_axis.direction, Vector(0, 1, 0))
self.assertEqual(arc.location_at(1).z_axis.direction, Vector(0, -1, 0))
self.assertEqual(arc.reversed().location_at(0).position, Vector(-1, 0, 0))
self.assertEqual(arc.reversed().location_at(1).position, Vector(1, 0, 0))
self.assertEqual(
arc.reversed().location_at(0).z_axis.direction, Vector(0, 1, 0)
)
self.assertEqual(
arc.reversed().location_at(1).z_axis.direction, Vector(0, -1, 0)
)
self.assertEqual(
arc.location_at(math.pi, position_mode=PositionMode.LENGTH).position,
Vector(-1, 0, 0),
)
self.assertEqual(
arc.reversed()
.location_at(math.pi, position_mode=PositionMode.LENGTH)
.position,
Vector(1, 0, 0),
)
if __name__ == "__main__":
unittest.main()

View file

@ -182,8 +182,6 @@ class TestWire(unittest.TestCase):
]
)
ordered_edges = w1.order_edges()
self.assertFalse(all(e.is_forward for e in w1.edges()))
self.assertTrue(all(e.is_forward for e in ordered_edges))
self.assertAlmostEqual(ordered_edges[0] @ 0, (0, 0, 0), 5)
self.assertAlmostEqual(ordered_edges[1] @ 0, (1, 0, 0), 5)
self.assertAlmostEqual(ordered_edges[2] @ 0, (1, 1, 0), 5)