Mesh.extrude_path demo

sweep a profile along a 3D centerline.

▶ Run in browser

Tags: 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())