diff --git a/src/bonsaiviewer-autodesk/CLOUD_SYNC_PROTOCOL.md b/src/bonsaiviewer-autodesk/CLOUD_SYNC_PROTOCOL.md deleted file mode 100644 index 053ec834ff..0000000000 --- a/src/bonsaiviewer-autodesk/CLOUD_SYNC_PROTOCOL.md +++ /dev/null @@ -1,498 +0,0 @@ -# Bonsai Viewer cloud connectors - -Bonsai Viewer will have the capability to load and save projects and models from a -cloud platform. Later on, there will be other resources stored on cloud -platforms too, such as issues, clash results, and so on, but this behaviour is -not currently designed. - -Due to the variety of cloud platforms, the Bonsai Viewer itself will depend on -a "connector" to integrate with each platform. The connector is a separate -application which will communicate to and from the Bonsai Viewer. - -The following types of resources may be managed with a connector: - - - Projects (.ifcfed) - - Models (.ifc, .rdb, .ifcview, .rdbview) - - Issues (.bcf, not yet supported nor defined) - - Specifications (.ids, not yet supported nor defined) - -## Communication protocol - -The Bonsai Viewer launches one connector process per session, on first use, and -keeps it alive for the duration of the session. This allows the connector to -maintain authentication tokens, browse state, in-flight downloads, and caches -in memory across calls without re-authenticating on every request. - -Communication is over stdio using newline-delimited JSON-RPC 2.0: - - - Requests and responses are single-line JSON objects on the connector's - stdin/stdout. Each message is terminated by a single `\n`. - - The connector must not emit literal newlines inside a JSON message. - - The connector may write arbitrary diagnostic output to stderr; the Bonsai Viewer - will not parse it. - -The Bonsai Viewer shuts a connector down by closing its stdin. The connector should -exit cleanly. If it does not exit within a few seconds, the Bonsai Viewer will -terminate it. - -## Connector scope - -The Bonsai Viewer has minimal knowledge about connectors. Bonsai Viewer only knows how -to work with local files. If it detects that a project or model is not local, -it will invoke a connector. The connector's job is to resolve the Bonsai Viewer's -request back into a local file and cloud metadata. The connector must not -modify the file in any way. - -A connector will handle anything necessary for the cloud platform (or arbitrary -data source). This includes authentication, browsing files, filters and -searches, selecting or pinning revisions, progress bars, cache, -platform-specific requirements, etc. The connector may or may not display a UI. -This makes connectors very flexible. - -A single project may have different resources coming from different connectors. -For example, some models might be on one platform, and some projects hosted on -another platform. The permissions regarding model access can be quite granular -and therefore managed by the platform, and not Bonsai Viewer. - -When a connector returns a local file, that file is required to be the sole -child in its directory. This is because there may be adjacent temporary files, -viewer-generated sidecar files, database locks or helpers (e.g. SQLite WAL), or -where filenames are significant (and cannot be renamed to prevent collisions), -or are actually directories containing other files. - -The connector is expected to persist this cache until explicitly cleared by a -user, because it will be used directly as a local path by the viewer. If a -connector invalidates a cache, it can simply delete the entire directory. If a -connector resolves to a new version of the file, it can create a fresh -directory (thus all sidecar artefacts will be regenerated if needed). It is not -prescribed how a connector manages cache. - -## Resource: Projects - -A project is defined using an `.ifcfed` file. The file stores settings (such as -units, home view coordinates, saved searches, etc) and models. - -For example: - -```json -# project.ifcfed -{ - "created": "2026-04-29T21:22:36Z", - "modified": "2026-04-29T21:22:36Z", - "home_view": null, - "models": ... # see Resources: Models, - ... -} -``` - -A project may have a manifest file (with `.manifest` as a suffix), which may -store metadata that a ifcfed was retrieved from a cloud source. - -```json -# project.ifcfed.manifest -{ - "connector": "mycompany", - # Arbitrary connector-specific data - "version": "2", - "url": "http://example.com/project.ifcfed", -} -``` - -## Resource: Models - -The list of models is defined in the .ifcfed. Each model may either point to a -local file (via the special "local" connector), or to a cloud file. - -Cloud connections may store arbitrary source data as keys. For example, they -might store a revision policy that determines whether the model is pinned to a -particular revision or must always be the latest. This is completely up to the -connector. - -Here is an example of how models might be stored in an .ifcfed: - -```json -[ - { - "display_name": "foo.ifc", - "id": "0503642e-e2f6-4700-87fd-16479542e801", - "source": { - "connector": "local", - "path": "path/to/foo.ifc" # Only for local - }, - }, - { - "display_name": "bar.ifc", - "id": "5fc69e6a-1ff0-4d8a-82c6-2215df53d2ed", - "source": { - "connector": "autodesk", - # Below is arbitrary data depending on the connector - "version": "1", - "hub_id": "b.hub123", - "project_id": "b.project456", - "item_id": "urn:adsk.wipprod:dm.lineage:abc", - "version_id": "urn:adsk.wipprod:fs.file:vf.xyz?version=3" - }, - }, -] -``` - -Note that cloud metadata (filename, cloud ID, revision, date modified, etc) is -not specified nor stored in the .ifcfed. This is to be returned by the -connector when requested. - -The Bonsai Viewer will display all returned cloud metadata as simple text strings. -However some keys are treated specially and shown in more places in the -Bonsai Viewer UI for convenience: - - - author - - revision - - date - -## Open from cloud workflow - -This opens a .ifcfed from a cloud platform and constitutes a fresh session. -Note that downloading the latest versions of all models immediately upon open -is not required. At a minimum, only the .ifcfed needs to be opened. It is -perfectly acceptable to give the user choice on whether to download all or some -models, or use cache (even if outdated). The user can always reopen the project -later. - - 1. The user presses a button in the Bonsai Viewer UI that says "Open from Cloud" - 2. The user chooses a connector. - 3. The `pull_ifcfed_interactive` method is sent to the connector. - ```json - { "jsonrpc": "2.0", "id": "0", "method": "pull_ifcfed_interactive" } - ``` - 4. The connector: - - (Does optional workflow) authenticates, browses projects, filters files, etc - - The user selects an .ifcfed file from the connector's UI - - Downloads (or retrieves from cache) the cloud .ifcfed into a connector managed directory - - The connector returns a path to the .ifcfed. The connector must also create an adjacent .ifcfed.manifest file: - ```json - { "jsonrpc": "2.0", "id": "0", "result": { "path": "/path/to/project/file.ifcfed" } } - ``` - 5. The Bonsai Viewer loads the `path`. This constitutes a fresh session. - 6. The Bonsai Viewer calls `pull_models`: - ```json - { "jsonrpc": "2.0", "id": "1", "method": "pull_models", "params": [ - { "display_name": ..., "id": ..., "source": ..., }, - { "display_name": ..., "id": ..., "source": ..., }, - ... - ] } - ``` - 7. The connector handles downloading files. It may always check and download the latest version of the file, or be designed to pin to a particular revision, or give the user the option of not downloading a file, etc. or retrieves from its own cache, and returns a path. - ```json - { "jsonrpc": "2.0", "id": "1", "result": [ - { "path": "/path/to/foo.ifc" }, - { - "path": "/path/to/model.ifc", # Used to load the model in Bonsai Viewer - "metadata": { "revision": "B", "date": "2nd Oct 2025" ... }, # Optional, used to display stats - }, - null, # If skipped, error, etc - { "path": "/path/to/bar.ifc" }, - ... - ] } - ``` - 8. The Bonsai Viewer may call another connector with more models to be downloaded. - 9. The Bonsai Viewer will load the downloaded models as regular files. Typically this will also result in the Bonsai Viewer reading / writing a cache (e.g. .ifcview) alongside this file, but it is not expected that the connector will know or care about this. - -## Sync cloud to local - -This refreshes cloud-sourced resources in the currently-open project to their -latest cloud revisions, without prompting the user. It is available whenever -the project has any cloud resources: a `.ifcfed.manifest` adjacent to the -.ifcfed, or one or more models whose `source.connector` is not `local`. The -.ifcfed-refresh phase and the model-refresh phase are independent — only the -first requires a manifest. - - 1. The user presses a button in the Bonsai Viewer UI that says "Sync Cloud to Local" - 2. Bonsai Viewer reads the .ifcfed.manifest and invokes the relevant connector with the manifest data with the `pull_ifcfed` method: - ```json - { "jsonrpc": "2.0", "id": "0", "method": "pull_ifcfed", "params": { - "connector": "mycompany", "version": "2", "url": ... - } } - ``` - 3. The connector does what it needs: - - Authenticates (optional) - - (Typically without user interaction) finds the .ifcfed on the cloud platform using the ifcfed manifest - - Downloads (or retrieves from cache) the cloud .ifcfed into a connector managed directory - - The connector returns a path to the .ifcfed. The connector must also create an adjacent .ifcfed.manifest file: - ```json - { "jsonrpc": "2.0", "id": "0", "result": { "path": "/path/to/project/file.ifcfed" } } - ``` - 4. Continue with step 5 of the "Open from cloud" workflow. - -If no `.ifcfed.manifest` is present, steps 2–4 are skipped. The .ifcfed on -disk is used as-is, and the Bonsai Viewer continues from step 6 of the -"Open from cloud" workflow (calling `pull_models` for any cloud-sourced models -referenced in the .ifcfed). - -Additionally, if the .ifcfed returned in step 3 is unchanged from the one -already loaded (e.g. the connector served a cached copy because the cloud -revision matched), the Bonsai Viewer skips step 5 as well and continues from -step 6, preserving the current session rather than forcing an unnecessary -fresh one. How "unchanged" is determined (byte equality, hash, mtime, etc.) -is left to the Bonsai Viewer. - -## Save as to cloud - -This pushes a .ifcfed to a fresh location on a cloud platform, chosen by the -user. It is the "Save As" equivalent and is the only way to first establish a -cloud location for a project that does not yet have a `.ifcfed.manifest`. - - 1. The user presses a button in the Bonsai Viewer UI that says "Save As to Cloud" - 2. The user chooses a connector. - 3. The `push_ifcfed_interactive` method is called with the path to the .ifcfed. The connector should treat this as a temporary .ifcfed file, as the real project may or may not be actually saved on disk. - ```json - { "jsonrpc": "2.0", "id": "0", "method": "push_ifcfed_interactive", "params": { "path": "/tmp/path/to/project.ifcfed" } } - ``` - 4. The connector does what it needs: - - (Does optional workflow) authenticates, browses projects, filters files, etc - - Selects existing or writes a new name for an .ifcfed file - - Uploads .ifcfed file to the cloud platform - - The connector returns a path to the .ifcfed. The connector must also create an adjacent .ifcfed.manifest file: - ```json - { "jsonrpc": "2.0", "id": "0", "result": { "path": "/path/to/project/file.ifcfed" } } - ``` - 5. The Bonsai Viewer "repoints" to the returned path. It is not necessary to do a full reload as no "changes" are made. - -## Save to cloud - -This pushes a .ifcfed back to the cloud location it originally came from, -without prompting the user. It is the "Save" equivalent and is only available -when there is a `.ifcfed.manifest` adjacent to the project. - - 1. The user presses a button in the Bonsai Viewer UI that says "Save to Cloud" - 2. Bonsai Viewer reads the .ifcfed.manifest and invokes the relevant connector with the `push_ifcfed` method, passing both the local path and the manifest data: - ```json - { "jsonrpc": "2.0", "id": "0", "method": "push_ifcfed", "params": { - "path": "/tmp/path/to/project.ifcfed", - "manifest": { "connector": "mycompany", "version": "2", "url": "..." } - } } - ``` - 3. The connector does what it needs: - - Authenticates (optional) - - (Typically without user interaction) locates the existing .ifcfed on the cloud platform using the manifest data - - Uploads the .ifcfed, overwriting or creating a new revision as the platform dictates - - The connector returns a path to the .ifcfed and rewrites the adjacent .ifcfed.manifest if any of its fields have changed (e.g. a new version number): - ```json - { "jsonrpc": "2.0", "id": "0", "result": { "path": "/path/to/project/file.ifcfed" } } - ``` - 4. The Bonsai Viewer "repoints" to the returned path. It is not necessary to do a full reload as no "changes" are made. - -Conflict resolution for non-interactive push methods (the cloud copy moved on -since the manifest or source was captured, the user lacks write permission, -revision-pinning policies, etc.) is entirely the connector's responsibility. -The connector may silently overwrite, prompt the user, refuse with a JSON-RPC -error, or anything in between. The Bonsai Viewer expresses no opinion. This rule -also applies to `push_model` below. - -## Add model from cloud - - 1. The user presses a button in the Bonsai Viewer UI that says "Add model from cloud" - 2. The user chooses a connector. - 3. The `pull_models_interactive` method is sent to the connector. - ```json - { "jsonrpc": "2.0", "id": "0", "method": "pull_models_interactive" } - ``` - 4. The connector does what it needs: - - (Does optional workflow) authenticates, browses projects, filters files, etc - - Selects a model (.ifc, .ifcview, .rdbview, .rdb, etc) - - Downloads (or retrieves from cache) the model into a connector managed directory - - The connector returns a successful result: - ```json - { "jsonrpc": "2.0", "id": "0", "result": [ - { - "display_name": "bar.ifc", # Stored in .ifcfed - "source": { "connector": "autodesk", ... }, # Stored in .ifcfed - "path": "/path/to/model.ifc", # Used to load the model in Bonsai Viewer - "metadata": { "revision": "B", "date": "2nd Oct 2025" ... }, # Optional, used to display stats - }, - { ... }, - ... - ] } - ``` - 5. The Bonsai Viewer updates the .ifcfed models section with new models using the - "source" and "display\_name" from the provided data. The models are - immediately loaded from the "path", and the Bonsai Viewer stores the "metadata" - for display. The path and metadata is never stored in the .ifcfed. - -## Save model as to cloud - -This pushes a model to a fresh location on a cloud platform, chosen by the -user. It is the "Save As" equivalent and is the only way to first establish a -cloud `source` for a model whose current source is `local`. - - 1. The user presses a button in the Bonsai Viewer UI that says "Save Model As to Cloud" - 2. The user chooses a connector. - 3. The `push_model_interactive` method is sent to the connector with a path to the model to be uploaded (typically a file, but RocksDB databases can be a folder). - ```json - { "jsonrpc": "2.0", "id": "0", "method": "push_model_interactive", "params": { "path": "/tmp/path/to/model.ifc" } } - ``` - 4. The connector does what it needs: - - (Does optional workflow) authenticates, browses projects, filters files, etc - - Selects existing or types a new name for the model - - Uploads the model (only the file in params, though the connector is free to do optional additional work) to the cloud platform - - The connector returns a successful result: - ```json - { "jsonrpc": "2.0", "id": "0", "result": { - "display_name": "bar.ifc", # Stored in .ifcfed - "path": "/path/to/model.ifc", - "source": { "connector": "autodesk", ... }, # Stored in .ifcfed - "metadata": { "revision": "B", "date": "2nd Oct 2025" ... }, # Optional, used to display stats - } } - ``` - 5. The Bonsai Viewer updates the .ifcfed models section with the new model metadata from the provided data. - -## Save model to cloud - -This pushes a model back to the cloud location it originally came from, -without prompting the user. It is the "Save" equivalent and is only available -for models whose .ifcfed `source` already points at a cloud connector (i.e. -anything other than `local`). - - 1. The user presses a button in the Bonsai Viewer UI that says "Save Model to Cloud" - 2. Bonsai Viewer invokes the connector named in the model's `source` with the `push_model` method, passing both the local path and the existing `source` object verbatim: - ```json - { "jsonrpc": "2.0", "id": "0", "method": "push_model", "params": { - "path": "/tmp/path/to/model.ifc", - "source": { "connector": "autodesk", "hub_id": "b.hub123", "item_id": "...", ... } - } } - ``` - 3. The connector does what it needs: - - Authenticates (optional) - - (Typically without user interaction) locates the existing model on the cloud platform using the `source` data - - Uploads the model, overwriting or creating a new revision as the platform dictates - - The connector returns a successful result. The returned `source` reflects the just-uploaded revision (e.g. a new `version_id`) and replaces the existing one in the .ifcfed; `display_name` is omitted (the existing one is retained): - ```json - { "jsonrpc": "2.0", "id": "0", "result": { - "source": { "connector": "autodesk", ... }, # Replaces existing source in .ifcfed - "metadata": { "revision": "C", "date": "19th May 2026" ... }, # Optional, used to display stats - } } - ``` - 4. The Bonsai Viewer replaces the model's `source` in the .ifcfed and refreshes the stored metadata for display. - -## Connector settings (optional) - -A connector MAY implement an `open_settings` method that the Bonsai Viewer invokes -when the user clicks the connector's settings entry (e.g. a gear icon next to -the connector name). The connector is responsible for the entire settings UI: -credentials, sign-out, default folders, anything connector-specific. - - 1. The user clicks the connector's settings entry in the Bonsai Viewer UI. - 2. The Bonsai Viewer sends `open_settings`: - ```json - { "jsonrpc": "2.0", "id": "0", "method": "open_settings" } - ``` - 3. The connector shows its own settings dialog. When the user closes it, the - connector returns: - ```json - { "jsonrpc": "2.0", "id": "0", "result": {} } - ``` - -If the connector returns a JSON-RPC `Method not found` error (code `-32601`), -the Bonsai Viewer should treat that connector as having no settings and hide its -settings entry. There is no other discovery mechanism — the viewer probes by -calling the method when needed. - -The connector is free to use this method for things like: - - - Signing in / signing out - - Setting API keys, client ids, or other credentials - - Choosing default upload folders or revision policies - - Clearing the connector's cache - -The result object is currently always empty (`{}`); future revisions may add -optional fields (for example a fresh display label for the connector). - -### Error Response - -The connector owns all user-facing error handling: dialogs, retry prompts, -re-auth flows, logs. The Bonsai Viewer does not interpret or display connector -errors directly. - -The protocol expresses only two outcomes: - - - **Per-item soft failure** (one model in a batch failed, others succeeded): - the connector returns `null` in that slot of the result array. The Bonsai Viewer - skips it and continues. - - **Whole-call hard failure** (the connector cannot service the request at - all): the connector returns a JSON-RPC error object. The Bonsai Viewer aborts - the operation. The `error.message` may be logged by the Bonsai Viewer for - diagnostics, but is not shown to the user — the connector is expected to - have already surfaced the problem in its own UI. - -Diagnostic detail (stack traces, codes, retry context) should be written to -stderr, which the Bonsai Viewer captures for logs. - -## Permissions - -Permissions are completely managed by the connector. For example: - -- one Autodesk model resolves successfully -- one Aconex model fails with access denied -- one Dropbox model resolves successfully - -The viewer will tolerate partial failure and report skipped resources that the connector cannot resolve. A federation does not need to become all-or-nothing just because some remote models are permission-restricted. - -## Connector discovery - -A connector is shipped as a folder containing a `connector.json` manifest and -an executable entry point. The Bonsai Viewer discovers connectors by scanning a -small, fixed set of locations for these folders. - -### Connector bundle layout - -``` -/ - autodesk/ # folder name is arbitrary; id comes from connector.json - connector.json # required, at the folder root - bonsaiviewer-autodesk # the executable (or a wrapper script) - ... # anything else the connector ships -``` - -### `connector.json` - -```json -{ - "id": "autodesk", - "name": "Autodesk Forma", - "version": "0.1.0", - "exec": "./bonsaiviewer-autodesk" -} -``` - - - `id` — stable identifier used in `.ifcfed` `source.connector` fields and in - `.ifcfed.manifest`. Must be unique across all discovered connectors. - - `name` — human-readable label shown in the Bonsai Viewer UI. - - `version` — connector version string; informational only. - - `exec` — path to the connector executable. Relative paths are resolved - against the connector folder; absolute paths are used as-is. Bundled - connectors should use a relative path so the bundle is self-contained. - - On Windows, the Bonsai Viewer will also try `.exe` if `` does not - exist as written. - -### Search locations - -The Bonsai Viewer scans the **user connectors directory**. The platform's per-user -application data location: - - - Linux: `~/.local/share/IfcOpenShell/BonsaiViewer/connectors/` - - macOS: `~/Library/Application Support/IfcOpenShell/BonsaiViewer/connectors/` - - Windows: `%APPDATA%\IfcOpenShell\BonsaiViewer\connectors\` - -The Bonsai Viewer looks at every immediate subdirectory and treats it as a -connector iff it contains a `connector.json`. Connectors are launched on -demand when the user invokes a cloud workflow, not at startup. - -### Conflicts and errors - - - If two folders declare the same `id`, the one found earlier in directory - order wins; the loser is skipped and a warning is written to the Bonsai Viewer's - log. - - A `connector.json` that is missing, unreadable, malformed, or missing - required fields causes that folder to be skipped (with a log entry); other - connectors are unaffected. - - A connector whose `exec` cannot be resolved or launched is reported to the - user only when the user actually tries to invoke it. diff --git a/src/bonsaiviewer-autodesk/README.md b/src/bonsaiviewer-autodesk/README.md deleted file mode 100644 index bdd6ed51f1..0000000000 --- a/src/bonsaiviewer-autodesk/README.md +++ /dev/null @@ -1,100 +0,0 @@ -# `bonsaiviewer-autodesk` - -Autodesk Forma (APS / Docs) connector for Bonsai Viewer. - -Implements the JSON-RPC connector contract defined in -[`CLOUD_SYNC_PROTOCOL.md`](CLOUD_SYNC_PROTOCOL.md). The connector is a separate -process the viewer launches and speaks to over stdio. - -UI is built on **CustomTkinter** (Tcl/Tk under the hood), keeping the -packaged connector around 50 MB unpacked / 21 MB zipped on Linux. The Python -running this code must include `tkinter` (most distribution Python builds do; -on Gentoo make sure `USE="tk"` is set for `dev-lang/python`). - -## Install - -```bash -cd src/bonsaiviewer-autodesk -python -m venv venv -source venv/bin/activate -pip install -e . -``` - -## Run - -```bash -bonsaiviewer-autodesk -``` - -The connector launches without any configuration; on first run, invoke -`open_settings` (or, equivalently, set the `APS_CLIENT_ID` env var) to -configure the Autodesk client id and OAuth callback port. - -Then send newline-delimited JSON-RPC 2.0 requests on `stdin`. Examples: - -```json -{"jsonrpc":"2.0","id":"0","method":"open_settings"} -{"jsonrpc":"2.0","id":"1","method":"pull_ifcfed_interactive"} -{"jsonrpc":"2.0","id":"2","method":"pull_models","params":[{"display_name":"foo.ifc","id":"abc","source":{"connector":"autodesk","hub_id":"b.hub","project_id":"b.proj","item_id":"urn:adsk...","version_id":"latest"}}]} -{"jsonrpc":"2.0","id":"3","method":"push_ifcfed_interactive","params":{"path":"/tmp/project.ifcfed"}} -{"jsonrpc":"2.0","id":"4","method":"push_ifcfed","params":{"path":"/tmp/project.ifcfed","manifest":{"connector":"autodesk","hub_id":"b.hub","project_id":"b.proj","item_id":"urn:adsk..."}}} -``` - -The viewer is expected to launch this binary once per session and keep it alive -until shutdown; closing the connector's `stdin` triggers a clean exit. - -### Configuration - -The connector reads the Autodesk client id from two places, in order: - -1. The `APS_CLIENT_ID` environment variable (takes precedence — useful for dev - overrides). -2. `/settings.json` (persisted via the settings dialog). - -The OAuth callback host is always `localhost`. The callback port defaults to -`8080` and can be changed in the settings dialog. - -The config directory is platform-specific: - -- Linux: `~/.config/bonsaiviewer-autodesk/` -- macOS: `~/Library/Application Support/bonsaiviewer-autodesk/` -- Windows: `%APPDATA%\bonsaiviewer-autodesk\` - -OAuth tokens are stored in the OS keychain (Secret Service on Linux, Keychain -on macOS, Credential Manager on Windows), keyed by the client id, so changing -the client id starts a fresh session. - -## Cache - -The connector owns its own cache. Resolved files live under (Linux): - -``` -~/.cache/bonsaiviewer-autodesk/ - ifcfeds//.ifcfed[.manifest] - models// -``` - -Each file is the sole child in its directory so the viewer can write sidecar -files (e.g. `.ifcview`) next to it without colliding. A new resolved version of -a model lands in a fresh `models//` directory; the old directory may be -removed manually to clear space. - -## Status - -What is implemented: - -- Strict JSON-RPC 2.0 host over stdio -- APS PKCE sign-in with keyring-backed token store -- Hub / project / folder browsing (Qt UI) -- `pull_ifcfed_interactive`, `pull_ifcfed`, `pull_models`, `pull_models_interactive` -- `push_ifcfed_interactive`, `push_ifcfed`, `push_model_interactive`, `push_model` -- `open_settings` — edit the client id and callback port, sign out -- Connector-managed cache with sole-child invariant -- Adjacent `.ifcfed.manifest` written/read alongside `.ifcfed` files - -What is intentionally not implemented: - -- JSON-RPC notifications for progress streaming (the connector shows its own - progress dialog instead, per spec) -- Cancellation of in-flight downloads -- Subdirectory upload layouts inside push destinations (one flat file at a time) diff --git a/src/bonsaiviewer-autodesk/packaging/README.md b/src/bonsaiviewer-autodesk/packaging/README.md deleted file mode 100644 index d6849b9a58..0000000000 --- a/src/bonsaiviewer-autodesk/packaging/README.md +++ /dev/null @@ -1,102 +0,0 @@ -# Packaging the Autodesk connector - -The connector is shipped as a self-contained folder ready to drop into the -Bonsai Viewer connectors directory. PyInstaller bundles the Python interpreter, -Qt, and all dependencies so end users do not need Python installed. - -PyInstaller does **not** cross-compile. Each OS must build on itself — -typically via a CI matrix. - -## Output - -``` -dist/ - autodesk/ # the connector folder, ready to install - connector.json - bonsaiviewer-autodesk[.exe] - _internal/... # PyInstaller dependencies (Qt, Python, …) - autodesk--.zip # the distribution archive -``` - -The folder is what Bonsai Viewer expects under -`~/.local/share/IfcOpenShell/BonsaiViewer/connectors/` (or the OS equivalent). - -## Build steps (any OS) - -```bash -cd src/bonsaiviewer-autodesk -python -m venv venv -venv/bin/activate # or venv\Scripts\activate on Windows -pip install -e ".[build]" -python packaging/build.py -``` - -The build: - -1. cleans `dist/` and `build/` -2. runs PyInstaller against `packaging/bonsaiviewer-autodesk.spec` -3. renames the produced folder to `autodesk/` and copies `connector.json` into it -4. zips the folder as `autodesk--.zip` - -The UI is Tcl/Tk via CustomTkinter, which keeps the bundle small. Expect -~50 MB unpacked / ~21 MB zipped per OS. The Python used to run the build -must include `tkinter` — most distribution and python-build-standalone -builds do; on Gentoo make sure `USE="tk"` is set for `dev-lang/python`. - -## Per-OS notes - -### Linux - -- Build on the **oldest glibc** you intend to support. Binaries built on a - newer glibc will not run on older distributions. Ubuntu 22.04 LTS - (glibc 2.35) is a reasonable lowest common denominator in 2026. -- The keyring backend used at runtime is `SecretService` (gnome-keyring or - KWallet); end users need a Secret Service provider running. -- Output: `autodesk-linux-x86_64.zip` (and/or `arm64`). - -### macOS - -- Each architecture builds separately. To support both Apple Silicon and - Intel, build on each and ship two zips, or post-process with `lipo` to - produce universal binaries. -- The keyring backend is the system Keychain. -- For distribution outside the developer's machine you will need to - **codesign** the executable and Tcl/Tk dylibs, and notarize the bundle. - Unsigned binaries trigger Gatekeeper warnings. Codesigning is left to the - caller; the spec's `codesign_identity` field can be wired up. -- Output: `autodesk-macos-arm64.zip` and/or `autodesk-macos-x86_64.zip`. - -### Windows - -- Build with the Microsoft Visual C++ runtime available (usually present in - any modern Python distribution). -- The keyring backend is Credential Manager. -- The `.exe` is built with `console=True` because the connector speaks - JSON-RPC over stdio. The Bonsai Viewer must launch the connector with - `CREATE_NO_WINDOW` (Qt: `QProcess::setCreateProcessArgumentsModifier`) so - end users never see a console window flicker. -- For distribution: sign the `.exe` with an Authenticode certificate to - avoid SmartScreen warnings. Signing is left to the caller. -- Output: `autodesk-windows-x86_64.zip`. - -## Installing a built connector - -```bash -# Linux -unzip dist/autodesk-linux-x86_64.zip -d ~/.local/share/IfcOpenShell/BonsaiViewer/connectors/ - -# macOS -unzip dist/autodesk-macos-arm64.zip -d "~/Library/Application Support/IfcOpenShell/BonsaiViewer/connectors/" - -# Windows (PowerShell) -Expand-Archive dist\autodesk-windows-x86_64.zip -DestinationPath "$env:APPDATA\IfcOpenShell\BonsaiViewer\connectors\" -``` - -Bonsai Viewer picks up the connector on next launch. - -## Out of scope here - -- Signing / notarization (caller's responsibility per OS) -- CI matrix (project-level concern) -- Auto-update (the Bonsai Viewer or the host installer handles this) -- Universal macOS binaries via `lipo` (post-process step, not part of `build.py`) diff --git a/src/bonsaiviewer-autodesk/pyproject.toml b/src/bonsaiviewer-autodesk/pyproject.toml index dc0eac20c1..988580b8f4 100644 --- a/src/bonsaiviewer-autodesk/pyproject.toml +++ b/src/bonsaiviewer-autodesk/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta" name = "bonsaiviewer-autodesk" version = "0.1.0" description = "Autodesk cloud connector for Bonsai Viewer" -readme = "README.md" +readme = { text = "Autodesk cloud connector for Bonsai Viewer. See src/bonsaiviewer/docs/connectors/autodesk.rst in the IfcOpenShell repository.", content-type = "text/x-rst" } requires-python = ">=3.11" dependencies = [ "customtkinter>=5.2", diff --git a/src/bonsaiviewer/docs/Makefile b/src/bonsaiviewer/docs/Makefile new file mode 100644 index 0000000000..ef277ae28b --- /dev/null +++ b/src/bonsaiviewer/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation + +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + @echo "" + @echo "You can also do 'make server' to launch html server for previously built html docs." + +.PHONY: help Makefile + +server: + python -m http.server 8080 --directory $(BUILDDIR)/html +.PHONY: server Makefile + +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/src/bonsaiviewer/docs/_static/custom.css b/src/bonsaiviewer/docs/_static/custom.css new file mode 100644 index 0000000000..880c31096a --- /dev/null +++ b/src/bonsaiviewer/docs/_static/custom.css @@ -0,0 +1,126 @@ +@import url("https://fonts.googleapis.com/css2?family=Nunito:wght@200;300;400;500;600;700;800&display=swap"); +@import url('https://fonts.googleapis.com/css2?family=Inconsolata:wght@200;300;400;500;600;700&display=swap'); + +h1, h2, h3, h4 { + font-weight: normal; + color: #7cbf33; + background: -webkit-linear-gradient(0deg, #7cbf33, #049344); + background-clip: border-box; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; +} + +a { + text-decoration: none; +} + +.toc-tree .reference:hover { + color: #d98014; +} + +.sidebar-brand-text { + font-size: 1rem; + text-align: center; +} + +.blockbutton { + max-width: 500px; + margin-left: auto; + margin-right: auto; + text-align: center; +} + +.blockbutton a { + display: block; + padding-top: 15px; + padding-bottom: 15px; + border-radius: 5px; + background: #40b74c; + width: 100%; + height: 100%; + color: #fff; + text-decoration: none; +} + +.blockbutton a:hover { + background: #70ba35; +} + +.blockbutton a.download code { + background: none !important; + border: none !important; + color: inherit !important; + font-family: inherit !important; + font-size: inherit !important; + font-weight: bold; + padding: 0 !important; +} + +.highlight .hll { + background-color: #ffc2; +} + +section img { + display: block; + margin-left: auto; + margin-right: auto; + box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px; + border-radius: 5px; +} + +.location-scene img { + display: inline; + border-radius: 0px; + box-shadow: none; + vertical-align: middle; + margin-top: -3px; +} + +.location-scene p { + margin: 0.75rem; +} + +.location-scene { + border: 1px solid var(--color-admonition-title); + background-color: var(--color-admonition-title-background); + border-radius: 5px; + font-style: italic; + color: var(--color-admonition-text); +} + +img.transparent { + width: auto; + height: auto; + box-shadow: none; +} + +img.icon { + width: auto; + height: auto; + border-radius: 0px; + box-shadow: none; + vertical-align: middle; +} + +.content figure figcaption>p { + font-style: italic; + font-size: small; + color: #808080; +} + +span.menuselection { + border: 1px solid var(--color-admonition-title); + background-color: var(--color-admonition-title-background); + color: var(--color-admonition-text); + border-radius: 5px; + padding-left: 3px; + padding-right: 3px; +} + +.toc-cards { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); + grid-gap: 20px; + list-style-type: none; + margin-bottom: 24px; +} diff --git a/src/bonsaiviewer/docs/conf.py b/src/bonsaiviewer/docs/conf.py new file mode 100644 index 0000000000..7585dd2d87 --- /dev/null +++ b/src/bonsaiviewer/docs/conf.py @@ -0,0 +1,89 @@ +# Configuration file for the Sphinx documentation builder. + +import os +from datetime import datetime + + +project = "Bonsai Viewer" +copyright = f"2020-{datetime.now().year} IfcOpenShell Contributors" +author = "IfcOpenShell Contributors" + +cwd = os.path.dirname(os.path.realpath(__file__)) +with open(os.path.join(cwd, "..", "..", "..", "VERSION"), "r") as f: + release = f.read().strip() + + +extensions = ["sphinx.ext.autodoc", "sphinx.ext.autosectionlabel", "sphinx_copybutton"] + +autosectionlabel_prefix_document = True +autosectionlabel_maxdepth = 2 + +templates_path = [] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", ".venv"] + + +html_theme = "furo" +html_static_path = ["_static"] +html_css_files = ["custom.css"] + +pygments_style = "one-dark" +pygments_dark_style = "one-dark" + +html_favicon = "https://ifcopenshell.org/assets/images/logo.png" +html_logo = "https://ifcopenshell.org/assets/images/logo.png" +html_theme_options = { + "source_repository": "https://github.com/IfcOpenShell/IfcOpenShell/", + "source_branch": "v0.8.0", + "source_directory": "src/bonsaiviewer/docs/", + "light_css_variables": { + "color-brand-primary": "#39b54a", + "color-brand-content": "#39b54a", + "color-brand-visited": "#d9e021", + "color-background-primary": "#f7f7f6", + "color-background-secondary": "#eeeeec", + "color-background-border": "#cfd0cb", + "color-foreground-primary": "#2e3436", + "color-sidebar-item-background--hover": "#f7f7f6", + "color-link": "#39b54a", + "color-link--visited": "#39b54a", + "color-link--hover": "#d98014", + "color-link--visited--hover": "#d98014", + "color-admonition-text": "#651fff", + "font-stack": "Nunito, -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji", + }, + "dark_css_variables": { + "color-brand-primary": "#39b54a", + "color-brand-content": "#39b54a", + "color-brand-visited": "#d9e021", + "color-background-primary": "#2e3436", + "color-background-border": "#2e3436", + "color-foreground-primary": "#eeeeec", + "color-sidebar-item-background--hover": "#2e3436", + "color-link": "#39b54a", + "color-link--visited": "#39b54a", + "color-link--hover": "#d98014", + "color-link--visited--hover": "#d98014", + "color-admonition-text": "#EEEEEC", + "font-stack": "Nunito, -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji", + }, + "footer_icons": [ + { + "name": "IfcOpenShell", + "url": "https://ifcopenshell.org", + "html": """ + + """, + "class": "", + }, + { + "name": "GitHub", + "url": "https://github.com/IfcOpenShell/IfcOpenShell/tree/v0.8.0/src/bonsaiviewer/docs", + "html": """ + + + + """, + "class": "", + }, + ], +} diff --git a/src/bonsaiviewer/docs/connectors/autodesk.rst b/src/bonsaiviewer/docs/connectors/autodesk.rst new file mode 100644 index 0000000000..62ba42e2b4 --- /dev/null +++ b/src/bonsaiviewer/docs/connectors/autodesk.rst @@ -0,0 +1,111 @@ +Autodesk Connector +================== + +The Autodesk connector integrates Bonsai Viewer with Autodesk Forma, APS, and +Docs. It implements the cloud connector protocol and runs as a separate process +that Bonsai Viewer launches and communicates with over standard input and +standard output. + +The connector UI is built with CustomTkinter. The Python runtime used for +development or packaging must include ``tkinter``. On Gentoo, make sure +``dev-lang/python`` is built with ``USE="tk"``. + +Install +------- + +From the repository root: + +.. code-block:: bash + + cd src/bonsaiviewer-autodesk + python -m venv venv + source venv/bin/activate + pip install -e . + +Run +--- + +.. code-block:: bash + + bonsaiviewer-autodesk + +The connector launches without configuration. On first run, open the settings +dialog, or set the ``APS_CLIENT_ID`` environment variable, to configure the +Autodesk client ID and OAuth callback port. + +For direct protocol testing, send newline-delimited JSON-RPC 2.0 requests on +standard input: + +.. code-block:: json + + {"jsonrpc":"2.0","id":"0","method":"open_settings"} + {"jsonrpc":"2.0","id":"1","method":"pull_ifcfed_interactive"} + {"jsonrpc":"2.0","id":"2","method":"pull_models","params":[{"display_name":"foo.ifc","id":"abc","source":{"connector":"autodesk","hub_id":"b.hub","project_id":"b.proj","item_id":"urn:adsk...","version_id":"latest"}}]} + {"jsonrpc":"2.0","id":"3","method":"push_ifcfed_interactive","params":{"path":"/tmp/project.ifcfed"}} + {"jsonrpc":"2.0","id":"4","method":"push_ifcfed","params":{"path":"/tmp/project.ifcfed","manifest":{"connector":"autodesk","hub_id":"b.hub","project_id":"b.proj","item_id":"urn:adsk..."}}} + +Bonsai Viewer is expected to launch this binary once per session and keep it +alive until shutdown. Closing the connector's standard input triggers a clean +exit. + +Configuration +------------- + +The connector reads the Autodesk client ID from these places, in order: + +1. ``APS_CLIENT_ID`` environment variable. +2. ``settings.json`` in the connector config directory, written by the settings + dialog. + +The OAuth callback host is always ``localhost``. The callback port defaults to +``8080`` and can be changed in the settings dialog. + +The config directory is platform-specific: + +- Linux: ``~/.config/bonsaiviewer-autodesk/`` +- macOS: ``~/Library/Application Support/bonsaiviewer-autodesk/`` +- Windows: ``%APPDATA%\bonsaiviewer-autodesk\`` + +OAuth tokens are stored in the OS keychain, keyed by the client ID. Changing +the client ID starts a fresh sign-in session. The keychain backend is Secret +Service on Linux, Keychain on macOS, and Credential Manager on Windows. + +Cache +----- + +The connector owns its own cache. On Linux, resolved files live under: + +.. code-block:: text + + ~/.cache/bonsaiviewer-autodesk/ + ifcfeds//.ifcfed[.manifest] + models// + +Each resolved file is the sole child in its directory so Bonsai Viewer can +write sidecar files, such as ``.ifcview``, next to it without colliding. A new +resolved model version lands in a fresh ``models//`` directory. Old cache +directories may be removed manually to clear space. + +Status +------ + +Implemented: + +- strict JSON-RPC 2.0 host over stdio +- APS PKCE sign-in with keyring-backed token storage +- hub, project, and folder browsing +- ``pull_ifcfed_interactive``, ``pull_ifcfed``, ``pull_models``, and + ``pull_models_interactive`` +- ``push_ifcfed_interactive``, ``push_ifcfed``, + ``push_model_interactive``, and ``push_model`` +- ``open_settings`` for client ID, callback port, and sign-out +- connector-managed cache with one resolved file per directory +- adjacent ``.ifcfed.manifest`` files written and read alongside + ``.ifcfed`` files + +Not implemented: + +- JSON-RPC notifications for progress streaming; the connector shows its own + progress dialog +- cancellation of in-flight downloads +- subdirectory upload layouts inside push destinations diff --git a/src/bonsaiviewer/docs/connectors/autodesk_packaging.rst b/src/bonsaiviewer/docs/connectors/autodesk_packaging.rst new file mode 100644 index 0000000000..49d72b395b --- /dev/null +++ b/src/bonsaiviewer/docs/connectors/autodesk_packaging.rst @@ -0,0 +1,130 @@ +Packaging the Autodesk Connector +================================ + +The Autodesk connector is shipped as a self-contained folder that can be +dropped into the Bonsai Viewer connectors directory. PyInstaller bundles the +Python interpreter, Qt, and dependencies so end users do not need Python +installed. + +PyInstaller does not cross-compile. Each operating system must build its own +package, typically through a CI matrix. + +Output +------ + +.. code-block:: text + + dist/ + autodesk/ + connector.json + bonsaiviewer-autodesk[.exe] + _internal/... + autodesk--.zip + +The ``autodesk/`` folder is what Bonsai Viewer expects under the +``connectors`` directory bundled next to the viewer executable +(``/connectors/``). + +Build +----- + +.. code-block:: bash + + cd src/bonsaiviewer-autodesk + python -m venv venv + source venv/bin/activate + pip install -e ".[build]" + python packaging/build.py + +On Windows, activate the virtual environment with +``venv\Scripts\activate``. + +The build script: + +1. cleans ``dist/`` and ``build/`` +2. runs PyInstaller against ``packaging/bonsaiviewer-autodesk.spec`` +3. renames the produced folder to ``autodesk/`` +4. copies ``connector.json`` into it +5. zips the folder as ``autodesk--.zip`` + +The UI uses Tcl/Tk through CustomTkinter, which keeps the bundle small. Expect +roughly 50 MB unpacked or 21 MB zipped per OS. The Python used to build must +include ``tkinter``. + +Linux +----- + +Build on the oldest glibc you intend to support. Binaries built on a newer +glibc will not run on older distributions. Ubuntu 22.04 LTS, with glibc 2.35, +is a reasonable lowest common denominator in 2026. + +The runtime keyring backend is Secret Service, such as gnome-keyring or +KWallet. End users need a Secret Service provider running. + +Expected output: ``autodesk-linux-x86_64.zip`` and/or an ARM64 archive. + +macOS +----- + +Each architecture builds separately. To support Apple Silicon and Intel, build +on each architecture and ship two zips, or post-process with ``lipo`` to +produce universal binaries. + +The runtime keyring backend is the system Keychain. For distribution outside +the developer's machine, codesign the executable and Tcl/Tk dylibs and notarize +the bundle. Unsigned binaries trigger Gatekeeper warnings. Codesigning is left +to the caller; the spec's ``codesign_identity`` field can be wired up. + +Expected output: ``autodesk-macos-arm64.zip`` and/or +``autodesk-macos-x86_64.zip``. + +Windows +------- + +Build with the Microsoft Visual C++ runtime available, which is usually true +for modern Python distributions. The runtime keyring backend is Credential +Manager. + +The executable is built with ``console=True`` because the connector speaks +JSON-RPC over stdio. Bonsai Viewer must launch it without showing a console +window, for example with Qt's ``QProcess::setCreateProcessArgumentsModifier`` +and ``CREATE_NO_WINDOW``. + +For distribution, sign the executable with an Authenticode certificate to avoid +SmartScreen warnings. Signing is left to the caller. + +Expected output: ``autodesk-windows-x86_64.zip``. + +Install a Built Connector +------------------------- + +Extract the zip into the ``connectors`` directory next to the Bonsai Viewer +executable. + +Linux: + +.. code-block:: bash + + unzip dist/autodesk-linux-x86_64.zip -d /connectors/ + +macOS: + +.. code-block:: bash + + unzip dist/autodesk-macos-arm64.zip -d "/connectors/" + +Windows PowerShell: + +.. code-block:: powershell + + Expand-Archive dist\autodesk-windows-x86_64.zip -DestinationPath "\connectors\" + +Bonsai Viewer discovers the connector on next launch. + +Out of Scope +------------ + +- signing and notarization +- CI matrix setup +- auto-update +- universal macOS binaries via ``lipo`` diff --git a/src/bonsaiviewer/docs/connectors/cloud_sync_protocol.rst b/src/bonsaiviewer/docs/connectors/cloud_sync_protocol.rst new file mode 100644 index 0000000000..92187e7200 --- /dev/null +++ b/src/bonsaiviewer/docs/connectors/cloud_sync_protocol.rst @@ -0,0 +1,384 @@ +Cloud Connector Protocol +======================== + +Bonsai Viewer can load and save projects and models from cloud platforms +through connectors. A connector is a separate application launched by Bonsai +Viewer. It handles authentication, browsing, downloads, uploads, caching, and +platform-specific behaviour. + +The protocol currently covers: + +- projects: ``.ifcfed`` +- models: ``.ifc``, ``.rdb``, ``.ifcview``, ``.rdbview`` + +Issues, clash results, specifications, and other resources are not yet defined. + +Communication +------------- + +Bonsai Viewer launches one connector process per session, on first use, and +keeps it alive for the duration of the session. Communication uses +newline-delimited JSON-RPC 2.0 over standard input and standard output: + +- requests and responses are single-line JSON objects +- every message is terminated by a single newline +- the connector must not emit literal newlines inside a JSON message +- diagnostics may be written to standard error + +Bonsai Viewer shuts down a connector by closing its standard input. The +connector should exit cleanly. If it does not exit within a few seconds, Bonsai +Viewer may terminate it. + +Connector Scope +--------------- + +Bonsai Viewer only works with local files. If a project or model is not local, +the viewer asks a connector to resolve it. The connector returns a local path +and optional cloud metadata. It must not modify the model file itself. + +The connector handles anything specific to the cloud platform or data source: +authentication, browsing, filtering, searches, revision pinning, progress UI, +caches, and platform-specific rules. A connector may show its own UI or run +without UI. + +Returned files must be the sole child in their directory. This allows Bonsai +Viewer to write adjacent sidecars, temporary files, database locks, or helper +files without filename collisions. A connector may invalidate cached files by +deleting the whole resolved directory. A new cloud revision should resolve to a +fresh directory. + +Projects +-------- + +A project is an ``.ifcfed`` file. It stores project settings and model entries. + +Example project: + +.. code-block:: json + + { + "created": "2026-04-29T21:22:36Z", + "modified": "2026-04-29T21:22:36Z", + "home_view": null, + "models": [] + } + +A cloud-sourced project may have an adjacent ``.ifcfed.manifest`` file storing +connector-specific source metadata. + +.. code-block:: json + + { + "connector": "mycompany", + "version": "2", + "url": "https://example.com/project.ifcfed" + } + +Models +------ + +Models are listed in the ``.ifcfed`` file. A model may point to a local path or +to a cloud connector source. + +Cloud connector source data is intentionally connector-specific. For example, +a connector may store whether a model is pinned to one revision or should +always resolve to the latest revision. + +.. code-block:: json + + [ + { + "display_name": "foo.ifc", + "id": "0503642e-e2f6-4700-87fd-16479542e801", + "source": { + "connector": "local", + "path": "path/to/foo.ifc" + } + }, + { + "display_name": "bar.ifc", + "id": "5fc69e6a-1ff0-4d8a-82c6-2215df53d2ed", + "source": { + "connector": "autodesk", + "version": "1", + "hub_id": "b.hub123", + "project_id": "b.project456", + "item_id": "urn:adsk.wipprod:dm.lineage:abc", + "version_id": "urn:adsk.wipprod:fs.file:vf.xyz?version=3" + } + } + ] + +Cloud metadata such as filename, cloud ID, revision, and date modified is not +specified or stored in the ``.ifcfed``. It is returned by the connector when +requested. Bonsai Viewer displays returned cloud metadata as text. The keys +``author``, ``revision``, and ``date`` may be shown in more prominent UI +locations. + +Open from Cloud +--------------- + +``pull_ifcfed_interactive`` opens an ``.ifcfed`` from a cloud platform and +starts a fresh session. + +.. code-block:: json + + { "jsonrpc": "2.0", "id": "0", "method": "pull_ifcfed_interactive" } + +The connector authenticates and lets the user browse or choose a project. It +downloads or resolves the ``.ifcfed`` into a connector-managed directory, +creates an adjacent ``.ifcfed.manifest``, and returns the local path: + +.. code-block:: json + + { "jsonrpc": "2.0", "id": "0", "result": { "path": "/path/to/project/file.ifcfed" } } + +Bonsai Viewer loads the project, then calls ``pull_models`` for cloud-sourced +models referenced by the ``.ifcfed``: + +.. code-block:: json + + { + "jsonrpc": "2.0", + "id": "1", + "method": "pull_models", + "params": [ + { "display_name": "bar.ifc", "id": "...", "source": { "connector": "autodesk" } } + ] + } + +The connector returns one result per requested model. A result may include +optional metadata. ``null`` means that item was skipped or failed without +aborting the whole batch. + +.. code-block:: json + + { + "jsonrpc": "2.0", + "id": "1", + "result": [ + { + "path": "/path/to/model.ifc", + "metadata": { "revision": "B", "date": "2nd Oct 2025" } + }, + null + ] + } + +Downloading the latest version of every model immediately is not required. A +connector may download the latest file, use a pinned revision, offer the user a +choice, skip a model, or return a cached file. The user can reopen or sync the +project later. + +Sync Cloud to Local +------------------- + +``pull_ifcfed`` refreshes the cloud-sourced project without prompting the user. +It is available when the project has cloud resources: an adjacent +``.ifcfed.manifest`` for the project itself, one or more cloud-sourced models, +or both. + +.. code-block:: json + + { + "jsonrpc": "2.0", + "id": "0", + "method": "pull_ifcfed", + "params": { + "connector": "mycompany", + "version": "2", + "url": "https://example.com/project.ifcfed" + } + } + +The connector returns a local ``.ifcfed`` path and writes a fresh adjacent +manifest. Bonsai Viewer then continues by calling ``pull_models`` for +cloud-sourced models. The project-refresh phase and model-refresh phase are +independent. If no manifest exists, the project refresh phase is skipped and +Bonsai Viewer refreshes only cloud-sourced models. + +If the returned ``.ifcfed`` is unchanged from the currently loaded file, Bonsai +Viewer may skip the fresh-session reload and continue to model refresh. How +"unchanged" is determined, such as byte equality, hash, or modified time, is +left to Bonsai Viewer. + +Save Projects to Cloud +---------------------- + +``push_ifcfed_interactive`` is the cloud equivalent of Save As. It pushes an +``.ifcfed`` to a new user-selected cloud location. + +.. code-block:: json + + { + "jsonrpc": "2.0", + "id": "0", + "method": "push_ifcfed_interactive", + "params": { "path": "/tmp/path/to/project.ifcfed" } + } + +The connector uploads the file, writes an adjacent manifest, and returns the +resolved local path. + +``push_ifcfed`` is the cloud equivalent of Save. It pushes back to the existing +cloud location described by the manifest. + +.. code-block:: json + + { + "jsonrpc": "2.0", + "id": "0", + "method": "push_ifcfed", + "params": { + "path": "/tmp/path/to/project.ifcfed", + "manifest": { + "connector": "mycompany", + "version": "2", + "url": "https://example.com/project.ifcfed" + } + } + } + +The connector may update the manifest if the cloud platform returns a new +revision or version identifier. + +Add and Save Models +------------------- + +``pull_models_interactive`` lets the user choose one or more cloud models to +add to the current project. + +.. code-block:: json + + { "jsonrpc": "2.0", "id": "0", "method": "pull_models_interactive" } + +The connector returns model entries with ``display_name``, ``source``, a local +``path``, and optional metadata: + +.. code-block:: json + + { + "jsonrpc": "2.0", + "id": "0", + "result": [ + { + "display_name": "bar.ifc", + "source": { "connector": "autodesk" }, + "path": "/path/to/model.ifc", + "metadata": { "revision": "B", "date": "2nd Oct 2025" } + } + ] + } + +Bonsai Viewer stores ``display_name`` and ``source`` in the ``.ifcfed`` and +loads the model from ``path``. The path and metadata are not stored in the +``.ifcfed``. + +``push_model_interactive`` is Save Model As to Cloud: + +.. code-block:: json + + { + "jsonrpc": "2.0", + "id": "0", + "method": "push_model_interactive", + "params": { "path": "/tmp/path/to/model.ifc" } + } + +``push_model`` saves a model back to the existing cloud location described by +the model's ``source``: + +.. code-block:: json + + { + "jsonrpc": "2.0", + "id": "0", + "method": "push_model", + "params": { + "path": "/tmp/path/to/model.ifc", + "source": { "connector": "autodesk", "hub_id": "b.hub123" } + } + } + +The connector returns an updated ``source`` and optional metadata. Bonsai +Viewer replaces the model's ``source`` in the ``.ifcfed``. + +Conflict handling for non-interactive push methods is the connector's +responsibility. The connector may overwrite, prompt, reject with a JSON-RPC +error, or apply a platform-specific policy. + +Settings and Errors +------------------- + +A connector may implement ``open_settings`` for credentials, sign-out, default +folders, cache management, or other connector-specific settings. + +.. code-block:: json + + { "jsonrpc": "2.0", "id": "0", "method": "open_settings" } + +If a connector returns JSON-RPC ``Method not found`` with code ``-32601``, +Bonsai Viewer treats it as having no settings UI. + +The settings result is currently always an empty object. Future revisions may +add optional fields, such as a refreshed display label for the connector. + +The connector owns user-facing error handling. Bonsai Viewer understands only: + +- per-item soft failure: ``null`` in a result array +- whole-call hard failure: a JSON-RPC error object + +Diagnostic details should be written to standard error for logs. + +Permissions +----------- + +Permissions are entirely connector-managed. A federation may contain models +from multiple platforms, and some resources may fail while others succeed. +Bonsai Viewer tolerates partial failure and does not require all remote models +to resolve successfully. + +Discovery +--------- + +A connector is shipped as a folder containing a ``connector.json`` manifest and +an executable entry point. + +.. code-block:: text + + / + autodesk/ + connector.json + bonsaiviewer-autodesk + ... + +Example ``connector.json``: + +.. code-block:: json + + { + "id": "autodesk", + "name": "Autodesk Forma", + "version": "0.1.0", + "exec": "./bonsaiviewer-autodesk" + } + +Fields: + +- ``id`` is the stable identifier used in ``.ifcfed`` ``source.connector`` + fields and in ``.ifcfed.manifest`` files. +- ``name`` is the human-readable UI label. +- ``version`` is informational. +- ``exec`` is the connector executable. Relative paths are resolved against the + connector folder. On Windows, Bonsai Viewer also tries ``.exe`` if the + path does not exist as written. + +Bonsai Viewer scans the ``connectors`` directory bundled alongside the +executable (``/connectors/``). + +Every immediate subdirectory containing ``connector.json`` is treated as a +connector. Connectors are launched on demand, not at startup. + +If two folders declare the same ``id``, the first one wins and the other is +skipped with a log warning. Malformed manifests or missing executables are +reported when relevant without preventing other connectors from loading. diff --git a/src/bonsaiviewer/docs/connectors/index.rst b/src/bonsaiviewer/docs/connectors/index.rst new file mode 100644 index 0000000000..8bd00e790f --- /dev/null +++ b/src/bonsaiviewer/docs/connectors/index.rst @@ -0,0 +1,14 @@ +Connectors +========== + +Bonsai Viewer connectors are separate applications that let the viewer open +and save projects or models from cloud platforms. The viewer itself works with +local files; a connector resolves cloud resources into local cached files and +returns the metadata needed to keep them connected to the cloud source. + +.. toctree:: + :maxdepth: 2 + + cloud_sync_protocol + autodesk + autodesk_packaging diff --git a/src/bonsaiviewer/docs/index.rst b/src/bonsaiviewer/docs/index.rst new file mode 100644 index 0000000000..65d88160f1 --- /dev/null +++ b/src/bonsaiviewer/docs/index.rst @@ -0,0 +1,45 @@ +Learn Bonsai Viewer +=================== + +Hello world. Bonsai Viewer is a desktop application for opening, federating, +and reviewing IFC models. + +It is focused on fast model viewing rather than authoring. It works with IFC +files and viewer-oriented formats such as ``.ifcview`` and ``.rdbview``. + +.. only:: builder_html and (not singlehtml) + + .. container:: toc-cards + + .. container:: card + + Getting started + Open a model, navigate the scene, and inspect project data. + + .. container:: card + + Large models + Use viewer caches and packaged formats for faster loading. + + .. container:: card + + Federation + Combine multiple model files into one review session. + + .. container:: card + + :doc:`/connectors/index` + Load models from external systems and cloud workflows. + +.. container:: global-index-toc + + .. toctree:: + :hidden: + :caption: Contents + :maxdepth: 2 + + connectors/index + +Need more help? Join the `live chat `__ or `community +forums `__. Something strange happening? Please +`report a bug `__. diff --git a/src/bonsaiviewer/docs/make.bat b/src/bonsaiviewer/docs/make.bat new file mode 100644 index 0000000000..922152e96a --- /dev/null +++ b/src/bonsaiviewer/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/src/bonsaiviewer/docs/requirements.txt b/src/bonsaiviewer/docs/requirements.txt new file mode 100644 index 0000000000..32f6a568fe --- /dev/null +++ b/src/bonsaiviewer/docs/requirements.txt @@ -0,0 +1,3 @@ +sphinx +furo +sphinx-copybutton diff --git a/src/bonsaiviewer/modules/connectors/Discovery.cpp b/src/bonsaiviewer/modules/connectors/Discovery.cpp index 307df1feb0..26d14ba5e1 100644 --- a/src/bonsaiviewer/modules/connectors/Discovery.cpp +++ b/src/bonsaiviewer/modules/connectors/Discovery.cpp @@ -83,7 +83,7 @@ QString userConnectorsDir() { // Linux -> ~/.local/share // macOS -> ~/Library/Application Support // Win -> %APPDATA% (Roaming) - // matching CLOUD_SYNC_PROTOCOL.md's listed locations. + // matching the Bonsai Viewer connector protocol docs. const QString base = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation); return QDir(base).filePath("IfcOpenShell/BonsaiViewer/connectors"); } diff --git a/src/bonsaiviewer/modules/connectors/Discovery.h b/src/bonsaiviewer/modules/connectors/Discovery.h index 9063165d7c..620aa590bd 100644 --- a/src/bonsaiviewer/modules/connectors/Discovery.h +++ b/src/bonsaiviewer/modules/connectors/Discovery.h @@ -35,7 +35,7 @@ struct ConnectorManifest { // otherwise a bare name for PATH lookup at launch }; -// Scans the per-user connectors directory per CLOUD_SYNC_PROTOCOL.md. +// Scans the per-user connectors directory per the connector protocol docs. // First match wins for any given id; duplicates and malformed manifests // are skipped with a qWarning. Returned in discovery order so first-wins // is observable to callers.