DocumentationDocumentation
User guide/Actions, state, and events

Keep the interface in sync with data

Declare readable state and fields, query or subscribe as needed, and notify listeners when data changes.

Declare fields and provide a query

State represents current facts. Define the query in a bpy-importing module and register once after binding. It returns scene name/frame without modifying the scene.

Query function in the module
python
def _scene_snapshot(query, context):
    scene = bpy.context.scene
    return {"name": scene.name, "frame": scene.frame_current}

Add this after bind succeeds in _get_plugin, not at module top level.

Register once after binding
python
_plugin.bridge().state(
    "plugin.my_addon.scene", _scene_snapshot, shape="item",
    fields={"name": {"type": "string"}, "frame": {"type": "integer"}},
)

Declare each returned field's type. Use list/item shapes with matching return structures, not merely a changed shape flag.

Read once or subscribe

One-time reader in an external page script
javascript
async function readScene(output) {
  try {
    const { snapshot } = await window.acquireNexusWebUI().state.get(
      "plugin.my_addon.scene", null
    );
    output.textContent = snapshot?.item
      ? `${snapshot.item.name} · 第 ${snapshot.item.frame} 帧` : "暂无数据";
  } catch (error) {
    output.textContent = `读取失败:${error.message || String(error)}`;
  }
}

output is an existing message element. Call readScene(output) from a button or initialization. Read snapshot.item for single state and snapshot.items for lists. get returns a wrapper containing snapshot, not raw application data.

Subscribe on mount and release on exit

Call mountScene(output) from your component, retain its synchronously returned cleanup function, and await it on unmount, using existing error handling for failures. It covers unmount before subscription creation completes.

Subscription and paired cleanup
javascript
function mountScene(output) {
  const webui = window.acquireNexusWebUI();
  let active = true;
  const pending = webui.state.subscribe("plugin.my_addon.scene", null, (snapshot, meta) => {
    if (!active) return;
    if (meta.error) {
      output.textContent = `订阅读取失败:${meta.error.message}`;
      return;
    }
    output.textContent = snapshot?.item
      ? `${snapshot.item.name} · 第 ${snapshot.item.frame} 帧` : "暂无数据";
  });
  pending.catch((error) => {
    if (active) output.textContent = `订阅读取失败:${error.message || String(error)}`;
  });
  return async function unmountScene() {
    active = false;
    const handle = await pending;
    await webui.state.unsubscribe(handle);
  };
}

Persistent panels use subscribe(key, query, callback); callbacks receive snapshots directly. Save the awaited handle and await webui.state.unsubscribe(handle) when no longer needed.

Creation is asynchronous. Release late handles after unmount to avoid accumulated listeners. Dispose the shared SDK only when the whole page ends. For query arguments/results, see State API reference.

Why data changed but the page did not

At the actual data-change notification; _plugin remains valid
python
_plugin.bridge().state.invalidate("plugin.my_addon.scene")

The call above requests a reread. For timeline changes, notify from your existing frame-change callback and remove it on unload. Registering a query does not observe Blender automatically. Page actions can also declare invalidates.

Query lists according to need

Custom plugin.* queries accept fields, filter, sort, and limit, not cursor/offset/page. Declare allowed fields and limits; Python handlers perform filtering/sorting while the framework validates, projects, and truncates.

Lists may be truncated; returned count is not total count. Generic cursor fields apply only to states explicitly supporting them. See State declarations and query scopes.

Return declared fields only and choose sensitive/large data deliberately. Do not expose every Blender internal. On resync_required, synchronize before deciding to retry rather than blindly repeating writes.

Check both directions

  1. Initial snapshots show real data.
  2. Page actions update associated state.
  3. Blender-native changes update through notifications too.
  4. Reopening does not duplicate callbacks, and invalid targets give clear results.
Based on actions/state guidance, public types, and scene examples.

Back to User guide · All topics