Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
4ba0539
Start scene viewer visualisation initiative
tomvanmele Nov 7, 2026
ac5d555
Accept scene viewer visualisation ADRs
tomvanmele Nov 7, 2026
b0b76f1
Revisit scene state architecture
tomvanmele Nov 7, 2026
79f4081
Decouple scene architecture from ModelViewer draft
tomvanmele Nov 7, 2026
1e25f10
Drop legacy scene loader proposal
tomvanmele Nov 7, 2026
441609e
Separate scene descriptions from session data
tomvanmele Nov 7, 2026
5c95c1a
Accept scene data reference boundary
tomvanmele Nov 7, 2026
ee8c4d8
Record scene phase zero checkpoint
tomvanmele Nov 7, 2026
666e98b
Record scene phase zero draft PRs
tomvanmele Nov 7, 2026
8c7b4a1
Add scene candidate integration gate
tomvanmele Nov 7, 2026
c8ba171
Install scene candidates without dependency re-resolution
tomvanmele Nov 7, 2026
245aab9
Resolve scene candidate dependencies in stages
tomvanmele Nov 7, 2026
e0e9891
Provision graphics runtime for scene integration
tomvanmele Nov 7, 2026
dbeabbe
Refresh scene candidate checkpoint
tomvanmele Nov 7, 2026
a3cf910
Gate indexed Viewer candidate
tomvanmele Nov 7, 2026
d9c72ff
Propose node-first scene architecture
tomvanmele Nov 7, 2026
d50d7b6
Propose stable scene objects and operation plugins
tomvanmele Nov 7, 2026
0093010
Accept stable scene architecture
tomvanmele Nov 7, 2026
0fe8f4a
Refresh stable scene integration gate
tomvanmele Nov 7, 2026
f2976d1
Record stable scene integration evidence
tomvanmele Nov 7, 2026
84b1a95
Record core patch coverage recovery
tomvanmele Nov 7, 2026
9d05138
Propose contextual scene form contributions
tomvanmele Nov 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/scene-candidates/core-requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
compas @ git+https://github.com/compas-dev/compas.git@d43db206afec36450efbe76b53306ce3bd468076
141 changes: 141 additions & 0 deletions .github/scene-candidates/test_scene_integration.py
Original file line number Diff line number Diff line change
@@ -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 == {}
1 change: 1 addition & 0 deletions .github/scene-candidates/viewer-requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
compas_viewer @ git+https://github.com/compas-dev/compas_viewer.git@a72b61b777bc3fd448c3d471c48ef7f65334b319
62 changes: 62 additions & 0 deletions .github/workflows/scene-candidate-integration.yml
Original file line number Diff line number Diff line change
@@ -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
23 changes: 23 additions & 0 deletions initiatives/scene-viewer-visualisation/README.md
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -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
Loading