Getting started — your own LEDs in voxeled

voxeled shows an LED piece as it will really look, and drives it. You describe the piece once — where every LED is, which way it points, how it's wired — in a small text file called a layout, and get: a 3D preview in the browser, a simulator that renders the LEDs as emitters (viewing angle, dark backsides, the frame blocking light), patterns that move through the piece in real millimetres, a builder to place things by dragging, and (when you run the hub next to the LEDs) Art-Net / DDP / sACN / dan-mx output plus inputs from other tools.

Nothing to install for the first part: https://pixeldestrukt.github.io/voxeled/ runs the whole authoring side in your browser, and the files you add stay in your browser.

  1. Five minutes: an example
  2. Describe your piece — strips, panels, rolled tubes, CAD, Blender
  3. Place it — instances, arrays, rings, the builder
  4. See it — simulator, the frame around the LEDs, standing at the site
  5. Light it — patterns and the show
  6. Drive real LEDs — the hub, the patch, protocols
  7. Share it — bundles, publishing a piece, your own copy of the site
  8. Troubleshooting

1. Five minutes: an example

Open https://pixeldestrukt.github.io/voxeled/viewer/?example=columns.

Everything you'll do with your own piece is the same loop: edit the layout (or drag in the builder), see it, save.

2. Describe your piece

A layout is YAML. The smallest useful one:

name: my-piece
fixtures:
  panel: { type: matrix, params: { cols: 32, rows: 8, pitchMM: 10, wiring: columns, start: top-left } }
instances:
  - { fixture: panel, name: panel-1, pos: [0, 1200, 0] }
show:
  scenes:
    - { name: chase, pattern: ribbonChase }

In the project panel, paste it into the layout box and apply. That's a 256-LED 8×32 panel, hanging 1.2 m up, running a chase in its data order.

Units and frame. Millimetres. Y is up. pos is where the fixture's origin sits; rotDeg: [x, y, z] rotates it (degrees; [0, 180, 0] turns it around to face the other way).

Fixtures are the kinds of thing you have. Pick what matches:

you havefixturethe essentials
a flat panel, a matrix, a strip laid in rowsmatrixcols, rows, pitchMM, `wiring: rows\columns (how the data snakes), start corner, serpentine, center`
a strip, string or rope along a line or a curveropepath (inline points [[x,y,z], …] or a named path), count or pitchMM; radiusMM + angleDeg if it's wrapped around a tube; normals point outward
a flexible panel rolled into a tubetubecols around, rows along, panels end to end, pitchMM, seamMM
a CAD model with the LED chips modelledmeshfile (STL/OBJ/GLB), scaleToMM; one LED per chip body, thin axis = the LED's normal
positions exported from Blender / Grasshopper / another toolvxlfile (a baked .vxl.json) — see the Blender addon and Grasshopper component
a glTF with points/normalsgltffile, scaleToMM
a Chromatik / LX modelvox import model.lxmon the command line, then vxl

Every LED gets a position and a normal (the direction it shines). Primitives know theirs; CAD import derives them from the chip geometry; exports carry them. That's what makes the simulator honest — a strip seen from behind is dark.

Files. Anything a layout names (file: for a mesh or baked fixture, structures, path files, photos) you add to the project by dropping it on the page (or + add files…). Paths in the layout are relative to the layout; a dropped file is found by its name, so file: ../models/frame.stl just needs frame.stl dropped in.

Two more you'll want soon:

paths:                                   # a curve LEDs follow (mm), or load one: { file: curve.json, scaleToMM: 1000 }
  arch: [[0, 0, 0], [500, 900, 0], [1000, 1200, 0], [1500, 900, 0], [2000, 0, 0]]
fixtures:
  string: { type: rope, params: { path: arch, pitchMM: 33 } }         # 12 mm pixels at 33 mm pitch along the arch
  frame:
    type: matrix
    params: { cols: 16, rows: 16 }
    structures: [{ file: frame.stl, opacity: 0.35 }]                # the thing it's mounted on, drawn around it (STL/GLB/OBJ)

The complete reference of every field is the authoring guide and FORMAT.md.

3. Place it

One fixture, many instances — that's how a piece is built:

instances:
  - { fixture: panel, name: wall, array: { count: [4, 2, 1], spacing: [330, 90, 0] } }   # a 4×2 wall of panels
  - { fixture: string, name: arches, ring: { count: 6, radiusMM: 3000, facing: center } }  # six arches in a circle
  - { fixture: column, name: c1, pos: [2200, 0, 600] }                                    # one, by hand

array, ring, and along (instances spaced along a path) generate many placements from one line. Or press E and place things by hand: click an instance, T/R to move/rotate with the gizmo (10 mm / 5° snaps) or type numbers, duplicate, delete, + add an instance of any fixture, ▦ make array. Every edit is live; 💾 saves the layout.

4. See it

5. Light it

Patterns are functions of each LED's real position (and normal), not of its index — so a wipe crosses the gap between two panels in real time, and a spiral wraps a tube.

show:
  holdS: 6          # seconds per scene
  fadeS: 2.5        # crossfade
  scenes:
    - { name: rising,  pattern: planeSweep, params: { speedMM: 400, spacingMM: 1200, widthMM: 250 } }
    - { name: wipe,    pattern: worldWipe,  params: { axis: 0, space: world } }
    - { name: helix,   pattern: helix,      params: { turns: 1, pitch: 3 } }
    - { name: lantern, pattern: lantern,    params: { path: eight, heightMM: 1200 } }
patternwhat it doesuses
ribbonChasea hue chase along each fixture's data orders
planeSweephorizontal planes of light rising through everythingworld Y
worldWipea plane wiping along an axis — space: world keeps real gaps, fixture syncs every instanceworld / local X·Y·Z
helixa barber-pole stripe winding around a tube as it climbs (pitch: 0 = rings)v, s
lanterna lamp carried through the room lights the sides facing itnormals
swirlspiral arms over the floor about the installation's centreworld angle
dropsdrops falling down one side of each columns, v
normalRGBcolours each LED by its normal — the map made visiblenormals
spotlight, projectoronly what a virtual camera can see; project a texture through itvisibility

Your own patterns. A pattern is (pixel, t, ctx) → [r, g, b] in 0..1, with pixel.p (mm), pixel.n, pixel.s/pixel.v (0→1 along/across its fixture), pixel.inst. Add one to src/patterns.mjs and register it in PATTERNS; it's then available by name in any layout. (On the hosted page that means running your own copy — see §7.)

6. Drive real LEDs

The browser can't send UDP. To light the piece, run the hub on a computer on the same network as your controllers:

git clone https://github.com/pixeldestrukt/voxeled && cd voxeled       # Node ≥ 18, no dependencies
node examples/mobius-heart/run.mjs path/to/my-piece.yaml               # → http://localhost:8080 — the same viewer, now from the hub

Export your project from the page as a bundle if you built it there (P → ⬇ export), or just keep the YAML + files in a folder — the hub reads them from disk, next to the layout.

The patch says where each fixture's pixels go:

fixtures:
  panel:
    type: matrix
    params: { cols: 32, rows: 8, wiring: columns }
    output: { protocol: artnet, host: 192.168.1.50, universe: 0, byteOrder: grb }   # one node, universes roll on from 0
instances:
  - { fixture: panel, name: a }
  - { fixture: panel, name: b, pos: [330, 0, 0], output: { universe: 2 } }          # per-instance override
  - { fixture: column, name: c1, output: { protocol: ddp, host: column-1.local } }   # a WLED / ESP32 node per fixture

Protocols: artnet (universes/channels), ddp (offsets — WLED, FPP, Falcon…), danmx (ESP32 nodes), with sacn on the way; details in protocols.md. The preview and the wire get the identical frame, so what you see is what the LEDs do.

Other tools driving it. The hub also receives: Art-Net or sACN from a console or sequencer, DDP from xLights/LedFx, TCP from TiXL, frames from a web page — several at once, merged per pixel by priority with failover to the show (inputs: + merge: in the layout). So you can keep sequencing in the tool you know and let voxeled own the map, the simulator and the wiring.

7. Share it

8. Troubleshooting