coordinate-system 7.0.0__cp313-cp313-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- coordinate_system/__init__.py +324 -0
- coordinate_system/complex_geometric_physics.py +519 -0
- coordinate_system/coordinate_system.cp313-win_amd64.pyd +0 -0
- coordinate_system/curve_interpolation.py +507 -0
- coordinate_system/differential_geometry.py +986 -0
- coordinate_system/spectral_geometry.py +1602 -0
- coordinate_system/u3_frame.py +885 -0
- coordinate_system/visualization.py +1069 -0
- coordinate_system-7.0.0.dist-info/LICENSE +9 -0
- coordinate_system-7.0.0.dist-info/METADATA +312 -0
- coordinate_system-7.0.0.dist-info/RECORD +13 -0
- coordinate_system-7.0.0.dist-info/WHEEL +5 -0
- coordinate_system-7.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1069 @@
|
|
|
1
|
+
"""
|
|
2
|
+
3D Coordinate System and Surface Visualization Module
|
|
3
|
+
=====================================================
|
|
4
|
+
|
|
5
|
+
Comprehensive visualization tools for coordinate systems, curves, and surfaces.
|
|
6
|
+
|
|
7
|
+
Features:
|
|
8
|
+
- Coordinate frame visualization with RGB color scheme (X=Red, Y=Green, Z=Blue)
|
|
9
|
+
- Parametric curve visualization with Frenet frames
|
|
10
|
+
- Surface rendering with curvature coloring
|
|
11
|
+
- Frame field visualization on surfaces
|
|
12
|
+
- Multiple view angles and animation support
|
|
13
|
+
|
|
14
|
+
Author: Coordinate System Package
|
|
15
|
+
Date: 2025-12-03
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import numpy as np
|
|
19
|
+
import matplotlib.pyplot as plt
|
|
20
|
+
from mpl_toolkits.mplot3d import Axes3D
|
|
21
|
+
from matplotlib import cm
|
|
22
|
+
from matplotlib.colors import Normalize, LinearSegmentedColormap
|
|
23
|
+
import matplotlib.animation as animation
|
|
24
|
+
from typing import List, Optional, Tuple, Callable, Union, Dict
|
|
25
|
+
|
|
26
|
+
# Import from the C extension module
|
|
27
|
+
try:
|
|
28
|
+
from .coordinate_system import vec3, coord3
|
|
29
|
+
except ImportError:
|
|
30
|
+
import coordinate_system
|
|
31
|
+
vec3 = coordinate_system.vec3
|
|
32
|
+
coord3 = coordinate_system.coord3
|
|
33
|
+
|
|
34
|
+
# Import differential geometry for surface visualization
|
|
35
|
+
try:
|
|
36
|
+
from .differential_geometry import Surface, Sphere, Torus, compute_gaussian_curvature, compute_mean_curvature
|
|
37
|
+
except ImportError:
|
|
38
|
+
Surface = None
|
|
39
|
+
Sphere = None
|
|
40
|
+
Torus = None
|
|
41
|
+
compute_gaussian_curvature = None
|
|
42
|
+
compute_mean_curvature = None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# ============================================================
|
|
46
|
+
# Color Schemes
|
|
47
|
+
# ============================================================
|
|
48
|
+
|
|
49
|
+
# Curvature colormap: blue (negative) -> white (zero) -> red (positive)
|
|
50
|
+
CURVATURE_COLORS = [
|
|
51
|
+
(0.0, 'blue'),
|
|
52
|
+
(0.5, 'white'),
|
|
53
|
+
(1.0, 'red')
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
def create_curvature_colormap():
|
|
57
|
+
"""Create a diverging colormap for curvature visualization."""
|
|
58
|
+
return LinearSegmentedColormap.from_list('curvature',
|
|
59
|
+
[(0.0, 'blue'), (0.5, 'white'), (1.0, 'red')])
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# ============================================================
|
|
63
|
+
# Coordinate System Visualizer
|
|
64
|
+
# ============================================================
|
|
65
|
+
|
|
66
|
+
class CoordinateSystemVisualizer:
|
|
67
|
+
"""
|
|
68
|
+
3D coordinate system visualization tool.
|
|
69
|
+
|
|
70
|
+
RGB color scheme:
|
|
71
|
+
- X axis: Red
|
|
72
|
+
- Y axis: Green
|
|
73
|
+
- Z axis: Blue
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
def __init__(self, figsize: Tuple[int, int] = (12, 9), dpi: int = 100):
|
|
77
|
+
"""
|
|
78
|
+
Initialize visualizer.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
figsize: Figure size (width, height) in inches
|
|
82
|
+
dpi: Dots per inch for rendering
|
|
83
|
+
"""
|
|
84
|
+
self.fig = plt.figure(figsize=figsize, dpi=dpi)
|
|
85
|
+
self.ax = self.fig.add_subplot(111, projection='3d')
|
|
86
|
+
self._setup_axis()
|
|
87
|
+
|
|
88
|
+
def _setup_axis(self):
|
|
89
|
+
"""Configure axis style."""
|
|
90
|
+
self.ax.set_xlabel('X', fontsize=12, color='red', fontweight='bold')
|
|
91
|
+
self.ax.set_ylabel('Y', fontsize=12, color='green', fontweight='bold')
|
|
92
|
+
self.ax.set_zlabel('Z', fontsize=12, color='blue', fontweight='bold')
|
|
93
|
+
self.ax.grid(True, alpha=0.3, linestyle='--')
|
|
94
|
+
self.ax.xaxis.pane.fill = False
|
|
95
|
+
self.ax.yaxis.pane.fill = False
|
|
96
|
+
self.ax.zaxis.pane.fill = False
|
|
97
|
+
|
|
98
|
+
def draw_coord_system(
|
|
99
|
+
self,
|
|
100
|
+
coord: coord3,
|
|
101
|
+
scale: float = 1.0,
|
|
102
|
+
linewidth: float = 2.0,
|
|
103
|
+
alpha: float = 0.8,
|
|
104
|
+
label_prefix: str = "",
|
|
105
|
+
arrow_style: bool = True
|
|
106
|
+
):
|
|
107
|
+
"""
|
|
108
|
+
Draw a single coordinate frame.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
coord: coord3 object
|
|
112
|
+
scale: Axis length scale factor
|
|
113
|
+
linewidth: Line width
|
|
114
|
+
alpha: Transparency
|
|
115
|
+
label_prefix: Label prefix for legend
|
|
116
|
+
arrow_style: Use arrow heads if True
|
|
117
|
+
"""
|
|
118
|
+
origin = coord.o
|
|
119
|
+
|
|
120
|
+
if arrow_style:
|
|
121
|
+
# Use quiver for arrows
|
|
122
|
+
self.ax.quiver(
|
|
123
|
+
origin.x, origin.y, origin.z,
|
|
124
|
+
coord.ux.x * scale, coord.ux.y * scale, coord.ux.z * scale,
|
|
125
|
+
color='red', linewidth=linewidth, alpha=alpha,
|
|
126
|
+
arrow_length_ratio=0.15
|
|
127
|
+
)
|
|
128
|
+
self.ax.quiver(
|
|
129
|
+
origin.x, origin.y, origin.z,
|
|
130
|
+
coord.uy.x * scale, coord.uy.y * scale, coord.uy.z * scale,
|
|
131
|
+
color='green', linewidth=linewidth, alpha=alpha,
|
|
132
|
+
arrow_length_ratio=0.15
|
|
133
|
+
)
|
|
134
|
+
self.ax.quiver(
|
|
135
|
+
origin.x, origin.y, origin.z,
|
|
136
|
+
coord.uz.x * scale, coord.uz.y * scale, coord.uz.z * scale,
|
|
137
|
+
color='blue', linewidth=linewidth, alpha=alpha,
|
|
138
|
+
arrow_length_ratio=0.15
|
|
139
|
+
)
|
|
140
|
+
else:
|
|
141
|
+
# Use simple lines
|
|
142
|
+
x_end = origin + coord.ux * scale
|
|
143
|
+
y_end = origin + coord.uy * scale
|
|
144
|
+
z_end = origin + coord.uz * scale
|
|
145
|
+
|
|
146
|
+
self.ax.plot([origin.x, x_end.x], [origin.y, x_end.y], [origin.z, x_end.z],
|
|
147
|
+
'r-', linewidth=linewidth, alpha=alpha)
|
|
148
|
+
self.ax.plot([origin.x, y_end.x], [origin.y, y_end.y], [origin.z, y_end.z],
|
|
149
|
+
'g-', linewidth=linewidth, alpha=alpha)
|
|
150
|
+
self.ax.plot([origin.x, z_end.x], [origin.y, z_end.y], [origin.z, z_end.z],
|
|
151
|
+
'b-', linewidth=linewidth, alpha=alpha)
|
|
152
|
+
|
|
153
|
+
def draw_world_coord(
|
|
154
|
+
self,
|
|
155
|
+
origin: vec3 = None,
|
|
156
|
+
scale: float = 1.0,
|
|
157
|
+
linewidth: float = 3.0
|
|
158
|
+
):
|
|
159
|
+
"""
|
|
160
|
+
Draw world coordinate system.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
origin: Origin position, default is (0, 0, 0)
|
|
164
|
+
scale: Axis length
|
|
165
|
+
linewidth: Line width
|
|
166
|
+
"""
|
|
167
|
+
if origin is None:
|
|
168
|
+
origin = vec3(0, 0, 0)
|
|
169
|
+
|
|
170
|
+
world_coord = coord3()
|
|
171
|
+
world_coord.o = origin
|
|
172
|
+
|
|
173
|
+
self.draw_coord_system(
|
|
174
|
+
world_coord,
|
|
175
|
+
scale=scale,
|
|
176
|
+
linewidth=linewidth,
|
|
177
|
+
alpha=1.0,
|
|
178
|
+
label_prefix="World-"
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
def draw_point(
|
|
182
|
+
self,
|
|
183
|
+
point: vec3,
|
|
184
|
+
color: str = 'black',
|
|
185
|
+
size: float = 50,
|
|
186
|
+
marker: str = 'o',
|
|
187
|
+
label: str = None
|
|
188
|
+
):
|
|
189
|
+
"""
|
|
190
|
+
Draw a single point.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
point: Point position
|
|
194
|
+
color: Point color
|
|
195
|
+
size: Marker size
|
|
196
|
+
marker: Marker style
|
|
197
|
+
label: Label for legend
|
|
198
|
+
"""
|
|
199
|
+
self.ax.scatter([point.x], [point.y], [point.z],
|
|
200
|
+
c=color, s=size, marker=marker, label=label)
|
|
201
|
+
|
|
202
|
+
def draw_vector(
|
|
203
|
+
self,
|
|
204
|
+
start: vec3,
|
|
205
|
+
direction: vec3,
|
|
206
|
+
color: str = 'black',
|
|
207
|
+
linewidth: float = 2.0,
|
|
208
|
+
alpha: float = 0.8,
|
|
209
|
+
label: str = None
|
|
210
|
+
):
|
|
211
|
+
"""
|
|
212
|
+
Draw a vector as an arrow.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
start: Starting point
|
|
216
|
+
direction: Direction vector
|
|
217
|
+
color: Arrow color
|
|
218
|
+
linewidth: Line width
|
|
219
|
+
alpha: Transparency
|
|
220
|
+
label: Label for legend
|
|
221
|
+
"""
|
|
222
|
+
self.ax.quiver(
|
|
223
|
+
start.x, start.y, start.z,
|
|
224
|
+
direction.x, direction.y, direction.z,
|
|
225
|
+
color=color, linewidth=linewidth, alpha=alpha,
|
|
226
|
+
arrow_length_ratio=0.15, label=label
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
def set_equal_aspect(self):
|
|
230
|
+
"""Set equal aspect ratio for all axes."""
|
|
231
|
+
xlim = self.ax.get_xlim3d()
|
|
232
|
+
ylim = self.ax.get_ylim3d()
|
|
233
|
+
zlim = self.ax.get_zlim3d()
|
|
234
|
+
|
|
235
|
+
x_range = abs(xlim[1] - xlim[0])
|
|
236
|
+
y_range = abs(ylim[1] - ylim[0])
|
|
237
|
+
z_range = abs(zlim[1] - zlim[0])
|
|
238
|
+
|
|
239
|
+
max_range = max(x_range, y_range, z_range)
|
|
240
|
+
|
|
241
|
+
x_middle = np.mean(xlim)
|
|
242
|
+
y_middle = np.mean(ylim)
|
|
243
|
+
z_middle = np.mean(zlim)
|
|
244
|
+
|
|
245
|
+
self.ax.set_xlim3d([x_middle - max_range/2, x_middle + max_range/2])
|
|
246
|
+
self.ax.set_ylim3d([y_middle - max_range/2, y_middle + max_range/2])
|
|
247
|
+
self.ax.set_zlim3d([z_middle - max_range/2, z_middle + max_range/2])
|
|
248
|
+
|
|
249
|
+
def set_view(self, elev: float = 30, azim: float = 45):
|
|
250
|
+
"""
|
|
251
|
+
Set camera view angle.
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
elev: Elevation angle in degrees
|
|
255
|
+
azim: Azimuth angle in degrees
|
|
256
|
+
"""
|
|
257
|
+
self.ax.view_init(elev=elev, azim=azim)
|
|
258
|
+
|
|
259
|
+
def set_title(self, title: str, fontsize: int = 14):
|
|
260
|
+
"""Set plot title."""
|
|
261
|
+
self.ax.set_title(title, fontsize=fontsize, fontweight='bold')
|
|
262
|
+
|
|
263
|
+
def show(self):
|
|
264
|
+
"""Display the figure."""
|
|
265
|
+
self.ax.legend(loc='upper left')
|
|
266
|
+
plt.tight_layout()
|
|
267
|
+
plt.show()
|
|
268
|
+
|
|
269
|
+
def save(self, filename: str, dpi: int = 300):
|
|
270
|
+
"""
|
|
271
|
+
Save figure to file.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
filename: Output filename
|
|
275
|
+
dpi: Resolution
|
|
276
|
+
"""
|
|
277
|
+
self.ax.legend(loc='upper left')
|
|
278
|
+
plt.tight_layout()
|
|
279
|
+
plt.savefig(filename, dpi=dpi, bbox_inches='tight')
|
|
280
|
+
print(f"Saved: {filename}")
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
# ============================================================
|
|
284
|
+
# Surface Visualizer
|
|
285
|
+
# ============================================================
|
|
286
|
+
|
|
287
|
+
class SurfaceVisualizer(CoordinateSystemVisualizer):
|
|
288
|
+
"""
|
|
289
|
+
Surface visualization with curvature coloring.
|
|
290
|
+
|
|
291
|
+
Supports:
|
|
292
|
+
- Wireframe and surface rendering
|
|
293
|
+
- Gaussian/Mean curvature coloring
|
|
294
|
+
- Frame field overlay
|
|
295
|
+
- Normal vector visualization
|
|
296
|
+
"""
|
|
297
|
+
|
|
298
|
+
def __init__(self, figsize: Tuple[int, int] = (14, 10), dpi: int = 100):
|
|
299
|
+
super().__init__(figsize, dpi)
|
|
300
|
+
self.colorbar = None
|
|
301
|
+
|
|
302
|
+
def draw_surface(
|
|
303
|
+
self,
|
|
304
|
+
surface: 'Surface',
|
|
305
|
+
u_range: Tuple[float, float] = (0.1, np.pi - 0.1),
|
|
306
|
+
v_range: Tuple[float, float] = (0, 2 * np.pi),
|
|
307
|
+
nu: int = 30,
|
|
308
|
+
nv: int = 40,
|
|
309
|
+
color: str = 'cyan',
|
|
310
|
+
alpha: float = 0.6,
|
|
311
|
+
wireframe: bool = True,
|
|
312
|
+
surface_plot: bool = True
|
|
313
|
+
):
|
|
314
|
+
"""
|
|
315
|
+
Draw a parametric surface.
|
|
316
|
+
|
|
317
|
+
Args:
|
|
318
|
+
surface: Surface object
|
|
319
|
+
u_range: Parameter u range
|
|
320
|
+
v_range: Parameter v range
|
|
321
|
+
nu: Number of u samples
|
|
322
|
+
nv: Number of v samples
|
|
323
|
+
color: Surface color
|
|
324
|
+
alpha: Transparency
|
|
325
|
+
wireframe: Show wireframe
|
|
326
|
+
surface_plot: Show filled surface
|
|
327
|
+
"""
|
|
328
|
+
u = np.linspace(u_range[0], u_range[1], nu)
|
|
329
|
+
v = np.linspace(v_range[0], v_range[1], nv)
|
|
330
|
+
U, V = np.meshgrid(u, v)
|
|
331
|
+
|
|
332
|
+
X = np.zeros_like(U)
|
|
333
|
+
Y = np.zeros_like(U)
|
|
334
|
+
Z = np.zeros_like(U)
|
|
335
|
+
|
|
336
|
+
for i in range(nu):
|
|
337
|
+
for j in range(nv):
|
|
338
|
+
pos = surface.position(U[j, i], V[j, i])
|
|
339
|
+
X[j, i] = pos.x
|
|
340
|
+
Y[j, i] = pos.y
|
|
341
|
+
Z[j, i] = pos.z
|
|
342
|
+
|
|
343
|
+
if surface_plot:
|
|
344
|
+
self.ax.plot_surface(X, Y, Z, color=color, alpha=alpha,
|
|
345
|
+
edgecolor='none', shade=True)
|
|
346
|
+
|
|
347
|
+
if wireframe:
|
|
348
|
+
self.ax.plot_wireframe(X, Y, Z, color='gray', alpha=0.3,
|
|
349
|
+
linewidth=0.5, rstride=2, cstride=2)
|
|
350
|
+
|
|
351
|
+
def draw_surface_curvature(
|
|
352
|
+
self,
|
|
353
|
+
surface: 'Surface',
|
|
354
|
+
curvature_type: str = 'gaussian',
|
|
355
|
+
u_range: Tuple[float, float] = (0.1, np.pi - 0.1),
|
|
356
|
+
v_range: Tuple[float, float] = (0, 2 * np.pi),
|
|
357
|
+
nu: int = 30,
|
|
358
|
+
nv: int = 40,
|
|
359
|
+
alpha: float = 0.8,
|
|
360
|
+
show_colorbar: bool = True,
|
|
361
|
+
step_size: float = 1e-3
|
|
362
|
+
):
|
|
363
|
+
"""
|
|
364
|
+
Draw surface with curvature coloring.
|
|
365
|
+
|
|
366
|
+
Args:
|
|
367
|
+
surface: Surface object
|
|
368
|
+
curvature_type: 'gaussian' or 'mean'
|
|
369
|
+
u_range: Parameter u range
|
|
370
|
+
v_range: Parameter v range
|
|
371
|
+
nu: Number of u samples
|
|
372
|
+
nv: Number of v samples
|
|
373
|
+
alpha: Transparency
|
|
374
|
+
show_colorbar: Show colorbar
|
|
375
|
+
step_size: Step size for curvature computation
|
|
376
|
+
"""
|
|
377
|
+
if compute_gaussian_curvature is None:
|
|
378
|
+
raise ImportError("differential_geometry module not available")
|
|
379
|
+
|
|
380
|
+
u = np.linspace(u_range[0], u_range[1], nu)
|
|
381
|
+
v = np.linspace(v_range[0], v_range[1], nv)
|
|
382
|
+
U, V = np.meshgrid(u, v)
|
|
383
|
+
|
|
384
|
+
X = np.zeros_like(U)
|
|
385
|
+
Y = np.zeros_like(U)
|
|
386
|
+
Z = np.zeros_like(U)
|
|
387
|
+
K = np.zeros_like(U)
|
|
388
|
+
|
|
389
|
+
compute_func = compute_gaussian_curvature if curvature_type == 'gaussian' else compute_mean_curvature
|
|
390
|
+
|
|
391
|
+
for i in range(nu):
|
|
392
|
+
for j in range(nv):
|
|
393
|
+
pos = surface.position(U[j, i], V[j, i])
|
|
394
|
+
X[j, i] = pos.x
|
|
395
|
+
Y[j, i] = pos.y
|
|
396
|
+
Z[j, i] = pos.z
|
|
397
|
+
K[j, i] = compute_func(surface, U[j, i], V[j, i], step_size)
|
|
398
|
+
|
|
399
|
+
# Normalize curvature for coloring
|
|
400
|
+
K_abs_max = max(abs(K.min()), abs(K.max()))
|
|
401
|
+
if K_abs_max > 1e-10:
|
|
402
|
+
K_normalized = (K / K_abs_max + 1) / 2 # Map to [0, 1]
|
|
403
|
+
else:
|
|
404
|
+
K_normalized = np.ones_like(K) * 0.5
|
|
405
|
+
|
|
406
|
+
# Create colormap
|
|
407
|
+
cmap = create_curvature_colormap()
|
|
408
|
+
colors = cmap(K_normalized)
|
|
409
|
+
|
|
410
|
+
# Draw surface
|
|
411
|
+
surf = self.ax.plot_surface(X, Y, Z, facecolors=colors, alpha=alpha,
|
|
412
|
+
shade=True, linewidth=0, antialiased=True)
|
|
413
|
+
|
|
414
|
+
if show_colorbar:
|
|
415
|
+
norm = Normalize(vmin=-K_abs_max, vmax=K_abs_max)
|
|
416
|
+
sm = plt.cm.ScalarMappable(cmap=cmap, norm=norm)
|
|
417
|
+
sm.set_array([])
|
|
418
|
+
self.colorbar = self.fig.colorbar(sm, ax=self.ax, shrink=0.6, aspect=20,
|
|
419
|
+
label=f'{curvature_type.capitalize()} Curvature')
|
|
420
|
+
|
|
421
|
+
def draw_surface_normals(
|
|
422
|
+
self,
|
|
423
|
+
surface: 'Surface',
|
|
424
|
+
u_range: Tuple[float, float] = (0.1, np.pi - 0.1),
|
|
425
|
+
v_range: Tuple[float, float] = (0, 2 * np.pi),
|
|
426
|
+
nu: int = 8,
|
|
427
|
+
nv: int = 12,
|
|
428
|
+
scale: float = 0.3,
|
|
429
|
+
color: str = 'blue',
|
|
430
|
+
alpha: float = 0.8
|
|
431
|
+
):
|
|
432
|
+
"""
|
|
433
|
+
Draw surface normal vectors.
|
|
434
|
+
|
|
435
|
+
Args:
|
|
436
|
+
surface: Surface object
|
|
437
|
+
u_range: Parameter u range
|
|
438
|
+
v_range: Parameter v range
|
|
439
|
+
nu: Number of u samples
|
|
440
|
+
nv: Number of v samples
|
|
441
|
+
scale: Normal vector length
|
|
442
|
+
color: Vector color
|
|
443
|
+
alpha: Transparency
|
|
444
|
+
"""
|
|
445
|
+
u = np.linspace(u_range[0], u_range[1], nu)
|
|
446
|
+
v = np.linspace(v_range[0], v_range[1], nv)
|
|
447
|
+
|
|
448
|
+
for ui in u:
|
|
449
|
+
for vi in v:
|
|
450
|
+
pos = surface.position(ui, vi)
|
|
451
|
+
n = surface.normal(ui, vi)
|
|
452
|
+
|
|
453
|
+
self.ax.quiver(
|
|
454
|
+
pos.x, pos.y, pos.z,
|
|
455
|
+
n.x * scale, n.y * scale, n.z * scale,
|
|
456
|
+
color=color, alpha=alpha, arrow_length_ratio=0.2
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
def draw_surface_frames(
|
|
460
|
+
self,
|
|
461
|
+
surface: 'Surface',
|
|
462
|
+
u_range: Tuple[float, float] = (0.1, np.pi - 0.1),
|
|
463
|
+
v_range: Tuple[float, float] = (0, 2 * np.pi),
|
|
464
|
+
nu: int = 6,
|
|
465
|
+
nv: int = 8,
|
|
466
|
+
scale: float = 0.25,
|
|
467
|
+
linewidth: float = 1.5,
|
|
468
|
+
alpha: float = 0.8
|
|
469
|
+
):
|
|
470
|
+
"""
|
|
471
|
+
Draw frame field on surface.
|
|
472
|
+
|
|
473
|
+
Args:
|
|
474
|
+
surface: Surface object
|
|
475
|
+
u_range: Parameter u range
|
|
476
|
+
v_range: Parameter v range
|
|
477
|
+
nu: Number of u samples
|
|
478
|
+
nv: Number of v samples
|
|
479
|
+
scale: Frame axis length
|
|
480
|
+
linewidth: Line width
|
|
481
|
+
alpha: Transparency
|
|
482
|
+
"""
|
|
483
|
+
u = np.linspace(u_range[0], u_range[1], nu)
|
|
484
|
+
v = np.linspace(v_range[0], v_range[1], nv)
|
|
485
|
+
|
|
486
|
+
for ui in u:
|
|
487
|
+
for vi in v:
|
|
488
|
+
pos = surface.position(ui, vi)
|
|
489
|
+
r_u = surface.tangent_u(ui, vi)
|
|
490
|
+
r_v = surface.tangent_v(ui, vi)
|
|
491
|
+
n = surface.normal(ui, vi)
|
|
492
|
+
|
|
493
|
+
# Normalize
|
|
494
|
+
r_u_norm = (r_u.x**2 + r_u.y**2 + r_u.z**2) ** 0.5
|
|
495
|
+
r_v_norm = (r_v.x**2 + r_v.y**2 + r_v.z**2) ** 0.5
|
|
496
|
+
|
|
497
|
+
if r_u_norm > 1e-10:
|
|
498
|
+
r_u = r_u * (1.0 / r_u_norm)
|
|
499
|
+
if r_v_norm > 1e-10:
|
|
500
|
+
r_v = r_v * (1.0 / r_v_norm)
|
|
501
|
+
|
|
502
|
+
# Draw frame (red=u, green=v, blue=normal)
|
|
503
|
+
self.ax.quiver(pos.x, pos.y, pos.z,
|
|
504
|
+
r_u.x * scale, r_u.y * scale, r_u.z * scale,
|
|
505
|
+
color='red', linewidth=linewidth, alpha=alpha,
|
|
506
|
+
arrow_length_ratio=0.15)
|
|
507
|
+
self.ax.quiver(pos.x, pos.y, pos.z,
|
|
508
|
+
r_v.x * scale, r_v.y * scale, r_v.z * scale,
|
|
509
|
+
color='green', linewidth=linewidth, alpha=alpha,
|
|
510
|
+
arrow_length_ratio=0.15)
|
|
511
|
+
self.ax.quiver(pos.x, pos.y, pos.z,
|
|
512
|
+
n.x * scale, n.y * scale, n.z * scale,
|
|
513
|
+
color='blue', linewidth=linewidth, alpha=alpha,
|
|
514
|
+
arrow_length_ratio=0.15)
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
# ============================================================
|
|
518
|
+
# Curve Visualizer
|
|
519
|
+
# ============================================================
|
|
520
|
+
|
|
521
|
+
class CurveVisualizer(CoordinateSystemVisualizer):
|
|
522
|
+
"""
|
|
523
|
+
Curve visualization with Frenet frame support.
|
|
524
|
+
|
|
525
|
+
Features:
|
|
526
|
+
- Curve path rendering
|
|
527
|
+
- Tangent, normal, binormal vectors
|
|
528
|
+
- Frenet frame field
|
|
529
|
+
- Animation support
|
|
530
|
+
"""
|
|
531
|
+
|
|
532
|
+
def __init__(self, figsize: Tuple[int, int] = (12, 9), dpi: int = 100):
|
|
533
|
+
super().__init__(figsize, dpi)
|
|
534
|
+
|
|
535
|
+
def draw_curve_vertices(
|
|
536
|
+
self,
|
|
537
|
+
points: List[vec3],
|
|
538
|
+
color: str = 'black',
|
|
539
|
+
linewidth: float = 2.0,
|
|
540
|
+
marker: str = '',
|
|
541
|
+
markersize: float = 4,
|
|
542
|
+
alpha: float = 0.9,
|
|
543
|
+
label: str = "Curve"
|
|
544
|
+
):
|
|
545
|
+
"""
|
|
546
|
+
Draw curve through vertices.
|
|
547
|
+
|
|
548
|
+
Args:
|
|
549
|
+
points: Vertex list
|
|
550
|
+
color: Line color
|
|
551
|
+
linewidth: Line width
|
|
552
|
+
marker: Marker style (empty for no markers)
|
|
553
|
+
markersize: Marker size
|
|
554
|
+
alpha: Transparency
|
|
555
|
+
label: Legend label
|
|
556
|
+
"""
|
|
557
|
+
if not points:
|
|
558
|
+
return
|
|
559
|
+
|
|
560
|
+
x = [p.x for p in points]
|
|
561
|
+
y = [p.y for p in points]
|
|
562
|
+
z = [p.z for p in points]
|
|
563
|
+
|
|
564
|
+
self.ax.plot(x, y, z, color=color, linewidth=linewidth,
|
|
565
|
+
marker=marker, markersize=markersize,
|
|
566
|
+
alpha=alpha, label=label)
|
|
567
|
+
|
|
568
|
+
def draw_tangents(
|
|
569
|
+
self,
|
|
570
|
+
points: List[vec3],
|
|
571
|
+
tangents: List[vec3],
|
|
572
|
+
scale: float = 0.5,
|
|
573
|
+
color: str = 'red',
|
|
574
|
+
linewidth: float = 1.5,
|
|
575
|
+
alpha: float = 0.8
|
|
576
|
+
):
|
|
577
|
+
"""
|
|
578
|
+
Draw tangent vectors.
|
|
579
|
+
|
|
580
|
+
Args:
|
|
581
|
+
points: Curve points
|
|
582
|
+
tangents: Tangent vectors
|
|
583
|
+
scale: Vector length scale
|
|
584
|
+
color: Vector color
|
|
585
|
+
linewidth: Line width
|
|
586
|
+
alpha: Transparency
|
|
587
|
+
"""
|
|
588
|
+
for point, tangent in zip(points, tangents):
|
|
589
|
+
self.ax.quiver(
|
|
590
|
+
point.x, point.y, point.z,
|
|
591
|
+
tangent.x * scale, tangent.y * scale, tangent.z * scale,
|
|
592
|
+
color=color, linewidth=linewidth, alpha=alpha,
|
|
593
|
+
arrow_length_ratio=0.2
|
|
594
|
+
)
|
|
595
|
+
|
|
596
|
+
def draw_normals(
|
|
597
|
+
self,
|
|
598
|
+
points: List[vec3],
|
|
599
|
+
normals: List[vec3],
|
|
600
|
+
scale: float = 0.5,
|
|
601
|
+
color: str = 'green',
|
|
602
|
+
linewidth: float = 1.5,
|
|
603
|
+
alpha: float = 0.8
|
|
604
|
+
):
|
|
605
|
+
"""
|
|
606
|
+
Draw normal vectors.
|
|
607
|
+
|
|
608
|
+
Args:
|
|
609
|
+
points: Curve points
|
|
610
|
+
normals: Normal vectors
|
|
611
|
+
scale: Vector length scale
|
|
612
|
+
color: Vector color
|
|
613
|
+
linewidth: Line width
|
|
614
|
+
alpha: Transparency
|
|
615
|
+
"""
|
|
616
|
+
for point, normal in zip(points, normals):
|
|
617
|
+
self.ax.quiver(
|
|
618
|
+
point.x, point.y, point.z,
|
|
619
|
+
normal.x * scale, normal.y * scale, normal.z * scale,
|
|
620
|
+
color=color, linewidth=linewidth, alpha=alpha,
|
|
621
|
+
arrow_length_ratio=0.2
|
|
622
|
+
)
|
|
623
|
+
|
|
624
|
+
def draw_binormals(
|
|
625
|
+
self,
|
|
626
|
+
points: List[vec3],
|
|
627
|
+
binormals: List[vec3],
|
|
628
|
+
scale: float = 0.5,
|
|
629
|
+
color: str = 'blue',
|
|
630
|
+
linewidth: float = 1.5,
|
|
631
|
+
alpha: float = 0.8
|
|
632
|
+
):
|
|
633
|
+
"""
|
|
634
|
+
Draw binormal vectors.
|
|
635
|
+
|
|
636
|
+
Args:
|
|
637
|
+
points: Curve points
|
|
638
|
+
binormals: Binormal vectors
|
|
639
|
+
scale: Vector length scale
|
|
640
|
+
color: Vector color
|
|
641
|
+
linewidth: Line width
|
|
642
|
+
alpha: Transparency
|
|
643
|
+
"""
|
|
644
|
+
for point, binormal in zip(points, binormals):
|
|
645
|
+
self.ax.quiver(
|
|
646
|
+
point.x, point.y, point.z,
|
|
647
|
+
binormal.x * scale, binormal.y * scale, binormal.z * scale,
|
|
648
|
+
color=color, linewidth=linewidth, alpha=alpha,
|
|
649
|
+
arrow_length_ratio=0.2
|
|
650
|
+
)
|
|
651
|
+
|
|
652
|
+
def draw_curve_frames(
|
|
653
|
+
self,
|
|
654
|
+
frames: List[coord3],
|
|
655
|
+
scale: float = 0.3,
|
|
656
|
+
linewidth: float = 1.5,
|
|
657
|
+
alpha: float = 0.7,
|
|
658
|
+
skip: int = 1
|
|
659
|
+
):
|
|
660
|
+
"""
|
|
661
|
+
Draw Frenet frames along curve.
|
|
662
|
+
|
|
663
|
+
Args:
|
|
664
|
+
frames: List of coord3 frames
|
|
665
|
+
scale: Axis length
|
|
666
|
+
linewidth: Line width
|
|
667
|
+
alpha: Transparency
|
|
668
|
+
skip: Draw every nth frame
|
|
669
|
+
"""
|
|
670
|
+
for i, frame in enumerate(frames):
|
|
671
|
+
if i % skip == 0:
|
|
672
|
+
self.draw_coord_system(frame, scale=scale,
|
|
673
|
+
linewidth=linewidth, alpha=alpha)
|
|
674
|
+
|
|
675
|
+
def draw_complete_curve(
|
|
676
|
+
self,
|
|
677
|
+
points: List[vec3],
|
|
678
|
+
tangents: Optional[List[vec3]] = None,
|
|
679
|
+
normals: Optional[List[vec3]] = None,
|
|
680
|
+
binormals: Optional[List[vec3]] = None,
|
|
681
|
+
frames: Optional[List[coord3]] = None,
|
|
682
|
+
curve_color: str = 'black',
|
|
683
|
+
tangent_scale: float = 0.5,
|
|
684
|
+
normal_scale: float = 0.5,
|
|
685
|
+
binormal_scale: float = 0.5,
|
|
686
|
+
frame_scale: float = 0.3,
|
|
687
|
+
frame_skip: int = 5,
|
|
688
|
+
show_world_coord: bool = True
|
|
689
|
+
):
|
|
690
|
+
"""
|
|
691
|
+
Draw complete curve with geometric properties.
|
|
692
|
+
|
|
693
|
+
Args:
|
|
694
|
+
points: Curve vertices
|
|
695
|
+
tangents: Tangent vectors (optional)
|
|
696
|
+
normals: Normal vectors (optional)
|
|
697
|
+
binormals: Binormal vectors (optional)
|
|
698
|
+
frames: Frenet frames (optional, overrides individual vectors)
|
|
699
|
+
curve_color: Curve color
|
|
700
|
+
tangent_scale: Tangent vector scale
|
|
701
|
+
normal_scale: Normal vector scale
|
|
702
|
+
binormal_scale: Binormal vector scale
|
|
703
|
+
frame_scale: Frame axis length
|
|
704
|
+
frame_skip: Frame drawing interval
|
|
705
|
+
show_world_coord: Show world coordinate system
|
|
706
|
+
"""
|
|
707
|
+
if show_world_coord:
|
|
708
|
+
self.draw_world_coord(scale=1.0)
|
|
709
|
+
|
|
710
|
+
self.draw_curve_vertices(points, color=curve_color, label="Curve")
|
|
711
|
+
|
|
712
|
+
if frames is not None:
|
|
713
|
+
# Use complete Frenet frames (RGB coloring)
|
|
714
|
+
self.draw_curve_frames(frames, scale=frame_scale, skip=frame_skip)
|
|
715
|
+
else:
|
|
716
|
+
# Draw individual vectors
|
|
717
|
+
if tangents is not None:
|
|
718
|
+
self.draw_tangents(points, tangents, scale=tangent_scale)
|
|
719
|
+
if normals is not None:
|
|
720
|
+
self.draw_normals(points, normals, scale=normal_scale)
|
|
721
|
+
if binormals is not None:
|
|
722
|
+
self.draw_binormals(points, binormals, scale=binormal_scale)
|
|
723
|
+
|
|
724
|
+
self.set_equal_aspect()
|
|
725
|
+
|
|
726
|
+
|
|
727
|
+
# ============================================================
|
|
728
|
+
# Parametric Curve
|
|
729
|
+
# ============================================================
|
|
730
|
+
|
|
731
|
+
class ParametricCurve:
|
|
732
|
+
"""
|
|
733
|
+
Parametric curve with Frenet frame computation.
|
|
734
|
+
|
|
735
|
+
Provides:
|
|
736
|
+
- Position r(t)
|
|
737
|
+
- Tangent T(t)
|
|
738
|
+
- Normal N(t)
|
|
739
|
+
- Binormal B(t)
|
|
740
|
+
- Frenet frame {T, N, B}
|
|
741
|
+
- Curvature and torsion
|
|
742
|
+
"""
|
|
743
|
+
|
|
744
|
+
def __init__(
|
|
745
|
+
self,
|
|
746
|
+
position_func: Callable[[float], vec3],
|
|
747
|
+
t_range: Tuple[float, float] = (0, 1),
|
|
748
|
+
num_points: int = 100
|
|
749
|
+
):
|
|
750
|
+
"""
|
|
751
|
+
Initialize parametric curve.
|
|
752
|
+
|
|
753
|
+
Args:
|
|
754
|
+
position_func: Position function r(t) -> vec3
|
|
755
|
+
t_range: Parameter range (t_min, t_max)
|
|
756
|
+
num_points: Number of sample points
|
|
757
|
+
"""
|
|
758
|
+
self.position_func = position_func
|
|
759
|
+
self.t_range = t_range
|
|
760
|
+
self.num_points = num_points
|
|
761
|
+
self.h = 1e-6 # Numerical differentiation step
|
|
762
|
+
|
|
763
|
+
def position(self, t: float) -> vec3:
|
|
764
|
+
"""Compute position at parameter t."""
|
|
765
|
+
return self.position_func(t)
|
|
766
|
+
|
|
767
|
+
def tangent(self, t: float, normalized: bool = True) -> vec3:
|
|
768
|
+
"""
|
|
769
|
+
Compute tangent vector T = dr/dt.
|
|
770
|
+
|
|
771
|
+
Args:
|
|
772
|
+
t: Parameter value
|
|
773
|
+
normalized: Normalize to unit vector
|
|
774
|
+
"""
|
|
775
|
+
r_plus = self.position_func(t + self.h)
|
|
776
|
+
r_minus = self.position_func(t - self.h)
|
|
777
|
+
tangent = (r_plus - r_minus) * (1.0 / (2.0 * self.h))
|
|
778
|
+
|
|
779
|
+
if normalized:
|
|
780
|
+
length = (tangent.x**2 + tangent.y**2 + tangent.z**2) ** 0.5
|
|
781
|
+
if length > 1e-10:
|
|
782
|
+
tangent = tangent * (1.0 / length)
|
|
783
|
+
|
|
784
|
+
return tangent
|
|
785
|
+
|
|
786
|
+
def second_derivative(self, t: float) -> vec3:
|
|
787
|
+
"""Compute second derivative d^2r/dt^2."""
|
|
788
|
+
r_plus = self.position_func(t + self.h)
|
|
789
|
+
r_center = self.position_func(t)
|
|
790
|
+
r_minus = self.position_func(t - self.h)
|
|
791
|
+
return (r_plus + r_minus - r_center * 2.0) * (1.0 / (self.h * self.h))
|
|
792
|
+
|
|
793
|
+
def normal(self, t: float, normalized: bool = True) -> vec3:
|
|
794
|
+
"""
|
|
795
|
+
Compute principal normal N = dT/ds / |dT/ds|.
|
|
796
|
+
|
|
797
|
+
Args:
|
|
798
|
+
t: Parameter value
|
|
799
|
+
normalized: Normalize to unit vector
|
|
800
|
+
"""
|
|
801
|
+
T_plus = self.tangent(t + self.h, normalized=True)
|
|
802
|
+
T_minus = self.tangent(t - self.h, normalized=True)
|
|
803
|
+
dT_dt = (T_plus - T_minus) * (1.0 / (2.0 * self.h))
|
|
804
|
+
|
|
805
|
+
length = (dT_dt.x**2 + dT_dt.y**2 + dT_dt.z**2) ** 0.5
|
|
806
|
+
|
|
807
|
+
if length > 1e-10:
|
|
808
|
+
N = dT_dt * (1.0 / length) if normalized else dT_dt
|
|
809
|
+
else:
|
|
810
|
+
N = vec3(0, 0, 1)
|
|
811
|
+
|
|
812
|
+
return N
|
|
813
|
+
|
|
814
|
+
def binormal(self, t: float, normalized: bool = True) -> vec3:
|
|
815
|
+
"""
|
|
816
|
+
Compute binormal B = T x N.
|
|
817
|
+
|
|
818
|
+
Args:
|
|
819
|
+
t: Parameter value
|
|
820
|
+
normalized: Normalize to unit vector
|
|
821
|
+
"""
|
|
822
|
+
T = self.tangent(t, normalized=True)
|
|
823
|
+
N = self.normal(t, normalized=True)
|
|
824
|
+
B = T.cross(N)
|
|
825
|
+
|
|
826
|
+
if normalized:
|
|
827
|
+
length = (B.x**2 + B.y**2 + B.z**2) ** 0.5
|
|
828
|
+
if length > 1e-10:
|
|
829
|
+
B = B * (1.0 / length)
|
|
830
|
+
|
|
831
|
+
return B
|
|
832
|
+
|
|
833
|
+
def curvature(self, t: float) -> float:
|
|
834
|
+
"""
|
|
835
|
+
Compute curvature kappa = |dT/ds|.
|
|
836
|
+
|
|
837
|
+
Args:
|
|
838
|
+
t: Parameter value
|
|
839
|
+
|
|
840
|
+
Returns:
|
|
841
|
+
Curvature value
|
|
842
|
+
"""
|
|
843
|
+
T_plus = self.tangent(t + self.h, normalized=True)
|
|
844
|
+
T_minus = self.tangent(t - self.h, normalized=True)
|
|
845
|
+
dT_dt = (T_plus - T_minus) * (1.0 / (2.0 * self.h))
|
|
846
|
+
|
|
847
|
+
# Get speed |dr/dt|
|
|
848
|
+
dr_dt = (self.position_func(t + self.h) - self.position_func(t - self.h)) * (1.0 / (2.0 * self.h))
|
|
849
|
+
speed = (dr_dt.x**2 + dr_dt.y**2 + dr_dt.z**2) ** 0.5
|
|
850
|
+
|
|
851
|
+
if speed > 1e-10:
|
|
852
|
+
kappa = (dT_dt.x**2 + dT_dt.y**2 + dT_dt.z**2) ** 0.5 / speed
|
|
853
|
+
else:
|
|
854
|
+
kappa = 0.0
|
|
855
|
+
|
|
856
|
+
return kappa
|
|
857
|
+
|
|
858
|
+
def frenet_frame(self, t: float) -> coord3:
|
|
859
|
+
"""
|
|
860
|
+
Compute Frenet frame {T, N, B}.
|
|
861
|
+
|
|
862
|
+
Returns coord3 with:
|
|
863
|
+
- o: Position
|
|
864
|
+
- ux: Tangent T (red)
|
|
865
|
+
- uy: Normal N (green)
|
|
866
|
+
- uz: Binormal B (blue)
|
|
867
|
+
"""
|
|
868
|
+
frame = coord3()
|
|
869
|
+
frame.o = self.position(t)
|
|
870
|
+
frame.ux = self.tangent(t, normalized=True)
|
|
871
|
+
frame.uy = self.normal(t, normalized=True)
|
|
872
|
+
frame.uz = self.binormal(t, normalized=True)
|
|
873
|
+
return frame
|
|
874
|
+
|
|
875
|
+
def sample_points(self) -> List[vec3]:
|
|
876
|
+
"""Sample curve positions."""
|
|
877
|
+
t_min, t_max = self.t_range
|
|
878
|
+
t_values = np.linspace(t_min, t_max, self.num_points)
|
|
879
|
+
return [self.position(t) for t in t_values]
|
|
880
|
+
|
|
881
|
+
def sample_tangents(self) -> List[vec3]:
|
|
882
|
+
"""Sample tangent vectors."""
|
|
883
|
+
t_min, t_max = self.t_range
|
|
884
|
+
t_values = np.linspace(t_min, t_max, self.num_points)
|
|
885
|
+
return [self.tangent(t) for t in t_values]
|
|
886
|
+
|
|
887
|
+
def sample_normals(self) -> List[vec3]:
|
|
888
|
+
"""Sample normal vectors."""
|
|
889
|
+
t_min, t_max = self.t_range
|
|
890
|
+
t_values = np.linspace(t_min, t_max, self.num_points)
|
|
891
|
+
return [self.normal(t) for t in t_values]
|
|
892
|
+
|
|
893
|
+
def sample_binormals(self) -> List[vec3]:
|
|
894
|
+
"""Sample binormal vectors."""
|
|
895
|
+
t_min, t_max = self.t_range
|
|
896
|
+
t_values = np.linspace(t_min, t_max, self.num_points)
|
|
897
|
+
return [self.binormal(t) for t in t_values]
|
|
898
|
+
|
|
899
|
+
def sample_frames(self) -> List[coord3]:
|
|
900
|
+
"""Sample Frenet frames."""
|
|
901
|
+
t_min, t_max = self.t_range
|
|
902
|
+
t_values = np.linspace(t_min, t_max, self.num_points)
|
|
903
|
+
return [self.frenet_frame(t) for t in t_values]
|
|
904
|
+
|
|
905
|
+
def sample_curvature(self) -> List[float]:
|
|
906
|
+
"""Sample curvature values."""
|
|
907
|
+
t_min, t_max = self.t_range
|
|
908
|
+
t_values = np.linspace(t_min, t_max, self.num_points)
|
|
909
|
+
return [self.curvature(t) for t in t_values]
|
|
910
|
+
|
|
911
|
+
|
|
912
|
+
# ============================================================
|
|
913
|
+
# Convenience Functions
|
|
914
|
+
# ============================================================
|
|
915
|
+
|
|
916
|
+
def visualize_coord_system(
|
|
917
|
+
coord: coord3,
|
|
918
|
+
scale: float = 1.0,
|
|
919
|
+
figsize: Tuple[int, int] = (10, 8),
|
|
920
|
+
show: bool = True,
|
|
921
|
+
save_path: Optional[str] = None,
|
|
922
|
+
title: str = "Coordinate System"
|
|
923
|
+
):
|
|
924
|
+
"""
|
|
925
|
+
Quick visualization of a single coordinate frame.
|
|
926
|
+
|
|
927
|
+
Args:
|
|
928
|
+
coord: Coordinate frame
|
|
929
|
+
scale: Axis length
|
|
930
|
+
figsize: Figure size
|
|
931
|
+
show: Display figure
|
|
932
|
+
save_path: Save path (optional)
|
|
933
|
+
title: Plot title
|
|
934
|
+
"""
|
|
935
|
+
vis = CoordinateSystemVisualizer(figsize=figsize)
|
|
936
|
+
vis.draw_world_coord(scale=scale * 0.8)
|
|
937
|
+
vis.draw_coord_system(coord, scale=scale, label_prefix="Frame-")
|
|
938
|
+
vis.set_equal_aspect()
|
|
939
|
+
vis.set_title(title)
|
|
940
|
+
|
|
941
|
+
if save_path:
|
|
942
|
+
vis.save(save_path)
|
|
943
|
+
if show:
|
|
944
|
+
vis.show()
|
|
945
|
+
|
|
946
|
+
|
|
947
|
+
def visualize_curve(
|
|
948
|
+
curve: ParametricCurve,
|
|
949
|
+
show_tangents: bool = False,
|
|
950
|
+
show_normals: bool = False,
|
|
951
|
+
show_binormals: bool = False,
|
|
952
|
+
show_frames: bool = True,
|
|
953
|
+
frame_skip: int = 5,
|
|
954
|
+
figsize: Tuple[int, int] = (12, 9),
|
|
955
|
+
show: bool = True,
|
|
956
|
+
save_path: Optional[str] = None,
|
|
957
|
+
title: str = "Parametric Curve"
|
|
958
|
+
):
|
|
959
|
+
"""
|
|
960
|
+
Quick visualization of a parametric curve.
|
|
961
|
+
|
|
962
|
+
Args:
|
|
963
|
+
curve: Parametric curve object
|
|
964
|
+
show_tangents: Show tangent vectors
|
|
965
|
+
show_normals: Show normal vectors
|
|
966
|
+
show_binormals: Show binormal vectors
|
|
967
|
+
show_frames: Show complete Frenet frames
|
|
968
|
+
frame_skip: Frame drawing interval
|
|
969
|
+
figsize: Figure size
|
|
970
|
+
show: Display figure
|
|
971
|
+
save_path: Save path (optional)
|
|
972
|
+
title: Plot title
|
|
973
|
+
"""
|
|
974
|
+
vis = CurveVisualizer(figsize=figsize)
|
|
975
|
+
|
|
976
|
+
points = curve.sample_points()
|
|
977
|
+
tangents = curve.sample_tangents() if show_tangents and not show_frames else None
|
|
978
|
+
normals = curve.sample_normals() if show_normals and not show_frames else None
|
|
979
|
+
binormals = curve.sample_binormals() if show_binormals and not show_frames else None
|
|
980
|
+
frames = curve.sample_frames() if show_frames else None
|
|
981
|
+
|
|
982
|
+
vis.draw_complete_curve(
|
|
983
|
+
points=points,
|
|
984
|
+
tangents=tangents,
|
|
985
|
+
normals=normals,
|
|
986
|
+
binormals=binormals,
|
|
987
|
+
frames=frames,
|
|
988
|
+
frame_skip=frame_skip
|
|
989
|
+
)
|
|
990
|
+
vis.set_title(title)
|
|
991
|
+
|
|
992
|
+
if save_path:
|
|
993
|
+
vis.save(save_path)
|
|
994
|
+
if show:
|
|
995
|
+
vis.show()
|
|
996
|
+
|
|
997
|
+
|
|
998
|
+
def visualize_surface(
|
|
999
|
+
surface: 'Surface',
|
|
1000
|
+
curvature_type: Optional[str] = None,
|
|
1001
|
+
show_normals: bool = False,
|
|
1002
|
+
show_frames: bool = False,
|
|
1003
|
+
u_range: Tuple[float, float] = (0.1, np.pi - 0.1),
|
|
1004
|
+
v_range: Tuple[float, float] = (0, 2 * np.pi),
|
|
1005
|
+
figsize: Tuple[int, int] = (14, 10),
|
|
1006
|
+
show: bool = True,
|
|
1007
|
+
save_path: Optional[str] = None,
|
|
1008
|
+
title: str = "Surface"
|
|
1009
|
+
):
|
|
1010
|
+
"""
|
|
1011
|
+
Quick visualization of a parametric surface.
|
|
1012
|
+
|
|
1013
|
+
Args:
|
|
1014
|
+
surface: Surface object
|
|
1015
|
+
curvature_type: 'gaussian' or 'mean' for curvature coloring (None for plain)
|
|
1016
|
+
show_normals: Show normal vectors
|
|
1017
|
+
show_frames: Show frame field
|
|
1018
|
+
u_range: Parameter u range
|
|
1019
|
+
v_range: Parameter v range
|
|
1020
|
+
figsize: Figure size
|
|
1021
|
+
show: Display figure
|
|
1022
|
+
save_path: Save path (optional)
|
|
1023
|
+
title: Plot title
|
|
1024
|
+
"""
|
|
1025
|
+
vis = SurfaceVisualizer(figsize=figsize)
|
|
1026
|
+
|
|
1027
|
+
if curvature_type:
|
|
1028
|
+
vis.draw_surface_curvature(surface, curvature_type=curvature_type,
|
|
1029
|
+
u_range=u_range, v_range=v_range)
|
|
1030
|
+
else:
|
|
1031
|
+
vis.draw_surface(surface, u_range=u_range, v_range=v_range)
|
|
1032
|
+
|
|
1033
|
+
if show_normals:
|
|
1034
|
+
vis.draw_surface_normals(surface, u_range=u_range, v_range=v_range)
|
|
1035
|
+
|
|
1036
|
+
if show_frames:
|
|
1037
|
+
vis.draw_surface_frames(surface, u_range=u_range, v_range=v_range)
|
|
1038
|
+
|
|
1039
|
+
vis.draw_world_coord(scale=0.5)
|
|
1040
|
+
vis.set_equal_aspect()
|
|
1041
|
+
vis.set_title(title)
|
|
1042
|
+
|
|
1043
|
+
if save_path:
|
|
1044
|
+
vis.save(save_path)
|
|
1045
|
+
if show:
|
|
1046
|
+
vis.show()
|
|
1047
|
+
|
|
1048
|
+
|
|
1049
|
+
# ============================================================
|
|
1050
|
+
# Export
|
|
1051
|
+
# ============================================================
|
|
1052
|
+
|
|
1053
|
+
__all__ = [
|
|
1054
|
+
# Visualizer classes
|
|
1055
|
+
'CoordinateSystemVisualizer',
|
|
1056
|
+
'CurveVisualizer',
|
|
1057
|
+
'SurfaceVisualizer',
|
|
1058
|
+
|
|
1059
|
+
# Curve class
|
|
1060
|
+
'ParametricCurve',
|
|
1061
|
+
|
|
1062
|
+
# Convenience functions
|
|
1063
|
+
'visualize_coord_system',
|
|
1064
|
+
'visualize_curve',
|
|
1065
|
+
'visualize_surface',
|
|
1066
|
+
|
|
1067
|
+
# Utility
|
|
1068
|
+
'create_curvature_colormap',
|
|
1069
|
+
]
|