Appearance
Layers
The layers in the Layers panel in the left sidebar: the panel headed "Layers" with the "+" (Add new layer) button and one row per layer, such as "Default" or "BIM layer", where the radio mark shows the active layer. Accessed via snaptrude.core.layers.
A layer is a named group of objects. Every object is on exactly one layer, and new objects land on the active layer. Each row can be renamed, locked, hidden and deleted, and objects can be moved between layers. This namespace does all of that: list / get / getActive read the panel, create, rename, update (lock / hide), setActive, delete and assign change it.
Looking for a storey's sketch surfaces and underlays?
The Wall / Rough sketch surfaces and imported CAD / PDF / image / terrain underlays of each storey (ids like ly_5) are not these layers. They live at core.storeys.layers (formerly core.layers). Wall / floor construction-assembly material layers are a third, unrelated thing.
Scope. Each design option (core.proposals) owns a set of layers. list and every write are scoped to the active design option, like the panel; get reads any layer, including other design options' layers. On legacy projects without design options every layer is in scope.
Layer ids in core.proposals
core.proposals records refer to these same ids as bucketIds and defaultBucketId. Pass them straight to core.layers.get.
Undo. create, update and assign are undoable through core.history.undo. rename and setActive are not undoable. Treat delete as permanent: undo restores the layer in the current session only, the server does not re-link it to its design option, and after a reload it is missing from the panel.
Reads never throw for a missing layer (get and getActive return null). Writes are gated by the plugin write floor.
Compatibility with the former core.layers
Before 0.9.15, core.layers was the storey sketch-surface / underlay API that now lives at core.storeys.layers. Plugins built against that shape keep working without changes: the host recognises the old call shapes on core.layers and routes them to core.storeys.layers.
Old call on core.layers | Routed to |
|---|---|
list(storeyId, layerType?) | core.storeys.layers.list(storeyId, layerType?) |
get(layerId) with a storey-layer id (ly_*) | core.storeys.layers.get(layerId) |
getActive(storeyId) | core.storeys.layers.getActive(storeyId) |
setActive(layerId) with a storey-layer id | core.storeys.layers.setActive(layerId) |
update(layerId, hidden?: boolean) with a storey-layer id | core.storeys.layers.update(layerId, hidden?) |
Old-shape calls keep their old validation and error codes too: any argument to list / getActive, and a non-object second argument to update, are handled by the storey-layer API exactly as before, and they never wait on the Layers-panel write queue. These routes are deprecated: new code should call snaptrude.core.storeys.layers.* directly.
What does differ, all on calls the old contract rejected:
list()andgetActive()with no argument now return the panel's layers (the old contract threwVALIDATIONfor the missingstoreyId).- A panel layer's id (a UUID, the ids
core.proposalslists asbucketIds) passed toget/setActive/updatenow resolves to that panel layer (the old contract returnednull/HANDLE_INVALID/VALIDATION). update(unknownId, { ... })with an options object reportsHANDLE_INVALIDinstead ofVALIDATION. Only the TypeScript type names moved (PluginCoreLayeris now the panel record; the storey record isPluginCoreStoreyLayer).
At a glance
| Method | What it does | Mutates? |
|---|---|---|
list() | The active design option's layers, default first | — |
get(layerId) | One layer by id (any design option), or null | — |
getActive() | The active layer (where new objects land), or null | — |
create(name) | Add a layer to the active design option (not made active) | ✓ (undoable) |
rename(layerId, name) | Rename a layer | ✓ (not undoable) |
update(layerId, options?) | Lock / unlock, hide / show (sparse) | ✓ (undoable, step per field) |
setActive(layerId) | Make a layer the active layer | ✓ (not undoable) |
delete(layerId, options) | Delete a layer, destroying or moving its objects | ✓ (treat as permanent) |
assign(componentIds, layerId) | Move objects onto a layer | ✓ (undoable, one step) |
Types
PluginCoreLayer
A layer in the Layers panel. Returned by every method except setActive, delete and assign.
| Property | Type | Description |
|---|---|---|
id | string | Layer id (the ids core.proposals records list as bucketIds) |
name | string | Display name (1-40 characters, unique case-insensitively) |
isDefault | boolean | The design option's default layer: cannot be renamed or deleted |
isActive | boolean | Whether new objects land on this layer |
locked | boolean | Whether the layer is locked (its objects cannot be edited) |
hidden | boolean | Whether the layer is hidden for the current user |
proposalIds | string[] | Design options that own the layer (more than one = shared) |
objectCount | number | Objects currently on the layer (0 = empty) |
PluginCoreLayerName
A layer name: a string, trimmed, 1-40 characters (the panel input's limit). Names must be unique across all layers of the project, case-insensitively.
PluginCoreLayersAssignResult
| Property | Type | Description |
|---|---|---|
assigned | string[] | Ids of the objects that were moved |
skipped | { id: string; reason: "not-found" | "locked" | "outside-proposal" }[] | Objects that were not moved, each with a reason |
Functions
list()
List the layers in the Layers panel: the active design option's layers, default layer first, then in creation order.
- Returns:
{ layers: PluginCoreLayer[] }
ts
const { layers } = await snaptrude.core.layers.list();
for (const l of layers) console.log(l.name, l.objectCount, l.locked, l.hidden);
const empty = layers.filter((l) => l.objectCount === 0 && !l.isDefault);get(layerId)
Get one Layers-panel layer by id, including layers owned by other design options.
- Parameters:
layerId:string— the id of the layer to read
- Returns:
PluginCoreLayer | null—nullif no layer has that id.
ts
const layer = await snaptrude.core.layers.get(layerId);
if (layer) console.log(layer.name, layer.objectCount);
// Name the layers a design option owns
const proposal = await snaptrude.core.proposals.getActive();
if (proposal) {
for (const id of proposal.bucketIds) console.log((await snaptrude.core.layers.get(id))?.name);
}getActive()
Get the active layer: the one new objects land on.
- Returns:
PluginCoreLayer | null—nullif no layer is active.
ts
const active = await snaptrude.core.layers.getActive();
if (active) console.log(`New objects go on ${active.name}`);create(name)
Create a new layer in the Layers panel (the panel's "Add new layer" button followed by naming it). The layer is added to the active design option and is not made active; call setActive to put new objects on it. Undoable.
- Parameters:
name:string— the new layer's name: trimmed, 1-40 characters, unique across all layers (case-insensitive)
- Returns:
PluginCoreLayer— the created layer. - Throws:
VALIDATIONfor an empty name or one longer than 40 charactersPRECONDITION_FAILEDif a layer with that name already exists (in any design option)
ts
const layer = await snaptrude.core.layers.create("Furniture");
await snaptrude.core.layers.setActive(layer.id);rename(layerId, name)
Rename a layer (the panel row's "Rename layer"). Names are trimmed, 1-40 characters and unique across all layers (case-insensitive); changing only the case of the layer's own name is allowed. The default layer cannot be renamed. Not undoable.
- Parameters:
layerId:string— the id of the layer to renamename:string— the new name
- Returns:
PluginCoreLayer— the updated layer. - Throws:
VALIDATIONfor a bad nameHANDLE_INVALIDfor an unknownlayerIdPRECONDITION_FAILEDif the layer is the default layer, is outside the active design option, or the name is already usedOPERATION_FAILEDif the app refuses the rename
ts
const { layers } = await snaptrude.core.layers.list();
const furniture = layers.find((l) => l.name === "Furniture");
if (furniture) await snaptrude.core.layers.rename(furniture.id, "Loose furniture");update(layerId, options?)
Lock / unlock or show / hide a layer (the panel row's padlock and eye). A sparse update: omitted fields are unchanged. hidden is per-user: it hides the layer for you, not for collaborators. Locking and hiding block interactive drawing in the app; plugin creates are not blocked (see setActive). Undoable, one undo step per changed field.
- Parameters:
layerId:string— the id of the layer to updateoptions.locked:boolean(optional) — new locked stateoptions.hidden:boolean(optional) — new hidden state for the current user
- Returns:
PluginCoreLayer— the updated layer. - Throws:
VALIDATIONfor any option other thanlocked/hidden(to rename, userename)HANDLE_INVALIDfor an unknownlayerIdPRECONDITION_FAILEDif the layer is outside the active design option, orhiddenis changed with no signed-in user
ts
await snaptrude.core.layers.update(layerId, { locked: true });
await snaptrude.core.layers.update(layerId, { locked: false, hidden: false });setActive(layerId)
Make a layer the active layer (clicking its row in the Layers panel). New objects, whether drawn in the app or created by plugins with design.create.*, land on the active layer. Not undoable.
Locked or hidden active layer
When the active layer is locked or hidden the app blocks interactive drawing, but plugin creates still land on it: objects created on a hidden layer are invisible, and objects on a locked layer cannot be edited. Unlock or show the layer first with update.
- Parameters:
layerId:string— the id of the layer to activate
- Returns:
{ layerId: string }— the id of the now-active layer. - Throws:
HANDLE_INVALIDfor an unknownlayerIdPRECONDITION_FAILEDif the layer is outside the active design option
ts
const layer = await snaptrude.core.layers.create("Structure");
await snaptrude.core.layers.setActive(layer.id);
// design.create.* calls from here on land on "Structure"delete(layerId, options)
Delete a layer (the panel row's "Delete layer"). Choose exactly one disposition, mirroring the delete dialog:
{ deleteObjects: true }destroys the objects on the layer.{ targetLayerId }moves them to another layer of the active design option first, then deletes the emptied layer.
A bare delete(layerId) is rejected: the engine would drop the layer and leave its objects on a missing layer.
Refused for:
- the default layer
- the last layer of the active design option
- a locked layer
- a layer shared with other design options (
proposalIds.length > 1; unshare it in the app first) - with
deleteObjects, a layer holding any locked object
The server checks that no collaborator is using the layer; this needs a live connection and times out after about 10 seconds. If the deleted layer was active, the active design option's default layer becomes active.
Treat as permanent
core.history.undo restores the layer in this session only. The server does not re-link it to its design option, so after a reload it is missing from the panel.
- Parameters:
layerId:string— the id of the layer to deleteoptions:{ deleteObjects: true } | { targetLayerId: string }— exactly one disposition;targetLayerIdmust be a different layer thanlayerId
- Returns:
{ deleted: true } - Throws:
VALIDATIONfor no disposition, both, ortargetLayerIdequal tolayerIdHANDLE_INVALIDfor an unknownlayerIdortargetLayerIdPRECONDITION_FAILEDfor the refusals above, or a layer or target outside the active design optionOPERATION_FAILEDif the server refuses (a collaborator is using the layer), the connection is down, or the check times out
ts
// Destroy the layer and everything on it
await snaptrude.core.layers.delete(layerId, { deleteObjects: true });
// Merge a layer into the default layer
const { layers } = await snaptrude.core.layers.list();
const def = layers.find((l) => l.isDefault)!;
await snaptrude.core.layers.delete(otherId, { targetLayerId: def.id });assign(componentIds, layerId)
Move objects onto a layer (picking a layer in the object properties panel). Doors, windows and voids hosted by a moved wall (and a terrain's neighbourhood buildings) move with it; if any of those is locked, the parent is skipped as locked. Locked objects, unknown ids and objects outside the active design option come back in skipped, never silently dropped. The target layer's own lock does not block the move. An object is always on exactly one layer, so there is no unassign: assign to another layer instead. Undoable (one step).
- Parameters:
componentIds:ComponentHandle[]— the objects to move (at least one)layerId:string— the target layer
- Returns:
PluginCoreLayersAssignResult—{ assigned, skipped }. - Throws:
VALIDATIONfor an emptycomponentIdsHANDLE_INVALIDfor an unknownlayerIdPRECONDITION_FAILEDif the target is outside the active design option
ts
const selected = await snaptrude.design.selection.get();
const { assigned, skipped } = await snaptrude.core.layers.assign(selected, layerId);
for (const s of skipped) console.log(`${s.id} not moved: ${s.reason}`);Errors
Failed calls reject with a typed PluginError — see Error Handling. Conditions specific to this namespace:
| Code | Thrown by | When |
|---|---|---|
VALIDATION | create, rename | Empty name, or longer than 40 characters after trimming |
VALIDATION | delete | No disposition, both dispositions, or targetLayerId equal to layerId |
VALIDATION | assign | Empty componentIds |
VALIDATION | update | An option other than locked / hidden (rename with rename) |
HANDLE_INVALID | rename, update, setActive, delete, assign | No layer has the given layerId (or, for delete, targetLayerId) |
PRECONDITION_FAILED | create, rename | Another layer already has that name (case-insensitive) |
PRECONDITION_FAILED | rename, update, setActive, delete, assign | The layer (or delete's target) is outside the active design option |
PRECONDITION_FAILED | rename, delete | The layer is the default layer |
PRECONDITION_FAILED | update | hidden changed with no signed-in user |
PRECONDITION_FAILED | delete | Last layer of the design option, locked layer, layer shared with other design options, or (deleteObjects) a locked object on it |
OPERATION_FAILED | rename | The app refused the rename (e.g. the name was taken concurrently) |
OPERATION_FAILED | delete | The server refused (a collaborator is using the layer), the connection is down, or the check timed out |
Reads never throw: get and getActive return null on a miss. assign never throws for individual objects; they come back in skipped.