Sandbox — LSD is in active testing. All systems operational·payments are test-mode (no real charges).
Overview Use Cases News Docs Tool Store Support Community Get LSD
All docs
Docs/ Examples/ Example: multi-output mesh
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 a Table.
  • Table({...}) — each output is built independently; they can have completely different columns and row counts (8 vertices vs 12 faces).
  • @tool.shape annotate — a normal transform with one input port t. t["a"] + t["b"] + t["c"] is a whole-column Column expression; with_column appends 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 vertices port into a second shape that recentres the mesh.
  • Return a third output ("normals") — just add it to outputs=[...] and the returned dict.

LearnMultiple outputs · Load steps · Tables & data flow

APITable · Engine · Tool

ExamplesLive node metrics · File loader

By LSD Team · Last updated Jul 17, 2026 Ask on Discord View as Markdown