Skip to content

Commit 06b9d3a

Browse files
committed
[Refactor NURBS surface processing and intersection algorithms]
1. mmcore/geom/surfaces/__init__.py Refactor imports and improve surface handling methods. - Added new imports `NURBSCurve` and `interpolate_nurbs_curve` to extend functionality for parametric NURBS curve operations. - Modified `interval()` method to return the curve's native interval, improving consistency in surface interpolation. These changes enhance flexibility in handling NURBS fidelity and operational accuracy. 2. mmcore/numeric/intersection/csx/__init__.py Improve intersection imports for NURBS surfaces and curves. - Replaced redundant imports with precise references to `NURBSCurve` and `NURBSSurface`. - Streamlined intersection operations by removing unnecessary dependencies. This enhances clarity and modularity for geometric operations. 3. mmcore/compat/step/step_writer.py Update boundary extraction functionality for STEP compatibility. - Replaced `extract_surface_boundaries` import to leverage new methods in `nurbs_iso`. - This adjustment aligns boundary computation methods across the module, improving reuse and integration for STEP export tasks. 4. mmcore/topo/mesh/tess.py Revise surface tessellation algorithms and add boundary adaptive methods. - Introduced `tess_boundaries` for adaptive boundary tessellation supporting specified tolerances. - Refactored core tessellate functions for enhanced customizability, replacing boundary density with adaptive polyline calculations. These updates enable precise and resource-efficient surface tessellation, especially for complex surfaces. 5. examples/ssx/nurbs_nurbs_intersection_3.py Increase precision in NURBS surface-surface intersection tests. - Adjusted `tol` parameter for `ssx` function from `1e-4` to `1e-7` to improve calculation accuracy in intersection results. This ensures high fidelity in numerical validations. 6. mmcore/numeric/intersection/ssx/_detect_intersections.py Update isocurve extraction for consistency with new imports. - Replaced `extract_isocurve` to use `nurbs_iso` module, ensuring uniform access to updated NURBS handling utilities. 7. mmcore/numeric/gauss_map.py Align intersection utilities for Gaussian map processing. - Modified isocurve extraction imports, replacing `boundary_intersection` with `nurbs_iso` functions. This maintains synchronization with recent framework enhancements. 8. mmcore/topo/brep/__init__.py Adjust BREP surface boundary extraction functionality. - Replaced boundary extraction methods with optimized utilities under `nurbs_iso`. Improved modular consistency simplifies surface operations. 9. tests/test_nurbs_extend.py Add comprehensive NURBS surface extension tests. - Created 1100+ lines of tests to validate new features including adaptive polyline approximation and intersection precision. - Introduced cases for boundary density and parameterization variations, ensuring robust regression coverage for recent changes.
1 parent db0857c commit 06b9d3a

File tree

18 files changed

+1631
-291
lines changed

18 files changed

+1631
-291
lines changed

examples/ssx/nurbs_nurbs_intersection_3.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424

2525

2626
s=time.time()
27-
result=ssx(s1,s2,tol=1e-4,spt=0.001)
27+
result=ssx(s1,s2,tol=1e-7,spt=0.001)
2828

2929

3030

mmcore/compat/step/step_writer.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,7 @@
88
from itertools import count
99
from mmcore.geom.nurbs import NURBSCurve, NURBSSurface
1010

11-
from mmcore.numeric.intersection.ssx.boundary_intersection import extract_surface_boundaries
12-
13-
11+
from mmcore.geom.nurbs_iso import extract_surface_boundaries
1412

1513
import re
1614

mmcore/geom/nurbs_iso.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
from __future__ import annotations
2+
3+
from typing import List
4+
5+
import numpy as np
6+
from mmcore.geom.nurbs import NURBSSurface, NURBSCurve, find_span, basis_functions
7+
8+
9+
def extract_surface_boundaries(surface: NURBSSurface) -> List[NURBSCurve]:
10+
"""
11+
Extract the four boundary curves of a NURBS surface.
12+
13+
Args:
14+
surface (NURBSSurface): The input NURBS surface
15+
16+
Returns:
17+
List[NURBSCurve]: List of four boundary curves in order:
18+
[u=0 curve, u=1 curve, v=0 curve, v=1 curve]
19+
"""
20+
(u_min, u_max), (v_min, v_max) = surface.interval()
21+
22+
# Extract iso-curves at the boundaries
23+
24+
25+
u0_curve = extract_isocurve(surface, u_min, 'u') # v-direction curve at u=0
26+
u1_curve = extract_isocurve(surface, u_max, 'u') # v-direction curve at u=1
27+
v0_curve = extract_isocurve(surface, v_min, 'v') # u-direction curve at v=0
28+
v1_curve = extract_isocurve(surface, v_max, 'v') # u-direction curve at v=1
29+
30+
return [u0_curve, u1_curve, v0_curve, v1_curve]
31+
32+
33+
def extract_isocurve(
34+
surface: NURBSSurface, param: float, direction: str = "u"
35+
) -> NURBSCurve:
36+
"""
37+
Extract an isocurve from a NURBS surface at a given parameter in the u or v direction.
38+
Args:
39+
surface (NURBSSurface): The input NURBS surface.
40+
param (float): The parameter value at which to extract the isocurve.
41+
direction (str): The direction of the isocurve, either 'u' or 'v'. Default is 'u'.
42+
Returns:
43+
NURBSCurve: The extracted isocurve as a NURBS curve.
44+
Raises:
45+
ValueError: If the direction is not 'u' or 'v', or if the param is out of range.
46+
"""
47+
if direction not in ["u", "v"]:
48+
raise ValueError("Direction must be either 'u' or 'v'.")
49+
interval = surface.interval()
50+
#print('ij', surface.knots_u, surface.knots_v, surface.interval())
51+
if direction == "u":
52+
# For u-direction: we fix u and vary v
53+
# First check if the u parameter is in range
54+
param_range = interval[0] # u range
55+
if param < param_range[0] or param > param_range[1]:
56+
raise ValueError(f"Parameter {param} is out of range {param_range}")
57+
58+
# Find the span and basis functions in u direction (the direction we're fixing)
59+
n_u = surface.shape[0] - 1 # number of control points in u direction - 1
60+
degree_u = surface.degree[0]
61+
span = find_span(n_u, degree_u, param, surface.knots_u, 0)
62+
basis = basis_functions(span, param, degree_u, surface.knots_u)
63+
64+
# The resulting curve will have as many control points as the surface has in v direction
65+
m = surface.shape[1]
66+
control_points = np.zeros((m, 4))
67+
68+
# Compute control points for the extracted curve
69+
for i in range(m): # iterate over v direction
70+
for j in range(degree_u + 1): # combine with basis functions
71+
control_points[i] += basis[j] * surface.control_points_w[span - degree_u + j, i]
72+
73+
# Return curve with v-direction degree and knots since we're varying in v
74+
75+
cc=NURBSCurve(control_points, degree=surface.degree[1],knots=surface.knots_v)
76+
#cc.knots=surface.knots_v
77+
78+
#print('j', cc.knots,cc.interval())
79+
return cc
80+
81+
else: # direction == 'v'
82+
# For v-direction: we fix v and vary u
83+
# First check if the v parameter is in range
84+
param_range = interval[1] # v range
85+
if param < param_range[0] or param > param_range[1]:
86+
raise ValueError(f"Parameter {param} is out of range {param_range}")
87+
88+
# Find the span and basis functions in v direction (the direction we're fixing)
89+
n_v = surface.shape[1] - 1 # number of control points in v direction - 1
90+
degree_v = surface.degree[1]
91+
span = find_span(n_v, degree_v, param, surface.knots_v, 0)
92+
basis = basis_functions(span, param, degree_v, surface.knots_v)
93+
94+
# The resulting curve will have as many control points as the surface has in u direction
95+
m = surface.shape[0]
96+
control_points = np.zeros((m, 4))
97+
98+
# Compute control points for the extracted curve
99+
for i in range(m): # iterate over u direction
100+
for j in range(degree_v + 1): # combine with basis functions
101+
control_points[i] += basis[j] * surface.control_points_w[i, span - degree_v + j]
102+
cc = NURBSCurve(control_points, surface.degree[0],surface.knots_u)
103+
#print('i',cc.knots,cc.interval())
104+
#cc.knots = surface.knots_u
105+
# Return curve with u-direction degree and knots since we're varying in u
106+
return cc

mmcore/geom/surfaces/__init__.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from scipy.integrate import quad
1212
from scipy.interpolate import interp1d
1313

14+
from mmcore.geom.curves.bspline_first import NURBSCurve
1415
from mmcore.geom.curves.curve import Curve
1516
from mmcore.geom.parametric import ParametricCurve
1617
from mmcore.geom.parametric import BiLinear as CBiLinear
@@ -22,7 +23,7 @@
2223
from mmcore.numeric.numeric import evaluate_normal2
2324
from mmcore.numeric.vectors import scalar_dot, scalar_cross, scalar_unit, scalar_norm
2425

25-
from mmcore.topo.mesh.tess import as_bvh, tessellate_surface
26+
from mmcore.topo.mesh.tess import tessellate_surface
2627

2728

2829
class TwoPointForm:
@@ -97,7 +98,8 @@ def compute_intersection_curvature(Su1, Sv1, Suu1, Suv1, Svv1, Su2, Sv2, Suu2, S
9798

9899
from mmcore.geom.implicit import ParametrizedImplicit2D
99100

100-
101+
from mmcore.numeric.algorithms.adaptive_polyline import adaptive_polyline
102+
from mmcore.geom.curves.bspline import interpolate_nurbs_curve
101103
class CurveOnSurface(Curve):
102104
def __init__(self, surf: "Surface", curve: "Curve|Callable", interval=(0., 1.)):
103105
super().__init__()
@@ -144,13 +146,15 @@ def evaluate_curve(self, t):
144146
return self._eval_crv_func(t)[..., :2]
145147

146148
def interval(self):
147-
return self._interval
149+
return self.curve.interval()
148150

149151
def point_inside(self, uv):
150152

151153
return point_in_parametric_curve(self.curve, uv)
152154

153155

156+
157+
154158
from mmcore.geom.bvh import BVHNode, contains_point
155159
from mmcore.numeric.divide_and_conquer import divide_and_conquer_min_2d
156160
from mmcore.numeric.fdm import gradient as fgrdient

mmcore/numeric/algorithms/adaptive_polyline.py

Lines changed: 35 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,54 @@
11
import numpy as np
2-
2+
import sys
3+
sys.setrecursionlimit(100000)
34
from mmcore.geom.nurbs import NURBSCurve
45
from mmcore.numeric.vectors import norm,dot
56

67
def chord_length(R, h):
78
return 2 * np.sqrt(2 * R * h - (h * h))
89

10+
def adaptive_polyline(curve: NURBSCurve, tol:float, max_depth=10):
11+
if isinstance(curve,NURBSCurve):
12+
greville_abscissae_points=np.array(curve.evaluate_multi(curve.greville_abscissae))
13+
if curve.degree < 2 :
914

10-
def adaptive_polyline(curve: NURBSCurve, tol:float):
11-
greville_abscissae_points=np.array(curve.evaluate_multi(curve.greville_abscissae))
12-
if curve.degree < 2 :
13-
14-
return greville_abscissae_points, curve.greville_abscissae
15-
normals=np.asarray(curve.control_points) - greville_abscissae_points
16-
dn=np.array(norm(normals))
17-
if np.allclose(dn,0):
18-
return greville_abscissae_points, curve.greville_abscissae
19-
_res=[]
20-
for i in range(normals.shape[0]):
21-
if np.isclose(dn[i],0):
22-
continue
15+
return greville_abscissae_points, curve.greville_abscissae
16+
normals=np.asarray(curve.control_points) - greville_abscissae_points
17+
dn=np.array(norm(normals))
18+
if np.allclose(dn,0):
19+
return greville_abscissae_points, curve.greville_abscissae
20+
_res=[]
21+
for i in range(normals.shape[0]):
22+
if np.isclose(dn[i],0):
23+
continue
2324

24-
n=normals[i]
25-
n/=dn[i]
26-
t=curve.greville_abscissae[i]
27-
tangent=np.array(curve.derivative( t))
28-
tangent/=np.linalg.norm(tangent
29-
)
30-
_res.append(1.-np.abs(np.dot(n,tangent)))
25+
n=normals[i]
26+
n/=dn[i]
27+
t=curve.greville_abscissae[i]
28+
tangent=np.array(curve.derivative( t))
29+
tangent/=np.linalg.norm(tangent
30+
)
31+
_res.append(1.-np.abs(np.dot(n,tangent)))
3132

3233

3334

34-
if np.allclose(_res,0):
35-
return greville_abscissae_points, curve.greville_abscissae
35+
if np.allclose(_res,0):
36+
return greville_abscissae_points, curve.greville_abscissae
3637

3738

39+
else:
40+
t0, t1 = curve.interval()
3841

42+
_prms = t0 + (t1 - t0) * np.random.random(7)
43+
if np.all([np.allclose(curve.curvature(_p), 0) for _p in _prms]):
44+
return curve.points()
3945

4046
params = [*tuple(curve.interval())]
4147
points = [curve.evaluate(params[0]), curve.evaluate(params[1])]
4248

43-
def subdivide(curve,t0, t1, p0, p1, tol, points_list):
49+
def subdivide(curve,t0, t1, p0, p1, tol, points_list, current_depth):
50+
if current_depth>=max_depth:
51+
return
4452
segment_length = np.linalg.norm(p0 - p1)
4553

4654
t_mid = (t0 + t1) / 2
@@ -57,12 +65,12 @@ def subdivide(curve,t0, t1, p0, p1, tol, points_list):
5765
if (L >= segment_length) and (np.linalg.norm(p_curve_mid - p_line_mid) < tol):
5866
return
5967
# Subdivide further
60-
subdivide(curve,t0, t_mid, p0, p_curve_mid, tol, points_list)
68+
subdivide(curve,t0, t_mid, p0, p_curve_mid, tol, points_list, current_depth+1)
6169
points_list.append((t_mid, p_curve_mid))
62-
subdivide(curve,t_mid, t1, p_curve_mid, p1, tol, points_list)
70+
subdivide(curve,t_mid, t1, p_curve_mid, p1, tol, points_list, current_depth+1)
6371

6472
points_list = []
65-
subdivide(curve,params[0], params[1], points[0], points[1], tol, points_list)
73+
subdivide(curve,params[0], params[1], points[0], points[1], tol, points_list, 0)
6674

6775
# Sort points based on parameter to maintain correct order
6876
points_list.sort(key=lambda x: x[0])

mmcore/numeric/gauss_map.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
from mmcore.numeric.algorithms.surface_area import v_min
1111
from mmcore.numeric.intersection.csx import nurbs_csx
12-
from mmcore.numeric.intersection.ssx.boundary_intersection import extract_isocurve
12+
from mmcore.geom.nurbs_iso import extract_isocurve
1313
from mmcore.numeric.monomial import bezier_to_monomial, monomial_to_bezier
1414
from mmcore.numeric.vectors import unit, scalar_dot, scalar_norm
1515
from mmcore.numeric.algorithms.cygjk import gjk

mmcore/numeric/intersection/csx/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
from mmcore.numeric.vectors import scalar_norm, norm
44
from numpy._typing import NDArray
55

6-
from mmcore.geom.curves.curve import curve_bvh
6+
77
from mmcore.geom.nurbs import NURBSCurve, NURBSSurface
8-
from mmcore.geom.surfaces import surface_bvh
8+
99
from mmcore.numeric.plane import inverse_evaluate_plane
1010
from mmcore.numeric.algorithms.point_inversion import point_inversion_surface
1111
from mmcore.numeric.intersection.ccx import curve_pix

mmcore/numeric/intersection/ssx/_detect_intersections.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from mmcore.numeric.gauss_map import GaussMap
1414
from mmcore.geom.bvh import BoundingBox, Object3D
1515

16-
from mmcore.numeric.intersection.ssx.boundary_intersection import extract_isocurve
16+
from mmcore.geom.nurbs_iso import extract_isocurve
1717

1818

1919
class DebugTree:

mmcore/numeric/intersection/ssx/_terminator.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
from mmcore.numeric.vectors import scalar_norm
1212

1313
from mmcore.numeric.intersection.csx import curve_surface_intersection, nurbs_csx
14-
from mmcore.numeric.intersection.ssx.boundary_intersection import extract_isocurve, find_boundary_intersections
14+
from mmcore.numeric.intersection.ssx.boundary_intersection import find_boundary_intersections
15+
from mmcore.geom.nurbs_iso import extract_isocurve
1516

1617

1718
class TerminatorType(int, Enum):

0 commit comments

Comments
 (0)