Skip to content

Commit 8ad61c4

Browse files
authored
Merge pull request #1286 from maharshi-gor/doc/dev-docs-fix
BF: Dev doc builds were not generating the examples.
2 parents a2476b7 + 2697bc6 commit 8ad61c4

5 files changed

Lines changed: 297 additions & 76 deletions

File tree

.github/workflows/doc.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ jobs:
2525
matrix:
2626
python-version: [3.12]
2727
env:
28-
FURY_OFFSCREEN: true
28+
FURY_OFFSCREEN: 1
29+
FURY_RECORD_ANIMATION: 1
30+
QT_QPA_PLATFORM: xcb
2931
steps:
3032
- uses: actions/checkout@v7
3133
with:
@@ -48,7 +50,7 @@ jobs:
4850
- name: Build docs
4951
run: |
5052
cd docs
51-
make -C . html-no-examples SPHINXOPTS="-W --keep-going"
53+
make -C . html SPHINXOPTS="-W --keep-going"
5254
- name: Deploy Devs
5355
if: success() && github.ref == 'refs/heads/master' && github.repository == 'fury-gl/fury'
5456
uses: JamesIves/github-pages-deploy-action@v4

ci/setup_headless.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ if [ "$RUNNER_OS" == "Linux" ]; then
77
sudo apt-get install --no-install-recommends -y libegl1-mesa-dev libglu1-mesa-dev libgl1-mesa-dri \
88
libxcb-xfixes0-dev mesa-vulkan-drivers xvfb libxcb-cursor0 libx11-dev libxrandr-dev libxinerama-dev \
99
libxcursor-dev libxi-dev libgl1-mesa-dev libglu1-mesa-dev x11proto-core-dev \
10-
libxcb1-dev libx11-xcb-dev libxkbcommon-dev;
10+
libxcb1-dev libx11-xcb-dev libxkbcommon-dev libxcb-keysyms1 libxcb-image0 libxcb-render-util0 \
11+
libxcb-shape0 libxcb-randr0 libxcb-icccm4 libxcb-xinerama0 libxkbcommon-x11-0;
1112
Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &
1213
sleep 3
1314
elif [ "$RUNNER_OS" == "Windows" ]; then

fury/lib.py

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,8 @@
4747
)
4848

4949
PySide6, have_py_side6, _ = optional_package("PySide6", trip_msg=qt_pckg_msg)
50-
PyQt6, have_py_qt6, _ = optional_package("PyQt6", trip_msg=qt_pckg_msg)
51-
PyQt5, have_py_qt5, _ = optional_package("PyQt5", trip_msg=qt_pckg_msg)
5250

53-
if have_py_side6 or have_py_qt6 or have_py_qt5:
51+
if have_py_side6:
5452
from rendercanvas.qt import RenderCanvas as QtRenderCanvas, loop as qloop
5553

5654
qcall_later = qloop.call_later
@@ -60,13 +58,7 @@ def get_app():
6058

6159

6260
if have_py_side6:
63-
from PySide6 import QtWidgets
64-
65-
if have_py_qt6:
66-
from PyQt6 import QtWidgets
67-
68-
if have_py_qt5:
69-
from PyQt5 import QtWidgets
61+
from PySide6 import QtGui, QtWidgets
7062

7163
GfxGroup = gfx.Group
7264
Texture = gfx.Texture
@@ -152,10 +144,11 @@ def get_app():
152144
JupyterCanvas = JupyterWgpuCanvas
153145
else:
154146
JupyterCanvas = jupyter_rfb
155-
if have_py_side6 or have_py_qt6 or have_py_qt5:
147+
if have_py_side6:
156148
QtCanvas = QtRenderCanvas
157149
else:
158150
QtCanvas = PySide6
151+
QtGui = PySide6
159152
QtWidgets = PySide6
160153
get_app = PySide6
161154
qcall_later = PySide6

fury/tests/test_window.py

Lines changed: 114 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import logging
22
import os
3-
from unittest import mock
43
from unittest.mock import patch
54

65
import numpy as np
@@ -17,12 +16,14 @@
1716
OrbitController,
1817
PerspectiveCamera,
1918
PointerEvent,
19+
QtWidgets,
2020
Renderer,
2121
Scene as GfxScene,
2222
ScreenCoordsCamera,
2323
Texture,
2424
TrackballController,
2525
have_imgui_bundle,
26+
have_py_side6,
2627
)
2728
from fury.motion import Animation, CameraAnimation, Timeline
2829
from fury.ui import Rectangle2D, UIContext
@@ -395,14 +396,12 @@ def test_show_manager_initialization_default_window():
395396
assert show_m.app is None
396397

397398

398-
# @pytest.mark.skipif(
399-
# not (have_py_side6 or have_py_qt6 or have_py_qt5), reason="Needs Qt"
400-
# )
401-
# def test_show_manager_initialization_qt_window():
402-
# """Test ShowManager initialization with a Qt window."""
403-
# show_m = ShowManager(window_type="qt")
404-
# assert show_m._is_qt is True
405-
# assert show_m.app is not None
399+
@pytest.mark.skipif(not (have_py_side6), reason="Needs Qt")
400+
def test_show_manager_initialization_qt_window():
401+
"""Test ShowManager initialization with a Qt window."""
402+
show_m = ShowManager(window_type="qt")
403+
assert show_m._is_qt is True
404+
assert isinstance(show_m.window, QtWidgets.QWidget)
406405

407406

408407
def test_show_manager_screen_setup():
@@ -925,41 +924,125 @@ def test_show_manager_register_drag():
925924
assert screen.controller.enabled is True
926925

927926

928-
def test_offscreen_animation_recording():
929-
930-
# Create a simple scene
927+
def test_offscreen_animation_recording(tmp_path):
931928
scene = window.Scene()
932929
scene.add(actor.axes(scale=(1, 1, 1)))
933930

934931
show_m = window.ShowManager(scene=scene, size=(200, 200), title="test_anim")
935932

936-
# to trigger animation logic
937-
def dummy_callback():
933+
def noop_callback():
938934
pass
939935

940-
show_m.register_callback(dummy_callback, time=1.0, repeat=True, name="dummy")
936+
show_m.register_callback(noop_callback, time=1.0, repeat=True, name="noop")
941937

942-
# test 1: record animation is FALSE
943-
with mock.patch.dict(
944-
os.environ, {"FURY_OFFSCREEN": "1", "FURY_RECORD_ANIMATION": "0"}
945-
):
946-
show_m.start()
947-
assert os.path.exists("test_anim.png")
948-
assert not os.path.exists("test_anim.gif")
938+
original_cwd = os.getcwd()
939+
original_offscreen = os.environ.get("FURY_OFFSCREEN")
940+
original_record = os.environ.get("FURY_RECORD_ANIMATION")
941+
os.chdir(tmp_path)
942+
os.environ["FURY_OFFSCREEN"] = "1"
943+
os.environ["FURY_RECORD_ANIMATION"] = "0"
949944

950-
os.remove("test_anim.png")
945+
try:
946+
show_m.start()
947+
finally:
948+
os.chdir(original_cwd)
949+
if original_offscreen is None:
950+
os.environ.pop("FURY_OFFSCREEN", None)
951+
else:
952+
os.environ["FURY_OFFSCREEN"] = original_offscreen
953+
if original_record is None:
954+
os.environ.pop("FURY_RECORD_ANIMATION", None)
955+
else:
956+
os.environ["FURY_RECORD_ANIMATION"] = original_record
957+
958+
assert (tmp_path / "test_anim.png").exists()
959+
assert not (tmp_path / "test_anim.gif").exists()
960+
os.remove(tmp_path / "test_anim.png")
961+
962+
original_cwd = os.getcwd()
963+
original_offscreen = os.environ.get("FURY_OFFSCREEN")
964+
original_record = os.environ.get("FURY_RECORD_ANIMATION")
965+
original_max_frames = os.environ.get("FURY_OFFSCREEN_MAX_FRAMES")
966+
os.chdir(tmp_path)
967+
os.environ["FURY_OFFSCREEN"] = "1"
968+
os.environ["FURY_RECORD_ANIMATION"] = "1"
969+
os.environ["FURY_OFFSCREEN_MAX_FRAMES"] = "2"
951970

952-
# test 2: record animation is TRUE
953-
with mock.patch.dict(
954-
os.environ, {"FURY_OFFSCREEN": "1", "FURY_RECORD_ANIMATION": "1"}
955-
):
971+
try:
956972
show_m2 = window.ShowManager(scene=scene, size=(200, 200), title="test_anim")
957-
show_m2.register_callback(dummy_callback, time=1.0, repeat=True, name="dummy")
973+
show_m2.register_callback(noop_callback, time=1.0, repeat=True, name="noop")
958974
show_m2.start()
959-
assert os.path.exists("test_anim.gif")
960-
assert not os.path.exists("test_anim.png")
975+
finally:
976+
os.chdir(original_cwd)
977+
if original_offscreen is None:
978+
os.environ.pop("FURY_OFFSCREEN", None)
979+
else:
980+
os.environ["FURY_OFFSCREEN"] = original_offscreen
981+
if original_record is None:
982+
os.environ.pop("FURY_RECORD_ANIMATION", None)
983+
else:
984+
os.environ["FURY_RECORD_ANIMATION"] = original_record
985+
if original_max_frames is None:
986+
os.environ.pop("FURY_OFFSCREEN_MAX_FRAMES", None)
987+
else:
988+
os.environ["FURY_OFFSCREEN_MAX_FRAMES"] = original_max_frames
989+
990+
assert (tmp_path / "test_anim.gif").exists()
991+
assert not (tmp_path / "test_anim.png").exists()
992+
os.remove(tmp_path / "test_anim.gif")
993+
994+
995+
@pytest.mark.skipif(
996+
not (have_py_side6),
997+
reason="A Qt binding is required for Qt offscreen capture",
998+
)
999+
def test_qt_offscreen_capture_saves_parent_widget(tmp_path):
1000+
app = QtWidgets.QApplication.instance() or QtWidgets.QApplication([])
1001+
parent = QtWidgets.QWidget()
1002+
parent.resize(240, 120)
9611003

962-
os.remove("test_anim.gif")
1004+
scene = window.Scene()
1005+
scene.add(actor.axes(scale=(1, 1, 1)))
1006+
1007+
show_m = window.ShowManager(
1008+
scene=scene,
1009+
size=(100, 100),
1010+
title="qt_capture",
1011+
window_type="qt",
1012+
qt_app=app,
1013+
qt_parent=parent,
1014+
)
1015+
1016+
layout = QtWidgets.QHBoxLayout()
1017+
parent.setLayout(layout)
1018+
layout.addWidget(QtWidgets.QPushButton("Capture", parent))
1019+
layout.addWidget(show_m.window)
1020+
1021+
original_cwd = os.getcwd()
1022+
original_offscreen = os.environ.get("FURY_OFFSCREEN")
1023+
original_record = os.environ.get("FURY_RECORD_ANIMATION")
1024+
os.chdir(tmp_path)
1025+
os.environ["FURY_OFFSCREEN"] = "1"
1026+
os.environ["FURY_RECORD_ANIMATION"] = "0"
1027+
1028+
try:
1029+
show_m.start()
1030+
finally:
1031+
os.chdir(original_cwd)
1032+
if original_offscreen is None:
1033+
os.environ.pop("FURY_OFFSCREEN", None)
1034+
else:
1035+
os.environ["FURY_OFFSCREEN"] = original_offscreen
1036+
if original_record is None:
1037+
os.environ.pop("FURY_RECORD_ANIMATION", None)
1038+
else:
1039+
os.environ["FURY_RECORD_ANIMATION"] = original_record
1040+
1041+
fname = tmp_path / "qt_capture.png"
1042+
assert fname.exists()
1043+
image = load_image(str(fname))
1044+
assert image.shape[0] == parent.height()
1045+
assert image.shape[1] == parent.width()
9631046

9641047

9651048
def test_show_manager_add_animation_registers_update_callback(timeline):

0 commit comments

Comments
 (0)