mirror of
https://github.com/gumyr/build123d.git
synced 2025-12-06 02:30:55 -08:00
Replacing location_at(planar) with (x_dir)
This commit is contained in:
parent
8c171837ee
commit
0624bff82e
2 changed files with 104 additions and 8 deletions
|
|
@ -120,7 +120,11 @@ from OCP.HLRAlgo import HLRAlgo_Projector
|
|||
from OCP.HLRBRep import HLRBRep_Algo, HLRBRep_HLRToShape
|
||||
from OCP.ShapeAnalysis import ShapeAnalysis_FreeBounds
|
||||
from OCP.ShapeFix import ShapeFix_Shape, ShapeFix_Wireframe
|
||||
from OCP.Standard import Standard_Failure, Standard_NoSuchObject
|
||||
from OCP.Standard import (
|
||||
Standard_Failure,
|
||||
Standard_NoSuchObject,
|
||||
Standard_ConstructionError,
|
||||
)
|
||||
from OCP.TColStd import (
|
||||
TColStd_Array1OfReal,
|
||||
TColStd_HArray1OfBoolean,
|
||||
|
|
@ -511,7 +515,8 @@ class Mixin1D(Shape):
|
|||
distance: float,
|
||||
position_mode: PositionMode = PositionMode.PARAMETER,
|
||||
frame_method: FrameMethod = FrameMethod.FRENET,
|
||||
planar: bool = False,
|
||||
planar: bool | None = None,
|
||||
x_dir: VectorLike | None = None,
|
||||
) -> Location:
|
||||
"""Locations along curve
|
||||
|
||||
|
|
@ -522,8 +527,18 @@ class Mixin1D(Shape):
|
|||
position_mode (PositionMode, optional): position calculation mode.
|
||||
Defaults to PositionMode.PARAMETER.
|
||||
frame_method (FrameMethod, optional): moving frame calculation method.
|
||||
The FRENET frame can “twist” or flip unexpectedly, especially near flat
|
||||
spots. The CORRECTED frame behaves more like a “camera dolly” or
|
||||
sweep profile would — it's smoother and more stable.
|
||||
Defaults to FrameMethod.FRENET.
|
||||
planar (bool, optional): planar mode. Defaults to False.
|
||||
planar (bool, optional): planar mode. Defaults to None.
|
||||
x_dir (VectorLike, optional): override the x_dir to help with plane
|
||||
creation along a 1D shape. Must be perpendicalar to shapes tangent.
|
||||
Defaults to None.
|
||||
|
||||
.. deprecated::
|
||||
The `planar` parameter is deprecated and will be removed in a future release.
|
||||
Use `x_dir` to specify orientation instead.
|
||||
|
||||
Returns:
|
||||
Location: A Location object representing local coordinate system
|
||||
|
|
@ -550,23 +565,45 @@ class Mixin1D(Shape):
|
|||
pnt = curve.Value(param)
|
||||
|
||||
transformation = gp_Trsf()
|
||||
if planar:
|
||||
if planar is not None:
|
||||
warnings.warn(
|
||||
"The 'planar' parameter is deprecated and will be removed in a future version. "
|
||||
"Use 'x_dir' to control orientation instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
if planar is not None and planar:
|
||||
transformation.SetTransformation(
|
||||
gp_Ax3(pnt, gp_Dir(0, 0, 1), gp_Dir(normal.XYZ())), gp_Ax3()
|
||||
)
|
||||
elif x_dir is not None:
|
||||
try:
|
||||
|
||||
transformation.SetTransformation(
|
||||
gp_Ax3(pnt, gp_Dir(tangent.XYZ()), Vector(x_dir).to_dir()), gp_Ax3()
|
||||
)
|
||||
except Standard_ConstructionError:
|
||||
raise ValueError(
|
||||
f"Unable to create location with given x_dir {x_dir}. "
|
||||
f"x_dir must be perpendicular to shape's tangent "
|
||||
f"{tuple(Vector(tangent))}."
|
||||
)
|
||||
|
||||
else:
|
||||
transformation.SetTransformation(
|
||||
gp_Ax3(pnt, gp_Dir(tangent.XYZ()), gp_Dir(normal.XYZ())), gp_Ax3()
|
||||
)
|
||||
loc = Location(TopLoc_Location(transformation))
|
||||
|
||||
return Location(TopLoc_Location(transformation))
|
||||
return loc
|
||||
|
||||
def locations(
|
||||
self,
|
||||
distances: Iterable[float],
|
||||
position_mode: PositionMode = PositionMode.PARAMETER,
|
||||
frame_method: FrameMethod = FrameMethod.FRENET,
|
||||
planar: bool = False,
|
||||
planar: bool | None = None,
|
||||
x_dir: VectorLike | None = None,
|
||||
) -> list[Location]:
|
||||
"""Locations along curve
|
||||
|
||||
|
|
@ -579,13 +616,21 @@ class Mixin1D(Shape):
|
|||
frame_method (FrameMethod, optional): moving frame calculation method.
|
||||
Defaults to FrameMethod.FRENET.
|
||||
planar (bool, optional): planar mode. Defaults to False.
|
||||
x_dir (VectorLike, optional): override the x_dir to help with plane
|
||||
creation along a 1D shape. Must be perpendicalar to shapes tangent.
|
||||
Defaults to None.
|
||||
|
||||
.. deprecated::
|
||||
The `planar` parameter is deprecated and will be removed in a future release.
|
||||
Use `x_dir` to specify orientation instead.
|
||||
|
||||
Returns:
|
||||
list[Location]: A list of Location objects representing local coordinate
|
||||
systems at the specified distances.
|
||||
"""
|
||||
return [
|
||||
self.location_at(d, position_mode, frame_method, planar) for d in distances
|
||||
self.location_at(d, position_mode, frame_method, planar, x_dir)
|
||||
for d in distances
|
||||
]
|
||||
|
||||
def normal(self) -> Vector:
|
||||
|
|
|
|||
|
|
@ -29,8 +29,16 @@ license:
|
|||
import math
|
||||
import unittest
|
||||
|
||||
from build123d.build_enums import CenterOf, GeomType, PositionMode, Side, SortBy
|
||||
from build123d.build_enums import (
|
||||
CenterOf,
|
||||
FrameMethod,
|
||||
GeomType,
|
||||
PositionMode,
|
||||
Side,
|
||||
SortBy,
|
||||
)
|
||||
from build123d.geometry import Axis, Location, Plane, Vector
|
||||
from build123d.objects_curve import Polyline
|
||||
from build123d.objects_part import Box, Cylinder
|
||||
from build123d.topology import Compound, Edge, Face, Wire
|
||||
|
||||
|
|
@ -201,6 +209,18 @@ class TestMixin1D(unittest.TestCase):
|
|||
self.assertAlmostEqual(loc.position, (0, 1, 0), 5)
|
||||
self.assertAlmostEqual(loc.orientation, (0, -90, -90), 5)
|
||||
|
||||
def test_location_at_x_dir(self):
|
||||
path = Polyline((-50, -40), (50, -40), (50, 40), (-50, 40), close=True)
|
||||
l1 = path.location_at(0)
|
||||
l2 = path.location_at(0, x_dir=(0, 1, 0))
|
||||
self.assertAlmostEqual(l1.position, l2.position, 5)
|
||||
self.assertAlmostEqual(l1.z_axis, l2.z_axis, 5)
|
||||
self.assertNotEqual(l1.x_axis, l2.x_axis, 5)
|
||||
self.assertAlmostEqual(l2.x_axis, Axis(path @ 0, (0, 1, 0)), 5)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
path.location_at(0, x_dir=(1, 0, 0))
|
||||
|
||||
def test_locations(self):
|
||||
locs = Edge.make_circle(1).locations([i / 4 for i in range(4)])
|
||||
self.assertAlmostEqual(locs[0].position, (1, 0, 0), 5)
|
||||
|
|
@ -212,6 +232,37 @@ class TestMixin1D(unittest.TestCase):
|
|||
self.assertAlmostEqual(locs[3].position, (0, -1, 0), 5)
|
||||
self.assertAlmostEqual(locs[3].orientation, (0, 90, 90), 5)
|
||||
|
||||
def test_location_at_corrected_frenet(self):
|
||||
# A polyline with sharp corners — problematic for classic Frenet
|
||||
path = Polyline((0, 0), (10, 0), (10, 10), (0, 10))
|
||||
|
||||
# Request multiple locations along the curve
|
||||
locations = [
|
||||
path.location_at(t, frame_method=FrameMethod.CORRECTED)
|
||||
for t in [0.0, 0.25, 0.5, 0.75, 1.0]
|
||||
]
|
||||
# Ensure all locations were created and have consistent orientation
|
||||
self.assertTrue(
|
||||
all(
|
||||
locations[0].x_axis.direction == l.x_axis.direction
|
||||
for l in locations[1:]
|
||||
)
|
||||
)
|
||||
|
||||
# Check that Z-axis is approximately orthogonal to X-axis
|
||||
for loc in locations:
|
||||
self.assertLess(abs(loc.z_axis.direction.dot(loc.x_axis.direction)), 1e-6)
|
||||
|
||||
# Check continuity of rotation (not flipping wildly)
|
||||
# Check angle between x_axes doesn't flip more than ~90 degrees
|
||||
angles = []
|
||||
for i in range(len(locations) - 1):
|
||||
a1 = locations[i].x_axis.direction
|
||||
a2 = locations[i + 1].x_axis.direction
|
||||
angle = a1.get_angle(a2)
|
||||
angles.append(angle)
|
||||
self.assertTrue(all(abs(angle) < 90 for angle in angles))
|
||||
|
||||
def test_project(self):
|
||||
target = Face.make_rect(10, 10, Plane.XY.rotated((0, 45, 0)))
|
||||
circle = Edge.make_circle(1).locate(Location((0, 0, 10)))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue