Extrude Path

sweep a profile along a 3D centerline.

▶ Run in browser

Tags: 3d

Mesh.extrude_path builds a tube or ribbon by sweeping a 2D cross-section along a polyline of 3D points: the primitive behind race tracks, rivers, rails, cables and tentacles. Pass the centerline as a list of (x, y, z) points (or an (N, 3) array) and hang the result on a MeshInstance3D like any other mesh:

tube = Mesh.extrude_path(points, sides=12, radius=0.18, closed=True)

With no profile the cross-section is a regular polygon of sides corners (8 by default) and the given radius. closed=True joins the last point back to the first, so a looping centerline gets no gap at the seam. To sweep any other shape, pass profile, an (M, 2) array of points in the cross-section’s own plane, with X across the path and Y along the path’s local up; sides and radius are then ignored.

The orange figure-eight is a 12-sided tube around a closed loop whose points stop one step short of the start, so closed=True is what joins it. The metal rail sweeps a flat rectangle along a quarter circle.

Source

 1"""Extrude Path: sweep a profile along a 3D centerline.
 2
 3``Mesh.extrude_path`` builds a tube or ribbon by sweeping a 2D cross-section along
 4a polyline of 3D points: the primitive behind race tracks, rivers, rails, cables
 5and tentacles. Pass the centerline as a list of ``(x, y, z)`` points (or an
 6``(N, 3)`` array) and hang the result on a ``MeshInstance3D`` like any other mesh:
 7
 8    tube = Mesh.extrude_path(points, sides=12, radius=0.18, closed=True)
 9
10With no ``profile`` the cross-section is a regular polygon of ``sides`` corners
11(8 by default) and the given ``radius``. ``closed=True`` joins the last point back
12to the first, so a looping centerline gets no gap at the seam. To sweep any other
13shape, pass ``profile``, an ``(M, 2)`` array of points in the cross-section's own
14plane, with X across the path and Y along the path's local up; ``sides`` and
15``radius`` are then ignored.
16
17The orange figure-eight is a 12-sided tube around a closed loop whose points stop
18one step short of the start, so ``closed=True`` is what joins it. The metal rail
19sweeps a flat rectangle along a quarter circle.
20"""
21
22import math
23
24import numpy as np
25
26from simvx.core import (
27    Anchor,
28    Camera3D,
29    CanvasLayer,
30    DirectionalLight3D,
31    Label,
32    Material,
33    Mesh,
34    MeshInstance3D,
35    Node,
36    WorldEnvironment,
37)
38from simvx.graphics import App
39
40
41def figure_eight(n: int = 80, scale: float = 4.0) -> list[tuple[float, float, float]]:
42    """A figure-eight in the XZ plane with a gentle vertical wobble, open at the seam."""
43    ts = (i / n * math.tau for i in range(n))
44    return [(math.sin(2 * t) * scale, math.sin(4 * t) * 0.4, math.sin(t) * scale) for t in ts]
45
46
47def quarter_arc(n: int = 30, radius: float = 3.5) -> list[tuple[float, float, float]]:
48    return [(math.cos(t) * radius, 0.0, math.sin(t) * radius) for t in np.linspace(0.0, math.pi / 2, n)]
49
50
51# A flat 0.6 x 0.1 rectangle, the rail's cross-section.
52RAIL_PROFILE = np.array([[0.3, 0.05], [-0.3, 0.05], [-0.3, -0.05], [0.3, -0.05]], dtype=np.float32)
53
54
55class ExtrudePathScene(Node):
56    def on_ready(self):
57        self.add_child(WorldEnvironment(ambient_light_energy=0.45))
58        self.add_child(DirectionalLight3D(direction=(-6, -10, -4), intensity=1.1))
59        self.add_child(Camera3D(position=(8, 6, 8), look_at=(0, 0.5, 0)))
60
61        ribbon = Mesh.extrude_path(figure_eight(), sides=12, radius=0.18, closed=True)
62        orange = Material(colour=(0.9, 0.4, 0.2), roughness=0.4)
63        self.add_child(MeshInstance3D(mesh=ribbon, material=orange, position=(0, 0.6, 0)))
64
65        rail = Mesh.extrude_path(quarter_arc(), profile=RAIL_PROFILE)
66        metal = Material(colour=(0.6, 0.6, 0.7), roughness=0.2, metallic=0.9)
67        self.add_child(MeshInstance3D(mesh=rail, material=metal, position=(-3.5, 0.05, -3.5)))
68
69        ground = Material(colour=(0.18, 0.2, 0.22))
70        self.add_child(MeshInstance3D(mesh=Mesh.plane(40.0), material=ground))
71
72        hud = self.add_child(CanvasLayer())
73        caption = "Mesh.extrude_path: figure-eight (circle profile) + arc (rectangle profile)"
74        hud.add_child(Label(caption, anchor=Anchor.TOP_LEFT, inset=12))
75
76
77if __name__ == "__main__":
78    App(title="Extrude Path", width=1280, height=720).run(ExtrudePathScene())