mirror of
https://github.com/gumyr/build123d.git
synced 2025-12-05 18:20:46 -08:00
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
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:
parent
d66e22655e
commit
453f676882
3 changed files with 83 additions and 25 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue