diff --git a/.github/scene-candidates/core-requirements.txt b/.github/scene-candidates/core-requirements.txt new file mode 100644 index 0000000..2f6e9fa --- /dev/null +++ b/.github/scene-candidates/core-requirements.txt @@ -0,0 +1 @@ +compas @ git+https://github.com/compas-dev/compas.git@d43db206afec36450efbe76b53306ce3bd468076 diff --git a/.github/scene-candidates/test_scene_integration.py b/.github/scene-candidates/test_scene_integration.py new file mode 100644 index 0000000..2873d6a --- /dev/null +++ b/.github/scene-candidates/test_scene_integration.py @@ -0,0 +1,141 @@ +import json +import os +import subprocess +import sys +from importlib.metadata import distribution + +import pytest +from compas.data import json_dumps +from compas.data import json_loads +from compas.geometry import Box +from compas.geometry import Translation +from compas.scene import Scene +from compas.scene import SceneItemNotBoundError +from compas.scene import SceneObject +from compas.scene import ShapeObject +from compas.scene import register_scene_operation +from compas_viewer.scene import BufferGeometry +from compas_viewer.scene import ViewerScene + +CORE_COMMIT = "d43db206afec36450efbe76b53306ce3bd468076" +VIEWER_COMMIT = "a72b61b777bc3fd448c3d471c48ef7f65334b319" + + +def _installed_commit(distribution_name: str) -> str: + direct_url = distribution(distribution_name).read_text("direct_url.json") + assert direct_url is not None + metadata = json.loads(direct_url) + return metadata["vcs_info"]["commit_id"] + + +def test_exact_source_candidates_are_installed(): + if os.environ.get("COMPAS_SCENE_SOURCE_CHECKOUTS") == "1": + pytest.skip("Direct-source local check; exact installed revisions are verified by framework CI.") + assert _installed_commit("compas") == CORE_COMMIT + assert _installed_commit("compas_viewer") == VIEWER_COMMIT + + +def test_plugin_discovery_does_not_import_graphics_modules(): + code = """ +import sys + +from compas.plugins import PluginManager + +manager = PluginManager() +manager.load_plugins() +modules = tuple(sys.modules) +assert "compas_viewer.plugins" in modules +assert not any(name == "PySide6" or name.startswith("PySide6.") for name in modules) +assert not any(name == "OpenGL" or name.startswith("OpenGL.") for name in modules) +""" + environment = os.environ.copy() + if os.environ.get("COMPAS_SCENE_SOURCE_CHECKOUTS") != "1": + environment.pop("PYTHONPATH", None) + subprocess.run([sys.executable, "-c", code], check=True, env=environment) + + +def test_indexed_buffer_geometry_stays_indexed_and_roundtrips(): + geometry = BufferGeometry.from_vertices_and_triangles( + [[0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0]], + [[0, 1, 2], [1, 3, 2]], + ) + + restored = json_loads(json_dumps(geometry)) + + assert isinstance(restored, BufferGeometry) + assert restored.vertices.shape == (4, 3) + assert restored.triangles is not None + assert restored.triangles.shape == (2, 3) + assert restored.triangles.tolist() == [[0, 1, 2], [1, 3, 2]] + + +def test_stable_scene_roundtrip_operations_and_explicit_binding(): + box = Box(1.0) + transformation = Translation.from_vector([2.0, 0.0, 0.0]) + scene = ViewerScene() + original = scene.add( + box, + name="Box", + is_selected=True, + show=False, + transformation=transformation, + u=7, + v=9, + ) + + assert type(original) is ShapeObject + assert original.node.object_guid == original.guid + assert original.node.__data__ == {"object": str(original.guid), "children": []} + + calls = [] + runtime = object() + + def draw(scene, sceneobject, active_runtime): + calls.append(("draw", sceneobject, active_runtime)) + return ["opaque-handle"] + + def clear(scene, sceneobject, active_runtime, resources): + calls.append(("clear", sceneobject, active_runtime, resources)) + + register_scene_operation("FrameworkIntegration", SceneObject, "draw", draw) + register_scene_operation("FrameworkIntegration", SceneObject, "clear", clear) + original.show = True + assert original.draw(context="FrameworkIntegration", runtime=runtime) == ["opaque-handle"] + assert scene.object(original.guid) is original + assert type(original) is ShapeObject + + payload = scene.to_jsonstring() + assert str(box.guid) in payload + assert "compas.geometry/Box" not in payload + assert "opaque-handle" not in payload + assert "FrameworkIntegration" not in payload + assert "is_selected" not in payload + assert "render_objects" not in payload + assert "instance_colors" not in payload + + restored_scene = Scene.from_jsonstring(payload) + restored_box = Box.from_jsonstring(box.to_jsonstring()) + assert isinstance(restored_scene, ViewerScene) + assert restored_box.guid == box.guid + assert restored_box.volume() == box.volume() + + restored = restored_scene.find_by_name("Box") + assert type(restored) is ShapeObject + assert restored.guid == original.guid + assert restored.item_guid == restored_box.guid + assert restored.transformation == transformation + assert restored.show is True + assert (restored.u, restored.v) == (7, 9) + assert restored_scene.item_store == {} + assert not restored_scene.is_selected(restored) + with pytest.raises(SceneItemNotBoundError, match=str(restored_box.guid)): + _ = restored.item + + restored_scene.bind(restored_box) + assert restored.item is restored_box + original.clear(context="FrameworkIntegration") + assert calls == [ + ("draw", original, runtime), + ("clear", original, runtime, ("opaque-handle",)), + ] + assert restored_scene.instance_colors == {} diff --git a/.github/scene-candidates/viewer-requirements.txt b/.github/scene-candidates/viewer-requirements.txt new file mode 100644 index 0000000..66a6b95 --- /dev/null +++ b/.github/scene-candidates/viewer-requirements.txt @@ -0,0 +1 @@ +compas_viewer @ git+https://github.com/compas-dev/compas_viewer.git@a72b61b777bc3fd448c3d471c48ef7f65334b319 diff --git a/.github/workflows/scene-candidate-integration.yml b/.github/workflows/scene-candidate-integration.yml new file mode 100644 index 0000000..77ab49c --- /dev/null +++ b/.github/workflows/scene-candidate-integration.yml @@ -0,0 +1,62 @@ +name: scene-candidate-integration + +on: + pull_request: + branches: [c3-upgrade] + paths: + - .github/scene-candidates/** + - .github/workflows/scene-candidate-integration.yml + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: scene-candidate-integration-${{ github.ref }} + cancel-in-progress: true + +jobs: + source-installed-candidates: + runs-on: ubuntu-24.04 + env: + PYTHONDONTWRITEBYTECODE: "1" + QT_QPA_PLATFORM: offscreen + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Install Linux EGL/OpenGL runtime + run: | + sudo apt-get update + sudo apt-get install --yes libegl1 python3-opengl + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: "3.12" + - uses: astral-sh/setup-uv@ae62891fec2bb8e7d6c99fc78c9fec3a63790f8d # v10.0.0 + with: + version: "0.12.7" + - name: Install test tools and exact source candidates + run: | + uv venv + uv pip install --python .venv/bin/python pytest==9.0.2 pyright==1.1.414 + uv pip install --python .venv/bin/python compas_viewer==2.0.2 + uv pip install \ + --python .venv/bin/python \ + --reinstall \ + --requirements .github/scene-candidates/core-requirements.txt + uv pip install \ + --python .venv/bin/python \ + --no-deps \ + --reinstall \ + --requirements .github/scene-candidates/viewer-requirements.txt + - name: Check installed scene typing boundary + run: >- + uv run --no-sync pyright + --pythonversion 3.12 + .github/scene-candidates/test_scene_integration.py + - name: Exercise installed scene and Viewer behavior + run: >- + uv run --no-sync pytest + --basetemp "${RUNNER_TEMP}/scene-candidate-pytest" + -o cache_dir="${RUNNER_TEMP}/scene-candidate-cache" + .github/scene-candidates/test_scene_integration.py diff --git a/initiatives/scene-viewer-visualisation/README.md b/initiatives/scene-viewer-visualisation/README.md new file mode 100644 index 0000000..6a949d4 --- /dev/null +++ b/initiatives/scene-viewer-visualisation/README.md @@ -0,0 +1,23 @@ +# Scene, Viewer, and Visualisation + +This initiative aligns backend-neutral scene infrastructure with Viewer +rendering, interaction, state, and UI composition. + +Current phase: **architecture correction approved; implementation pending**. + +- [Objective](objective.md) +- [Inventory](inventory.md) +- [Proposal](proposal.md) +- [Architecture decisions](decisions/README.md) +- [Implementation plan](implementation-plan.md) +- [Current handoff](handoff.md) + +ADRs 013–014 are Accepted and define hierarchy-only nodes, stable durable scene +objects, and context-keyed operation plugins. Scene remains reference-only and +optional aggregation is reserved for a separate future Session design. + +The refined initial package scope is `compas`, `compas_viewer`, and +`compas_framework`. The current `compas_model.ModelViewer` is draft evidence +and a later redesign consumer, not a behavior contract. Session persistence, +notebook visualisation, and CAD-host adapters are compatibility-review +consumers until an approved decision admits them to implementation scope. diff --git a/initiatives/scene-viewer-visualisation/decisions/001-ownership-boundaries.md b/initiatives/scene-viewer-visualisation/decisions/001-ownership-boundaries.md new file mode 100644 index 0000000..5c81733 --- /dev/null +++ b/initiatives/scene-viewer-visualisation/decisions/001-ownership-boundaries.md @@ -0,0 +1,86 @@ +# ADR 001: Ownership and State Boundaries + +## Status + +Accepted. + +## Context + +Core scene objects currently combine durable content with context-adapter +state, while Viewer selection, GPU identity, configuration, tool state, and UI +refresh paths are distributed across objects, renderer, commands, and widgets. +Moving everything into core would make Qt/rendering concerns framework +dependencies; leaving ownership implicit perpetuates coupling. + +## Options + +1. Keep core responsible for durable scene content and place renderer and + interaction state in Viewer. +2. Add rendering, selection, and reactive application state to core Scene. +3. Keep all current ownership and coordinate behavior through conventions. +4. Move durable scene content into Viewer and reduce core Scene to adapters. + +## Recommendation + +Choose option 1. + +- Core owns scene items, hierarchy, scene-object GUIDs, local transformations, + visibility, durable appearance, and serialization. +- Viewer configuration owns persistent application preferences. +- Viewer session state owns selection, hover, tools, gestures, and transient + results. +- Viewer rendering owns indexed buffers, pick IDs, GPU resources, and refresh. +- Domain packages own authoritative domain hierarchies and caches and project + them into core scenes through public Viewer operations. + +GUIDs connect durable scene objects to Viewer state. Pick IDs and transient +state are never serialized. Do not add Qt signals or GPU contracts to core. + +## Consequences + +- Core remains usable by notebook and host adapters without Viewer. +- Selection and tools stop leaking into serialized scene-object settings. +- Viewer must provide explicit public state and synchronization operations. +- Domain packages retain their own data authority rather than duplicating it + in a generic application store. +- Existing Viewer code that reaches its singleton, renderer, or widgets must + migrate gradually after the public boundary exists. + +## Compatibility + +ADR 008 proposes replacing the current incomplete scene serialization without +a compatibility loader. Removing transient Viewer fields and introducing the +new scene-node schema therefore form a deliberate C3 serialization break. + +## Verification + +- Round-trip core scenes without Viewer installed. +- Prove Viewer selection, hover, tools, and pick IDs are absent from scene + serialization. +- Verify domain objects resolve through stable scene-object GUIDs after UI + rebuilds. + +## Affected packages + +Initial implementation: `compas`, `compas_viewer`, and `compas_framework`. + +Later redesign/regression consumer: `compas_model`. + +## Dependencies + +- Geometry-tessellation ADRs 022 and 024 +- Plugin-implementations ADR 002 + +## Pending amendments + +Verified round-trip failures showed that this decision did not distinguish the +durable scene node/state from the context-specific runtime `SceneObject` +adapter precisely enough. Accepted [ADR 008](008-scene-nodes-and-runtime-adapters.md) +and [ADR 009](009-scene-state-fields.md) amend that boundary. Accepted +[ADR 010](010-scene-description-and-item-bundling.md) defines item ownership +and the later Session aggregation boundary. + +The generic principle that domain packages retain their own data authority +does not preserve the current draft `ModelViewer` controller, projections, +selection behavior, or cache shape. Explicit user correction moves that draft +to a later redesign phase. diff --git a/initiatives/scene-viewer-visualisation/decisions/002-indexed-buffer-geometry.md b/initiatives/scene-viewer-visualisation/decisions/002-indexed-buffer-geometry.md new file mode 100644 index 0000000..cf552f3 --- /dev/null +++ b/initiatives/scene-viewer-visualisation/decisions/002-indexed-buffer-geometry.md @@ -0,0 +1,75 @@ +# ADR 002: Indexed Viewer Buffer Geometry + +## Status + +Accepted. + +## Context + +Viewer can batch geometry efficiently but its general `BufferGeometry` +requires expanded line and triangle vertices. This duplicates arrays and makes +callers manually expand vertices plus edges or triangles. Core numerical APIs +already accept structural Python and NumPy buffers, and Viewer-specific GPU +layout should not become a compulsory core geometry representation. + +## Options + +1. Add one Viewer-owned shared-vertex indexed `BufferGeometry` contract and + convenience constructors. +2. Put a GPU-oriented buffer type in core. +3. Require `Tessellation`, `PolygonalSurface`, or `Mesh` for every input. +4. Preserve expanded arrays and add only helper functions that duplicate them. + +## Recommendation + +Choose option 1. Use shared `vertices` plus optional point, edge, and triangle +index channels. Provide: + +```python +BufferGeometry.from_points(vertices) +BufferGeometry.from_vertices_and_edges(vertices, edges) +BufferGeometry.from_vertices_and_triangles(vertices, triangles) +``` + +Accept structural Python sequences and NumPy arrays. Normalize once to Viewer +dtypes and check rank, column count, and index bounds. Preserve indices through +the buffer manager and do not create one scene object per primitive. + +Keep styling on scene objects. Do not add a universal render geometry to core +or make this contract an input to geometry algorithms. + +## Consequences + +- Large edge and triangle datasets avoid Python-object and vertex-expansion + overhead. +- Viewer adapters gain one common path for indexed geometry. +- Geometry with independent point/line/triangle vertex pools must combine + them into one vertex table with offset indices at the Viewer boundary. +- Per-channel or per-primitive color arrays beyond current behavior require + evidence and a separate refinement rather than speculative fields. + +## Compatibility + +The existing expanded `points`, `lines`, and `faces` constructor is replaced +in a coordinated Viewer change. Examples and adapters migrate together; no +parallel deprecated constructor is proposed. + +## Verification + +- Test list and NumPy inputs, exact shapes/dtypes, bounds, empty channels, and + input non-mutation. +- Assert indices remain indexed in CPU/GPU assembly. +- Exercise large input without per-edge or per-triangle scene objects. +- Compare rendering semantics with current points, lines, and faces. + +## Affected packages + +Initial implementation: `compas_viewer` and `compas_framework`. + +Later redesign consumer: `compas_model`; its current render-cache shape is not +preserved by this decision. + +## Dependencies + +- [ADR 001](001-ownership-boundaries.md) +- Accepted structural numerical-input decisions from typing alignment diff --git a/initiatives/scene-viewer-visualisation/decisions/003-viewer-state-and-selection.md b/initiatives/scene-viewer-visualisation/decisions/003-viewer-state-and-selection.md new file mode 100644 index 0000000..de0febb --- /dev/null +++ b/initiatives/scene-viewer-visualisation/decisions/003-viewer-state-and-selection.md @@ -0,0 +1,76 @@ +# ADR 003: Viewer State and Object Selection + +## Status + +Accepted. + +## Context + +Selection currently lives in mutable `ViewerSceneObject.is_selected` flags. +Viewport commands, tree widgets, shaders, and programmatic consumers mutate or +poll those flags and then trigger imperative refreshes. `ViewerState` observes +persistent configuration but not selection or tool state. + +## Options + +1. Add one Viewer-owned observable object-selection model and separate + persistent configuration from transient state. +2. Keep scene-object flags and add selection-change signals around them. +3. Put selection into core Scene and serialize it. +4. Adopt a generic third-party reactive state store for all Viewer data. + +## Recommendation + +Choose option 1. + +`Viewer.state.selection` stores ordered scene-object GUIDs and provides +replace, add, remove, toggle, clear, membership, and ordered-resolution +operations. It emits one batched change notification per operation. Viewport, +rectangle selection, scene tree, commands, shaders, and programmatic consumers use +this model exclusively. + +Keep `Viewer.config` for persistent preferences. Keep hover, active tool, +gesture, and temporary tool values in small explicit Viewer state fields. Use a +Qt-neutral Python model with a Qt bridge. Remove `is_selected` rather than +retaining a second writable authority. + +Initial selection is scene-object-level only. Do not invent a generic vertex, +edge, face, or domain-component token until an approved editing workflow +requires it. + +## Consequences + +- Tree, viewport, code, and renderer observe one mutation + path. +- Selection no longer participates in scene serialization. +- Batch changes avoid repeated renderer/UI updates. +- Existing selection APIs and tests require a coordinated breaking migration. +- Sub-object selection remains explicitly unsupported in the initial wave. + +## Compatibility + +Remove Viewer `is_selected` constructor/settings behavior. Later integrations +may consume the selection model but do not define its contract. + +## Verification + +- Exercise replace/add/remove/toggle/clear from viewport, rectangle, tree, and + programmatic paths. +- Assert one notification and one render/UI synchronization per batch. +- Verify stable ordering, removed-object cleanup, and serialization exclusion. + +## Affected packages + +`compas_viewer` and `compas_framework`. `compas_model` is a later redesign +consumer. + +## Dependencies + +- [ADR 001](001-ownership-boundaries.md) + +## Refinement after acceptance + +The current `compas_model.ModelViewer` is a draft and does not promise a +model-order selection contract that this ADR must preserve. The accepted +decision remains one Viewer-owned selection model; model-specific preservation +language has been removed by explicit user correction. diff --git a/initiatives/scene-viewer-visualisation/decisions/004-transform-refresh-undo.md b/initiatives/scene-viewer-visualisation/decisions/004-transform-refresh-undo.md new file mode 100644 index 0000000..fa49950 --- /dev/null +++ b/initiatives/scene-viewer-visualisation/decisions/004-transform-refresh-undo.md @@ -0,0 +1,76 @@ +# ADR 004: Transformations, Refresh, and Undo + +## Status + +Accepted. + +## Context + +Core already owns local/world transformations and Viewer can update transform +buffers efficiently, but there is no public multi-object transformation +operation, gizmo contract, or undo behavior. Geometry, selection, sidebar, and +renderer refreshes are invoked through concrete internals. A universal core +event bus would make every mutable property observable before its semantics are +known. + +## Options + +1. Add explicit Viewer transform transactions, undo/redo, and categorized + refresh operations while retaining core transformations. +2. Add a generic observable mutation/event system to core Scene. +3. Mutate scene objects directly and require callers to refresh internals. +4. Move transformations and history entirely into Viewer state. + +## Recommendation + +Choose option 1. + +Expose `viewer.transform_objects(objects, transformation, space="world")`. +Apply one world-space delta to each pre-operation world transformation and +derive each new local transformation through its parent. Reject requests that +contain both an ancestor and its descendant in the initial version. Validate +the entire request before mutation and leave all objects unchanged on failure. + +Record before/after local transformations as one reversible Viewer +transaction. Add a small undo/redo stack for explicit reversible Viewer +transactions; do not absorb general session or domain history. + +Expose explicit geometry, appearance, transform, and hierarchy refresh +operations. Do not add a core scene event bus in this wave. Direct external +mutation requires the matching Viewer refresh call. + +## Consequences + +- Code, commands, and a future gizmo use identical transformation semantics. +- Atomic transactions provide predictable undo and efficient transform-buffer + updates. +- Ancestor/descendant simultaneous transforms remain unsupported until their + intended group semantics are decided. +- Arbitrary item mutation is not magically detected; synchronization remains + explicit and testable. + +## Compatibility + +Existing direct transformation mutation remains a core operation, but Viewer +consumers migrate to the public transaction when they require refresh or undo. +Existing callback commands remain valid and simply do not enter history unless +they produce a reversible transaction. + +## Verification + +- Test world/local conversion under transformed parents. +- Test multiple independent objects, atomic validation failure, ancestor/ + descendant rejection, undo, redo, and redo invalidation after new work. +- Assert transform-only refresh does not rebuild geometry buffers. +- Verify failed commands do not create history entries. + +## Affected packages + +Initial implementation: `compas`, `compas_viewer`, and `compas_framework`. + +Later redesign consumer: `compas_model`. + +## Dependencies + +- [ADR 001](001-ownership-boundaries.md) +- [ADR 003](003-viewer-state-and-selection.md) diff --git a/initiatives/scene-viewer-visualisation/decisions/005-annotations-and-overlays.md b/initiatives/scene-viewer-visualisation/decisions/005-annotations-and-overlays.md new file mode 100644 index 0000000..9dbf1ad --- /dev/null +++ b/initiatives/scene-viewer-visualisation/decisions/005-annotations-and-overlays.md @@ -0,0 +1,70 @@ +# ADR 005: Annotations, Measurements, and Overlays + +## Status + +Accepted. + +## Context + +Viewer has concrete `Tag` and `ArrowOverlay` rendering paths but no general +measurement, dimension, scale, or legend behavior. Treating every visual aid +as a scene item would serialize view-dependent UI; treating every annotation +as an overlay would lose world anchoring and durable intent. + +## Options + +1. Separate durable world annotations from transient screen overlays. +2. Make every annotation and overlay a core scene item. +3. Keep every visual aid as a renderer-special-cased Viewer class. +4. Add a framework-wide overlay plugin registry before adding features. + +## Recommendation + +Choose option 1. + +- World text annotations and distance dimensions are serializable scene items + with Viewer adapters. Distance dimensions store two anchors, unit, + precision, and optional label override; distance is derived. +- Scale indicators and legends are transient screen-space Viewer overlays and + are not serialized or picked initially. +- Measurement preview state belongs to the active Viewer tool. Completing the + tool creates a durable distance dimension; cancelling creates nothing. + +Align current Tag/Arrow behavior with these paths. Use one Viewer overlay layer +for the approved screen types and ordinary scene adapter registration for +world annotations. Do not add a general overlay registry. + +## Consequences + +- Persistence follows user intent rather than rendering technique. +- World annotations participate in hierarchy and transformations. +- View-dependent overlays remain independent of scene files. +- Current concrete renderer checks can be consolidated behind the two paths. +- Editing, snapping, angular dimensions, and arbitrary overlay plugins remain + later requirements. + +## Compatibility + +Inspect existing serialized Tag/Arrow fixtures before choosing a migration. +Do not reinterpret ambiguous old data silently. Keep equivalent visible +behavior where the old type maps exactly; otherwise document removal and the +new construction path. + +## Verification + +- Test distance derivation, units, precision, label override, transformed + parents, serialization, and reload. +- Test measurement preview/cancel/complete state transitions. +- Assert scale and legend overlays are absent from scene serialization. +- Verify renderer ordering, resize behavior, and no concrete type checks in + the main render loop for approved types. + +## Affected packages + +`compas`, `compas_viewer`, and `compas_framework`. + +## Dependencies + +- [ADR 001](001-ownership-boundaries.md) +- [ADR 002](002-indexed-buffer-geometry.md) +- [ADR 003](003-viewer-state-and-selection.md) diff --git a/initiatives/scene-viewer-visualisation/decisions/006-viewer-integration-facade.md b/initiatives/scene-viewer-visualisation/decisions/006-viewer-integration-facade.md new file mode 100644 index 0000000..597fd63 --- /dev/null +++ b/initiatives/scene-viewer-visualisation/decisions/006-viewer-integration-facade.md @@ -0,0 +1,88 @@ +# ADR 006: Public Viewer Integration Facade + +## Status + +Accepted. + +## Context + +Viewer-owned commands and components reach into concrete renderer and widget +objects. Viewer has command and menu composition but no equally clear public +dock or synchronization boundary. Optional consoles or assistants should not +force shell, network, or credential behavior into the Viewer core. + +## Options + +1. Expose a small public Viewer facade for scene, state, refresh, history, + commands, docks, annotations, and overlays. +2. Let integrations continue using renderer and widget internals. +3. Define a large abstract Viewer/controller framework for every domain. +4. Design the facade around the current draft `ModelViewer` controller. + +## Recommendation + +Choose option 1. Provide public operations for: + +- scene access and GUID resolution; +- selection and transient state; +- categorized refresh; +- transform transactions and undo/redo; +- command registration and menu placement; +- dock addition/removal; and +- annotation and overlay addition/removal. + +The facade is defined only by approved Viewer-owned tools, commands, state, +annotations, overlays, refresh, and UI composition. Do not create a generic +controller base class or add operations solely to match a current external +consumer. + +A future console, terminal, or assistant may use a dock and registered command +API. Arbitrary Python/shell execution, subprocesses, credentials, network +transport, and assistant policy require a separate security ADR and are not +implemented in this wave. + +## Consequences + +- Viewer-owned components gain a stable, testable integration surface that + later consumers may reuse. +- Viewer internals can change without coordinated domain rewrites. +- Public API growth is limited to demonstrated Viewer feature needs. +- Dock content remains Viewer/Qt-specific; core stays UI-independent. +- Console/chat presentation becomes possible without prematurely authorizing + execution or remote services. + +## Compatibility + +The current draft `ModelViewer` has no compatibility requirements under this +ADR. Its later redesign may consume the proven facade but may not enlarge it +without a demonstrated requirement and approval. + +## Verification + +- Test facade behavior with a lightweight fake UI/renderer boundary. +- Forbid private renderer/widget access in Viewer-owned command, tool, and + component tests. +- Verify dock and command registration/removal and duplicate-ID failures. +- Verify plugin discovery imports no Qt/OpenGL or optional-consumer + implementation modules. + +## Affected packages + +`compas_viewer` and `compas_framework`. `compas_model` is a later redesign +consumer. + +## Dependencies + +- [ADR 001](001-ownership-boundaries.md) +- [ADR 003](003-viewer-state-and-selection.md) +- [ADR 004](004-transform-refresh-undo.md) +- [ADR 005](005-annotations-and-overlays.md) + +## Refinement after acceptance + +The original context and compatibility clauses relied too heavily on the +current `compas_model.ModelViewer`. Explicit user correction establishes that +controller as a draft that may be redesigned freely. The accepted decision is +therefore narrowed to a small Viewer-owned public boundary justified by +Viewer's own approved capabilities; no ModelViewer behavior or controller +shape is preserved. diff --git a/initiatives/scene-viewer-visualisation/decisions/007-scope-and-verification.md b/initiatives/scene-viewer-visualisation/decisions/007-scope-and-verification.md new file mode 100644 index 0000000..97ea306 --- /dev/null +++ b/initiatives/scene-viewer-visualisation/decisions/007-scope-and-verification.md @@ -0,0 +1,89 @@ +# ADR 007: Initial Scope and Verification + +## Status + +Accepted. + +## Context + +Core Scene is consumed by Viewer, model, notebook, session, and CAD-host +packages. Expanding the first implementation across every backend would mix +runtime-specific migrations and make completion evidence unavailable. Package +tests alone do not prove installed cross-package behavior. + +## Options + +1. Implement an initial core/Viewer/framework wave and compatibility- + review adjacent consumers. +2. Migrate every visualisation and host package in one wave. +3. Change Viewer only and defer core scene alignment. +4. Accept package-local source tests as completion evidence. + +## Recommendation + +Choose option 1. + +Initial implementation packages are `compas`, `compas_viewer`, and +`compas_framework`. Review `compas_model`, `compas_session`, +`compas_notebook`, Rhino, Grasshopper, and Blender for compatibility with core +changes, but do not modify them without a scope amendment and approval. + +Require: + +- focused and full applicable local tests, Ruff, Pyright, and strict docs; +- clean package CI and built-wheel checks at exact heads; +- framework CI installing exact candidates and exercising plugin discovery, + scene and Viewer construction, indexed buffers, selection, transformations, + annotations, serialization, and graphics-free discovery; and +- candidate-artifact CI for claims about independently built distributable + compatibility. + +Coordinate the plugin initiative's installed model/viewer registry gate rather +than creating a second competing test. + +## Consequences + +- The first wave remains verifiable on ordinary CI with offscreen Viewer + support. +- Host-specific behavior remains unverified and unmodified, not implicitly + passed. +- Core compatibility risks are still inspected before implementation. +- Later session, notebook, host, sub-object selection, advanced transform, + and console/assistant waves require explicit decisions. + +## Compatibility + +Any approved core change must preserve current public scene contracts for +compatibility-only consumers or identify a concrete blocker and return for +scope/compatibility approval before implementation proceeds. + +## Verification + +- Record local, package, framework-integration, and candidate-artifact + evidence separately in the handoff. +- Pin exact commit and artifact identities. +- Treat unavailable required runtime evidence as unverified, not passing. +- Keep generated files in isolated temporary directories. + +## Affected packages + +Initial: `compas`, `compas_viewer`, and `compas_framework`. + +Later redesign consumer and regression evidence: `compas_model`. + +Compatibility review only: `compas_session`, `compas_notebook`, Rhino, +Grasshopper, and Blender scene adapters. + +## Dependencies + +- [ADRs 001–006](README.md) +- Plugin-implementations ADR 005 + +## Refinement after acceptance + +The initial package list previously included `compas_model` as though the +current `ModelViewer` behavior constrained this initiative. Explicit user +correction moves it to a later redesign phase. Its current serialization +failure remains valid evidence for core scene reconstruction, but its +hierarchy projections, selection behavior, cache, controller shape, and facade +usage are not initial-wave completion criteria. diff --git a/initiatives/scene-viewer-visualisation/decisions/008-scene-nodes-and-runtime-adapters.md b/initiatives/scene-viewer-visualisation/decisions/008-scene-nodes-and-runtime-adapters.md new file mode 100644 index 0000000..ea845f3 --- /dev/null +++ b/initiatives/scene-viewer-visualisation/decisions/008-scene-nodes-and-runtime-adapters.md @@ -0,0 +1,180 @@ +# ADR 008: Serializable Scene Nodes and Runtime Adapters + +## Status + +Superseded by ADRs 013–014. + +## Context + +ADR 001 correctly separates durable scene content from Viewer interaction and +renderer state, but it still treats `SceneObject` as both durable node and +context-specific runtime adapter. Current serialization stores an item's GUID +plus `sceneobject.settings`, then calls `Scene.add(..., **settings)` during +load. Factory dispatch chooses a runtime subclass and executes its constructor +before that object is attached to the reconstructed hierarchy. + +Two verified regressions expose the consequences: + +1. A Viewer Box/ShapeObject with local translation `[2, 0, 0]` reloads with + `transformation is None` because `ViewerSceneObject.settings` replaces + rather than extends core settings. In a headless ShapeObject case, `u=7`, + `v=9`, and a face color reload as the defaults `16`, `16`, and the default + face color. +2. A `ModelViewer` scene cannot reload. The controller originally creates + `ModelObject(add_elements=False)` and `ElementObject(add_children=False)`, + but those construction controls are absent from serialized settings. + Reload dispatches `ModelObject` with `add_elements=True`; its constructor + calls `self.add(...)` before attachment and raises `RuntimeError: A detached + scene object cannot own child objects.` Render caches are transient and + should not be serialized, but hierarchy reconstruction must not depend on + them or on constructor side effects. + +The shared item store already preserves identity when multiple nodes reference +one item GUID. Scene-object/node GUIDs are not preserved across round trips. +Context also determines which runtime subclass is constructed, even though a +serialized scene should remain loadable without that backend. + +## Options + +1. Separate a serializable scene-owned node/state value from context-specific + runtime adapters and reconstruct them in two phases. +2. Keep `SceneObject` as both node and adapter and expand every subclass's + `settings` property until current tests pass. +3. Serialize complete runtime adapter objects, including their concrete class + and context. +4. Serialize only items and hierarchy and discard all per-node appearance and + transformations. + +## Recommendation + +Choose option 1. + +`Scene` owns a serializable node/state record for every hierarchy entry. The +closed base record contains: + +- a durable node GUID; +- node kind (`item` or `group`); +- optional item GUID; +- name; +- local transformation; +- visibility, opacity, and base appearance; and +- explicitly declared extension state governed by ADR 009. + +Items remain separate and keyed by item GUID, preserving shared item identity. +Accepted ADR 010 requires a serialized scene description to reference those +items without embedding them. Node GUIDs are serialized +and restored so selection references, domain indexes, and external scene +references have durable identity. Runtime adapter instances are not durable +identities. + +Deserialize in two phases: + +1. validate and create nodes, state, GUIDs, and the complete hierarchy without + requiring item bindings, context dispatch, or adapter constructors; +2. bind adapters for an explicitly requested or detected runtime context only + after the scene is structurally complete, either eagerly for drawing or + lazily on first use. + +`SceneObject` becomes the runtime adapter bound to a scene, node, and optional +item. Its constructor must not populate hierarchy or own serialized state. +Application/domain projection creates nodes explicitly through an owning +controller; adapter binding never recreates children. The current draft +`ModelViewer` is evidence of why constructor-driven hierarchy creation fails, +not a controller contract to preserve. + +Scene context is runtime binding configuration, not authoritative serialized +content. A scene may record a non-binding context hint for user experience, +but loading must not require that context or silently select a different one. +Drawing without a usable context raises a precise runtime error after the +backend-neutral scene has loaded. + +Known extension fields are activated only by a compatible adapter. Unknown +extension namespaces and fields are preserved losslessly as inactive node +state and written back unchanged. Unknown required base schema versions fail +validation; unknown adapter state is not passed blindly to constructors. + +## Refinement of ADR 001 + +This ADR preserves ADR 001's package boundaries but refines these clauses: + +- "Core owns scene items, hierarchy, scene-object GUIDs..." becomes: core + owns durable node/state GUIDs and stable item references; runtime adapter + identity is not serialized. Accepted ADR 010 defines the non-owning runtime + binding boundary for items. +- "GUIDs connect durable scene objects to Viewer state" becomes: durable node + GUIDs connect scene content to Viewer state and domain indexes. +- Core still owns transformations, visibility, durable appearance, and + serialization, but those values live on node state rather than being + discovered from a runtime adapter's constructor/settings implementation. + +ADR 001's durable identity and serialization boundary is amended by this +Accepted decision and ADR 009. Accepted ADR 010 defines item ownership and +packaging. + +## Consequences + +- Scenes load without Viewer, model adapters, or another visualization backend. +- Runtime constructors no longer execute hierarchy-building side effects during + deserialization. +- Switching or omitting runtime contexts does not destroy durable node state. +- Shared item identity and durable node identity have separate, explicit GUIDs. +- Core Scene/SceneObject APIs require a coordinated redesign and compatibility + review; this is larger than patching two `settings` properties. +- Adapter binding and lifecycle need focused tests, but no universal renderer + abstraction or event bus is introduced. + +## Compatibility + +The new node schema replaces the existing serialized scene shape. The initial +implementation does not include a compatibility loader because current files +omit required state and cannot be migrated reliably without guessing. If +concrete legacy files later require recovery, migration tooling needs separate +evidence, scope, and approval. + +Existing public code may continue to receive a `SceneObject` adapter from +`Scene.add`, but code that treats adapter identity or constructor side effects +as durable must migrate. Exact compatibility is part of the Phase 1 audit. + +## Verification + +- Reproduce the Viewer regression as a focused test and retain the draft + ModelViewer failure as evidence. +- Round-trip local transformations, `u=7`, `v=9`, face color, shared items, + node GUIDs, groups, and hierarchy without a visualization backend installed. +- Use a minimal test adapter with constructor-side-effect behavior to prove + hierarchy deserialization never invokes runtime construction; redesign and + test the actual ModelViewer later. +- Load and re-save unknown extension state without loss or constructor use. +- Verify missing/unknown context permits load but produces a precise binding or + draw error. +- Verify duplicate item or node GUIDs and unsupported base schema versions fail + atomically. + +## Affected packages + +Initial implementation: `compas`, `compas_viewer`, and `compas_framework`. + +Later redesign/regression consumer: `compas_model`. + +Compatibility review only: `compas_session`, `compas_notebook`, and CAD-host +scene adapters. + +## Dependencies + +- [ADR 001](001-ownership-boundaries.md), refined by this decision + +ADR 009 supplies the serialization field policy required to implement this +decision. ADR 010 separately decides that referenced items remain external to +Scene and defines the boundary for later Session aggregation. + +## Decision + +Accepted on 2026-11-07. The user additionally required a separate decision +ensuring that independently serializable `Data` objects are not forced to live +inside a scene. Accepted ADR 010 records that packaging refinement without +changing this decision's accepted node/adapter separation. + +Superseded on 2026-11-07 after implementation exposed duplicate detached/bound +state and backend-dependent object types. Accepted ADRs 013–014 retain +backend-independent loading and reference-only data binding while assigning +durable state to stable scene objects and backend behavior to operations. diff --git a/initiatives/scene-viewer-visualisation/decisions/009-scene-state-fields.md b/initiatives/scene-viewer-visualisation/decisions/009-scene-state-fields.md new file mode 100644 index 0000000..93752df --- /dev/null +++ b/initiatives/scene-viewer-visualisation/decisions/009-scene-state-fields.md @@ -0,0 +1,113 @@ +# ADR 009: Explicit Scene-State Serialization Fields + +## Status + +Superseded in part by ADR 013. + +## Context + +Current scene serialization depends on each runtime adapter's `settings` +property and reconstructs state by replaying that mapping as constructor +arguments. Subclasses can accidentally replace base settings, as demonstrated +by the lost transformation regression. Required subclass state can be omitted, +as demonstrated by lost ShapeObject resolution and face color. Conversely, +serializing `__dict__`, every property, annotations, or constructor parameters +would capture caches, renderer handles, item/scene backreferences, derived +values, and construction commands such as `add_elements`. + +The durable node proposed by ADR 008 needs an explicit field policy that +supports inheritance without treating Python introspection as persistence. + +## Options + +1. Use a closed base node-state schema plus explicit MRO-composed extension + field declarations. +2. Serialize every entry in `__dict__` except a growing denylist. +3. Serialize every public property, type annotation, or constructor parameter + discovered through introspection. +4. Require every subclass to replace and manually merge a free-form + `settings` dictionary. +5. Allow only the closed base schema and prohibit all adapter/domain extension + state. + +## Recommendation + +Choose option 1. + +Base node state is a closed value-object schema owned by core. A class that +owns additional durable scene state declares only its local fields through an +explicit class-level declaration. The serializer composes declarations across +the MRO from base to subclass, rejects duplicate or incompatible declarations, +and stores extension values under a stable owning namespace. + +Declarations specify the serialized name, expected value contract, default or +required status, and whether omission is allowed. They do not execute arbitrary +getters during schema discovery. The implementation may use annotations, +constructor signatures, descriptors, and properties only to validate a +declaration or generate editor/documentation metadata; introspection never +decides that a field is persistent. + +Unrestricted `__dict__`, public-property, constructor-parameter, or annotation +serialization is forbidden. Derived values, scene/item backreferences, +renderer/GPU handles, pick IDs, caches, selection, hover, tools, and command +history are not durable fields. `ModelObject.add_elements` and +`ElementObject.add_children` are construction commands, not state. Model render +caches are transient. Shape resolution and durable colors are serialized only +through explicit declarations. + +Deserialization validates the closed base state and known declarations without +passing a free-form mapping to adapter constructors. Unknown namespaced +extension values remain inactive and round-trip unchanged under ADR 008. + +## Consequences + +- Base transformations and subclass fields cannot disappear because a + subclass forgot `super().settings`. +- Persistence changes are visible in code review and independently testable. +- Runtime caches and constructor side effects are excluded by default. +- Subclasses must deliberately declare every durable field and migration. +- Editor tooling can reuse schema metadata without becoming the source of + truth for persistence. +- A small schema/version and migration mechanism is required for renamed or + removed fields; it remains data-focused rather than adapter-constructor + compatibility code. + +## Compatibility + +Legacy `settings` mappings are not accepted by the new serializer. They are +incomplete, unversioned, and cannot be mapped into declared base/extension +fields reliably. The new implementation reads and writes only the explicit +schema. Any later recovery tool remains separate from runtime serialization. + +## Verification + +- Assert MRO composition preserves base transformation plus declared subclass + `u`, `v`, and face color. +- Assert duplicate field declarations, invalid values, missing required fields, + and unknown base fields fail with precise paths. +- Prove caches, Viewer handles, pick IDs, selection, and construction commands + are absent from serialized state. +- Prove unknown namespaced extension state round-trips byte-for-value without + importing or constructing its adapter. +- Use introspection-based validation tests to detect a declaration that names a + missing/incompatible attribute, without auto-serializing undeclared members. + +## Affected packages + +Initial implementation: `compas`, `compas_viewer`, and `compas_framework`. + +Later redesign/regression consumer: `compas_model`. + +## Dependencies + +- [ADR 008](008-scene-nodes-and-runtime-adapters.md) + +## Decision + +Accepted on 2026-11-07. Persistent fields use the closed base schema and +explicit MRO-composed declarations described above. Item ownership and +serialization packaging are decided separately by Accepted ADR 010. + +Superseded in part on 2026-11-07. Accepted ADR 013 retains the closed-schema, +explicit-field principle but moves durable state from node extensions onto +ordinary stable scene-object attributes and rejects attribute interception. diff --git a/initiatives/scene-viewer-visualisation/decisions/010-scene-description-and-item-bundling.md b/initiatives/scene-viewer-visualisation/decisions/010-scene-description-and-item-bundling.md new file mode 100644 index 0000000..0b7fe72 --- /dev/null +++ b/initiatives/scene-viewer-visualisation/decisions/010-scene-description-and-item-bundling.md @@ -0,0 +1,144 @@ +# ADR 010: Scene Description, Item Bindings, and Aggregation Boundary + +## Status + +Accepted. + +## Context + +ADRs 008–009 separate durable scene node state from runtime adapters and define +its serialization fields. They do not yet decide whether referenced domain +items must be embedded in a serialized scene. + +A geometry or other `Data` object may need to be serialized independently for +later algorithmic processing while also being referenced by one or more scene +nodes. It must not be forced to live inside the scene or require extraction +from a scene file before ordinary data processing. Conversely, users sometimes +need an aggregate containing data, one or more scenes, settings, and history. + +The current item table preserves shared identity inside one scene file, but +conflates a scene description, runtime bindings, and packaging. + +## Options + +1. Keep Scene reference-only and treat a redesigned Session as the likely + optional aggregate under a separate decision and scope. +2. Always embed every referenced item in the scene serialization. +3. Add a dedicated `SceneBundle` containing one scene plus its items. +4. Introduce a general asset catalog with URIs, search paths, network loading, + and resolver plugins. + +## Recommendation + +Choose option 1. + +Define one Scene role, its runtime bindings, and one later aggregation boundary: + +- `Scene` is the durable, backend-neutral visual description from ADRs 008–009. + Its serialized node hierarchy contains stable item GUID references only. + At runtime it may hold a nonserialized binding table from those GUIDs to + already loaded `Data` objects. A binding is a reference, not ownership; the + same object remains independently usable and serializable. +- A redesigned `compas_session.Session` is the likely optional aggregate for + independently serialized data, one or more scene descriptions, settings, + history, and their shared identity. Its exact schema, ownership, and loading + behavior require a separate dependent decision and explicit scope approval. + Do not add Session implementation to the current wave. + +A later Viewer/Session integration may provide convenient new, open, and save +operations. Viewer remains a client of Session persistence: it does not own the +aggregate format, and session support remains optional and lazily loaded. The +exact convenience API follows the separately approved Session redesign. + +The intended runtime operations are conceptually: + +```python +scene = Scene.from_json(...) +scene.bind(item) +scene.bind_many(items) +``` + +Exact method naming may follow established core conventions during +implementation. Scene serialization remains reference-only; it is not +controlled by an `include_items` boolean. + +Loading a Scene succeeds when item references are unresolved. Hierarchy, +node state, and reference inspection remain available. Binding an item uses +its existing GUID. Binding the same instance again is idempotent; binding a +different object with the same GUID is rejected. All nodes referencing one +GUID resolve to the same bound object instance. + +Drawing, adapter binding, or another operation that requires an +unresolved item raises a precise error containing the node GUID and item GUID. +The caller may bind the item later and retry. Serialization and inspection of +the reference-bearing description do not require bindings. + +Do not add URIs, filesystem lookup, network loading, an asset registry, or a +resolver plugin. Callers load `Data` objects through existing serialization and +bind them explicitly. A concrete external-resource requirement would need a +separate decision. + +## Refinement of ADR 008 + +ADR 008's separate item store is refined into a runtime binding table. Core +owns the Scene description and stable references, but it does not claim +exclusive ownership of bound `Data` objects. Aggregation belongs outside Scene; +a future Session design may package objects without making that aggregate their +only valid serialization path. + +## Consequences + +- Domain data remains independently serializable and directly available for + algorithms. +- Scenes can be stored, compared, or transmitted without + duplicating large items. +- A future self-contained aggregate can support multiple scenes, settings, and + history without introducing a redundant one-scene bundle type now. +- Shared identity is deterministic within a runtime Scene and must remain so + in any future approved Session aggregate. +- Unresolved references become a normal, inspectable state with precise + failure at item-dependent operations. +- Cross-file discovery remains the caller's responsibility; no speculative + asset system is introduced. + +## Compatibility + +The new Scene format is reference-only. Existing `Data` serialization remains +unchanged. A future Session aggregate and any legacy recovery tooling remain +outside Scene runtime serialization and require separate approval. + +## Verification + +- Serialize and load one `Data` object independently, bind it to a separately + loaded Scene, and run an algorithm on the original/bound object + without extraction or copying. +- Round-trip a reference-only Scene while items remain unresolved. +- Bind later and prove every node sharing one item GUID resolves to the same + object instance. +- Reject a different object with a duplicate GUID atomically. +- Raise precise node/item errors for draw and adapter operations that + require unresolved items. +- Prove description serialization needs no bound items and no URI/network + resolver is invoked. +- In a separate future Session decision, verify data and multiple scenes share + identity without requiring extraction from Scene. + +## Affected packages + +Initial implementation: `compas` and `compas_framework`. + +Consumer: `compas_viewer`. + +Separate later design/scope: `compas_session`, followed by `compas_model`, +notebook, and CAD-host integrations as applicable. + +## Dependencies + +- [ADR 008](008-scene-nodes-and-runtime-adapters.md) +- [ADR 009](009-scene-state-fields.md) + +## Amendment + +Accepted ADR 013 amended this decision on 2026-11-07: item GUID references live +on stable scene objects rather than hierarchy nodes. Items remain independently +serializable, explicitly bound, and outside the serialized scene description. diff --git a/initiatives/scene-viewer-visualisation/decisions/011-node-first-scene-adapters.md b/initiatives/scene-viewer-visualisation/decisions/011-node-first-scene-adapters.md new file mode 100644 index 0000000..8980f81 --- /dev/null +++ b/initiatives/scene-viewer-visualisation/decisions/011-node-first-scene-adapters.md @@ -0,0 +1,102 @@ +# ADR 011: Node-First Scene Construction and Adapter Lifetime + +## Status + +Superseded by ADRs 013–014 before acceptance. + +## Context + +ADR 008 correctly separates durable scene nodes from runtime adapters, but its +first implementation still constructs a detached `SceneObject` before creating +the node. The adapter temporarily owns the item reference, GUID, name, +transformation, appearance, and extension values. Binding then changes those +properties to read from the node. + +This creates two object modes, duplicate storage, and identity whose meaning +changes after binding. Verified probes show that removal can expose stale +provisional values and a different GUID. Context rebinding replaces adapters +and leaves external references detached with the same stale fallback state. +The implementation therefore contradicts ADR 008's requirement that an +adapter constructor must not own serialized state. + +## Options + +1. Create the node first and construct a permanently node-bound runtime + adapter from `(scene, node)`. +2. Retain detached and bound adapter modes while hiding the transition behind + additional helpers. +3. Put runtime drawing behavior directly on `SceneNode`. +4. Preserve detached adapters through a compatibility wrapper. + +## Recommendation + +Choose option 1. + +`SceneNode` remains the sole durable authority for identity, hierarchy, item +GUID, name, local transformation, appearance, visibility, and extension state. +`SceneObject` becomes a plain runtime adapter rather than a `Data` object. It +is constructed only from an existing scene and node and owns only context +handles, derived caches, drawing behavior, and invalidation. + +Keep the convenient public call: + +```python +sceneobject = scene.add(mesh, color=Color.red(), show=True) +``` + +Internally, `Scene.add` resolves the adapter type, validates node and extension +arguments, creates and attaches the node, records the item binding, and only +then creates `adapter_type(scene, node)`. Failure rolls back the node and item +binding atomically. + +Deserialization remains node-only. `Scene.bind_context` constructs a complete +replacement adapter table after hierarchy and required items exist, then swaps +it in atomically. Adapter construction never creates hierarchy or durable +state. + +Remove detached adapter construction, `Scene.add(sceneobject)`, adapter +serialization, adapter-owned temporary GUIDs and presentation fallbacks, and +constructor-driven child creation. Registry dispatch moves from +`SceneObject.__new__` to `Scene`. + +Removed or context-replaced adapters become invalid runtime references. They +do not transition to a detached reusable mode. Re-adding content means adding +the `Data` item or retaining and explicitly rebinding an existing node. + +## Compatibility + +This intentionally breaks direct `SceneObject(item, ...)`, +`Scene.add(sceneobject)`, reusable removed adapters, and one-shot +`SceneObject(item).draw()`. Core and Viewer tests migrate to `Scene.add`. +GhPython one-shot conversion requires a small explicit context helper or local +scene. CAD-host adapters require audit before their later migration. + +`Scene.add(data, ...)`, registry/context dispatch, durable node GUIDs, +reference-only serialization, unresolved loading, explicit item binding, and +shared item identity remain public behavior. + +Direct `Group(...)` construction and arbitrary group constructor keywords as +future child defaults are removed. `scene.add_group(...)` creates the node +first; callers pass child presentation explicitly. + +## Verification + +- Prove the node exists before adapter construction. +- Prove adapter constructors receive only scene and node. +- Prove construction and context-binding failures roll back atomically. +- Prove GUID meaning and durable property storage never switch. +- Prove removed and replaced adapters fail precisely. +- Prove deserialization and unknown extension round trips need no adapters. +- Preserve Viewer round trips and `compas_model` compatibility through the + public `Scene.add(data, ...)` path. + +## Dependencies + +- Supersedes the construction, adapter identity, and compatibility clauses of + Accepted [ADR 008](008-scene-nodes-and-runtime-adapters.md). +- Preserves Accepted [ADR 010](010-scene-description-and-item-bundling.md). +- Works with Proposed [ADR 012](012-explicit-node-backed-adapter-state.md). + +## Decision + +Not accepted. Superseded on 2026-11-07 by Accepted ADRs 013–014. diff --git a/initiatives/scene-viewer-visualisation/decisions/012-explicit-node-backed-adapter-state.md b/initiatives/scene-viewer-visualisation/decisions/012-explicit-node-backed-adapter-state.md new file mode 100644 index 0000000..3f41682 --- /dev/null +++ b/initiatives/scene-viewer-visualisation/decisions/012-explicit-node-backed-adapter-state.md @@ -0,0 +1,79 @@ +# ADR 012: Explicit Node-Backed Adapter State + +## Status + +Superseded by ADR 013 before acceptance. + +## Context + +ADR 009 correctly requires a closed base schema and explicit namespaced +extension declarations. Its implementation, however, uses +`SceneObject.__getattribute__` and `__setattr__` to scan declarations and +redirect attributes dynamically. Other durable fields use individual +node-or-local properties, and color uses a special descriptor. The result is +three different state-routing mechanisms whose behavior changes when an +adapter binds to a node. + +The declarations should define persistence and validation, not invisibly +change normal Python attribute access. + +## Options + +1. Keep declarations as schema metadata and implement ordinary explicit + node-backed adapter properties. +2. Replace interception with a general descriptor or state-proxy framework. +3. Store durable adapter state in a third state object. +4. Keep the current interception and add documentation. + +## Recommendation + +Choose option 1. + +Keep `SceneStateField`-style declarations only for stable names, namespaces, +types, defaults, required fields, validation, and preservation of unknown +extension state. `Scene.add` validates declared keyword arguments and writes +them directly to `SceneNode.extensions`. Deserialization validates known state +without constructing adapters. + +Adapters expose ordinary properties whose implementation visibly reads and +writes the node. A small typed `SceneNode.extension(namespace, field)` and +`set_extension(...)` helper may remove repetitive mapping access. Do not add a +general descriptor, proxy, reflection layer, or global attribute interception. +Property setters perform required invalidation explicitly. + +Remove extension capture/apply operations, declaration-driven +`__getattribute__`/`__setattr__`, detached attribute validation, and all local +durable fallbacks. Base properties such as name, transformation, color, +opacity, and visibility are likewise direct node-backed properties on an +always-bound adapter. + +## Consequences + +- Durable state has one storage location and one visible access path. +- Persistence remains explicit and reviewable. +- Adapter classes contain some repetitive properties, which is preferable to + hidden control flow. +- Unknown namespaced state remains serializable without importing its adapter. +- Constructor signatures no longer double as persistence or editor schemas. + +## Verification + +- Prove each declared property reads and writes its node extension. +- Prove setters trigger their documented cache invalidation. +- Reject unknown, missing, duplicate, and invalid declared fields precisely. +- Preserve unknown namespaces without adapter imports. +- Prove caches, selection, context handles, and construction commands are not + durable. +- Assert no runtime adapter class uses persistence interception or capture. + +## Dependencies + +- Supersedes the runtime interception and capture mechanism introduced under + Accepted [ADR 009](009-scene-state-fields.md), while retaining its closed + schema and explicit declarations. +- Depends on Proposed [ADR 011](011-node-first-scene-adapters.md). +- Preserves Accepted [ADR 010](010-scene-description-and-item-bundling.md). + +## Decision + +Not accepted. Superseded on 2026-11-07 by Accepted ADR 013. diff --git a/initiatives/scene-viewer-visualisation/decisions/013-hierarchy-nodes-and-stable-sceneobjects.md b/initiatives/scene-viewer-visualisation/decisions/013-hierarchy-nodes-and-stable-sceneobjects.md new file mode 100644 index 0000000..c57b732 --- /dev/null +++ b/initiatives/scene-viewer-visualisation/decisions/013-hierarchy-nodes-and-stable-sceneobjects.md @@ -0,0 +1,125 @@ +# ADR 013: Hierarchy-Only Nodes and Stable Scene Objects + +## Status + +Accepted. + +## Context + +Accepted ADR 008 separated durable scene nodes from runtime adapters, but the +implementation duplicated state across detached and bound adapter modes. +Proposed ADR 011 removes that dual lifecycle, but still makes `SceneNode` own +both hierarchy and all durable visual state while `SceneObject` is only a +runtime projection. This leaves two public concepts that appear to describe +the same scene entry and makes backend-specific replacement classes central to +ordinary object access. + +A simpler separation is possible: nodes describe only hierarchy; stable scene +objects describe everything durable about a visualized item; backends provide +operations rather than replacement object classes. + +## Options + +1. Use hierarchy-only nodes and durable, stable scene objects. +2. Keep durable state on nodes and make scene objects always-bound adapters as + proposed by ADRs 011–012. +3. Merge hierarchy and durable state back into one tree-node class. +4. Keep the implemented detached/bound adapter model. + +## Recommendation + +Choose option 1. + +`SceneObject` is a backend-neutral `Data` object with a stable Python type and +GUID. It owns: + +- the referenced item GUID, if any; +- name and local transformation; +- visibility, opacity, and durable backend-neutral appearance; and +- stable semantic type-specific state. + +Stable subclasses such as `GroupObject`, `GeometryObject`, `ShapeObject`, +`MeshObject`, `GraphObject`, and `VolMeshObject` express durable semantics. +Installing Viewer, Rhino, Blender, or another backend never changes an +object's class. + +`SceneNode` owns only ordered parent/child relationships and a reference to one +scene-object GUID. Enforce exactly one node per scene object in one scene. +Instancing is not included. Multiple appearances of one data item use multiple +scene objects referencing the same item GUID. + +Do not serialize a second durable node GUID. The scene-object GUID is the +hierarchy key. The root is an ordinary `GroupObject` with a corresponding root +node rather than a special identity model. + +`Scene` owns separate object and hierarchy stores. `scene.add(item, **state)` +creates the stable semantic scene object, attaches its hierarchy node, and +returns the object. `scene.add_object(sceneobject, parent=...)` explicitly +attaches an existing durable scene object; `Scene.add` is not overloaded with +runtime adapters. + +Scene serialization contains a version, name, stable scene-object records, +and a hierarchy of scene-object GUID references. Referenced `Data` items remain +external under ADR 010. Loading requires no visualization backend and leaves +item references unresolved until explicitly bound. + +Durable type-specific state follows ADR 009's useful rule: a closed base schema +plus explicit fields. State lives as ordinary `SceneObject` attributes, not in +node extensions and not behind attribute interception. Unknown namespaced +state may be retained solely for lossless forward round trips. Backend plugins +may not add durable fields. + +Selection, picking, UI state, renderer configuration, generated buffers, +handles, and caches remain runtime/application state outside `SceneObject`. + +## Consequences + +- Nodes, scene objects, represented data, and backend operations have distinct + and explainable roles. +- Scene-object type and GUID remain stable through attachment, removal, + serialization, and context changes. +- A durable scene object may exist detached without acquiring a second storage + mode; attachment adds only a nonserialized scene backreference. +- Removing and explicitly reattaching an object never reveals stale fallback + values or changes identity. +- The scene schema changes from nested stateful nodes to object records plus a + hierarchy of references. +- Backend-specific replacement classes are removed under ADR 014. + +## Compatibility + +Preserve `scene.add(data, ...)`, shared item identity, unresolved scene loading, +explicit item binding, stable object GUIDs, and hierarchy operations through +returned scene objects. + +Remove backend adapter serialization, constructor-driven child creation, and +implicit group child defaults. `GroupObject` is durable and backend-neutral. +`compas_model`'s maintained controller path already uses `scene.add`; its custom +scene-object redesign remains a later phase. + +## Verification + +- Prove nodes serialize only hierarchy references. +- Prove one node per scene object and reject accidental instancing. +- Preserve scene-object GUIDs through add, move, remove, reattach, reload, and + visualization-context changes. +- Prove durable state has one storage location. +- Load, inspect, edit, and serialize a scene without backend plugins. +- Bind independently loaded items with shared identity. +- Round-trip multiple scene objects referencing one item. +- Exclude selection, handles, buffers, caches, and context from serialization. + +## Dependencies + +- Intended to supersede Accepted ADR 008 and ADR 009's placement of extension + state if approved. +- Retains ADR 009's closed-schema principle. +- Preserves Accepted ADR 010, with item references moving from nodes to scene + objects. +- Works with Proposed [ADR 014](014-context-keyed-scene-operations.md). + +## Decision + +Accepted on 2026-11-07. The user approved hierarchy-only nodes, stable durable +scene objects, one-to-one node/object cardinality without instancing, and the +scene-object GUID as the sole durable hierarchy key. diff --git a/initiatives/scene-viewer-visualisation/decisions/014-context-keyed-scene-operations.md b/initiatives/scene-viewer-visualisation/decisions/014-context-keyed-scene-operations.md new file mode 100644 index 0000000..6e50e8a --- /dev/null +++ b/initiatives/scene-viewer-visualisation/decisions/014-context-keyed-scene-operations.md @@ -0,0 +1,128 @@ +# ADR 014: Context-Keyed Scene Operation Plugins + +## Status + +Accepted. + +## Context + +Current visualization plugins register replacement `SceneObject` subclasses by +represented item type and context. `SceneObject.__new__` may therefore return a +Viewer, Rhino, Blender, Grasshopper, or Notebook subclass. This changes object +type based on installed plugins and mixes durable scene semantics with backend +runtime behavior. + +The general COMPAS pluggable dispatcher identifies extension points globally +by domain, category, and function name. It does not select an implementation +from a call's explicit context or represented object type. Directly registering +multiple `draw` plugins would therefore conflict when several backends are +installed. + +## Options + +1. Register operation functions in a small scene-specific registry keyed by + context, stable scene-object type, and operation. +2. Extend the general plugin manager with argument-aware dispatch. +3. Use one global backend plugin that switches internally on context and type. +4. Retain backend-specific replacement classes. + +## Recommendation + +Choose option 1. + +Backend packages expose lightweight registration plugins that register +functions rather than classes: + +```python +register_scene_operation( + context="Viewer", + sceneobject_type=MeshObject, + operation="draw", + implementation=draw_meshobject, +) +``` + +Lookup is deterministic by `(context, scene-object type MRO, operation)`. +Duplicate registrations for one exact key fail clearly. Heavy Qt, OpenGL, +Rhino, Blender, or other host imports remain inside operation bodies. + +The initial public operation surface is only: + +- `draw`; +- `clear`. + +Backend-private buffer extraction, host-object creation, component drawing, +refresh strategies, and cache structures do not become speculative public +operations. Additional operations require demonstrated cross-backend need and +approval. + +Conceptually: + +```python +scene.draw(context="Viewer", runtime=viewer) +meshobject.draw(context="Viewer", runtime=viewer) +``` + +The selected implementation receives the owning scene, stable scene object, +and optional transient backend runtime. It may return opaque handles. Runtime +resources live in a nonserialized context store keyed by context and +scene-object GUID, never on the durable object. + +There is no adapter-binding phase. Changing context selects different +operations and resources without replacing scene objects. Automatic context +selection is permitted only when exactly one compatible context is available. +Missing operations report context, scene-object type, and operation. + +Keep the general COMPAS plugin manager unchanged. Its existing lightweight +`register_scene_objects` pattern may be renamed or replaced by +`register_scene_operations`, but discovery must remain graphics-free. + +## Consequences + +- Several visualization backends can coexist without class or plugin + conflicts. +- Backend implementations become ordinary functions with explicit inputs. +- Stable scene-object classes remain backend-neutral. +- Runtime ownership is explicit and serialization-safe. +- Viewer requires a coordinated migration because its renderer currently + consumes concrete `ViewerSceneObject` buffer APIs rather than only `draw`. +- CAD, notebook, robots, and domain backends require later coordinated + migrations before they are admitted as compatible C3 candidates. + +## Compatibility + +Remove context-specific scene-object class registration, +`SceneObject(item, context=...)` factory substitution, `sceneobject_type=`, +`Scene.bind_context`, and direct one-shot `SceneObject(item).draw()`. + +Preserve explicit context selection, automatic selection when unambiguous, +graphics-free plugin discovery, `scene.add(data, ...)`, and deterministic +resource clearing. + +The initial coordinated implementation remains core, Viewer, and framework. +Other backends are recorded as incompatible until migrated; no compatibility +factory should retain type substitution. + +## Verification + +- Install Viewer and a fake second backend simultaneously and select each + explicitly. +- Verify deterministic MRO fallback and duplicate-registration errors. +- Verify missing-operation diagnostics. +- Prove discovery imports no graphics modules. +- Prove context changes never replace or mutate durable scene objects. +- Prove draw/clear resource handling does not enter serialized state. +- Preserve indexed Viewer buffers, picking by stable scene-object GUID, and + exact-candidate framework integration. + +## Dependencies + +- Depends on Proposed [ADR 013](013-hierarchy-nodes-and-stable-sceneobjects.md). +- Replaces context-specific class registration if approved. +- Leaves the general plugin dispatcher unchanged. + +## Decision + +Accepted on 2026-11-07. The user approved context-keyed operation functions, +the initial `draw`/`clear` surface, backend-neutral durable state, and +coordinated later migration of visualization hosts. diff --git a/initiatives/scene-viewer-visualisation/decisions/015-contextual-scene-form-contributions.md b/initiatives/scene-viewer-visualisation/decisions/015-contextual-scene-form-contributions.md new file mode 100644 index 0000000..fb5654f --- /dev/null +++ b/initiatives/scene-viewer-visualisation/decisions/015-contextual-scene-form-contributions.md @@ -0,0 +1,114 @@ +# ADR 015: Contextual Scene Form Contributions + +## Status + +Proposed. + +## Context + +Viewer's object-settings form currently decides which controls to show through +hardcoded attribute checks on private render helpers. This couples the form to +renderer aliases, makes capability exceptions such as face-less graph objects +ad hoc, and gives external packages no supported way to expose a specific +stable scene-object attribute or represented-item parameter. + +The accepted scene architecture keeps durable `SceneObject` types stable and +backend-neutral. ADR 014 deliberately limits core's context-keyed scene +operation registry to `draw` and `clear`. Forms, controls, layout, and refresh +behavior are Viewer UI concerns and must not become serialized scene state. + +## Options + +1. Add a Viewer-owned registry of declarative form contributions. +2. Let plugins construct and return arbitrary Viewer or Qt components. +3. Add form operations to core's context-keyed scene operation registry. +4. Store form annotations on durable scene-object classes or instances. +5. Retain hardcoded `hasattr`-based form construction. + +## Recommendation + +Choose option 1. + +`compas_viewer` owns a runtime registry keyed by: + +```text +(context, form, stable scene-object type, contribution ID) +``` + +The initial public form identifier is only `object-settings`. Registration is +lightweight and may occur during ordinary plugin discovery without importing +Qt, OpenGL, or another graphics runtime. A provider is called only when that +form is populated. It receives the stable scene object and the explicit +transient Viewer/runtime context, and returns declarative field links governed +by ADR 016. + +Applicable contributions are collected through the stable scene-object MRO, +from general types to specific types. Fields are sorted deterministically by +`(field.order, general-to-specific MRO rank, contribution_id, +provider-return sequence)`. Registering the same exact registry key twice is +an error. Producing the same final field ID twice is also an error; a more +specific provider does not silently replace another provider's field. + +If a provider raises, Viewer aborts population of the complete form and +reports the context, form identifier, stable scene-object type, and +contribution ID. It never silently omits a failed contribution or exposes a +partially rebuilt form. + +The Viewer creates the controls, applies its consistent layout and lifecycle, +and performs mutations through one binding path. Providers do not return +ready-made widgets. By contract, a provider must not mutate the scene object +or represented item while describing the form. + +The built-in object-settings controls migrate to built-in contributions. +Unsupported capabilities are omitted rather than inferred from renderer +attributes. For example, a graph contributes no face field, while mesh and +shape contributions may do so. The current private face-capability workaround +can then be removed. + +## Consequences + +- Packages can expose only the fields appropriate for a particular form and + Viewer context without replacing scene-object classes. +- Stable scene objects remain free of Viewer-specific form metadata. +- Plugin discovery remains graphics-free and control construction stays + consistent. +- The registry is intentionally a Viewer extension point, not a universal + form or schema framework. +- Arbitrary widgets, nested layouts, conditional schema languages, and other + form identifiers require demonstrated needs and separate approval. + +## Compatibility + +The first implementation replaces the hardcoded object-settings population +path without adding durable fields or changing scene serialization. The +existing general represented-item `ParameterForm` remains available. Explicit +item-parameter contributions may coexist with it; replacing the general dialog +is not part of this decision. + +## Verification + +- Compose general and specific contributions in the complete deterministic + order defined above. +- Reject duplicate exact registrations and duplicate final field IDs. +- Verify a provider exception reports the complete registry context, aborts + population, and leaves no partially rebuilt form. +- Verify Graph omits face controls while Mesh and Shape retain them. +- Verify providers are invoked only while the requested form is populated. +- Prove plugin discovery imports no graphics or UI modules. +- Verify built-in providers leave scene objects, represented items, and their + serialized data unchanged while describing a form. +- Verify two contexts can contribute different fields for the same stable + scene-object type. + +## Dependencies + +- Depends on Accepted ADR 013 for stable scene-object types. +- Respects ADR 014's restricted `draw`/`clear` operation surface and does not + extend the core scene operation registry. +- Its implementation depends on Proposed ADR 016 for the declarative field and + mutation contract; the Viewer ownership decision can be approved + independently. + +## Decision + +Pending explicit user approval. diff --git a/initiatives/scene-viewer-visualisation/decisions/016-scene-form-field-bindings.md b/initiatives/scene-viewer-visualisation/decisions/016-scene-form-field-bindings.md new file mode 100644 index 0000000..410b259 --- /dev/null +++ b/initiatives/scene-viewer-visualisation/decisions/016-scene-form-field-bindings.md @@ -0,0 +1,106 @@ +# ADR 016: Scene Form Field Bindings + +## Status + +Proposed. + +## Context + +ADR 015 requires one small declarative contract that links a form field to its +actual owner. The initial use cases need to edit either stable presentation +state on a `SceneObject` or a supported parameter on its externally bound +represented item. The contract must make ownership, mutation, refresh, and +failure behavior explicit without growing into a general form language. + +## Options + +1. Use flat, named attribute bindings with a fixed initial editor and refresh + vocabulary. +2. Accept arbitrary getters, setters, callbacks, validators, and refresh + functions on every field. +3. Resolve dotted paths dynamically from arbitrary runtime objects. +4. Infer editable fields from annotations, constructor signatures, or + `hasattr` checks. + +## Recommendation + +Choose option 1. + +Each field declares only: + +- a form-unique `id`; +- a user-facing `label`; +- `target`, either `sceneobject` or `item`; +- one flat named `attribute`; +- `editor`, initially `boolean`, `number`, `text`, or `color`; +- `refresh`, either `draw` or `geometry`; +- an explicit numeric `order`; and +- an optional flat `section` label. + +`sceneobject` targets bind a named public attribute of the stable scene object. +`item` targets bind a named writable attribute or supported parameter of the +explicitly bound represented item. Dotted paths, implicit item loading, nested +schemas, arbitrary widget factories, and per-field executable callbacks are +outside the initial contract. + +Before displaying an editable control, Viewer resolves the complete binding +and verifies that the target and attribute are available and writable. An +unresolved external item never triggers loading or lookup beyond the scene's +existing binding. Its item-targeted fields are omitted from the form. + +Mutation uses `setattr` on stable scene-object attributes. Item parameters +reuse the existing represented-item parameter mutation and rollback behavior +where it applies. A failed setter restores the previous value and control, +reports the original exception, and performs no refresh. + +After a successful mutation: + +- `draw` refreshes the object's appearance using existing render data; +- `geometry` rebuilds the object's render data and then redraws it. + +Form descriptors, resolved bindings, control values, validation errors, and +refresh state are transient Viewer state and are never serialized with a scene +or represented item. + +## Consequences + +- Ownership is visible at every field rather than inferred from similarly + named attributes. +- All contextual fields share one mutation, failure, and refresh path. +- The initial editor vocabulary matches existing Viewer controls. +- More complex editors or bindings require evidence and an amendment rather + than hidden escape hatches. +- The API may later gain narrowly justified metadata without revisiting the + Viewer ownership decision in ADR 015. + +## Compatibility + +Built-in object-settings controls map to equivalent declarative fields. The +general represented-item parameter dialog remains a separate explicit command +during the initial migration. No serialized scene format or represented-item +format changes. + +## Verification + +- Mutate a stable scene-object attribute and the intended represented-item + parameter through separate fields. +- Verify `draw` and `geometry` invoke only their specified refresh behavior. +- Verify read-only or missing attributes do not produce an editable binding. +- Verify a failed setter restores the old object and control values, reports + the failure, and does not redraw. +- Verify unresolved external items omit item-targeted fields without loading + data implicitly. +- Verify supported Boolean, numeric, text, and color fields use the existing + Viewer controls consistently. +- Prove field and binding state is absent from scene and item serialization. + +## Dependencies + +- Depends on Proposed ADR 015 for contribution registration, lookup, and + lifecycle. +- Reuses accepted stable scene-object and external item-binding semantics from + ADRs 010 and 013. + +## Decision + +Pending explicit user approval. diff --git a/initiatives/scene-viewer-visualisation/decisions/README.md b/initiatives/scene-viewer-visualisation/decisions/README.md new file mode 100644 index 0000000..af671c4 --- /dev/null +++ b/initiatives/scene-viewer-visualisation/decisions/README.md @@ -0,0 +1,30 @@ +# Scene, Viewer, and Visualisation Decisions + +ADRs 013–014 are Accepted and supersede the implemented node/runtime-adapter +model of ADR 008, part of ADR 009, and the unaccepted alternatives in ADRs +011–012. ADR 010 remains Accepted with item references moved to stable scene +objects. ADRs 015–016 propose a Viewer-owned mechanism for contextual +object-settings fields; they are not approved for implementation. + +| ADR | Decision | Status | Dependencies | +|---|---|---|---| +| 001 | [Ownership and state boundaries](001-ownership-boundaries.md) | Accepted | Geometry 022/024; Plugin 002 | +| 002 | [Indexed Viewer buffer geometry](002-indexed-buffer-geometry.md) | Accepted | 001; Typing structural inputs | +| 003 | [Viewer state and object selection](003-viewer-state-and-selection.md) | Accepted | 001 | +| 004 | [Transformations, refresh, and undo](004-transform-refresh-undo.md) | Accepted | 001, 003 | +| 005 | [Annotations, measurements, and overlays](005-annotations-and-overlays.md) | Accepted | 001, 002, 003 | +| 006 | [Public Viewer integration facade](006-viewer-integration-facade.md) | Accepted | 001, 003, 004, 005 | +| 007 | [Initial scope and verification](007-scope-and-verification.md) | Accepted | 001–006; Plugin 005 | +| 008 | [Serializable scene nodes and runtime adapters](008-scene-nodes-and-runtime-adapters.md) | Superseded by 013–014 | 001; verified round-trip regressions | +| 009 | [Explicit scene-state serialization fields](009-scene-state-fields.md) | Superseded in part by 013 | 008 | +| 010 | [Scene description, item bindings, and aggregation boundary](010-scene-description-and-item-bundling.md) | Accepted | 008, 009 | +| 011 | [Node-first scene construction and adapter lifetime](011-node-first-scene-adapters.md) | Superseded before acceptance | 008; 010 | +| 012 | [Explicit node-backed adapter state](012-explicit-node-backed-adapter-state.md) | Superseded before acceptance | 011 | +| 013 | [Hierarchy-only nodes and stable scene objects](013-hierarchy-nodes-and-stable-sceneobjects.md) | Accepted | Supersedes 008/009 placement; preserves 010 | +| 014 | [Context-keyed scene operation plugins](014-context-keyed-scene-operations.md) | Accepted | 013; replaces backend class registration | +| 015 | [Contextual scene form contributions](015-contextual-scene-form-contributions.md) | Proposed | 013; respects 014 | +| 016 | [Scene form field bindings](016-scene-form-field-bindings.md) | Proposed | 015; 010 and 013 semantics | + +ADRs 008–010 were accepted on 2026-11-07. ADR 010 refines ADR 008's item store +into non-owning runtime bindings and keeps aggregation outside Scene. A +redesigned Session is the likely aggregate under separate future approval. diff --git a/initiatives/scene-viewer-visualisation/handoff.md b/initiatives/scene-viewer-visualisation/handoff.md new file mode 100644 index 0000000..4ecefab --- /dev/null +++ b/initiatives/scene-viewer-visualisation/handoff.md @@ -0,0 +1,59 @@ +# Scene, Viewer, and Visualisation Handoff + +Last updated: 2026-11-07 + +## Resume here + +Current phase: `integration verification` + +ADRs 013–014 are implemented and independently approved in core and Viewer. +They replace the earlier adapter design with hierarchy-only nodes, stable +backend-neutral scene objects, and context-keyed `draw`/`clear` operations. +The refreshed exact-candidate framework gate is green, so the accepted Phase 2 +work may resume independently of the proposed contextual form extension. + +ADRs 015–016 now propose contextual object-settings contributions and explicit +field binding/mutation semantics. No implementation is authorized until both +are explicitly approved. + +Read next: + +1. [Implementation plan](implementation-plan.md) +2. [Decision index](decisions/README.md) +3. [Proposal](proposal.md) + +## Exact candidates + +| Repository | Branch | Exact head | Pull request | Status | +|---|---|---|---|---| +| `compas-dev/compas_framework` | `initiative/scene-viewer-visualisation` | `0fe8f4a` plus this evidence update | [7](https://github.com/compas-dev/compas_framework/pull/7) | Exact-source contracts and installed-candidate gate passed | +| `compas-dev/compas` | `initiative/scene-viewer-visualisation` | runtime candidate `d43db206afec36450efbe76b53306ce3bd468076`; test-only head `41e51713352` | [1540](https://github.com/compas-dev/compas/pull/1540) | Codecov patch/project, coverage, build, and NumPy checks passed; remaining matrix rerun pending | +| `compas-dev/compas_viewer` | `initiative/scene-viewer-visualisation` | `a72b61b777bc3fd448c3d471c48ef7f65334b319` | [233](https://github.com/compas-dev/compas_viewer/pull/233) | Complete hosted matrix passed | +| `BlockResearchGroup/compas_model` | `c3-upgrade` | `83df4167f7ba1539ae205a837e0238afecc0a36b` | None | Compatibility consumer only; no source changes authorized | + +## Verification evidence + +| Layer | Status | Evidence | +|---|---|---| +| Local package checks | Passed with one local-policy exclusion | Core focused scene/glTF checks, Ruff, Pyright, strict docs, and independent review passed. Viewer passed 161 applicable tests, Ruff, Pyright, strict docs, and independent review. Viewer’s ignored local `docs/examples/__temp` directory remains the sole excluded repository-policy assertion. | +| Framework integration | Passed | The refreshed gate covers exact revisions, graphics-free discovery, stable semantic types and GUIDs, hierarchy-only nodes, external item rebinding, context-keyed operations without class substitution, transient runtime exclusion, and indexed-buffer round trips. Local source execution passed 3 tests with the exact-install assertion skipped by design; hosted contracts and source-installed candidates passed. | +| Package CI | Viewer passed; core rerun in progress | Viewer build, coverage, policy, distribution, and all nine OS/Python quality jobs passed. Core's Codecov patch/project, coverage, build, and NumPy checks passed after focused closed-schema tests raised expected patch coverage from 85.59% to 86.10%; the remaining matrix jobs are rerunning. | +| Candidate artifacts | Not run | Required before independently built artifact compatibility is claimed. | + +The broader `tests/test_package.py` run is locally unavailable because the +active `compas3` editable-import hook resolves `compas_cgal` from +`/private/tmp/compas-cgal-typing-adr017` instead of its checked-out sibling. +This is an environment provenance failure, not a scene-gate failure. + +## Next tasks + +1. Review and approve or revise Proposed ADRs 015–016. +2. Confirm the remaining core PR 1540 matrix rerun completes green. +3. Resume Phase 2 from the verified stable-scene foundation after its approval gates are clear. + +## Approval gates + +- The accepted initial implementation scope is core, Viewer, and framework. +- Adding model, session, notebook, or host packages requires explicit approval. +- Contextual form implementation requires explicit approval of ADRs 015–016. +- Do not merge or publish without explicit approval. diff --git a/initiatives/scene-viewer-visualisation/implementation-plan.md b/initiatives/scene-viewer-visualisation/implementation-plan.md new file mode 100644 index 0000000..d8a5119 --- /dev/null +++ b/initiatives/scene-viewer-visualisation/implementation-plan.md @@ -0,0 +1,171 @@ +# Scene, Viewer, and Visualisation Implementation Plan + +## Status + +Architecture correction implemented and verified. ADRs 013–014 are Accepted +and supersede the adapter model. Phase 2 may resume. Contextual form work is +blocked on Proposed ADRs 015–016. + +## Dependency graph + +```text +ADR 001 --> ADR 008 --> ADR 009 --> ADR 010 + | + +--------> ADR 002 + +--------> ADR 003 --> ADR 004 + +ADRs 001–003 ----------> ADR 005 +ADRs 001, 003–005 -----> ADR 006 +ADRs 001–010 ----------> ADR 007 rollout gate +``` + +## Phase 0 — Scene ownership and serialization amendment + +Governing ADRs: 001, 008, 009, and 010. + +Status: implemented, but subject to the Accepted ADR 013–014 correction before +Phase 2 proceeds. + +1. Add the verified Viewer round-trip regression and a minimal core adapter + test covering the constructor-before-attachment failure. Keep the actual + draft ModelViewer failure as evidence for its later redesign. +2. Audit Scene, SceneObject, Group, Viewer adapters, the draft model adapters, + and compatibility-only consumers against the proposed node/adapter split. +3. Implement the closed scene node/state schema, durable node GUIDs, + reference-only descriptions, runtime item bindings, and two-phase + deserialize/bind lifecycle. +4. Implement explicit MRO-composed extension field declarations; preserve + unknown namespaced extension state within the new schema. +5. Migrate core and Viewer adapters without serializing caches, selection, + construction commands, or runtime handles. Use the draft ModelViewer failure + as external regression evidence, not as behavior to preserve. +6. Document the separate future Session aggregation boundary without adding a + `SceneBundle`, URI resolver, asset registry, or Session implementation. + +Exit gate: the Viewer and minimal-adapter regressions pass; shared item +identity and durable node identity round-trip separately; scenes load without +a visualization backend; runtime adapters bind only after the complete +hierarchy exists; independently loaded items bind without copying; unresolved +references follow ADR 010; local/package quality, typing, docs, and artifact +checks pass. + +## Phase 1 — Boundaries and indexed geometry + +Governing ADRs: 001, 002, 007, 008, 009, and 010. + +Status: implemented and verified. + +1. Audit compatibility-only consumers for affected core scene contracts and + stop for approval if a required core change would break them. +2. Implement indexed `BufferGeometry` and convenience constructors in Viewer. +3. Migrate Viewer adapters and buffer assembly without expanding indexed data. +4. Add performance-regression assertions based on buffer/object counts rather + than unstable wall-clock thresholds. + +Exit gate: package-local source, typing, artifact, rendering-semantic, and +large-indexed-input evidence passes at exact candidate heads. + +## Phase 2 — State, selection, and transforms + +Governing ADRs: 001, 003, 004, 007, 008, and 009. + +Status: ready on the implemented and verified ADR 013–014 scene foundation. + +1. Separate persistent Viewer configuration from transient Viewer state. +2. Implement the object-selection model and migrate viewport, rectangle, tree, + commands, renderer settings, and programmatic Viewer APIs. +3. Remove `is_selected` as a second authority and update serialization/tests. +4. Add atomic multi-object transform transactions, categorized refresh, and + the reversible transaction stack. +5. Add a minimal transform interaction only after command/programmatic + semantics pass; visual gizmo design may be a follow-up if it requires new + choices. + +Exit gate: all selection paths share one model; transform/undo tests cover +parents, batches, failures, and buffer-update scope. + +## Proposed contextual object-settings work + +Governing ADRs: 013, 014, 015, and 016. + +Status: blocked pending explicit approval of ADRs 015–016. + +1. Add the lightweight Viewer-owned contribution registry and deterministic + MRO composition. +2. Add the flat field descriptor and the two explicit binding targets, + editor sets, and refresh modes. +3. Migrate built-in object-settings controls and remove attribute-driven + capability inference. +4. Add package contributions only for demonstrated fields; keep plugin + discovery free of Qt and graphics imports. +5. Verify mutation, rollback, unresolved-item behavior, refresh scope, + provider failure atomicity, serialization exclusion, and multiple contexts. + +Exit gate: object-settings fields are explicit and contextual; stable scene +objects and external items retain their accepted ownership and serialization; +all applicable Viewer package and framework integration checks pass. + +## Phase 3 — Annotations and overlays + +Governing ADRs: 001–005 and 007. + +1. Inspect current Tag/Arrow serialized behavior and document the exact + migration. +2. Implement world text annotation and distance dimension scene items/adapters. +3. Implement measurement tool preview, cancel, and completion. +4. Implement scale and legend screen overlays through one Viewer overlay layer. +5. Remove concrete approved-type checks from the main render loop. + +Exit gate: durable annotation round trips, transient overlays do not serialize, +and visual behavior passes offscreen and package artifact tests. + +## Phase 4 — Public Viewer operations + +Governing ADRs: 001, 003–007. + +1. Add public refresh, history, command/menu, dock, annotation, and overlay + operations required by approved Viewer-owned features. +2. Add direct script examples for indexed buffers, selection, transforms, + measurements, overlays, and one custom dock. +3. Update core and Viewer documentation using repository skills and + current MkDocs-compatible NumPy style. + +Exit gate: Viewer-owned tools and components use public Viewer operations and +package-local tests/docs/artifacts pass. + +## Later phase — compas_model redesign + +The current `ModelViewer` is a draft and is not migrated as a compatibility +exercise. A later approved design may replace its controller, hierarchy +projections, selection, caching, and UI composition. It should consume proven +core Scene and Viewer contracts where they fit, and propose additions only for +new demonstrated requirements. + +## Phase 5 — Framework integration and review + +Governing ADR: 007. + +1. Verify installed core Scene and Viewer behavior at exact candidates; keep + the separate plugin initiative's model/viewer lifecycle gate independent. +2. Install exact candidate revisions and exercise the approved cross-package + runtime and typing contracts. +3. Run package and framework CI, candidate-artifact verification where + applicable, and independent code/documentation review. +4. Resolve findings, update the handoff with evidence by layer, and request + explicit merge approval. + +Exit gate: all applicable verification layers pass at recorded exact heads; +required unavailable evidence remains an explicit blocker. + +## Deferred work requiring new approval + +- sub-object or domain-component selection; +- ancestor/descendant group-transform semantics and advanced gizmos; +- angular, radial, area, or snapping measurement systems; +- a general overlay/plugin registry; +- console, terminal, Python/shell execution, assistant services, credentials, + or remote transports; +- `compas_session` reactive-state integration; and +- `compas_session` aggregation of data, one or more scenes, settings, and + history, including optional Viewer new/open/save conveniences; +- notebook, Rhino, Grasshopper, Blender, or other backend migrations. diff --git a/initiatives/scene-viewer-visualisation/inventory.md b/initiatives/scene-viewer-visualisation/inventory.md new file mode 100644 index 0000000..358c566 --- /dev/null +++ b/initiatives/scene-viewer-visualisation/inventory.md @@ -0,0 +1,181 @@ +# Scene, Viewer, and Visualisation Inventory + +Inventory date: 2026-11-07. + +## Audited checkpoints + +| Repository | Branch | Exact head | State | +|---|---|---|---| +| `compas-dev/compas` | `remove-future` | `99b82a6673ff7d05c1c323c8921135c274993f11` | Clean | +| `compas-dev/compas_viewer` | `c3-upgrade` | `cf5a5195bda840a511319d6b7201978af68c15e3` | Clean | +| `BlockResearchGroup/compas_model` | `c3-upgrade` | `83df4167f7ba1539ae205a837e0238afecc0a36b` | Clean | +| `compas-dev/compas_session` | `c3-upgrade` | `e0bdd02aa8490248c6f2f36042651d8ebfc4442b` | Clean | +| `compas-dev/compas_framework` | `initiative/scene-viewer-visualisation` | Based on `e797d49f77ac32b65371497a21925a02b7d048d8` | Clean before initiative documents | + +## Core scene infrastructure + +`compas.scene.Scene` owns a serializable tree, item and scene-object stores, +context selection, hierarchy mutation, drawing traversal, and glTF conversion. +`SceneObject` combines serializable visual configuration with context-adapter +factory dispatch, local/world transformations, renderer identifiers, and +manual cache invalidation. + +The global context registry maps item types to adapter classes and uses plugin +registration. Its lifecycle offers `clear`, `before_draw`, and `after_draw`, +but no renderer capability, event, change-notification, reverse-picking, or +incremental-update contract. Core has no common indexed render-buffer type. + +Core tests cover context registration, hierarchy, transformations, +serialization, glTF conversion, and scene ownership. They do not cover +selection, interaction state, undo, incremental redraw, overlays, or batched +render data. The visualisation introduction is stale in its use of `data`, its +transformation explanation, and its assumptions about available adapters. + +## Viewer architecture + +`Viewer` coordinates the Qt application, `ViewerScene`, renderer, +`ViewerState`, commands, events, and UI as a singleton. `ViewerScene` fixes the +context to `"Viewer"`, maps picking colors, and directly coordinates renderer +and sidebar updates. `ViewerSceneObject` converts items into point, line, +front-face, and back-face channels and reaches the active viewer through that +singleton. + +`BufferManager` efficiently batches ordinary objects into shared GPU-oriented +buffers and can update transforms without rebuilding topology. However, +`BufferGeometry` accepts expanded point, line-vertex, and triangle-vertex +arrays rather than shared vertices plus edge or triangle indices. Examples and +several adapters therefore duplicate vertices manually. `TessellationObject` +and `PolygonalSurfaceObject` already demonstrate indexed paths, but there is no +uniform public convenience layer. + +Viewport and scene-tree multi-selection exist. Selection is stored as mutable +`is_selected` flags on scene objects and synchronized by imperative refresh +calls. There is no separate observable selection model, selection-change +event, complete rectangle-deselection path, or common programmatic mutation +contract. + +Programmatic transforms and efficient transform-buffer updates exist, but +there is no object gizmo, transformation command, multi-object transform UI, +or defined undo contract. `Tag` and `ArrowOverlay` provide useful text and +arrow primitives, but the renderer special-cases their concrete classes. No +measurement, dimension, scale-bar, legend, or general overlay contract exists. + +`ViewerState` provides observable dotted-path access to persistent +configuration. Selection, active tools, gestures, scene mutations, and command +execution live elsewhere. Commands and UI definitions offer useful extension +points, but there is no comparable public dock composition API and no console, +terminal, assistant, or safe execution boundary. + +## Draft model integration evidence + +`compas_model` owns backend-neutral model scene adapters, Viewer-specific +adapters, a `ModelViewer` controller, NumPy-based `RenderGeometry`, quality +settings, a revision-aware CPU cache, hierarchy projections, selection, +inspectors, and contact layers. + +The draft treats `Model.tree` and rendered `Scene` hierarchy as separate. +remain separate. `ModelViewer` constructs and indexes the scene projection, +but reaches into concrete viewer sidebar and renderer APIs. Structural changes +are reconciled through explicit rebuilds rather than an observed projection +contract. + +Draft model render geometry preserves separate indexed filled triangles and visible +line segments, but Viewer-specific adapters expand some arrays back to Python +lists. This makes buffer ownership and reusable conversion responsibilities +unclear. Package tests and documentation describe the current controller, +including multi-selection, visibility, caching, contacts, and offscreen viewer +integration. They establish implementation evidence, not public behavior that +this initiative must preserve. + +## Adjacent packages + +`compas_session` persists a generic core scene, data, settings, and undo/redo +history, but it is not a reactive viewer/UI state store. Notebook and CAD-host +packages consume core scene contracts under different runtime constraints. +They should be checked for compatibility with approved core changes, but +implementation migration would broaden the initial initiative materially. + +## Framework evidence + +Package-local core and Viewer tests provide substantial evidence. Draft model +tests provide additional coupling and regression evidence. The +framework repository currently has no installed cross-package scene/viewer +test. The plugin initiative separately requires a coordinated installed +model/viewer registry and lifecycle test; this initiative should coordinate +that gate rather than duplicate it. + +Focused reconnaissance results: + +- Core scene/plugin/glTF suite: 53 passed; one non-authoritative cache-write + warning because the checkout cache directory was not writable. +- Viewer selection/state/tessellation/tag/overlay suite: 35 passed offscreen. +- Draft model integration behavior was inspected from its existing tests and + build workflow; no new model tests were run and those tests are not treated + as preservation requirements. + +## Principal gaps and risks + +1. Durable scene state, persistent configuration, and transient interaction + state do not have explicit ownership boundaries. +2. Renderer identity has no common reverse mapping to scene objects or domain + elements. +3. Scene mutations, selection, renderer updates, and UI refreshes use + distributed imperative paths. +4. Raw indexed buffers lack a uniform public input and batching contract. +5. Overlay rendering is coupled to concrete Viewer classes. +6. The draft model controller demonstrates coupling to private or concrete + Viewer structure, but its shape is not an architectural constraint. +7. Core appearance serialization and glTF export do not cover all visual + settings consistently. +8. Per-object pickability conflicts with batching for large auxiliary data. +9. Singleton coupling makes state, commands, rendering, and scene behavior + harder to test independently. +10. Scope can expand rapidly across notebook and host adapters unless initial + and compatibility-only packages are explicit. + +## Scene state and serialization follow-up + +The current scene format is not ordinary `SceneObject` serialization. `Scene` +stores every domain item once by GUID and writes hierarchy nodes as an optional +item GUID, a free-form `settings` mapping, and children. Loading replays those +settings through `Scene.add`, which selects and constructs a runtime adapter +before the reconstructed hierarchy is complete. Multiple nodes can correctly +share one item, but node/adapter GUIDs are not preserved. + +Two regressions were reproduced at the audited heads: + +- a Viewer Box/ShapeObject translated by `[2, 0, 0]` reloads without its + transformation because `ViewerSceneObject.settings` replaces the base + mapping; `u=7`, `v=9`, and face color also reload as defaults; +- a `ModelViewer` scene fails to load because constructor replay uses + `ModelObject(add_elements=True)` before attachment, causing its child + creation to raise `RuntimeError: A detached scene object cannot own child + objects.` + +Unrestricted serialization introspection is unsafe. Concrete COMPAS objects +contain cycles, scene/item backreferences, GPU and picking identifiers, native +handles, lazy caches, derived NumPy buffers, and constructor commands. Public +properties can be computed or side-effecting, while persistent descriptor +values are often stored privately. Constructor signatures also lose inherited +state behind `**kwargs`. + +Introspection remains useful when applied to a closed declared schema: it can +validate readable fields, constructor compatibility, and editor metadata. It +cannot safely determine persistence from `__dict__`, public names, +annotations, properties, or constructor parameters alone. + +## Architecture questions + +1. Does core own only scene hierarchy and transformations, or also neutral + render primitives and change events? +2. Where do indexed buffers, picking identities, and reverse lookup live? +3. What observable state model unifies viewport, tree, and programmatic + selection without making UI state serializable scene data? +4. What change categories allow transform-only, style-only, geometry, and + hierarchy updates to remain efficient? +5. Which annotation and overlay objects are durable, transient, or both? +6. Which Viewer capabilities should be public because Viewer-owned tools and + components require them, independently of a draft domain controller? +7. What is the smallest safe extension boundary for docks, consoles, and + optional assistant integrations? +8. Which scene appearance semantics must serialization and glTF preserve? diff --git a/initiatives/scene-viewer-visualisation/objective.md b/initiatives/scene-viewer-visualisation/objective.md new file mode 100644 index 0000000..f1e0231 --- /dev/null +++ b/initiatives/scene-viewer-visualisation/objective.md @@ -0,0 +1,113 @@ +# Scene, Viewer, and Visualisation Initiative + +## Objective + +Establish a simple, coherent, and scalable visualisation architecture across +COMPAS core scenes and `compas_viewer`. Users should be able to visualise large indexed datasets, +select and transform objects consistently, add measurements and annotations, +and extend the viewer UI without coupling durable scene data to a particular +renderer or Qt widget hierarchy. + +The initiative should clarify ownership before adding features. Core scene +data, renderer-facing geometry, persistent configuration, transient tool and +selection state, and domain-specific controllers must each have one clear +home and contract. + +## Questions to resolve + +1. Which data and behavior belong to backend-neutral `compas.scene`, the + viewer renderer, viewer application state, and package-specific adapters? +2. What indexed point, line, and triangle buffer contract supports efficient + visualisation without requiring heavyweight core geometry objects? +3. How should selection, element picking, hover, and multi-selection be shared + by the viewport, scene graph, and programmatic consumers? +4. What mutation and undo contract supports single- and multi-object + transformations? +5. Which annotations, measurements, scale indicators, legends, and overlays + are durable scene data, and which are transient tool or UI state? +6. How should persistent configuration be separated from reactive session, + tool, and UI state? +7. Which stable extension boundary can support docks, consoles, terminals, or + optional assistants without embedding those concerns in core scene data? +8. Which public Viewer operations are required by its own tools, UI components, + and independent future consumers? +9. Which local, package, framework-integration, and artifact evidence is + required for each approved wave? +10. How can scene nodes reference independently serialized `Data` objects while + leaving optional aggregation to a separately designed Session? + +## Design principles + +- Keep core scene infrastructure backend-neutral. +- Preserve indexed data and batch rendering where possible; do not expand a + large edge or triangle set into one Python object per primitive. +- Keep raw NumPy-compatible buffers valid inputs without forcing conversion + through COMPAS geometry or data objects. +- Keep referenced `Data` independently serializable and algorithm-ready; keep + optional Session aggregation separate from Scene. +- Separate durable scene state, persistent viewer configuration, and transient + interaction state. +- Provide one observable selection path for viewport, scene graph, and code. +- Prefer explicit, small data contracts over renderer-specific base classes or + speculative extension frameworks. +- Keep plugin discovery graphics-free and optional UI/runtime imports lazy. +- Reuse accepted tessellation, typing, plugin, and repository-modernisation + contracts rather than duplicating them. +- Make high-level convenience APIs simple while keeping ownership and + performance costs visible. + +## Refined initial scope + +- `compas`: scene hierarchy, scene-object identity and transformations, and + any approved backend-neutral contracts. +- `compas_viewer`: render buffers, GPU lifecycle, viewport interaction, + selection, overlays, commands, and UI state. +- `compas_framework`: installed cross-package verification. + +## Deliverables + +1. A verified cross-package inventory and approved architecture decisions. +2. Clear ownership and state boundaries across core scene and Viewer runtime + concerns. +3. Efficient convenience APIs for indexed points, edges, and triangles. +4. Consistent observable selection and transformation contracts. +5. An extensible annotation, measurement, and overlay foundation. +6. Public integration boundaries for Viewer tools, components, and optional + UI extensions. +7. Package-local and framework-level tests, examples, and documentation. +8. Reference-only scene descriptions with deterministic GUID binding and + unresolved-reference behavior, plus a clear boundary for a separately + approved Session aggregate. + +## Non-goals without a later scope decision + +- Replacing the core plugin manager or accepted geometry conversion APIs. +- Making Qt, OpenGL, or viewer-specific state a core COMPAS dependency. +- Implementing a chatbot service, shell execution engine, or remote transport + before the UI extension and security boundaries are approved. +- Migrating notebook, Rhino, Grasshopper, or Blender adapters in the initial + wave. +- Turning `compas_session` into a general reactive application store without + a separate persistence decision. +- Preserving the current draft `compas_model.ModelViewer` controller shape or + behavior. It is a later redesign consumer, not an architectural constraint. +- Adding modeling functionality to the viewer or duplicating domain data + structures in the scene graph. + +## Dependencies + +- Geometry-tessellation ADRs 022 and 024 govern transfer-oriented + `Tessellation`, producer conversions, and representation-preserving mesh + triangulation. +- Typing-alignment decisions govern structural NumPy-compatible inputs. +- Plugin-implementations decisions govern lightweight Viewer-context + registration and leave coordinated model/viewer lifecycle evidence open. +- Repository-modernisation decisions govern package workflows and artifact + verification. + +## Current status + +Reconnaissance and architecture are complete. ADRs 013–014 are Accepted and +the scene architecture correction may proceed within the approved initial +scope before Phase 2 resumes. The current draft `ModelViewer` is evidence and +a later redesign target, not a contract. diff --git a/initiatives/scene-viewer-visualisation/proposal.md b/initiatives/scene-viewer-visualisation/proposal.md new file mode 100644 index 0000000..50e28cc --- /dev/null +++ b/initiatives/scene-viewer-visualisation/proposal.md @@ -0,0 +1,449 @@ +# Scene, Viewer, and Visualisation Proposal + +## Summary + +Keep `compas.scene` responsible for durable, backend-neutral scene content and +hierarchy. Put indexed render buffers, picking, selection, transient state, +tools, overlays, GPU synchronization, and UI composition in `compas_viewer`. +Expose only the public operations required by Viewer-owned tools and UI +components. Future integrations may consume that boundary after it is proven. + +The first implementation wave should establish these boundaries and deliver +object selection, efficient indexed geometry, multi-object transforms, +measurements, annotations, scale/legend overlays, and public dock/command +composition. It should not attempt a universal renderer API, a generic reactive +framework, sub-object editing, a shell, or host-application migration. + +ADRs 013–014 are Accepted, implemented, and verified. Session redesign and +Viewer/Session convenience operations remain later work. + +ADRs 015–016 propose a later Viewer-owned contextual form mechanism. They are +not approved for implementation. + +## New evidence requiring amendment + +- A Viewer Box/ShapeObject local translation `[2, 0, 0]` reloads as + `transformation is None` because `ViewerSceneObject.settings` replaces core + settings. A headless ShapeObject with `u=7`, `v=9`, and a face color reloads + with resolution defaults `16`, `16`, and the default face color. +- A ModelViewer scene fails while loading. Runtime factory dispatch constructs + `ModelObject` with its default `add_elements=True`; it calls `self.add` + before attachment and raises `RuntimeError: A detached scene object cannot + own child objects.` The original controller deliberately used + `add_elements=False` and `add_children=False`. Render settings and caches are + absent too, but caches are transient and should stay out of scene files. +- Shared item identity survives through the item GUID store. Scene-object GUIDs + do not survive, so they cannot yet serve as the durable identity assumed by + ADR 001. + +## Recommended architecture + +### Durable scene content and runtime adapters + +Core `Scene` remains the backend-neutral serializable hierarchy, but its +durable entries become scene-owned node/state value objects rather than runtime +`SceneObject` adapters. Nodes preserve their own GUID, optional item GUID, +local transformation, hierarchy, visibility, base appearance, and explicitly +declared extension state. Runtime item bindings preserve shared identity +without making Scene the owner or serialization container of those items. + +Runtime `SceneObject` instances bind a complete node and item to a chosen +visualization context. They are not serialized identities and may be created +only after deserialization has rebuilt the hierarchy and resolved the required +item bindings. +Their constructors do not populate hierarchy. Scene context is runtime binding +configuration; absence of a backend cannot prevent loading durable content. + +Durable node GUIDs are the stable boundary between scene content and Viewer +state. Renderer pick IDs remain short-lived Viewer mappings and are never +serialized. Unknown namespaced adapter state remains inactive but round-trips +without importing that adapter. + +Base node state uses a closed core schema. Additional durable fields use +explicit MRO-composed declarations. Introspection may validate declarations +and support editors, but `__dict__`, public properties, annotations, and +constructor parameters never define persistence automatically. + +### Scene descriptions, item bindings, and aggregation + +Durable scene nodes reference items by stable item GUID. Serialized `Scene` +contains the visual node hierarchy and state but does not require referenced +`Data` objects to live inside it. At runtime Scene binds already loaded objects +to those GUIDs without taking exclusive ownership; the same object remains +independently serializable and directly usable by algorithms. + +A separately loaded description may contain unresolved references. Its +hierarchy and node state remain inspectable, and callers can bind items later. +All nodes sharing one GUID resolve to the same bound instance. Item-dependent +adapter, draw, and packaging operations fail precisely when the required GUID +is unresolved. + +A redesigned `compas_session.Session` is the likely optional aggregate for +independently serialized data, one or more scene descriptions, settings, +history, and their shared identity. Its design and implementation require a +separate dependent decision and scope approval; this initiative does not add a +redundant one-scene `SceneBundle`. Scene remains usable without Session. + +Do not add URI resolution, filesystem search, network loading, or an asset +registry without a concrete future requirement. + +### Indexed render geometry + +`compas_viewer` owns one public indexed `BufferGeometry` contract: + +```python +geometry = BufferGeometry( + vertices=vertices, + points=point_indices, + edges=edge_indices, + triangles=triangle_indices, +) +``` + +Vertices accept the approved structural numerical input, including Python +sequences and NumPy arrays. Index channels are optional and retain their +connectivity. Convenience constructors cover the common direct cases: + +```python +BufferGeometry.from_points(vertices) +BufferGeometry.from_vertices_and_edges(vertices, edges) +BufferGeometry.from_vertices_and_triangles(vertices, triangles) +``` + +Construction normalizes each supplied channel once to the Viewer's required +NumPy dtype and validates only rank, column count, and index bounds. It does +not create `Mesh`, `PolygonalSurface`, `Tessellation`, or one object per +primitive. Styling stays on the scene object; a separate color-buffer decision +is required only when current uniform/per-item styling proves insufficient. + +Existing Viewer adapters should return indexed channels to the buffer manager +instead of expanding geometry. Geometry producers continue to use their approved +`to_tessellation` and `to_polygonal_surface` APIs; this Viewer contract does +not become a geometry-algorithm input. + +### Viewer state and selection + +Separate three state classes: + +| State | Owner | Persistence | +|---|---|---| +| Scene items, hierarchy, local transformations, durable appearance | `compas.scene` | Scene serialization | +| Viewer defaults, appearance, key bindings, layout preferences | `Viewer.config` | Viewer configuration | +| Selection, hover, active tool, gestures, and transient tool results | `Viewer.state` | Session-only | + +`Viewer.state.selection` is the one object-selection model. It stores ordered +scene-object GUIDs and exposes replace, add, remove, toggle, clear, and query +operations plus one change notification. Viewport picking, rectangle +selection, the scene tree, commands, and programmatic consumers all mutate +this model. +The current mutable and serialized `ViewerSceneObject.is_selected` path is +removed rather than retained as a second authority. + +Initial selection is object-level. The architecture reserves no generic +component-token schema; vertex, edge, face, or domain-element selection should +be proposed when a concrete editing workflow requires it. + +The state implementation should be a small Python model with a Qt bridge, not +a generic observable dictionary or third-party reactive framework. The +renderer reads selection state when updating its per-object settings buffer. + +### Transformations and undo + +Core transformations remain authoritative. Viewer exposes one public +multi-object transformation operation and uses it from code, commands, and a +future gizmo: + +```python +viewer.transform_objects(objects, transformation, space="world") +``` + +The initial operation applies one world-space delta to every selected object's +pre-operation world transformation. For each object, Viewer derives the new +local transformation from its parent world transformation. Ancestor/descendant +pairs in one request are rejected initially because applying the same delta to +both is ambiguous. Invalid targets leave all objects unchanged. + +Every successful call is one transaction containing the before and after local +transformations. A small Viewer undo stack handles these explicit reversible +transactions. Existing callback commands remain valid; only mutating commands +that implement the reversible transaction contract enter history. This does +not move general application/session history into Viewer or `compas_session`. + +Viewer retains explicit refresh operations for geometry, appearance, +transform, and hierarchy changes. The initial wave does not add a generic core +scene event bus: direct external mutation still requires the corresponding +public Viewer refresh call. This keeps synchronization costs explicit and +avoids making all core scene properties observable. + +### Annotations and overlays + +Use two explicit paths: + +- World-anchored annotations are serializable scene items with Viewer scene + adapters. The initial types are text annotation and distance dimension. A + distance dimension stores its two world anchors, display unit/precision, and + optional label override; its measured value is derived, not duplicated. +- Screen-space overlays are transient Viewer UI objects. The initial types are + scale indicator and legend. They are not scene items and do not participate + in scene serialization or picking unless a later requirement says so. + +Existing `Tag` and `ArrowOverlay` should be aligned with these two paths rather +than leaving concrete-class checks in the renderer. Do not introduce a +framework-wide overlay registry. One Viewer-owned world-annotation adapter +path and one Viewer overlay layer are sufficient for the approved types. + +The measurement tool owns temporary pointer/preview state in `Viewer.state`. +Completing a measurement creates a durable distance-dimension scene item; +cancelling it leaves no scene mutation. + +### Public Viewer operations + +Expose the small set of operations that Viewer-owned commands, tools, and UI +components need without coupling them to renderer or widget internals: + +- `viewer.scene` for durable content; +- `viewer.state.selection` for selection; +- `viewer.refresh_geometry`, `refresh_appearance`, `refresh_transform`, and + `refresh_hierarchy` for explicit synchronization; +- `viewer.transform_objects` and undo/redo; +- public command registration and menu placement; +- public dock addition/removal using Viewer component/widget content; and +- public annotation and overlay addition. + +These operations are justified and tested inside Viewer. They are not designed +around the current draft `compas_model.ModelViewer`. That controller may be +redesigned freely in a later consumer phase, at which point it can use the +small proven Viewer boundary or motivate a separately approved addition. + +A console, terminal, or assistant can later occupy a public dock and invoke +registered commands, but arbitrary code execution, shell access, credentials, +remote transports, and assistant policy are explicitly outside this wave and +require a security decision. + +## Alternatives and tradeoffs + +### Put render buffers in core + +Rejected for the initial wave. Core would gain renderer-oriented dtype, +channel, and update assumptions while notebook and host backends have not +demonstrated a shared contract. Structural geometry inputs and `Tessellation` +already cover neutral transfer needs; GPU-ready batching belongs to Viewer. + +### Use scene-object flags as selection state + +Rejected. It makes transient interaction state serializable, requires every UI +to poll/mutate objects, and leaves viewport, tree, domain controller, and +renderer synchronization distributed. + +### Add a universal reactive store and scene event bus + +Rejected for the initial wave. It adds a second application framework and +forces observability into core without established semantics for arbitrary +item mutation. Explicit state models and refresh categories satisfy current +requirements more transparently. + +### Make all annotations screen overlays + +Rejected. Measurements and world labels need world anchors, scene hierarchy, +transformations, and persistence. Conversely, scale indicators and legends are +view-dependent and should not become durable geometry. + +### Standardize every visualisation backend now + +Rejected. Notebook and CAD hosts are important compatibility consumers, but +their runtime and rendering models differ. The first wave should avoid breaking +their core scene contracts and record follow-up work only after the Viewer +design is proven. + +## Compatibility implications + +- Removing `ViewerSceneObject.is_selected` is a deliberate Viewer API break. + Commands, tree widgets, shaders, and tests migrate in one coordinated change; + no writable compatibility alias remains. +- `BufferGeometry` construction changes from expanded channel vertices to + shared indexed vertices. Current examples and adapters migrate together. + A deprecated parallel constructor is not proposed. +- Existing core scene serialization is not sufficient for the approved + architecture. ADRs 008–009 define a new explicit node schema without a + compatibility loader for existing scene files. ADR 010 separates + reference-only descriptions from a possible future Session aggregate; + implementation is blocked pending its approval. +- Current `Tag` and `ArrowOverlay` data may require a documented migration to + the accepted annotation/overlay types. Exact compatibility should be checked + against serialized fixtures before implementation; do not silently reinterpret + ambiguous old data. +- The current draft `compas_model.ModelViewer` behavior, hierarchy projections, + selection behavior, controller shape, and cache arrangement are not + compatibility requirements. Its failure remains evidence for the scene + reconstruction problem, and its redesign is deferred. +- Viewer plugin discovery remains graphics-free and function-local under the + accepted plugin ADRs. + +## Affected packages + +| Package | Responsibility | +|---|---| +| `compas` | Preserve scene identity, hierarchy, transformations, durable appearance, and serialization; clarify stale documentation. | +| `compas_viewer` | Implement indexed buffers, state/selection, transforms/history, annotations, overlays, facade, rendering, and UI. | +| `compas_model` | Later redesign consumer and regression evidence; no current draft controller behavior is preserved by this wave. | +| `compas_framework` | Pin exact candidates and verify installed cross-package scene, plugin, selection, transform, and buffer behavior. | + +`compas_session`, `compas_notebook`, Rhino, Grasshopper, and Blender are +compatibility-reviewed but not implementation targets in the initial wave. + +## Tests and examples + +### Core + +- Preserve scene serialization, shared-item identity, hierarchy, and local/ + world transformation tests. +- Add the verified Viewer transformation/ShapeObject settings regression. + Preserve the ModelViewer detached-child failure as evidence and cover its + core failure mode with a minimal adapter whose constructor must not run + during hierarchy deserialization; the draft controller itself is deferred. +- Preserve durable node GUIDs separately from shared item GUIDs and round-trip + unknown namespaced extension state without its runtime adapter. +- Load independently serialized items, bind them to reference-only scene + descriptions, preserve shared identity, and verify unresolved-reference + behavior without Session. +- Add regression coverage proving transient Viewer state is absent from core + scene data and serialization. +- Correct and test the core scene/visualisation guide against current APIs. + +### Viewer + +- Accept list and NumPy vertices/indices; verify shapes, bounds, dtypes, + non-mutation, and retained indices. +- Compare large indexed edge/triangle input with expected buffer counts and + assert it does not create one scene object per primitive. +- Exercise selection replacement/add/remove/toggle from viewport, tree, and + code through the same observable model. +- Verify renderer picking IDs resolve to scene GUIDs and are not serialized. +- Test world-space multi-object transforms, parent conversion, atomic failure, + undo/redo, and ancestor/descendant rejection. +- Test distance derivation, unit/precision formatting, tool cancel/complete, + annotation serialization, and transient overlay non-serialization. +- Test public dock/command composition without importing optional integrations + during plugin discovery. + +### Framework and later consumers + +- Use the current draft ModelViewer load failure as regression evidence for + two-phase scene reconstruction, not as a public behavior suite. +- Add an installed exact-candidate test for core Scene and Viewer contracts. +- Redesign and verify model integration only in a later approved consumer phase. +- Keep candidate-artifact evidence separate from source-build integration. + +Examples remain direct scripts and demonstrate, in order: + +1. vertices and edges; +2. vertices and triangles; +3. shared indexed geometry with selection; +4. multi-object transformation and undo; +5. distance measurement and world annotation; and +6. scale/legend overlays and a custom dock. + +## Dependency order + +```text +ADR 001 --> ADR 008 --> ADR 009 --> ADR 010 + | + +--------> ADR 002 + +--------> ADR 003 --> ADR 004 + +ADRs 001–003 ----------> ADR 005 +ADRs 001, 003–005 -----> ADR 006 +ADRs 001–010 ----------> ADR 007 rollout gate +``` + +Core geometry, typing, plugin, and repository-modernisation decisions remain +upstream constraints. Implementation starts only after all ADRs required for a +wave are Accepted. + +## Architecture correction after Phase 1 + +Implementation evidence invalidates the detached/bound adapter mechanism used +for the first implementation of ADRs 008–009. It duplicates provisional and +node state, changes GUID meaning at binding, and requires hidden attribute +interception. Removal and context replacement can expose stale values on old +adapter references. Backend class substitution adds another ambiguity: the +type returned for one scene entry depends on which visualization plugins are +installed and selected. + +The preferred correction assigns one purpose to each concept: + +- `SceneNode` stores only ordered hierarchy through a scene-object GUID; +- stable backend-neutral `SceneObject` values own item references, identity, + transformation, appearance, visibility, and semantic type-specific state; +- independently serialized `Data` items remain external and bind by GUID; and +- visualization backends register context-keyed `draw` and `clear` functions + rather than replacement scene-object classes. + +`scene.add(data, ...)` remains the primary convenience path and returns the +stable scene object. One node is allowed per scene object; repeated appearances +of one item use multiple scene objects. The scene-object GUID is the hierarchy +key, so there is no second durable node identity. A scene serializes stable +object records plus a hierarchy of GUID references and loads without any +backend installed. + +The general COMPAS plugin dispatcher cannot select a `draw` implementation +from an invocation's explicit visualization context. The preferred design does +not complicate it. Lightweight discovery plugins populate a small scene +operation registry keyed by `(context, scene-object type, operation)`. Runtime +buffers, host handles, picking state, and caches live outside durable objects. + +ADRs 011–012 record the unaccepted first node-first alternative. Accepted ADRs +013–014 record the selected design, which is now implemented and verified. + +## Proposed contextual object-settings contributions + +Viewer's object-settings form currently selects controls through hardcoded +attribute checks on private render helpers. This confuses renderer-facing +aliases with editable UI capabilities and provides no supported extension +point for packages that need to expose a particular stable scene-object +attribute or represented-item parameter. + +The proposed correction is deliberately Viewer-owned. Lightweight plugins +register declarative contributions by context, form identifier, stable +scene-object type, and contribution ID. Providers are invoked only when the +form is populated, compose through the stable type MRO, and describe flat +field links. Viewer remains responsible for controls, layout, mutation, +failure handling, and refresh. Field ordering is deterministic across explicit +order, MRO rank, contribution ID, and provider-return sequence. A provider +failure aborts form population with a contextual diagnostic; it does not leave +a partial form. + +The initial scope is only `object-settings`, with Boolean, numeric, text, and +color editors. A field explicitly targets either a stable scene-object +attribute or an attribute/parameter of its already bound represented item. +Successful changes request either a draw-only or geometry rebuild refresh. +Fields for unresolved items are omitted without implicit loading, and failed +setters restore the prior value without refreshing. + +This mechanism does not extend core's `draw`/`clear` registry, attach UI +metadata to durable objects, return arbitrary Qt controls from plugins, or +introduce a general form-schema framework. The existing general parameter +dialog remains available during the initial migration. + +## Decision record + +1. [ADR 001 — Ownership and state boundaries](decisions/001-ownership-boundaries.md) +2. [ADR 002 — Indexed Viewer buffer geometry](decisions/002-indexed-buffer-geometry.md) +3. [ADR 003 — Viewer state and object selection](decisions/003-viewer-state-and-selection.md) +4. [ADR 004 — Transformations, refresh, and undo](decisions/004-transform-refresh-undo.md) +5. [ADR 005 — Annotations, measurements, and overlays](decisions/005-annotations-and-overlays.md) +6. [ADR 006 — Public Viewer integration facade](decisions/006-viewer-integration-facade.md) +7. [ADR 007 — Initial scope and verification](decisions/007-scope-and-verification.md) +8. [ADR 008 — Serializable scene nodes and runtime adapters](decisions/008-scene-nodes-and-runtime-adapters.md) +9. [ADR 009 — Explicit scene-state serialization fields](decisions/009-scene-state-fields.md) +10. [ADR 010 — Scene description, item bindings, and aggregation boundary](decisions/010-scene-description-and-item-bundling.md) +11. [ADR 011 — Node-first scene construction and adapter lifetime](decisions/011-node-first-scene-adapters.md) +12. [ADR 012 — Explicit node-backed adapter state](decisions/012-explicit-node-backed-adapter-state.md) +13. [ADR 013 — Hierarchy-only nodes and stable scene objects](decisions/013-hierarchy-nodes-and-stable-sceneobjects.md) +14. [ADR 014 — Context-keyed scene operation plugins](decisions/014-context-keyed-scene-operations.md) +15. [ADR 015 — Contextual scene form contributions](decisions/015-contextual-scene-form-contributions.md) +16. [ADR 016 — Scene form field bindings](decisions/016-scene-form-field-bindings.md) + +ADRs 013–014 are Accepted. ADRs 008–009 and 011–012 are superseded as recorded +in the decision index; ADR 010 remains Accepted with amended terminology. ADRs +015–016 remain Proposed and block contextual form implementation. diff --git a/tests/test_package.py b/tests/test_package.py index b877fbd..f623f6f 100644 --- a/tests/test_package.py +++ b/tests/test_package.py @@ -57,6 +57,21 @@ def test_native_candidate_integration_runs_from_the_protected_initiative_branch( assert "inputs.manifest_path || '.github/candidates/native-linux-py312.json'" in workflow +def test_scene_candidate_integration_pins_exact_source_revisions(): + source = Path(__file__).parents[1] + core_requirements = (source / ".github" / "scene-candidates" / "core-requirements.txt").read_text() + viewer_requirements = (source / ".github" / "scene-candidates" / "viewer-requirements.txt").read_text() + workflow = (source / ".github" / "workflows" / "scene-candidate-integration.yml").read_text() + + assert "compas.git@d43db206afec36450efbe76b53306ce3bd468076" in core_requirements + assert "compas_viewer.git@a72b61b777bc3fd448c3d471c48ef7f65334b319" in viewer_requirements + assert "branches: [c3-upgrade]" in workflow + assert "core-requirements.txt" in workflow + assert "viewer-requirements.txt" in workflow + assert "--no-deps" in workflow + assert "source-installed-candidates" in workflow + + def test_framework_examples_page_is_link_oriented(): page = (Path(__file__).parents[1] / "docs" / "examples.md").read_text()