Skip to content

Feature/plugins - #63

Draft
ericgozzi wants to merge 10 commits into
mainfrom
feature/plugins
Draft

ericgozzi wants to merge 10 commits into
mainfrom
feature/plugins

Conversation

@ericgozzi

@ericgozzi ericgozzi commented Oct 13, 2026 •

Copy link
Copy Markdown
Collaborator

This pull request introduces a new plugin system to the COMPAS Three.js viewer, enabling toolbar and workspace extensibility through Python and frontend plugins. The changes add a robust API for managing plugins, toolbar buttons, and workspace-specific behaviors, and lay the groundwork for multi-workspace applications. Below are the most important changes grouped by theme:

Plugin System Core:

  • Introduced the Plugin base class and the @action decorator in compas_threejs.viewer, allowing Python plugins to define toolbar buttons and actions that communicate with their frontend counterparts. (src/compas_threejs/viewer/__init__.py, CHANGELOG.md) [1] [2]
  • Added the App.plugins property to manage the viewer's plugins in toolbar order, with validation for unique plugin names and type safety. Plugins are now served to the frontend via /plugins.json, and each plugin can be attached to the app before startup. (src/compas_threejs/viewer/app.py, CHANGELOG.md) [1] [2]

Workspace and Plugin Interaction:

  • Enabled workspace-aware plugins by passing the workspace context to @action handlers and allowing plugins to respond to specific tabs. Added Workspace.plugins for per-workspace plugin lists and App.on_new_workspace()/Plugin.new_workspace() hooks for dynamic workspace setup. (src/compas_threejs/viewer/app.py, CHANGELOG.md) [1] [2]
  • Implemented backend handling for plugin messages: the inbox now routes frontend plugin actions to the correct Python plugin instance and workspace, ensuring only enabled plugins in a workspace handle messages. (src/compas_threejs/viewer/inbox.py) [1] [2]

Documentation and Design Proposals:

  • Added PLUGIN_PROPOSALS.md, a comprehensive document outlining the current state, design proposals, and future directions for the plugin system, including multi-workspace support, panels, and ideas for new plugins.

These changes collectively provide a foundation for a flexible, extensible viewer architecture, supporting both general-purpose and domain-specific plugins, and enabling advanced multi-workspace workflows.

ericgozzi and others added 10 commits October 13, 2026 14:15
`app.plugins = [Standard(), Camera()]` adds toolbar rows to the viewer; by
default there are none. A `Plugin` subclass names its built frontend
(`static/plugin.js`), handles what its buttons send with `@action`, and
answers with `send()`; `send(..., key=...)` is also replayed to browsers
that connect later. The App serves each plugin at /plugins/<name>/ and lists
them at /plugins.json for the viewer page to load.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`plugin.hide("display")` / `show` / `disable("scale")` / `enable` take the ids
of button groups or single buttons. The state is sent as one persisted
`plugin_buttons` message, so browsers that connect later get it too, and
state set before the plugin is added to an App is sent when it is. A plugin
may list its `ids`, so a misspelled one raises.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`attached()` runs once a plugin is added to an App, sending state set
before that (hidden/disabled buttons; subclasses add their own, like
Standard's locks). `find_geometry(guid)` gives plugins the geometry the
frontend names by guid.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A plugin is one object for all workspaces (browser tabs), so it needs to
know which tab is talking to it:

- @action handlers and on() callbacks are called with (data, workspace),
  the Workspace of the tab the message came from.
- Inside a handler, Plugin.send answers that tab; elsewhere it goes to
  main, and send(..., workspace=) picks one. Plugin.send_all reaches
  every tab; its keyed messages are replayed to tabs opened later, in
  any workspace (AppServer.shared_state, workspace_id=None).
- Button state is sent with send_all, so tabs other than main get it.
- Workspace.plugins: the subset of App.plugins a workspace's tabs show,
  served at /plugins.json?workspace=<id>. Messages for a plugin the
  workspace doesn't show are dropped.
- Workspace.url.

BREAKING CHANGE: plugin handlers take (data, workspace) instead of (data).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Set up a workspace a browser tab asks for before it exists (e.g. one a
plugin opened with viewer.workspaceUrl), before the tab loads its plugins.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A picked object's metadata travels as a protobuf map, which loses the order
of its fields: they showed in the viewer in any order. The message now
carries that order as "__order__", for the viewer to show them in.

Sending no longer writes "dispatch" into the registered Metadata object
itself: the message is a copy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant