Skip to content

Commit 60c8952

Browse files
committed
serialize only the camera
1 parent 0248dd4 commit 60c8952

3 files changed

Lines changed: 78 additions & 66 deletions

File tree

‎src/ansys/visor/viewer/renderer/local_renderer.py‎

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -318,25 +318,25 @@ def serialize_camera_state(self) -> None:
318318
"""See :meth:`IRenderer.serialize_camera_state`.
319319
320320
``vtklocal`` advertises each object's modification time off the live
321-
VTK object but serves state out of a serialization cache that only
322-
``UpdateStatesFromObjects`` refreshes. Writing the pipeline camera
323-
without this call publishes a new version number against the old
324-
content, and the client fetches the pre-write camera and applies it
325-
over the one just installed.
326-
327-
The render window's id is passed, not the camera's own. It is the
328-
form the framework itself reproduces -- ``LocalView.update`` resolves
329-
``[self._render_window, *registered]`` to ids and hands those to
330-
``UpdateStatesFromObjects`` -- and the camera sits inside the render
331-
window's dependency closure, which is why ``get_status`` can name the
332-
camera's id at all when building ``ignore_ids``.
333-
334-
No ``js_call``: that lives in ``LocalView.update``, not in the object
335-
manager, so this serialises without pushing and without re-opening
336-
the rebuild race ``_apply_runtime_state_to_render`` refuses.
321+
VTK object but serves state out of a serialization cache, so a write
322+
to the pipeline camera without this call publishes a new
323+
version number against the old content: the client fetches the
324+
pre-write camera and applies it over the one just installed.
325+
326+
``UpdateStateFromObject`` resets the camera's dependency edges
327+
and re-serializes it, and does nothing else. It is not
328+
interchangeable with ``UpdateStatesFromObjects``, which prunes
329+
the whole store whichever ids are passed. The interactor,
330+
picker and widget objects are named in no other object's state
331+
and survive a prune only by being re-serialized as roots in the
332+
same pass, which nothing else on the load path does.
333+
334+
No ``js_call``: that lives in ``LocalView.update``, so this
335+
serialises without re-opening the rebuild race
336+
``_apply_runtime_state_to_render`` refuses.
337337
"""
338-
self._object_manager.UpdateStatesFromObjects(
339-
[self._object_manager.GetId(self._render_window)]
338+
self._object_manager.UpdateStateFromObject(
339+
self._object_manager.GetId(self._vtk_renderer.GetActiveCamera())
340340
)
341341

342342
# ------------------------------------------------------------------

‎tests/unit/renderer/test_local_renderer.py‎

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -185,18 +185,17 @@ def SetParallelScale(self, value): # noqa: N802
185185
# ---------------------------------------------------------------------------
186186
# Object-manager id literals
187187
#
188-
# Hand-written, and distinctive on purpose. The id source in the fixture is
189-
# keyed on object *identity*: the render window resolves to the first literal
190-
# and every other object to the second. A test that asserts the first literal
191-
# therefore fails if production names the renderer, the interactor, the picker
192-
# or the active camera, instead of coinciding with whatever it named.
193-
#
194-
# Both are far outside the small-integer range a real object manager hands out
195-
# in a freshly initialised scene, so a literal arriving from anywhere other
196-
# than here is visible on sight.
188+
# Hand-written, and distinctive on purpose. The id source is keyed on object
189+
# *identity*: the active camera resolves to its own literal, the render window
190+
# to a second, and every other object to the third. A test that asserts
191+
# ACTIVE_CAMERA_WASM_ID therefore fails if production names the render window,
192+
# the renderer, the interactor or the picker, instead of coinciding with
193+
# whatever it named. The render window keeps a literal of its own so that a
194+
# revert to the previous call shape fails by name rather than as the catch-all.
197195
# ---------------------------------------------------------------------------
198196

199197
RENDER_WINDOW_WASM_ID = 8150001
198+
ACTIVE_CAMERA_WASM_ID = 8150002
200199
WRONG_OBJECT_WASM_ID = 8150999
201200

202201

@@ -738,32 +737,37 @@ def test_sync_camera_stores_the_object_without_copying(self, renderer):
738737

739738
assert renderer.get_camera_state() is cam
740739

741-
def test_serialize_camera_state_updates_states_from_the_render_window_id(
740+
def test_serialize_camera_state_updates_the_states_from_the_active_camera_id(
742741
self, renderer
743742
):
744-
"""The re-serialise names the render window, and nothing else.
743+
"""The re-serialise names the active camera, and nothing else.
745744
746745
The id source is keyed on object identity, so every object other than
747-
the render window resolves to a different, equally distinctive
748-
literal. Asserting ``RENDER_WINDOW_WASM_ID`` therefore fails if the
749-
implementation names ``_vtk_renderer``, the interactor, or the active
750-
camera, rather than coinciding with them. Asserting against
751-
``GetId(renderer._render_window)`` -- the attribute production reads
752-
-- would pass in all of those cases and pin nothing.
753-
754-
The expected value is the list production passes, not a repacking of
755-
it: ``UpdateStatesFromObjects`` takes a sequence of ids.
746+
the active camera resolves to a different, equally distinctive
747+
literal. Asserting ``ACTIVE_CAMERA_WASM_ID`` therefore fails if the
748+
implementation names ``_render_window``, ``_vtk_renderer``, the
749+
interactor or the picker, rather than coinciding with them. The
750+
render window has a literal of its own, so the previous call shape
751+
fails by name. Asserting against
752+
``GetId(renderer._vtk_renderer.GetActiveCamera())`` -- the expression
753+
production reads -- would pass in all of those cases and pin nothing.
754+
755+
The expected value is the bare id production passes:
756+
``UpdateStateFromObject`` takes a single id, not a sequence.
756757
"""
758+
camera = renderer._vtk_renderer.GetActiveCamera()
757759
renderer._object_manager.GetId.side_effect = (
758-
lambda obj: RENDER_WINDOW_WASM_ID
760+
lambda obj: ACTIVE_CAMERA_WASM_ID
761+
if obj is camera
762+
else RENDER_WINDOW_WASM_ID
759763
if obj is renderer._render_window
760764
else WRONG_OBJECT_WASM_ID
761765
)
762766

763767
renderer.serialize_camera_state()
764768

765-
renderer._object_manager.UpdateStatesFromObjects.assert_called_with(
766-
[RENDER_WINDOW_WASM_ID]
769+
renderer._object_manager.UpdateStateFromObject.assert_called_with(
770+
ACTIVE_CAMERA_WASM_ID
767771
)
768772

769773
def test_serialize_camera_state_does_not_notify_the_client(self, renderer):

‎tests/unit/vtk/scene/test_base.py‎

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
from ansys.visor.viewer.vtk.scene.base import VisorSceneBase
4343
from ansys.visor.viewer.vtk.variables.visor_part_variables import VisorPartVariables
4444
from ansys.visor.viewer.vtk.variables.visor_variables import VisorVariable
45+
from tests.unit.renderer.test_local_renderer import ACTIVE_CAMERA_WASM_ID
4546

4647
NODE_ID = 7
4748
SECOND_NODE_ID = 8
@@ -158,15 +159,18 @@ def array_dataset() -> vtkPolyData:
158159
# Object-manager id literals
159160
#
160161
# Hand-written and distinctive. Seeded into the renderer fixture's id source
161-
# keyed on object identity, so that asserting RENDER_WINDOW_WASM_ID fails --
162-
# rather than coincides -- if the re-serialisation names the renderer, the
163-
# interactor, the picker or the active camera instead of the render window.
162+
# keyed on object identity, so that asserting ACTIVE_CAMERA_WASM_ID fails --
163+
# rather than coincides -- if the re-serialization names the render window,
164+
# the renderer, the interactor or the picker instead of the active camera.
165+
# The render window keeps a literal of its own so that a revert to the
166+
# previous call shape fails by name rather than as the catch-all.
164167
#
165168
# Asserting against the id source's own answer for the attribute production
166169
# reads would pin nothing: it would pass whichever object production named.
167170
# ---------------------------------------------------------------------------
168171

169172
RENDER_WINDOW_WASM_ID = 8150001
173+
ACTIVE_CAMERA_WASM_ID = 8150002
170174
WRONG_OBJECT_WASM_ID = 8150999
171175

172176

@@ -180,9 +184,10 @@ def renderer():
180184
181185
The object manager that arrives with the patched LocalView is a MagicMock,
182186
so its ``GetId`` is seeded here rather than in a test: keyed on object
183-
identity, the render window resolves to RENDER_WINDOW_WASM_ID and every
184-
other object to WRONG_OBJECT_WASM_ID. That is what lets the ordering test
185-
below assert which object the re-serialisation named.
187+
identity, the active camera resolves to ACTIVE_CAMERA_WASM_ID, the render
188+
window to RENDER_WINDOW_WASM_ID and every other object to
189+
WRONG_OBJECT_WASM_ID. This is what lets the ordering test below assert
190+
which object the re-serialization named.
186191
"""
187192
mock_server = MagicMock()
188193
mock_server.state = {}
@@ -204,8 +209,11 @@ def renderer():
204209
):
205210
r = VisorLocalRenderer(mock_server)
206211

212+
camera = r._vtk_renderer.GetActiveCamera.return_value
207213
r._object_manager.GetId.side_effect = (
208-
lambda obj: RENDER_WINDOW_WASM_ID
214+
lambda obj: ACTIVE_CAMERA_WASM_ID
215+
if obj is camera
216+
else RENDER_WINDOW_WASM_ID
209217
if obj is r._render_window
210218
else WRONG_OBJECT_WASM_ID
211219
)
@@ -1131,10 +1139,10 @@ def test_apply_state_syncs_the_camera_under_the_lock_before_the_render_step(scen
11311139
server correct; it does not make the state the client is served correct,
11321140
and the two are separate steps that can each silently do nothing. The
11331141
spy appends the literal string ``"camera"`` for the write, the two-tuple
1134-
``("serialize", <ids>)`` for the re-serialisation -- the tuple is the
1142+
``("serialize", <id>)`` for the re-serialisation -- the tuple is the
11351143
recording format, not the argument -- and ``"bridge"`` for the delegated
1136-
render step. ``<ids>`` is asserted as the list production passes, since
1137-
``UpdateStatesFromObjects`` takes a sequence.
1144+
render step. ``<id>`` is asserted as the bare id production passes, since
1145+
``UpdateStateFromObject`` takes a single id.
11381146
11391147
Ordered between the two: after the write, because re-serialising before
11401148
it would publish the pre-load camera; before the render step, because the
@@ -1152,16 +1160,16 @@ def _sync(camera_state):
11521160
return real_sync(camera_state)
11531161

11541162
scene._renderer.sync_camera = _sync
1155-
scene._renderer._object_manager.UpdateStatesFromObjects = (
1156-
lambda ids: order.append(("serialize", ids))
1163+
scene._renderer._object_manager.UpdateStateFromObject = (
1164+
lambda object_id: order.append(("serialize", object_id))
11571165
)
11581166
scene._apply_runtime_state_to_render = lambda state: order.append("bridge")
11591167

11601168
scene.apply_state(_persisted_state(_persisted_camera()))
11611169

11621170
assert order == [
11631171
"camera",
1164-
("serialize", [RENDER_WINDOW_WASM_ID]),
1172+
("serialize", ACTIVE_CAMERA_WASM_ID),
11651173
"bridge",
11661174
]
11671175
assert observed["depth"] >= 1
@@ -1184,8 +1192,8 @@ def test_apply_state_serializes_the_camera_under_the_lock(scene):
11841192
scene._vtk_lock = _LockSpy()
11851193
observed = {}
11861194

1187-
scene._renderer._object_manager.UpdateStatesFromObjects = (
1188-
lambda ids: observed.update(depth=scene._vtk_lock.depth)
1195+
scene._renderer._object_manager.UpdateStateFromObject = (
1196+
lambda object_id: observed.update(depth=scene._vtk_lock.depth)
11891197
)
11901198

11911199
scene.apply_state(_persisted_state(_persisted_camera()))
@@ -1202,15 +1210,15 @@ def test_apply_state_serializes_the_camera_under_the_lock(scene):
12021210
# then ``self._renderer.serialize_camera_state()``, both inside
12031211
# ``_vtk_lock``. Same shape as the ``sync_camera`` trigger section above: the
12041212
# re-serialisation runs after the reset, is spied on
1205-
# ``_object_manager.UpdateStatesFromObjects`` with the render-window id, and
1213+
# ``_object_manager.UpdateStateFromObject`` with the active-camera id, and
12061214
# the lock is held (depth >= 1) at both points.
12071215
#
12081216
# Own literals are unnecessary here -- reset_camera takes no camera argument,
12091217
# only the scene-graph bounds -- so what is pinned is order and lock depth,
12101218
# not a value.
12111219
# ===========================================================================
12121220

1213-
def test_reset_camera_serializes_after_the_reset_with_the_render_window_id(scene):
1221+
def test_reset_camera_serializes_after_the_reset_with_the_active_camera_id(scene):
12141222
"""The re-serialisation follows the reset, and carries production's id.
12151223
12161224
Reverted -- the reset kept and the re-serialisation dropped -- the
@@ -1219,9 +1227,9 @@ def test_reset_camera_serializes_after_the_reset_with_the_render_window_id(scene
12191227
fetch.
12201228
12211229
The spy appends ``"reset"`` for the reset call and the two-tuple
1222-
``("serialize", <ids>)`` for the re-serialisation; the tuple is the
1223-
recording format, not the argument. ``<ids>`` is asserted as the list
1224-
production passes, since ``UpdateStatesFromObjects`` takes a sequence.
1230+
``("serialize", <id>)`` for the re-serialisation; the tuple is the
1231+
recording format, not the argument. ``<id>`` is asserted as the bare id
1232+
production passes, since ``UpdateStateFromObject`` takes a single id.
12251233
"""
12261234
order = []
12271235
real_reset = scene._renderer.reset_camera
@@ -1231,13 +1239,13 @@ def _reset(bounds):
12311239
return real_reset(bounds)
12321240

12331241
scene._renderer.reset_camera = _reset
1234-
scene._renderer._object_manager.UpdateStatesFromObjects = (
1235-
lambda ids: order.append(("serialize", ids))
1242+
scene._renderer._object_manager.UpdateStateFromObject = (
1243+
lambda object_id: order.append(("serialize", object_id))
12361244
)
12371245

12381246
scene.reset_camera()
12391247

1240-
assert order == ["reset", ("serialize", [RENDER_WINDOW_WASM_ID])]
1248+
assert order == ["reset", ("serialize", ACTIVE_CAMERA_WASM_ID)]
12411249

12421250

12431251
def test_reset_camera_holds_the_lock_across_both_halves(scene):
@@ -1261,8 +1269,8 @@ def _reset(bounds):
12611269
return real_reset(bounds)
12621270

12631271
scene._renderer.reset_camera = _reset
1264-
scene._renderer._object_manager.UpdateStatesFromObjects = (
1265-
lambda ids: observed.update(serialize_depth=scene._vtk_lock.depth)
1272+
scene._renderer._object_manager.UpdateStateFromObject = (
1273+
lambda object_id: observed.update(serialize_depth=scene._vtk_lock.depth)
12661274
)
12671275

12681276
scene.reset_camera()

0 commit comments

Comments
 (0)