diff --git a/opengeodeweb_viewer_schemas.json b/opengeodeweb_viewer_schemas.json index 47126516..e29f0e60 100644 --- a/opengeodeweb_viewer_schemas.json +++ b/opengeodeweb_viewer_schemas.json @@ -1927,6 +1927,39 @@ ], "additionalProperties": false }, + "hover_highlight": { + "$id": "opengeodeweb_viewer.viewer.hover_highlight", + "rpc": "hover_highlight", + "type": "object", + "properties": { + "x": { + "type": "number" + }, + "y": { + "type": "number" + }, + "field_type": { + "enum": [ + "CELL", + "POINT" + ] + }, + "ids": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + } + }, + "required": [ + "x", + "y", + "field_type", + "ids" + ], + "additionalProperties": false + }, "grid_scale": { "$id": "opengeodeweb_viewer.viewer.grid_scale", "rpc": "grid_scale", diff --git a/requirements.txt b/requirements.txt index 74d61ff7..7819587c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -61,4 +61,3 @@ wslink==1.12.4 yarl>=1 # via aiohttp -opengeodeweb-microservice==1.*,>=1.1.3 diff --git a/src/opengeodeweb_viewer/object/object_methods.py b/src/opengeodeweb_viewer/object/object_methods.py index 6e6899b3..8d253aa9 100644 --- a/src/opengeodeweb_viewer/object/object_methods.py +++ b/src/opengeodeweb_viewer/object/object_methods.py @@ -4,6 +4,7 @@ # Third party imports from vtkmodules.vtkIOXML import vtkXMLDataReader, vtkXMLImageDataReader from vtkmodules.vtkCommonExecutionModel import vtkAlgorithm +from vtkmodules.vtkCommonCore import vtkIdTypeArray from vtkmodules.vtkRenderingCore import ( vtkMapper, vtkActor, @@ -15,6 +16,7 @@ from vtkmodules.vtkCommonDataModel import ( vtkDataObject, vtkDataSet, + vtkSelectionNode, ) # Local application imports @@ -48,6 +50,7 @@ def registerObject( resetCamara = False renderer.AddActor(data.actor) renderer.AddActor(data.highlightActor) + renderer.AddActor(data.hoverHighlightActor) if resetCamara: renderer.ResetCamera() @@ -57,6 +60,7 @@ def deregisterObject(self, data_id: str) -> None: renderer = renderWindow.GetRenderers().GetFirstRenderer() renderer.RemoveActor(pipeline.actor) renderer.RemoveActor(pipeline.highlightActor) + renderer.RemoveActor(pipeline.hoverHighlightActor) self.deregister_object(data_id) def SetVisibility(self, data_id: str, visibility: bool) -> None: @@ -178,3 +182,28 @@ def highlight( prop.SetEdgeColor(0.12, 0.35, 0.30) actor.SetMapper(mapper) actor.VisibilityOff() + + def setupHoverHighlight(self, pipeline: VtkPipeline) -> None: + actor = pipeline.hoverHighlightActor + mapper = pipeline.hoverHighlightMapper + mapper.ScalarVisibilityOff() + mapper.SetResolveCoincidentTopologyToPolygonOffset() + prop = actor.GetProperty() + prop.SetColor(1, 0.5, 0) + prop.SetLineWidth(5) + prop.SetPointSize(16) + prop.SetRenderPointsAsSpheres(True) + prop.SetLighting(False) + prop.SetEdgeVisibility(True) + prop.SetEdgeColor(1, 0.5, 0) + actor.SetMapper(mapper) + actor.VisibilityOff() + input_port = ( + pipeline.filter.GetOutputPort() + if pipeline.filter + else pipeline.reader.GetOutputPort() + ) + pipeline.selection.AddNode(pipeline.selectionNode) + pipeline.extractSelection.SetInputConnection(0, input_port) + pipeline.extractSelection.SetInputData(1, pipeline.selection) + mapper.SetInputConnection(pipeline.extractSelection.GetOutputPort()) diff --git a/src/opengeodeweb_viewer/rpc/mesh/mesh_protocols.py b/src/opengeodeweb_viewer/rpc/mesh/mesh_protocols.py index ce7e3a67..c754ca65 100644 --- a/src/opengeodeweb_viewer/rpc/mesh/mesh_protocols.py +++ b/src/opengeodeweb_viewer/rpc/mesh/mesh_protocols.py @@ -55,6 +55,7 @@ def registerMesh(self, rpc_params: RpcParams) -> None: self.highlight( data.highlightActor, data.highlightMapper, reader.GetOutputDataObject(0) ) + self.setupHoverHighlight(data) self.registerObject(data_id, file_name, data) except Exception as e: print(f"Error registering mesh {data_id}: {str(e)}", flush=True) diff --git a/src/opengeodeweb_viewer/rpc/model/model_protocols.py b/src/opengeodeweb_viewer/rpc/model/model_protocols.py index 29e41c8f..508860e1 100644 --- a/src/opengeodeweb_viewer/rpc/model/model_protocols.py +++ b/src/opengeodeweb_viewer/rpc/model/model_protocols.py @@ -126,6 +126,7 @@ def registerModel(self, rpc_params: RpcParams) -> None: ) data = VtkPipeline(reader, highlight_mapper, mapper, filter) self.highlight(data.highlightActor, data.highlightMapper, geometry_output) + self.setupHoverHighlight(data) iterator = geometry_output.NewTreeIterator() iterator.InitTraversal() while not iterator.IsDoneWithTraversal(): diff --git a/src/opengeodeweb_viewer/rpc/viewer/schemas/__init__.py b/src/opengeodeweb_viewer/rpc/viewer/schemas/__init__.py index aa88dcf8..dc64d6ce 100644 --- a/src/opengeodeweb_viewer/rpc/viewer/schemas/__init__.py +++ b/src/opengeodeweb_viewer/rpc/viewer/schemas/__init__.py @@ -7,6 +7,7 @@ from .reset_camera import * from .render import * from .picked_ids import * +from .hover_highlight import * from .grid_scale import * from .get_point_position import * from .axes import * diff --git a/src/opengeodeweb_viewer/rpc/viewer/schemas/hover_highlight.json b/src/opengeodeweb_viewer/rpc/viewer/schemas/hover_highlight.json new file mode 100644 index 00000000..365cf0fd --- /dev/null +++ b/src/opengeodeweb_viewer/rpc/viewer/schemas/hover_highlight.json @@ -0,0 +1,24 @@ +{ + "rpc": "hover_highlight", + "type": "object", + "properties": { + "x": { + "type": "number" + }, + "y": { + "type": "number" + }, + "field_type": { + "enum": ["CELL", "POINT"] + }, + "ids": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + } + }, + "required": ["x", "y", "field_type", "ids"], + "additionalProperties": false +} diff --git a/src/opengeodeweb_viewer/rpc/viewer/schemas/hover_highlight.py b/src/opengeodeweb_viewer/rpc/viewer/schemas/hover_highlight.py new file mode 100644 index 00000000..f256a026 --- /dev/null +++ b/src/opengeodeweb_viewer/rpc/viewer/schemas/hover_highlight.py @@ -0,0 +1,20 @@ +from dataclasses_json import DataClassJsonMixin +from enum import Enum +from dataclasses import dataclass +from typing import List + + +class FieldType(Enum): + CELL = "CELL" + POINT = "POINT" + + +@dataclass +class HoverHighlight(DataClassJsonMixin): + def __post_init__(self) -> None: + print(self, flush=True) + + field_type: FieldType + ids: List[str] + x: float + y: float diff --git a/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py b/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py index c2153d96..3f405f01 100644 --- a/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py +++ b/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py @@ -19,8 +19,8 @@ vtkCompositePolyDataMapper, ) from vtkmodules.vtkInteractionStyle import vtkInteractorStyleTrackball -from vtkmodules.vtkCommonCore import reference -from vtkmodules.vtkCommonDataModel import vtkBoundingBox, vtkDataSet +from vtkmodules.vtkCommonCore import reference, vtkIdTypeArray +from vtkmodules.vtkCommonDataModel import vtkBoundingBox, vtkDataSet, vtkSelectionNode from vtkmodules.vtkCommonTransforms import vtkTransform from vtkmodules.vtkInteractionWidgets import vtkOrientationMarkerWidget from opengeodeweb_microservice.schemas import get_schemas_dict @@ -303,6 +303,36 @@ def renderNow(self, rpc_params: RpcParams) -> None: ) self.render() + @exportRpc(viewer_prefix + viewer_schemas_dict["hover_highlight"]["rpc"]) + def setHoverHighlight(self, rpc_params: RpcParams) -> None: + validate_schema( + rpc_params, self.viewer_schemas_dict["hover_highlight"], self.viewer_prefix + ) + params = schemas.HoverHighlight.from_dict(rpc_params) + renderer = self.get_renderer() + picker = vtkCellPicker(tolerance=0.005) + picker.Pick(params.x, params.y, 0, renderer) + actor = picker.GetActor() + self.clearHoverHighlights(params.ids) + if actor: + for data_id in params.ids: + pipeline = self.get_vtk_pipeline(data_id) + if pipeline.actor == actor: + cell_id = picker.GetCellId() + point_id = picker.GetPointId() + + id_to_select = ( + cell_id + if params.field_type == schemas.FieldType.CELL + else point_id + ) + if id_to_select != -1: + self.updateHoverHighlight( + pipeline, id_to_select, params.field_type.value + ) + break + self.render(-1) + @exportRpc(viewer_prefix + viewer_schemas_dict["set_z_scaling"]["rpc"]) def setZScaling(self, rpc_params: RpcParams) -> None: validate_schema( diff --git a/src/opengeodeweb_viewer/vtk_protocol.py b/src/opengeodeweb_viewer/vtk_protocol.py index 603fb5ea..20ec126e 100644 --- a/src/opengeodeweb_viewer/vtk_protocol.py +++ b/src/opengeodeweb_viewer/vtk_protocol.py @@ -18,9 +18,16 @@ vtkRenderer, vtkRenderWindow, vtkCompositePolyDataMapper, + vtkDataSetMapper, ) -from vtkmodules.vtkCommonDataModel import vtkDataObject, vtkBoundingBox -from vtkmodules.vtkCommonCore import vtkStringArray +from vtkmodules.vtkCommonDataModel import ( + vtkDataObject, + vtkBoundingBox, + vtkSelection, + vtkSelectionNode, +) +from vtkmodules.vtkFiltersExtraction import vtkExtractSelection +from vtkmodules.vtkCommonCore import vtkStringArray, vtkIdTypeArray from vtkmodules.vtkRenderingAnnotation import vtkCubeAxesActor, vtkAxesActor from vtkmodules.vtkInteractionWidgets import vtkOrientationMarkerWidget @@ -46,6 +53,11 @@ class VtkPipeline: filter: vtkAlgorithm | None = None actor: vtkActor = field(default_factory=vtkActor) highlightActor: vtkActor = field(default_factory=vtkActor) + hoverHighlightActor: vtkActor = field(default_factory=vtkActor) + hoverHighlightMapper: vtkDataSetMapper = field(default_factory=vtkDataSetMapper) + selectionNode: vtkSelectionNode = field(default_factory=vtkSelectionNode) + selection: vtkSelection = field(default_factory=vtkSelection) + extractSelection: vtkExtractSelection = field(default_factory=vtkExtractSelection) blockDataSets: list[vtkDataObject | None] = field(default_factory=list) blockGeodeIds: list[str] = field(default_factory=list) activeHighlightIds: list[int] = field(default_factory=list) @@ -138,6 +150,28 @@ def reset_camera_clipping_range(self) -> None: else: renderer.ResetCameraClippingRange() + def updateHoverHighlight( + self, pipeline: VtkPipeline, id_to_select: int, field_type: str + ) -> None: + node = pipeline.selectionNode + node.SetContentType(vtkSelectionNode.INDICES) + node.SetFieldType( + vtkSelectionNode.CELL if field_type == "CELL" else vtkSelectionNode.POINT + ) + selection_list = vtkIdTypeArray() + selection_list.SetNumberOfComponents(1) + selection_list.InsertNextValue(id_to_select) + node.SetSelectionList(selection_list) + + pipeline.extractSelection.Modified() + pipeline.extractSelection.Update() + pipeline.hoverHighlightActor.VisibilityOn() + + def clearHoverHighlights(self, ids: list[str]) -> None: + for data_id in ids: + pipeline = self.get_vtk_pipeline(data_id) + pipeline.hoverHighlightActor.VisibilityOff() + def update_grid_scale_and_clipping_range(self) -> None: grid_scale = self.get_grid_scale() if grid_scale is not None: