From 3bbbb14197c9a604863ed997491411b3576488b7 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Wed, 17 Jun 2026 15:11:04 +0200 Subject: [PATCH 01/10] fix(highlight): refacto logic --- .../rpc/viewer/viewer_protocols.py | 138 +++--------------- src/opengeodeweb_viewer/vtk_protocol.py | 95 +++++++++++- 2 files changed, 116 insertions(+), 117 deletions(-) diff --git a/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py b/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py index 39089c1..4305fec 100644 --- a/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py +++ b/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py @@ -15,7 +15,6 @@ vtkWorldPointPicker, vtkCellPicker, vtkPropPicker, - vtkCompositePolyDataMapper, vtkDataSetMapper, vtkActor, ) @@ -262,52 +261,24 @@ def pickedIds(self, rpc_params: RpcParams) -> dict[str, list[str] | int | None]: rpc_params, self.viewer_schemas_dict["picked_ids"], self.viewer_prefix ) params = schemas.PickedIDS.from_dict(rpc_params) - renderer = self.getView("-1").GetRenderers().GetFirstRenderer() - - pipelines_to_restore = [ - pipeline - for pipeline_id in params.ids - if (pipeline := self.get_vtk_pipeline(pipeline_id)).pick_mapper is not None - ] - for pipeline in pipelines_to_restore: - pick_mapper = pipeline.pick_mapper - if pick_mapper is not None: - pipeline.actor.SetMapper(pick_mapper) - - actors = [] picker = vtkCellPicker(tolerance=0.005) - picker.Pick(params.x, params.y, 0, renderer) - actor = picker.GetActor() - viewer_id = picker.GetFlatBlockIndex() - - while actor: - actors.append(actor) - actor.SetPickable(False) - picker.Pick(params.x, params.y, 0, renderer) - actor = picker.GetActor() - - for actor in actors: - actor.SetPickable(True) - for pipeline in pipelines_to_restore: - pipeline.actor.SetMapper(pipeline.mapper) - + actors, flat_index = self.pick_actors_under_coordinate(params.ids, params.x, params.y, picker) array_ids = [ - id for id in params.ids if self.get_vtk_pipeline(id).actor in actors + data_id + for data_id in params.ids + if self.get_vtk_pipeline(data_id).actor in actors ] if not array_ids: return {"array_ids": [], "viewer_id": None} - if array_ids and viewer_id != -1: + viewer_id = flat_index if flat_index != -1 else None + if viewer_id is not None: pipeline = self.get_vtk_pipeline(array_ids[0]) - mapper = pipeline.mapper - if isinstance(mapper, vtkCompositePolyDataMapper): - attr = mapper.GetCompositeDataDisplayAttributes() - if attr and not attr.GetBlockVisibility( - pipeline.blockDataSets[viewer_id] - ): - array_ids, viewer_id = [], -1 + dataset, geode_id, is_visible = self.get_composite_block_info(pipeline, picker) + if not is_visible: + return {"array_ids": [], "viewer_id": None} return { "array_ids": array_ids, - "viewer_id": viewer_id if viewer_id != -1 else None, + "viewer_id": viewer_id, } @exportRpc(viewer_prefix + viewer_schemas_dict["grid_scale"]["rpc"]) @@ -362,89 +333,26 @@ def setHighlight( rpc_params, self.viewer_schemas_dict["highlight"], self.viewer_prefix ) params = schemas.Highlight.from_dict(rpc_params) - picker = vtkCellPicker(tolerance=0.005) - - pipelines_to_restore = [ - pipeline - for pipeline_id in params.ids - if (pipeline := self.get_vtk_pipeline(pipeline_id)).pick_mapper is not None - ] - for pipeline in pipelines_to_restore: - pick_mapper = pipeline.pick_mapper - if pick_mapper is not None: - pipeline.actor.SetMapper(pick_mapper) - try: - picker.Pick(params.x, params.y, 0, self.get_renderer()) - finally: - for pipeline in pipelines_to_restore: - pipeline.actor.SetMapper(pipeline.mapper) - self.clear_highlights(params.ids) - actor = picker.GetActor() - pipeline_id = next( - (id for id in params.ids if self.get_vtk_pipeline(id).actor == actor), None - ) - id_to_select = ( - picker.GetCellId() - if params.field_type == schemas.FieldType.CELL - else picker.GetPointId() + picker = vtkCellPicker(tolerance=0.005) + data_id, id_to_select = self.pick_cell_or_point( + params.ids, params.x, params.y, params.field_type.value, picker ) - - if not pipeline_id or id_to_select == -1: + if not data_id or id_to_select == -1: + self.render(-1) + return {} + pipeline = self.get_vtk_pipeline(data_id) + dataset, geode_id, is_visible = self.get_composite_block_info(pipeline, picker) + if not is_visible: self.render(-1) return {} - - pipeline = self.get_vtk_pipeline(pipeline_id) - dataset = None - geode_id = None - if isinstance(pipeline.mapper, vtkCompositePolyDataMapper): - flat_index = picker.GetFlatBlockIndex() - dataset = ( - pipeline.blockDataSets[flat_index] - if 0 <= flat_index < len(pipeline.blockDataSets) - else None - ) - if dataset: - attr = pipeline.mapper.GetCompositeDataDisplayAttributes() - if attr and not attr.GetBlockVisibility(dataset): - self.render(-1) - return {} - geode_id = ( - pipeline.blockGeodeIds[flat_index] - if 0 <= flat_index < len(pipeline.blockGeodeIds) - else None - ) - self.update_highlight(pipeline, id_to_select, params.field_type.value, dataset) self.render(-1) - - data_obj = dataset or pipeline.reader.GetOutputDataObject(0) - data_attributes = {} - if isinstance(data_obj, vtkDataSet): - field_data = ( - data_obj.GetCellData() - if params.field_type == schemas.FieldType.CELL - else data_obj.GetPointData() - ) - for array_index in range(field_data.GetNumberOfArrays()): - array = field_data.GetArray(array_index) - if array and array.GetName(): - num_comps = array.GetNumberOfComponents() - component_values = [ - array.GetComponent(id_to_select, component_index) - for component_index in range(num_comps) - ] - data_attributes[array.GetName()] = ( - component_values[0] if num_comps == 1 else component_values - ) - - if params.field_type == schemas.FieldType.POINT: - point_coordinates = data_obj.GetPoint(id_to_select) - if point_coordinates: - data_attributes["coordinates"] = list(point_coordinates) - + data_attributes = self.extract_picked_attributes( + pipeline, id_to_select, params.field_type.value, dataset + ) return { - "id": pipeline_id, + "id": data_id, "picked_id": id_to_select, "field_type": params.field_type.value, "geode_id": geode_id, diff --git a/src/opengeodeweb_viewer/vtk_protocol.py b/src/opengeodeweb_viewer/vtk_protocol.py index 32169f9..329e520 100644 --- a/src/opengeodeweb_viewer/vtk_protocol.py +++ b/src/opengeodeweb_viewer/vtk_protocol.py @@ -18,6 +18,8 @@ vtkRenderer, vtkRenderWindow, vtkDataSetMapper, + vtkCompositePolyDataMapper, + vtkCellPicker, ) from vtkmodules.vtkCommonDataModel import ( vtkDataObject, @@ -189,11 +191,100 @@ def update_highlight( pipeline.highlight.extractSelection.Update() pipeline.highlight.actor.VisibilityOn() - def clear_highlights(self, ids: list[str]) -> None: - for data_id in ids: + def clear_highlights(self, data_ids: list[str]) -> None: + for data_id in data_ids: pipeline = self.get_vtk_pipeline(data_id) pipeline.highlight.actor.VisibilityOff() + def swap_pick_mappers(self, data_ids: list[str], use_pick_mapper: bool) -> None: + for data_id in data_ids: + pipeline = self.get_vtk_pipeline(data_id) + if pipeline.pick_mapper: + mapper = pipeline.pick_mapper if use_pick_mapper else pipeline.mapper + pipeline.actor.SetMapper(mapper) + + def pick_cell_or_point( + self, data_ids: list[str], x: float, y: float, field_type: str, picker: vtkCellPicker + ) -> tuple[str | None, int]: + self.swap_pick_mappers(data_ids, use_pick_mapper=True) + try: + picker.Pick(x, y, 0, self.get_renderer()) + finally: + self.swap_pick_mappers(data_ids, use_pick_mapper=False) + actor = picker.GetActor() + data_id = next( + ( + current_data_id + for current_data_id in data_ids + if self.get_vtk_pipeline(current_data_id).actor == actor + ), + None, + ) + id_to_select = picker.GetCellId() if field_type == "CELL" else picker.GetPointId() + return data_id, id_to_select + + def pick_actors_under_coordinate( + self, data_ids: list[str], x: float, y: float, picker: vtkCellPicker + ) -> tuple[list[vtkActor], int]: + renderer = self.get_renderer() + self.swap_pick_mappers(data_ids, use_pick_mapper=True) + actors = [] + viewer_id = -1 + try: + picker.Pick(x, y, 0, renderer) + viewer_id = picker.GetFlatBlockIndex() + while actor := picker.GetActor(): + actors.append(actor) + actor.SetPickable(False) + picker.Pick(x, y, 0, renderer) + finally: + for actor in actors: + actor.SetPickable(True) + self.swap_pick_mappers(data_ids, use_pick_mapper=False) + return actors, viewer_id + + def get_composite_block_info( + self, pipeline: VtkPipeline, picker: vtkCellPicker + ) -> tuple[vtkDataObject | None, str | None, bool]: + if not isinstance(pipeline.mapper, vtkCompositePolyDataMapper): + return None, None, True + flat_index = picker.GetFlatBlockIndex() + if not (0 <= flat_index < len(pipeline.blockDataSets)): + return None, None, True + dataset = pipeline.blockDataSets[flat_index] + if dataset: + attr = pipeline.mapper.GetCompositeDataDisplayAttributes() + if attr and not attr.GetBlockVisibility(dataset): + return None, None, False + geode_id = pipeline.blockGeodeIds[flat_index] if flat_index < len(pipeline.blockGeodeIds) else None + return dataset, geode_id, True + + def get_array_values(self, array: Any, id_to_select: int) -> list[float] | float: + components = array.GetNumberOfComponents() + if components == 1: + return array.GetComponent(id_to_select, 0) + return [array.GetComponent(id_to_select, i) for i in range(components)] + + def extract_picked_attributes( + self, + pipeline: VtkPipeline, + id_to_select: int, + field_type: str, + dataset: vtkDataObject | None, + ) -> dict[str, list[float] | float]: + data_object = dataset or pipeline.reader.GetOutputDataObject(0) + if not isinstance(data_object, vtkDataSet): + return {} + field_data = data_object.GetCellData() if field_type == "CELL" else data_object.GetPointData() + attributes = {} + for i in range(field_data.GetNumberOfArrays()): + array = field_data.GetArray(i) + if array and array.GetName(): + attributes[array.GetName()] = self.get_array_values(array, id_to_select) + if field_type == "POINT" and (coords := data_object.GetPoint(id_to_select)): + attributes["coordinates"] = list(coords) + return attributes + def update_grid_scale_and_clipping_range(self) -> None: grid_scale = self.get_grid_scale() if grid_scale is not None: From 6cffa23e1ffbb555df4724ffa47d0cdb50fc67cb Mon Sep 17 00:00:00 2001 From: MaxNumerique <144453705+MaxNumerique@users.noreply.github.com> Date: Wed, 17 Jun 2026 13:24:08 +0000 Subject: [PATCH 02/10] Apply prepare changes --- requirements.txt | 1 - .../rpc/viewer/viewer_protocols.py | 8 +++++-- src/opengeodeweb_viewer/vtk_protocol.py | 23 +++++++++++++++---- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/requirements.txt b/requirements.txt index 50aaa5f..9108a15 100644 --- a/requirements.txt +++ b/requirements.txt @@ -63,4 +63,3 @@ wslink==1.12.4 yarl>=1 # via aiohttp -opengeodeweb-microservice==1.*,>=1.1.3 diff --git a/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py b/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py index 4305fec..c5aabe9 100644 --- a/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py +++ b/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py @@ -262,7 +262,9 @@ def pickedIds(self, rpc_params: RpcParams) -> dict[str, list[str] | int | None]: ) params = schemas.PickedIDS.from_dict(rpc_params) picker = vtkCellPicker(tolerance=0.005) - actors, flat_index = self.pick_actors_under_coordinate(params.ids, params.x, params.y, picker) + actors, flat_index = self.pick_actors_under_coordinate( + params.ids, params.x, params.y, picker + ) array_ids = [ data_id for data_id in params.ids @@ -273,7 +275,9 @@ def pickedIds(self, rpc_params: RpcParams) -> dict[str, list[str] | int | None]: viewer_id = flat_index if flat_index != -1 else None if viewer_id is not None: pipeline = self.get_vtk_pipeline(array_ids[0]) - dataset, geode_id, is_visible = self.get_composite_block_info(pipeline, picker) + dataset, geode_id, is_visible = self.get_composite_block_info( + pipeline, picker + ) if not is_visible: return {"array_ids": [], "viewer_id": None} return { diff --git a/src/opengeodeweb_viewer/vtk_protocol.py b/src/opengeodeweb_viewer/vtk_protocol.py index 329e520..b36a025 100644 --- a/src/opengeodeweb_viewer/vtk_protocol.py +++ b/src/opengeodeweb_viewer/vtk_protocol.py @@ -204,7 +204,12 @@ def swap_pick_mappers(self, data_ids: list[str], use_pick_mapper: bool) -> None: pipeline.actor.SetMapper(mapper) def pick_cell_or_point( - self, data_ids: list[str], x: float, y: float, field_type: str, picker: vtkCellPicker + self, + data_ids: list[str], + x: float, + y: float, + field_type: str, + picker: vtkCellPicker, ) -> tuple[str | None, int]: self.swap_pick_mappers(data_ids, use_pick_mapper=True) try: @@ -220,7 +225,9 @@ def pick_cell_or_point( ), None, ) - id_to_select = picker.GetCellId() if field_type == "CELL" else picker.GetPointId() + id_to_select = ( + picker.GetCellId() if field_type == "CELL" else picker.GetPointId() + ) return data_id, id_to_select def pick_actors_under_coordinate( @@ -256,7 +263,11 @@ def get_composite_block_info( attr = pipeline.mapper.GetCompositeDataDisplayAttributes() if attr and not attr.GetBlockVisibility(dataset): return None, None, False - geode_id = pipeline.blockGeodeIds[flat_index] if flat_index < len(pipeline.blockGeodeIds) else None + geode_id = ( + pipeline.blockGeodeIds[flat_index] + if flat_index < len(pipeline.blockGeodeIds) + else None + ) return dataset, geode_id, True def get_array_values(self, array: Any, id_to_select: int) -> list[float] | float: @@ -275,7 +286,11 @@ def extract_picked_attributes( data_object = dataset or pipeline.reader.GetOutputDataObject(0) if not isinstance(data_object, vtkDataSet): return {} - field_data = data_object.GetCellData() if field_type == "CELL" else data_object.GetPointData() + field_data = ( + data_object.GetCellData() + if field_type == "CELL" + else data_object.GetPointData() + ) attributes = {} for i in range(field_data.GetNumberOfArrays()): array = field_data.GetArray(i) From f3052f4c9339d8012e258fdadab970b46743527d Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Wed, 17 Jun 2026 15:29:10 +0200 Subject: [PATCH 03/10] mypy --- src/opengeodeweb_viewer/vtk_protocol.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/opengeodeweb_viewer/vtk_protocol.py b/src/opengeodeweb_viewer/vtk_protocol.py index b36a025..71ebd0c 100644 --- a/src/opengeodeweb_viewer/vtk_protocol.py +++ b/src/opengeodeweb_viewer/vtk_protocol.py @@ -273,8 +273,8 @@ def get_composite_block_info( def get_array_values(self, array: Any, id_to_select: int) -> list[float] | float: components = array.GetNumberOfComponents() if components == 1: - return array.GetComponent(id_to_select, 0) - return [array.GetComponent(id_to_select, i) for i in range(components)] + return float(array.GetComponent(id_to_select, 0)) + return [float(array.GetComponent(id_to_select, i)) for i in range(components)] def extract_picked_attributes( self, From 8110f3f0ac4c33f407bd80afee18974125b55e7b Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Wed, 17 Jun 2026 17:05:16 +0200 Subject: [PATCH 04/10] vtkHardwarePicker instead of vtkCellPicker --- .../object/object_methods.py | 36 ------------------ .../rpc/viewer/viewer_protocols.py | 20 +++------- src/opengeodeweb_viewer/vtk_protocol.py | 37 +++++-------------- 3 files changed, 16 insertions(+), 77 deletions(-) diff --git a/src/opengeodeweb_viewer/object/object_methods.py b/src/opengeodeweb_viewer/object/object_methods.py index c2ec3ee..ee9d948 100644 --- a/src/opengeodeweb_viewer/object/object_methods.py +++ b/src/opengeodeweb_viewer/object/object_methods.py @@ -9,14 +9,11 @@ vtkActor, vtkTexture, vtkCompositePolyDataMapper, - vtkCompositeDataDisplayAttributes, vtkDataSetMapper, ) from vtkmodules.vtkCommonDataModel import ( vtkDataSet, - vtkMultiBlockDataSet, ) -from vtkmodules.vtkFiltersExtraction import vtkExtractSelection # Local application imports from opengeodeweb_viewer.vtk_protocol import VtkView, VtkPipeline @@ -133,27 +130,6 @@ def SetPointsColor( actor = self.get_vtk_pipeline(data_id).actor actor.GetProperty().SetVertexColor([red / 255, green / 255, blue / 255]) - def _prune_hidden_blocks( - self, - dataset: vtkMultiBlockDataSet, - visibility_attributes: vtkCompositeDataDisplayAttributes, - ) -> vtkMultiBlockDataSet: - pruned = vtkMultiBlockDataSet() - pruned.SetNumberOfBlocks(dataset.GetNumberOfBlocks()) - for index in range(dataset.GetNumberOfBlocks()): - block = dataset.GetBlock(index) - if block is None: - continue - if not visibility_attributes.GetBlockVisibility(block): - continue - if isinstance(block, vtkMultiBlockDataSet): - pruned.SetBlock( - index, self._prune_hidden_blocks(block, visibility_attributes) - ) - else: - pruned.SetBlock(index, block) - return pruned - def SetBlocksVisibility( self, data_id: str, block_ids: list[int], visibility: bool ) -> None: @@ -165,18 +141,6 @@ def SetBlocksVisibility( visibility_attributes = mapper.GetCompositeDataDisplayAttributes() for block_id in block_ids: visibility_attributes.SetBlockVisibility(blocks[block_id], visibility) - dataset = ( - pipeline.filter.GetOutputDataObject(0) - if pipeline.filter - else pipeline.reader.GetOutputDataObject(0) - ) - if not isinstance(dataset, vtkMultiBlockDataSet): - return - if pipeline.pick_mapper is None: - pipeline.pick_mapper = vtkCompositePolyDataMapper() - pipeline.pick_mapper.SetInputDataObject( - self._prune_hidden_blocks(dataset, visibility_attributes) - ) def SetBlocksColor( self, diff --git a/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py b/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py index c5aabe9..8de1c31 100644 --- a/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py +++ b/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py @@ -13,7 +13,7 @@ vtkRenderWindowInteractor, vtkAbstractMapper, vtkWorldPointPicker, - vtkCellPicker, + vtkHardwarePicker, vtkPropPicker, vtkDataSetMapper, vtkActor, @@ -261,7 +261,8 @@ def pickedIds(self, rpc_params: RpcParams) -> dict[str, list[str] | int | None]: rpc_params, self.viewer_schemas_dict["picked_ids"], self.viewer_prefix ) params = schemas.PickedIDS.from_dict(rpc_params) - picker = vtkCellPicker(tolerance=0.005) + picker = vtkHardwarePicker() + picker.SetPixelTolerance(15) actors, flat_index = self.pick_actors_under_coordinate( params.ids, params.x, params.y, picker ) @@ -273,13 +274,6 @@ def pickedIds(self, rpc_params: RpcParams) -> dict[str, list[str] | int | None]: if not array_ids: return {"array_ids": [], "viewer_id": None} viewer_id = flat_index if flat_index != -1 else None - if viewer_id is not None: - pipeline = self.get_vtk_pipeline(array_ids[0]) - dataset, geode_id, is_visible = self.get_composite_block_info( - pipeline, picker - ) - if not is_visible: - return {"array_ids": [], "viewer_id": None} return { "array_ids": array_ids, "viewer_id": viewer_id, @@ -338,7 +332,8 @@ def setHighlight( ) params = schemas.Highlight.from_dict(rpc_params) self.clear_highlights(params.ids) - picker = vtkCellPicker(tolerance=0.005) + picker = vtkHardwarePicker() + picker.SetPixelTolerance(15) data_id, id_to_select = self.pick_cell_or_point( params.ids, params.x, params.y, params.field_type.value, picker ) @@ -346,10 +341,7 @@ def setHighlight( self.render(-1) return {} pipeline = self.get_vtk_pipeline(data_id) - dataset, geode_id, is_visible = self.get_composite_block_info(pipeline, picker) - if not is_visible: - self.render(-1) - return {} + dataset, geode_id = self.get_composite_block_info(pipeline, picker) self.update_highlight(pipeline, id_to_select, params.field_type.value, dataset) self.render(-1) data_attributes = self.extract_picked_attributes( diff --git a/src/opengeodeweb_viewer/vtk_protocol.py b/src/opengeodeweb_viewer/vtk_protocol.py index 71ebd0c..ef02e58 100644 --- a/src/opengeodeweb_viewer/vtk_protocol.py +++ b/src/opengeodeweb_viewer/vtk_protocol.py @@ -19,7 +19,7 @@ vtkRenderWindow, vtkDataSetMapper, vtkCompositePolyDataMapper, - vtkCellPicker, + vtkHardwarePicker, ) from vtkmodules.vtkCommonDataModel import ( vtkDataObject, @@ -79,7 +79,6 @@ class VtkPipeline: blockGeodeIds: list[str] = field(default_factory=list) scalarBar: vtkScalarBarActor = field(default_factory=vtkScalarBarActor) block_styles: dict[int, BlockStyle] = field(default_factory=dict) - pick_mapper: vtkMapper | None = None class VtkTypingMixin: @@ -196,26 +195,16 @@ def clear_highlights(self, data_ids: list[str]) -> None: pipeline = self.get_vtk_pipeline(data_id) pipeline.highlight.actor.VisibilityOff() - def swap_pick_mappers(self, data_ids: list[str], use_pick_mapper: bool) -> None: - for data_id in data_ids: - pipeline = self.get_vtk_pipeline(data_id) - if pipeline.pick_mapper: - mapper = pipeline.pick_mapper if use_pick_mapper else pipeline.mapper - pipeline.actor.SetMapper(mapper) - def pick_cell_or_point( self, data_ids: list[str], x: float, y: float, field_type: str, - picker: vtkCellPicker, + picker: vtkHardwarePicker, ) -> tuple[str | None, int]: - self.swap_pick_mappers(data_ids, use_pick_mapper=True) - try: - picker.Pick(x, y, 0, self.get_renderer()) - finally: - self.swap_pick_mappers(data_ids, use_pick_mapper=False) + picker.SetSnapToMeshPoint(field_type == "POINT") + picker.Pick(x, y, 0, self.get_renderer()) actor = picker.GetActor() data_id = next( ( @@ -231,10 +220,9 @@ def pick_cell_or_point( return data_id, id_to_select def pick_actors_under_coordinate( - self, data_ids: list[str], x: float, y: float, picker: vtkCellPicker + self, data_ids: list[str], x: float, y: float, picker: vtkHardwarePicker ) -> tuple[list[vtkActor], int]: renderer = self.get_renderer() - self.swap_pick_mappers(data_ids, use_pick_mapper=True) actors = [] viewer_id = -1 try: @@ -247,28 +235,23 @@ def pick_actors_under_coordinate( finally: for actor in actors: actor.SetPickable(True) - self.swap_pick_mappers(data_ids, use_pick_mapper=False) return actors, viewer_id def get_composite_block_info( - self, pipeline: VtkPipeline, picker: vtkCellPicker - ) -> tuple[vtkDataObject | None, str | None, bool]: + self, pipeline: VtkPipeline, picker: vtkHardwarePicker + ) -> tuple[vtkDataObject | None, str | None]: if not isinstance(pipeline.mapper, vtkCompositePolyDataMapper): - return None, None, True + return None, None flat_index = picker.GetFlatBlockIndex() if not (0 <= flat_index < len(pipeline.blockDataSets)): - return None, None, True + return None, None dataset = pipeline.blockDataSets[flat_index] - if dataset: - attr = pipeline.mapper.GetCompositeDataDisplayAttributes() - if attr and not attr.GetBlockVisibility(dataset): - return None, None, False geode_id = ( pipeline.blockGeodeIds[flat_index] if flat_index < len(pipeline.blockGeodeIds) else None ) - return dataset, geode_id, True + return dataset, geode_id def get_array_values(self, array: Any, id_to_select: int) -> list[float] | float: components = array.GetNumberOfComponents() From 91e450ba057a7b257bb67f8b7d224d3e9e0e0ae4 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Wed, 17 Jun 2026 17:14:25 +0200 Subject: [PATCH 05/10] better for curves and points --- src/opengeodeweb_viewer/vtk_protocol.py | 71 +++++++++++++++++++------ 1 file changed, 55 insertions(+), 16 deletions(-) diff --git a/src/opengeodeweb_viewer/vtk_protocol.py b/src/opengeodeweb_viewer/vtk_protocol.py index ef02e58..52685be 100644 --- a/src/opengeodeweb_viewer/vtk_protocol.py +++ b/src/opengeodeweb_viewer/vtk_protocol.py @@ -29,7 +29,7 @@ vtkSelectionNode, ) from vtkmodules.vtkFiltersExtraction import vtkExtractSelection -from vtkmodules.vtkCommonCore import vtkStringArray, vtkIdTypeArray +from vtkmodules.vtkCommonCore import vtkStringArray, vtkIdTypeArray, vtkIdList from vtkmodules.vtkRenderingAnnotation import ( vtkCubeAxesActor, vtkAxesActor, @@ -203,21 +203,50 @@ def pick_cell_or_point( field_type: str, picker: vtkHardwarePicker, ) -> tuple[str | None, int]: - picker.SetSnapToMeshPoint(field_type == "POINT") - picker.Pick(x, y, 0, self.get_renderer()) - actor = picker.GetActor() - data_id = next( - ( - current_data_id - for current_data_id in data_ids - if self.get_vtk_pipeline(current_data_id).actor == actor - ), - None, - ) - id_to_select = ( - picker.GetCellId() if field_type == "CELL" else picker.GetPointId() - ) - return data_id, id_to_select + renderer = self.get_renderer() + + def find_data_id(actor: vtkActor | None) -> str | None: + return next( + ( + current_data_id + for current_data_id in data_ids + if self.get_vtk_pipeline(current_data_id).actor == actor + ), + None, + ) + + picker.SnapToMeshPointOff() + picker.Pick(x, y, 0, renderer) + data_id = find_data_id(picker.GetActor()) + if data_id: + if field_type == "POINT": + return data_id, picker.GetPointId() + cell_id = picker.GetCellId() + if cell_id != -1: + return data_id, cell_id + + picker.SnapToMeshPointOn() + picker.Pick(x, y, 0, renderer) + data_id = find_data_id(picker.GetActor()) + if not data_id: + return None, -1 + point_id = picker.GetPointId() + if field_type == "POINT": + return data_id, point_id + if point_id == -1: + return data_id, -1 + pipeline = self.get_vtk_pipeline(data_id) + dataset, _ = self.get_composite_block_info(pipeline, picker) + data_object = dataset or pipeline.reader.GetOutputDataObject(0) + if not isinstance(data_object, vtkDataSet): + return data_id, -1 + if hasattr(data_object, "BuildLinks"): + data_object.BuildLinks() + cell_ids = vtkIdList() + data_object.GetPointCells(point_id, cell_ids) + if cell_ids.GetNumberOfIds() > 0: + return data_id, cell_ids.GetId(0) + return data_id, -1 def pick_actors_under_coordinate( self, data_ids: list[str], x: float, y: float, picker: vtkHardwarePicker @@ -226,12 +255,22 @@ def pick_actors_under_coordinate( actors = [] viewer_id = -1 try: + # direct pick + picker.SnapToMeshPointOff() picker.Pick(x, y, 0, renderer) viewer_id = picker.GetFlatBlockIndex() while actor := picker.GetActor(): actors.append(actor) actor.SetPickable(False) picker.Pick(x, y, 0, renderer) + + #finds thin geometry (lines/curves) + if not actors: + picker.SnapToMeshPointOn() + picker.Pick(x, y, 0, renderer) + if actor := picker.GetActor(): + actors.append(actor) + viewer_id = picker.GetFlatBlockIndex() finally: for actor in actors: actor.SetPickable(True) From f55821d57e06ddac5936fb8062d588ca25ce6f8e Mon Sep 17 00:00:00 2001 From: MaxNumerique <144453705+MaxNumerique@users.noreply.github.com> Date: Wed, 17 Jun 2026 15:15:18 +0000 Subject: [PATCH 06/10] Apply prepare changes --- src/opengeodeweb_viewer/vtk_protocol.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/opengeodeweb_viewer/vtk_protocol.py b/src/opengeodeweb_viewer/vtk_protocol.py index 52685be..0c06308 100644 --- a/src/opengeodeweb_viewer/vtk_protocol.py +++ b/src/opengeodeweb_viewer/vtk_protocol.py @@ -255,7 +255,7 @@ def pick_actors_under_coordinate( actors = [] viewer_id = -1 try: - # direct pick + # direct pick picker.SnapToMeshPointOff() picker.Pick(x, y, 0, renderer) viewer_id = picker.GetFlatBlockIndex() @@ -264,7 +264,7 @@ def pick_actors_under_coordinate( actor.SetPickable(False) picker.Pick(x, y, 0, renderer) - #finds thin geometry (lines/curves) + # finds thin geometry (lines/curves) if not actors: picker.SnapToMeshPointOn() picker.Pick(x, y, 0, renderer) From 34df74b2aff51b2f773fccb4cc93fecddca1316b Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Thu, 18 Jun 2026 10:33:00 +0200 Subject: [PATCH 07/10] revert back to vtkCellPicker and add comments --- .../object/object_methods.py | 36 ++++++ .../rpc/viewer/viewer_protocols.py | 33 ++++- src/opengeodeweb_viewer/vtk_protocol.py | 115 ++++++++---------- 3 files changed, 116 insertions(+), 68 deletions(-) diff --git a/src/opengeodeweb_viewer/object/object_methods.py b/src/opengeodeweb_viewer/object/object_methods.py index ee9d948..bb650b8 100644 --- a/src/opengeodeweb_viewer/object/object_methods.py +++ b/src/opengeodeweb_viewer/object/object_methods.py @@ -9,11 +9,14 @@ vtkActor, vtkTexture, vtkCompositePolyDataMapper, + vtkCompositeDataDisplayAttributes, vtkDataSetMapper, ) from vtkmodules.vtkCommonDataModel import ( vtkDataSet, + vtkMultiBlockDataSet, ) +from vtkmodules.vtkFiltersExtraction import vtkExtractSelection # Local application imports from opengeodeweb_viewer.vtk_protocol import VtkView, VtkPipeline @@ -130,6 +133,28 @@ def SetPointsColor( actor = self.get_vtk_pipeline(data_id).actor actor.GetProperty().SetVertexColor([red / 255, green / 255, blue / 255]) + def _prune_hidden_blocks( + self, + dataset: vtkMultiBlockDataSet, + visibility_attributes: vtkCompositeDataDisplayAttributes, + ) -> vtkMultiBlockDataSet: + # Recursively construct a new multi-block dataset excluding hidden blocks. + pruned = vtkMultiBlockDataSet() + pruned.SetNumberOfBlocks(dataset.GetNumberOfBlocks()) + for index in range(dataset.GetNumberOfBlocks()): + block = dataset.GetBlock(index) + if block is None: + continue + if not visibility_attributes.GetBlockVisibility(block): + continue + if isinstance(block, vtkMultiBlockDataSet): + pruned.SetBlock( + index, self._prune_hidden_blocks(block, visibility_attributes) + ) + else: + pruned.SetBlock(index, block) + return pruned + def SetBlocksVisibility( self, data_id: str, block_ids: list[int], visibility: bool ) -> None: @@ -137,10 +162,21 @@ def SetBlocksVisibility( mapper = pipeline.mapper if not isinstance(mapper, vtkCompositePolyDataMapper): raise Exception("Mapper is not a vtkCompositePolyDataMapper") + # Update block visibility attributes on the main mapper blocks = pipeline.blockDataSets visibility_attributes = mapper.GetCompositeDataDisplayAttributes() for block_id in block_ids: visibility_attributes.SetBlockVisibility(blocks[block_id], visibility) + # Extract output dataset from filter or reader + dataset = (pipeline.filter or pipeline.reader).GetOutputDataObject(0) + if not isinstance(dataset, vtkMultiBlockDataSet): + return + # Re-build a pruned dataset for the dedicated pick mapper + if pipeline.pick_mapper is None: + pipeline.pick_mapper = vtkCompositePolyDataMapper() + pipeline.pick_mapper.SetInputDataObject( + self._prune_hidden_blocks(dataset, visibility_attributes) + ) def SetBlocksColor( self, diff --git a/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py b/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py index 8de1c31..299bddf 100644 --- a/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py +++ b/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py @@ -13,7 +13,7 @@ vtkRenderWindowInteractor, vtkAbstractMapper, vtkWorldPointPicker, - vtkHardwarePicker, + vtkCellPicker, vtkPropPicker, vtkDataSetMapper, vtkActor, @@ -261,11 +261,12 @@ def pickedIds(self, rpc_params: RpcParams) -> dict[str, list[str] | int | None]: rpc_params, self.viewer_schemas_dict["picked_ids"], self.viewer_prefix ) params = schemas.PickedIDS.from_dict(rpc_params) - picker = vtkHardwarePicker() - picker.SetPixelTolerance(15) + picker = vtkCellPicker(tolerance=0.005) + # Retrieve all actors under the clicked coordinates actors, flat_index = self.pick_actors_under_coordinate( params.ids, params.x, params.y, picker ) + # Filter pipeline IDs whose actors are in the picked list array_ids = [ data_id for data_id in params.ids @@ -273,7 +274,17 @@ def pickedIds(self, rpc_params: RpcParams) -> dict[str, list[str] | int | None]: ] if not array_ids: return {"array_ids": [], "viewer_id": None} + # Map flat_index to None if it is -1 (no composite block picked) viewer_id = flat_index if flat_index != -1 else None + if viewer_id is not None: + pipeline = self.get_vtk_pipeline(array_ids[0]) + # Verify composite block visibility (discard pick if block is hidden) + dataset, geode_id, is_visible = self.get_composite_block_info( + pipeline, picker + ) + if not is_visible: + return {"array_ids": [], "viewer_id": None} + return { "array_ids": array_ids, "viewer_id": viewer_id, @@ -331,17 +342,27 @@ def setHighlight( rpc_params, self.viewer_schemas_dict["highlight"], self.viewer_prefix ) params = schemas.Highlight.from_dict(rpc_params) + + # 1. Clear previous highlights self.clear_highlights(params.ids) - picker = vtkHardwarePicker() - picker.SetPixelTolerance(15) + picker = vtkCellPicker(tolerance=0.005) + + # 2. Perform pick operation to identify clicked pipeline and primitive ID data_id, id_to_select = self.pick_cell_or_point( params.ids, params.x, params.y, params.field_type.value, picker ) if not data_id or id_to_select == -1: self.render(-1) return {} + + # 3. Retrieve picked composite block information and check block visibility pipeline = self.get_vtk_pipeline(data_id) - dataset, geode_id = self.get_composite_block_info(pipeline, picker) + dataset, geode_id, is_visible = self.get_composite_block_info(pipeline, picker) + if not is_visible: + self.render(-1) + return {} + + # 4. Update highlight visibility and extract attributes from the picked element self.update_highlight(pipeline, id_to_select, params.field_type.value, dataset) self.render(-1) data_attributes = self.extract_picked_attributes( diff --git a/src/opengeodeweb_viewer/vtk_protocol.py b/src/opengeodeweb_viewer/vtk_protocol.py index 0c06308..d20d85b 100644 --- a/src/opengeodeweb_viewer/vtk_protocol.py +++ b/src/opengeodeweb_viewer/vtk_protocol.py @@ -19,6 +19,7 @@ vtkRenderWindow, vtkDataSetMapper, vtkCompositePolyDataMapper, + vtkCellPicker, vtkHardwarePicker, ) from vtkmodules.vtkCommonDataModel import ( @@ -29,7 +30,7 @@ vtkSelectionNode, ) from vtkmodules.vtkFiltersExtraction import vtkExtractSelection -from vtkmodules.vtkCommonCore import vtkStringArray, vtkIdTypeArray, vtkIdList +from vtkmodules.vtkCommonCore import vtkStringArray, vtkIdTypeArray from vtkmodules.vtkRenderingAnnotation import ( vtkCubeAxesActor, vtkAxesActor, @@ -79,6 +80,7 @@ class VtkPipeline: blockGeodeIds: list[str] = field(default_factory=list) scalarBar: vtkScalarBarActor = field(default_factory=vtkScalarBarActor) block_styles: dict[int, BlockStyle] = field(default_factory=dict) + pick_mapper: vtkMapper | None = None class VtkTypingMixin: @@ -195,102 +197,91 @@ def clear_highlights(self, data_ids: list[str]) -> None: pipeline = self.get_vtk_pipeline(data_id) pipeline.highlight.actor.VisibilityOff() + def swap_pick_mappers(self, data_ids: list[str], use_pick_mapper: bool) -> None: + # Swap actor mappers between the default and the pick_mapper (where hidden blocks are pruned). + for data_id in data_ids: + pipeline = self.get_vtk_pipeline(data_id) + if pipeline.pick_mapper: + mapper = pipeline.pick_mapper if use_pick_mapper else pipeline.mapper + pipeline.actor.SetMapper(mapper) + def pick_cell_or_point( self, data_ids: list[str], x: float, y: float, field_type: str, - picker: vtkHardwarePicker, + picker: vtkCellPicker, ) -> tuple[str | None, int]: - renderer = self.get_renderer() - - def find_data_id(actor: vtkActor | None) -> str | None: - return next( - ( - current_data_id - for current_data_id in data_ids - if self.get_vtk_pipeline(current_data_id).actor == actor - ), - None, - ) - - picker.SnapToMeshPointOff() - picker.Pick(x, y, 0, renderer) - data_id = find_data_id(picker.GetActor()) - if data_id: - if field_type == "POINT": - return data_id, picker.GetPointId() - cell_id = picker.GetCellId() - if cell_id != -1: - return data_id, cell_id - - picker.SnapToMeshPointOn() - picker.Pick(x, y, 0, renderer) - data_id = find_data_id(picker.GetActor()) - if not data_id: - return None, -1 - point_id = picker.GetPointId() - if field_type == "POINT": - return data_id, point_id - if point_id == -1: - return data_id, -1 - pipeline = self.get_vtk_pipeline(data_id) - dataset, _ = self.get_composite_block_info(pipeline, picker) - data_object = dataset or pipeline.reader.GetOutputDataObject(0) - if not isinstance(data_object, vtkDataSet): - return data_id, -1 - if hasattr(data_object, "BuildLinks"): - data_object.BuildLinks() - cell_ids = vtkIdList() - data_object.GetPointCells(point_id, cell_ids) - if cell_ids.GetNumberOfIds() > 0: - return data_id, cell_ids.GetId(0) - return data_id, -1 + # Temporarily swap to pick mappers to prevent picking hidden blocks + self.swap_pick_mappers(data_ids, use_pick_mapper=True) + try: + picker.Pick(x, y, 0, self.get_renderer()) + finally: + # Swap back to normal mappers + self.swap_pick_mappers(data_ids, use_pick_mapper=False) + + actor = picker.GetActor() + # Find which pipeline owns the picked actor + data_id = next( + ( + current_data_id + for current_data_id in data_ids + if self.get_vtk_pipeline(current_data_id).actor == actor + ), + None, + ) + id_to_select = ( + picker.GetCellId() if field_type == "CELL" else picker.GetPointId() + ) + return data_id, id_to_select def pick_actors_under_coordinate( - self, data_ids: list[str], x: float, y: float, picker: vtkHardwarePicker + self, data_ids: list[str], x: float, y: float, picker: vtkCellPicker ) -> tuple[list[vtkActor], int]: + # Swap mappers to ensure picking respects block visibility renderer = self.get_renderer() + self.swap_pick_mappers(data_ids, use_pick_mapper=True) actors = [] viewer_id = -1 try: - # direct pick - picker.SnapToMeshPointOff() picker.Pick(x, y, 0, renderer) viewer_id = picker.GetFlatBlockIndex() + # Iteratively pick overlapping actors by temporarily hiding them while actor := picker.GetActor(): actors.append(actor) actor.SetPickable(False) picker.Pick(x, y, 0, renderer) - - # finds thin geometry (lines/curves) - if not actors: - picker.SnapToMeshPointOn() - picker.Pick(x, y, 0, renderer) - if actor := picker.GetActor(): - actors.append(actor) - viewer_id = picker.GetFlatBlockIndex() finally: + # Revert pickability and swap back to normal mappers for actor in actors: actor.SetPickable(True) + self.swap_pick_mappers(data_ids, use_pick_mapper=False) return actors, viewer_id def get_composite_block_info( - self, pipeline: VtkPipeline, picker: vtkHardwarePicker - ) -> tuple[vtkDataObject | None, str | None]: + self, pipeline: VtkPipeline, picker: vtkCellPicker + ) -> tuple[vtkDataObject | None, str | None, bool]: + # Extract the specific block dataset and metadata from a picked composite flat index if not isinstance(pipeline.mapper, vtkCompositePolyDataMapper): - return None, None + return None, None, True flat_index = picker.GetFlatBlockIndex() if not (0 <= flat_index < len(pipeline.blockDataSets)): - return None, None + return None, None, True + dataset = pipeline.blockDataSets[flat_index] + if dataset: + # Extra safety check on the display visibility attribute + attr = pipeline.mapper.GetCompositeDataDisplayAttributes() + if attr and not attr.GetBlockVisibility(dataset): + return None, None, False + geode_id = ( pipeline.blockGeodeIds[flat_index] if flat_index < len(pipeline.blockGeodeIds) else None ) - return dataset, geode_id + return dataset, geode_id, True def get_array_values(self, array: Any, id_to_select: int) -> list[float] | float: components = array.GetNumberOfComponents() From 4ff52a9db9b9b8ffa4fb8b909e9219141b3e58b1 Mon Sep 17 00:00:00 2001 From: MaxNumerique <144453705+MaxNumerique@users.noreply.github.com> Date: Thu, 18 Jun 2026 08:33:42 +0000 Subject: [PATCH 08/10] Apply prepare changes --- src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py | 10 +++++----- src/opengeodeweb_viewer/vtk_protocol.py | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py b/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py index 299bddf..f394752 100644 --- a/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py +++ b/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py @@ -284,7 +284,7 @@ def pickedIds(self, rpc_params: RpcParams) -> dict[str, list[str] | int | None]: ) if not is_visible: return {"array_ids": [], "viewer_id": None} - + return { "array_ids": array_ids, "viewer_id": viewer_id, @@ -342,11 +342,11 @@ def setHighlight( rpc_params, self.viewer_schemas_dict["highlight"], self.viewer_prefix ) params = schemas.Highlight.from_dict(rpc_params) - + # 1. Clear previous highlights self.clear_highlights(params.ids) picker = vtkCellPicker(tolerance=0.005) - + # 2. Perform pick operation to identify clicked pipeline and primitive ID data_id, id_to_select = self.pick_cell_or_point( params.ids, params.x, params.y, params.field_type.value, picker @@ -354,14 +354,14 @@ def setHighlight( if not data_id or id_to_select == -1: self.render(-1) return {} - + # 3. Retrieve picked composite block information and check block visibility pipeline = self.get_vtk_pipeline(data_id) dataset, geode_id, is_visible = self.get_composite_block_info(pipeline, picker) if not is_visible: self.render(-1) return {} - + # 4. Update highlight visibility and extract attributes from the picked element self.update_highlight(pipeline, id_to_select, params.field_type.value, dataset) self.render(-1) diff --git a/src/opengeodeweb_viewer/vtk_protocol.py b/src/opengeodeweb_viewer/vtk_protocol.py index d20d85b..15b868c 100644 --- a/src/opengeodeweb_viewer/vtk_protocol.py +++ b/src/opengeodeweb_viewer/vtk_protocol.py @@ -220,7 +220,7 @@ def pick_cell_or_point( finally: # Swap back to normal mappers self.swap_pick_mappers(data_ids, use_pick_mapper=False) - + actor = picker.GetActor() # Find which pipeline owns the picked actor data_id = next( @@ -268,14 +268,14 @@ def get_composite_block_info( flat_index = picker.GetFlatBlockIndex() if not (0 <= flat_index < len(pipeline.blockDataSets)): return None, None, True - + dataset = pipeline.blockDataSets[flat_index] if dataset: # Extra safety check on the display visibility attribute attr = pipeline.mapper.GetCompositeDataDisplayAttributes() if attr and not attr.GetBlockVisibility(dataset): return None, None, False - + geode_id = ( pipeline.blockGeodeIds[flat_index] if flat_index < len(pipeline.blockGeodeIds) From e787fb53f52ac9e9aa04d8d362359154114cc0c9 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Thu, 18 Jun 2026 10:37:55 +0200 Subject: [PATCH 09/10] clear some com --- src/opengeodeweb_viewer/object/object_methods.py | 2 -- src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py | 8 ++++---- src/opengeodeweb_viewer/vtk_protocol.py | 5 ----- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/src/opengeodeweb_viewer/object/object_methods.py b/src/opengeodeweb_viewer/object/object_methods.py index bb650b8..1f2de7c 100644 --- a/src/opengeodeweb_viewer/object/object_methods.py +++ b/src/opengeodeweb_viewer/object/object_methods.py @@ -162,12 +162,10 @@ def SetBlocksVisibility( mapper = pipeline.mapper if not isinstance(mapper, vtkCompositePolyDataMapper): raise Exception("Mapper is not a vtkCompositePolyDataMapper") - # Update block visibility attributes on the main mapper blocks = pipeline.blockDataSets visibility_attributes = mapper.GetCompositeDataDisplayAttributes() for block_id in block_ids: visibility_attributes.SetBlockVisibility(blocks[block_id], visibility) - # Extract output dataset from filter or reader dataset = (pipeline.filter or pipeline.reader).GetOutputDataObject(0) if not isinstance(dataset, vtkMultiBlockDataSet): return diff --git a/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py b/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py index f394752..f92c63a 100644 --- a/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py +++ b/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py @@ -343,11 +343,11 @@ def setHighlight( ) params = schemas.Highlight.from_dict(rpc_params) - # 1. Clear previous highlights + # Clear previous highlights self.clear_highlights(params.ids) picker = vtkCellPicker(tolerance=0.005) - # 2. Perform pick operation to identify clicked pipeline and primitive ID + # Perform pick operation to identify clicked pipeline and primitive ID data_id, id_to_select = self.pick_cell_or_point( params.ids, params.x, params.y, params.field_type.value, picker ) @@ -355,14 +355,14 @@ def setHighlight( self.render(-1) return {} - # 3. Retrieve picked composite block information and check block visibility + # Retrieve picked composite block information and check block visibility pipeline = self.get_vtk_pipeline(data_id) dataset, geode_id, is_visible = self.get_composite_block_info(pipeline, picker) if not is_visible: self.render(-1) return {} - # 4. Update highlight visibility and extract attributes from the picked element + # Update highlight visibility and extract attributes from the picked element self.update_highlight(pipeline, id_to_select, params.field_type.value, dataset) self.render(-1) data_attributes = self.extract_picked_attributes( diff --git a/src/opengeodeweb_viewer/vtk_protocol.py b/src/opengeodeweb_viewer/vtk_protocol.py index 15b868c..49ef92f 100644 --- a/src/opengeodeweb_viewer/vtk_protocol.py +++ b/src/opengeodeweb_viewer/vtk_protocol.py @@ -213,12 +213,10 @@ def pick_cell_or_point( field_type: str, picker: vtkCellPicker, ) -> tuple[str | None, int]: - # Temporarily swap to pick mappers to prevent picking hidden blocks self.swap_pick_mappers(data_ids, use_pick_mapper=True) try: picker.Pick(x, y, 0, self.get_renderer()) finally: - # Swap back to normal mappers self.swap_pick_mappers(data_ids, use_pick_mapper=False) actor = picker.GetActor() @@ -239,7 +237,6 @@ def pick_cell_or_point( def pick_actors_under_coordinate( self, data_ids: list[str], x: float, y: float, picker: vtkCellPicker ) -> tuple[list[vtkActor], int]: - # Swap mappers to ensure picking respects block visibility renderer = self.get_renderer() self.swap_pick_mappers(data_ids, use_pick_mapper=True) actors = [] @@ -247,13 +244,11 @@ def pick_actors_under_coordinate( try: picker.Pick(x, y, 0, renderer) viewer_id = picker.GetFlatBlockIndex() - # Iteratively pick overlapping actors by temporarily hiding them while actor := picker.GetActor(): actors.append(actor) actor.SetPickable(False) picker.Pick(x, y, 0, renderer) finally: - # Revert pickability and swap back to normal mappers for actor in actors: actor.SetPickable(True) self.swap_pick_mappers(data_ids, use_pick_mapper=False) From 97a9426ab71c4b07f98b2350aef6d33b7b52406e Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Thu, 18 Jun 2026 10:43:08 +0200 Subject: [PATCH 10/10] cleanup --- .../rpc/viewer/viewer_protocols.py | 20 +++---------------- src/opengeodeweb_viewer/vtk_protocol.py | 16 ++++----------- 2 files changed, 7 insertions(+), 29 deletions(-) diff --git a/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py b/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py index f92c63a..07ae72d 100644 --- a/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py +++ b/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py @@ -274,17 +274,10 @@ def pickedIds(self, rpc_params: RpcParams) -> dict[str, list[str] | int | None]: ] if not array_ids: return {"array_ids": [], "viewer_id": None} - # Map flat_index to None if it is -1 (no composite block picked) viewer_id = flat_index if flat_index != -1 else None if viewer_id is not None: pipeline = self.get_vtk_pipeline(array_ids[0]) - # Verify composite block visibility (discard pick if block is hidden) - dataset, geode_id, is_visible = self.get_composite_block_info( - pipeline, picker - ) - if not is_visible: - return {"array_ids": [], "viewer_id": None} - + dataset, geode_id = self.get_composite_block_info(pipeline, picker) return { "array_ids": array_ids, "viewer_id": viewer_id, @@ -342,11 +335,9 @@ def setHighlight( rpc_params, self.viewer_schemas_dict["highlight"], self.viewer_prefix ) params = schemas.Highlight.from_dict(rpc_params) - # Clear previous highlights self.clear_highlights(params.ids) picker = vtkCellPicker(tolerance=0.005) - # Perform pick operation to identify clicked pipeline and primitive ID data_id, id_to_select = self.pick_cell_or_point( params.ids, params.x, params.y, params.field_type.value, picker @@ -354,14 +345,9 @@ def setHighlight( if not data_id or id_to_select == -1: self.render(-1) return {} - - # Retrieve picked composite block information and check block visibility + # Retrieve picked composite block information pipeline = self.get_vtk_pipeline(data_id) - dataset, geode_id, is_visible = self.get_composite_block_info(pipeline, picker) - if not is_visible: - self.render(-1) - return {} - + dataset, geode_id = self.get_composite_block_info(pipeline, picker) # Update highlight visibility and extract attributes from the picked element self.update_highlight(pipeline, id_to_select, params.field_type.value, dataset) self.render(-1) diff --git a/src/opengeodeweb_viewer/vtk_protocol.py b/src/opengeodeweb_viewer/vtk_protocol.py index 49ef92f..09a6926 100644 --- a/src/opengeodeweb_viewer/vtk_protocol.py +++ b/src/opengeodeweb_viewer/vtk_protocol.py @@ -218,7 +218,6 @@ def pick_cell_or_point( picker.Pick(x, y, 0, self.get_renderer()) finally: self.swap_pick_mappers(data_ids, use_pick_mapper=False) - actor = picker.GetActor() # Find which pipeline owns the picked actor data_id = next( @@ -256,27 +255,20 @@ def pick_actors_under_coordinate( def get_composite_block_info( self, pipeline: VtkPipeline, picker: vtkCellPicker - ) -> tuple[vtkDataObject | None, str | None, bool]: + ) -> tuple[vtkDataObject | None, str | None]: # Extract the specific block dataset and metadata from a picked composite flat index if not isinstance(pipeline.mapper, vtkCompositePolyDataMapper): - return None, None, True + return None, None flat_index = picker.GetFlatBlockIndex() if not (0 <= flat_index < len(pipeline.blockDataSets)): - return None, None, True - + return None, None dataset = pipeline.blockDataSets[flat_index] - if dataset: - # Extra safety check on the display visibility attribute - attr = pipeline.mapper.GetCompositeDataDisplayAttributes() - if attr and not attr.GetBlockVisibility(dataset): - return None, None, False - geode_id = ( pipeline.blockGeodeIds[flat_index] if flat_index < len(pipeline.blockGeodeIds) else None ) - return dataset, geode_id, True + return dataset, geode_id def get_array_values(self, array: Any, id_to_select: int) -> list[float] | float: components = array.GetNumberOfComponents()