diff --git a/opengeodeweb_viewer_schemas.json b/opengeodeweb_viewer_schemas.json index 47ffb9db..426c79ec 100644 --- a/opengeodeweb_viewer_schemas.json +++ b/opengeodeweb_viewer_schemas.json @@ -1183,6 +1183,13 @@ }, "minItems": 1 }, + "color_mode": { + "type": "string", + "enum": [ + "constant", + "random" + ] + }, "color": { "type": "object", "properties": { @@ -1219,7 +1226,7 @@ "required": [ "id", "block_ids", - "color" + "color_mode" ], "additionalProperties": false } @@ -1268,6 +1275,13 @@ }, "minItems": 1 }, + "color_mode": { + "type": "string", + "enum": [ + "constant", + "random" + ] + }, "color": { "type": "object", "properties": { @@ -1304,7 +1318,7 @@ "required": [ "id", "block_ids", - "color" + "color_mode" ], "additionalProperties": false } @@ -1374,6 +1388,13 @@ }, "minItems": 1 }, + "color_mode": { + "type": "string", + "enum": [ + "constant", + "random" + ] + }, "color": { "type": "object", "properties": { @@ -1410,7 +1431,7 @@ "required": [ "id", "block_ids", - "color" + "color_mode" ], "additionalProperties": false } @@ -1548,6 +1569,13 @@ }, "minItems": 1 }, + "color_mode": { + "type": "string", + "enum": [ + "constant", + "random" + ] + }, "color": { "type": "object", "properties": { @@ -1584,7 +1612,7 @@ "required": [ "id", "block_ids", - "color" + "color_mode" ], "additionalProperties": false }, diff --git a/requirements.txt b/requirements.txt index 3e6ddf7f..16407b43 100644 --- a/requirements.txt +++ b/requirements.txt @@ -61,4 +61,3 @@ wslink==1.12.4 yarl>=1 # via aiohttp -opengeodeweb-microservice==1.*,>=1.1.2 diff --git a/src/opengeodeweb_viewer/rpc/model/blocks/model_blocks_protocols.py b/src/opengeodeweb_viewer/rpc/model/blocks/model_blocks_protocols.py index 692e9ec3..fb59d603 100644 --- a/src/opengeodeweb_viewer/rpc/model/blocks/model_blocks_protocols.py +++ b/src/opengeodeweb_viewer/rpc/model/blocks/model_blocks_protocols.py @@ -6,11 +6,8 @@ from opengeodeweb_microservice.schemas import get_schemas_dict # Local application imports -from opengeodeweb_viewer.utils_functions import ( - validate_schema, - RpcParams, -) -from opengeodeweb_viewer.rpc.model.model_protocols import VtkModelView +from opengeodeweb_viewer.utils_functions import validate_schema, RpcParams +from opengeodeweb_viewer.rpc.model.model_protocols import VtkModelView, ColorResult from . import schemas @@ -34,12 +31,14 @@ def setModelBlocksPolyhedraVisibility(self, rpc_params: RpcParams) -> None: self.SetBlocksVisibility(params.id, params.block_ids, params.visibility) @exportRpc(model_blocks_prefix + model_blocks_schemas_dict["color"]["rpc"]) - def setModelBlocksPolyhedraColor(self, rpc_params: RpcParams) -> None: + def setModelBlocksColor(self, rpc_params: RpcParams) -> list[ColorResult]: validate_schema( rpc_params, self.model_blocks_schemas_dict["color"], self.model_blocks_prefix, ) params = schemas.Color.from_dict(rpc_params) - color = params.color - self.SetBlocksColor(params.id, params.block_ids, color.r, color.g, color.b) + pipeline = self.get_vtk_pipeline(params.id) + return self.apply_color( + pipeline, params.block_ids, params.color_mode.value, params.color + ) diff --git a/src/opengeodeweb_viewer/rpc/model/blocks/schemas/color.json b/src/opengeodeweb_viewer/rpc/model/blocks/schemas/color.json index a775e915..0d7c3543 100644 --- a/src/opengeodeweb_viewer/rpc/model/blocks/schemas/color.json +++ b/src/opengeodeweb_viewer/rpc/model/blocks/schemas/color.json @@ -8,48 +8,25 @@ }, "block_ids": { "type": "array", - "items": { - "type": "integer" - }, + "items": { "type": "integer" }, "minItems": 1 }, + "color_mode": { + "type": "string", + "enum": ["constant", "random"] + }, "color": { "type": "object", "properties": { - "r": { - "type": "integer", - "minimum": 0, - "maximum": 255 - }, - "g": { - "type": "integer", - "minimum": 0, - "maximum": 255 - }, - "b": { - "type": "integer", - "minimum": 0, - "maximum": 255 - }, - "a": { - "type": "number", - "minimum": 0, - "maximum": 1, - "default": 1 - } + "r": { "type": "integer", "minimum": 0, "maximum": 255 }, + "g": { "type": "integer", "minimum": 0, "maximum": 255 }, + "b": { "type": "integer", "minimum": 0, "maximum": 255 }, + "a": { "type": "number", "minimum": 0, "maximum": 1, "default": 1 } }, - "required": [ - "r", - "g", - "b" - ], + "required": ["r", "g", "b"], "additionalProperties": false } }, - "required": [ - "id", - "block_ids", - "color" - ], + "required": ["id", "block_ids", "color_mode"], "additionalProperties": false -} \ No newline at end of file +} diff --git a/src/opengeodeweb_viewer/rpc/model/blocks/schemas/color.py b/src/opengeodeweb_viewer/rpc/model/blocks/schemas/color.py index 9763bc1c..8a0e135c 100644 --- a/src/opengeodeweb_viewer/rpc/model/blocks/schemas/color.py +++ b/src/opengeodeweb_viewer/rpc/model/blocks/schemas/color.py @@ -1,6 +1,7 @@ from dataclasses_json import DataClassJsonMixin from dataclasses import dataclass from typing import Optional, List +from enum import Enum @dataclass @@ -14,11 +15,17 @@ def __post_init__(self) -> None: a: Optional[float] = None +class ColorMode(Enum): + CONSTANT = "constant" + RANDOM = "random" + + @dataclass class Color(DataClassJsonMixin): def __post_init__(self) -> None: print(self, flush=True) block_ids: List[int] - color: ColorClass + color_mode: ColorMode id: str + color: Optional[ColorClass] = None diff --git a/src/opengeodeweb_viewer/rpc/model/corners/model_corners_protocols.py b/src/opengeodeweb_viewer/rpc/model/corners/model_corners_protocols.py index ef376891..7e5d0428 100644 --- a/src/opengeodeweb_viewer/rpc/model/corners/model_corners_protocols.py +++ b/src/opengeodeweb_viewer/rpc/model/corners/model_corners_protocols.py @@ -6,11 +6,8 @@ from opengeodeweb_microservice.schemas import get_schemas_dict # Local application imports -from opengeodeweb_viewer.utils_functions import ( - validate_schema, - RpcParams, -) -from opengeodeweb_viewer.rpc.model.model_protocols import VtkModelView +from opengeodeweb_viewer.utils_functions import validate_schema, RpcParams +from opengeodeweb_viewer.rpc.model.model_protocols import VtkModelView, ColorResult from . import schemas @@ -34,12 +31,14 @@ def setModelCornersPointsVisibility(self, rpc_params: RpcParams) -> None: self.SetBlocksVisibility(params.id, params.block_ids, params.visibility) @exportRpc(model_corners_prefix + model_corners_schemas_dict["color"]["rpc"]) - def setModelCornersPointsColor(self, rpc_params: RpcParams) -> None: + def setModelCornersColor(self, rpc_params: RpcParams) -> list[ColorResult]: validate_schema( rpc_params, self.model_corners_schemas_dict["color"], self.model_corners_prefix, ) params = schemas.Color.from_dict(rpc_params) - color = params.color - self.SetBlocksColor(params.id, params.block_ids, color.r, color.g, color.b) + pipeline = self.get_vtk_pipeline(params.id) + return self.apply_color( + pipeline, params.block_ids, params.color_mode.value, params.color + ) diff --git a/src/opengeodeweb_viewer/rpc/model/corners/schemas/color.json b/src/opengeodeweb_viewer/rpc/model/corners/schemas/color.json index a775e915..0d7c3543 100644 --- a/src/opengeodeweb_viewer/rpc/model/corners/schemas/color.json +++ b/src/opengeodeweb_viewer/rpc/model/corners/schemas/color.json @@ -8,48 +8,25 @@ }, "block_ids": { "type": "array", - "items": { - "type": "integer" - }, + "items": { "type": "integer" }, "minItems": 1 }, + "color_mode": { + "type": "string", + "enum": ["constant", "random"] + }, "color": { "type": "object", "properties": { - "r": { - "type": "integer", - "minimum": 0, - "maximum": 255 - }, - "g": { - "type": "integer", - "minimum": 0, - "maximum": 255 - }, - "b": { - "type": "integer", - "minimum": 0, - "maximum": 255 - }, - "a": { - "type": "number", - "minimum": 0, - "maximum": 1, - "default": 1 - } + "r": { "type": "integer", "minimum": 0, "maximum": 255 }, + "g": { "type": "integer", "minimum": 0, "maximum": 255 }, + "b": { "type": "integer", "minimum": 0, "maximum": 255 }, + "a": { "type": "number", "minimum": 0, "maximum": 1, "default": 1 } }, - "required": [ - "r", - "g", - "b" - ], + "required": ["r", "g", "b"], "additionalProperties": false } }, - "required": [ - "id", - "block_ids", - "color" - ], + "required": ["id", "block_ids", "color_mode"], "additionalProperties": false -} \ No newline at end of file +} diff --git a/src/opengeodeweb_viewer/rpc/model/corners/schemas/color.py b/src/opengeodeweb_viewer/rpc/model/corners/schemas/color.py index 9763bc1c..8a0e135c 100644 --- a/src/opengeodeweb_viewer/rpc/model/corners/schemas/color.py +++ b/src/opengeodeweb_viewer/rpc/model/corners/schemas/color.py @@ -1,6 +1,7 @@ from dataclasses_json import DataClassJsonMixin from dataclasses import dataclass from typing import Optional, List +from enum import Enum @dataclass @@ -14,11 +15,17 @@ def __post_init__(self) -> None: a: Optional[float] = None +class ColorMode(Enum): + CONSTANT = "constant" + RANDOM = "random" + + @dataclass class Color(DataClassJsonMixin): def __post_init__(self) -> None: print(self, flush=True) block_ids: List[int] - color: ColorClass + color_mode: ColorMode id: str + color: Optional[ColorClass] = None diff --git a/src/opengeodeweb_viewer/rpc/model/lines/model_lines_protocols.py b/src/opengeodeweb_viewer/rpc/model/lines/model_lines_protocols.py index 60c11925..a44cecd5 100644 --- a/src/opengeodeweb_viewer/rpc/model/lines/model_lines_protocols.py +++ b/src/opengeodeweb_viewer/rpc/model/lines/model_lines_protocols.py @@ -6,11 +6,8 @@ from opengeodeweb_microservice.schemas import get_schemas_dict # Local application imports -from opengeodeweb_viewer.utils_functions import ( - validate_schema, - RpcParams, -) -from opengeodeweb_viewer.rpc.model.model_protocols import VtkModelView +from opengeodeweb_viewer.utils_functions import validate_schema, RpcParams +from opengeodeweb_viewer.rpc.model.model_protocols import VtkModelView, ColorResult from . import schemas @@ -34,12 +31,14 @@ def setModelLinesEdgesVisibility(self, rpc_params: RpcParams) -> None: self.SetBlocksVisibility(params.id, params.block_ids, params.visibility) @exportRpc(model_lines_prefix + model_lines_schemas_dict["color"]["rpc"]) - def setModelLinesEdgesColor(self, rpc_params: RpcParams) -> None: + def setModelLinesColor(self, rpc_params: RpcParams) -> list[ColorResult]: validate_schema( rpc_params, self.model_lines_schemas_dict["color"], self.model_lines_prefix, ) params = schemas.Color.from_dict(rpc_params) - color = params.color - self.SetBlocksColor(params.id, params.block_ids, color.r, color.g, color.b) + pipeline = self.get_vtk_pipeline(params.id) + return self.apply_color( + pipeline, params.block_ids, params.color_mode.value, params.color + ) diff --git a/src/opengeodeweb_viewer/rpc/model/lines/schemas/color.json b/src/opengeodeweb_viewer/rpc/model/lines/schemas/color.json index a775e915..0d7c3543 100644 --- a/src/opengeodeweb_viewer/rpc/model/lines/schemas/color.json +++ b/src/opengeodeweb_viewer/rpc/model/lines/schemas/color.json @@ -8,48 +8,25 @@ }, "block_ids": { "type": "array", - "items": { - "type": "integer" - }, + "items": { "type": "integer" }, "minItems": 1 }, + "color_mode": { + "type": "string", + "enum": ["constant", "random"] + }, "color": { "type": "object", "properties": { - "r": { - "type": "integer", - "minimum": 0, - "maximum": 255 - }, - "g": { - "type": "integer", - "minimum": 0, - "maximum": 255 - }, - "b": { - "type": "integer", - "minimum": 0, - "maximum": 255 - }, - "a": { - "type": "number", - "minimum": 0, - "maximum": 1, - "default": 1 - } + "r": { "type": "integer", "minimum": 0, "maximum": 255 }, + "g": { "type": "integer", "minimum": 0, "maximum": 255 }, + "b": { "type": "integer", "minimum": 0, "maximum": 255 }, + "a": { "type": "number", "minimum": 0, "maximum": 1, "default": 1 } }, - "required": [ - "r", - "g", - "b" - ], + "required": ["r", "g", "b"], "additionalProperties": false } }, - "required": [ - "id", - "block_ids", - "color" - ], + "required": ["id", "block_ids", "color_mode"], "additionalProperties": false -} \ No newline at end of file +} diff --git a/src/opengeodeweb_viewer/rpc/model/lines/schemas/color.py b/src/opengeodeweb_viewer/rpc/model/lines/schemas/color.py index 9763bc1c..8a0e135c 100644 --- a/src/opengeodeweb_viewer/rpc/model/lines/schemas/color.py +++ b/src/opengeodeweb_viewer/rpc/model/lines/schemas/color.py @@ -1,6 +1,7 @@ from dataclasses_json import DataClassJsonMixin from dataclasses import dataclass from typing import Optional, List +from enum import Enum @dataclass @@ -14,11 +15,17 @@ def __post_init__(self) -> None: a: Optional[float] = None +class ColorMode(Enum): + CONSTANT = "constant" + RANDOM = "random" + + @dataclass class Color(DataClassJsonMixin): def __post_init__(self) -> None: print(self, flush=True) block_ids: List[int] - color: ColorClass + color_mode: ColorMode id: str + color: Optional[ColorClass] = None diff --git a/src/opengeodeweb_viewer/rpc/model/model_protocols.py b/src/opengeodeweb_viewer/rpc/model/model_protocols.py index 6ca47947..bdad0f36 100644 --- a/src/opengeodeweb_viewer/rpc/model/model_protocols.py +++ b/src/opengeodeweb_viewer/rpc/model/model_protocols.py @@ -2,6 +2,9 @@ import os # Third party imports +from vtkmodules.vtkCommonDataModel import ( + vtkCompositeDataSet, +) from vtkmodules.vtkRenderingCore import ( vtkCompositeDataDisplayAttributes, vtkCompositePolyDataMapper, @@ -15,12 +18,32 @@ from opengeodeweb_viewer.utils_functions import ( validate_schema, RpcParams, + deterministic_color, ) from opengeodeweb_viewer.object.object_methods import VtkObjectView from opengeodeweb_viewer.vtk_protocol import VtkPipeline +from typing import Optional, List, TypedDict, Protocol from . import schemas +class ColorProtocol(Protocol): + r: int + g: int + b: int + + +class ColorRGB(TypedDict): + r: int + g: int + b: int + + +class ColorResult(TypedDict): + viewer_id: int + geode_id: str + color: ColorRGB + + class VtkModelView(VtkObjectView): model_prefix = "opengeodeweb_viewer.model." model_schemas_dict = get_schemas_dict( @@ -30,6 +53,45 @@ class VtkModelView(VtkObjectView): def __init__(self) -> None: super().__init__() + def apply_color( + self, + pipeline: VtkPipeline, + block_ids: list[int], + color_mode: str, + color: Optional[ColorProtocol] = None, + ) -> list[ColorResult]: + mapper = pipeline.mapper + if not isinstance(mapper, vtkCompositePolyDataMapper): + return [] + attr = mapper.GetCompositeDataDisplayAttributes() + colors: list[ColorResult] = [] + for block_id in block_ids: + block_ds = pipeline.blockDataSets[block_id] + if color_mode == "random": + geode_id = pipeline.blockGeodeIds[block_id] + r, g, b = deterministic_color(str(geode_id)) + attr.SetBlockColor(block_ds, [r, g, b]) + colors.append( + { + "viewer_id": block_id, + "geode_id": str(geode_id), + "color": { + "r": round(r * 255), + "g": round(g * 255), + "b": round(b * 255), + }, + } + ) + elif color is not None: + r, g, b = ( + color.r / 255, + color.g / 255, + color.b / 255, + ) + attr.SetBlockColor(block_ds, [r, g, b]) + mapper.Modified() + return colors + @exportRpc(model_prefix + model_schemas_dict["register"]["rpc"]) def registerModel(self, rpc_params: RpcParams) -> None: validate_schema( @@ -59,7 +121,11 @@ def registerModel(self, rpc_params: RpcParams) -> None: flat_index = iterator.GetCurrentFlatIndex() while flat_index > len(data.blockDataSets): data.blockDataSets.append(None) + data.blockGeodeIds.append("") data.blockDataSets.append(block) + meta = iterator.GetCurrentMetaData() + name = meta.Get(vtkCompositeDataSet.NAME()) + data.blockGeodeIds.append(name) iterator.GoToNextItem() self.registerObject(data_id, file_name, data) except Exception as e: diff --git a/src/opengeodeweb_viewer/rpc/model/surfaces/model_surfaces_protocols.py b/src/opengeodeweb_viewer/rpc/model/surfaces/model_surfaces_protocols.py index aaa3a818..0c761d29 100644 --- a/src/opengeodeweb_viewer/rpc/model/surfaces/model_surfaces_protocols.py +++ b/src/opengeodeweb_viewer/rpc/model/surfaces/model_surfaces_protocols.py @@ -6,11 +6,8 @@ from opengeodeweb_microservice.schemas import get_schemas_dict # Local application imports -from opengeodeweb_viewer.utils_functions import ( - validate_schema, - RpcParams, -) -from opengeodeweb_viewer.rpc.model.model_protocols import VtkModelView +from opengeodeweb_viewer.utils_functions import validate_schema, RpcParams +from opengeodeweb_viewer.rpc.model.model_protocols import VtkModelView, ColorResult from . import schemas @@ -34,12 +31,14 @@ def setModelSurfacesPolygonsVisibility(self, rpc_params: RpcParams) -> None: self.SetBlocksVisibility(params.id, params.block_ids, params.visibility) @exportRpc(model_surfaces_prefix + model_surfaces_schemas_dict["color"]["rpc"]) - def setModelSurfacesPolygonsCOlor(self, rpc_params: RpcParams) -> None: + def setModelSurfacesColor(self, rpc_params: RpcParams) -> list[ColorResult]: validate_schema( rpc_params, self.model_surfaces_schemas_dict["color"], self.model_surfaces_prefix, ) params = schemas.Color.from_dict(rpc_params) - color = params.color - self.SetBlocksColor(params.id, params.block_ids, color.r, color.g, color.b) + pipeline = self.get_vtk_pipeline(params.id) + return self.apply_color( + pipeline, params.block_ids, params.color_mode.value, params.color + ) diff --git a/src/opengeodeweb_viewer/rpc/model/surfaces/schemas/color.json b/src/opengeodeweb_viewer/rpc/model/surfaces/schemas/color.json index a775e915..0d7c3543 100644 --- a/src/opengeodeweb_viewer/rpc/model/surfaces/schemas/color.json +++ b/src/opengeodeweb_viewer/rpc/model/surfaces/schemas/color.json @@ -8,48 +8,25 @@ }, "block_ids": { "type": "array", - "items": { - "type": "integer" - }, + "items": { "type": "integer" }, "minItems": 1 }, + "color_mode": { + "type": "string", + "enum": ["constant", "random"] + }, "color": { "type": "object", "properties": { - "r": { - "type": "integer", - "minimum": 0, - "maximum": 255 - }, - "g": { - "type": "integer", - "minimum": 0, - "maximum": 255 - }, - "b": { - "type": "integer", - "minimum": 0, - "maximum": 255 - }, - "a": { - "type": "number", - "minimum": 0, - "maximum": 1, - "default": 1 - } + "r": { "type": "integer", "minimum": 0, "maximum": 255 }, + "g": { "type": "integer", "minimum": 0, "maximum": 255 }, + "b": { "type": "integer", "minimum": 0, "maximum": 255 }, + "a": { "type": "number", "minimum": 0, "maximum": 1, "default": 1 } }, - "required": [ - "r", - "g", - "b" - ], + "required": ["r", "g", "b"], "additionalProperties": false } }, - "required": [ - "id", - "block_ids", - "color" - ], + "required": ["id", "block_ids", "color_mode"], "additionalProperties": false -} \ No newline at end of file +} diff --git a/src/opengeodeweb_viewer/rpc/model/surfaces/schemas/color.py b/src/opengeodeweb_viewer/rpc/model/surfaces/schemas/color.py index 9763bc1c..8a0e135c 100644 --- a/src/opengeodeweb_viewer/rpc/model/surfaces/schemas/color.py +++ b/src/opengeodeweb_viewer/rpc/model/surfaces/schemas/color.py @@ -1,6 +1,7 @@ from dataclasses_json import DataClassJsonMixin from dataclasses import dataclass from typing import Optional, List +from enum import Enum @dataclass @@ -14,11 +15,17 @@ def __post_init__(self) -> None: a: Optional[float] = None +class ColorMode(Enum): + CONSTANT = "constant" + RANDOM = "random" + + @dataclass class Color(DataClassJsonMixin): def __post_init__(self) -> None: print(self, flush=True) block_ids: List[int] - color: ColorClass + color_mode: ColorMode id: str + color: Optional[ColorClass] = None diff --git a/src/opengeodeweb_viewer/utils_functions.py b/src/opengeodeweb_viewer/utils_functions.py index f2c468de..cf7697fe 100644 --- a/src/opengeodeweb_viewer/utils_functions.py +++ b/src/opengeodeweb_viewer/utils_functions.py @@ -2,6 +2,7 @@ # Third party imports import fastjsonschema # type: ignore +import math from opengeodeweb_microservice.schemas import SchemaDict @@ -25,3 +26,32 @@ def validate_schema( "description": e.message, } ) + + +def deterministic_color(identifier: str) -> tuple[float, float, float]: + CIRCLE_DEGREES = 360 + HASH_PRIME = 31 + DEGREES_PER_STEP = 30 + STEPS_COUNT = 12 + BASE_LIGHTNESS = 0.5 + VIBRANCY_RANGE = 0.35 + MIRROR_MAX = 9 + PHASE_GREEN = 8 + + if not identifier: + return (128 / 255, 128 / 255, 128 / 255) + + h = 0 + for ch in identifier: + h = ord(ch) + h * HASH_PRIME + + hue = abs(h % CIRCLE_DEGREES) + + def component(phase: int) -> float: + step = (phase + hue / DEGREES_PER_STEP) % STEPS_COUNT + intensity = BASE_LIGHTNESS - VIBRANCY_RANGE * max( + min(step - 3, MIRROR_MAX - step, 1), -1 + ) + return round(255 * intensity) / 255 + + return (component(0), component(PHASE_GREEN), component(4)) diff --git a/src/opengeodeweb_viewer/vtk_protocol.py b/src/opengeodeweb_viewer/vtk_protocol.py index 4b6d5131..59256a97 100644 --- a/src/opengeodeweb_viewer/vtk_protocol.py +++ b/src/opengeodeweb_viewer/vtk_protocol.py @@ -43,6 +43,7 @@ class VtkPipeline: filter: vtkAlgorithm | None = None actor: vtkActor = field(default_factory=vtkActor) blockDataSets: list[vtkDataObject | None] = field(default_factory=list) + blockGeodeIds: list[str] = field(default_factory=list) class VtkTypingMixin: