otko/tests/gui/test_viewport_axes.py
smillmorel 612936a00b
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
feat: initial otko import
2026-09-08 02:12:15 -04:00

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"