@witwin/host services

Every service and host member of @witwin/host 2.0 that a Plugin web half can use, generated from the Studio code.

Unstable: proposed services (@witwin/host/proposed) are for system Plugins and for Plugins loaded from a local path while you develop. They can change in any release and an installed Plugin cannot use them, so build a Plugin you release on the stable services only.

What a web half sees. How to use it: Web API. Source: studio/packages/witwin-host/src/index.ts (stable) and proposed.ts (proposed); ledger: dev_docs/frontend/HOST_API_PROPOSALS.md.

  • Stable services (8): scene, commands, contextKeys, notifications, opener, events, resources, rpc.
  • Proposed services (27): selection, history, fs, localFiles, platform, connection, panels, settings, slots, sceneEditor, parameters, runs, agent, results, arrays, timeline, agentChat, sceneView, tools, meshAssets, studies, thumbnails, assetImport, sceneFiles, components, componentForm, gallery. A system Plugin may inject them; a third-party Plugin only from a local path and only the names in its enabledApiProposals.

definePlugin({...}): the PluginDefinition#

FieldWhat it does
id: stringMust equal the manifest id.
inject?: readonly string[]Services that must exist before activate runs.
dependencies?: readonly PluginDependency[]Plugins this one needs.
slots?: readonly string[]Slots this Plugin declares, so a dependent's entries can wait for them (§5.1).
replaces?: readonly string[]What this Plugin replaces (§5.7): a Plugin id ("studio.console") stays off while this one is active; "<slot id>:<key>" takes that one entry ("properties.inspector:component:transform").
hosts?: readonly PluginHostKind[]Hosts this web half runs on; omitted means every host.
activate(host: FrontendHost): void | Promise<void>
deactivate?(): void | Promise<void>

host: the FrontendHost passed to activate#

Identity and services#

MemberWhat it does
readonly version: stringHost SDK version (semver).
readonly pluginId: stringThe Plugin this host is scoped to.
readonly system: booleanA system Plugin (source = "builtin"): pre-trusted, may use proposed services.
readonly services: HostServices

Workbench contributions#

MemberWhat it does
registerPanel(cfg: PanelConfig): Disposable
registerSettingsPage(cfg: SettingsPageConfig): Disposable
registerCommand(cfg: CommandConfig, handler: CommandHandler): Disposable
registerMenuItem(cfg: MenuItemConfig): Disposable
registerKeybinding(cfg: KeybindingConfig): Disposable
registerTopBarItem(cfg: TopBarItemConfig): Disposable
registerFileType(cfg: FileTypeConfig): Disposable

3D content (R3F)#

MemberWhat it does
registerVisualizer(cfg: VisualizerConfig): Disposable
registerGizmo(cfg: GizmoProvider): Disposable
registerOverlay(zone: OverlayZone, render: () => React.ReactNode): Disposable
registerPass(pass: RenderPass): Disposable
registerTool(tool: ViewportTool): Disposable

Built-in widget slots#

MemberWhat it does
registerWidgetDecorator(cfg: WidgetDecoratorConfig): DisposableClaim the values this Plugin owns inside a built-in widget slot.
registerInspector(componentName: string, component: React.ComponentType<InspectorProps>): DisposableDraw the Properties section body of every component named componentName (an entry of properties.inspector, key component:<name>).

Assistant#

MemberWhat it does
registerAssistantCard(cfg: AssistantCardConfig): Disposable
registerToolResultEffect(cfg: ToolResultEffectConfig): Disposable
registerToolArgumentPolicy(cfg: ToolArgumentPolicyConfig): Disposable
registerChatLink(cfg: ChatLinkConfig): DisposableHover, embed and existence of witwin://<pluginId>.<name>/… links; opening goes through opener.
registerAgentContext(provider: AgentContextProvider): Disposable

Plugin-scoped helpers#

MemberWhat it does
readonly state: PluginStateApi
readonly theme: { tokens(): Record<string, string>; onChange(cb: () => void): Disposable }
readonly assets: ViewportAssetsApi
readonly interaction: ViewportInteractionApi
readonly playback: PlaybackApi
readonly assistant: AssistantHostApi
readonly ui: { Button: React.ComponentType<any>; Select: React.ComponentType<any>; DraggableNumberInput: React.ComponentType<any>; Checkbox: React.ComponentType<any>; /** * Render the built-in widget for props.config.widget_type with the * Inspector's widget props ({ value, field, config, ... }). One argument: * the config travels inside props. */ renderWidget: (props: any) => React.ReactNode; }Host UI widgets, live components that share the host's React.

Stable services#

scene: SceneService#

Scene objects of the current Scene, read and written through the op stream.

MemberWhat it does
getObjects(): Record<string, HostSceneObject>Current objects, keyed by id.
getSelection(): string[]Ids of the selected scene objects.
currentSceneId(): string | nullBackend id of the current Scene, or null while none is loaded.
subscribe(listener: () => void): DisposableCalled on object and selection changes.
getObjectComponents(objectId: string): unknown[]Component display metadata (fields, buttons, groups) of one object; [] if unknown.
updateComponentField(objectId: string, componentName: string, fieldName: string, value: unknown): Promise<void>One undo step through the scene command stream.
updateObjectTransform( objectId: string, transform: { position?: [number, number, number]; rotation?: [number, number, number, number] | [number, number, number]; scale?: [number, number, number]; }, ): Promise<void>

commands: CommandsService#

MemberWhat it does
execute(id: string, args?: unknown): Promise<unknown>
register(command: CommandConfig, handler: CommandHandler): DisposableRegister a command run in this window.

contextKeys: ContextKeysService#

MemberWhat it does
get(key: string): unknown
set(key: string, value: unknown): void
subscribe(listener: () => void): DisposableCalled after any key changed.

notifications: NotificationsService#

MemberWhat it does
show(notification: HostNotification): voidA toast plus an entry in the notification panel; the same id updates it.

opener: OpenerService#

Show a thing by its URI instead of naming the panel that shows it: witwin://file/data/a.npz, witwin://scene/world/object/box, witwin://result/12, witwin://panel/<panelId>.

MemberWhat it does
open(uri: string, options?: OpenOptions): Promise<boolean>Resolves true when a handler took the URI.
canOpen(uri: string, options?: OpenOptions): boolean
registerHandler(handler: OpenHandler): Disposable

events: EventsService#

"Something happened" notices (project.file.created, run.finished, ...).

MemberWhat it does
emit(name: string, payload: unknown): void
on(name: string, listener: (payload: any) => void): Disposable

resources: ResourcesService#

MemberWhat it does
fetch(ref: ResourceRef, init?: RequestInit): Promise<Response>fetch with the connection's authorization.
objectUrl(ref: ResourceRef): Promise<{ url: string; release(): void }>A blob: URL for an <img>; call release when done.
signedUrl(ref: ResourceRef): Promise<string>A URL that loads without headers (iframes, loaders): short-lived and signed where the connection needs it.

rpc: RpcService#

Calls into the Plugin's Python half over the Project connection.

MemberWhat it does
call<T = unknown>(method: string, params?: Record<string, unknown>, options?: RpcCallOptions): Promise<T>
subscribe(topic: string, listener: (event: RpcEvent) => void, options?: { fromSeq?: number }): Disposable

Proposed services#

selection: SelectionService#

The one "current thing" of the window.

  • Used by: Project (studio.project: the projectFile kind), Properties (studio.properties: which inspector to draw)
  • Stable when: Hierarchy and Properties use it with no Studio store import
MemberWhat it does
current(): Subject | null
set(subject: Subject | null): void
subscribe(listener: (subject: Subject | null) => void): Disposable
registerKind(kind: { kind: string; clear(): void }): Disposableclear runs when a Subject of another kind replaces this kind's.

history: HistoryService#

The Scene's undo history.

  • Used by: Modeling (studio.modeling), Properties (studio.properties: ScriptGeometry Design Apply)
  • Stable when: Viewport tools record through it and Ctrl+Z works across Plugins
MemberWhat it does
undo(): void
redo(): void
canUndo(): boolean
canRedo(): boolean
subscribe(listener: () => void): Disposable
push(step: HistoryStep): voidRecord a step.
recordComponentEdit( objectId: string, componentName: string, oldValues: Record<string, unknown>, newValues: Record<string, unknown>, ): voidRecord component fields the Plugin already wrote as one step; values by field name.
runSceneEdit<T>(body: () => Promise<T>, options?: { label?: string }): Promise<SceneEditOutcome<T>>Run body (Tool calls or ops that edit the current Scene) and record everything it changed, once its broadcasts arrived, as one step: objects created and deleted (restored with their geometry), parents, transforms, component fields.

fs: FsService#

Project files, on the server.

  • Used by: Git (studio.git: refresh when Project files change), Project (studio.project: the whole panel)
  • Stable when: Project panel runs on it on Local, Router and Control
MemberWhat it does
list(path: string, options?: { offset?: number; limit?: number; filter?: string; sort?: FsSort; signal?: AbortSignal }): Promise<FsListResult>One folder, or one page of it with offset / limit.
read(path: string): Promise<FsReadResult>
write(path: string, content: string, options?: { expectedMtime?: number }): Promise<void>expectedMtime: refuse with conflict when the file changed meanwhile.
createFolder(path: string): Promise<void>
rename(path: string, newName: string): Promise<void>
move(paths: string[], destination: string): Promise<void>
delete(paths: string[]): Promise<void>
copy(paths: string[], destination: string): Promise<void>Copy into destination; a name that exists there gets a number (name 2).
createIn(parent: string, kind: 'file' | 'folder'): Promise<string>A new empty file or folder in parent, named by the server ("Untitled", "New Folder").
reveal(path: string): Promise<void>Show the file in the file manager of the computer that runs the ProjectServer.
openExternal(path: string): Promise<void>Open the file with that computer's default application.
onDidChange(listener: (path: string) => void): DisposableCalled with the changed path after a file appeared, changed or went away.

localFiles: LocalFilesService#

Files on the user's own computer: pick, read, import into the Project; native open dialog, fromNativePath, projectPath (P5c); the window's import queue (imports: tasks with progress, conflicts, retryable, folder; start, cancel, retry with a conflict policy, dismiss)

  • Used by: Modeling (studio.modeling, Import CAD), Project (studio.project: drop and pick imports, the queue, the conflict dialog)
  • Stable when: Import works in the browser and the desktop app through the same calls
MemberWhat it does
fromFile(file: File): LocalFileHandle
pick(options?: { accept?: string; multiple?: boolean }): Promise<LocalFileHandle[]>
fromDroppedFolders(items: DataTransferItemList): Promise<{ folder: string; files: LocalFileHandle[] }[]>
showOpenDialog(options?: { title?: string; filters?: { name: string; extensions: string[] }[] }): Promise<string[] | null>The native open dialog, where the platform has nativeDialogs; it starts in the Project folder.
fromNativePath(path: string): LocalFileHandleA native path (from showOpenDialog) as a handle, for importInto.
projectPath(nativePath: string): string | nullThe Project-relative POSIX path of a native path inside the Project, else null.
imports: ProjectImportsApiFiles being copied or uploaded into the Project, and their progress (D16 P5b2).

platform: PlatformService#

What this connection can do (sameMachine, nativeDialogs, revealLocal, serverProcess, pluginInstall)

  • Used by: none yet
  • Stable when: Control sends the full table and no Plugin guesses from the host name
MemberWhat it does
has(capability: PlatformCapability): boolean
capabilities(): Readonly<Record<PlatformCapability, boolean>>
subscribe(listener: () => void): Disposable

connection: ConnectionService#

State of the Project connection and binary frames; the open Project (project(), onProjectChanged). onMessage (flat broadcasts by type) was removed with the flat bus (D16 P9, 2026-11-04)

  • Used by: Terminal (studio.terminal), Console, Git, Tasks, Library, Results (studio.console, studio.git, studio.tasks, studio.library, studio.results)
  • Stable when: One more system Plugin uses binary frames without changes
MemberWhat it does
isConnected(): boolean
onConnected(listener: () => void): DisposableAfter every (re)connect.
onReset(listener: () => void): DisposableThe server restarted or lost state: refetch everything.
onBinary(listener: (buffer: ArrayBuffer) => void): DisposableBinary frames on the Project connection (terminal output, streams).
project(): { id: string; name: string } | nullThe Project this window serves; null while none is open.
onProjectChanged(listener: () => void): DisposableAfter project() changed (opened, closed, switched).

panels: PanelsService#

The track layout.

  • Used by: Terminal (studio.terminal), Console, Tasks, Results (studio.console, studio.tasks, studio.results), Hierarchy (studio.hierarchy), Parameters (studio.parameters), Project (studio.project: focus-scoped shortcuts), Gallery (studio.gallery: its tab, and its page with no Project open)
  • Stable when: The Window menu and the palette use it for every Plugin panel
MemberWhat it does
open(panelId: string): void
focus(panelId: string): void
close(panelId: string): void
isOpen(panelId: string): boolean
list(): PanelInfo[]
openInstance(panelId: string, key: string, options?: { title?: string; params?: Record<string, unknown>; preview?: boolean }): voidOpen instance key of a multi-instance panel as tab <id>:<key> with params (the component reads them).
focused(): string | nullThe panel the user last pressed in (hierarchy, project, ...), for shortcuts that act only in the focused panel; null when none.
setFocused(panelId: string | null): voidCall on pointer-down in the panel's root.
setTitle(tabId: string, title: string): voidRetitle an open instance tab (<prefix>:<key>), e.g.
openPage(pageId: string): voidShow the workbench.pages entry pageId over the editor area, in place of the stage and Chat; the Plugins page closes.
closePage(pageId?: string): voidClose the open page; with pageId, only when that page is the open one.
openPageId(): string | nullThe workbench.pages entry showing now; null when none.

settings: SettingsService#

Values of settings entries the Python halves declare (settings_id → fields).

  • Used by: none yet
  • Stable when: Settings pages of two Plugins read and write through it
MemberWhat it does
get(settingsId: string, field: string): unknown
set(settingsId: string, field: string, value: unknown): void
subscribe(listener: () => void): Disposable
openPage(pageId?: string): voidOpen the Settings dialog at a page (settings.pages id or a built-in page).

slots: SlotsService#

Raw slot access (plan §5.4): declare a slot this Plugin owns, register into any slot.

  • Used by: Console (console.channels), Tasks (tasks.step.editor), Library (viewport.create, viewport.drop, hierarchy.*, agent.drops), Results (viewport.overlay, plot.result, workbench.root), Hierarchy (hierarchy.*), Parameters (parameters.variedBy), Project (project.fileTypes), Properties (properties.inspector), Arrays (studio.arrays), Viewport (viewport.toolbar, viewport.sceneLayers, viewport.overlayMenu, viewport.mode), Modeling (those and viewport.overlay), Document (document.viewer), Agent (agent.*), Studies (studies.kind.editor, studies.method.editor; fills parameters.variedBy), Materials (project.fileTypes, properties.inspector, workbench.root), EM (properties.inspector facet component:material#em), Gallery (workbench.pages)
  • Stable when: Two Plugins extend each other through declared slots
MemberWhat it does
declare<T>(slot: { id: string; shape: SlotShape; keyOf?: (entry: T) => string; orderOf?: (entry: T) => number | null | undefined; fallback?: T; }): Disposable
register<T>(slotId: string, value: T, options?: { key?: string; order?: number; replaces?: string }): Disposablereplaces: the owner whose entry for the same key this one replaces on purpose (§5.7), e.g.
values<T>(slotId: string): T[]
get<T>(slotId: string, key: string): T | undefined
subscribe(listener: () => void): Disposable

sceneEditor: SceneEditorService#

Edit the current Scene.

  • Used by: Hierarchy (studio.hierarchy), Parameters (studio.parameters), Library (studio.library), Results (studio.results), Modeling (studio.modeling), Properties (studio.properties: component edits, transform, previews), Materials (studio.materials)
  • Stable when: Viewport and Properties edit the Scene only through it, and a third-party outline panel works on it
MemberWhat it does
state(): SceneEditorState
subscribe(listener: () => void): Disposable
outline(): SceneOutlineNode[]The objects as a tree in child order.
select(ids: readonly string[], mode?: SceneSelectMode): void[] clears.
rename(id: string, name: string): voidUndoable.
setVisible(id: string, visible: boolean): void
setLocked(id: string, locked: boolean): void
createObject(spec: { name: string; mesh?: string; parentId?: string | null }): void
deleteSelection(): void
copySelection(): void
paste(): void
duplicate(id: string): void
reparent(ids: readonly string[], parentId: string | null, index?: number): voidMove under parentId (null: top level) at index.
reorder(id: string, targetId: string, position: 'before' | 'after'): voidReorder among the same siblings.
extractToScene(id: string, name?: string): Promise<void>Move the subtree into its own Scene file and reference it.
focus(id: string): voidFrame the object in the viewport.
chatAttachment(id: string): { label: string; [key: string]: unknown }An Agent chat attachment for the object (opaque; pass it to AddToChatMenuItem).
applyOp(op: Record<string, unknown>): Promise<SceneOpResult>Send one scene op ({type, ...}, snake_case keys) through the op stream and resolve with the server's answer.
onPatch(type: string, listener: (patch: Record<string, any>) => void): DisposableEach patch of type in the committed scene ops ({type, ...}, snake_case keys, as the server records it; e.g.
loadComponents(id: string): voidAsk the server for an object's components when it arrived without them (large Scenes load lazily).
addComponent(id: string, componentName: string): void
removeComponent(id: string, componentName: string): void
previewObject(object: SceneEditorObject): voidShow an edited copy of the object in this window until the next server update (live preview while dragging).
setComponentField(id: string, componentName: string, fieldName: string, value: unknown, options?: { from?: unknown }): voidWrite one component field.
transformOf(id: string): SceneTransform | nullThe object's transform, or null when there is no such object.
previewTransform(id: string, transform: SceneTransform): voidShow a transform in this window and on the drawn object; no server write.
setTransform(id: string, transform: SceneTransform, options?: { from?: SceneTransform }): voidWrite a transform.
recordTransform(id: string, from: SceneTransform, to: SceneTransform): voidOne undo step for a transform already written by setTransform calls without from (a drag that wrote on every release of one axis).

parameters: ParametersService#

The scene's parameters and the formula editors (D16 P5b).

  • Used by: Properties (studio.properties, optional), Studies (studio.studies: variables table, value text)
  • Stable when: Studies and Properties use it with no Parameters import, and both work while Parameters is disabled
MemberWhat it does
state(): ParametersState
subscribe(listener: () => void): Disposable
update(paramId: string, patch: ParameterPatch): Promise<ParameterOpResult>
bindField(field: FieldRef, expr: string, unit?: string | null): Promise<BindingOpResult>Bind one field component to expr, read in unit.
unbindField(field: FieldRef): Promise<BindingOpResult>
setFieldUnit(objectId: string, target: string, unit: string | null): Promise<BindingOpResult>Change a field's display unit (null: SI).
editFieldFormula(context: FieldEquationContext): voidOpen the Equation Editor on a field; OK binds it.
formatValue(value: number | null): stringDisplay text of a parameter or constant value.

runs: RunsService#

Runs of the Project: read live state, stop one, show their metrics.

  • Used by: Console (studio.console, a Run's output channel), Tasks (studio.tasks, Run rows), Studies (studio.studies: partials, progress, train curves)
  • Stable when: A frontend studio.runs system Plugin provides it instead of the kernel
MemberWhat it does
get(runId: string): RunInfo | undefined
subscribe(listener: () => void): Disposable
cancel(runId: string): Promise<void>
reportsItself(pluginId: string): DisposableRuns of pluginId show their progress where they were started, so they leave the panel layout alone: Console and Results do not open or close for them.
MetricsView: ComponentType<RunMetricsViewProps>The charts of the Run metrics panel, for a Plugin that shows them inline.

agent: AgentService#

This window's Agent runtime, for panels that start work in a Session.

  • Used by: Tasks (studio.tasks: Save as Workflow, Workflow runs whose agent steps start here)
  • Stable when: The Chat column itself is a Plugin and starts turns through it
MemberWhat it does
sendMessage(sessionId: string, text: string): Promise<void>Send text as the user's message in Session sessionId and start a turn.
turnRequest(): Record<string, unknown>The model, effort, permission mode and client Tools a turn started here would use.

results: ResultsService#

The Project's Results history (the results.* RPCs and the results topic).

  • Used by: Results (studio.results: both panels, the result / result_view openers, the Agent's Result View Tools, Save to Results)
  • Stable when: The history moves into studio.results once those readers take it from a Results-owned service or event
MemberWhat it does
history(): readonly ResultEntry[]Oldest first.
subscribe(listener: () => void): Disposable
deleteVersion(version: number, deleteFiles: boolean): Promise<void>deleteFiles also removes the saved files; otherwise the version returns on restart.
clear(deleteFiles: boolean): Promise<void>
showScene(sceneId: string | null | undefined): Promise<boolean>Make sceneId the current Scene, asking "Switch Scene" first.
openPlots(entry: { version: number; sceneId: string; title: string }): voidOpen the Plot panel tab on a Result's plots; the tab's body comes from plot.result.
snapshotWidget(widget: { widgetType: string; value: unknown; config: Record<string, unknown>; title?: string; }): Promise<Record<string, unknown> | null>A plot record (RFC-017, the plot of results.commit_plot) of a widget's current value: a plot value as it is, the latest frame of a stream widget, an image-preview as an RGB imshow.

arrays: ArraysService#

Windows of arrays in Project .npy / .npz files, read without loading the whole file.

  • Used by: Results (studio.results: Result views with array data)
  • Stable when: A stable resources route for array windows
MemberWhat it does
window(path: string, key: string, selection: ArrayAxisSelection[], maxCells?: number): Promise<ArrayWindow>key is the array's name in an .npz, '' for an .npy.

timeline: TimelineService#

The current Scene's timeline: clip content, the Scene clock and the edits on them.

  • Used by: Timeline (studio.timeline), Results (studio.results: views whose frames follow the Timeline)
  • Stable when: The viewport and the top-bar transport use it with no timeline store import
MemberWhat it does
state(): TimelineState
subscribe(listener: () => void): Disposable
currentTime(): numberThe playhead, seconds; Results views follow it.
activate(): voidShow the current Scene's timeline; fetches its clip when the Scene changed.
keyframeRange(): { start: number; end: number }Keyframe range of the clip, seconds; the clip length when there are no keyframes.
replayRange(datasetId: string | null): { start: number; end: number }Time range of a replay dataset, seconds; zeros when unknown.
preview(time: number): voidShow time locally (scrubbing): playhead and keyframed values; nothing is sent.
seek(time: number): voidpreview, then pause the Scene clock there.
playKeyframes(): voidPlay the keyframes from the playhead to the clip end.
play(options?: { time?: number; loop?: boolean }): void
pause(options?: { time?: number; loop?: boolean }): void
stop(): voidLeave replay or live: back to edit mode.
setMode(mode: 'edit' | 'live' | 'replay', options?: { source?: string }): void
setRecording(recording: boolean): voidEdit mode: record field edits as keyframes.
setLiveRecording(recording: boolean): voidLive mode: record the simulation into a dataset.
addKeyframe(objectId: string, componentName: string, fieldName: string, value: unknown, time: number): void
removeKeyframe(objectId: string, componentName: string, fieldName: string, time: number): void
moveKeyframes(moves: TimelineKeyframeMove[]): voidMoves the keys at once on screen, then sends one request.
bakeProducers(): Promise<{ id: string; label: string }[]>
bake(params: { producerId: string; range: [number, number]; fps: number; config?: Record<string, unknown>; sceneId?: string; label?: string; kind?: string; }): voidBake producerId over range (seconds) at fps; a pending dataset row shows until frames arrive.
cancelBake(runId: string): void
deleteDataset(datasetId: string): void

agentChat: AgentChatService#

The Agent chat, still kernel code (D16 P5c): the chat column and the window's part of the Agent loop (model relay, local.* tools, plan and question responders).

  • Used by: Agent (studio.agent)
  • Stable when: Never stable: removed when the chat code moves into plugins/agent and reads the agent.* slots itself
MemberWhat it does
Chat: ComponentType<AgentChatProps>
start(): DisposableAnswer the ProjectServer's model and local.* calls from this window, and hand the plugins' assistant contributions in the agent.* slots to the chat.

sceneView: SceneViewService#

The kernel's Scene view component, start() for the hand-over of the viewport.* slots to it, the view state tools read and set (transform mode and space, Measure, gizmo drags, the three.js object of a scene object) and the spawn point (cameraTarget(), Scene metres)

  • Used by: Viewport (studio.viewport), Modeling (studio.modeling), Library (studio.library: where an added item goes)
  • Stable when: Never stable as a whole: removed when the viewport code moves into plugins/viewport; studio.viewport then provides the tool part and the spawn point itself
MemberWhat it does
View: ComponentType<{ isDarkTheme: boolean }>
start(): DisposableHand the plugins' 3D contributions in the viewport.* slots to the view.
state(): SceneViewStateUnchanged inputs give the same object.
subscribe(listener: () => void): Disposable
setTransformMode(mode: ViewportTransformMode): void
setMeasuring(on: boolean): void
setGizmoDragging(kind: 'transform' | 'handle', dragging: boolean): voidA drag of a Plugin's gizmo is in progress: transform for a transform gizmo (object selection and camera keys stand down), handle for a drag handle (the object gizmo hides).
object3D(objectId: string): unknownThe three.js object drawn for a scene object, or null when it is not drawn.
cameraTarget(): [number, number, number]Work that must finish before the current Scene goes away (a scene switch, a Project close): resolve true once done, false to stop it.
registerLeaveGuard(guard: (reason: 'scene_switch' | 'project_close') => boolean | Promise<boolean>): Disposable

tools: ToolsService#

Run a ProjectServer Tool as a user action (D16 P5c, Modeling): the click is the approval, so a Tool that needs confirmation is approved once by the window's confirmation authority.

  • Used by: Modeling (studio.modeling)
  • Stable when: One confirmation path for UI-run and Agent-run Tools, and a third-party name rule that Plugins accept
MemberWhat it does
call<R = unknown>(name: string, args?: Record<string, unknown>): Promise<R>

meshAssets: MeshAssetsService#

Mesh geometry that scene ops and RPCs do not carry inline (D16 P5c, Modeling).

  • Used by: Modeling (studio.modeling)
  • Stable when: The meshbin format and the upload route are frozen
MemberWhat it does
load(ref: MeshAssetRef): Promise<{ positions: Float32Array; indices: Uint32Array }>Flat xyz positions and triangle indices, local frame; cached by asset version.
upload(positions: Float32Array, indices: Uint32Array): Promise<{ assetId: string }>Upload a mesh to the current Scene's assets; put the id into a scene op.

studies: StudiesService#

The Project's Studies (studies/<id>.toml).

  • Used by: none yet (Parameters uses the parameters.variedBy slot entry studio.studies registers)
  • Stable when: One Plugin outside Studies reads Studies through it with no Studies import
MemberWhat it does
list(): readonly StudyInfo[]
subscribe(listener: () => void): Disposable
reveal(studyId: string, options?: { variable?: string }): voidOpen the Studies page on a Study; variable scrolls its Variables table to that row.

thumbnails: ThumbnailsService#

Offscreen renders of Project files and the live material view (D16 P5b2).

  • Used by: Project (studio.project: cards and the detail preview), Materials (studio.materials: .wtmat cards and the editor preview)
  • Stable when: A third-party file type draws its card through it, and the renderer behind it can change without a Plugin change
MemberWhat it does
mesh(file: ProjectFileRef): Promise<string | null>A renderable mesh (obj, glb, gltf, ply, stl) rendered offscreen; null for other files or no connection.
image(file: ProjectFileRef): Promise<string | null>An image: the import cache's 256 px preview once ready, else the image itself.
material(key: string, values: MaterialValues): Promise<string | null>A lit sphere with these material values, on a transparent background.
MaterialView: ComponentType<MaterialViewProps>An interactive view of a material (orbit, sky lighting), sized by its parent.

assetImport: AssetImportService#

Imported meshes and textures (D16 P5b2): their import records, settings and placing them in the current Scene.

  • Used by: Project (studio.project: built-in types, import settings view, card status)
  • Stable when: A Plugin file type with its own importer shows its state and settings through it
MemberWhat it does
get(path: string): ImportedAssetInfo | nullThe record of the file at path, or null when it is not an imported asset.
subscribe(listener: () => void): DisposableCalls listener when any record changes.
watch(path: string, listener: () => void): DisposableCalls listener when the record of path changes (status, settings, removal).
describe(uid: string): Promise<ImportedAssetDescription>
ensureCache(target: { uid: string } | { path: string }): Promise<void>Build the cache now (by UID, or by path for a file with no sidecar yet).
setSettings(uid: string, settings: Record<string, unknown>): Promise<void>Merge settings into the sidecar; the server rebuilds the cache.
reimport(uid: string): Promise<void>
placeInScene(file: ProjectFileRef, as: 'mesh' | 'splat' | 'cad'): voidPlace the file in the current Scene in front of the camera.

sceneFiles: SceneFilesService#

The Project's Scenes as files (D16 P5b2): the Scene a .wtscene holds, open one, make it the main Scene, set its stage.

  • Used by: Project (studio.project: .wtscene type and menu)
  • Stable when: The scene list moves behind a Scenes service and this becomes its file view
MemberWhat it does
sceneOf(file: ProjectFileRef): SceneFileInfo | nullThe Scene in the .wtscene file at path; falls back to the file name before the catalog loads.
subscribe(listener: () => void): DisposableCalls listener when the catalog changes.
open(sceneId: string, title: string): voidOpen the Scene in the Scene view (asks first when the current Scene has unsaved edits).
setMain(sceneId: string): Promise<void>
setStage(sceneId: string, stage: 'world' | 'bench'): Promise<void>

components: ComponentsService#

Scene component definitions and component buttons (D16 P5b2): what can be added, which Plugin hides a component while it is off, an object's components in form shape, and button clicks with their operations.

  • Used by: Properties (studio.properties), Materials (studio.materials: the material editor)
  • Stable when: A Plugin outside Studio draws a component and clicks its buttons with no Studio import
MemberWhat it does
catalog(): readonly ComponentCatalogEntry[]Components the server offers; empty until the definitions arrive.
disabledBy(componentName: string): string | nullThe Plugin whose disabling hides componentName, or null.
subscribe(listener: () => void): DisposableCalls listener when the catalog, the disabled set or an operation changes.
of(object: SceneEditorObject): FormComponent[]The object's components in form shape, in object order.
fromWire(component: unknown): FormComponentConvert one backend component (Component.to_dict()) to form shape.
activeOperations(): readonly ComponentOperationInfo[]Operations not yet finished, newest first.
clickButton(target: { sceneId: string | null; objectId: string; componentName: string; button: FormButton }): voidClick a component button.
cancelOperation(operationId: string): voidAsk the operation's Run to stop; the operation shows "cancelling" until it ends.

componentForm: ComponentFormService#

The schema form (D16 P5b2): the rows, groups, widgets, units, show_if rules and formula bindings that components, schema panels and Settings pages draw.

  • Used by: Properties (studio.properties), Materials (studio.materials)
  • Stable when: Settings and one Plugin panel draw with it, and its props are frozen
MemberWhat it does
Section: ComponentType<FormSectionProps>One component: header (fold, menu), rows, groups, buttons, facet sections.
Field: ComponentType<FormFieldProps>The control of one field (no label).
Row: ComponentType<FormRowProps>Label plus control, at the table's label width.
InspectorBody: ComponentType<FormInspectorBodyProps>A Plugin's component inspector body (InspectorProps) on the form's edit callbacks, inside an error boundary: a throwing body draws nothing.
useLabelWidth(): FormLabelWidthReact hook: the label column of a table.
useExpanded(objectId: string, componentName: string): [boolean, (expanded: boolean) => void]React hook: whether a section is unfolded, per object and component, and its setter.
useFieldHighlight(objectId: string | null | undefined, target: string): { key: number; index?: number } | undefinedReact hook: the pending "show me this field" request for a field (witwin://scene/.../object/<id>#Component.field), consumed once drawn.
valuesOf(fields: readonly FormField[]): Record<string, unknown>Field name to value, the map isVisible and isEnabled read.
isVisible(field: FormField, values: Record<string, unknown>): booleanshow_if / hide_if and the field's hidden, against the component's values.
useFieldAnimated(objectId: string | null | undefined, componentName: string, fieldName: string): booleanReact hook: the field has animation keyframes, so it cannot take a formula (formula plan §5).
animatedReason: stringTooltip of a disabled fx button on an animated field.
isEnabled(field: FormField, values: Record<string, unknown>): booleanenable_if / disable_if, against the component's values.
rootGroup(group: string): stringRoot of a group path: Tabs for Tabs.Advanced.

The Gallery's homepage-api reads and its two ways into a Project (View: a trial; Create: a persistent Project).

  • Used by: Gallery (studio.gallery)
  • Stable when: A second Plugin lists or opens Gallery items through it
MemberWhat it does
list(filter: GalleryListFilter, cursor?: string | null): Promise<GalleryListPage>One page of published items, best first.
get(itemId: string): Promise<GalleryItem>
errorMessage(error: unknown): stringThe sentence to show for a failed Gallery request.
view(item: GalleryItem): Promise<boolean>View: open item as a trial Project (a Demo sends its prompt).
create(item: GalleryItem, name: string, options?: { sendDemo?: boolean }): Promise<boolean>Create: a persistent Project named name; sendDemo (default true) sends a Demo's prompt.
studioTooOld(item: GalleryItem): booleanTrue when item needs a newer Studio than this one.
missingPlugin(item: GalleryItem): Promise<GalleryMissingPlugin | null>Desktop: the Plugin a template item needs and lacks; null when none is missing.
NameForm: ComponentType<GalleryNameFormProps>