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
6 changes: 6 additions & 0 deletions opengeodeweb_viewer_schemas.json
Original file line number Diff line number Diff line change
Expand Up @@ -4330,6 +4330,12 @@
"properties": {
"data_id": {
"type": "string"
},
"block_ids": {
"type": "array",
"items": {
"type": "integer"
}
}
},
"required": [],
Expand Down
3 changes: 2 additions & 1 deletion opengeodeweb_viewer_typed_schemas.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1187,7 +1187,8 @@ export interface ViewerPickColormapParams {
}

export interface ViewerPickColormapResponse {
data_id?: string;
block_ids?: number[];
data_id?: string;
}

export interface ViewerHighlightParams {
Expand Down
1 change: 0 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,3 @@ wslink==2.6.0
yarl==1.*
# via aiohttp

opengeodeweb-microservice==1.*,>=1.3.2
18 changes: 9 additions & 9 deletions src/opengeodeweb_viewer/object/object_methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ def remove_object(self, data_id: str) -> None:
for size_actor in pipeline.points_size_actors.values():
renderer.RemoveActor(size_actor.actor)
renderer.RemoveViewProp(pipeline.scalar_bar)
for bar in pipeline.scalar_bars.values():
renderer.RemoveViewProp(bar)
for scalar_bar in pipeline.scalar_bars.values():
renderer.RemoveViewProp(scalar_bar.actor)
self.deregister_object(data_id)
self.update_scalar_bars_layout()

Expand All @@ -74,8 +74,8 @@ def set_visibility(self, data_id: str, *, visibility: bool) -> None:
self.set_points_actors_visibility(pipeline, visibility=visibility)
if not visibility:
pipeline.scalar_bar.VisibilityOff()
for bar in pipeline.scalar_bars.values():
bar.VisibilityOff()
for scalar_bar in pipeline.scalar_bars.values():
scalar_bar.actor.VisibilityOff()
if pipeline.points_scalar_bar is not None:
pipeline.points_scalar_bar.VisibilityOff()
else:
Expand All @@ -87,9 +87,9 @@ def set_visibility(self, data_id: str, *, visibility: bool) -> None:
self.update_points_scalar_bar_visibility(pipeline)
for style in pipeline.block_styles.values():
if style.get("name"):
for bar in pipeline.scalar_bars.values():
if bar.GetLookupTable() is not None:
bar.VisibilityOn()
for scalar_bar in pipeline.scalar_bars.values():
if scalar_bar.actor.GetLookupTable() is not None:
scalar_bar.actor.VisibilityOn()
break
self.update_scalar_bars_layout()

Expand Down Expand Up @@ -234,8 +234,8 @@ def clear_colors(self, data_id: str) -> None:
pipeline.clear_blocks_scalars()
mapper.ScalarVisibilityOff()
pipeline.scalar_bar.VisibilityOff()
for bar in pipeline.scalar_bars.values():
bar.VisibilityOff()
for scalar_bar in pipeline.scalar_bars.values():
scalar_bar.actor.VisibilityOff()
self.update_scalar_bars_layout()

def _apply_highlight_style(self, actor: vtkActor, mapper: vtkDataSetMapper) -> None:
Expand Down
73 changes: 42 additions & 31 deletions src/opengeodeweb_viewer/rpc/model/model_protocols.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
)
from vtkmodules.vtkFiltersCore import vtkAppendDataSets
from vtkmodules.vtkIOXML import vtkXMLMultiBlockDataReader
from vtkmodules.vtkRenderingAnnotation import vtkScalarBarActor
from vtkmodules.vtkRenderingCore import (
vtkActor,
vtkCompositeDataDisplayAttributes,
Expand All @@ -32,6 +31,7 @@
from opengeodeweb_viewer.vtk_pipeline import (
BlockStyle,
ModelPointsStyle,
ModelScalarBar,
PointsSizeActor,
VtkPipeline,
)
Expand Down Expand Up @@ -122,46 +122,57 @@ def apply_color(
self.setup_model_color_map(pipeline)
return colors

@staticmethod
def scalar_bar_key(active_attrs: dict[str, BlockStyle], style: BlockStyle, label: str) -> str:
# Styles sharing the same color map share a scalar bar,
# the key only grows to tell them apart
name = style["name"]
item = style["item"]
minimum = style["minimum"]
maximum = style["maximum"]
points = style["points"]
attr_key = name
if attr_key in active_attrs and (
active_attrs[attr_key]["item"] != item
or active_attrs[attr_key]["minimum"] != minimum
or active_attrs[attr_key]["maximum"] != maximum
or active_attrs[attr_key]["points"] != points
):
attr_key = f"{name} (Item {item + 1})" if item > 0 else name
if attr_key in active_attrs and (
active_attrs[attr_key]["minimum"] != minimum
or active_attrs[attr_key]["maximum"] != maximum
or active_attrs[attr_key]["points"] != points
):
attr_key = f"{attr_key} [{minimum:g}, {maximum:g}]"
if attr_key in active_attrs and active_attrs[attr_key]["points"] != points:
attr_key = f"{attr_key} (Block {label})"
return attr_key

def setup_model_color_map(self, pipeline: VtkPipeline) -> None:
# Points attributes join the polygons ones, so identical color maps share one scalar bar
displayed_styles = [
(str(block_id), style) for block_id, style in pipeline.block_styles.items()
(block_id, str(block_id), style) for block_id, style in pipeline.block_styles.items()
] + [
(f"{block_id} points", points_style.attribute)
(block_id, f"{block_id} points", points_style.attribute)
for block_id, points_style in pipeline.points_block_styles.items()
if points_style.attribute is not None and points_style.visibility
]
active_attrs: dict[str, BlockStyle] = {}
for block_id, style in displayed_styles:
active_block_ids: dict[str, list[int]] = {}
for block_id, label, style in displayed_styles:
if style["name"]:
name = style["name"]
item = style["item"]
minimum = style["minimum"]
maximum = style["maximum"]
points = style["points"]
attr_key = name
if attr_key in active_attrs and (
active_attrs[attr_key]["item"] != item
or active_attrs[attr_key]["minimum"] != minimum
or active_attrs[attr_key]["maximum"] != maximum
or active_attrs[attr_key]["points"] != points
):
attr_key = f"{name} (Item {item + 1})" if item > 0 else name
if attr_key in active_attrs and (
active_attrs[attr_key]["minimum"] != minimum
or active_attrs[attr_key]["maximum"] != maximum
or active_attrs[attr_key]["points"] != points
):
attr_key = f"{attr_key} [{minimum:g}, {maximum:g}]"
if attr_key in active_attrs and active_attrs[attr_key]["points"] != points:
attr_key = f"{attr_key} (Block {block_id})"
attr_key = self.scalar_bar_key(active_attrs, style, label)
active_attrs[attr_key] = style
bar_block_ids = active_block_ids.setdefault(attr_key, [])
if block_id not in bar_block_ids:
bar_block_ids.append(block_id)
for name, style in active_attrs.items():
if name not in pipeline.scalar_bars:
bar = vtkScalarBarActor()
self.get_renderer().AddViewProp(bar)
pipeline.scalar_bars[name] = bar
bar = pipeline.scalar_bars[name]
pipeline.scalar_bars[name] = ModelScalarBar()
self.get_renderer().AddViewProp(pipeline.scalar_bars[name].actor)
pipeline.scalar_bars[name].block_ids = active_block_ids[name]
bar = pipeline.scalar_bars[name].actor
item = style["item"]
minimum = style["minimum"]
maximum = style["maximum"]
Expand All @@ -170,9 +181,9 @@ def setup_model_color_map(self, pipeline: VtkPipeline) -> None:
lut = create_color_transfer_function(points, minimum, maximum, item, no_data_color)
bar.SetLookupTable(lut)
bar.VisibilityOn()
for name, bar in pipeline.scalar_bars.items():
for name, scalar_bar in pipeline.scalar_bars.items():
if name not in active_attrs:
bar.VisibilityOff()
scalar_bar.actor.VisibilityOff()

self.update_scalar_bars_layout()

Expand Down
6 changes: 6 additions & 0 deletions src/opengeodeweb_viewer/rpc/viewer/schemas/pick_colormap.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@
"properties": {
"data_id": {
"type": "string"
},
"block_ids": {
"type": "array",
"items": {
"type": "integer"
}
}
},
"required": [],
Expand Down
3 changes: 2 additions & 1 deletion src/opengeodeweb_viewer/rpc/viewer/schemas/pick_colormap.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from pathlib import Path
from opengeodeweb_microservice.schemas import Route, load_schema
from typing import Optional
from typing import List, Optional
from dataclasses_json import DataClassJsonMixin
from opengeodeweb_microservice.schemas import print_dataclass
from dataclasses import dataclass
Expand All @@ -20,6 +20,7 @@ class PickColormapResponse(DataClassJsonMixin):
def __post_init__(self) -> None:
print_dataclass(self)

block_ids: Optional[List[int]] = None
data_id: Optional[str] = None


Expand Down
28 changes: 18 additions & 10 deletions src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@

# Third party imports
from vtkmodules.vtkIOImage import vtkJPEGWriter, vtkPNGWriter
from vtkmodules.vtkRenderingAnnotation import vtkAxesActor, vtkCubeAxesActor
from vtkmodules.vtkRenderingAnnotation import vtkAxesActor, vtkCubeAxesActor, vtkScalarBarActor
from vtkmodules.vtkRenderingCore import (
vtkAbstractMapper,
vtkActor,
vtkCellPicker,
vtkCompositePolyDataMapper,
vtkDataSetMapper,
vtkPropPicker,
vtkRenderer,
Expand Down Expand Up @@ -210,18 +211,25 @@ def pick_colormap(self, params: schemas.PickColormap) -> schemas.PickColormapRes
ny = 1.0 - (params.y / size[1])

for data_id, pipeline in self.get_data_base().items():
bar = pipeline.scalar_bar
if bar.GetVisibility() and bar.GetLookupTable() is not None:
pos = bar.GetPositionCoordinate()
bx, by = pos.GetValue()[0], pos.GetValue()[1]
w, h = bar.GetWidth(), bar.GetHeight()

# Check if click falls within the scalar bar bounding box
if bx <= nx <= bx + w and by <= ny <= by + h:
return schemas.PickColormapResponse(data_id=data_id)
# A model has one scalar bar per attribute, each coloring some of its blocks
if isinstance(pipeline.mapper, vtkCompositePolyDataMapper):
for scalar_bar in pipeline.scalar_bars.values():
if self.is_scalar_bar_clicked(scalar_bar.actor, nx, ny):
return schemas.PickColormapResponse(
data_id=data_id, block_ids=scalar_bar.block_ids
)
elif self.is_scalar_bar_clicked(pipeline.scalar_bar, nx, ny):
return schemas.PickColormapResponse(data_id=data_id)

return schemas.PickColormapResponse()

@staticmethod
def is_scalar_bar_clicked(bar: vtkScalarBarActor, nx: float, ny: float) -> bool:
if not bar.GetVisibility() or bar.GetLookupTable() is None:
return False
bx, by = bar.GetPositionCoordinate().GetValue()[:2]
return bool(bx <= nx <= bx + bar.GetWidth() and by <= ny <= by + bar.GetHeight())

def compute_epsilon(self, renderer: vtkRenderer, z: float) -> float:
renderer.SetDisplayPoint(0, 0, z)
renderer.DisplayToWorld()
Expand Down
9 changes: 8 additions & 1 deletion src/opengeodeweb_viewer/vtk_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,13 @@ class BlockStyle(TypedDict):
no_data_color: ColorClassProtocol | None


@dataclass
class ModelScalarBar:
actor: vtkScalarBarActor = field(default_factory=vtkScalarBarActor)
# Blocks colored by this scalar bar
block_ids: list[int] = field(default_factory=list)


@dataclass
class PointsSizeActor:
mapper: vtkCompositePolyDataMapper
Expand Down Expand Up @@ -239,7 +246,7 @@ class VtkPipeline:
block_data_sets: list[vtkDataObject | None] = field(default_factory=list)
block_geode_ids: list[str] = field(default_factory=list)
scalar_bar: vtkScalarBarActor = field(default_factory=vtkScalarBarActor)
scalar_bars: dict[str, vtkScalarBarActor] = field(default_factory=dict)
scalar_bars: dict[str, ModelScalarBar] = field(default_factory=dict)
block_styles: dict[int, BlockStyle] = field(default_factory=dict)
pick_mapper: vtkMapper | None = None
points_mapper: vtkDataSetMapper | None = None
Expand Down
6 changes: 3 additions & 3 deletions src/opengeodeweb_viewer/vtk_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -530,9 +530,9 @@ def visible_scalar_bars(
points_attr_name = pipeline.points_mapper.GetArrayName()
visible_bars.append((points_bar, points_attr_name, data_id, pipeline))

for attr_name, bar in pipeline.scalar_bars.items():
if bar.GetVisibility() and bar.GetLookupTable() is not None:
visible_bars.append((bar, attr_name, data_id, pipeline))
for attr_name, scalar_bar in pipeline.scalar_bars.items():
if scalar_bar.actor.GetVisibility() and scalar_bar.actor.GetLookupTable() is not None:
visible_bars.append((scalar_bar.actor, attr_name, data_id, pipeline))
return visible_bars

def update_scalar_bars_layout(self) -> None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
)

# Local application imports
from opengeodeweb_viewer.rpc.viewer.viewer_protocols import VtkViewerView
from tests.conftest import ServerMonitor
from tests.model.test_model_protocols import test_register_model_cube

if TYPE_CHECKING:
Expand Down Expand Up @@ -419,3 +421,35 @@ def test_blocks_polyhedron_color_map_rainbow(
)

assert server.compare_image("model/blocks/color_map_rainbow.jpeg")


def test_pick_blocks_polyhedron_color_map(
server: ServerMonitor, dataset_factory: Callable[..., str]
) -> None:

test_register_model_cube(server, dataset_factory)

server.call(
VtkModelBlocksAttributePolyhedronView.model_blocks_attribute_polyhedron_prefix
+ VtkModelBlocksAttributePolyhedronView.model_blocks_attribute_polyhedron_schemas_dict[
"attribute"
]["rpc"],
[
{
"id": model_id,
"block_ids": list(range(48, 50)),
"name": "tetrahedron_vertices",
"item": 0,
"points": [0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0],
"minimum": 0.0,
"maximum": 1.0,
}
],
)

# The first scalar bar is at the bottom left of the 300x300 view
pick_rpc = (
VtkViewerView.viewer_prefix + VtkViewerView.viewer_schemas_dict["pick_colormap"]["rpc"]
)
server.call(pick_rpc, [{"x": 81, "y": 276}])
assert server.get_result(pick_rpc) == {"data_id": model_id, "block_ids": [48, 49]}
Loading