trame-vtkjs 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. trame_vtkjs-0.1.0/.gitignore +42 -0
  2. trame_vtkjs-0.1.0/CONTRIBUTING.md +11 -0
  3. trame_vtkjs-0.1.0/LICENSE +15 -0
  4. trame_vtkjs-0.1.0/PKG-INFO +76 -0
  5. trame_vtkjs-0.1.0/README.md +44 -0
  6. trame_vtkjs-0.1.0/docs/api.md +114 -0
  7. trame_vtkjs-0.1.0/docs/development.md +70 -0
  8. trame_vtkjs-0.1.0/docs/releases.md +74 -0
  9. trame_vtkjs-0.1.0/hatch_build.py +89 -0
  10. trame_vtkjs-0.1.0/pyproject.toml +165 -0
  11. trame_vtkjs-0.1.0/src/trame/__init__.py +1 -0
  12. trame_vtkjs-0.1.0/src/trame/modules/__init__.py +1 -0
  13. trame_vtkjs-0.1.0/src/trame/modules/vtkjs.py +1 -0
  14. trame_vtkjs-0.1.0/src/trame/widgets/__init__.py +1 -0
  15. trame_vtkjs-0.1.0/src/trame/widgets/vtkjs.py +9 -0
  16. trame_vtkjs-0.1.0/src/trame_vtkjs/__init__.py +39 -0
  17. trame_vtkjs-0.1.0/src/trame_vtkjs/host_types.py +43 -0
  18. trame_vtkjs-0.1.0/src/trame_vtkjs/interaction.py +122 -0
  19. trame_vtkjs-0.1.0/src/trame_vtkjs/module/__init__.py +47 -0
  20. trame_vtkjs-0.1.0/src/trame_vtkjs/module/array_datatypes.py +98 -0
  21. trame_vtkjs-0.1.0/src/trame_vtkjs/module/coincident_topology.py +40 -0
  22. trame_vtkjs-0.1.0/src/trame_vtkjs/module/feature_blocks.py +89 -0
  23. trame_vtkjs-0.1.0/src/trame_vtkjs/module/node_arrays.py +338 -0
  24. trame_vtkjs-0.1.0/src/trame_vtkjs/module/node_translator.py +400 -0
  25. trame_vtkjs-0.1.0/src/trame_vtkjs/module/point_cloud_presentation.py +44 -0
  26. trame_vtkjs-0.1.0/src/trame_vtkjs/module/point_gaussian.py +30 -0
  27. trame_vtkjs-0.1.0/src/trame_vtkjs/module/protocol.py +60 -0
  28. trame_vtkjs-0.1.0/src/trame_vtkjs/module/push_views.py +135 -0
  29. trame_vtkjs-0.1.0/src/trame_vtkjs/module/serve/js/build-info.json +31 -0
  30. trame_vtkjs-0.1.0/src/trame_vtkjs/module/serve/js/tiles3dDecodeWorker.classic.js +13 -0
  31. trame_vtkjs-0.1.0/src/trame_vtkjs/module/serve/js/trame_vtkjs.umd.js +1112 -0
  32. trame_vtkjs-0.1.0/src/trame_vtkjs/module/serve/wasm/tiles3d/basis_encoder.js +21 -0
  33. trame_vtkjs-0.1.0/src/trame_vtkjs/module/serve/wasm/tiles3d/basis_encoder.wasm +0 -0
  34. trame_vtkjs-0.1.0/src/trame_vtkjs/module/serve/wasm/tiles3d/draco_decoder.wasm +0 -0
  35. trame_vtkjs-0.1.0/src/trame_vtkjs/module/serve/wasm/tiles3d/draco_wasm_wrapper.js +117 -0
  36. trame_vtkjs-0.1.0/src/trame_vtkjs/module/state_cache.py +106 -0
  37. trame_vtkjs-0.1.0/src/trame_vtkjs/module/streamed_scene_translation.py +36 -0
  38. trame_vtkjs-0.1.0/src/trame_vtkjs/module/validation.py +14 -0
  39. trame_vtkjs-0.1.0/src/trame_vtkjs/module/vtkjs_translator.py +342 -0
  40. trame_vtkjs-0.1.0/src/trame_vtkjs/projected_texture.py +121 -0
  41. trame_vtkjs-0.1.0/src/trame_vtkjs/py.typed +0 -0
  42. trame_vtkjs-0.1.0/src/trame_vtkjs/screen_size_glyphs.py +61 -0
  43. trame_vtkjs-0.1.0/src/trame_vtkjs/store.py +511 -0
  44. trame_vtkjs-0.1.0/src/trame_vtkjs/streamed_scene.py +384 -0
  45. trame_vtkjs-0.1.0/src/trame_vtkjs/streamed_scene_blocks.py +88 -0
  46. trame_vtkjs-0.1.0/src/trame_vtkjs/view_types.py +8 -0
  47. trame_vtkjs-0.1.0/src/trame_vtkjs/widgets/__init__.py +4 -0
  48. trame_vtkjs-0.1.0/src/trame_vtkjs/widgets/blob_payloads.py +114 -0
  49. trame_vtkjs-0.1.0/src/trame_vtkjs/widgets/dependency_graph.py +148 -0
  50. trame_vtkjs-0.1.0/src/trame_vtkjs/widgets/dirty_batch.py +34 -0
  51. trame_vtkjs-0.1.0/src/trame_vtkjs/widgets/dirty_tracker.py +324 -0
  52. trame_vtkjs-0.1.0/src/trame_vtkjs/widgets/hot_array_batch.py +83 -0
  53. trame_vtkjs-0.1.0/src/trame_vtkjs/widgets/hot_arrays.py +348 -0
  54. trame_vtkjs-0.1.0/src/trame_vtkjs/widgets/publisher.py +494 -0
  55. trame_vtkjs-0.1.0/src/trame_vtkjs/widgets/scene_events.py +33 -0
  56. trame_vtkjs-0.1.0/src/trame_vtkjs/widgets/vtk_dependencies.py +137 -0
  57. trame_vtkjs-0.1.0/src/trame_vtkjs/widgets/vtkjs_base.py +263 -0
  58. trame_vtkjs-0.1.0/src/trame_vtkjs/widgets/vtkjs_shared_view.py +11 -0
  59. trame_vtkjs-0.1.0/src/trame_vtkjs/widgets/vtkjs_view.py +41 -0
  60. trame_vtkjs-0.1.0/src/trame_vtkjs/wire.py +86 -0
@@ -0,0 +1,42 @@
1
+ .DS_Store
2
+ build
3
+ node_modules
4
+ .venv
5
+ .venv-pv
6
+ .pyvista
7
+ /.vite/
8
+ /dist/
9
+
10
+ bike.vtp
11
+ seeds.jpg
12
+ tunnel.vtu
13
+
14
+ .coverage
15
+ dev
16
+ .dev-seb.md
17
+ visual_baseline/**/*.txt
18
+ visual_baseline/**/baseline.png
19
+ visual_baseline/**/baseline_diff_*
20
+
21
+ # local env files
22
+ .env.local
23
+ .env.*.local
24
+
25
+ # Log files
26
+ npm-debug.log*
27
+ yarn-debug.log*
28
+ yarn-error.log*
29
+ pnpm-debug.log*
30
+
31
+ # Editor directories and files
32
+ .idea
33
+ .vscode
34
+ *.suo
35
+ *.ntvs*
36
+ *.njsproj
37
+ *.sln
38
+ *.sw?
39
+
40
+ __pycache__
41
+ *egg-info
42
+ *pyc
@@ -0,0 +1,11 @@
1
+ # Contributing
2
+
3
+ Create a branch for your change and follow [build and test](docs/development.md).
4
+ Install the configured checks with `python -m pip install pre-commit`, then run
5
+ `pre-commit install` and `pre-commit run --all-files`.
6
+
7
+ Use Conventional Commits, such as `feat: add a view option` or
8
+ `fix: repair scene recovery`. CI checks the incoming messages, and release-causing
9
+ commits select versions when merged to `main`. See [release rules](docs/releases.md).
10
+
11
+ Open a pull request with a concrete description of the behavior being changed.
@@ -0,0 +1,15 @@
1
+ Apache Software License 2.0
2
+
3
+ Copyright (c) 2024, Kitware Inc.
4
+
5
+ Licensed under the Apache License, Version 2.0 (the "License");
6
+ you may not use this file except in compliance with the License.
7
+ You may obtain a copy of the License at
8
+
9
+ http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ Unless required by applicable law or agreed to in writing, software
12
+ distributed under the License is distributed on an "AS IS" BASIS,
13
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ See the License for the specific language governing permissions and
15
+ limitations under the License.
@@ -0,0 +1,76 @@
1
+ Metadata-Version: 2.5
2
+ Name: trame-vtkjs
3
+ Version: 0.1.0
4
+ Summary: vtk.js scene replication and shared-context rendering for trame
5
+ Project-URL: Repository, https://github.com/PaulHax/trame-vtkjs
6
+ Project-URL: Issues, https://github.com/PaulHax/trame-vtkjs/issues
7
+ Author: Kitware Inc., Paul Elliott
8
+ License: Apache Software License
9
+ License-File: LICENSE
10
+ Keywords: Application,Framework,Interactive,Python,Web
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Environment :: Web Environment
13
+ Classifier: License :: OSI Approved :: Apache Software License
14
+ Classifier: Natural Language :: English
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
+ Requires-Python: >=3.9
20
+ Requires-Dist: trame
21
+ Requires-Dist: trame-common
22
+ Provides-Extra: dev
23
+ Requires-Dist: mypy; extra == 'dev'
24
+ Requires-Dist: pre-commit; extra == 'dev'
25
+ Requires-Dist: pytest; extra == 'dev'
26
+ Requires-Dist: pytest-asyncio; extra == 'dev'
27
+ Requires-Dist: ruff; extra == 'dev'
28
+ Requires-Dist: trame-client[test]; extra == 'dev'
29
+ Provides-Extra: vtk
30
+ Requires-Dist: vtk<9.7,>=9.6; extra == 'vtk'
31
+ Description-Content-Type: text/markdown
32
+
33
+ # trame-vtkjs
34
+
35
+ Render Python VTK scenes in the browser with vtk.js. The Python server and
36
+ browser client ship together in one wheel, with scene updates, picking,
37
+ streaming, and recovery across connected clients.
38
+
39
+ | View | Rendering context |
40
+ | --- | --- |
41
+ | `VtkJsLocalView` | Own canvas and camera controls |
42
+ | `VtkJsSharedView` | Host-owned WebGL context, including MapLibre |
43
+
44
+ ## Install and try it
45
+
46
+ Install a wheel built from this repository or downloaded from its releases:
47
+
48
+ ```sh
49
+ python -m pip install "./dist/trame_vtkjs-<version>-py3-none-any.whl[vtk]"
50
+ ```
51
+
52
+ The `vtk` extra installs the supported VTK 9.6 series. The Python namespace is
53
+ `trame_vtkjs`; application widgets are available through trame:
54
+
55
+ ```python
56
+ from trame.widgets.vtkjs import VtkJsLocalView, VtkJsSharedView
57
+ ```
58
+
59
+ From the checkout, run the interactive cone example:
60
+
61
+ ```sh
62
+ python examples/vtk/vtkjs_cone.py
63
+ ```
64
+
65
+ The [MapLibre example](https://github.com/PaulHax/trame-vtkjs/blob/main/examples/vtk/maplibre_demo/README.md) combines shared
66
+ rendering, textures, editable landmarks, and streamed geometry.
67
+
68
+ ## Documentation
69
+
70
+ - [API and rendering contracts](https://github.com/PaulHax/trame-vtkjs/blob/main/docs/api.md)
71
+ - [Build and test](https://github.com/PaulHax/trame-vtkjs/blob/main/docs/development.md)
72
+ - [Releases and publisher setup](https://github.com/PaulHax/trame-vtkjs/blob/main/docs/releases.md)
73
+ - [Contributing](https://github.com/PaulHax/trame-vtkjs/blob/main/CONTRIBUTING.md)
74
+
75
+ Licensed under [Apache 2.0](https://github.com/PaulHax/trame-vtkjs/blob/main/LICENSE), retaining Kitware's copyright and
76
+ attribution. Maintained by Paul Elliott.
@@ -0,0 +1,44 @@
1
+ # trame-vtkjs
2
+
3
+ Render Python VTK scenes in the browser with vtk.js. The Python server and
4
+ browser client ship together in one wheel, with scene updates, picking,
5
+ streaming, and recovery across connected clients.
6
+
7
+ | View | Rendering context |
8
+ | --- | --- |
9
+ | `VtkJsLocalView` | Own canvas and camera controls |
10
+ | `VtkJsSharedView` | Host-owned WebGL context, including MapLibre |
11
+
12
+ ## Install and try it
13
+
14
+ Install a wheel built from this repository or downloaded from its releases:
15
+
16
+ ```sh
17
+ python -m pip install "./dist/trame_vtkjs-<version>-py3-none-any.whl[vtk]"
18
+ ```
19
+
20
+ The `vtk` extra installs the supported VTK 9.6 series. The Python namespace is
21
+ `trame_vtkjs`; application widgets are available through trame:
22
+
23
+ ```python
24
+ from trame.widgets.vtkjs import VtkJsLocalView, VtkJsSharedView
25
+ ```
26
+
27
+ From the checkout, run the interactive cone example:
28
+
29
+ ```sh
30
+ python examples/vtk/vtkjs_cone.py
31
+ ```
32
+
33
+ The [MapLibre example](https://github.com/PaulHax/trame-vtkjs/blob/main/examples/vtk/maplibre_demo/README.md) combines shared
34
+ rendering, textures, editable landmarks, and streamed geometry.
35
+
36
+ ## Documentation
37
+
38
+ - [API and rendering contracts](https://github.com/PaulHax/trame-vtkjs/blob/main/docs/api.md)
39
+ - [Build and test](https://github.com/PaulHax/trame-vtkjs/blob/main/docs/development.md)
40
+ - [Releases and publisher setup](https://github.com/PaulHax/trame-vtkjs/blob/main/docs/releases.md)
41
+ - [Contributing](https://github.com/PaulHax/trame-vtkjs/blob/main/CONTRIBUTING.md)
42
+
43
+ Licensed under [Apache 2.0](https://github.com/PaulHax/trame-vtkjs/blob/main/LICENSE), retaining Kitware's copyright and
44
+ attribution. Maintained by Paul Elliott.
@@ -0,0 +1,114 @@
1
+ # API and rendering contracts
2
+
3
+ ## Python imports
4
+
5
+ ```python
6
+ from trame.widgets.vtkjs import VtkJsLocalView, VtkJsSharedView
7
+ from trame_vtkjs import (
8
+ PointCloudSource, StreamedSceneActor, Tiles3DSource,
9
+ Tiles3DQualityPolicy, Tiles3DTexturePolicy,
10
+ )
11
+ from trame_vtkjs.interaction import make_pickable, clear_pickable, PickPreview
12
+ from trame_vtkjs.projected_texture import (
13
+ MODE_HOMOGRAPHY, MODE_WORLD_TO_CLIP,
14
+ mark_projected_texture, set_projected_texture_matrix,
15
+ )
16
+ from trame_vtkjs.screen_size_glyphs import mark_screen_size_glyphs
17
+ from trame_vtkjs.streamed_scene import DEFAULT_ADAPTIVE_MIN_BUDGET, source_block
18
+ from trame_vtkjs.streamed_scene_blocks import AdaptiveOptions, Presentation
19
+ ```
20
+
21
+ The helper modules also expose configuration types and readers.
22
+ `streamed_scene_blocks` describes the JSON feature blocks; `view_types` contains
23
+ view policy types. Application code should use these supported modules and
24
+ widgets rather than importing from the view implementation.
25
+
26
+ ## Views and browser access
27
+
28
+ `VtkJsLocalView` renders on its own canvas. `VtkJsSharedView` renders in a
29
+ host-owned WebGL context; the host controls its canvas, camera, and interaction.
30
+ Both accept a Python `vtkRenderWindow`.
31
+
32
+ The Vue plugin `trame_vtkjs` exports `install`, `VtkJsLocal`, and `VtkJsShared`.
33
+ Their element names are `vtk-js-local` and `vtk-js-shared`. Browser hosts resolve
34
+ mounted views through `window.trameVtkjs.getView(key)` or `whenView(key)`, and
35
+ access the shared matrix helpers through `window.trameVtkjs.glMatrix`.
36
+ `trame.modules.vtkjs` exposes module registration and serves the browser runtime
37
+ under `__trame_vtkjs`. Scene replication uses protocol v2 with `scene.ops` and
38
+ `scene.resync`.
39
+
40
+ ## Publish and recover
41
+
42
+ Use `view.transaction()` to publish batched VTK mutations and commands together
43
+ when the context exits. Use `view.sync()` to flush other pending changes.
44
+ Replaced datasets, properties, transforms, and pipeline
45
+ connections are tracked automatically. Publication also checks pipeline and
46
+ transform MTimes because some VTK operations omit `ModifiedEvent`.
47
+
48
+ After bypassing VTK modification notifications, call `view.recover()` to scan
49
+ MTimes and publish missed changes. Browser resynchronization performs recovery
50
+ before returning a snapshot. A serialization failure preserves pending changes
51
+ and command order for a later retry.
52
+
53
+ ## Streaming policies
54
+
55
+ Both views accept these construction options:
56
+
57
+ | Option | Default | Alternatives |
58
+ | --- | --- | --- |
59
+ | `tiles3d_texture_policy` | `"auto"` | `"native"`, `"rgba"` |
60
+ | `tiles3d_quality_policy` | `"adaptive"` | `"fixed"` |
61
+ | `streamed_memory_budget_bytes` | Browser-derived | A page-wide byte allowance |
62
+
63
+ Automatic texture policy chooses native compression, except for software
64
+ renderers and headless browsers, which use RGBA. Views on one page share a memory
65
+ allowance and must receive the same value. It covers resident streamed geometry
66
+ and textures, not total browser memory or reserved GPU memory. Changing it
67
+ requires a page reload.
68
+
69
+ ## External-resource gates
70
+
71
+ Shared views expose `registerSceneGate(hold)` and `retrySceneGate()`.
72
+ `hold(message)` receives an in-order scene message and returns `true` to delay
73
+ it, for example until matching video pixels arrive. A later message that is not
74
+ held first releases the queued messages, then applies itself. Global sequence
75
+ order is preserved, even if the held resources are still missing.
76
+ Call `retrySceneGate()` when the
77
+ resource arrives. A held message applies after three seconds even if its gate
78
+ still holds it; request missing resources before that deadline. Snapshots are
79
+ never held. Disposing a registration releases its held messages.
80
+
81
+ ## Picking and drag acknowledgement
82
+
83
+ Gesture `pointer_event` camera matrices and `setRenderedCamera` use column-major
84
+ layout in both views. Drag start, move, and end events share an opaque
85
+ `gesture_id`. The browser keeps its final preview until the originating view
86
+ acknowledges the release after publishing the final scene mutations:
87
+
88
+ ```python
89
+ view.send_command("pointer.drag.end", {"gesture_id": event["gesture_id"]})
90
+ ```
91
+
92
+ Send this command even for unchanged, cancelled, or rejected drops, using
93
+ `finally` when a handler can exit early. Do not retain it. Async handlers must
94
+ await scene mutations before acknowledging. The ordered acknowledgement makes
95
+ the server position authoritative.
96
+
97
+ For points that move between render buckets, use the same nonempty
98
+ `preview_group` in `make_pickable` for every bucket in a layer. IDs must be unique
99
+ within the group, using one coordinate system and preview mode. Give independent
100
+ layers distinct groups. Previews follow `(preview_group, point_id)` across
101
+ buckets; duplicate IDs or removed targets stop them safely. Points keep
102
+ independent pending previews, and a regrab transfers its point to the new gesture.
103
+
104
+ ## Point mapper limits
105
+
106
+ For direct `vtkPointGaussianMapper` scenes, explicitly call
107
+ `mapper.SetScaleFactor(0)` to select simple points. Nonzero scale factors,
108
+ anisotropy, Gaussian arrays/functions, rotations, and custom splat shaders raise
109
+ translation errors. `Emissive` and `BoundScale` are not forwarded in this mode.
110
+
111
+ Point size comes from the actor property. The direct-cloud presentation block
112
+ uses vtk.js `pointSizeScale` for CSS pixels without changing `scaleFactor`.
113
+ Streamed scenes construct their mappers in the browser and control the resident
114
+ draw prefix there.
@@ -0,0 +1,70 @@
1
+ # Build and test
2
+
3
+ Use Python 3.10 and Node.js 24 for development, matching CI. Work from the
4
+ repository root with npm available. The browser
5
+ bundle needs the exact vtk.js commit in [`vtkjs-fork.env`](../vtkjs-fork.env).
6
+
7
+ ## Build the renderer and browser client
8
+
9
+ ```sh
10
+ bridge_root="$PWD"
11
+ source ./vtkjs-fork.env
12
+ vtkjs_build="$(dirname "$bridge_root")/vtk-js-build"
13
+ git clone "$VTKJS_FORK_REPO" "$vtkjs_build"
14
+ git -C "$vtkjs_build" checkout --detach "$VTKJS_FORK_COMMIT"
15
+ (
16
+ cd "$vtkjs_build"
17
+ npm ci
18
+ npm run build:esm
19
+ cd dist/esm
20
+ npm link
21
+ )
22
+ (
23
+ cd "$bridge_root/vue-components"
24
+ npm ci
25
+ npm link @kitware/vtk.js
26
+ npm run build
27
+ )
28
+ ```
29
+
30
+ Use an existing clean renderer checkout at the pinned commit if one is available.
31
+ The `tiled-vtkjs` dependency must come from its integrity-checked lockfile,
32
+ not a linked development tree. Rebuild the browser client after changing its
33
+ source; Python serves the built files.
34
+
35
+ ## Install development dependencies
36
+
37
+ ```sh
38
+ python -m venv .venv
39
+ source .venv/bin/activate
40
+ python -m pip install -e ".[vtk,dev]" -r tests/requirements.txt
41
+ ```
42
+
43
+ ## Run checks
44
+
45
+ ```sh
46
+ (cd vue-components && npm test)
47
+ python -m pytest -m "not js_oracle" \
48
+ --ignore=tests/push_oracle_e2e --ignore=tests/test_shared_gl_context.py
49
+ python -m playwright install chromium
50
+ python -m pytest tests/test_shared_gl_context.py tests/push_oracle_e2e \
51
+ tests/test_module_assets_e2e.py --browser chromium
52
+ ```
53
+
54
+ Browser checks run headless by default. MapLibre checks additionally need local
55
+ vendor files; see the [example guide](../examples/vtk/maplibre_demo/README.md).
56
+ Headless runs establish correctness, not hardware performance.
57
+
58
+ ## Build distributable artifacts
59
+
60
+ From committed source, with the renderer link available:
61
+
62
+ ```sh
63
+ ./release.sh build
64
+ ./release.sh verify
65
+ ```
66
+
67
+ The script installs the locked browser dependencies, rebuilds the served client
68
+ and streaming codecs, and verifies wheel/sdist contents and dependency revisions.
69
+ Artifacts and evidence go in `dist/`. These commands publish nothing. See
70
+ [releases](releases.md) for public versions and publisher setup.
@@ -0,0 +1,74 @@
1
+ # Releases
2
+
3
+ The [workflow](../.github/workflows/release.yml) checks incoming commits, builds
4
+ and tests distributions, then publishes release-causing `main` changes to GitHub
5
+ Releases and PyPI. It uses Python Semantic Release's parser and version algorithm.
6
+
7
+ ## Commit rules
8
+
9
+ | Commit | Version change |
10
+ | --- | --- |
11
+ | `feat: ...` | Minor |
12
+ | `fix: ...`, `perf: ...` | Patch |
13
+ | `type!: ...` or a `BREAKING CHANGE:` footer | Minor before 1.0; major thereafter |
14
+ | Ordinary docs, maintenance, tests, refactors | No release |
15
+
16
+ CI validates every incoming non-merge commit. Prefer rebase merging to preserve
17
+ the validated messages; squash merges also need a valid final message. To check
18
+ one locally, install `requirements-release.txt` and run:
19
+
20
+ ```sh
21
+ python release_plan.py lint --message-file PATH
22
+ ```
23
+
24
+ The first product version is `0.1.0`; tags use `trame-vtkjs-vX.Y.Z`. The baseline
25
+ in `pyproject.toml` excludes inherited history from initial message validation
26
+ and version calculation. No generated version/changelog commit changes the
27
+ source revision after testing.
28
+
29
+ ## Publisher setup
30
+
31
+ Keep Actions disabled while seeding the new repository. Its initial `main` must
32
+ already contain the exact `baseline_commit` configured in `pyproject.toml`,
33
+ including the identity rename and its shipping ancestry. Open the release/docs
34
+ PR from that baseline, and rebase only the commits after it. Rebase-merging the
35
+ entire migration from an earlier shipping commit would rewrite the required
36
+ baseline and prevent release planning. The initial seed therefore includes the
37
+ identity rename; review it before authorizing that push. Preserve the old
38
+ repository's tags/assets for existing wheel URLs. Enable Actions only when
39
+ the release-capable merge is intended to publish.
40
+
41
+ Register a PyPI [pending trusted publisher](https://docs.pypi.org/trusted-publishers/creating-a-project-through-oidc/)
42
+ or update an existing project's publisher with these fields:
43
+
44
+ | Field | Value |
45
+ | --- | --- |
46
+ | PyPI project | `trame-vtkjs` |
47
+ | GitHub owner / repository | `PaulHax` / `trame-vtkjs` |
48
+ | Workflow filename | `release.yml` |
49
+ | Environment | `pypi` |
50
+
51
+ Create the matching GitHub environment, allow `main` releases, and configure
52
+ branch protection and merge settings. Build jobs have read-only repository
53
+ access. Only the GitHub release job has `contents: write`; only the separate
54
+ PyPI job has `id-token: write`. No PyPI token is stored in the repository.
55
+
56
+ ## Artifacts and recovery
57
+
58
+ A source-bound plan selects the public version before building. CI verifies
59
+ that the wheel and sdist contain identical runtime files, installs/tests the
60
+ wheel, and transfers those artifacts to publication jobs. Those jobs do not
61
+ rebuild source. Evidence records source/tree, upstream pins, and artifact hashes.
62
+
63
+ Existing GitHub assets and PyPI digests must match the tested bytes. Exact-byte
64
+ reruns stage only missing distributions; conflicts require a new source change
65
+ and version. Rerun failed publication jobs with their retained artifacts to
66
+ complete a partial release. Main releases are serialized, and stale plans stop
67
+ publication. Registry races fail safely and are checked again on a rerun.
68
+
69
+ Local builds use `0.1.0+git.<sha>`. A public build receives
70
+ `TRAME_VTKJS_RELEASE_VERSION=X.Y.Z` from `release_plan.py`; its sdist preserves
71
+ that version and built browser assets without requiring Git or npm at install.
72
+ See [build and test](development.md) for non-publishing commands. Release-tool
73
+ updates require contract tests and workflow validation. Full MapLibre/recovery
74
+ qualification remains separate from CI's headless shared-GL/codec smoke gate.
@@ -0,0 +1,89 @@
1
+ """Resolve public release, source distribution and development versions.
2
+
3
+ Release builds provide ``TRAME_VTKJS_RELEASE_VERSION`` as a public ``X.Y.Z``
4
+ version. Source distributions retain that version in ``PKG-INFO`` so installing
5
+ them requires neither Git nor a release environment. Development checkouts use
6
+ ``<BASE_VERSION>+git.<short-sha>``. Exact source provenance lives in the bundled
7
+ build metadata for both public and development wheels.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import os
13
+ import re
14
+ import subprocess
15
+ from email.parser import Parser
16
+ from pathlib import Path
17
+
18
+ from hatchling.metadata.plugin.interface import MetadataHookInterface
19
+ from packaging.utils import canonicalize_name
20
+ from packaging.version import InvalidVersion, Version
21
+
22
+ # Development versions retain a readable base independently of release tags.
23
+ BASE_VERSION = "0.1.0"
24
+ LOCAL_LABEL = "git"
25
+ PUBLIC_VERSION = re.compile(r"(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)")
26
+
27
+
28
+ def _release_version() -> str | None:
29
+ version = os.environ.get("TRAME_VTKJS_RELEASE_VERSION")
30
+ if version is not None and not PUBLIC_VERSION.fullmatch(version):
31
+ raise ValueError("TRAME_VTKJS_RELEASE_VERSION must be a public X.Y.Z version")
32
+ return version
33
+
34
+
35
+ def _sdist_version(root: str) -> str | None:
36
+ path = Path(root) / "PKG-INFO"
37
+ if not path.is_file():
38
+ return None
39
+ metadata = Parser().parsestr(path.read_text(encoding="utf-8"))
40
+ if canonicalize_name(metadata.get("Name", "")) != "trame-vtkjs":
41
+ raise ValueError("PKG-INFO does not identify the trame-vtkjs distribution")
42
+ version = metadata.get("Version", "")
43
+ try:
44
+ return str(Version(version))
45
+ except InvalidVersion as error:
46
+ raise ValueError("PKG-INFO must contain a valid package version") from error
47
+
48
+
49
+ def _short_sha(root: str) -> str | None:
50
+ sha = os.environ.get("TRAME_VTKJS_SHA")
51
+ if sha:
52
+ return sha.strip()
53
+ try:
54
+ toplevel = subprocess.check_output(
55
+ ["git", "rev-parse", "--show-toplevel"],
56
+ cwd=root,
57
+ stderr=subprocess.DEVNULL,
58
+ )
59
+ if Path(toplevel.decode().strip()).resolve() != Path(root).resolve():
60
+ return None
61
+ out = subprocess.check_output(
62
+ ["git", "rev-parse", "--short", "HEAD"],
63
+ cwd=root,
64
+ stderr=subprocess.DEVNULL,
65
+ )
66
+ except (OSError, subprocess.CalledProcessError):
67
+ return None
68
+ return out.decode().strip() or None
69
+
70
+
71
+ def _stamped_version(root: str) -> str:
72
+ release = _release_version()
73
+ if release is not None:
74
+ return release
75
+ archived = _sdist_version(root)
76
+ if archived is not None:
77
+ return archived
78
+ sha = _short_sha(root)
79
+ if not sha:
80
+ raise ValueError(
81
+ "Package version requires a Git checkout, sdist PKG-INFO, or "
82
+ "TRAME_VTKJS_RELEASE_VERSION"
83
+ )
84
+ return f"{BASE_VERSION}+{LOCAL_LABEL}.{sha}"
85
+
86
+
87
+ class ShaVersionMetadataHook(MetadataHookInterface):
88
+ def update(self, metadata: dict) -> None:
89
+ metadata["version"] = _stamped_version(self.root)