Arranging and adjusting
Two nodes decide where things sit without changing what they are: one solves an arrangement from rules, the other records a drag.
Copying & instancing makes copies. This page is what happens next, when the copies exist and have to be placed. Layout solves positions from rules you set, and re-solves whenever the contents change size. Edit stores the nudges you make by hand and replays them.
Both write positions over what is upstream, and both carry a viewport tool, so the arrangement is usually authored by dragging and then tuned on the node.
Layout
Arrange geometry into a flow: pack along an axis with gap, padding and cross-axis alignment.
Wire one collection into content to arrange its rows, or wire several inputs to arrange each as a single unit. That distinction decides what a row is for everything below.
mode sets the packing. Flow packs along one axis, Grid uses counts to fix how many fit along X, Y and Z, Wrap breaks a flow at the container edge, and Pack fits the largest first. main_axis picks the axis a flow runs along, and its negative senses pack from the far side back.
The container comes from the bounds pin, or from region_center and region_size when nothing is wired. An SDF on bounds makes the container a shape rather than a box, and rows land inside it. space decides where the region defaults come from when you leave those numbers alone: auto follows the content's own layer, comp uses the canvas rectangle, world uses the 3D defaults.
Inside the container, padding_min and padding_max inset each side, gap separates consecutive rows, and cross_align_x / _y / _z place rows on the axes the flow is not running along. sizing_x / _y / _z decide how the container itself is sized: fixed keeps the region, hug shrinks it to the content plus padding, and fill stretches the rows to shares of the container. pin_x / _y / _z anchor rows to the container instead of putting them in the flow, which is how a header holds the top edge while the rest reflows beneath it.
Three outputs leave the node: out is the arranged content, bounds_out is the region the arrangement actually filled, and count is how many rows were placed.
The viewport tool
Select a Layout node on its own and the container appears with handles on it.
Drag a container edge to resize the region
Drag between two rows to set
gapDrag a splitter between filled rows to change their shares
Drag a row to move it in the flow, with a live-solved ghost showing where it will land
Toggle a row's per-axis pins
Right-click to clear one override, or to hand the container back to following the canvas
Everything the tool writes lands in ordinary parameters on the node, so a drag and a typed number are the same edit. On an axis set to fill, the gap handle gives way to the share splitters, so set gap on the node there.
A single mesh wired straight in passes through unarranged, with count reporting 0 and no error anywhere. Two or more inputs arrange correctly, and a collection of rows arranges correctly. If one unit comes out where it went in, that is why.
Layout measures on the CPU. Content that was living on the GPU is read back first, which costs about 0.4 ms at 4000 rows, so it is not a reason to avoid the node but it is a reason not to put one inside a per-frame feedback loop over heavy geometry.
| Parameter | Type | Default | What it does |
|---|---|---|---|
content | Any | List | Connect one collection to arrange its rows, or several inputs to arrange each as a whole unit |
bounds | Number[] or Collection or SDF | — | Container bounds. An SDF makes the container non-rectangular — rows land inside the shape. Leave unconnected to use the Region params below. |
mode | String | "Flow" | Flow packs along the main axis · Grid uses the counts below · Wrap breaks at the container edge · Pack fits largest-first. One of: Flow, Grid, Wrap, Pack. |
main_axis | String | "x" | Axis rows pack along. The negative senses pack from the far side back. One of: x, y, z, neg_x, neg_y, neg_z. |
gap | Number | 0 | Space between consecutive rows. On a fill axis the viewport handle yields to the share splitters — edit it here. |
counts | Vec3 | (0, 0, 0) | Grid only: how many along X / Y / Z. 0 = no limit. Rows fill along the main axis first, then the other two; the last of those takes as many tracks as it needs. |
padding_min | Vec3 | (0, 0, 0) | Inset from the region's minimum side (per axis) |
padding_max | Vec3 | (0, 0, 0) | Inset from the region's maximum side (per axis) |
cross_align_x | String | "start" | Alignment on X. Ignored when X is the main axis. One of: start, center, end. |
cross_align_y | String | "start" | Alignment on Y. Ignored when Y is the main axis. One of: start, center, end. |
cross_align_z | String | "start" | Alignment on Z. Ignored when Z is the main axis. One of: start, center, end. |
sizing_x | String | "fixed" | X sizing. hug = content + padding · fill = stretch rows to shares of the container (per-row @grow weights the shares; scales the rows, so corner radii and strokes stretch too). One of: fixed, hug, fill. |
sizing_y | String | "fixed" | Y sizing. hug = content + padding · fill = stretch rows to shares of the container (per-row @grow weights the shares; scales the rows, so corner radii and strokes stretch too). One of: fixed, hug, fill. |
sizing_z | String | "fixed" | Z sizing. hug = content + padding · fill = stretch rows to shares of the container (per-row @grow weights the shares; scales the rows, so corner radii and strokes stretch too). One of: fixed, hug, fill. |
pin_x | String | "none" | Anchor rows to the container on X. min/max hold a face · center tracks the centre · both stretches · scale proportions. Per-row @pin_x overrides this. One of: none, min, max, both, center, scale. |
pin_y | String | "none" | Anchor rows to the container on Y. min/max hold a face · center tracks the centre · both stretches · scale proportions. Per-row @pin_y overrides this. One of: none, min, max, both, center, scale. |
pin_z | String | "none" | Anchor rows to the container on Z. min/max hold a face · center tracks the centre · both stretches · scale proportions. Per-row @pin_z overrides this. One of: none, min, max, both, center, scale. |
space | String | "auto" | Where the container defaults from. auto = follow the content's layer · comp = the canvas rect · world = the 3D defaults. Only sets the Region defaults below — never how things are measured. One of: auto, world, comp. |
region_center | Vec3 | (0, 0, 0) | Container centre, used when the bounds pin is unconnected |
region_size | Vec3 | (0, 0, 0) | Container size, used when the bounds pin is unconnected. Zero packs outward from the centre. |
overrides | String | "" | — |
strength | Number | 1 | Effect strength / wet-dry (0 = no effect, 1 = full). Editable, wirable, keyframable. |
Edit
Store and apply hand-authored per-element edits to positions and per-anchor channels as additive deltas keyed by stable identity.
Edit records what you drag and stores it against the element's own identity, not its position in the list. Add a point upstream, or reorder the collection, and the stored nudge still moves the thing it was made on. Nothing dragged means nothing stored, and an Edit node with an empty store is a passthrough.
Select the node and drag in the viewport. mode picks what you are grabbing on a mesh or a point cloud: Points grabs a vertex, Edges drags an edge's two corners, Primitives drags a face's corners. An analytic shape is edited by its anchors instead, and mode does not apply to one.
radius spreads a drag to the neighbours within that distance in world units, weighted by falloff, which offers Smooth, Linear, Constant and Soft. At the shipped radius of 0 a drag moves exactly one thing. construction_plane decides where the drag is projected: Camera keeps it parallel to the screen at the anchor's depth, and XZ, XY and YZ hold it to a world plane through the anchor, offset by plane_offset.
Those five controls shape the next drag. They are not read again once an edit exists, so changing radius afterwards does not resmooth an edit already made.
The stored deltas are additive rather than absolute, which matters in one place: run the same Edit node inside a feedback loop and each pass adds the nudge again, so the edit accumulates instead of settling. That is what you want for progressive sculpting and is a surprise everywhere else.
Points, meshes and splat clouds are all editable, and so are analytic shapes. A table or a list passes straight through.
| Parameter | Type | Default | What it does |
|---|---|---|---|
input | Collection | — | Shapes / points / geometry whose per-element edits are stored here |
edits | String | "[]" | — |
mode | String | "Points" | Component the gizmo edits on a mesh/points input: Points (vertices), Edges (drag an edge's 2 corners), Primitives (drag a face's corners). Ignored for CRC shapes. One of: Points, Edges, Primitives. |
radius | Number | 0 | Soft-select falloff radius in world units. 0 = hard single-anchor edit; > 0 spreads a gizmo drag to neighbouring anchors weighted by falloff distance (proportional editing). |
falloff | String | "Smooth" | Soft-select falloff curve, applied when radius > 0. Smooth (quadratic) / Linear / Constant (hard edge) / Soft (gaussian). One of: Smooth, Linear, Constant, Soft. |
construction_plane | String | "Camera" | Plane the viewport anchor drag projects onto. Camera = parallel to screen; XZ/XY/YZ = a fixed world plane through the anchor. One of: Camera, XZ, XY, YZ. |
plane_offset | Number | 0 | Offset of the construction plane from the anchor along its normal. |
strength | Number | 1 | Effect strength / wet-dry (0 = no effect, 1 = full). Editable, wirable, keyframable. |
One gap worth knowing. If the element an edit was made on disappears upstream, the edit is dropped in silence: nothing counts the orphans, and there is no way to rebind or prune them. Work that looked authored can quietly stop applying after a change further up the graph.