Skip to content

Commit 874d7cb

Browse files
feat: Remote rendering 3.1a - add per-part write path to the dataset registry (#50)
Co-authored-by: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com>
1 parent 92c6ee4 commit 874d7cb

4 files changed

Lines changed: 383 additions & 2 deletions

File tree

‎doc/changelog.d/50.added.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Remote rendering 3.1a - add per-part write path to the dataset registry

‎src/ansys/visor/viewer/vtk/datasets/visor_dataset.py‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,19 @@ def list_variables(self) -> List[VisorPartVariables]:
8888
return result
8989

9090
def set_state(self, new_state: PersistedDatasetState) -> None:
91-
"""Set the state of the dataset."""
91+
"""
92+
Set the state of the dataset from persisted, name-keyed part state.
93+
94+
This is the persisted-state conversion path: it converts
95+
new_state.parts (keyed by part name) into this dataset's runtime
96+
state (keyed by part ID) via persisted_to_runtime_state.
97+
98+
Note: a second, id-keyed replacement path also exists, on the
99+
registry rather than here: VisorDatasetRegistry.replace_part_states
100+
replaces a dataset's .state directly with an already-runtime,
101+
id-keyed RuntimeDatasetState, without going through this method or
102+
its name-to-id conversion.
103+
"""
92104
self.state = self.persisted_to_runtime_state(new_state.parts)
93105

94106
def mark_clean(self) -> None:

‎src/ansys/visor/viewer/vtk/datasets/visor_dataset_registry.py‎

Lines changed: 179 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66
from ansys.visor.viewer.core.metadata import ExtendedMetadata
77
from ansys.visor.viewer.core.visor_logging import VisorDefaultLogger
88
from ansys.visor.viewer.core.visor_types import VisorDatasetType
9-
from ansys.visor.viewer.models.runtime.dataset.runtime_dataset_state import RuntimeDatasetState
9+
from ansys.visor.viewer.models.runtime.dataset.runtime_dataset_state import (
10+
RuntimeDatasetState,
11+
RuntimePartProperties,
12+
)
1013
from ansys.visor.viewer.vtk.datasets.visor_dataset import VisorDataset
1114
from ansys.visor.viewer.vtk.variables.visor_part_variables import VisorPartVariables
1215
from ansys.visor.viewer.vtk.variables.visor_variable_update import VisorVariableUpdate
@@ -106,6 +109,181 @@ def remove(self, dataset_id: int) -> None:
106109
if dataset_id in self.datasets:
107110
self.datasets.pop(dataset_id)
108111

112+
# ------------------------------------------------------------------
113+
# Per-part state: write path
114+
# ------------------------------------------------------------------
115+
116+
def find_dataset_id_for_part(self, part_id: int) -> int | None:
117+
"""
118+
Find the ID of the dataset that owns the given part.
119+
120+
Args:
121+
part_id (int): The scene-graph node ID identifying the part.
122+
123+
Returns:
124+
int | None: The dataset ID that owns the part, or None if no
125+
dataset in the registry has this part_id in its PartIndex.
126+
"""
127+
for dataset_id, dataset in self.datasets.items():
128+
if part_id in dataset.part_index.part_ids:
129+
return dataset_id
130+
return None
131+
132+
def get_part_state(self, part_id: int) -> RuntimePartProperties | None:
133+
"""
134+
Get the current runtime state record for a single part.
135+
136+
This is a read-only lookup: unlike the setters, it does not upsert
137+
a record for a part that has no state yet.
138+
139+
Args:
140+
part_id (int): The scene-graph node ID identifying the part.
141+
142+
Returns:
143+
RuntimePartProperties | None: The live state record for the
144+
part, or None if part_id resolves to no dataset, or the owning
145+
dataset has no recorded state for it.
146+
"""
147+
dataset_id = self.find_dataset_id_for_part(part_id)
148+
if dataset_id is None:
149+
return None
150+
return self.datasets[dataset_id].state.part_states.get(part_id)
151+
152+
def _get_or_create_part_state(self, part_id: int) -> RuntimePartProperties | None:
153+
"""
154+
Resolve the live part-state record for part_id, upserting if needed.
155+
156+
If the owning dataset has no part_states entry yet for part_id (e.g.
157+
a freshly added dataset with no persisted part state), a
158+
RuntimePartProperties(id=part_id) record is created and inserted
159+
into the dataset's part_states dict first. This is mandatory: a
160+
freshly added dataset has an empty part_states dict, so without
161+
this upsert every setter would silently no-op for it.
162+
163+
Returns:
164+
RuntimePartProperties | None: The live state record, or None if
165+
part_id resolves to no dataset.
166+
"""
167+
dataset_id = self.find_dataset_id_for_part(part_id)
168+
if dataset_id is None:
169+
return None
170+
dataset = self.datasets[dataset_id]
171+
part_state = dataset.state.part_states.get(part_id)
172+
if part_state is None:
173+
part_state = RuntimePartProperties(id=part_id)
174+
dataset.state.part_states[part_id] = part_state
175+
return part_state
176+
177+
def set_part_visibility(self, part_id: int, visible: bool) -> bool:
178+
"""
179+
Set whether a part is visible.
180+
181+
Returns:
182+
bool: True when the record was written, False when part_id
183+
resolves to no dataset. Never raises.
184+
"""
185+
part_state = self._get_or_create_part_state(part_id)
186+
if part_state is None:
187+
return False
188+
part_state.visible = visible
189+
return True
190+
191+
def set_part_opacity(self, part_id: int, opacity: float) -> bool:
192+
"""
193+
Set a part's opacity.
194+
195+
Returns:
196+
bool: True when the record was written, False when part_id
197+
resolves to no dataset. Never raises.
198+
"""
199+
part_state = self._get_or_create_part_state(part_id)
200+
if part_state is None:
201+
return False
202+
part_state.opacity = opacity
203+
return True
204+
205+
def set_part_diffuse_color(self, part_id: int, diffuse_rgb: list[float] | None) -> bool:
206+
"""
207+
Set a part's custom diffuse colour, or clear it with None.
208+
209+
Returns:
210+
bool: True when the record was written, False when part_id
211+
resolves to no dataset. Never raises.
212+
"""
213+
part_state = self._get_or_create_part_state(part_id)
214+
if part_state is None:
215+
return False
216+
part_state.diffuse_rgb = diffuse_rgb
217+
return True
218+
219+
def set_part_selected(self, part_id: int, selected: bool) -> bool:
220+
"""
221+
Set whether a part is selected.
222+
223+
Returns:
224+
bool: True when the record was written, False when part_id
225+
resolves to no dataset. Never raises.
226+
"""
227+
part_state = self._get_or_create_part_state(part_id)
228+
if part_state is None:
229+
return False
230+
part_state.selected = selected
231+
return True
232+
233+
def set_part_color_variable(self, part_id: int, variable_id: str, component: int | None) -> bool:
234+
"""
235+
Set the variable a part is coloured by, and its component.
236+
237+
variable_id and component are set together, atomically, in this one
238+
call, mirroring clear_part_color_variable's atomic clear.
239+
240+
Returns:
241+
bool: True when the record was written, False when part_id
242+
resolves to no dataset. Never raises.
243+
"""
244+
part_state = self._get_or_create_part_state(part_id)
245+
if part_state is None:
246+
return False
247+
part_state.spectrum_id = variable_id
248+
part_state.spectrum_component = component
249+
return True
250+
251+
def clear_part_color_variable(self, part_id: int) -> bool:
252+
"""
253+
Clear the variable a part is coloured by.
254+
255+
Sets spectrum_id and spectrum_component to None together, in one
256+
call — the compound class is only ever set or cleared atomically,
257+
never field by field.
258+
259+
Returns:
260+
bool: True when the record was written, False when part_id
261+
resolves to no dataset. Never raises.
262+
"""
263+
part_state = self._get_or_create_part_state(part_id)
264+
if part_state is None:
265+
return False
266+
part_state.spectrum_id = None
267+
part_state.spectrum_component = None
268+
return True
269+
270+
def replace_part_states(self, dataset_states: Dict[int, RuntimeDatasetState]) -> None:
271+
"""
272+
Replace the runtime state of registered datasets in bulk.
273+
274+
For each dataset ID present in both dataset_states and the
275+
registry, that dataset's .state is replaced directly with the
276+
supplied RuntimeDatasetState (already-runtime, id-keyed input).
277+
Dataset IDs in dataset_states that are not present in the registry
278+
are skipped silently; processing continues for the remaining
279+
entries. Never raises.
280+
"""
281+
for dataset_id, runtime_state in dataset_states.items():
282+
dataset = self.datasets.get(dataset_id)
283+
if dataset is None:
284+
continue
285+
dataset.state = runtime_state
286+
109287
def _get_unique_dataset_name(self, name: str) -> str:
110288
"""
111289
Rename the dataset by appending a suffix to ensure uniqueness.

0 commit comments

Comments
 (0)