Skip to content

Commit 13a0f14

Browse files
authored
Merge pull request #159 from Geode-solutions/fix/highlight-refacto
fix(highlight): refacto logic
2 parents 87bb9fa + 97a9426 commit 13a0f14

4 files changed

Lines changed: 137 additions & 123 deletions

File tree

requirements.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,3 @@ wslink==1.12.4
6363
yarl>=1
6464
# via aiohttp
6565

66-
opengeodeweb-microservice==1.*,>=1.1.3

src/opengeodeweb_viewer/object/object_methods.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ def _prune_hidden_blocks(
138138
dataset: vtkMultiBlockDataSet,
139139
visibility_attributes: vtkCompositeDataDisplayAttributes,
140140
) -> vtkMultiBlockDataSet:
141+
# Recursively construct a new multi-block dataset excluding hidden blocks.
141142
pruned = vtkMultiBlockDataSet()
142143
pruned.SetNumberOfBlocks(dataset.GetNumberOfBlocks())
143144
for index in range(dataset.GetNumberOfBlocks()):
@@ -165,13 +166,10 @@ def SetBlocksVisibility(
165166
visibility_attributes = mapper.GetCompositeDataDisplayAttributes()
166167
for block_id in block_ids:
167168
visibility_attributes.SetBlockVisibility(blocks[block_id], visibility)
168-
dataset = (
169-
pipeline.filter.GetOutputDataObject(0)
170-
if pipeline.filter
171-
else pipeline.reader.GetOutputDataObject(0)
172-
)
169+
dataset = (pipeline.filter or pipeline.reader).GetOutputDataObject(0)
173170
if not isinstance(dataset, vtkMultiBlockDataSet):
174171
return
172+
# Re-build a pruned dataset for the dedicated pick mapper
175173
if pipeline.pick_mapper is None:
176174
pipeline.pick_mapper = vtkCompositePolyDataMapper()
177175
pipeline.pick_mapper.SetInputDataObject(

src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py

Lines changed: 26 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
vtkWorldPointPicker,
1616
vtkCellPicker,
1717
vtkPropPicker,
18-
vtkCompositePolyDataMapper,
1918
vtkDataSetMapper,
2019
vtkActor,
2120
)
@@ -262,52 +261,26 @@ def pickedIds(self, rpc_params: RpcParams) -> dict[str, list[str] | int | None]:
262261
rpc_params, self.viewer_schemas_dict["picked_ids"], self.viewer_prefix
263262
)
264263
params = schemas.PickedIDS.from_dict(rpc_params)
265-
renderer = self.getView("-1").GetRenderers().GetFirstRenderer()
266-
267-
pipelines_to_restore = [
268-
pipeline
269-
for pipeline_id in params.ids
270-
if (pipeline := self.get_vtk_pipeline(pipeline_id)).pick_mapper is not None
271-
]
272-
for pipeline in pipelines_to_restore:
273-
pick_mapper = pipeline.pick_mapper
274-
if pick_mapper is not None:
275-
pipeline.actor.SetMapper(pick_mapper)
276-
277-
actors = []
278264
picker = vtkCellPicker(tolerance=0.005)
279-
picker.Pick(params.x, params.y, 0, renderer)
280-
actor = picker.GetActor()
281-
viewer_id = picker.GetFlatBlockIndex()
282-
283-
while actor:
284-
actors.append(actor)
285-
actor.SetPickable(False)
286-
picker.Pick(params.x, params.y, 0, renderer)
287-
actor = picker.GetActor()
288-
289-
for actor in actors:
290-
actor.SetPickable(True)
291-
for pipeline in pipelines_to_restore:
292-
pipeline.actor.SetMapper(pipeline.mapper)
293-
265+
# Retrieve all actors under the clicked coordinates
266+
actors, flat_index = self.pick_actors_under_coordinate(
267+
params.ids, params.x, params.y, picker
268+
)
269+
# Filter pipeline IDs whose actors are in the picked list
294270
array_ids = [
295-
id for id in params.ids if self.get_vtk_pipeline(id).actor in actors
271+
data_id
272+
for data_id in params.ids
273+
if self.get_vtk_pipeline(data_id).actor in actors
296274
]
297275
if not array_ids:
298276
return {"array_ids": [], "viewer_id": None}
299-
if array_ids and viewer_id != -1:
277+
viewer_id = flat_index if flat_index != -1 else None
278+
if viewer_id is not None:
300279
pipeline = self.get_vtk_pipeline(array_ids[0])
301-
mapper = pipeline.mapper
302-
if isinstance(mapper, vtkCompositePolyDataMapper):
303-
attr = mapper.GetCompositeDataDisplayAttributes()
304-
if attr and not attr.GetBlockVisibility(
305-
pipeline.blockDataSets[viewer_id]
306-
):
307-
array_ids, viewer_id = [], -1
280+
dataset, geode_id = self.get_composite_block_info(pipeline, picker)
308281
return {
309282
"array_ids": array_ids,
310-
"viewer_id": viewer_id if viewer_id != -1 else None,
283+
"viewer_id": viewer_id,
311284
}
312285

313286
@exportRpc(viewer_prefix + viewer_schemas_dict["grid_scale"]["rpc"])
@@ -362,89 +335,27 @@ def setHighlight(
362335
rpc_params, self.viewer_schemas_dict["highlight"], self.viewer_prefix
363336
)
364337
params = schemas.Highlight.from_dict(rpc_params)
365-
picker = vtkCellPicker(tolerance=0.005)
366-
367-
pipelines_to_restore = [
368-
pipeline
369-
for pipeline_id in params.ids
370-
if (pipeline := self.get_vtk_pipeline(pipeline_id)).pick_mapper is not None
371-
]
372-
for pipeline in pipelines_to_restore:
373-
pick_mapper = pipeline.pick_mapper
374-
if pick_mapper is not None:
375-
pipeline.actor.SetMapper(pick_mapper)
376-
try:
377-
picker.Pick(params.x, params.y, 0, self.get_renderer())
378-
finally:
379-
for pipeline in pipelines_to_restore:
380-
pipeline.actor.SetMapper(pipeline.mapper)
381-
338+
# Clear previous highlights
382339
self.clear_highlights(params.ids)
383-
actor = picker.GetActor()
384-
pipeline_id = next(
385-
(id for id in params.ids if self.get_vtk_pipeline(id).actor == actor), None
386-
)
387-
id_to_select = (
388-
picker.GetCellId()
389-
if params.field_type == schemas.FieldType.CELL
390-
else picker.GetPointId()
340+
picker = vtkCellPicker(tolerance=0.005)
341+
# Perform pick operation to identify clicked pipeline and primitive ID
342+
data_id, id_to_select = self.pick_cell_or_point(
343+
params.ids, params.x, params.y, params.field_type.value, picker
391344
)
392-
393-
if not pipeline_id or id_to_select == -1:
345+
if not data_id or id_to_select == -1:
394346
self.render(-1)
395347
return {}
396-
397-
pipeline = self.get_vtk_pipeline(pipeline_id)
398-
dataset = None
399-
geode_id = None
400-
if isinstance(pipeline.mapper, vtkCompositePolyDataMapper):
401-
flat_index = picker.GetFlatBlockIndex()
402-
dataset = (
403-
pipeline.blockDataSets[flat_index]
404-
if 0 <= flat_index < len(pipeline.blockDataSets)
405-
else None
406-
)
407-
if dataset:
408-
attr = pipeline.mapper.GetCompositeDataDisplayAttributes()
409-
if attr and not attr.GetBlockVisibility(dataset):
410-
self.render(-1)
411-
return {}
412-
geode_id = (
413-
pipeline.blockGeodeIds[flat_index]
414-
if 0 <= flat_index < len(pipeline.blockGeodeIds)
415-
else None
416-
)
417-
348+
# Retrieve picked composite block information
349+
pipeline = self.get_vtk_pipeline(data_id)
350+
dataset, geode_id = self.get_composite_block_info(pipeline, picker)
351+
# Update highlight visibility and extract attributes from the picked element
418352
self.update_highlight(pipeline, id_to_select, params.field_type.value, dataset)
419353
self.render(-1)
420-
421-
data_obj = dataset or pipeline.reader.GetOutputDataObject(0)
422-
data_attributes = {}
423-
if isinstance(data_obj, vtkDataSet):
424-
field_data = (
425-
data_obj.GetCellData()
426-
if params.field_type == schemas.FieldType.CELL
427-
else data_obj.GetPointData()
428-
)
429-
for array_index in range(field_data.GetNumberOfArrays()):
430-
array = field_data.GetArray(array_index)
431-
if array and array.GetName():
432-
num_comps = array.GetNumberOfComponents()
433-
component_values = [
434-
array.GetComponent(id_to_select, component_index)
435-
for component_index in range(num_comps)
436-
]
437-
data_attributes[array.GetName()] = (
438-
component_values[0] if num_comps == 1 else component_values
439-
)
440-
441-
if params.field_type == schemas.FieldType.POINT:
442-
point_coordinates = data_obj.GetPoint(id_to_select)
443-
if point_coordinates:
444-
data_attributes["coordinates"] = list(point_coordinates)
445-
354+
data_attributes = self.extract_picked_attributes(
355+
pipeline, id_to_select, params.field_type.value, dataset
356+
)
446357
return {
447-
"id": pipeline_id,
358+
"id": data_id,
448359
"picked_id": id_to_select,
449360
"field_type": params.field_type.value,
450361
"geode_id": geode_id,

src/opengeodeweb_viewer/vtk_protocol.py

Lines changed: 108 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
vtkRenderer,
1919
vtkRenderWindow,
2020
vtkDataSetMapper,
21+
vtkCompositePolyDataMapper,
22+
vtkCellPicker,
23+
vtkHardwarePicker,
2124
)
2225
from vtkmodules.vtkCommonDataModel import (
2326
vtkDataObject,
@@ -189,11 +192,114 @@ def update_highlight(
189192
pipeline.highlight.extractSelection.Update()
190193
pipeline.highlight.actor.VisibilityOn()
191194

192-
def clear_highlights(self, ids: list[str]) -> None:
193-
for data_id in ids:
195+
def clear_highlights(self, data_ids: list[str]) -> None:
196+
for data_id in data_ids:
194197
pipeline = self.get_vtk_pipeline(data_id)
195198
pipeline.highlight.actor.VisibilityOff()
196199

200+
def swap_pick_mappers(self, data_ids: list[str], use_pick_mapper: bool) -> None:
201+
# Swap actor mappers between the default and the pick_mapper (where hidden blocks are pruned).
202+
for data_id in data_ids:
203+
pipeline = self.get_vtk_pipeline(data_id)
204+
if pipeline.pick_mapper:
205+
mapper = pipeline.pick_mapper if use_pick_mapper else pipeline.mapper
206+
pipeline.actor.SetMapper(mapper)
207+
208+
def pick_cell_or_point(
209+
self,
210+
data_ids: list[str],
211+
x: float,
212+
y: float,
213+
field_type: str,
214+
picker: vtkCellPicker,
215+
) -> tuple[str | None, int]:
216+
self.swap_pick_mappers(data_ids, use_pick_mapper=True)
217+
try:
218+
picker.Pick(x, y, 0, self.get_renderer())
219+
finally:
220+
self.swap_pick_mappers(data_ids, use_pick_mapper=False)
221+
actor = picker.GetActor()
222+
# Find which pipeline owns the picked actor
223+
data_id = next(
224+
(
225+
current_data_id
226+
for current_data_id in data_ids
227+
if self.get_vtk_pipeline(current_data_id).actor == actor
228+
),
229+
None,
230+
)
231+
id_to_select = (
232+
picker.GetCellId() if field_type == "CELL" else picker.GetPointId()
233+
)
234+
return data_id, id_to_select
235+
236+
def pick_actors_under_coordinate(
237+
self, data_ids: list[str], x: float, y: float, picker: vtkCellPicker
238+
) -> tuple[list[vtkActor], int]:
239+
renderer = self.get_renderer()
240+
self.swap_pick_mappers(data_ids, use_pick_mapper=True)
241+
actors = []
242+
viewer_id = -1
243+
try:
244+
picker.Pick(x, y, 0, renderer)
245+
viewer_id = picker.GetFlatBlockIndex()
246+
while actor := picker.GetActor():
247+
actors.append(actor)
248+
actor.SetPickable(False)
249+
picker.Pick(x, y, 0, renderer)
250+
finally:
251+
for actor in actors:
252+
actor.SetPickable(True)
253+
self.swap_pick_mappers(data_ids, use_pick_mapper=False)
254+
return actors, viewer_id
255+
256+
def get_composite_block_info(
257+
self, pipeline: VtkPipeline, picker: vtkCellPicker
258+
) -> tuple[vtkDataObject | None, str | None]:
259+
# Extract the specific block dataset and metadata from a picked composite flat index
260+
if not isinstance(pipeline.mapper, vtkCompositePolyDataMapper):
261+
return None, None
262+
flat_index = picker.GetFlatBlockIndex()
263+
if not (0 <= flat_index < len(pipeline.blockDataSets)):
264+
return None, None
265+
dataset = pipeline.blockDataSets[flat_index]
266+
geode_id = (
267+
pipeline.blockGeodeIds[flat_index]
268+
if flat_index < len(pipeline.blockGeodeIds)
269+
else None
270+
)
271+
return dataset, geode_id
272+
273+
def get_array_values(self, array: Any, id_to_select: int) -> list[float] | float:
274+
components = array.GetNumberOfComponents()
275+
if components == 1:
276+
return float(array.GetComponent(id_to_select, 0))
277+
return [float(array.GetComponent(id_to_select, i)) for i in range(components)]
278+
279+
def extract_picked_attributes(
280+
self,
281+
pipeline: VtkPipeline,
282+
id_to_select: int,
283+
field_type: str,
284+
dataset: vtkDataObject | None,
285+
) -> dict[str, list[float] | float]:
286+
data_object = dataset or pipeline.reader.GetOutputDataObject(0)
287+
if not isinstance(data_object, vtkDataSet):
288+
return {}
289+
field_data = (
290+
data_object.GetCellData()
291+
if field_type == "CELL"
292+
else data_object.GetPointData()
293+
)
294+
attributes = {}
295+
for i in range(field_data.GetNumberOfArrays()):
296+
array = field_data.GetArray(i)
297+
if array and array.GetName():
298+
attributes[array.GetName()] = self.get_array_values(array, id_to_select)
299+
if field_type == "POINT" and (coords := data_object.GetPoint(id_to_select)):
300+
attributes["coordinates"] = list(coords)
301+
return attributes
302+
197303
def update_grid_scale_and_clipping_range(self) -> None:
198304
grid_scale = self.get_grid_scale()
199305
if grid_scale is not None:

0 commit comments

Comments
 (0)