Live Scene Control
On this page
- tako.select.set
- tako.select.add
- tako.select.remove
- tako.select.clear
- tako.select.all
- tako.select.invert
- tako.select.byElement
- tako.select.byFilter
- tako.select.get
- tako.select.tags
- tako.select.save
- tako.select.applyNamed
- tako.layers.list
- tako.layers.create
- tako.layers.rename
- tako.layers.remove
- tako.layers.setVisible
- tako.layers.moveAtoms
- tako.layers.group
- tako.layers.merge
- tako.annotations.add
- tako.annotations.list
- tako.annotations.update
- tako.annotations.remove
- tako.annotations.move
- tako.measure.distance
- tako.measure.angle
- tako.measure.list
- tako.measure.clear
- tako.constraints.fix
- tako.constraints.free
- tako.constraints.list
- tako.trajectory.frame
- tako.trajectory.count
- tako.trajectory.play
- tako.trajectory.pause
- tako.trajectory.loop
- tako.trajectory.range
- tako.trajectory.mode
- tako.trajectory.rate
- tako.trajectory.skip
- tako.project.list
- tako.project.active
- tako.project.create
- tako.project.switch
- tako.project.close
- tako.project.rename
- tako.project.pin
- tako.history.undo
- tako.history.redo
- tako.history.checkout
- tako.history.list
Every call in this namespace crosses the worker→host RPC bridge and MUTATES the live application state the user is currently looking at — the active selection highlights, the layer tree, annotations/measurements, the camera, trajectory playback, which project tab is open, and undo history itself. That is the opposite of tako.structure.*, whose verbs are pure functions that return a new, independent Structure copy and touch nothing on screen. Reach for tako.select/tako.layers/tako.annotations/etc. specifically when the goal is to inspect, stage, or present the scene the user sees (select the atoms a result is about, label a key distance, frame the camera before tako.ui.capture()) rather than to compute a new structure.
Most structural edits here — layer changes, annotations, constraints, named selections — go through the same auto-commit path as manual editing and are undoable through tako.history.*. A few things are deliberately excluded from that history: atom/tag selection, camera position, and trajectory-playback state are never recorded, so undo/redo/checkout never moves the camera or reverts a selection. Each project tab (tako.project.*) also carries its own independent undo history and camera — switching tabs swaps both in, so tako.history.* always operates on whichever project is currently active.
tako.select.set
await tako.select.set(ids: number[]) => { atoms: number[]; tags: string[] }
Replace the atom selection with exactly these atom ids.
| Parameter | Type | Default | Contract |
|---|---|---|---|
ids | number[] | — | Stable Atom.id values (not array indices); ids that do not exist in the current structure are dropped before selecting. |
Returns: { atoms, tags } — the resulting selection: atoms is the new selected-atom-id list, tags is the (unchanged, unless cleared — see notes) selected-tag-id list.
- Also clears the tag/annotation selection (tags becomes []) in the app’s default selection-click mode. If the UI is currently in ‘onlyAdd’ selection mode (a sticky click-mode toggle the user can leave on, off by default), ids are added to the existing selection instead of replacing it.
- If ‘select across layers’ is off in the UI (on by default), ids outside the active layer/group are silently dropped too.
const { atoms } = await tako.select.set([12, 13, 14]);
tako.select.add
await tako.select.add(ids: number[]) => { atoms: number[]; tags: string[] }
Add these atom ids to the current atom selection.
| Parameter | Type | Default | Contract |
|---|---|---|---|
ids | number[] | — | Ids to add; ids not present in the structure are dropped, ids already selected are left as-is. |
Returns: { atoms, tags } — the selection after the union.
- Unlike select.set, this does not filter by the active layer/group selection scope and does not touch the tag selection.
- This is a set union, not a toggle — re-adding an already-selected id is a no-op for that id.
tako.select.remove
await tako.select.remove(ids: number[]) => { atoms: number[]; tags: string[] }
Remove these atom ids from the current atom selection.
| Parameter | Type | Default | Contract |
|---|---|---|---|
ids | number[] | — | Ids to drop from the selection; ids not currently selected are ignored. |
Returns: { atoms, tags } — the selection after removal.
- Only affects atoms; the tag selection is untouched.
tako.select.clear
await tako.select.clear() => { atoms: number[]; tags: string[] }
Clear both the atom and tag/annotation selection.
Returns: { atoms: [], tags: [] }.
tako.select.all
await tako.select.all() => { atoms: number[]; tags: string[] }
Select every atom in the structure.
Returns: { atoms, tags } — atoms is every atom id in the structure.
- Ignores the active layer/group selection scope and the ‘select across layers’ setting — always selects every atom in the live structure.
- Leaves the tag selection untouched.
tako.select.invert
await tako.select.invert() => { atoms: number[]; tags: string[] }
Invert the atom selection across the whole structure.
Returns: { atoms, tags } — atoms is every atom id that was NOT previously selected.
- Inversion is over the entire structure regardless of layer scope: previously-selected atoms become unselected and vice versa.
- Leaves the tag selection untouched.
tako.select.byElement
await tako.select.byElement(element: string, mode?: 'replace' | 'add' | 'remove' | 'invert') => { atoms: number[]; tags: string[] }
Select atoms of one element.
| Parameter | Type | Default | Contract |
|---|---|---|---|
element | string | — | Exact, case-sensitive match against atom.element (e.g. “Fe”). |
mode | 'replace' | 'add' | 'remove' | 'invert' | 'replace' | ’replace’ sets the selection to just the matches; ‘add’ unions them in; ‘remove’ drops them from the current selection; ‘invert’ toggles just the matched atoms. |
Returns: { atoms, tags } — the selection after applying the mode.
- Matching ignores layer scope/visibility/lock state entirely — it filters purely on atom.element across the whole structure.
await tako.select.byElement('O');
await tako.select.byElement('H', 'add');
tako.select.byFilter
await tako.select.byFilter(filter: { query?: string; element?: string; layerId?: string; selected?: boolean; constrained?: 'any' | 'constrained' | 'free'; visible?: boolean; locked?: boolean }, mode?: 'replace' | 'add' | 'remove' | 'invert') => { atoms: number[]; tags: string[] }
Select atoms matching a filter predicate.
| Parameter | Type | Default | Contract |
|---|---|---|---|
filter.query | string | — | Case-insensitive substring match against a haystack of id, element, layerId, the layer’s name, label, and x/y/z/fx/fy/fz (stringified). |
filter.element | string | — | Exact match on atom.element. |
filter.layerId | string | — | Exact match on atom.layerId. |
filter.selected | boolean | — | Matches the atom’s selection state BEFORE this call is applied. |
filter.constrained | 'any' | 'constrained' | 'free' | 'any' (field omitted) | ’constrained’ = atom has at least one fixed axis; ‘free’ = no fixed axis; ‘any’/omitted = no filtering on constraint state. |
filter.visible | boolean | — | Matches whether the atom’s layer (and its layer group) is currently visible. |
filter.locked | boolean | — | Matches whether the atom’s layer is locked. |
mode | 'replace' | 'add' | 'remove' | 'invert' | 'replace' | Same semantics as select.byElement’s mode. |
Returns: { atoms, tags } — the selection after applying the mode.
- Filter fields are ANDed together; an empty object {} matches every atom (so byFilter({}, ‘invert’) behaves like select.invert()).
- Ignores layer scope the same way select.byElement/all/invert do — it is not restricted by the active-layer selection scope.
await tako.select.byFilter({ element: 'C', constrained: 'free' });
tako.select.get
await tako.select.get() => { atoms: number[]; tags: string[] }
Read the current selection without changing it.
Returns: { atoms, tags } — the current selected atom ids and selected tag/annotation ids.
tako.select.tags
await tako.select.tags(ids: string[], mode?: 'replace' | 'add') => { atoms: number[]; tags: string[] }
Select text tags (annotations) by id.
| Parameter | Type | Default | Contract |
|---|---|---|---|
ids | string[] | — | Annotation ids to select. Only text-kind annotations are selectable as tags — distance/angle measurement ids and unknown ids are silently filtered out. |
mode | 'replace' | 'add' | 'replace' | ’replace’ sets the tag selection to exactly these (valid) ids. ‘add’ toggles each given id individually: one not currently selected is added, one already selected is removed. |
Returns: { atoms, tags } — tags reflects the new tag selection.
- In ‘replace’ mode, if none of the given ids are valid text tags, the tag selection is cleared to [].
- Unlike clicking a tag in the UI (which replaces the whole selection with just that tag), this call never touches the atom selection — atoms is unaffected either way.
tako.select.save
await tako.select.save(name: string) => { id: string; name: string; atomIds: number[] } | null
Save the current atom selection as a named selection.
| Parameter | Type | Default | Contract |
|---|---|---|---|
name | string | — | Display name; if it trims to empty, an auto-generated name like ‘Selection N’ is used instead. |
Returns: The new named selection, or null if there is no active structure. Saved from the CURRENT atom selection (not an argument), deduplicated and filtered to atoms that still exist.
- Records an undo step (‘Create named selection’) and persists it into the active project.
- Named selections only ever store atom ids — the tag selection is not part of them.
await tako.select.set([0, 1, 2]);
const saved = await tako.select.save('active site');
tako.select.applyNamed
await tako.select.applyNamed(id: string) => { atoms: number[]; tags: string[] }
Re-apply a previously saved named selection by id.
| Parameter | Type | Default | Contract |
|---|---|---|---|
id | string | — | Id returned by tako.select.save. |
Returns: { atoms, tags } — the selection after applying (or the unchanged current selection if id is unknown).
- Re-resolves the saved atom ids against the LIVE structure at apply time, dropping any that no longer exist (e.g. atoms deleted since the selection was saved).
- An unknown id is a silent no-op: the current selection is returned unchanged, with no error.
- Replaces the atom selection outright (like select.set) and does not affect the tag selection.
tako.layers.list
await tako.layers.list() => Array<{ id: string; name: string; kind: 'layer' | 'group'; visible: boolean; locked: boolean; groupId?: string | null; active: boolean }>
List every layer and layer group (folder) in the active structure.
Returns: Groups (folders) first, then every layer — including layers nested inside a group — as one flat array.
- A layer’s groupId names its parent folder (null/absent at the top level); group entries (kind: ‘group’) have no groupId field.
- active marks the current UI-focused layer or group; it is independent of the atom selection.
tako.layers.create
await tako.layers.create(options?: { name?: string; group?: string | null }) => { id: string; name: string; kind: 'layer'; visible: boolean; locked: boolean; groupId?: string | null; active: boolean }
Create a new (empty) layer.
| Parameter | Type | Default | Contract |
|---|---|---|---|
options.name | string | — | If given, renames the new layer after creation; otherwise it gets an auto-numbered name like ‘Layer N’. |
options.group | string | null | the currently active group in the UI | Parent folder id. An id that does not match a real group is accepted without validation — the layer is simply tagged with it. |
Returns: The created layer’s info.
- The new layer becomes the active layer and the sole selected layer.
- If name is given, this records TWO undo steps (‘Add layer’ then ‘Rename layer’).
const layer = await tako.layers.create({ name: 'Adsorbate' });
tako.layers.rename
await tako.layers.rename(id: string, name: string) => void
Rename a layer or a layer group (folder).
| Parameter | Type | Default | Contract |
|---|---|---|---|
id | string | — | A layer id or a group id (checked against groups first). |
name | string | — | New name; trimmed. An empty (after trim) name is ignored. |
Returns: void.
- An id that matches neither a layer nor a group is a silent no-op on the tree, but the layer-rename path still sets it as the ‘active layer’ and unconditionally records an undo step (‘Rename layer’) — so a bogus id can add a no-op entry to tako.history.list().
tako.layers.remove
await tako.layers.remove(id: string) => void
Delete a layer or ungroup a layer group (folder).
| Parameter | Type | Default | Contract |
|---|---|---|---|
id | string | — | A layer id or a group id. |
Returns: void.
- Deleting a group ungroups its layers (they become top-level) rather than deleting their atoms; deleting a layer reassigns its atoms to another existing layer — atoms are never deleted by this call.
- The last remaining layer cannot be removed (silently refused) — there is always at least one layer.
- An unknown id is a silent no-op on the tree, but (like rename) it still becomes the ‘active layer’ first and unconditionally records an undo step, which can also reassign the active-layer pointer to an arbitrary fallback layer.
tako.layers.setVisible
await tako.layers.setVisible(id: string, visible: boolean) => void
Show or hide a layer or layer group.
| Parameter | Type | Default | Contract |
|---|---|---|---|
id | string | — | A layer id or a group id. |
visible | boolean | — | Target visibility. |
Returns: void.
- Cleanly no-ops (no undo step) for an unknown id or when visible already matches the current state — unlike rename/remove, this path is properly existence-guarded.
tako.layers.moveAtoms
await tako.layers.moveAtoms(ids: number[], layerId: string) => void
Move atoms into a layer.
| Parameter | Type | Default | Contract |
|---|---|---|---|
ids | number[] | — | Atom ids to move; subject to the same existence/active-layer-scope filtering as select.set, since it is implemented by selecting the atoms first. |
layerId | string | — | Destination layer id; must already exist. |
Returns: void.
- If layerId does not match an existing layer, or no atoms end up selected after filtering, this is a silent no-op with no undo step.
- On success this reassigns atom.layerId (a structure edit) and records an undo step; it also makes layerId the active layer.
tako.layers.group
await tako.layers.group(layerIds: string[], name?: string) => void
Group these layers into a new folder.
| Parameter | Type | Default | Contract |
|---|---|---|---|
layerIds | string[] | — | Layer ids to fold into a new group. |
name | string | — | Name for the new folder; auto-numbered (‘Folder N’) if omitted. |
Returns: void.
- If none of layerIds match existing layers (including an empty array), this still creates a new EMPTY folder rather than failing.
- The new folder becomes the active group; records an undo step.
tako.layers.merge
await tako.layers.merge(layerIds: string[]) => void
Merge these layers into one.
| Parameter | Type | Default | Contract |
|---|---|---|---|
layerIds | string[] | — | Layers to merge. The first id is the merge target; the rest are absorbed into it (their atoms’ layerId is reassigned, then the source layers are deleted). If only one id is given, the currently-active layer is used as the target instead. |
Returns: void.
- A no-op if fewer than two distinct target/source ids result (e.g. an empty or single-element array with no active layer to fall back to).
- Source ids that don’t match a real layer still count toward ‘there is something to merge’: merge([‘realLayer’, ‘bogus’]) still records an undo step (‘Merge layers’) even though nothing observable changed.
tako.annotations.add
await tako.annotations.add(input: { text?: string; atomId?: number; position?: { x: number; y: number; z: number }; style?: { color?: string; size?: number; weight?: number } }) => string
Add a text annotation (tag) to the scene.
| Parameter | Type | Default | Contract |
|---|---|---|---|
input.text | string | 'Text' | Label text. |
input.atomId | number | — | Anchor the label to an atom: it live-follows that atom’s current position and is hidden while the atom doesn’t exist. |
input.position | { x: number; y: number; z: number } | (0,0,0), or the anchor atom’s position if atomId is set | World position (Å); used as the creation-time fallback, especially when atomId is absent. |
input.style | { color?: string; size?: number; weight?: number } | — | size is clamped to [8, 32]px. |
Returns: The new annotation’s id.
- An atomId that doesn’t match any atom in the structure is accepted without error — the label is created but stays hidden until an atom with that id exists.
- Records an undo step (‘Add annotation’).
const id = await tako.annotations.add({ atomId: 3, text: 'active site' });
tako.annotations.list
await tako.annotations.list() => Array<{ id: string; kind: string; text: string; atomId?: number; position?: { x: number; y: number; z: number } }>
List every text annotation (tag) currently on the structure.
Returns: One entry per text annotation; kind is always ‘text’.
- Distance/angle measurements are excluded — see tako.measure.list.
- position reflects where the label was created/last explicitly placed in world space, not a live-tracked atom position — for atom-anchored labels (atomId set) the actual on-screen position instead follows the atom live and can differ from this field.
tako.annotations.update
await tako.annotations.update(id: string, patch: { text?: string; style?: { color?: string; size?: number; weight?: number } }) => void
Edit an annotation’s text or style.
| Parameter | Type | Default | Contract |
|---|---|---|---|
id | string | — | Any annotation id — text tag or measurement. |
patch.text | string | — | New label text. |
patch.style | { color?: string; size?: number; weight?: number } | — | Only applies to text-kind annotations; ignored for measurements. |
Returns: void.
- IMPORTANT: on a distance or angle measurement id, a patch.text that parses as a leading number is NOT a relabel — it re-solves the measurement to that value by MOVING an atom (mirroring dragging the measurement handle in the UI) and records a structure-edit undo step (‘Set distance’/‘Set angle’). Use a non-numeric string, or target a text tag, to just change display text.
- An unknown id is a silent no-op — no error, no undo step.
tako.annotations.remove
await tako.annotations.remove(id: string) => void
Remove an annotation.
| Parameter | Type | Default | Contract |
|---|---|---|---|
id | string | — | Any annotation id — text tag or measurement. |
Returns: void.
- Works uniformly across text tags and distance/angle measurements. Unknown id: silent no-op, no undo step.
tako.annotations.move
await tako.annotations.move(id: string, dx: number, dy: number) => void
Nudge a label’s on-screen offset.
| Parameter | Type | Default | Contract |
|---|---|---|---|
id | string | — | Annotation id. |
dx | number | — | Pixel offset delta added to the label’s current screen-space offset. |
dy | number | — | Pixel offset delta (screen space, not world units). |
Returns: void.
- Additive: nudges the existing on-screen offset by (dx, dy) pixels; call repeatedly to accumulate a move.
- Unknown id: fully silent no-op, no undo step.
tako.measure.distance
await tako.measure.distance(i: number, j: number) => { id: string; kind: 'distance'; atomIds: number[]; label: string; value: number; unit: 'Å' }
Place a persistent distance measurement between two atoms.
| Parameter | Type | Default | Contract |
|---|---|---|---|
i | number | — | First atom id. |
j | number | — | Second atom id. Order doesn’t matter — atomIds are stored sorted ascending. |
Returns: { id, kind, atomIds, label, value, unit } — value is the live distance in Å, recomputed from current atom positions on every call (even if the stored label string is stale). unit is ‘Å’.
- Idempotent by atom pair: if a distance measurement already exists between these two atoms (regardless of call order), the existing annotation is returned rather than creating a duplicate.
- Throws (‘Cannot measure missing atoms.’) if either atom id doesn’t exist in the structure.
tako.measure.angle
await tako.measure.angle(i: number, j: number, k: number) => { id: string; kind: 'angle'; atomIds: number[]; label: string; value: number; unit: '°' }
Place a persistent angle measurement between three atoms.
| Parameter | Type | Default | Contract |
|---|---|---|---|
i | number | — | First atom id. |
j | number | — | Vertex atom id. |
k | number | — | Third atom id. |
Returns: { id, kind, atomIds, label, value, unit } — value is the live angle in degrees at vertex j, recomputed on every call. unit is ’°’.
- Unlike measure.distance, atomIds preserve call order (not sorted) — measuring the same three atoms in a different order creates a distinct measurement rather than reusing one.
- Adding an angle measurement removes any existing distance measurement between its first two atoms (i, j) — the angle supersedes it visually.
- Throws if any of the three atoms doesn’t exist.
tako.measure.list
await tako.measure.list() => Array<{ id: string; kind: 'distance' | 'angle'; atomIds: number[]; label: string; value: number; unit: string }>
List every persistent distance/angle measurement.
Returns: One entry per measurement.
- value is always recomputed from the atoms’ current positions (accurate even if the structure changed after the measurement was created); label is the last text explicitly set on the annotation and can lag behind if the geometry changed some other way.
- Text annotations (tags) are excluded — see tako.annotations.list.
tako.measure.clear
await tako.measure.clear() => void
Remove every distance/angle measurement (text tags are left alone).
Returns: void.
- Each removal records its own undo step (‘Remove measurement’) rather than one batched step.
tako.constraints.fix
await tako.constraints.fix(ids: number[], options?: { axes?: string | boolean }) => void
Fix atoms along the given axes.
| Parameter | Type | Default | Contract |
|---|---|---|---|
ids | number[] | — | Atom ids to fix. |
options.axes | string | boolean | true (all axes) | Omitted or true fixes all of x/y/z; false fixes none (equivalent to freeing); a string is matched case-insensitively for the letters it contains — e.g. ‘z’ fixes only z, ‘xy’ fixes x and y and leaves the other axis free. |
Returns: void.
- Ids in a locked layer are silently dropped (locked atoms can’t be edited); if none remain, this is a no-op.
- Implemented via the same tako.structure.fix verb the pure structure API uses, so this is exactly what later tako.optimize/tako.molecularDynamics calculations read as fixed degrees of freedom.
- Records an undo step (‘Set constraints’).
await tako.constraints.fix(bottomLayerIds, { axes: 'z' });
tako.constraints.free
await tako.constraints.free(ids: number[]) => void
Free atoms (remove all constraints).
| Parameter | Type | Default | Contract |
|---|---|---|---|
ids | number[] | — | Atom ids to fully release. |
Returns: void.
- Equivalent to constraints.fix(ids, { axes: false }); removes the constraint field entirely rather than leaving an explicit all-free marker.
- Same locked-layer filtering as constraints.fix.
tako.constraints.list
await tako.constraints.list() => Array<{ atomId: number; fixed: [boolean, boolean, boolean] }>
Per-atom fixed-axis mask for currently-constrained atoms.
Returns: fixed[k] is true where that axis (x, y, z) is fixed.
- Only atoms with at least one fixed axis are listed — fully free atoms (never constrained, or freed back to all-free) don’t appear at all.
tako.trajectory.frame
await tako.trajectory.frame(n?: number) => number
Get the current trajectory frame index, or set it when n is given.
| Parameter | Type | Default | Contract |
|---|---|---|---|
n | number | — | If given, jumps to this frame index (floored, then clamped into [0, frameCount - 1]); if omitted, just reads the current index. |
Returns: The resulting current frame index.
- Throws (‘No active calculation with a trajectory. Open a calculation project first.’) if no calculation project tab is currently active — including the getter form (frame() with no argument).
- If the active calculation has zero trajectory frames, setting n is a silent no-op (the index is unaffected) rather than throwing.
tako.trajectory.count
await tako.trajectory.count() => number
Number of frames in the active calculation’s trajectory.
Returns: 0 if there’s no active calculation or it has no frames.
- Unlike the other trajectory.* calls, this never throws when there’s no active calculation — it just returns 0.
tako.trajectory.play
await tako.trajectory.play() => void
Start trajectory playback for the active calculation.
Returns: void.
- Throws the same ‘no active calculation’ error as trajectory.frame.
- Playback state is not part of the undo history.
tako.trajectory.pause
await tako.trajectory.pause() => void
Pause trajectory playback.
Returns: void.
- Throws the same ‘no active calculation’ error as trajectory.frame.
tako.trajectory.loop
await tako.trajectory.loop(on: boolean) => void
Set whether trajectory playback loops back to the range start.
| Parameter | Type | Default | Contract |
|---|---|---|---|
on | boolean | — | Whether playback loops after reaching the end of its range. |
Returns: void.
- Throws the same ‘no active calculation’ error as trajectory.frame.
tako.trajectory.range
await tako.trajectory.range(start: number, end: number | null) => void
Set the trajectory playback range.
| Parameter | Type | Default | Contract |
|---|---|---|---|
start | number | — | First frame of the playback range. |
end | number | null | — | Last frame of the range; null means through the last available frame. |
Returns: void.
- Both bounds are clamped into the valid frame range, and the current frame index is re-clamped into the new range too.
- Throws the shared ‘no active calculation’ error, or is a no-op if the active calculation has zero frames.
tako.trajectory.mode
await tako.trajectory.mode(modeIndex: number) => void
Jump playback to a named mode group within the trajectory (e.g. one vibrational/phonon normal mode).
| Parameter | Type | Default | Contract |
|---|---|---|---|
modeIndex | number | — | Selects a frame group tagged with this mode index at calculation time. |
Returns: void.
- Jumps the current frame to that mode’s first frame and sets the playback range to exactly that mode’s frame span.
- An unrecognized modeIndex (or a trajectory with no mode-tagged frames, e.g. a plain MD/optimization run) is a silent no-op.
- Throws the shared ‘no active calculation’ error.
tako.trajectory.rate
await tako.trajectory.rate(framesPerSecond: number) => void
Set the trajectory playback rate.
| Parameter | Type | Default | Contract |
|---|---|---|---|
framesPerSecond | number | — | Rounded and clamped to [1, 60]. |
Returns: void.
- Throws the shared ‘no active calculation’ error.
tako.trajectory.skip
await tako.trajectory.skip(skipFrames: number) => void
Set how many extra frames playback advances per tick.
| Parameter | Type | Default | Contract |
|---|---|---|---|
skipFrames | number | — | 0 advances one frame at a time; clamped to [0, 120]. |
Returns: void.
- Throws the shared ‘no active calculation’ error.
tako.project.list
await tako.project.list() => Array<{ id: string; name: string; kind: 'structure' | 'calculation'; pinned: boolean; active: boolean }>
List every open project tab.
Returns: One entry per project tab.
tako.project.active
await tako.project.active() => { id: string; name: string; kind: 'structure' | 'calculation'; pinned: boolean; active: boolean } | null
Read the currently active project tab.
Returns: The active project’s info, or null (there is normally always at least one project tab open).
tako.project.create
await tako.project.create(options?: { structure?: Structure; title?: string }) => { id: string; name: string; kind: 'structure' | 'calculation'; pinned: boolean; active: boolean } | null
Open a new project tab, or a blank one.
| Parameter | Type | Default | Contract |
|---|---|---|---|
options.structure | Structure | — | If given, opens it as the project’s structure. |
options.title | string | — | Tab name; defaults to the structure’s own title or an auto-generated name. |
Returns: The resulting active project’s info.
- Without structure, opens a blank project tab.
- Reuses the CURRENT tab instead of opening a new one if it’s already blank (no structure/layers) — so this doesn’t always create a brand-new tab.
- Always creates a kind: ‘structure’ tab; calculation tabs (kind: ‘calculation’) are created by running a calculation operation (tako.optimize, etc.), not by this call.
- Switches the active project to the new/reused tab.
tako.project.switch
await tako.project.switch(id: string) => void
Switch the active project tab.
| Parameter | Type | Default | Contract |
|---|---|---|---|
id | string | — | Target project id. |
Returns: void.
- Silently no-ops for an unknown id, an empty id, or the already-active id.
- Saves the outgoing tab’s full scene snapshot (structure, layers, annotations, camera, undo history, …) before switching, and restores the target tab’s own independent undo history and camera — tako.history.* after switching operates on the NEW tab’s history, not a shared global one.
tako.project.close
await tako.project.close(id?: string) => void
Close a project tab.
| Parameter | Type | Default | Contract |
|---|---|---|---|
id | string | the currently active project | Project id to close. |
Returns: void.
- Silently no-ops for an unknown id.
- Closing the last remaining tab doesn’t leave zero tabs — it’s replaced by a fresh blank project (there is always at least one).
- Discards that tab’s in-memory scene state and undo history; there is no confirmation step at this layer.
tako.project.rename
await tako.project.rename(id: string, name: string) => void
Rename a project tab.
| Parameter | Type | Default | Contract |
|---|---|---|---|
id | string | — | Project id. |
name | string | — | New name; trimmed. |
Returns: void.
- Silent no-op for an unknown id, or a name that trims to empty.
tako.project.pin
await tako.project.pin(id: string) => void
Pin a project tab.
| Parameter | Type | Default | Contract |
|---|---|---|---|
id | string | — | Project id. |
Returns: void.
- No-op if id is unknown or already pinned.
- Pinned tabs are moved to the front of the tab order (grouped together); there is no unpin call in this API surface.
tako.history.undo
await tako.history.undo() => void
Step one entry back in the active project’s undo history.
Returns: void.
- A no-op at the start of history (nothing to undo).
- Restores the ENTIRE scene snapshot at that point — structure, layers/groups, components, annotations, named selections, visualization settings, periodic-boundary flags — and always clears the current atom selection as part of the restore (the tag selection is left as-is). Camera position is NOT part of this snapshot, so undo never moves the camera.
tako.history.redo
await tako.history.redo() => void
Step one entry forward in the active project’s undo history.
Returns: void.
- A no-op at the end of history (nothing to redo).
- Same full-snapshot restore semantics as undo: everything except camera is restored, and the atom selection is always cleared.
tako.history.checkout
await tako.history.checkout(index: number) => void
Jump directly to an arbitrary history entry.
| Parameter | Type | Default | Contract |
|---|---|---|---|
index | number | — | Absolute history entry index, as listed by tako.history.list; may jump more than one step in either direction. |
Returns: void.
- An out-of-range index (negative or >= history length) is a silent no-op.
- Same full-snapshot restore semantics as undo/redo (camera untouched, atom selection cleared).
tako.history.list
await tako.history.list() => Array<{ index: number; current: boolean; title: string }>
List the auto-commit history entries recorded for the active project.
Returns: One entry per recorded edit.
- Each entry corresponds to one auto-recorded edit (the same auto-commit that fires for most structure/layer/annotation/constraint mutations in this namespace) — not every scene mutation creates one: selection, camera, and trajectory-playback changes are excluded from history.
- current marks the entry the workspace is presently sitting at (after undo/redo/checkout).
- Empty until the project’s first recorded edit — a freshly opened structure with no edits yet has no history entries.
Return to the Tako Script API index.