Fixed param_at_point Issues #570 & #708

This commit is contained in:
gumyr 2024-10-02 15:44:10 -04:00
parent 55f6e8f073
commit 04cf1efd89
2 changed files with 48 additions and 17 deletions

View file

@ -65,6 +65,8 @@ from typing_extensions import Self, Literal, deprecated
from anytree import NodeMixin, PreOrderIter, RenderTree
from IPython.lib.pretty import pretty
from numpy import ndarray
from scipy.optimize import minimize
from scipy.spatial import ConvexHull
from vtkmodules.vtkCommonDataModel import vtkPolyData
from vtkmodules.vtkFiltersCore import vtkPolyDataNormals, vtkTriangleFilter
@ -3730,13 +3732,17 @@ class ShapeList(list[T]):
def __eq__(self, other: object):
"""ShapeLists equality operator =="""
return set(self) == set(other) if isinstance(other, ShapeList) else NotImplemented
return (
set(self) == set(other) if isinstance(other, ShapeList) else NotImplemented
)
# Normally implementing __eq__ is enough, but ShapeList subclasses list,
# which already implements __ne__, so we need to override it, too
def __ne__(self, other: ShapeList):
"""ShapeLists inequality operator !="""
return set(self) != set(other) if isinstance(other, ShapeList) else NotImplemented
return (
set(self) != set(other) if isinstance(other, ShapeList) else NotImplemented
)
def __add__(self, other: ShapeList):
"""Combine two ShapeLists together operator +"""
@ -4849,28 +4855,42 @@ class Edge(Mixin1D, Shape):
return Edge(new_edge)
def param_at_point(self, point: VectorLike) -> float:
"""Parameter at point of Edge"""
"""Normalized parameter at point along Edge"""
def _project_point_on_curve(curve, gp_pnt) -> float:
projector = GeomAPI_ProjectPointOnCurve(gp_pnt, curve)
parameter = projector.LowerDistanceParameter()
return parameter
# Note that this search algorithm would ideally be replaced with
# an OCP based solution, something like that which is shown below.
# However, there are known issues with the OCP methods for some
# curves which may return negative values or incorrect values at
# end points. Also note that this search takes about 1.5ms while
# the OCP methods take about 0.4ms.
#
# curve = BRep_Tool.Curve_s(self.wrapped, float(), float())
# param_min, param_max = BRep_Tool.Range_s(self.wrapped)
# projector = GeomAPI_ProjectPointOnCurve(point.to_pnt(), curve)
# param_value = projector.LowerDistanceParameter()
# u_value = (param_value - param_min) / (param_max - param_min)
point = Vector(point)
if not isclose_b(self.distance_to(point), 0, abs_tol=TOLERANCE):
raise ValueError(f"point ({point}) is not on edge")
# Get the extreme of the parameter values for this Edge/Wire
curve = BRep_Tool.Curve_s(self.wrapped, 0, 1)
param_min = _project_point_on_curve(curve, self.position_at(0).to_pnt())
param_value = _project_point_on_curve(curve, point.to_pnt())
if self.is_closed:
u_value = (param_value - param_min) / (self.param_at(1) - self.param_at(0))
else:
param_max = _project_point_on_curve(curve, self.position_at(1).to_pnt())
u_value = (param_value - param_min) / (param_max - param_min)
# Function to be minimized
def func(param: ndarray) -> float:
return (self.position_at(param[0]) - point).length
# Find the u value that results in a point within tolerance of the target
initial_guess = max(
0.0, min(1.0, (point - self.position_at(0)).length / self.length)
)
result = minimize(
func,
x0=initial_guess,
method="Nelder-Mead",
bounds=[(0.0, 1.0)],
tol=TOLERANCE,
)
u_value = float(result.x[0])
return u_value
@classmethod

View file

@ -52,7 +52,7 @@ from build123d.operations_part import extrude
from build123d.operations_sketch import make_face
from build123d.operations_generic import fillet, add, sweep
from build123d.objects_part import Box, Cylinder
from build123d.objects_curve import JernArc, Polyline
from build123d.objects_curve import CenterArc, EllipticalCenterArc, JernArc, Polyline
from build123d.build_sketch import BuildSketch
from build123d.build_line import BuildLine
from build123d.objects_curve import Spline
@ -378,6 +378,7 @@ class TestAxis(DirectApiTestCase):
random_obj = object()
self.assertNotEqual(Axis.X, random_obj)
class TestBoundBox(DirectApiTestCase):
def test_basic_bounding_box(self):
v = Vertex(1, 1, 1)
@ -1086,6 +1087,16 @@ class TestEdge(DirectApiTestCase):
pnt = edge.position_at(u)
self.assertAlmostEqual(edge.param_at_point(pnt), u, 5)
ca = CenterArc((0, 0), 1, -200, 220).edge()
for u in [0.3, 1.0]:
pnt = ca.position_at(u)
self.assertAlmostEqual(ca.param_at_point(pnt), u, 5)
ea = EllipticalCenterArc((15, 0), 10, 5, start_angle=90, end_angle=270).edge()
for u in [0.3, 0.9]:
pnt = ea.position_at(u)
self.assertAlmostEqual(ea.param_at_point(pnt), u, 5)
with self.assertRaises(ValueError):
edge.param_at_point((-1, 1))