Adding points to trim
Some checks are pending
benchmarks / benchmarks (macos-14, 3.12) (push) Waiting to run
benchmarks / benchmarks (macos-15-intel, 3.12) (push) Waiting to run
benchmarks / benchmarks (ubuntu-latest, 3.12) (push) Waiting to run
benchmarks / benchmarks (windows-latest, 3.12) (push) Waiting to run
Upload coverage reports to Codecov / run (push) Waiting to run
pylint / lint (3.10) (push) Waiting to run
Run type checker / typecheck (3.10) (push) Waiting to run
Run type checker / typecheck (3.13) (push) Waiting to run
Wheel building and publishing / Build wheel on ubuntu-latest (push) Waiting to run
Wheel building and publishing / upload_pypi (push) Blocked by required conditions
tests / tests (macos-14, 3.10) (push) Waiting to run
tests / tests (macos-14, 3.13) (push) Waiting to run
tests / tests (macos-15-intel, 3.10) (push) Waiting to run
tests / tests (macos-15-intel, 3.13) (push) Waiting to run
tests / tests (ubuntu-latest, 3.10) (push) Waiting to run
tests / tests (ubuntu-latest, 3.13) (push) Waiting to run
tests / tests (windows-latest, 3.10) (push) Waiting to run
tests / tests (windows-latest, 3.13) (push) Waiting to run

This commit is contained in:
gumyr 2025-10-20 18:50:14 -04:00
parent d66e22655e
commit 453f676882
3 changed files with 83 additions and 25 deletions

View file

@ -358,6 +358,21 @@ class Mixin1D(Shape):
"""Unused - only here because Mixin1D is a subclass of Shape""" """Unused - only here because Mixin1D is a subclass of Shape"""
return NotImplemented return NotImplemented
# ---- Static Methods ----
@staticmethod
def _to_param(edge_wire: Mixin1D, value: float | VectorLike, name: str) -> float:
"""Convert a float or VectorLike into a curve parameter."""
if isinstance(value, (int, float)):
return float(value)
try:
point = Vector(value)
except TypeError as exc:
raise TypeError(
f"{name} must be a float or VectorLike, not {value!r}"
) from exc
return edge_wire.param_at_point(point)
# ---- Instance Methods ---- # ---- Instance Methods ----
def __add__( def __add__(
@ -2972,24 +2987,43 @@ class Edge(Mixin1D, Shape[TopoDS_Edge]):
) )
return Wire([self]) return Wire([self])
def trim(self, start: float, end: float) -> Edge: def trim(self, start: float | VectorLike, end: float | VectorLike) -> Edge:
"""_summary_
Args:
start (float | VectorLike): _description_
end (float | VectorLike): _description_
Raises:
TypeError: _description_
ValueError: _description_
Returns:
Edge: _description_
"""
"""trim """trim
Create a new edge by keeping only the section between start and end. Create a new edge by keeping only the section between start and end.
Args: Args:
start (float): 0.0 <= start < 1.0 start (float | VectorLike): 0.0 <= start < 1.0 or point on edge
end (float): 0.0 < end <= 1.0 end (float | VectorLike): 0.0 < end <= 1.0 or point on edge
Raises: Raises:
ValueError: start >= end TypeError: invalid input, must be float or VectorLike
ValueError: can't trim empty edge ValueError: can't trim empty edge
Returns: Returns:
Edge: trimmed edge Edge: trimmed edge
""" """
if start >= end:
raise ValueError(f"start ({start}) must be less than end ({end})") start_u = Mixin1D._to_param(self, start, "start")
end_u = Mixin1D._to_param(self, end, "end")
start_u, end_u = sorted([start_u, end_u])
# if start_u >= end_u:
# raise ValueError(f"start ({start_u}) must be less than end ({end_u})")
if self.wrapped is None: if self.wrapped is None:
raise ValueError("Can't trim empty edge") raise ValueError("Can't trim empty edge")
@ -3000,8 +3034,8 @@ class Edge(Mixin1D, Shape[TopoDS_Edge]):
new_curve = BRep_Tool.Curve_s( new_curve = BRep_Tool.Curve_s(
self_copy.wrapped, self.param_at(0), self.param_at(1) self_copy.wrapped, self.param_at(0), self.param_at(1)
) )
parm_start = self.param_at(start) parm_start = self.param_at(start_u)
parm_end = self.param_at(end) parm_end = self.param_at(end_u)
trimmed_curve = Geom_TrimmedCurve( trimmed_curve = Geom_TrimmedCurve(
new_curve, new_curve,
parm_start, parm_start,
@ -3010,14 +3044,14 @@ class Edge(Mixin1D, Shape[TopoDS_Edge]):
new_edge = BRepBuilderAPI_MakeEdge(trimmed_curve).Edge() new_edge = BRepBuilderAPI_MakeEdge(trimmed_curve).Edge()
return Edge(new_edge) return Edge(new_edge)
def trim_to_length(self, start: float, length: float) -> Edge: def trim_to_length(self, start: float | VectorLike, length: float) -> Edge:
"""trim_to_length """trim_to_length
Create a new edge starting at the given normalized parameter of a Create a new edge starting at the given normalized parameter of a
given length. given length.
Args: Args:
start (float): 0.0 <= start < 1.0 start (float | VectorLike): 0.0 <= start < 1.0 or point on edge
length (float): target length length (float): target length
Raise: Raise:
@ -3029,6 +3063,8 @@ class Edge(Mixin1D, Shape[TopoDS_Edge]):
if self.wrapped is None: if self.wrapped is None:
raise ValueError("Can't trim empty edge") raise ValueError("Can't trim empty edge")
start_u = Mixin1D._to_param(self, start, "start")
self_copy = copy.deepcopy(self) self_copy = copy.deepcopy(self)
assert self_copy.wrapped is not None assert self_copy.wrapped is not None
@ -3040,7 +3076,7 @@ class Edge(Mixin1D, Shape[TopoDS_Edge]):
adaptor_curve = GeomAdaptor_Curve(new_curve) adaptor_curve = GeomAdaptor_Curve(new_curve)
# Find the parameter corresponding to the desired length # Find the parameter corresponding to the desired length
parm_start = self.param_at(start) parm_start = self.param_at(start_u)
abscissa_point = GCPnts_AbscissaPoint(adaptor_curve, length, parm_start) abscissa_point = GCPnts_AbscissaPoint(adaptor_curve, length, parm_start)
# Get the parameter at the desired length # Get the parameter at the desired length
@ -3550,7 +3586,6 @@ class Wire(Mixin1D, Shape[TopoDS_Wire]):
return Wire.make_polygon(corners_world, close=True) return Wire.make_polygon(corners_world, close=True)
# ---- Static Methods ---- # ---- Static Methods ----
@staticmethod @staticmethod
def order_chamfer_edges( def order_chamfer_edges(
reference_edge: Edge | None, edges: tuple[Edge, Edge] reference_edge: Edge | None, edges: tuple[Edge, Edge]
@ -4066,29 +4101,31 @@ class Wire(Mixin1D, Shape[TopoDS_Wire]):
) )
return self return self
def trim(self: Wire, start: float, end: float) -> Wire: def trim(self: Wire, start: float | VectorLike, end: float | VectorLike) -> Wire:
"""Trim a wire between [start, end] normalized over total length. """Trim a wire between [start, end] normalized over total length.
Args: Args:
start (float): normalized start position (0.0 to <1.0) start (float | VectorLike): normalized start position (0.0 to <1.0) or point
end (float): normalized end position (>0.0 to 1.0) end (float | VectorLike): normalized end position (>0.0 to 1.0) or point
Returns: Returns:
Wire: trimmed Wire Wire: trimmed Wire
""" """
if start >= end: start_u = Mixin1D._to_param(self, start, "start")
raise ValueError("start must be less than end") end_u = Mixin1D._to_param(self, end, "end")
start_u, end_u = sorted([start_u, end_u])
# Extract the edges in order # Extract the edges in order
ordered_edges = self.edges().sort_by(self) ordered_edges = self.edges().sort_by(self)
# If this is really just an edge, skip the complexity of a Wire # If this is really just an edge, skip the complexity of a Wire
if len(ordered_edges) == 1: if len(ordered_edges) == 1:
return Wire([ordered_edges[0].trim(start, end)]) return Wire([ordered_edges[0].trim(start_u, end_u)])
total_length = self.length total_length = self.length
start_len = start * total_length start_len = start_u * total_length
end_len = end * total_length end_len = end_u * total_length
trimmed_edges = [] trimmed_edges = []
cur_length = 0.0 cur_length = 0.0

View file

@ -37,7 +37,7 @@ from build123d.geometry import Axis, Plane, Vector
from build123d.objects_curve import CenterArc, EllipticalCenterArc from build123d.objects_curve import CenterArc, EllipticalCenterArc
from build123d.objects_sketch import Circle, Rectangle, RegularPolygon from build123d.objects_sketch import Circle, Rectangle, RegularPolygon
from build123d.operations_generic import sweep from build123d.operations_generic import sweep
from build123d.topology import Edge, Face, Wire from build123d.topology import Edge, Face, Wire, Vertex
from OCP.GeomProjLib import GeomProjLib from OCP.GeomProjLib import GeomProjLib
@ -183,8 +183,23 @@ class TestEdge(unittest.TestCase):
line = Edge.make_line((-2, 0), (2, 0)) line = Edge.make_line((-2, 0), (2, 0))
self.assertAlmostEqual(line.trim(0.25, 0.75).position_at(0), (-1, 0, 0), 5) self.assertAlmostEqual(line.trim(0.25, 0.75).position_at(0), (-1, 0, 0), 5)
self.assertAlmostEqual(line.trim(0.25, 0.75).position_at(1), (1, 0, 0), 5) self.assertAlmostEqual(line.trim(0.25, 0.75).position_at(1), (1, 0, 0), 5)
with self.assertRaises(ValueError):
line.trim(0.75, 0.25) l1 = CenterArc((0, 0), 1, 0, 180)
l2 = l1.trim(0, l1 @ 0.5)
self.assertAlmostEqual(l2 @ 0, (1, 0, 0), 5)
self.assertAlmostEqual(l2 @ 1, (0, 1, 0), 5)
l3 = l1.trim((1, 0), (0, 1))
self.assertAlmostEqual(l3 @ 0, (1, 0, 0), 5)
self.assertAlmostEqual(l3 @ 1, (0, 1, 0), 5)
l4 = l1.trim(0.5, (-1, 0))
self.assertAlmostEqual(l4 @ 0, (0, 1, 0), 5)
self.assertAlmostEqual(l4 @ 1, (-1, 0, 0), 5)
l5 = l1.trim(0.5, Vertex(-1, 0))
self.assertAlmostEqual(l5 @ 0, (0, 1, 0), 5)
self.assertAlmostEqual(l5 @ 1, (-1, 0, 0), 5)
line.wrapped = None line.wrapped = None
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
@ -213,6 +228,10 @@ class TestEdge(unittest.TestCase):
e4_trim = Edge(a4).trim_to_length(0.5, 2) e4_trim = Edge(a4).trim_to_length(0.5, 2)
self.assertAlmostEqual(e4_trim.length, 2, 5) self.assertAlmostEqual(e4_trim.length, 2, 5)
e5 = e1.trim_to_length((5, 5), 1)
self.assertAlmostEqual(e5 @ 0, (5, 5), 5)
self.assertAlmostEqual(e5.length, 1, 5)
e1.wrapped = None e1.wrapped = None
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
e1.trim_to_length(0.1, 2) e1.trim_to_length(0.1, 2)

View file

@ -155,8 +155,10 @@ class TestWire(unittest.TestCase):
t4 = o.trim(0.5, 0.75) t4 = o.trim(0.5, 0.75)
self.assertAlmostEqual(t4.length, o.length * 0.25, 5) self.assertAlmostEqual(t4.length, o.length * 0.25, 5)
with self.assertRaises(ValueError): w0 = Polyline((0, 0), (0, 1), (1, 1), (1, 0))
o.trim(0.75, 0.25) w2 = w0.trim(0, (0.5, 1))
self.assertAlmostEqual(w2 @ 1, (0.5, 1), 5)
spline = Spline( spline = Spline(
(0, 0, 0), (0, 0, 0),
(0, 10, 0), (0, 10, 0),