All docs
Examples
Example: multi-output mesh
A load step that emits a cube as two tables — vertices and faces — via outputs=[...], then wires one named output into a downstream shape.
TL;DRA @tool.load with outputs=["vertices", "faces"] returns a dict of two Tables. Downstream, a shape reads a specific port with step.output("faces"). Engine().run(...).outputs gives every port as a Table.
Some nodes naturally produce more than one table. A mesh is the classic case: vertices and
faces are two datasets with different shapes. Declare outputs=[...], return a dict, and wire
each output on its own port.
The code#
Python
# tool.py
from lsdtools import Tool, Table
tool = Tool("mesh", label="Mesh")
@tool.load(outputs=["vertices", "faces"])
def make_cube(size: float = 1.0) -> dict:
"""Emit a cube as two tables: 8 vertex positions and 12 triangle indices."""
s = size / 2
verts = [(x, y, z) for x in (-s, s) for y in (-s, s) for z in (-s, s)]
tris = [
(0, 1, 3), (0, 3, 2), # x = -s
(4, 6, 7), (4, 7, 5), # x = +s
(0, 4, 5), (0, 5, 1), # y = -s
(2, 3, 7), (2, 7, 6), # y = +s
(0, 2, 6), (0, 6, 4), # z = -s
(1, 5, 7), (1, 7, 3), # z = +s
]
return {
"vertices": Table({"x": [v[0] for v in verts],
"y": [v[1] for v in verts],
"z": [v[2] for v in verts]}),
"faces": Table({"a": [t[0] for t in tris],
"b": [t[1] for t in tris],
"c": [t[2] for t in tris]}),
}
@tool.shape
def annotate(t: Table) -> Table:
"""Add an `index_sum` column (a + b + c) to each face."""
return t.with_column("index_sum", t["a"] + t["b"] + t["c"])
What each line does#
@tool.load(outputs=["vertices", "faces"])— declares two named output ports. The function must return a dict keyed by those names, each value aTable.Table({...})— each output is built independently; they can have completely different columns and row counts (8 vertices vs 12 faces).@tool.shapeannotate— a normal transform with one input portt.t["a"] + t["b"] + t["c"]is a whole-columnColumnexpression;with_columnappends it.- Wiring a specific port happens at call time with
make_cube(...).output("faces")— see below.
Run it in Python#
Python
from lsdtools import Engine
# read every output port of the load node
res = Engine().run(make_cube(size=2.0))
res.outputs["vertices"].print() # → 8 rows of x, y, z (each ±1.0)
res.outputs["faces"].print() # → 12 rows of a, b, c
# wire ONE named output ("faces") into the shape node
faces = annotate(make_cube(size=2.0).output("faces"))
Engine().run(faces).output.print()
# → a b c index_sum
# 0 1 3 4
# 0 3 2 5
# … 12 rows
make_cube(...).output("faces") returns a port reference; passing it as the shape's input wires
that exact output. RunResult.outputs is a {port: Table} dict, while RunResult.output is the
terminal step's primary table.
Try changing it#
- Add a
graph_outputs={"n_faces": int}scalar to show the face count on the node card — see live node metrics. - Wire the
verticesport into a second shape that recentres the mesh. - Return a third output (
"normals") — just add it tooutputs=[...]and the returned dict.
Related#
Learn — Multiple outputs · Load steps · Tables & data flow
Examples — Live node metrics · File loader
By LSD Team · Last updated Jul 17, 2026
Ask on Discord
View as Markdown