Some checks failed
CI / lint (push) Has been cancelled
CI / test (macos-latest, 3.10) (push) Has been cancelled
CI / test (macos-latest, 3.11) (push) Has been cancelled
CI / test (macos-latest, 3.12) (push) Has been cancelled
CI / test (ubuntu-latest, 3.10) (push) Has been cancelled
CI / test (ubuntu-latest, 3.11) (push) Has been cancelled
CI / test (ubuntu-latest, 3.12) (push) Has been cancelled
CI / test (windows-latest, 3.10) (push) Has been cancelled
CI / test (windows-latest, 3.11) (push) Has been cancelled
CI / test (windows-latest, 3.12) (push) Has been cancelled
107 lines
4 KiB
Python
107 lines
4 KiB
Python
"""Viewport orientation triad is present and survives every re-render path.
|
|
|
|
Before this change the 3D viewport had no X/Y/Z corner marker — users
|
|
who rotated the camera off the default isometric view lost track of
|
|
which world axis was which. The triad is now built once during the
|
|
canvas's scene-furniture setup and lives in screen-space via PyVista's
|
|
``vtkOrientationMarkerWidget``, so it is not affected by the regular
|
|
actor rebuilds in :meth:`ModelRenderer.render` /
|
|
:meth:`ModelRenderer._teardown_all`.
|
|
|
|
These tests pin the contract:
|
|
|
|
- Triad exists right after ``MainWindow`` construction.
|
|
- It still exists after a tree refresh (which calls
|
|
``ModelRenderer.render`` through ``MainWindow._on_project_changed``).
|
|
- It still exists after loading an empty project, then a small project
|
|
with a node — the most common real-world re-render path.
|
|
- The triad carries the conventional X / Y / Z labels.
|
|
- It is pinned to the lower-left viewport corner.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
pytest.importorskip("PySide6")
|
|
|
|
from otko.core import Node
|
|
|
|
|
|
@pytest.mark.gui
|
|
def test_axes_widget_exists_after_window_construction(qtbot) -> None: # type: ignore[no-untyped-def]
|
|
from otko.views.main_window import MainWindow
|
|
|
|
mw = MainWindow()
|
|
qtbot.addWidget(mw)
|
|
aw = mw._canvas.renderer.axes_widget
|
|
assert aw is not None
|
|
# PyVista binds a vtkOrientationMarkerWidget; the orientation marker
|
|
# it carries is a vtkAxesActor with X/Y/Z label text.
|
|
actor = aw.GetOrientationMarker()
|
|
assert actor.GetXAxisLabelText() == "X"
|
|
assert actor.GetYAxisLabelText() == "Y"
|
|
assert actor.GetZAxisLabelText() == "Z"
|
|
# Pinned to the lower-left viewport patch (normalized 0..1 coords).
|
|
vp = aw.GetViewport()
|
|
assert vp[0] == pytest.approx(0.0)
|
|
assert vp[1] == pytest.approx(0.0)
|
|
|
|
|
|
@pytest.mark.gui
|
|
def test_axes_widget_survives_tree_refresh(qtbot) -> None: # type: ignore[no-untyped-def]
|
|
"""``MainWindow._refresh_tree`` triggers a renderer rebuild via
|
|
``_on_project_changed``; the orientation widget must stay put."""
|
|
from otko.views.main_window import MainWindow
|
|
|
|
mw = MainWindow()
|
|
qtbot.addWidget(mw)
|
|
aw_before = mw._canvas.renderer.axes_widget
|
|
|
|
mw._vm.new_project(ndm=3, ndf=6)
|
|
mw._vm.project.nodes.extend(
|
|
[
|
|
Node(id=1, coords=(0, 0, 0)),
|
|
Node(id=2, coords=(3, 0, 0)),
|
|
]
|
|
)
|
|
mw._refresh_tree(mw._vm.project)
|
|
|
|
aw_after = mw._canvas.renderer.axes_widget
|
|
# Same widget instance — proves the marker survived the rebuild.
|
|
assert aw_after is aw_before
|
|
actor = aw_after.GetOrientationMarker()
|
|
assert actor.GetXAxisLabelText() == "X"
|
|
assert actor.GetYAxisLabelText() == "Y"
|
|
assert actor.GetZAxisLabelText() == "Z"
|
|
|
|
|
|
@pytest.mark.gui
|
|
def test_axes_widget_survives_empty_then_loaded_project(qtbot) -> None: # type: ignore[no-untyped-def]
|
|
"""The two re-render paths the engineer hits during a session:
|
|
empty project → first node added → tree refreshed. The triad must
|
|
still be in the lower-left corner with all three labels."""
|
|
from otko.views.main_window import MainWindow
|
|
|
|
mw = MainWindow()
|
|
qtbot.addWidget(mw)
|
|
aw_before = mw._canvas.renderer.axes_widget
|
|
assert aw_before is not None
|
|
|
|
# Empty project — model renderer builds no actors, just the grid.
|
|
mw._vm.new_project(ndm=3, ndf=6)
|
|
mw._refresh_tree(mw._vm.project)
|
|
assert mw._canvas.renderer.axes_widget is aw_before
|
|
|
|
# Small project with one node — exercises the node-actor rebuild path.
|
|
mw._vm.project.nodes.append(Node(id=1, coords=(1, 2, 3)))
|
|
mw._refresh_tree(mw._vm.project)
|
|
assert mw._canvas.renderer.axes_widget is aw_before
|
|
|
|
# Same after a full canvas refresh too — show_project rebuilds actors.
|
|
mw._canvas.show_project(mw._vm.project)
|
|
assert mw._canvas.renderer.axes_widget is aw_before
|
|
actor = mw._canvas.renderer.axes_widget.GetOrientationMarker()
|
|
assert actor.GetXAxisLabelText() == "X"
|
|
assert actor.GetYAxisLabelText() == "Y"
|
|
assert actor.GetZAxisLabelText() == "Z"
|