mirror of
https://github.com/gumyr/build123d.git
synced 2025-12-05 18:20:46 -08:00
Replacing Mixin1D.discretize with enhanced Minxin1D.positions
Some checks failed
benchmarks / benchmarks (macos-14, 3.12) (push) Has been cancelled
benchmarks / benchmarks (macos-15-intel, 3.12) (push) Has been cancelled
benchmarks / benchmarks (ubuntu-latest, 3.12) (push) Has been cancelled
benchmarks / benchmarks (windows-latest, 3.12) (push) Has been cancelled
Upload coverage reports to Codecov / run (push) Has been cancelled
pylint / lint (3.10) (push) Has been cancelled
Run type checker / typecheck (3.10) (push) Has been cancelled
Run type checker / typecheck (3.13) (push) Has been cancelled
Wheel building and publishing / Build wheel on ubuntu-latest (push) Has been cancelled
tests / tests (macos-14, 3.10) (push) Has been cancelled
tests / tests (macos-14, 3.13) (push) Has been cancelled
tests / tests (macos-15-intel, 3.10) (push) Has been cancelled
tests / tests (macos-15-intel, 3.13) (push) Has been cancelled
tests / tests (ubuntu-latest, 3.10) (push) Has been cancelled
tests / tests (ubuntu-latest, 3.13) (push) Has been cancelled
tests / tests (windows-latest, 3.10) (push) Has been cancelled
tests / tests (windows-latest, 3.13) (push) Has been cancelled
Wheel building and publishing / upload_pypi (push) Has been cancelled
Some checks failed
benchmarks / benchmarks (macos-14, 3.12) (push) Has been cancelled
benchmarks / benchmarks (macos-15-intel, 3.12) (push) Has been cancelled
benchmarks / benchmarks (ubuntu-latest, 3.12) (push) Has been cancelled
benchmarks / benchmarks (windows-latest, 3.12) (push) Has been cancelled
Upload coverage reports to Codecov / run (push) Has been cancelled
pylint / lint (3.10) (push) Has been cancelled
Run type checker / typecheck (3.10) (push) Has been cancelled
Run type checker / typecheck (3.13) (push) Has been cancelled
Wheel building and publishing / Build wheel on ubuntu-latest (push) Has been cancelled
tests / tests (macos-14, 3.10) (push) Has been cancelled
tests / tests (macos-14, 3.13) (push) Has been cancelled
tests / tests (macos-15-intel, 3.10) (push) Has been cancelled
tests / tests (macos-15-intel, 3.13) (push) Has been cancelled
tests / tests (ubuntu-latest, 3.10) (push) Has been cancelled
tests / tests (ubuntu-latest, 3.13) (push) Has been cancelled
tests / tests (windows-latest, 3.10) (push) Has been cancelled
tests / tests (windows-latest, 3.13) (push) Has been cancelled
Wheel building and publishing / upload_pypi (push) Has been cancelled
This commit is contained in:
commit
a8fc16b344
2 changed files with 100 additions and 36 deletions
|
|
@ -120,6 +120,9 @@ from OCP.GeomAbs import (
|
||||||
GeomAbs_C0,
|
GeomAbs_C0,
|
||||||
GeomAbs_C1,
|
GeomAbs_C1,
|
||||||
GeomAbs_C2,
|
GeomAbs_C2,
|
||||||
|
GeomAbs_C3,
|
||||||
|
GeomAbs_CN,
|
||||||
|
GeomAbs_C1,
|
||||||
GeomAbs_G1,
|
GeomAbs_G1,
|
||||||
GeomAbs_G2,
|
GeomAbs_G2,
|
||||||
GeomAbs_JoinType,
|
GeomAbs_JoinType,
|
||||||
|
|
@ -545,31 +548,6 @@ class Mixin1D(Shape[TOPODS]):
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def discretize(self, deflection: float = 0.1, quasi=True) -> list[Vector]:
|
|
||||||
"""Discretize the shape into a list of points"""
|
|
||||||
if self.wrapped is None:
|
|
||||||
raise ValueError("Cannot discretize an empty shape")
|
|
||||||
curve = self.geom_adaptor()
|
|
||||||
if quasi:
|
|
||||||
discretizer = GCPnts_QuasiUniformDeflection()
|
|
||||||
else:
|
|
||||||
discretizer = GCPnts_UniformDeflection()
|
|
||||||
discretizer.Initialize(
|
|
||||||
curve,
|
|
||||||
deflection,
|
|
||||||
curve.FirstParameter(),
|
|
||||||
curve.LastParameter(),
|
|
||||||
)
|
|
||||||
|
|
||||||
assert discretizer.IsDone()
|
|
||||||
|
|
||||||
return [
|
|
||||||
Vector(v.X(), v.Y(), v.Z())
|
|
||||||
for v in (
|
|
||||||
curve.Value(discretizer.Parameter(i))
|
|
||||||
for i in range(1, discretizer.NbPoints() + 1)
|
|
||||||
)
|
|
||||||
]
|
|
||||||
def curvature_comb(
|
def curvature_comb(
|
||||||
self, count: int = 100, max_tooth_size: float | None = None
|
self, count: int = 100, max_tooth_size: float | None = None
|
||||||
) -> ShapeList[Edge]:
|
) -> ShapeList[Edge]:
|
||||||
|
|
@ -1207,22 +1185,52 @@ class Mixin1D(Shape[TOPODS]):
|
||||||
|
|
||||||
def positions(
|
def positions(
|
||||||
self,
|
self,
|
||||||
distances: Iterable[float],
|
distances: Iterable[float] | None = None,
|
||||||
position_mode: PositionMode = PositionMode.PARAMETER,
|
position_mode: PositionMode = PositionMode.PARAMETER,
|
||||||
|
deflection: float | None = None,
|
||||||
) -> list[Vector]:
|
) -> list[Vector]:
|
||||||
"""Positions along curve
|
"""Positions along curve
|
||||||
|
|
||||||
Generate positions along the underlying curve
|
Generate positions along the underlying curve
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
distances (Iterable[float]): distance or parameter values
|
distances (Iterable[float] | None, optional): distance or parameter values.
|
||||||
position_mode (PositionMode, optional): position calculation mode.
|
Defaults to None.
|
||||||
Defaults to PositionMode.PARAMETER.
|
position_mode (PositionMode, optional): position calculation mode only applies
|
||||||
|
when using distances. Defaults to PositionMode.PARAMETER.
|
||||||
|
deflection (float | None, optional): maximum deflection between the curve and
|
||||||
|
the polygon that results from the computed points. Defaults to None.
|
||||||
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
list[Vector]: positions along curve
|
list[Vector]: positions along curve
|
||||||
"""
|
"""
|
||||||
return [self.position_at(d, position_mode) for d in distances]
|
if deflection is not None:
|
||||||
|
curve: BRepAdaptor_Curve | BRepAdaptor_CompCurve = self.geom_adaptor()
|
||||||
|
# GCPnts_UniformDeflection provides the best results but is limited
|
||||||
|
if curve.Continuity() in (GeomAbs_C2, GeomAbs_C3, GeomAbs_CN):
|
||||||
|
discretizer: (
|
||||||
|
GCPnts_UniformDeflection | GCPnts_QuasiUniformDeflection
|
||||||
|
) = GCPnts_UniformDeflection()
|
||||||
|
else:
|
||||||
|
discretizer = GCPnts_QuasiUniformDeflection()
|
||||||
|
|
||||||
|
discretizer.Initialize(
|
||||||
|
curve,
|
||||||
|
deflection,
|
||||||
|
curve.FirstParameter(),
|
||||||
|
curve.LastParameter(),
|
||||||
|
)
|
||||||
|
if not discretizer.IsDone() or discretizer.NbPoints() == 0:
|
||||||
|
raise RuntimeError("Deflection calculation failed")
|
||||||
|
return [
|
||||||
|
Vector(curve.Value(discretizer.Parameter(i + 1)))
|
||||||
|
for i in range(discretizer.NbPoints())
|
||||||
|
]
|
||||||
|
elif distances is not None:
|
||||||
|
return [self.position_at(d, position_mode) for d in distances]
|
||||||
|
else:
|
||||||
|
raise ValueError("Either distances or deflection must be provided")
|
||||||
|
|
||||||
def project(
|
def project(
|
||||||
self, face: Face, direction: VectorLike, closest: bool = True
|
self, face: Face, direction: VectorLike, closest: bool = True
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ license:
|
||||||
|
|
||||||
import math
|
import math
|
||||||
import unittest
|
import unittest
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
from build123d.build_enums import (
|
from build123d.build_enums import (
|
||||||
CenterOf,
|
CenterOf,
|
||||||
|
|
@ -106,13 +107,73 @@ class TestMixin1D(unittest.TestCase):
|
||||||
5,
|
5,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_positions(self):
|
def test_positions_with_distances(self):
|
||||||
e = Edge.make_line((0, 0, 0), (1, 1, 1))
|
e = Edge.make_line((0, 0, 0), (1, 1, 1))
|
||||||
distances = [i / 4 for i in range(3)]
|
distances = [i / 4 for i in range(3)]
|
||||||
pts = e.positions(distances)
|
pts = e.positions(distances)
|
||||||
for i, position in enumerate(pts):
|
for i, position in enumerate(pts):
|
||||||
self.assertAlmostEqual(position, (i / 4, i / 4, i / 4), 5)
|
self.assertAlmostEqual(position, (i / 4, i / 4, i / 4), 5)
|
||||||
|
|
||||||
|
def test_positions_deflection_line(self):
|
||||||
|
"""Deflection sampling on a straight line should yield exactly 2 points."""
|
||||||
|
e = Edge.make_line((0, 0, 0), (10, 0, 0))
|
||||||
|
pts = e.positions(deflection=0.1)
|
||||||
|
|
||||||
|
self.assertEqual(len(pts), 2)
|
||||||
|
self.assertAlmostEqual(pts[0], (0, 0, 0), 7)
|
||||||
|
self.assertAlmostEqual(pts[1], (10, 0, 0), 7)
|
||||||
|
|
||||||
|
def test_positions_deflection_circle(self):
|
||||||
|
"""Deflection on a C2 curve (circle) should produce multiple points."""
|
||||||
|
radius = 5
|
||||||
|
e = Edge.make_circle(radius)
|
||||||
|
|
||||||
|
pts = e.positions(deflection=0.1)
|
||||||
|
|
||||||
|
# Should produce more than just two points
|
||||||
|
self.assertGreater(len(pts), 2)
|
||||||
|
|
||||||
|
# Endpoints should match curve endpoints
|
||||||
|
first, last = pts[0], pts[-1]
|
||||||
|
curve = e.geom_adaptor()
|
||||||
|
p0 = Vector(curve.Value(curve.FirstParameter()))
|
||||||
|
p1 = Vector(curve.Value(curve.LastParameter()))
|
||||||
|
|
||||||
|
self.assertAlmostEqual(first, p0, 7)
|
||||||
|
self.assertAlmostEqual(last, p1, 7)
|
||||||
|
|
||||||
|
def test_positions_deflection_resolution(self):
|
||||||
|
"""Smaller deflection tolerance should produce more points."""
|
||||||
|
e = Edge.make_circle(10)
|
||||||
|
|
||||||
|
pts_coarse = e.positions(deflection=0.5)
|
||||||
|
pts_fine = e.positions(deflection=0.05)
|
||||||
|
|
||||||
|
self.assertGreater(len(pts_fine), len(pts_coarse))
|
||||||
|
|
||||||
|
def test_positions_deflection_C0_curve(self):
|
||||||
|
"""C0 spline should use QuasiUniformDeflection and still succeed."""
|
||||||
|
e = Polyline((0, 0), (1, 2), (2, 0))._to_bspline() # C0
|
||||||
|
pts = e.positions(deflection=0.1)
|
||||||
|
|
||||||
|
self.assertGreater(len(pts), 2)
|
||||||
|
|
||||||
|
def test_positions_missing_arguments(self):
|
||||||
|
e = Edge.make_line((0, 0, 0), (1, 0, 0))
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
e.positions()
|
||||||
|
|
||||||
|
def test_positions_deflection_failure(self):
|
||||||
|
e = Edge.make_circle(1.0)
|
||||||
|
|
||||||
|
with patch("build123d.topology.one_d.GCPnts_UniformDeflection") as MockDefl:
|
||||||
|
instance = MockDefl.return_value
|
||||||
|
instance.IsDone.return_value = False
|
||||||
|
instance.NbPoints.return_value = 0
|
||||||
|
|
||||||
|
with self.assertRaises(RuntimeError):
|
||||||
|
e.positions(deflection=0.1)
|
||||||
|
|
||||||
def test_tangent_at(self):
|
def test_tangent_at(self):
|
||||||
self.assertAlmostEqual(
|
self.assertAlmostEqual(
|
||||||
Edge.make_circle(1, start_angle=0, end_angle=90).tangent_at(1.0),
|
Edge.make_circle(1, start_angle=0, end_angle=90).tangent_at(1.0),
|
||||||
|
|
@ -368,11 +429,6 @@ class TestMixin1D(unittest.TestCase):
|
||||||
self.assertAlmostEqual(common.z_dir.Y, 0, 5)
|
self.assertAlmostEqual(common.z_dir.Y, 0, 5)
|
||||||
self.assertAlmostEqual(common.z_dir.Z, 0, 5)
|
self.assertAlmostEqual(common.z_dir.Z, 0, 5)
|
||||||
|
|
||||||
def test_discretize(self):
|
|
||||||
edge = Edge.make_circle(2, start_angle=0, end_angle=180)
|
|
||||||
points = edge.discretize(0.1)
|
|
||||||
self.assertEqual(len(points), 6)
|
|
||||||
|
|
||||||
def test_edge_volume(self):
|
def test_edge_volume(self):
|
||||||
edge = Edge.make_line((0, 0), (1, 1))
|
edge = Edge.make_line((0, 0), (1, 1))
|
||||||
self.assertAlmostEqual(edge.volume, 0, 5)
|
self.assertAlmostEqual(edge.volume, 0, 5)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue