Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/changelog.d/51.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Remote rendering 3.1b - implement per-part apply logic on the renderer and node pipeline
91 changes: 85 additions & 6 deletions src/ansys/visor/viewer/renderer/local_renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,23 +158,73 @@ def deregister_all(self) -> None:
# ------------------------------------------------------------------

def apply_visibility(self, node_id: int, visible: bool) -> None:
"""No-op in Story 1.2. Phase 3 populates."""
"""See :meth:`IRenderer.apply_visibility`.

Resolves the pipeline and delegates to
:meth:`VtkNodePipeline.set_visibility`. An unknown *node_id* is a
logged no-op, never a raise.
"""
pipe = self._pipelines.get(node_id)
if pipe is None:
logger.debug(
"apply_visibility: no pipeline for node %s; skipping.", node_id
)
return
pipe.set_visibility(visible)

def apply_opacity(self, node_id: int, opacity: float) -> None:
"""No-op in Story 1.2. Phase 3 populates."""
"""See :meth:`IRenderer.apply_opacity`.

Resolves the pipeline and delegates to
:meth:`VtkNodePipeline.set_opacity`. An unknown *node_id* is a
logged no-op, never a raise.
"""
pipe = self._pipelines.get(node_id)
if pipe is None:
logger.debug(
"apply_opacity: no pipeline for node %s; skipping.", node_id
)
return
pipe.set_opacity(opacity)

def apply_diffuse_color(
self, node_id: int, r: float, g: float, b: float
) -> None:
"""No-op in Story 1.2. Phase 3 populates."""
"""See :meth:`IRenderer.apply_diffuse_color`.

Resolves the pipeline and delegates to
:meth:`VtkNodePipeline.set_diffuse_color`. An unknown *node_id*
is a logged no-op, never a raise.
"""
pipe = self._pipelines.get(node_id)
if pipe is None:
logger.debug(
"apply_diffuse_color: no pipeline for node %s; skipping.", node_id
)
return
pipe.set_diffuse_color(r, g, b)

def apply_edge_visibility(self, node_id: int, edge_visible: bool) -> None:
"""No-op in Story 1.2. Phase 3 populates."""

def apply_selected(
self, node_id: int, selected: bool, diffuse_rgb: list
) -> None:
"""No-op in Story 1.2. Phase 3 populates."""
"""See :meth:`IRenderer.apply_selected`.

Resolves the pipeline and delegates to
:meth:`VtkNodePipeline.set_selected`. *diffuse_rgb* is passed
through as given; supplying a default when the part has no stored
colour is the coordinator's job, not the renderer's. An unknown
*node_id* is a logged no-op, never a raise.
"""
pipe = self._pipelines.get(node_id)
if pipe is None:
logger.debug(
"apply_selected: no pipeline for node %s; skipping.", node_id
)
return
pipe.set_selected(selected, diffuse_rgb)

def apply_color_variable(
self,
Expand All @@ -186,10 +236,39 @@ def apply_color_variable(
min_val: float,
max_val: float,
) -> None:
"""No-op in Story 1.2. Phase 3 populates."""
"""See :meth:`IRenderer.apply_color_variable`.

Resolves the pipeline and delegates to
:meth:`VtkNodePipeline.set_color_variable`. *array_type* must
already be a :class:`VisorVtkVariableType`; it is parsed at the
trigger boundary, never here, and the pipeline compares it by
identity, so any other value is a logged no-op there.
*spectrum_id* is not forwarded -- it is stored opaquely by the
registry and is not needed to configure the mapper. An unknown
*node_id* is a logged no-op, never a raise.
"""
pipe = self._pipelines.get(node_id)
if pipe is None:
logger.debug(
"apply_color_variable: no pipeline for node %s; skipping.", node_id
)
return
pipe.set_color_variable(array_type, array_name, component, min_val, max_val)

def clear_color_variable(self, node_id: int) -> None:
"""No-op in Story 1.2. Phase 3 populates."""
"""See :meth:`IRenderer.clear_color_variable`.

Resolves the pipeline and delegates to
:meth:`VtkNodePipeline.clear_color_variable`. An unknown
*node_id* is a logged no-op, never a raise.
"""
pipe = self._pipelines.get(node_id)
if pipe is None:
logger.debug(
"clear_color_variable: no pipeline for node %s; skipping.", node_id
)
return
pipe.clear_color_variable()

def refresh_color_variable_range(
self,
Expand Down
140 changes: 140 additions & 0 deletions src/ansys/visor/viewer/vtk/node_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from vtkmodules.vtkRenderingCore import vtkActor, vtkPolyDataMapper

from ansys.visor.viewer.core.visor_colors import VisorColors
from ansys.visor.viewer.core.visor_enums import VisorVtkVariableType
from ansys.visor.viewer.core.visor_logging import VisorDefaultLogger

logger = VisorDefaultLogger(__name__)
Expand Down Expand Up @@ -105,6 +106,145 @@ def update_input(
algorithm = algorithm_filter(algorithm)
self.mapper.SetInputConnection(algorithm.GetOutputPort())

# ------------------------------------------------------------------
# Per-part visual mutations
#
# These bodies live here rather than on the renderer because each is
# more than one VTK call, and the colour-variable body additionally
# reads a pipeline-internal object (``base_algorithm``). Identity
# resolution (node id -> pipeline) and the miss branch stay on the
# renderer, which is the only holder of the id -> pipeline map.
# ------------------------------------------------------------------

def set_selected(self, selected: bool, diffuse_rgb: list[float]) -> None:
"""Apply or remove the selection highlight on this part.

Parameters
----------
selected:
Target state. Absolute, never a toggle.
diffuse_rgb:
The part's diffuse colour, re-applied unconditionally on both
branches -- selection changes the ambient/diffuse lighting
terms, it does not replace the part's colour.

Note: :meth:`set_diffuse_color` also writes ``SetDiffuseColor``;
this method is not a substitute for it and vice versa.
"""
prop = self.actor.GetProperty()
if selected:
prop.SetAmbientColor(0 / 255, 62 / 255, 111 / 255)
prop.SetDiffuse(0.5)
prop.SetAmbient(0.5)
else:
prop.SetDiffuse(1.0)
prop.SetAmbient(0.0)
prop.SetDiffuseColor(*diffuse_rgb)

def set_visibility(self, visible: bool) -> None:
"""Show or hide this part.

Mutates the actor itself, not its property.

Parameters
----------
visible:
Target state. Absolute, never a toggle.
"""
self.actor.SetVisibility(1 if visible else 0)

def set_opacity(self, opacity: float) -> None:
"""Set this part's opacity.

Mutates the actor's property.

Parameters
----------
opacity:
The opacity value to apply, passed through unchanged.
"""
self.actor.GetProperty().SetOpacity(opacity)

def set_diffuse_color(self, r: float, g: float, b: float) -> None:
"""Set this part's diffuse colour.

Mutates the actor property's diffuse colour only.

Parameters
----------
r, g, b:
The diffuse colour components, passed through unchanged.

Note: :meth:`set_selected` also writes ``SetDiffuseColor`` on both
of its branches; this method is not a substitute for it and vice
versa.
"""
self.actor.GetProperty().SetDiffuseColor(r, g, b)

def set_color_variable(
self,
association: VisorVtkVariableType,
array_name: str,
component: int,
min_val: float,
max_val: float,
) -> None:
"""Colour this part by a scalar array, over an explicit range.

Configures the mapper only. No lookup table is authored here: the
table belongs to a later story, and until then a reference resolves
against the client-held default table.

``association`` is compared by identity against
:class:`VisorVtkVariableType`; it is never parsed, upper-cased or
string-compared. A value that is neither member, and an array name
that does not exist on the input, are both logged no-ops that
mutate nothing.
"""
in_data = self.base_algorithm.GetInput()
if association is VisorVtkVariableType.POINT:
field = in_data.GetPointData()
elif association is VisorVtkVariableType.CELL:
field = in_data.GetCellData()
else:
logger.warning(
"set_color_variable: association %r is not a VisorVtkVariableType; "
"skipping.",
association,
)
return

if field.GetArray(array_name) is None:
logger.warning(
"set_color_variable: array %r not found for association %s; skipping.",
array_name,
association,
)
return

mapper = self.mapper
if association is VisorVtkVariableType.POINT:
mapper.SetScalarModeToUsePointFieldData()
else:
mapper.SetScalarModeToUseCellFieldData()
mapper.SelectColorArray(array_name)
mapper.SetArrayComponent(component)
mapper.SetScalarRange(min_val, max_val)
mapper.SetColorModeToMapScalars()
mapper.SetScalarVisibility(True)
# Makes the mapper honour the range set above rather than the
# lookup table's own range.
mapper.SetUseLookupTableScalarRange(0)

def clear_color_variable(self) -> None:
"""Stop colouring this part by a scalar array.

One call. Does not touch a lookup table and does not restore a
diffuse colour -- the part's colour is whatever was last applied
to it.
"""
self.mapper.SetScalarVisibility(False)

# ------------------------------------------------------------------
# Private helpers
# ------------------------------------------------------------------
Expand Down
Loading
Loading