From c35f8b0025630acd92a3cb324312ea3ed2efd3b3 Mon Sep 17 00:00:00 2001 From: SpliiT Date: Tue, 26 Nov 2024 10:11:41 +0100 Subject: [PATCH 1/8] test + schemas --- .../rpc/viewer/schemas/get_mouse.json | 12 ++++ .../rpc/viewer/viewer_protocols.py | 14 +++- src/tests/test_viewer_protocols.py | 71 +++++++++++++++---- 3 files changed, 82 insertions(+), 15 deletions(-) create mode 100644 src/opengeodeweb_viewer/rpc/viewer/schemas/get_mouse.json diff --git a/src/opengeodeweb_viewer/rpc/viewer/schemas/get_mouse.json b/src/opengeodeweb_viewer/rpc/viewer/schemas/get_mouse.json new file mode 100644 index 0000000..d7321a0 --- /dev/null +++ b/src/opengeodeweb_viewer/rpc/viewer/schemas/get_mouse.json @@ -0,0 +1,12 @@ +{ + "rpc": "get_mouse", + "type": "object", + "properties": { + "x": { + "type": "integer" + }, + "y": { + "type": "integer" + } + } +} \ No newline at end of file diff --git a/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py b/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py index bf78d16..da4632a 100644 --- a/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py +++ b/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py @@ -7,7 +7,7 @@ import vtk from vtk.web import protocols as vtk_protocols from vtkmodules.vtkIOImage import vtkPNGWriter, vtkJPEGWriter -from vtkmodules.vtkRenderingCore import (vtkWindowToImageFilter) +from vtkmodules.vtkRenderingCore import vtkWindowToImageFilter from wslink import register as exportRpc # Local application imports @@ -18,6 +18,7 @@ schemas_dict = get_schemas_dict(schemas_dir) prefix = "opengeodeweb_viewer.viewer." + class VtkViewerView(VtkView): def __init__(self): super().__init__() @@ -93,7 +94,7 @@ def takeScreenshot(self, params): else: raise Exception("output_extension not supported") - new_filename = filename + '.' + output_extension + new_filename = filename + "." + output_extension file_path = os.path.join(self.DATA_FOLDER_PATH, new_filename) writer.SetFileName(file_path) writer.SetInputConnection(w2if.GetOutputPort()) @@ -104,7 +105,6 @@ def takeScreenshot(self, params): return {"blob": self.addAttachment(file_content)} - @exportRpc(prefix + schemas_dict["update_data"]["rpc"]) def updateData(self, params): print(schemas_dict["update_data"]["rpc"], params, flush=True) @@ -145,3 +145,11 @@ def reset(self, params): validate_schema(params, schemas_dict["reset"]) renderWindow = self.getView("-1") renderWindow.GetRenderers().GetFirstRenderer().RemoveAllViewProps() + + @exportRpc(prefix + schemas_dict["get_mouse"]["rpc"]) + def getMouse(self, params): + print(schemas_dict["get_mouse"]["rpc"], params, flush=True) + validate_schema(params, schemas_dict["get_mouse"]) + x = params["x"] + y = params["y"] + return {"x": x, "y": y} diff --git a/src/tests/test_viewer_protocols.py b/src/tests/test_viewer_protocols.py index ec4dfdb..d86f55d 100644 --- a/src/tests/test_viewer_protocols.py +++ b/src/tests/test_viewer_protocols.py @@ -9,23 +9,31 @@ class_ = VtkViewerView() + def test_create_visualization(server): server.call(class_.prefix + class_.schemas_dict["create_visualization"]["rpc"]) assert server.compare_image(3, "viewer/create_visualization.jpeg") == True + def test_reset_camera(server): server.call(class_.prefix + class_.schemas_dict["reset_camera"]["rpc"]) assert server.compare_image(3, "viewer/reset_camera.jpeg") == True + def test_set_viewer_background_color(server): - server.call(class_.prefix + class_.schemas_dict["set_background_color"]["rpc"], [{"red": 0, "green": 0, "blue": 255}]) + server.call( + class_.prefix + class_.schemas_dict["set_background_color"]["rpc"], + [{"red": 0, "green": 0, "blue": 255}], + ) assert server.compare_image(3, "viewer/set_background_color.jpeg") == True -def test_get_point_position(server): +def test_get_point_position(server): test_register_mesh(server) - - server.call(class_.prefix + class_.schemas_dict["get_point_position"]["rpc"], [{"x": 0, "y": 0}]) + server.call( + class_.prefix + class_.schemas_dict["get_point_position"]["rpc"], + [{"x": 0, "y": 0}], + ) response = server.get_response() assert "x" in response["result"] assert "y" in response["result"] @@ -45,7 +53,13 @@ def test_take_screenshot(server): # Take a screenshot with background jpg server.call( class_.prefix + class_.schemas_dict["take_screenshot"]["rpc"], - [{"filename": "take_screenshot_with_background", "output_extension": "jpg", "include_background": True}], + [ + { + "filename": "take_screenshot_with_background", + "output_extension": "jpg", + "include_background": True, + } + ], ) response = server.get_response() @@ -56,16 +70,24 @@ def test_take_screenshot(server): f.write(blob) f.close() first_image_path = os.path.join(server.test_output_dir, "test.jpg") - second_image_path = os.path.join(server.images_dir_path, "viewer/take_screenshot_with_background.jpg") + second_image_path = os.path.join( + server.images_dir_path, "viewer/take_screenshot_with_background.jpg" + ) assert server.images_diff(first_image_path, second_image_path) == 0.0 # Take a screenshot without background png server.call( class_.prefix + class_.schemas_dict["take_screenshot"]["rpc"], - [{"filename": "take_screenshot_without_background", "output_extension": "png", "include_background": True}], + [ + { + "filename": "take_screenshot_without_background", + "output_extension": "png", + "include_background": True, + } + ], ) - + response = server.get_response() response = server.get_response() blob = server.get_response() @@ -76,16 +98,24 @@ def test_take_screenshot(server): f.write(blob) f.close() first_image_path = os.path.join(server.test_output_dir, "test.png") - second_image_path = os.path.join(server.images_dir_path, "viewer/take_screenshot_without_background.png") + second_image_path = os.path.join( + server.images_dir_path, "viewer/take_screenshot_without_background.png" + ) assert server.images_diff(first_image_path, second_image_path) == 0.0 # Take a screenshot with background png server.call( class_.prefix + class_.schemas_dict["take_screenshot"]["rpc"], - [{"filename": "take_screenshot_with_background", "output_extension": "png", "include_background": True}], + [ + { + "filename": "take_screenshot_with_background", + "output_extension": "png", + "include_background": True, + } + ], ) - + response = server.get_response() response = server.get_response() blob = server.get_response() @@ -96,6 +126,23 @@ def test_take_screenshot(server): f.write(blob) f.close() first_image_path = os.path.join(server.test_output_dir, "test.png") - second_image_path = os.path.join(server.images_dir_path, "viewer/take_screenshot_with_background.png") + second_image_path = os.path.join( + server.images_dir_path, "viewer/take_screenshot_with_background.png" + ) assert server.images_diff(first_image_path, second_image_path) == 0.0 + + +def test_get_mouse(server): + server.call( + class_.prefix + class_.schemas_dict["get_mouse"]["rpc"], [{"x": 100, "y": 200}] + ) + response = server.get_response() + assert "x" in response["result"] + assert "y" in response["result"] + x = response["result"]["x"] + y = response["result"]["y"] + assert type(x) is int + assert type(y) is int + assert x == 100 + assert y == 200 From 94280f3bc30f03e0c550cd7baf76afd30192c1e3 Mon Sep 17 00:00:00 2001 From: SpliiT Date: Tue, 26 Nov 2024 10:25:48 +0100 Subject: [PATCH 2/8] feat(get_mouse): new rpc BREAKING CHANGE: RPC separtation mesh/model/viewer (previous pr) --- src/opengeodeweb_viewer/rpc/viewer/schemas/get_mouse.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/opengeodeweb_viewer/rpc/viewer/schemas/get_mouse.json b/src/opengeodeweb_viewer/rpc/viewer/schemas/get_mouse.json index d7321a0..e3b6b7b 100644 --- a/src/opengeodeweb_viewer/rpc/viewer/schemas/get_mouse.json +++ b/src/opengeodeweb_viewer/rpc/viewer/schemas/get_mouse.json @@ -8,5 +8,10 @@ "y": { "type": "integer" } - } + }, + "required": [ + "x", + "y" + ], + "additionalProperties": false } \ No newline at end of file From 956b59dc6a99b5e58ce0fad596bf32e8b01cf35f Mon Sep 17 00:00:00 2001 From: JulienChampagnol Date: Fri, 29 Nov 2024 16:37:46 +0100 Subject: [PATCH 3/8] fix class props --- src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py | 4 ++-- src/tests/test_viewer_protocols.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py b/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py index a9cde91..0a13252 100644 --- a/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py +++ b/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py @@ -144,8 +144,8 @@ def reset(self, params): @exportRpc(prefix + schemas_dict["get_mouse"]["rpc"]) def getMouse(self, params): - print(schemas_dict["get_mouse"]["rpc"], params, flush=True) - validate_schema(params, schemas_dict["get_mouse"]) + print(self.schemas_dict["get_mouse"]["rpc"], f"{params=}", flush=True) + validate_schema(params, self.schemas_dict["get_mouse"]) x = params["x"] y = params["y"] return {"x": x, "y": y} diff --git a/src/tests/test_viewer_protocols.py b/src/tests/test_viewer_protocols.py index 1fcf85d..cc3518d 100644 --- a/src/tests/test_viewer_protocols.py +++ b/src/tests/test_viewer_protocols.py @@ -109,7 +109,7 @@ def test_take_screenshot(server): def test_get_mouse(server): server.call( - class_.prefix + class_.schemas_dict["get_mouse"]["rpc"], [{"x": 100, "y": 200}] + VtkViewerView.prefix + VtkViewerView.schemas_dict["get_mouse"]["rpc"], [{"x": 100, "y": 200}] ) response = server.get_response() assert "x" in response["result"] From 299514d52cae72f3a3d8972b89b919f487eb7e73 Mon Sep 17 00:00:00 2001 From: SpliiT Date: Tue, 3 Dec 2024 15:22:31 +0100 Subject: [PATCH 4/8] edit test --- src/tests/test_viewer_protocols.py | 3 ++- src/tests/tests_output/test.jpeg | Bin 10466 -> 10543 bytes 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tests/test_viewer_protocols.py b/src/tests/test_viewer_protocols.py index 77ee276..1dff570 100644 --- a/src/tests/test_viewer_protocols.py +++ b/src/tests/test_viewer_protocols.py @@ -139,7 +139,8 @@ def test_take_screenshot(server): def test_get_mouse(server): server.call( - class_.prefix + class_.schemas_dict["get_mouse"]["rpc"], [{"x": 100, "y": 200}] + VtkViewerView.prefix + VtkViewerView.schemas_dict["get_mouse"]["rpc"], + [{"x": 100, "y": 200}], ) response = server.get_response() assert "x" in response["result"] diff --git a/src/tests/tests_output/test.jpeg b/src/tests/tests_output/test.jpeg index ee7e0565b11ab1ed5c41aeaeaec44a49cf4fb74c..6b2509f219dee5c4eca6d83f49c57222970133e8 100644 GIT binary patch delta 9711 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 I81Jk91AWZ}f&c&j delta 9672 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 E0PRo4!~g&Q From b83953ebfd8dda5962aeb302dbc9b4728ac5e2ff Mon Sep 17 00:00:00 2001 From: SpliiT Date: Tue, 3 Dec 2024 15:29:39 +0100 Subject: [PATCH 5/8] edit get_mouse --- .../rpc/viewer/schemas/get_mouse.json | 26 +++++++++---------- .../rpc/viewer/viewer_protocols.py | 5 ++-- src/tests/test_viewer_protocols.py | 16 +++++------- 3 files changed, 22 insertions(+), 25 deletions(-) diff --git a/src/opengeodeweb_viewer/rpc/viewer/schemas/get_mouse.json b/src/opengeodeweb_viewer/rpc/viewer/schemas/get_mouse.json index e3b6b7b..d6553ad 100644 --- a/src/opengeodeweb_viewer/rpc/viewer/schemas/get_mouse.json +++ b/src/opengeodeweb_viewer/rpc/viewer/schemas/get_mouse.json @@ -1,17 +1,17 @@ { "rpc": "get_mouse", - "type": "object", - "properties": { - "x": { - "type": "integer" + "schema": { + "type": "object", + "properties": { + "mouse_ids": { + "type": "array", + "items": { + "type": "string" + } + } }, - "y": { - "type": "integer" - } - }, - "required": [ - "x", - "y" - ], - "additionalProperties": false + "required": [ + "mouse_ids" + ] + } } \ No newline at end of file diff --git a/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py b/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py index 911fa04..726a92a 100644 --- a/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py +++ b/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py @@ -151,6 +151,5 @@ def reset(self, params): def getMouse(self, params): print(self.schemas_dict["get_mouse"]["rpc"], f"{params=}", flush=True) validate_schema(params, self.schemas_dict["get_mouse"]) - x = params["x"] - y = params["y"] - return {"x": x, "y": y} + mouse_ids = params["mouse_ids"] + return {"mouse_ids": mouse_ids} diff --git a/src/tests/test_viewer_protocols.py b/src/tests/test_viewer_protocols.py index 1dff570..af09d3f 100644 --- a/src/tests/test_viewer_protocols.py +++ b/src/tests/test_viewer_protocols.py @@ -140,14 +140,12 @@ def test_take_screenshot(server): def test_get_mouse(server): server.call( VtkViewerView.prefix + VtkViewerView.schemas_dict["get_mouse"]["rpc"], - [{"x": 100, "y": 200}], + [{"mouse_ids": ["id1", "id2", "id3"]}], ) response = server.get_response() - assert "x" in response["result"] - assert "y" in response["result"] - x = response["result"]["x"] - y = response["result"]["y"] - assert type(x) is int - assert type(y) is int - assert x == 100 - assert y == 200 + + assert "mouse_ids" in response["result"] + + mouse_ids = response["result"]["mouse_ids"] + assert isinstance(mouse_ids, list) + assert set(mouse_ids) == {"id1", "id2", "id3"} From 260b8f48ff5ab799cbbd0bcdaf260e7247a21b52 Mon Sep 17 00:00:00 2001 From: SpliiT Date: Tue, 3 Dec 2024 15:36:50 +0100 Subject: [PATCH 6/8] edit get_mouse --- .../rpc/viewer/schemas/get_mouse.json | 13 +++++++------ .../rpc/viewer/viewer_protocols.py | 5 ++++- src/tests/test_viewer_protocols.py | 6 +++--- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/opengeodeweb_viewer/rpc/viewer/schemas/get_mouse.json b/src/opengeodeweb_viewer/rpc/viewer/schemas/get_mouse.json index d6553ad..ffb4a60 100644 --- a/src/opengeodeweb_viewer/rpc/viewer/schemas/get_mouse.json +++ b/src/opengeodeweb_viewer/rpc/viewer/schemas/get_mouse.json @@ -3,15 +3,16 @@ "schema": { "type": "object", "properties": { - "mouse_ids": { - "type": "array", - "items": { - "type": "string" - } + "x": { + "type": "number" + }, + "y": { + "type": "number" } }, "required": [ - "mouse_ids" + "x", + "y" ] } } \ No newline at end of file diff --git a/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py b/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py index 726a92a..b147790 100644 --- a/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py +++ b/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py @@ -151,5 +151,8 @@ def reset(self, params): def getMouse(self, params): print(self.schemas_dict["get_mouse"]["rpc"], f"{params=}", flush=True) validate_schema(params, self.schemas_dict["get_mouse"]) - mouse_ids = params["mouse_ids"] + x = params["x"] + y = params["y"] + mouse_ids = self.get_object_ids_at_position(x, y) + return {"mouse_ids": mouse_ids} diff --git a/src/tests/test_viewer_protocols.py b/src/tests/test_viewer_protocols.py index af09d3f..68ed32a 100644 --- a/src/tests/test_viewer_protocols.py +++ b/src/tests/test_viewer_protocols.py @@ -140,12 +140,12 @@ def test_take_screenshot(server): def test_get_mouse(server): server.call( VtkViewerView.prefix + VtkViewerView.schemas_dict["get_mouse"]["rpc"], - [{"mouse_ids": ["id1", "id2", "id3"]}], + [{"x": 100, "y": 200}], ) response = server.get_response() - assert "mouse_ids" in response["result"] mouse_ids = response["result"]["mouse_ids"] assert isinstance(mouse_ids, list) - assert set(mouse_ids) == {"id1", "id2", "id3"} + assert isinstance(mouse_ids[0], str) + assert len(mouse_ids) > 0 From 1d9fe8b1dc6a195f932366a49e20b6a7dbb7c89f Mon Sep 17 00:00:00 2001 From: SpliiT Date: Tue, 3 Dec 2024 15:45:42 +0100 Subject: [PATCH 7/8] edit test and protocole --- .../rpc/viewer/viewer_protocols.py | 2 +- src/tests/test_viewer_protocols.py | 15 +++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py b/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py index b147790..ca5665c 100644 --- a/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py +++ b/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py @@ -153,6 +153,6 @@ def getMouse(self, params): validate_schema(params, self.schemas_dict["get_mouse"]) x = params["x"] y = params["y"] - mouse_ids = self.get_object_ids_at_position(x, y) + mouse_ids = ["id1", "id2", "id3"] return {"mouse_ids": mouse_ids} diff --git a/src/tests/test_viewer_protocols.py b/src/tests/test_viewer_protocols.py index 68ed32a..42a5f82 100644 --- a/src/tests/test_viewer_protocols.py +++ b/src/tests/test_viewer_protocols.py @@ -143,9 +143,16 @@ def test_get_mouse(server): [{"x": 100, "y": 200}], ) response = server.get_response() - assert "mouse_ids" in response["result"] + + print(f"Response: {response}") + + assert "result" in response, f"Key 'result' not found in response: {response}" + + assert ( + "mouse_ids" in response["result"] + ), f"Key 'mouse_ids' not found in response['result']: {response['result']}" mouse_ids = response["result"]["mouse_ids"] - assert isinstance(mouse_ids, list) - assert isinstance(mouse_ids[0], str) - assert len(mouse_ids) > 0 + assert isinstance(mouse_ids, list), f"Expected a list, but got {type(mouse_ids)}" + assert all(isinstance(id, str) for id in mouse_ids), "All IDs should be strings" + assert len(mouse_ids) > 0, "The list of mouse_ids should not be empty" From b39518c879b09bb02ca279b5daacd155d08f5f87 Mon Sep 17 00:00:00 2001 From: SpliiT Date: Tue, 3 Dec 2024 16:30:29 +0100 Subject: [PATCH 8/8] changes, new rpc : picked_ids formerly get_mouse --- .../{get_mouse.json => picked_ids.json} | 11 ++++- .../rpc/viewer/viewer_protocols.py | 44 ++++++++++++++++--- src/tests/test_viewer_protocols.py | 23 +++++----- 3 files changed, 60 insertions(+), 18 deletions(-) rename src/opengeodeweb_viewer/rpc/viewer/schemas/{get_mouse.json => picked_ids.json} (55%) diff --git a/src/opengeodeweb_viewer/rpc/viewer/schemas/get_mouse.json b/src/opengeodeweb_viewer/rpc/viewer/schemas/picked_ids.json similarity index 55% rename from src/opengeodeweb_viewer/rpc/viewer/schemas/get_mouse.json rename to src/opengeodeweb_viewer/rpc/viewer/schemas/picked_ids.json index ffb4a60..012afcc 100644 --- a/src/opengeodeweb_viewer/rpc/viewer/schemas/get_mouse.json +++ b/src/opengeodeweb_viewer/rpc/viewer/schemas/picked_ids.json @@ -1,5 +1,5 @@ { - "rpc": "get_mouse", + "rpc": "picked_ids", "schema": { "type": "object", "properties": { @@ -8,11 +8,18 @@ }, "y": { "type": "number" + }, + "ids": { + "type": "array", + "items": { + "type": "string" + } } }, "required": [ "x", - "y" + "y", + "ids" ] } } \ No newline at end of file diff --git a/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py b/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py index ca5665c..98c355a 100644 --- a/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py +++ b/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py @@ -1,5 +1,6 @@ # Standard library imports import json +import math import os from pathlib import Path @@ -147,12 +148,43 @@ def reset(self, params): renderWindow = self.getView("-1") renderWindow.GetRenderers().GetFirstRenderer().RemoveAllViewProps() - @exportRpc(prefix + schemas_dict["get_mouse"]["rpc"]) - def getMouse(self, params): - print(self.schemas_dict["get_mouse"]["rpc"], f"{params=}", flush=True) - validate_schema(params, self.schemas_dict["get_mouse"]) + def computeEpsilon(self, renderer, z): + renderer.SetDisplayPoint(0, 0, z) + renderer.DisplayToWorld() + windowLowerLeft = renderer.GetWorldPoint() + size = renderer.GetRenderWindow().GetSize() + renderer.SetDisplayPoint(size[0], size[1], z) + renderer.DisplayToWorld() + windowUpperRight = renderer.GetWorldPoint() + epsilon = 0 + for i in range(3): + epsilon += (windowUpperRight[i] - windowLowerLeft[i]) * ( + windowUpperRight[i] - windowLowerLeft[i] + ) + return math.sqrt(epsilon) * 0.0125 + + @exportRpc(prefix + schemas_dict["picked_ids"]["rpc"]) + def pickedIds(self, params): + print(self.schemas_dict["picked_ids"]["rpc"], f"{params=}", flush=True) + validate_schema(params, self.schemas_dict["picked_ids"]) x = params["x"] y = params["y"] - mouse_ids = ["id1", "id2", "id3"] + ids = params["ids"] - return {"mouse_ids": mouse_ids} + renderWindow = self.getView("-1") + renderer = renderWindow.GetRenderers().GetFirstRenderer() + picker = vtk.vtkWorldPointPicker() + picker.Pick([x, y, 0], renderer) + point = picker.GetPickPosition() + epsilon = self.computeEpsilon(renderer, point[2]) + bbox = vtk.vtkBoundingBox() + bbox.AddPoint(point[0] + epsilon, point[1] + epsilon, point[2] + epsilon) + bbox.AddPoint(point[0] - epsilon, point[1] - epsilon, point[2] - epsilon) + + array_ids = [] + for id in ids: + bounds = self.get_object(id)["actor"].GetBounds() + if bbox.Intersects(bounds): + array_ids.append(id) + + return {"array_ids": array_ids} diff --git a/src/tests/test_viewer_protocols.py b/src/tests/test_viewer_protocols.py index 42a5f82..bef7ac1 100644 --- a/src/tests/test_viewer_protocols.py +++ b/src/tests/test_viewer_protocols.py @@ -137,22 +137,25 @@ def test_take_screenshot(server): assert server.images_diff(first_image_path, second_image_path) == 0.0 -def test_get_mouse(server): +def test_picked_ids(server): + + test_register_mesh(server) + server.call( - VtkViewerView.prefix + VtkViewerView.schemas_dict["get_mouse"]["rpc"], - [{"x": 100, "y": 200}], + VtkViewerView.prefix + VtkViewerView.schemas_dict["picked_ids"]["rpc"], + [{"x": 100, "y": 200, "ids": ["123456789"]}], ) response = server.get_response() - print(f"Response: {response}") + print(f"Response: {response}", flush=True) assert "result" in response, f"Key 'result' not found in response: {response}" assert ( - "mouse_ids" in response["result"] - ), f"Key 'mouse_ids' not found in response['result']: {response['result']}" + "array_ids" in response["result"] + ), f"Key 'array_ids' not found in response['result']: {response['result']}" - mouse_ids = response["result"]["mouse_ids"] - assert isinstance(mouse_ids, list), f"Expected a list, but got {type(mouse_ids)}" - assert all(isinstance(id, str) for id in mouse_ids), "All IDs should be strings" - assert len(mouse_ids) > 0, "The list of mouse_ids should not be empty" + array_ids = response["result"]["array_ids"] + assert isinstance(array_ids, list), f"Expected a list, but got {type(array_ids)}" + assert all(isinstance(id, str) for id in array_ids), "All IDs should be strings" + assert len(array_ids) > 0, "The list of array_ids should not be empty"