From 084c87ea06282f14180a44deeb695d83b292f200 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 20 May 2026 09:49:34 +1000 Subject: [PATCH] Rename ifcviewer-autodesk connector to bonsaiviewer-autodesk Follows the host viewer's rename to Bonsai Viewer: directory, Python package, entry point, PyInstaller spec, keyring service, and on-disk config/cache paths all use the bonsaiviewer-autodesk name. CI workflow filename and path filters updated to match. Co-Authored-By: Claude Opus 4.7 --- ...sk.yml => build-bonsaiviewer-autodesk.yml} | 14 +-- .gitignore | 6 +- .../CLOUD_SYNC_PROTOCOL.md | 112 +++++++++--------- .../README.md | 16 +-- .../bonsaiviewer_autodesk}/__init__.py | 0 .../bonsaiviewer_autodesk}/__main__.py | 6 +- .../bonsaiviewer_autodesk}/autodesk.py | 2 +- .../bonsaiviewer_autodesk}/cache.py | 6 +- .../bonsaiviewer_autodesk}/connector.py | 10 +- .../bonsaiviewer_autodesk}/rpc.py | 0 .../bonsaiviewer_autodesk}/settings.py | 6 +- .../bonsaiviewer_autodesk}/ui.py | 17 ++- .../connector.json | 2 +- .../packaging/README.md | 12 +- .../packaging/bonsaiviewer-autodesk.spec} | 10 +- .../packaging/build.py | 10 +- .../pyproject.toml | 6 +- 17 files changed, 121 insertions(+), 114 deletions(-) rename .github/workflows/{build-ifcviewer-autodesk.yml => build-bonsaiviewer-autodesk.yml} (81%) rename src/{ifcviewer-autodesk => bonsaiviewer-autodesk}/CLOUD_SYNC_PROTOCOL.md (81%) rename src/{ifcviewer-autodesk => bonsaiviewer-autodesk}/README.md (91%) rename src/{ifcviewer-autodesk/ifcviewer_autodesk => bonsaiviewer-autodesk/bonsaiviewer_autodesk}/__init__.py (100%) rename src/{ifcviewer-autodesk/ifcviewer_autodesk => bonsaiviewer-autodesk/bonsaiviewer_autodesk}/__main__.py (65%) rename src/{ifcviewer-autodesk/ifcviewer_autodesk => bonsaiviewer-autodesk/bonsaiviewer_autodesk}/autodesk.py (99%) rename src/{ifcviewer-autodesk/ifcviewer_autodesk => bonsaiviewer-autodesk/bonsaiviewer_autodesk}/cache.py (91%) rename src/{ifcviewer-autodesk/ifcviewer_autodesk => bonsaiviewer-autodesk/bonsaiviewer_autodesk}/connector.py (98%) rename src/{ifcviewer-autodesk/ifcviewer_autodesk => bonsaiviewer-autodesk/bonsaiviewer_autodesk}/rpc.py (100%) rename src/{ifcviewer-autodesk/ifcviewer_autodesk => bonsaiviewer-autodesk/bonsaiviewer_autodesk}/settings.py (92%) rename src/{ifcviewer-autodesk/ifcviewer_autodesk => bonsaiviewer-autodesk/bonsaiviewer_autodesk}/ui.py (97%) rename src/{ifcviewer-autodesk => bonsaiviewer-autodesk}/connector.json (68%) rename src/{ifcviewer-autodesk => bonsaiviewer-autodesk}/packaging/README.md (91%) rename src/{ifcviewer-autodesk/packaging/ifcviewer-autodesk.spec => bonsaiviewer-autodesk/packaging/bonsaiviewer-autodesk.spec} (87%) rename src/{ifcviewer-autodesk => bonsaiviewer-autodesk}/packaging/build.py (90%) rename src/{ifcviewer-autodesk => bonsaiviewer-autodesk}/pyproject.toml (73%) diff --git a/.github/workflows/build-ifcviewer-autodesk.yml b/.github/workflows/build-bonsaiviewer-autodesk.yml similarity index 81% rename from .github/workflows/build-ifcviewer-autodesk.yml rename to .github/workflows/build-bonsaiviewer-autodesk.yml index 0bce9147b5..d137e89da5 100644 --- a/.github/workflows/build-ifcviewer-autodesk.yml +++ b/.github/workflows/build-bonsaiviewer-autodesk.yml @@ -1,15 +1,15 @@ -name: Build IfcViewer Autodesk Connector +name: Build Bonsai Viewer Autodesk Connector on: workflow_dispatch: push: paths: - - 'src/ifcviewer-autodesk/**' - - '.github/workflows/build-ifcviewer-autodesk.yml' + - 'src/bonsaiviewer-autodesk/**' + - '.github/workflows/build-bonsaiviewer-autodesk.yml' pull_request: paths: - - 'src/ifcviewer-autodesk/**' - - '.github/workflows/build-ifcviewer-autodesk.yml' + - 'src/bonsaiviewer-autodesk/**' + - '.github/workflows/build-bonsaiviewer-autodesk.yml' jobs: build: @@ -39,7 +39,7 @@ jobs: defaults: run: - working-directory: src/ifcviewer-autodesk + working-directory: src/bonsaiviewer-autodesk steps: - uses: actions/checkout@v6 @@ -64,5 +64,5 @@ jobs: uses: actions/upload-artifact@v7 with: name: autodesk-${{ matrix.os_label }}-${{ matrix.arch }} - path: src/ifcviewer-autodesk/dist/autodesk-${{ matrix.os_label }}-${{ matrix.arch }}.zip + path: src/bonsaiviewer-autodesk/dist/autodesk-${{ matrix.os_label }}-${{ matrix.arch }}.zip if-no-files-found: error diff --git a/.gitignore b/.gitignore index e6255fca43..9312a043f6 100644 --- a/.gitignore +++ b/.gitignore @@ -129,6 +129,6 @@ src/ifcopenshell-python/ifcopenshell/express/*.exp.cache.dat *.py.tmp* *.json.tmp* -# ifcviewer-autodesk connector build artifacts -/src/ifcviewer-autodesk/build/ -/src/ifcviewer-autodesk/dist/ +# bonsaiviewer-autodesk connector build artifacts +/src/bonsaiviewer-autodesk/build/ +/src/bonsaiviewer-autodesk/dist/ diff --git a/src/ifcviewer-autodesk/CLOUD_SYNC_PROTOCOL.md b/src/bonsaiviewer-autodesk/CLOUD_SYNC_PROTOCOL.md similarity index 81% rename from src/ifcviewer-autodesk/CLOUD_SYNC_PROTOCOL.md rename to src/bonsaiviewer-autodesk/CLOUD_SYNC_PROTOCOL.md index af1e5a73c3..053ec834ff 100644 --- a/src/ifcviewer-autodesk/CLOUD_SYNC_PROTOCOL.md +++ b/src/bonsaiviewer-autodesk/CLOUD_SYNC_PROTOCOL.md @@ -1,13 +1,13 @@ -# IfcViewer cloud connectors +# Bonsai Viewer cloud connectors -IfcViewer will have the capability to load and save projects and models from a +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 IfcViewer itself will depend on +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 IfcViewer. +application which will communicate to and from the Bonsai Viewer. The following types of resources may be managed with a connector: @@ -18,7 +18,7 @@ The following types of resources may be managed with a connector: ## Communication protocol -The IfcViewer launches one connector process per session, on first use, and +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. @@ -28,18 +28,18 @@ 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 IfcViewer + - The connector may write arbitrary diagnostic output to stderr; the Bonsai Viewer will not parse it. -The IfcViewer shuts a connector down by closing its stdin. The connector should -exit cleanly. If it does not exit within a few seconds, the IfcViewer will +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 IfcViewer has minimal knowledge about connectors. IfcViewer only knows how +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 IfcViewer's +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. @@ -52,7 +52,7 @@ 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 IfcViewer. +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, @@ -140,9 +140,9 @@ 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 IfcViewer will display all returned cloud metadata as simple text strings. +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 -IfcViewer UI for convenience: +Bonsai Viewer UI for convenience: - author - revision @@ -157,7 +157,7 @@ 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 IfcViewer UI that says "Open from Cloud" + 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 @@ -171,8 +171,8 @@ later. ```json { "jsonrpc": "2.0", "id": "0", "result": { "path": "/path/to/project/file.ifcfed" } } ``` - 5. The IfcViewer loads the `path`. This constitutes a fresh session. - 6. The IfcViewer calls `pull_models`: + 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": ..., }, @@ -185,7 +185,7 @@ later. { "jsonrpc": "2.0", "id": "1", "result": [ { "path": "/path/to/foo.ifc" }, { - "path": "/path/to/model.ifc", # Used to load the model in IfcViewer + "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 @@ -193,8 +193,8 @@ later. ... ] } ``` - 8. The IfcViewer may call another connector with more models to be downloaded. - 9. The IfcViewer will load the downloaded models as regular files. Typically this will also result in the IfcViewer reading / writing a cache (e.g. .ifcview) alongside this file, but it is not expected that the connector will know or care about this. + 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 @@ -205,8 +205,8 @@ the project has any cloud resources: a `.ifcfed.manifest` adjacent to 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 IfcViewer UI that says "Sync Cloud to Local" - 2. IfcViewer reads the .ifcfed.manifest and invokes the relevant connector with the manifest data with the `pull_ifcfed` method: + 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": ... @@ -223,16 +223,16 @@ first requires a manifest. 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 IfcViewer continues from step 6 of the +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 IfcViewer skips step 5 as well and continues from +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 IfcViewer. +is left to the Bonsai Viewer. ## Save as to cloud @@ -240,7 +240,7 @@ 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 IfcViewer UI that says "Save As to Cloud" + 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 @@ -254,7 +254,7 @@ cloud location for a project that does not yet have a `.ifcfed.manifest`. ```json { "jsonrpc": "2.0", "id": "0", "result": { "path": "/path/to/project/file.ifcfed" } } ``` - 5. The IfcViewer "repoints" to the returned path. It is not necessary to do a full reload as no "changes" are made. + 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 @@ -262,8 +262,8 @@ 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 IfcViewer UI that says "Save to Cloud" - 2. IfcViewer reads the .ifcfed.manifest and invokes the relevant connector with the `push_ifcfed` method, passing both the local path and the manifest data: + 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", @@ -278,18 +278,18 @@ when there is a `.ifcfed.manifest` adjacent to the project. ```json { "jsonrpc": "2.0", "id": "0", "result": { "path": "/path/to/project/file.ifcfed" } } ``` - 4. The IfcViewer "repoints" to the returned path. It is not necessary to do a full reload as no "changes" are made. + 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 IfcViewer expresses no opinion. This rule +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 IfcViewer UI that says "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 @@ -305,16 +305,16 @@ also applies to `push_model` below. { "display_name": "bar.ifc", # Stored in .ifcfed "source": { "connector": "autodesk", ... }, # Stored in .ifcfed - "path": "/path/to/model.ifc", # Used to load the model in IfcViewer + "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 IfcViewer updates the .ifcfed models section with new models using the + 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 IfcViewer stores the "metadata" + 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 @@ -323,7 +323,7 @@ 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 IfcViewer UI that says "Save Model As to Cloud" + 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 @@ -342,7 +342,7 @@ cloud `source` for a model whose current source is `local`. "metadata": { "revision": "B", "date": "2nd Oct 2025" ... }, # Optional, used to display stats } } ``` - 5. The IfcViewer updates the .ifcfed models section with the new model metadata from the provided data. + 5. The Bonsai Viewer updates the .ifcfed models section with the new model metadata from the provided data. ## Save model to cloud @@ -351,8 +351,8 @@ 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 IfcViewer UI that says "Save Model to Cloud" - 2. IfcViewer 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: + 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", @@ -370,17 +370,17 @@ anything other than `local`). "metadata": { "revision": "C", "date": "19th May 2026" ... }, # Optional, used to display stats } } ``` - 4. The IfcViewer replaces the model's `source` in the .ifcfed and refreshes the stored metadata for display. + 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 IfcViewer invokes +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 IfcViewer UI. - 2. The IfcViewer sends `open_settings`: + 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" } ``` @@ -391,7 +391,7 @@ credentials, sign-out, default folders, anything connector-specific. ``` If the connector returns a JSON-RPC `Method not found` error (code `-32601`), -the IfcViewer should treat that connector as having no settings and hide its +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. @@ -408,22 +408,22 @@ 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 IfcViewer does not interpret or display connector +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 IfcViewer + 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 IfcViewer aborts - the operation. The `error.message` may be logged by the IfcViewer for + 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 IfcViewer captures for logs. +stderr, which the Bonsai Viewer captures for logs. ## Permissions @@ -438,7 +438,7 @@ The viewer will tolerate partial failure and report skipped resources that the c ## Connector discovery A connector is shipped as a folder containing a `connector.json` manifest and -an executable entry point. The IfcViewer discovers connectors by scanning a +an executable entry point. The Bonsai Viewer discovers connectors by scanning a small, fixed set of locations for these folders. ### Connector bundle layout @@ -447,7 +447,7 @@ small, fixed set of locations for these folders. / autodesk/ # folder name is arbitrary; id comes from connector.json connector.json # required, at the folder root - ifcviewer-autodesk # the executable (or a wrapper script) + bonsaiviewer-autodesk # the executable (or a wrapper script) ... # anything else the connector ships ``` @@ -458,38 +458,38 @@ small, fixed set of locations for these folders. "id": "autodesk", "name": "Autodesk Forma", "version": "0.1.0", - "exec": "./ifcviewer-autodesk" + "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 IfcViewer UI. + - `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 IfcViewer will also try `.exe` if `` does not + On Windows, the Bonsai Viewer will also try `.exe` if `` does not exist as written. ### Search locations -The IfcViewer scans the **user connectors directory**. The platform's per-user +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 IfcViewer looks at every immediate subdirectory and treats it as a +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 IfcViewer's + 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 diff --git a/src/ifcviewer-autodesk/README.md b/src/bonsaiviewer-autodesk/README.md similarity index 91% rename from src/ifcviewer-autodesk/README.md rename to src/bonsaiviewer-autodesk/README.md index 5a4c45b3cf..f57475da56 100644 --- a/src/ifcviewer-autodesk/README.md +++ b/src/bonsaiviewer-autodesk/README.md @@ -1,6 +1,6 @@ -# `ifcviewer-autodesk` +# `bonsaiviewer-autodesk` -Autodesk Forma (APS / Docs) connector for IfcViewer. +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 @@ -14,7 +14,7 @@ on Gentoo make sure `USE="tk"` is set for `dev-lang/python`). ## Install ```bash -cd src/ifcviewer-autodesk +cd src/bonsaiviewer-autodesk python -m venv venv source venv/bin/activate pip install -e . @@ -23,7 +23,7 @@ pip install -e . ## Run ```bash -ifcviewer-autodesk +bonsaiviewer-autodesk ``` The connector launches without any configuration; on first run, invoke @@ -53,9 +53,9 @@ The connector reads the Autodesk client id from two places, in order: The config directory is platform-specific: -- Linux: `~/.config/ifcviewer-autodesk/` -- macOS: `~/Library/Application Support/ifcviewer-autodesk/` -- Windows: `%APPDATA%\ifcviewer-autodesk\` +- 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 @@ -66,7 +66,7 @@ the client id starts a fresh session. The connector owns its own cache. Resolved files live under (Linux): ``` -~/.cache/ifcviewer-autodesk/ +~/.cache/bonsaiviewer-autodesk/ ifcfeds//.ifcfed[.manifest] models// ``` diff --git a/src/ifcviewer-autodesk/ifcviewer_autodesk/__init__.py b/src/bonsaiviewer-autodesk/bonsaiviewer_autodesk/__init__.py similarity index 100% rename from src/ifcviewer-autodesk/ifcviewer_autodesk/__init__.py rename to src/bonsaiviewer-autodesk/bonsaiviewer_autodesk/__init__.py diff --git a/src/ifcviewer-autodesk/ifcviewer_autodesk/__main__.py b/src/bonsaiviewer-autodesk/bonsaiviewer_autodesk/__main__.py similarity index 65% rename from src/ifcviewer-autodesk/ifcviewer_autodesk/__main__.py rename to src/bonsaiviewer-autodesk/bonsaiviewer_autodesk/__main__.py index 54e665a2fe..d45b9a078e 100644 --- a/src/ifcviewer-autodesk/ifcviewer_autodesk/__main__.py +++ b/src/bonsaiviewer-autodesk/bonsaiviewer_autodesk/__main__.py @@ -2,9 +2,9 @@ from __future__ import annotations import sys -from ifcviewer_autodesk.connector import AutodeskConnector -from ifcviewer_autodesk.rpc import JsonRpcHost -from ifcviewer_autodesk.ui import ensure_tk_app +from bonsaiviewer_autodesk.connector import AutodeskConnector +from bonsaiviewer_autodesk.rpc import JsonRpcHost +from bonsaiviewer_autodesk.ui import ensure_tk_app def main() -> int: diff --git a/src/ifcviewer-autodesk/ifcviewer_autodesk/autodesk.py b/src/bonsaiviewer-autodesk/bonsaiviewer_autodesk/autodesk.py similarity index 99% rename from src/ifcviewer-autodesk/ifcviewer_autodesk/autodesk.py rename to src/bonsaiviewer-autodesk/bonsaiviewer_autodesk/autodesk.py index 23ea5ea800..439d749ecd 100644 --- a/src/ifcviewer-autodesk/ifcviewer_autodesk/autodesk.py +++ b/src/bonsaiviewer-autodesk/bonsaiviewer_autodesk/autodesk.py @@ -17,7 +17,7 @@ import httpx import keyring import keyring.errors -from ifcviewer_autodesk.rpc import JSONRPC_INTERNAL_ERROR, RpcError +from bonsaiviewer_autodesk.rpc import JSONRPC_INTERNAL_ERROR, RpcError Progress = Callable[[str, str, "int | None"], None] diff --git a/src/ifcviewer-autodesk/ifcviewer_autodesk/cache.py b/src/bonsaiviewer-autodesk/bonsaiviewer_autodesk/cache.py similarity index 91% rename from src/ifcviewer-autodesk/ifcviewer_autodesk/cache.py rename to src/bonsaiviewer-autodesk/bonsaiviewer_autodesk/cache.py index 1c454f2b21..1a9a98e20f 100644 --- a/src/ifcviewer-autodesk/ifcviewer_autodesk/cache.py +++ b/src/bonsaiviewer-autodesk/bonsaiviewer_autodesk/cache.py @@ -13,12 +13,12 @@ def cache_root() -> Path: system = platform.system() if system == "Windows": base = os.environ.get("LOCALAPPDATA") or os.path.expanduser("~") - root = Path(base) / "ifcviewer-autodesk" / "Cache" + root = Path(base) / "bonsaiviewer-autodesk" / "Cache" elif system == "Darwin": - root = Path.home() / "Library" / "Caches" / "ifcviewer-autodesk" + root = Path.home() / "Library" / "Caches" / "bonsaiviewer-autodesk" else: base = os.environ.get("XDG_CACHE_HOME") or os.path.expanduser("~/.cache") - root = Path(base) / "ifcviewer-autodesk" + root = Path(base) / "bonsaiviewer-autodesk" root.mkdir(parents=True, exist_ok=True) return root diff --git a/src/ifcviewer-autodesk/ifcviewer_autodesk/connector.py b/src/bonsaiviewer-autodesk/bonsaiviewer_autodesk/connector.py similarity index 98% rename from src/ifcviewer-autodesk/ifcviewer_autodesk/connector.py rename to src/bonsaiviewer-autodesk/bonsaiviewer_autodesk/connector.py index 4b3bad01d7..0f2b721fa9 100644 --- a/src/ifcviewer-autodesk/ifcviewer_autodesk/connector.py +++ b/src/bonsaiviewer-autodesk/bonsaiviewer_autodesk/connector.py @@ -5,10 +5,10 @@ import traceback from pathlib import Path from typing import Any, Callable -from ifcviewer_autodesk import cache, settings -from ifcviewer_autodesk.autodesk import ApsClient, AuthSessionService, KeyringTokenStore -from ifcviewer_autodesk.rpc import JSONRPC_INTERNAL_ERROR, JSONRPC_INVALID_PARAMS, RpcError -from ifcviewer_autodesk.ui import BrowseDialog, SettingsDialog, progress_dialog, prompt_for_filename +from bonsaiviewer_autodesk import cache, settings +from bonsaiviewer_autodesk.autodesk import ApsClient, AuthSessionService, KeyringTokenStore +from bonsaiviewer_autodesk.rpc import JSONRPC_INTERNAL_ERROR, JSONRPC_INVALID_PARAMS, RpcError +from bonsaiviewer_autodesk.ui import BrowseDialog, SettingsDialog, progress_dialog, prompt_for_filename ApsProgress = Callable[[str, "int | None"], None] @@ -34,7 +34,7 @@ def _upload_callback(report: Report) -> ApsProgress: CONNECTOR_ID = "autodesk" -KEYRING_SERVICE = "ifcviewer-autodesk" +KEYRING_SERVICE = "bonsaiviewer-autodesk" DEFAULT_CALLBACK_URL = "http://localhost:8080/" DEFAULT_SCOPE = "data:read data:write data:create" diff --git a/src/ifcviewer-autodesk/ifcviewer_autodesk/rpc.py b/src/bonsaiviewer-autodesk/bonsaiviewer_autodesk/rpc.py similarity index 100% rename from src/ifcviewer-autodesk/ifcviewer_autodesk/rpc.py rename to src/bonsaiviewer-autodesk/bonsaiviewer_autodesk/rpc.py diff --git a/src/ifcviewer-autodesk/ifcviewer_autodesk/settings.py b/src/bonsaiviewer-autodesk/bonsaiviewer_autodesk/settings.py similarity index 92% rename from src/ifcviewer-autodesk/ifcviewer_autodesk/settings.py rename to src/bonsaiviewer-autodesk/bonsaiviewer_autodesk/settings.py index 8a4b8c037b..3b4ebdf8d7 100644 --- a/src/ifcviewer-autodesk/ifcviewer_autodesk/settings.py +++ b/src/bonsaiviewer-autodesk/bonsaiviewer_autodesk/settings.py @@ -11,12 +11,12 @@ def config_root() -> Path: system = platform.system() if system == "Windows": base = os.environ.get("APPDATA") or os.path.expanduser("~") - root = Path(base) / "ifcviewer-autodesk" + root = Path(base) / "bonsaiviewer-autodesk" elif system == "Darwin": - root = Path.home() / "Library" / "Application Support" / "ifcviewer-autodesk" + root = Path.home() / "Library" / "Application Support" / "bonsaiviewer-autodesk" else: base = os.environ.get("XDG_CONFIG_HOME") or os.path.expanduser("~/.config") - root = Path(base) / "ifcviewer-autodesk" + root = Path(base) / "bonsaiviewer-autodesk" root.mkdir(parents=True, exist_ok=True) return root diff --git a/src/ifcviewer-autodesk/ifcviewer_autodesk/ui.py b/src/bonsaiviewer-autodesk/bonsaiviewer_autodesk/ui.py similarity index 97% rename from src/ifcviewer-autodesk/ifcviewer_autodesk/ui.py rename to src/bonsaiviewer-autodesk/bonsaiviewer_autodesk/ui.py index 1fabdcc23b..9138648ce4 100644 --- a/src/ifcviewer-autodesk/ifcviewer_autodesk/ui.py +++ b/src/bonsaiviewer-autodesk/bonsaiviewer_autodesk/ui.py @@ -6,12 +6,12 @@ from typing import TYPE_CHECKING, Any, Callable, Literal import customtkinter as ctk -from ifcviewer_autodesk import settings -from ifcviewer_autodesk.autodesk import ApsClient, AuthSessionService, KeyringTokenStore -from ifcviewer_autodesk.rpc import JSONRPC_INTERNAL_ERROR, RpcError +from bonsaiviewer_autodesk import settings +from bonsaiviewer_autodesk.autodesk import ApsClient, AuthSessionService, KeyringTokenStore +from bonsaiviewer_autodesk.rpc import JSONRPC_INTERNAL_ERROR, RpcError if TYPE_CHECKING: - from ifcviewer_autodesk.connector import AutodeskConnector + from bonsaiviewer_autodesk.connector import AutodeskConnector MODEL_EXTENSIONS = (".ifc", ".ifcview", ".rdb", ".rdbview") @@ -161,10 +161,17 @@ class _ProgressContext: def __exit__(self, *_exc: object) -> None: if self.dialog is not None: try: + self.dialog.withdraw() self.dialog.destroy() except tk.TclError: pass self.dialog = None + try: + root = ensure_tk_app() + root.update_idletasks() + root.update() + except tk.TclError: + pass def progress_dialog(message: str) -> _ProgressContext: @@ -629,7 +636,7 @@ class SettingsDialog(_BaseDialog): ): return try: - KeyringTokenStore(service_name="ifcviewer-autodesk", username=client_id).delete() + KeyringTokenStore(service_name="bonsaiviewer-autodesk", username=client_id).delete() except RpcError as exc: show_error(title="Sign Out Failed", message=exc.message) return diff --git a/src/ifcviewer-autodesk/connector.json b/src/bonsaiviewer-autodesk/connector.json similarity index 68% rename from src/ifcviewer-autodesk/connector.json rename to src/bonsaiviewer-autodesk/connector.json index 6246935b2d..7d52ed647f 100644 --- a/src/ifcviewer-autodesk/connector.json +++ b/src/bonsaiviewer-autodesk/connector.json @@ -2,5 +2,5 @@ "id": "autodesk", "name": "Autodesk Forma", "version": "0.1.0", - "exec": "ifcviewer-autodesk" + "exec": "bonsaiviewer-autodesk" } diff --git a/src/ifcviewer-autodesk/packaging/README.md b/src/bonsaiviewer-autodesk/packaging/README.md similarity index 91% rename from src/ifcviewer-autodesk/packaging/README.md rename to src/bonsaiviewer-autodesk/packaging/README.md index e0469c2ed1..d6849b9a58 100644 --- a/src/ifcviewer-autodesk/packaging/README.md +++ b/src/bonsaiviewer-autodesk/packaging/README.md @@ -1,7 +1,7 @@ # Packaging the Autodesk connector The connector is shipped as a self-contained folder ready to drop into the -IfcViewer connectors directory. PyInstaller bundles the Python interpreter, +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 — @@ -13,7 +13,7 @@ typically via a CI matrix. dist/ autodesk/ # the connector folder, ready to install connector.json - ifcviewer-autodesk[.exe] + bonsaiviewer-autodesk[.exe] _internal/... # PyInstaller dependencies (Qt, Python, …) autodesk--.zip # the distribution archive ``` @@ -24,7 +24,7 @@ The folder is what Bonsai Viewer expects under ## Build steps (any OS) ```bash -cd src/ifcviewer-autodesk +cd src/bonsaiviewer-autodesk python -m venv venv venv/bin/activate # or venv\Scripts\activate on Windows pip install -e ".[build]" @@ -34,7 +34,7 @@ python packaging/build.py The build: 1. cleans `dist/` and `build/` -2. runs PyInstaller against `packaging/ifcviewer-autodesk.spec` +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` @@ -72,7 +72,7 @@ builds do; on Gentoo make sure `USE="tk"` is set for `dev-lang/python`. 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 IfcViewer must launch the connector with + 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 @@ -98,5 +98,5 @@ Bonsai Viewer picks up the connector on next launch. - Signing / notarization (caller's responsibility per OS) - CI matrix (project-level concern) -- Auto-update (the IfcViewer or the host installer handles this) +- 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/ifcviewer-autodesk/packaging/ifcviewer-autodesk.spec b/src/bonsaiviewer-autodesk/packaging/bonsaiviewer-autodesk.spec similarity index 87% rename from src/ifcviewer-autodesk/packaging/ifcviewer-autodesk.spec rename to src/bonsaiviewer-autodesk/packaging/bonsaiviewer-autodesk.spec index 6e995f62e8..f221395444 100644 --- a/src/ifcviewer-autodesk/packaging/ifcviewer-autodesk.spec +++ b/src/bonsaiviewer-autodesk/packaging/bonsaiviewer-autodesk.spec @@ -1,7 +1,7 @@ -# PyInstaller spec for the IfcViewer Autodesk connector (Tk + CustomTkinter). +# PyInstaller spec for the Bonsai Viewer Autodesk connector (Tk + CustomTkinter). # # The connector talks JSON-RPC over stdio, so `console=True` is required to -# attach stdin/stdout on Windows. The IfcViewer is expected to spawn the +# attach stdin/stdout on Windows. The Bonsai Viewer is expected to spawn the # connector with the OS's "hide console window" flag on Windows # (CREATE_NO_WINDOW) so end users never see a console pop up. @@ -22,7 +22,7 @@ HIDDEN_IMPORTS = [ a = Analysis( - [str(PROJECT_ROOT / "ifcviewer_autodesk" / "__main__.py")], + [str(PROJECT_ROOT / "bonsaiviewer_autodesk" / "__main__.py")], pathex=[str(PROJECT_ROOT)], binaries=[], datas=[], @@ -56,7 +56,7 @@ exe = EXE( a.scripts, [], exclude_binaries=True, - name="ifcviewer-autodesk", + name="bonsaiviewer-autodesk", debug=False, bootloader_ignore_signals=False, strip=False, @@ -77,5 +77,5 @@ coll = COLLECT( strip=False, upx=True, upx_exclude=[], - name="ifcviewer-autodesk", + name="bonsaiviewer-autodesk", ) diff --git a/src/ifcviewer-autodesk/packaging/build.py b/src/bonsaiviewer-autodesk/packaging/build.py similarity index 90% rename from src/ifcviewer-autodesk/packaging/build.py rename to src/bonsaiviewer-autodesk/packaging/build.py index 61c3f13705..3b2b2457aa 100644 --- a/src/ifcviewer-autodesk/packaging/build.py +++ b/src/bonsaiviewer-autodesk/packaging/build.py @@ -1,12 +1,12 @@ """Build the Autodesk connector bundle for the current OS. Each OS builds on itself (PyInstaller does not cross-compile). The output is a -single zip ready to drop into the IfcViewer connectors directory: +single zip ready to drop into the Bonsai Viewer connectors directory: dist/autodesk--.zip autodesk/ connector.json - ifcviewer-autodesk[.exe] + bonsaiviewer-autodesk[.exe] _internal/... Usage: @@ -27,12 +27,12 @@ from pathlib import Path PROJECT_ROOT = Path(__file__).resolve().parent.parent PACKAGING_DIR = PROJECT_ROOT / "packaging" -SPEC_FILE = PACKAGING_DIR / "ifcviewer-autodesk.spec" +SPEC_FILE = PACKAGING_DIR / "bonsaiviewer-autodesk.spec" DIST_DIR = PROJECT_ROOT / "dist" BUILD_DIR = PROJECT_ROOT / "build" CONNECTOR_FOLDER_NAME = "autodesk" -PYINSTALLER_OUTPUT_NAME = "ifcviewer-autodesk" +PYINSTALLER_OUTPUT_NAME = "bonsaiviewer-autodesk" def _platform_tag() -> str: @@ -92,7 +92,7 @@ def _assemble_connector_folder(pyinstaller_output: Path) -> Path: # `pip install -e .` works for development. For the bundled folder, the # binary lives next to connector.json, so rewrite `exec` to a relative path. manifest = json.loads((PROJECT_ROOT / "connector.json").read_text(encoding="utf-8")) - binary_name = "ifcviewer-autodesk.exe" if platform.system() == "Windows" else "ifcviewer-autodesk" + binary_name = "bonsaiviewer-autodesk.exe" if platform.system() == "Windows" else "bonsaiviewer-autodesk" manifest["exec"] = f"./{binary_name}" (connector_dir / "connector.json").write_text(json.dumps(manifest, indent=2) + "\n", encoding="utf-8") diff --git a/src/ifcviewer-autodesk/pyproject.toml b/src/bonsaiviewer-autodesk/pyproject.toml similarity index 73% rename from src/ifcviewer-autodesk/pyproject.toml rename to src/bonsaiviewer-autodesk/pyproject.toml index 09753ce18c..dc0eac20c1 100644 --- a/src/ifcviewer-autodesk/pyproject.toml +++ b/src/bonsaiviewer-autodesk/pyproject.toml @@ -3,9 +3,9 @@ requires = ["setuptools>=69", "wheel"] build-backend = "setuptools.build_meta" [project] -name = "ifcviewer-autodesk" +name = "bonsaiviewer-autodesk" version = "0.1.0" -description = "Autodesk cloud connector for IfcViewer" +description = "Autodesk cloud connector for Bonsai Viewer" readme = "README.md" requires-python = ">=3.11" dependencies = [ @@ -24,4 +24,4 @@ include-package-data = true where = ["."] [project.scripts] -ifcviewer-autodesk = "ifcviewer_autodesk.__main__:main" +bonsaiviewer-autodesk = "bonsaiviewer_autodesk.__main__:main"