Mesh.extrude_path demo¶
sweep a profile along a 3D centerline.
▶ Run in browserTags: 3d
Demonstrates:
Mesh.extrude_path() generating a tube mesh from a polyline + circle profile
A curved track with a custom square profile (rail)
Default 8-sided circle profile and ad-hoc cross-section override
Controls: Escape - Quit
Run: uv run python examples/features/3d/extrude_path.py
Source¶
1"""Mesh.extrude_path demo: sweep a profile along a 3D centerline.
2
3Demonstrates:
4 - Mesh.extrude_path() generating a tube mesh from a polyline + circle profile
5 - A curved track with a custom square profile (rail)
6 - Default 8-sided circle profile and ad-hoc cross-section override
7
8Controls:
9 Escape - Quit
10
11Run: uv run python examples/features/3d/extrude_path.py
12"""
13
14from __future__ import annotations
15
16import math
17
18import numpy as np
19
20from simvx.core import (
21 Camera3D,
22 DirectionalLight3D,
23 Input,
24 InputMap,
25 Key,
26 Material,
27 Mesh,
28 MeshInstance3D,
29 Node,
30 Text2D,
31 Vec3,
32 WorldEnvironment,
33)
34from simvx.graphics import App
35
36
37def _figure_eight(n: int = 80, scale: float = 4.0) -> list:
38 """Lissajous-style figure-eight centerline lying in the XZ plane."""
39 pts = []
40 for i in range(n):
41 t = i / n * math.tau
42 x = math.sin(t * 2.0) * scale
43 z = math.sin(t) * scale
44 y = math.sin(t * 4.0) * 0.4 # gentle vertical wobble
45 pts.append((x, y, z))
46 return pts
47
48
49def _spline_arc(n: int = 30) -> list:
50 """Quarter-circle arc: used for the square-profile rail."""
51 return [
52 (math.cos(t) * 3.5, 0.0, math.sin(t) * 3.5)
53 for t in np.linspace(0.0, math.pi * 0.5, n)
54 ]
55
56
57class ExtrudePathScene(Node):
58 def on_ready(self):
59 InputMap.add_action("quit", [Key.ESCAPE])
60
61 env = self.add_child(WorldEnvironment())
62 env.bloom_enabled = False
63 env.ambient_light_energy = 0.45
64
65 sun = DirectionalLight3D(position=(6, 10, 4))
66 sun.intensity = 1.1
67 sun.look_at(Vec3(0, 0, 0))
68 self.add_child(sun)
69
70 self.add_child(Camera3D(
71 position=(8, 6, 8), fov=60.0, near=0.1, far=200.0,
72 look_at=Vec3(0, 0.5, 0),
73 ))
74
75 # Default circle profile sweep: figure-eight ribbon.
76 ribbon_mesh = Mesh.extrude_path(_figure_eight(), sides=12, radius=0.18)
77 self.add_child(MeshInstance3D(
78 mesh=ribbon_mesh,
79 material=Material(colour=(0.9, 0.4, 0.2, 1.0), roughness=0.4, metallic=0.0),
80 position=(0, 0.6, 0),
81 ))
82
83 # Square-profile rail along a quarter arc.
84 square = np.array([[0.3, 0.05], [-0.3, 0.05], [-0.3, -0.05], [0.3, -0.05]], dtype=np.float32)
85 rail = Mesh.extrude_path(_spline_arc(), profile=square)
86 self.add_child(MeshInstance3D(
87 mesh=rail,
88 material=Material(colour=(0.6, 0.6, 0.7, 1.0), roughness=0.2, metallic=0.9),
89 position=(-3.5, 0.0, -3.5),
90 ))
91
92 # Ground plane.
93 self.add_child(MeshInstance3D(
94 mesh=Mesh.cube(size=1.0),
95 material=Material(colour=(0.18, 0.2, 0.22, 1.0)),
96 position=(0, -0.1, 0),
97 scale=Vec3(40, 0.1, 40),
98 ))
99
100 self.add_child(Text2D(
101 text="Mesh.extrude_path: figure-eight (circle profile) + arc (square profile)",
102 x=12, y=12, font_scale=1.0,
103 ))
104
105 def on_process(self, dt: float):
106 if Input.is_action_just_pressed("quit"):
107 self.app.quit()
108
109
110if __name__ == "__main__":
111 App(title="Mesh.extrude_path", width=1280, height=720).run(ExtrudePathScene())