Skip to content

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.layersRouted 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 idcore.storeys.layers.setActive(layerId)
update(layerId, hidden?: boolean) with a storey-layer idcore.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() and getActive() with no argument now return the panel's layers (the old contract threw VALIDATION for the missing storeyId).
  • A panel layer's id (a UUID, the ids core.proposals lists as bucketIds) passed to get / setActive / update now resolves to that panel layer (the old contract returned null / HANDLE_INVALID / VALIDATION).
  • update(unknownId, { ... }) with an options object reports HANDLE_INVALID instead of VALIDATION. Only the TypeScript type names moved (PluginCoreLayer is now the panel record; the storey record is PluginCoreStoreyLayer).

At a glance ​

MethodWhat it doesMutates?
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.

PropertyTypeDescription
idstringLayer id (the ids core.proposals records list as bucketIds)
namestringDisplay name (1-40 characters, unique case-insensitively)
isDefaultbooleanThe design option's default layer: cannot be renamed or deleted
isActivebooleanWhether new objects land on this layer
lockedbooleanWhether the layer is locked (its objects cannot be edited)
hiddenbooleanWhether the layer is hidden for the current user
proposalIdsstring[]Design options that own the layer (more than one = shared)
objectCountnumberObjects 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 ​

PropertyTypeDescription
assignedstring[]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 — null if 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 — null if 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:
    • VALIDATION for an empty name or one longer than 40 characters
    • PRECONDITION_FAILED if 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 rename
    • name: string — the new name
  • Returns: PluginCoreLayer — the updated layer.
  • Throws:
    • VALIDATION for a bad name
    • HANDLE_INVALID for an unknown layerId
    • PRECONDITION_FAILED if the layer is the default layer, is outside the active design option, or the name is already used
    • OPERATION_FAILED if 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 update
    • options.locked: boolean (optional) — new locked state
    • options.hidden: boolean (optional) — new hidden state for the current user
  • Returns: PluginCoreLayer — the updated layer.
  • Throws:
    • VALIDATION for any option other than locked / hidden (to rename, use rename)
    • HANDLE_INVALID for an unknown layerId
    • PRECONDITION_FAILED if the layer is outside the active design option, or hidden is 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_INVALID for an unknown layerId
    • PRECONDITION_FAILED if 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 delete
    • options: { deleteObjects: true } | { targetLayerId: string } — exactly one disposition; targetLayerId must be a different layer than layerId
  • Returns: { deleted: true }
  • Throws:
    • VALIDATION for no disposition, both, or targetLayerId equal to layerId
    • HANDLE_INVALID for an unknown layerId or targetLayerId
    • PRECONDITION_FAILED for the refusals above, or a layer or target outside the active design option
    • OPERATION_FAILED if 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:
    • VALIDATION for an empty componentIds
    • HANDLE_INVALID for an unknown layerId
    • PRECONDITION_FAILED if 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:

CodeThrown byWhen
VALIDATIONcreate, renameEmpty name, or longer than 40 characters after trimming
VALIDATIONdeleteNo disposition, both dispositions, or targetLayerId equal to layerId
VALIDATIONassignEmpty componentIds
VALIDATIONupdateAn option other than locked / hidden (rename with rename)
HANDLE_INVALIDrename, update, setActive, delete, assignNo layer has the given layerId (or, for delete, targetLayerId)
PRECONDITION_FAILEDcreate, renameAnother layer already has that name (case-insensitive)
PRECONDITION_FAILEDrename, update, setActive, delete, assignThe layer (or delete's target) is outside the active design option
PRECONDITION_FAILEDrename, deleteThe layer is the default layer
PRECONDITION_FAILEDupdatehidden changed with no signed-in user
PRECONDITION_FAILEDdeleteLast layer of the design option, locked layer, layer shared with other design options, or (deleteObjects) a locked object on it
OPERATION_FAILEDrenameThe app refused the rename (e.g. the name was taken concurrently)
OPERATION_FAILEDdeleteThe 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.