diff --git a/python_sdk/docs/specifications/examples/mesh_sampling_specs.py b/python_sdk/docs/specifications/examples/mesh_sampling_specs.py new file mode 100644 index 00000000..0dd71a98 --- /dev/null +++ b/python_sdk/docs/specifications/examples/mesh_sampling_specs.py @@ -0,0 +1,7 @@ +from reality_capture.specifications.mesh_sampling import (MeshSamplingSpecificationsCreate, MeshSamplingInputs, + MeshSamplingOptions, MeshSamplingFormat) + +inputs = MeshSamplingInputs(meshes=["dbad181b-5079-4224-8561-96ad218bcfc9", "ea881e3e-b0e3-4b23-bc3f-4ee8e9acdf61"]) +options = MeshSamplingOptions(outputFormat=MeshSamplingFormat.THREE_D_TILES_GLBC) + +specs = MeshSamplingSpecificationsCreate(inputs=inputs, options=options) diff --git a/python_sdk/docs/specifications/examples/pc_conv_specs.py b/python_sdk/docs/specifications/examples/pc_conv_specs.py new file mode 100644 index 00000000..1ba954b8 --- /dev/null +++ b/python_sdk/docs/specifications/examples/pc_conv_specs.py @@ -0,0 +1,8 @@ +from reality_capture.specifications.point_cloud_conversion import (PointCloudConversionSpecificationsCreate, + PCConversionInputs, PCConversionOptions, + PCConversionFormat) + +inputs = PCConversionInputs(pointCloud="e3e12e4f-9fe0-4169-9571-41ecebd8c229") +options = PCConversionOptions(outputFormat=PCConversionFormat.LAS) + +specs = PointCloudConversionSpecificationsCreate(inputs=inputs, options=options) diff --git a/python_sdk/docs/specifications/examples/pc_optim_specs.py b/python_sdk/docs/specifications/examples/pc_optim_specs.py new file mode 100644 index 00000000..50630134 --- /dev/null +++ b/python_sdk/docs/specifications/examples/pc_optim_specs.py @@ -0,0 +1,9 @@ +from reality_capture.specifications.point_cloud_optimization import (PCOptimizationSpecificationsCreate, + PCOptimizationFormat, PCOptimizationInputs, + PCOptimizationOptions) + +inputs = PCOptimizationInputs(pointClouds=["dbad181b-5079-4224-8561-96ad218bcfc9", + "ea881e3e-b0e3-4b23-bc3f-4ee8e9acdf61"]) +options = PCOptimizationOptions(outputFormat=PCOptimizationFormat.THREE_D_TILES_GLBC) + +specs = PCOptimizationSpecificationsCreate(inputs=inputs, options=options) diff --git a/python_sdk/docs/specifications/examples/tm_optimization.py b/python_sdk/docs/specifications/examples/tm_optimization.py new file mode 100644 index 00000000..b4ad3319 --- /dev/null +++ b/python_sdk/docs/specifications/examples/tm_optimization.py @@ -0,0 +1,9 @@ +from reality_capture.specifications.tile_map_optimization import (TileMapOptimizationSpecificationsCreate, + TileMapOptimizationInputs, TileMapOptimizationOptions, + TileMapOptimizationFormat) + +inputs = TileMapOptimizationInputs(tileMaps=["dbad181b-5079-4224-8561-96ad218bcfc9", + "ea881e3e-b0e3-4b23-bc3f-4ee8e9acdf61"]) +options = TileMapOptimizationOptions(outputFormat=TileMapOptimizationFormat.XYZ_TILE_MAP, backgroundColor="#000000") + +specs = TileMapOptimizationSpecificationsCreate(inputs=inputs, options=options) diff --git a/python_sdk/docs/specifications/examples/vector_optim.py b/python_sdk/docs/specifications/examples/vector_optim.py new file mode 100644 index 00000000..27cd9a5b --- /dev/null +++ b/python_sdk/docs/specifications/examples/vector_optim.py @@ -0,0 +1,9 @@ +from reality_capture.specifications.vector_optimization import (VectorOptimizationSpecificationsCreate, + VectorOptimizationInputs, VectorOptimizationOptions, + VectorOptimizationFormat) + +inputs = VectorOptimizationInputs(vectors=["dbad181b-5079-4224-8561-96ad218bcfc9", + "ea881e3e-b0e3-4b23-bc3f-4ee8e9acdf61"]) +options = VectorOptimizationOptions(outputFormat=VectorOptimizationFormat.FEATURE_DB, featureClassDisplayName="Road") + +specs = VectorOptimizationSpecificationsCreate(inputs=inputs, options=options) diff --git a/python_sdk/docs/specifications/index.rst b/python_sdk/docs/specifications/index.rst index 3daad349..415361f9 100644 --- a/python_sdk/docs/specifications/index.rst +++ b/python_sdk/docs/specifications/index.rst @@ -34,7 +34,11 @@ Specifications regroup all the settings used to create jobs with our APIs. eval_s3d eval_sortho clearance - + pc_conversion + pc_optimization + mesh_sampling + tile_map_optimization + vector_optimization Modeling ======== @@ -61,10 +65,14 @@ Analysis * :doc:`/specifications/eval_o2d`, :doc:`/specifications/eval_o3d`, :doc:`/specifications/eval_s2d`, :doc:`/specifications/eval_s3d` and :doc:`/specifications/eval_sortho` will compare a prediction to a reference for a specific detection. * :doc:`/specifications/clearance` uses a 3D model and a footprint to compute clearance information. -.. Conversion -.. ========== +Conversion +========== -.. * :doc:`/specifications/point_cloud_conversion` will convert point clouds from one format to another. +* :doc:`/specifications/pc_conversion` will convert a single point cloud from one format to another. +* :doc:`/specifications/pc_optimization` will consolidate multiple point cloud to a given format. +* :doc:`/specifications/mesh_sampling` will sample meshes and consolidate them to a given format. +* :doc:`/specifications/tile_map_optimization` will consolidate tile maps to a given format. +* :doc:`/specifications/vector_optimization` will consolidate vector data to a given format. Utilities ========= diff --git a/python_sdk/docs/specifications/mesh_sampling.rst b/python_sdk/docs/specifications/mesh_sampling.rst new file mode 100644 index 00000000..c42dc3cc --- /dev/null +++ b/python_sdk/docs/specifications/mesh_sampling.rst @@ -0,0 +1,37 @@ +============= +Mesh sampling +============= + +The *mesh sampling* job takes one or multiple reality data meshes as an input and will sample and consolidate them into a single point cloud. + +.. contents:: Quick access + :local: + :depth: 2 + +Examples +======== + +In this example, we create a specification for submitting a mesh sampling job to 3D Tiles format: + +.. literalinclude:: examples/mesh_sampling_specs.py + :language: Python + +Classes +======= + +.. currentmodule:: reality_capture.specifications.mesh_sampling + +.. autopydantic_model:: MeshSamplingSpecificationsCreate + +.. autopydantic_model:: MeshSamplingSpecifications + +.. autopydantic_model:: MeshSamplingInputs + :model-show-json: False + +.. autopydantic_model:: MeshSamplingOptions + :model-show-json: False + +.. autoclass:: MeshSamplingFormat + :show-inheritance: + :members: + :undoc-members: diff --git a/python_sdk/docs/specifications/pc_conversion.rst b/python_sdk/docs/specifications/pc_conversion.rst new file mode 100644 index 00000000..231d5d58 --- /dev/null +++ b/python_sdk/docs/specifications/pc_conversion.rst @@ -0,0 +1,38 @@ +====================== +Point cloud conversion +====================== + +The *point cloud conversion* job takes a reality data point cloud as an input and will convert it in the selected format. +Should multiple point clouds be present in the reality data, each point cloud will be converted to a different file. + +.. contents:: Quick access + :local: + :depth: 2 + +Examples +======== + +In this example, we create a specification for submitting a point cloud conversion job to LAS format: + +.. literalinclude:: examples/pc_conv_specs.py + :language: Python + +Classes +======= + +.. currentmodule:: reality_capture.specifications.point_cloud_conversion + +.. autopydantic_model:: PointCloudConversionSpecificationsCreate + +.. autopydantic_model:: PointCloudConversionSpecifications + +.. autopydantic_model:: PCConversionInputs + :model-show-json: False + +.. autopydantic_model:: PCConversionOptions + :model-show-json: False + +.. autoclass:: PCConversionFormat + :show-inheritance: + :members: + :undoc-members: diff --git a/python_sdk/docs/specifications/pc_optimization.rst b/python_sdk/docs/specifications/pc_optimization.rst new file mode 100644 index 00000000..d8a50cca --- /dev/null +++ b/python_sdk/docs/specifications/pc_optimization.rst @@ -0,0 +1,37 @@ +======================== +Point cloud optimization +======================== + +The *point cloud optimization* job takes one or multiple reality data point cloud as an input and will consolidate them into a single point cloud. + +.. contents:: Quick access + :local: + :depth: 2 + +Examples +======== + +In this example, we create a specification for submitting a point cloud optimization job to 3D Tiles format: + +.. literalinclude:: examples/pc_optim_specs.py + :language: Python + +Classes +======= + +.. currentmodule:: reality_capture.specifications.point_cloud_optimization + +.. autopydantic_model:: PCOptimizationSpecificationsCreate + +.. autopydantic_model:: PCOptimizationSpecifications + +.. autopydantic_model:: PCOptimizationInputs + :model-show-json: False + +.. autopydantic_model:: PCOptimizationOptions + :model-show-json: False + +.. autoclass:: PCOptimizationFormat + :show-inheritance: + :members: + :undoc-members: diff --git a/python_sdk/docs/specifications/tile_map_optimization.rst b/python_sdk/docs/specifications/tile_map_optimization.rst new file mode 100644 index 00000000..e1e9e862 --- /dev/null +++ b/python_sdk/docs/specifications/tile_map_optimization.rst @@ -0,0 +1,42 @@ +===================== +Tile map optimization +===================== + +The *tile map optimization* job takes one or multiple reality data tile maps as an input and will consolidate them into a single tile map. + +.. contents:: Quick access + :local: + :depth: 2 + +Examples +======== + +In this example, we create a specification for submitting a tile map optimization job to XYZ format: + +.. literalinclude:: examples/tm_optimization.py + :language: Python + +Classes +======= + +.. currentmodule:: reality_capture.specifications.tile_map_optimization + +.. autopydantic_model:: TileMapOptimizationSpecificationsCreate + +.. autopydantic_model:: TileMapOptimizationSpecifications + +.. autopydantic_model:: TileMapOptimizationInputs + :model-show-json: False + +.. autopydantic_model:: TileMapOptimizationOptions + :model-show-json: False + +.. autoclass:: TileMapOptimizationFormat + :show-inheritance: + :members: + :undoc-members: + +.. autoclass:: TileMapImageFormat + :show-inheritance: + :members: + :undoc-members: diff --git a/python_sdk/docs/specifications/vector_optimization.rst b/python_sdk/docs/specifications/vector_optimization.rst new file mode 100644 index 00000000..a64f54ec --- /dev/null +++ b/python_sdk/docs/specifications/vector_optimization.rst @@ -0,0 +1,37 @@ +=================== +Vector optimization +=================== + +The *vector optimization* job takes one or multiple reality data vectors as an input and will consolidate them into a vector data. + +.. contents:: Quick access + :local: + :depth: 2 + +Examples +======== + +In this example, we create a specification for submitting a vector optimization job to the Bentley Feature Database: + +.. literalinclude:: examples/vector_optim.py + :language: Python + +Classes +======= + +.. currentmodule:: reality_capture.specifications.vector_optimization + +.. autopydantic_model:: VectorOptimizationSpecificationsCreate + +.. autopydantic_model:: VectorOptimizationSpecifications + +.. autopydantic_model:: VectorOptimizationInputs + :model-show-json: False + +.. autopydantic_model:: VectorOptimizationOptions + :model-show-json: False + +.. autoclass:: VectorOptimizationFormat + :show-inheritance: + :members: + :undoc-members: diff --git a/python_sdk/src/reality_capture/service/job.py b/python_sdk/src/reality_capture/service/job.py index 2c095918..9b32f910 100644 --- a/python_sdk/src/reality_capture/service/job.py +++ b/python_sdk/src/reality_capture/service/job.py @@ -26,8 +26,17 @@ TouchUpExportSpecifications, TouchUpExportSpecificationsCreate) from reality_capture.specifications.water_constraints import (WaterConstraintsSpecifications, WaterConstraintsSpecificationsCreate) -"""from reality_capture.specifications.point_cloud_conversion import (PointCloudConversionSpecificationsCreate, - PointCloudConversionSpecifications)""" +from reality_capture.specifications.point_cloud_conversion import (PointCloudConversionSpecificationsCreate, + PointCloudConversionSpecifications) +from reality_capture.specifications.point_cloud_optimization import (PCOptimizationSpecificationsCreate, + PCOptimizationSpecifications) +from reality_capture.specifications.mesh_sampling import MeshSamplingSpecificationsCreate, MeshSamplingSpecifications +from reality_capture.specifications.tile_map_optimization import (TileMapOptimizationSpecificationsCreate, + TileMapOptimizationSpecifications) +from reality_capture.specifications.vector_optimization import (VectorOptimizationSpecificationsCreate, + VectorOptimizationSpecifications) +# from reality_capture.specifications.cs_tiler import (ContextSceneTilerSpecifications, +# ContextSceneTilerSpecificationsCreate) from reality_capture.specifications.gaussian_splats import (GaussianSplatsSpecificationsCreate, GaussianSplatsSpecifications) @@ -64,12 +73,18 @@ class JobType(Enum): TOUCH_UP_EXPORT = "TouchUpExport" WATER_CONSTRAINTS = "WaterConstraints" CLEARANCE_CALCULATION = "ClearanceCalculation" - # POINT_CLOUD_CONVERSION = "PointCloudConversion" + POINT_CLOUD_CONVERSION = "PointCloudConversion" + POINT_CLOUD_OPTIMIZATION = "PointCloudOptimization" + MESH_SAMPLING = "MeshSampling" + TILE_MAP_OPTIMIZATION = "TileMapOptimization" + VECTOR_OPTIMIZATION = "VectorOptimization" + # CONTEXTSCENE_TILER = "ContextSceneTiler" + class Service(Enum): MODELING = "Modeling" ANALYSIS = "Analysis" - # CONVERSION = "Conversion" + CONVERSION = "Conversion" def _get_appropriate_service(jt: JobType): @@ -81,7 +96,9 @@ def _get_appropriate_service(jt: JobType): JobType.CHANGE_DETECTION, JobType.EVAL_O2D, JobType.EVAL_O3D, JobType.EVAL_S2D, JobType.EVAL_S3D, JobType.EVAL_SORTHO, JobType.CLEARANCE_CALCULATION]: return Service.ANALYSIS - # return Service.CONVERSION + if jt in [JobType.POINT_CLOUD_CONVERSION, JobType.POINT_CLOUD_OPTIMIZATION, JobType.MESH_SAMPLING, + JobType.TILE_MAP_OPTIMIZATION, JobType.VECTOR_OPTIMIZATION]: # JobType.CONTEXTSCENE_TILER + return Service.CONVERSION raise NotImplementedError("Other services not yet implemented") @@ -98,7 +115,6 @@ class JobState(Enum): class JobCreate(BaseModel): name: Optional[str] = Field(None, description="Displayable job name.", min_length=3) type: JobType = Field(description="Type of job.") - # TODO : PointCloudConversionSpecificationsCreate, specifications: Union[CalibrationSpecificationsCreate, ChangeDetectionSpecificationsCreate, ConstraintsSpecificationsCreate, EvalO2DSpecificationsCreate, EvalO3DSpecificationsCreate, @@ -110,6 +126,9 @@ class JobCreate(BaseModel): Segmentation3DSpecificationsCreate, SegmentationOrthophotoSpecificationsCreate, TilingSpecificationsCreate, TouchUpExportSpecificationsCreate, TouchUpImportSpecificationsCreate, WaterConstraintsSpecificationsCreate, + PointCloudConversionSpecificationsCreate, PCOptimizationSpecificationsCreate, + MeshSamplingSpecificationsCreate, TileMapOptimizationSpecificationsCreate, + VectorOptimizationSpecificationsCreate, # ContextSceneTilerSpecificationsCreate, ClearanceSpecificationsCreate] = ( Field(description="Specifications aligned with the job type.")) itwin_id: str = Field(description="iTwin ID, used by the service for finding " @@ -155,6 +174,9 @@ class Job(BaseModel): Segmentation3DSpecifications, SegmentationOrthophotoSpecifications, TilingSpecifications, TouchUpExportSpecifications, TouchUpImportSpecifications, WaterConstraintsSpecifications, + PointCloudConversionSpecifications, PCOptimizationSpecifications, + MeshSamplingSpecifications, TileMapOptimizationSpecifications, + VectorOptimizationSpecifications, # ContextSceneTilerSpecifications, ClearanceSpecifications] = ( Field(description="Specifications aligned with the job type.")) @@ -209,6 +231,18 @@ def set_specification_validation_model(cls, raw_dict: dict[str, Any], validation specifications = WaterConstraintsSpecifications(**raw_dict) elif job_type == JobType.CLEARANCE_CALCULATION: specifications = ClearanceSpecifications(**raw_dict) + elif job_type == JobType.POINT_CLOUD_CONVERSION: + specifications = PointCloudConversionSpecifications(**raw_dict) + elif job_type == JobType.POINT_CLOUD_OPTIMIZATION: + specifications = PCOptimizationSpecifications(**raw_dict) + elif job_type == JobType.MESH_SAMPLING: + specifications = MeshSamplingSpecifications(**raw_dict) + elif job_type == JobType.TILE_MAP_OPTIMIZATION: + specifications = TileMapOptimizationSpecifications(**raw_dict) + elif job_type == JobType.VECTOR_OPTIMIZATION: + specifications = VectorOptimizationSpecifications(**raw_dict) + # elif job_type == JobType.CONTEXTSCENE_TILER: + # specifications = ContextSceneTilerSpecifications(**raw_dict) else: raise ValueError(f"Unsupported job type: {job_type}") diff --git a/python_sdk/src/reality_capture/service/service.py b/python_sdk/src/reality_capture/service/service.py index d01dc23a..257fc564 100644 --- a/python_sdk/src/reality_capture/service/service.py +++ b/python_sdk/src/reality_capture/service/service.py @@ -80,11 +80,16 @@ def _get_modeling_url(self) -> str: def _get_analysis_url(self) -> str: return self._service_url + "reality-analysis/" + def _get_conversion_url(self) -> str: + return self._service_url + "reality-conversion/" + def _get_correct_url(self, service: Service) -> str: if service == Service.MODELING: return self._get_modeling_url() if service == Service.ANALYSIS: return self._get_analysis_url() + if service == Service.CONVERSION: + return self._get_conversion_url() raise NotImplementedError("Other services not yet implemented") @staticmethod diff --git a/python_sdk/src/reality_capture/specifications/cs_tiler.py b/python_sdk/src/reality_capture/specifications/cs_tiler.py new file mode 100644 index 00000000..f5d27415 --- /dev/null +++ b/python_sdk/src/reality_capture/specifications/cs_tiler.py @@ -0,0 +1,28 @@ +# from typing import Optional +# from pydantic import BaseModel, Field +# from enum import Enum +# +# +# class CSTilerInputs(BaseModel): +# scene: str = Field(description="ContextScene reality data id to tile") +# +# class CSObject(Enum): +# CAMERAS = "Cameras" +# TIE_POINTS = "TiePoints" +# ANNOTATIONS = "Annotations" +# +# +# class CSTilerOptions(BaseModel): +# object_to_tile: Optional[CSObject] = Field(None, description="Object to tile inside the ContextScene.", +# alias="objectToTile") +# +# +# class ContextSceneTilerSpecificationsCreate(BaseModel): +# inputs: CSTilerInputs = Field(description="Inputs") +# options: Optional[CSTilerOptions] = Field(None, description="Options") +# +# +# class ContextSceneTilerSpecifications(BaseModel): +# inputs: CSTilerInputs = Field(description="Inputs") +# output: str = Field(description="Reality Data id of tiled ContextScene") +# options: Optional[CSTilerOptions] = Field(None, description="Options") diff --git a/python_sdk/src/reality_capture/specifications/mesh_sampling.py b/python_sdk/src/reality_capture/specifications/mesh_sampling.py new file mode 100644 index 00000000..859979f0 --- /dev/null +++ b/python_sdk/src/reality_capture/specifications/mesh_sampling.py @@ -0,0 +1,36 @@ +from pydantic import BaseModel, Field +from typing import Optional +from enum import Enum + + +class MeshSamplingInputs(BaseModel): + meshes: list[str] = Field(description="Reality data Ids of meshes to sample") + + +class MeshSamplingFormat(Enum): + OPC = "OPC" + THREE_D_TILES_PNTS = "3DTilesPnts" + THREE_D_TILES_GLBC = "3DTilesGlbc" + LAS = "LAS" + LAZ = "LAZ" + E57 = "E57" + POD = "POD" + + +class MeshSamplingOptions(BaseModel): + output_format: Optional[MeshSamplingFormat] = Field(None, description="Output format for the conversion.", + alias="outputFormat") + input_crs: Optional[str] = Field(None, description="CRS for the input data", alias="inputCrs") + output_crs: Optional[str] = Field(None, description="CRS for the output data", alias="outputCrs") + sampling: Optional[float] = Field(None, description="Sampling value in meter") + + +class MeshSamplingSpecificationsCreate(BaseModel): + inputs: MeshSamplingInputs = Field(description="Inputs") + options: Optional[MeshSamplingOptions] = Field(None, description="Options") + + +class MeshSamplingSpecifications(BaseModel): + inputs: MeshSamplingInputs = Field(description="Inputs") + output: str = Field(description="Reality Data id of the sampled point cloud") + options: Optional[MeshSamplingOptions] = Field(None, description="Options") diff --git a/python_sdk/src/reality_capture/specifications/point_cloud_conversion.py b/python_sdk/src/reality_capture/specifications/point_cloud_conversion.py index 33baaf00..b544111e 100644 --- a/python_sdk/src/reality_capture/specifications/point_cloud_conversion.py +++ b/python_sdk/src/reality_capture/specifications/point_cloud_conversion.py @@ -1,45 +1,35 @@ -# from pydantic import BaseModel, Field, model_validator -# from typing import Optional -# from enum import Enum -# -# -# class PCConversionInputs(BaseModel): -# point_clouds: list[str] = Field(description="Reality data Ids of point clouds to convert", alias="pointClouds") -# -# -# class PCConversionOutputsCreate(Enum): -# OPC = "opc" -# PNTS = "pnts" -# GLB = "glb" -# GLBC = "glbc" -# -# -# class PCConversionOutputs(BaseModel): -# opc: Optional[str] = Field(None, description="Id of the conversion as Orbit Point Cloud.") -# pnts: Optional[str] = Field(None, description="Id of the conversion as Cesium 3D Point Cloud") -# -# @model_validator(mode='after') -# def only_one_field(cls, model): -# """ -# Checks that one and only one field is provided. -# """ -# if (model.opc and model.pnts) or (not model.opc and not model.pnts): -# raise ValueError("Exactly one of 'opc' or 'pnts' must be provided.") -# return model -# -# -# class PCConversionOptions(BaseModel): -# merge: Optional[bool] = Field(None, description="If true, when possible and if relevant, all the files in the " -# "input reality data will be merged as one file.") -# -# -# class PointCloudConversionSpecificationsCreate(BaseModel): -# inputs: PCConversionInputs = Field(description="Inputs") -# outputs: PCConversionOutputsCreate = Field(description="Outputs") -# options: Optional[PCConversionOptions] = Field(None, description="Options") -# -# -# class PointCloudConversionSpecifications(BaseModel): -# inputs: PCConversionInputs = Field(description="Inputs") -# outputs: PCConversionOutputs = Field(description="Outputs") -# options: Optional[PCConversionOptions] = Field(None, description="Options") +from pydantic import BaseModel, Field +from typing import Optional +from enum import Enum + + +class PCConversionInputs(BaseModel): + point_cloud: str = Field(description="Reality data Id of point cloud to convert", alias="pointCloud") + + +class PCConversionFormat(Enum): + OPC = "OPC" + THREE_D_TILES_PNTS = "3DTilesPnts" + THREE_D_TILES_GLBC = "3DTilesGlbc" + LAS = "LAS" + LAZ = "LAZ" + E57 = "E57" + POD = "POD" + + +class PCConversionOptions(BaseModel): + output_format: Optional[PCConversionFormat] = Field(None, description="Output format for the conversion.", + alias="outputFormat") + input_crs: Optional[str] = Field(None, description="CRS for the input data", alias="inputCrs") + output_crs: Optional[str] = Field(None, description="CRS for the output data", alias="outputCrs") + + +class PointCloudConversionSpecificationsCreate(BaseModel): + inputs: PCConversionInputs = Field(description="Inputs") + options: Optional[PCConversionOptions] = Field(None, description="Options") + + +class PointCloudConversionSpecifications(BaseModel): + inputs: PCConversionInputs = Field(description="Inputs") + output: str = Field(description="Reality Data id of the converted point cloud") + options: Optional[PCConversionOptions] = Field(None, description="Options") diff --git a/python_sdk/src/reality_capture/specifications/point_cloud_optimization.py b/python_sdk/src/reality_capture/specifications/point_cloud_optimization.py new file mode 100644 index 00000000..8de49273 --- /dev/null +++ b/python_sdk/src/reality_capture/specifications/point_cloud_optimization.py @@ -0,0 +1,35 @@ +from pydantic import BaseModel, Field +from typing import Optional +from enum import Enum + + +class PCOptimizationInputs(BaseModel): + point_clouds: list[str] = Field(description="Reality data Ids of point cloud(s) to convert", alias="pointClouds") + + +class PCOptimizationFormat(Enum): + OPC = "OPC" + THREE_D_TILES_PNTS = "3DTilesPnts" + THREE_D_TILES_GLBC = "3DTilesGlbc" + LAS = "LAS" + LAZ = "LAZ" + E57 = "E57" + POD = "POD" + + +class PCOptimizationOptions(BaseModel): + output_format: Optional[PCOptimizationFormat] = Field(None, description="Output format for the conversion.", + alias="outputFormat") + input_crs: Optional[str] = Field(None, description="CRS for the input data", alias="inputCrs") + output_crs: Optional[str] = Field(None, description="CRS for the output data", alias="outputCrs") + + +class PCOptimizationSpecificationsCreate(BaseModel): + inputs: PCOptimizationInputs = Field(description="Inputs") + options: Optional[PCOptimizationOptions] = Field(None, description="Options") + + +class PCOptimizationSpecifications(BaseModel): + inputs: PCOptimizationInputs = Field(description="Inputs") + output: str = Field(description="Reality Data id of the converted point cloud") + options: Optional[PCOptimizationOptions] = Field(None, description="Options") diff --git a/python_sdk/src/reality_capture/specifications/tile_map_optimization.py b/python_sdk/src/reality_capture/specifications/tile_map_optimization.py new file mode 100644 index 00000000..6a8c7094 --- /dev/null +++ b/python_sdk/src/reality_capture/specifications/tile_map_optimization.py @@ -0,0 +1,42 @@ +from pydantic import BaseModel, Field, constr +from typing import Optional +from enum import Enum + + +class TileMapOptimizationInputs(BaseModel): + tile_maps: list[str] = Field(description="Reality data Ids of tile maps to convert", alias="tileMaps") + + +class TileMapOptimizationFormat(Enum): + XYZ_TILE_MAP = "XYZTileMap" + + +class TileMapImageFormat(Enum): + JPG = "JPG" + PNG = "PNG" + + +class TileMapOptimizationOptions(BaseModel): + output_format: Optional[TileMapOptimizationFormat] = Field(None, description="Output format for the conversion.", + alias="outputFormat") + input_crs: Optional[str] = Field(None, description="CRS for the input data", alias="inputCrs") + output_crs: Optional[str] = Field(None, description="CRS for the output data", alias="outputCrs") + top_level: Optional[int] = Field(None, description="Top level of the tile map to generate.", alias="topLevel") + bottom_level: Optional[int] = Field(None, description="Bottom level of the tile map to generate.", + alias="bottomLevel") + image_format: Optional[TileMapImageFormat] = Field(None, alias="imageFormat", description="Image format") + jpg_quality: Optional[int] = Field(None, description="Quality of JPG tiles to generate", alias="jpgQuality", + ge=10, le=99) + background_color: Optional[constr(pattern=r'^#[a-fA-F0-9]{6}$')] = ( + Field(None, description="Background color to use for tiles", alias="backgroundColor")) + + +class TileMapOptimizationSpecificationsCreate(BaseModel): + inputs: TileMapOptimizationInputs = Field(description="Inputs") + options: Optional[TileMapOptimizationOptions] = Field(None, description="Options") + + +class TileMapOptimizationSpecifications(BaseModel): + inputs: TileMapOptimizationInputs = Field(description="Inputs") + output: str = Field(description="Reality Data id of the tile map") + options: Optional[TileMapOptimizationOptions] = Field(None, description="Options") diff --git a/python_sdk/src/reality_capture/specifications/vector_optimization.py b/python_sdk/src/reality_capture/specifications/vector_optimization.py new file mode 100644 index 00000000..b6cd3d62 --- /dev/null +++ b/python_sdk/src/reality_capture/specifications/vector_optimization.py @@ -0,0 +1,32 @@ +from pydantic import BaseModel, Field +from typing import Optional +from enum import Enum + + +class VectorOptimizationInputs(BaseModel): + vectors: list[str] = Field(description="Reality data Ids of vectors to consolidate") + + +class VectorOptimizationFormat(Enum): + GEO_JSON = "GeoJSON" + FEATURE_DB = "FeatureDB" + + +class VectorOptimizationOptions(BaseModel): + output_format: Optional[VectorOptimizationFormat] = Field(None, description="Output format for the conversion.", + alias="outputFormat") + input_crs: Optional[str] = Field(None, description="CRS for the input data", alias="inputCrs") + output_crs: Optional[str] = Field(None, description="CRS for the output data", alias="outputCrs") + feature_class_display_name: Optional[str] = Field(None, description="Display class name", + alias="featureClassDisplayName") + + +class VectorOptimizationSpecificationsCreate(BaseModel): + inputs: VectorOptimizationInputs = Field(description="Inputs") + options: Optional[VectorOptimizationOptions] = Field(None, description="Options") + + +class VectorOptimizationSpecifications(BaseModel): + inputs: VectorOptimizationInputs = Field(description="Inputs") + output: str = Field(description="Reality Data id of the vector data or Feature DB index (fdb:)") + options: Optional[VectorOptimizationOptions] = Field(None, description="Options") diff --git a/python_sdk/tests/test_job.py b/python_sdk/tests/test_job.py index d2bb96a0..4fe222ea 100644 --- a/python_sdk/tests/test_job.py +++ b/python_sdk/tests/test_job.py @@ -20,10 +20,10 @@ def test_appropriate_service_job(self): j = Job(id="id", type=JobType.EVAL_O2D, iTwinId="itwin", state=JobState.SUCCESS, executionInfo=cdt, userId="claude@example.org", specifications=specs) assert j.get_appropriate_service() == Service.ANALYSIS - # pc_conversion_specs = {"inputs": {"pointClouds": ["point_cloud"]}, "outputs": {"opc": "opc"}} - # j = Job(id="id", type=JobType.POINT_CLOUD_CONVERSION, iTwinId="itwin", state=JobState.SUCCESS, - # executionInfo=cdt, userId="claude@example.org", specifications=pc_conversion_specs, bucketId="bucket") - # assert j.get_appropriate_service() == Service.CONVERSION + pc_conversion_specs = {"inputs": {"pointCloud": "point_cloud"}, "output": "pcId"} + j = Job(id="id", type=JobType.POINT_CLOUD_CONVERSION, iTwinId="itwin", state=JobState.SUCCESS, + executionInfo=cdt, userId="claude@example.org", specifications=pc_conversion_specs) + assert j.get_appropriate_service() == Service.CONVERSION def test_appropriate_service_job_create(self): tiling_specs = {"inputs": {"scene": "scene"}, "outputs": [TilingOutputsCreate.MODELING_REFERENCE]} @@ -33,10 +33,10 @@ def test_appropriate_service_job_create(self): "outputs": [Segmentation3DOutputsCreate.SEGMENTATION3D, Segmentation3DOutputsCreate.SEGMENTED_POINT_CLOUD]} j = JobCreate(**{"type": JobType.SEGMENTATION_3D, "iTwinId": "itwin", "specifications": eg_specs}) - # assert j.get_appropriate_service() == Service.ANALYSIS - # pc_conversion_specs = {"inputs": {"pointClouds": ["point_cloud"]}, "outputs": PCConversionOutputsCreate.OPC} - # j = JobCreate(**{"type": JobType.POINT_CLOUD_CONVERSION, "iTwinId": "itwin", "specifications": pc_conversion_specs}) - # assert j.get_appropriate_service() == Service.CONVERSION + assert j.get_appropriate_service() == Service.ANALYSIS + pc_conversion_specs = {"inputs": {"pointCloud": "point_cloud"}} + j = JobCreate(**{"type": JobType.POINT_CLOUD_CONVERSION, "iTwinId": "itwin", "specifications": pc_conversion_specs}) + assert j.get_appropriate_service() == Service.CONVERSION def test_get_appropriate_service_unsupported_job_type(self): unsupported = MagicMock(name="UnsupportedJobType") diff --git a/python_sdk/tests/test_job_validator.py b/python_sdk/tests/test_job_validator.py index 25fe1323..be649967 100644 --- a/python_sdk/tests/test_job_validator.py +++ b/python_sdk/tests/test_job_validator.py @@ -19,6 +19,12 @@ from reality_capture.specifications.tiling import TilingSpecifications from reality_capture.specifications.touchup import TouchUpImportSpecifications, TouchUpExportSpecifications from reality_capture.specifications.water_constraints import WaterConstraintsSpecifications +from reality_capture.specifications.point_cloud_conversion import PointCloudConversionSpecifications +from reality_capture.specifications.point_cloud_optimization import PCOptimizationSpecifications +from reality_capture.specifications.mesh_sampling import MeshSamplingSpecifications +from reality_capture.specifications.tile_map_optimization import TileMapOptimizationSpecifications +from reality_capture.specifications.vector_optimization import VectorOptimizationSpecifications +#from reality_capture.specifications.cs_tiler import ContextSceneTilerSpecifications from reality_capture.specifications.clearance import ClearanceSpecifications import pytest from unittest.mock import patch, MagicMock @@ -371,3 +377,74 @@ def test_validation_unsupported_job_type_raises(self): ) assert "Unsupported job type" in str(exc_info.value) + def test_validation_pc_conv(self): + j = self.j_base.copy() + j["type"] = "PointCloudConversion" + j["specifications"] = { + "inputs": { + "pointCloud": "mfid" + }, + "output": "outId" + } + job = Job(**j) + assert isinstance(job.specifications, PointCloudConversionSpecifications) + + def test_validation_pc_optim(self): + j = self.j_base.copy() + j["type"] = "PointCloudOptimization" + j["specifications"] = { + "inputs": { + "pointClouds": ["mfid"] + }, + "output": "outId" + } + job = Job(**j) + assert isinstance(job.specifications, PCOptimizationSpecifications) + + def test_validation_mesh_sampling(self): + j = self.j_base.copy() + j["type"] = "MeshSampling" + j["specifications"] = { + "inputs": { + "meshes": ["mId"] + }, + "output": "outId" + } + job = Job(**j) + assert isinstance(job.specifications, MeshSamplingSpecifications) + + def test_validation_tm_optim(self): + j = self.j_base.copy() + j["type"] = "TileMapOptimization" + j["specifications"] = { + "inputs": { + "tileMaps": ["mId"] + }, + "output": "outId" + } + job = Job(**j) + assert isinstance(job.specifications, TileMapOptimizationSpecifications) + + def test_validation_vector_optim(self): + j = self.j_base.copy() + j["type"] = "VectorOptimization" + j["specifications"] = { + "inputs": { + "vectors": ["vId"] + }, + "output": "outId" + } + job = Job(**j) + assert isinstance(job.specifications, VectorOptimizationSpecifications) + + # def test_validation_cs_tiler(self): + # j = self.j_base.copy() + # j["type"] = "ContextSceneTiler" + # j["specifications"] = { + # "inputs": { + # "scene": "vId" + # }, + # "output": "outId" + # } + # job = Job(**j) + # assert isinstance(job.specifications, ContextSceneTilerSpecifications) diff --git a/python_sdk/tests/test_service_base.py b/python_sdk/tests/test_service_base.py index ef80b551..2cbbf9fe 100644 --- a/python_sdk/tests/test_service_base.py +++ b/python_sdk/tests/test_service_base.py @@ -22,6 +22,8 @@ def get_token() -> str: assert r.is_error() r = rcs_dev.get_job("fake_job_dev", Service.MODELING) assert r.is_error() + r = rcs_dev.get_job("fake_job_dev", Service.CONVERSION) + assert r.is_error() def test_get_correct_url_unsupported_service_raises(self): rcs = RealityCaptureService(None) diff --git a/typescript/examples/src/example_modeling.ts b/typescript/examples/src/example_modeling.ts index e3c99fcd..a1f5a768 100644 --- a/typescript/examples/src/example_modeling.ts +++ b/typescript/examples/src/example_modeling.ts @@ -1,128 +1,284 @@ /*--------------------------------------------------------------------------------------------- -* Copyright (c) Bentley Systems, Incorporated. All rights reserved. -* See LICENSE.md in the project root for license terms and full copyright notice. -*--------------------------------------------------------------------------------------------*/ + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ import * as dotenv from "dotenv"; import { ServiceAuthorizationClient } from "@itwin/service-authorization"; import { - CalibrationInputs, CalibrationSpecificationsCreate, CalibrationOptions, CalibrationOutputs, - CalibrationOutputsCreate, FillImagePropertiesInputs, FillImagePropertiesSpecificationsCreate, - FillImagePropertiesOutputsCreate, FillImagePropertiesOptions, FillImagePropertiesOutputs, - RealityCaptureService, ReconstructionSpecificationsCreate, ReconstructionInputs, ReconstructionOutputsCreate, - ReconstructionOutputs, ExportCreate, Format, OptionsLAS, SamplingStrategy, TilingOptions, GeometricPrecision, - JobCreate, JobType, JobState, Progress, getAppropriateService, RealityDataHandler, - Options3DTiles, BucketDataHandler, AdjustmentConstraints, - LODScope + CalibrationInputs, + CalibrationSpecificationsCreate, + CalibrationOptions, + CalibrationOutputs, + CalibrationSpecifications, + CalibrationOutputsCreate, + FillImagePropertiesInputs, + FillImagePropertiesSpecificationsCreate, + FillImagePropertiesOutputsCreate, + FillImagePropertiesOptions, + FillImagePropertiesSpecifications, + RealityCaptureService, + ReconstructionSpecificationsCreate, + ReconstructionInputs, + ReconstructionOutputsCreate, + ReconstructionOutputs, + ExportCreate, + Format, + OptionsLAS, + SamplingStrategy, + TilingOptions, + GeometricPrecision, + JobCreate, + JobType, + JobState, + Progress, + getAppropriateService, + RealityDataHandler, + Options3DTiles, + BucketDataHandler, + AdjustmentConstraints, + LODScope, + ReconstructionSpecifications, } from "@itwin/reality-capture"; -import { RealityDataClientOptions, RealityDataAccessClient, ITwinRealityData } from "@itwin/reality-data-client"; +import { + RealityDataClientOptions, + RealityDataAccessClient, + ITwinRealityData, +} from "@itwin/reality-data-client"; import path from "path"; +export async function sleep(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} -export async function sleep(ms: number) { return new Promise(resolve => setTimeout(resolve, ms)); } - -async function monitorJob(realityCaptureService: RealityCaptureService, jobId: string) { +async function monitorJob( + realityCaptureService: RealityCaptureService, + jobId: string, +) { // Monitor job progress every 10s let progress: Progress = { state: JobState.QUEUED, percentage: 0 }; - while (progress.state !== JobState.CANCELLED && progress.state !== JobState.FAILED && progress.state !== JobState.SUCCESS) { - const response = await realityCaptureService.getJobProgress(jobId, getAppropriateService(JobType.RECONSTRUCTION)); + while ( + progress.state !== JobState.CANCELLED && + progress.state !== JobState.FAILED && + progress.state !== JobState.SUCCESS + ) { + const response = await realityCaptureService.getJobProgress( + jobId, + getAppropriateService(JobType.RECONSTRUCTION), + ); if (response.isError()) { console.log("Can't get job progress"); } progress = response.value!; console.log("Progress : ", progress.percentage); await sleep(10000); - if (progress.state === JobState.CANCELLED || progress.state === JobState.FAILED) { + if ( + progress.state === JobState.CANCELLED || + progress.state === JobState.FAILED + ) { console.log("Job not completed"); - } - else if (progress.state === JobState.SUCCESS) { + } else if (progress.state === JobState.SUCCESS) { console.log("Job completed"); } } } -async function runFillImageProperties(realityCaptureService: RealityCaptureService, imagesRealityData: ITwinRealityData, fipJobName: string, iTwinId: string): Promise { +async function runFillImageProperties( + realityCaptureService: RealityCaptureService, + imagesRealityData: ITwinRealityData, + fipJobName: string, + iTwinId: string, +): Promise { // Submit FillImageProperties job to get a context scene from the images - let fipInputs: FillImagePropertiesInputs = { imageCollections: [imagesRealityData.id] }; + let fipInputs: FillImagePropertiesInputs = { + imageCollections: [imagesRealityData.id], + }; let fipOutputs = [FillImagePropertiesOutputsCreate.SCENE]; let fipOptions: FillImagePropertiesOptions = {}; - let fipSpecs: FillImagePropertiesSpecificationsCreate = { inputs: fipInputs, outputs: fipOutputs, options: fipOptions }; - let fipJobToSubmit: JobCreate = { name: fipJobName, specifications: fipSpecs, type: JobType.FILL_IMAGE_PROPERTIES, iTwinId: iTwinId }; - const submitFipResponse = await realityCaptureService.submitJob(fipJobToSubmit); + let fipSpecs: FillImagePropertiesSpecificationsCreate = { + inputs: fipInputs, + outputs: fipOutputs, + options: fipOptions, + }; + let fipJobToSubmit: JobCreate = { + name: fipJobName, + specifications: fipSpecs, + type: JobType.FILL_IMAGE_PROPERTIES, + iTwinId: iTwinId, + }; + const submitFipResponse = + await realityCaptureService.submitJob(fipJobToSubmit); if (submitFipResponse.isError()) { - throw new Error("Failed to submit FillImageProperties job : " + submitFipResponse.error!.error.message); + throw new Error( + "Failed to submit FillImageProperties job : " + + submitFipResponse.error!.error.message, + ); } const fipJobId = submitFipResponse.value!.id; console.log("FillImageProperties job submitted"); await monitorJob(realityCaptureService, fipJobId); - const fipPropertiesResponse = await realityCaptureService.getJob(fipJobId, getAppropriateService(JobType.FILL_IMAGE_PROPERTIES)); + const fipPropertiesResponse = await realityCaptureService.getJob( + fipJobId, + getAppropriateService(JobType.FILL_IMAGE_PROPERTIES), + ); if (fipPropertiesResponse.isError()) { - throw new Error("Failed to retrieve FillImageProperties job properties : " + fipPropertiesResponse.error!.error.message); + throw new Error( + "Failed to retrieve FillImageProperties job properties : " + + fipPropertiesResponse.error!.error.message, + ); } - return (fipPropertiesResponse.value!.specifications.outputs as FillImagePropertiesOutputs).scene; + return ( + fipPropertiesResponse.value! + .specifications as FillImagePropertiesSpecifications + ).outputs.scene; } -async function runCalibration(realityCaptureService: RealityCaptureService, bucketDataHandler: BucketDataHandler, fipOutputContextScene: string, calibJobName: string, outputPath: string, iTwinId: string, calibOptions: CalibrationOptions): Promise { +async function runCalibration( + realityCaptureService: RealityCaptureService, + bucketDataHandler: BucketDataHandler, + fipOutputContextScene: string, + calibJobName: string, + outputPath: string, + iTwinId: string, + calibOptions: CalibrationOptions, +): Promise { // Submit Calibration job to get an oriented context scene let calibInputs: CalibrationInputs = { scene: fipOutputContextScene }; - let calibOutputs = [CalibrationOutputsCreate.SCENE, CalibrationOutputsCreate.REPORT]; - let calibSpecs: CalibrationSpecificationsCreate = { inputs: calibInputs, outputs: calibOutputs, options: calibOptions }; - let calibJobToSubmit: JobCreate = { name: calibJobName, specifications: calibSpecs, type: JobType.CALIBRATION, iTwinId: iTwinId }; - const submitCalibResponse = await realityCaptureService.submitJob(calibJobToSubmit); + let calibOutputs = [ + CalibrationOutputsCreate.SCENE, + CalibrationOutputsCreate.REPORT, + ]; + let calibSpecs: CalibrationSpecificationsCreate = { + inputs: calibInputs, + outputs: calibOutputs, + options: calibOptions, + }; + let calibJobToSubmit: JobCreate = { + name: calibJobName, + specifications: calibSpecs, + type: JobType.CALIBRATION, + iTwinId: iTwinId, + }; + const submitCalibResponse = + await realityCaptureService.submitJob(calibJobToSubmit); if (submitCalibResponse.isError()) { - throw new Error("Failed to submit Calibration job : " + submitCalibResponse.error!.error.message); + throw new Error( + "Failed to submit Calibration job : " + + submitCalibResponse.error!.error.message, + ); } const calibJobId = submitCalibResponse.value!.id; console.log("Calibration job submitted"); await monitorJob(realityCaptureService, calibJobId); - const calibPropertiesResponse = await realityCaptureService.getJob(calibJobId, getAppropriateService(JobType.CALIBRATION)); + const calibPropertiesResponse = await realityCaptureService.getJob( + calibJobId, + getAppropriateService(JobType.CALIBRATION), + ); if (calibPropertiesResponse.isError()) { - throw new Error("Failed to retrieve Calibration job properties : " + calibPropertiesResponse.error!.error.message); + throw new Error( + "Failed to retrieve Calibration job properties : " + + calibPropertiesResponse.error!.error.message, + ); } - const outputs = calibPropertiesResponse.value!.specifications.outputs as CalibrationOutputs; + const outputs = ( + calibPropertiesResponse.value!.specifications as CalibrationSpecifications + ).outputs; const chars = outputs.report!.split(":"); // Remove 'bkt:' from the report bucket path - const calibDownloadResponse = await bucketDataHandler.downloadData(iTwinId, path.join(outputPath, "Report"), chars[1]); + const calibDownloadResponse = await bucketDataHandler.downloadData( + iTwinId, + path.join(outputPath, "Report"), + chars[1], + ); if (calibDownloadResponse.isError()) { - throw new Error("Failed to download report " + calibDownloadResponse.error!.error.message); + throw new Error( + "Failed to download report " + calibDownloadResponse.error!.error.message, + ); } - return (calibPropertiesResponse.value!.specifications.outputs as CalibrationOutputs).scene; + return ( + calibPropertiesResponse.value!.specifications as CalibrationSpecifications + ).outputs.scene; } -async function runReconstruction(realityCaptureService: RealityCaptureService, realityDataHandler: RealityDataHandler, calibOutputContextScene: string, reconsJobName: string, outputPath: string, iTwinId: string, - lasOptions: OptionsLAS, tiles3dOptions: Options3DTiles) { +async function runReconstruction( + realityCaptureService: RealityCaptureService, + realityDataHandler: RealityDataHandler, + calibOutputContextScene: string, + reconsJobName: string, + outputPath: string, + iTwinId: string, + lasOptions: OptionsLAS, + tiles3dOptions: Options3DTiles, +) { // Submit Reconstruction job to generate the LAS let reconsInputs: ReconstructionInputs = { scene: calibOutputContextScene }; let exportLas: ExportCreate = { format: Format.LAS, options: lasOptions }; - let export3dTiles: ExportCreate = { format: Format.THREED_TILES, options: tiles3dOptions }; - let reconsOutputs: ReconstructionOutputsCreate = { exports: [exportLas, export3dTiles] }; - let tilingOptions: TilingOptions = { geometricPrecision: GeometricPrecision.EXTRA }; - let reconsSpecs: ReconstructionSpecificationsCreate = { inputs: reconsInputs, outputs: reconsOutputs, options: tilingOptions }; - let reconsJobToSubmit: JobCreate = { name: reconsJobName, specifications: reconsSpecs, type: JobType.RECONSTRUCTION, iTwinId: iTwinId }; - const submitReconsResponse = await realityCaptureService.submitJob(reconsJobToSubmit); + let export3dTiles: ExportCreate = { + format: Format.THREED_TILES, + options: tiles3dOptions, + }; + let reconsOutputs: ReconstructionOutputsCreate = { + exports: [exportLas, export3dTiles], + }; + let tilingOptions: TilingOptions = { + geometricPrecision: GeometricPrecision.EXTRA, + }; + let reconsSpecs: ReconstructionSpecificationsCreate = { + inputs: reconsInputs, + outputs: reconsOutputs, + options: tilingOptions, + }; + let reconsJobToSubmit: JobCreate = { + name: reconsJobName, + specifications: reconsSpecs, + type: JobType.RECONSTRUCTION, + iTwinId: iTwinId, + }; + const submitReconsResponse = + await realityCaptureService.submitJob(reconsJobToSubmit); if (submitReconsResponse.isError()) { - throw new Error("Failed to submit Reconstruction job : " + submitReconsResponse.error!.error.message); + throw new Error( + "Failed to submit Reconstruction job : " + + submitReconsResponse.error!.error.message, + ); } const reconsJobId = submitReconsResponse.value!.id; console.log("Reconstruction job submitted"); await monitorJob(realityCaptureService, reconsJobId); console.log("Downloading Reconstruction LAS output"); - const reconsPropertiesResponse = await realityCaptureService.getJob(reconsJobId, getAppropriateService(JobType.RECONSTRUCTION)); + const reconsPropertiesResponse = await realityCaptureService.getJob( + reconsJobId, + getAppropriateService(JobType.RECONSTRUCTION), + ); if (reconsPropertiesResponse.isError()) { - throw new Error("Failed to retrieve Reconstruction job properties : " + reconsPropertiesResponse.error!.error.message); + throw new Error( + "Failed to retrieve Reconstruction job properties : " + + reconsPropertiesResponse.error!.error.message, + ); } - const outputs = reconsPropertiesResponse.value!.specifications.outputs as ReconstructionOutputs; - for(let reconsExport of outputs.exports!) - { - const reconsDownloadResponse = await realityDataHandler.downloadData(reconsExport.location, path.join(outputPath, reconsExport.format), "", iTwinId); + const outputs = ( + reconsPropertiesResponse.value! + .specifications as ReconstructionSpecifications + ).outputs; + for (let reconsExport of outputs.exports!) { + const reconsDownloadResponse = await realityDataHandler.downloadData( + reconsExport.location, + path.join(outputPath, reconsExport.format), + "", + iTwinId, + ); if (reconsDownloadResponse.isError()) { - throw new Error("Failed to download export " + reconsExport.format + " : " + reconsDownloadResponse.error!.error.message); + throw new Error( + "Failed to download export " + + reconsExport.format + + " : " + + reconsDownloadResponse.error!.error.message, + ); } } console.log("Successfully downloaded Reconstruction LAS output"); } - async function runModelingExample() { /** * This example shows how to submit a Reconstruction job and get a LAS from images @@ -133,23 +289,23 @@ async function runModelingExample() { const outputPath = "D:/Datasets/Helico/Results"; // Options for calibration - const calibOptions: CalibrationOptions = { - adjustmentConstraints: [AdjustmentConstraints.POSITION_METADATA] // Adjustment from photos position metadata + const calibOptions: CalibrationOptions = { + adjustmentConstraints: [AdjustmentConstraints.POSITION_METADATA], // Adjustment from photos position metadata }; // Options for Reconstruction LAS export const lasOptions: OptionsLAS = { samplingDistance: 0.5, // sampling distance (in meter). samplingStrategy: SamplingStrategy.ABSOLUTE, - crs: "EPSG:32631" // CRS used for this export - } + crs: "EPSG:32631", // CRS used for this export + }; // Options for Reconstruction 3DTiles export const tiles3dOptions: Options3DTiles = { - lodScope: LODScope.ACROSS_TILES // produce global LOD structure (e.g., a quadtree) for an entire reconstruction - } + lodScope: LODScope.ACROSS_TILES, // produce global LOD structure (e.g., a quadtree) for an entire reconstruction + }; // Optional : images reality data name - const imagesRealityDataName = "Reality Capture SDK sample images" + const imagesRealityDataName = "Reality Capture SDK sample images"; // Optional : jobs name const fipJobName = "Reality Capture SDK FillImageProperties job example"; const calibJobName = "Reality Capture SDK Calibration job example"; @@ -183,30 +339,69 @@ async function runModelingExample() { authorizationClient: authorizationClient, baseUrl: "https://api.bentley.com/reality-management/reality-data", }; - const realityDataClient = new RealityDataAccessClient(realityDataClientOptions); + const realityDataClient = new RealityDataAccessClient( + realityDataClientOptions, + ); console.log("Reality Data Client initialized"); try { console.log("Upload images in ", iTwinId); - const realityData = new ITwinRealityData(realityDataClient, undefined, iTwinId); + const realityData = new ITwinRealityData( + realityDataClient, + undefined, + iTwinId, + ); realityData.displayName = imagesRealityDataName; realityData.type = "CCImageCollection"; - const createdRealityData = await realityDataClient.createRealityData("", iTwinId, realityData); - const uploadResponse = await realityDataHandler.uploadData(createdRealityData.id, imagesPath, "", iTwinId); + const createdRealityData = await realityDataClient.createRealityData( + "", + iTwinId, + realityData, + ); + const uploadResponse = await realityDataHandler.uploadData( + createdRealityData.id, + imagesPath, + "", + iTwinId, + ); if (uploadResponse.isError()) { - throw new Error("Failed to upload reality data : " + uploadResponse.error!.error.message); + throw new Error( + "Failed to upload reality data : " + + uploadResponse.error!.error.message, + ); } console.log("Successfully uploaded images"); - const fipOutputContextScene = await runFillImageProperties(realityCaptureService, createdRealityData, fipJobName, iTwinId); + const fipOutputContextScene = await runFillImageProperties( + realityCaptureService, + createdRealityData, + fipJobName, + iTwinId, + ); - const calibOutputContextScene = await runCalibration(realityCaptureService, bucketDataHandler, fipOutputContextScene, calibJobName, outputPath, iTwinId, calibOptions); + const calibOutputContextScene = await runCalibration( + realityCaptureService, + bucketDataHandler, + fipOutputContextScene, + calibJobName, + outputPath, + iTwinId, + calibOptions, + ); - await runReconstruction(realityCaptureService, realityDataHandler, calibOutputContextScene, reconsJobName, outputPath, iTwinId, lasOptions, tiles3dOptions); - } - catch (error: any) { + await runReconstruction( + realityCaptureService, + realityDataHandler, + calibOutputContextScene, + reconsJobName, + outputPath, + iTwinId, + lasOptions, + tiles3dOptions, + ); + } catch (error: any) { console.log(error); } } -runModelingExample(); \ No newline at end of file +runModelingExample(); diff --git a/typescript/packages/reality-capture/src/index.node.ts b/typescript/packages/reality-capture/src/index.node.ts index c41ea8a8..5f899c59 100644 --- a/typescript/packages/reality-capture/src/index.node.ts +++ b/typescript/packages/reality-capture/src/index.node.ts @@ -34,3 +34,9 @@ export * from "./specifications/tiling"; export * from "./specifications/training"; export * from "./specifications/touchup"; export * from "./specifications/water_constraints"; +//export * from "./specifications/cs_tiler"; +export * from "./specifications/mesh_sampling"; +export * from "./specifications/point_cloud_conversion"; +export * from "./specifications/point_cloud_optimization"; +export * from "./specifications/tile_map_optimization"; +export * from "./specifications/vector_optimization"; diff --git a/typescript/packages/reality-capture/src/index.web.ts b/typescript/packages/reality-capture/src/index.web.ts index c539280a..8a737ab4 100644 --- a/typescript/packages/reality-capture/src/index.web.ts +++ b/typescript/packages/reality-capture/src/index.web.ts @@ -34,3 +34,9 @@ export * from "./specifications/tiling"; export * from "./specifications/training"; export * from "./specifications/touchup"; export * from "./specifications/water_constraints"; +//export * from "./specifications/cs_tiler"; +export * from "./specifications/mesh_sampling"; +export * from "./specifications/point_cloud_conversion"; +export * from "./specifications/point_cloud_optimization"; +export * from "./specifications/tile_map_optimization"; +export * from "./specifications/vector_optimization"; diff --git a/typescript/packages/reality-capture/src/service/job.ts b/typescript/packages/reality-capture/src/service/job.ts index fe2929df..874adcf1 100644 --- a/typescript/packages/reality-capture/src/service/job.ts +++ b/typescript/packages/reality-capture/src/service/job.ts @@ -1,29 +1,124 @@ import { z } from "zod"; -import { CalibrationSpecificationsCreateSchema, CalibrationSpecificationsSchema } from "../specifications/calibration"; -import { ChangeDetectionSpecificationsCreateSchema, ChangeDetectionSpecificationsSchema } from "../specifications/change_detection"; -import { ConstraintsSpecificationsCreateSchema, ConstraintsSpecificationsSchema } from "../specifications/constraints"; -import { FillImagePropertiesSpecificationsCreateSchema, FillImagePropertiesSpecificationsSchema } from "../specifications/fill_image_properties"; -import { ImportPCSpecificationsCreateSchema, ImportPCSpecificationsSchema } from "../specifications/import_point_cloud"; -import { Objects2DSpecificationsCreateSchema, Objects2DSpecificationsSchema } from "../specifications/objects2d"; -import { ProductionSpecificationsCreateSchema, ProductionSpecificationsSchema } from "../specifications/production"; -import { ReconstructionSpecificationsCreateSchema, ReconstructionSpecificationsSchema } from "../specifications/reconstruction"; -import { Segmentation2DSpecificationsCreateSchema, Segmentation2DSpecificationsSchema } from "../specifications/segmentation2d"; -import { Segmentation3DSpecificationsCreateSchema, Segmentation3DSpecificationsSchema } from "../specifications/segmentation3d"; -import { SegmentationOrthophotoSpecificationsCreateSchema, SegmentationOrthophotoSpecificationsSchema } from "../specifications/segmentation_orthophoto"; -import { TilingSpecificationsCreateSchema, TilingSpecificationsSchema } from "../specifications/tiling"; -import { TouchUpExportSpecificationsCreateSchema, TouchUpImportSpecificationsCreateSchema, TouchUpExportSpecificationsSchema, TouchUpImportSpecificationsSchema } from "../specifications/touchup"; -import { WaterConstraintsSpecificationsCreateSchema, WaterConstraintsSpecificationsSchema } from "../specifications/water_constraints"; -import { TrainingO2DSpecificationsCreateSchema, TrainingS3DSpecificationsCreateSchema, TrainingO2DSpecificationsSchema, TrainingS3DSpecificationsSchema } from "../specifications/training"; -//import { PointCloudConversionSpecificationsCreateSchema, PointCloudConversionSpecificationsSchema } from '../specifications/point_cloud_conversion'; -import { GaussianSplatsSpecificationsCreateSchema, GaussianSplatsSpecificationsSchema } from "../specifications/gaussian_splats"; +import { + CalibrationSpecificationsCreateSchema, + CalibrationSpecificationsSchema, +} from "../specifications/calibration"; +import { + ChangeDetectionSpecificationsCreateSchema, + ChangeDetectionSpecificationsSchema, +} from "../specifications/change_detection"; +import { + ConstraintsSpecificationsCreateSchema, + ConstraintsSpecificationsSchema, +} from "../specifications/constraints"; +import { + FillImagePropertiesSpecificationsCreateSchema, + FillImagePropertiesSpecificationsSchema, +} from "../specifications/fill_image_properties"; +import { + ImportPCSpecificationsCreateSchema, + ImportPCSpecificationsSchema, +} from "../specifications/import_point_cloud"; +import { + Objects2DSpecificationsCreateSchema, + Objects2DSpecificationsSchema, +} from "../specifications/objects2d"; +import { + ProductionSpecificationsCreateSchema, + ProductionSpecificationsSchema, +} from "../specifications/production"; +import { + ReconstructionSpecificationsCreateSchema, + ReconstructionSpecificationsSchema, +} from "../specifications/reconstruction"; +import { + Segmentation2DSpecificationsCreateSchema, + Segmentation2DSpecificationsSchema, +} from "../specifications/segmentation2d"; +import { + Segmentation3DSpecificationsCreateSchema, + Segmentation3DSpecificationsSchema, +} from "../specifications/segmentation3d"; +import { + SegmentationOrthophotoSpecificationsCreateSchema, + SegmentationOrthophotoSpecificationsSchema, +} from "../specifications/segmentation_orthophoto"; +import { + TilingSpecificationsCreateSchema, + TilingSpecificationsSchema, +} from "../specifications/tiling"; +import { + TouchUpExportSpecificationsCreateSchema, + TouchUpImportSpecificationsCreateSchema, + TouchUpExportSpecificationsSchema, + TouchUpImportSpecificationsSchema, +} from "../specifications/touchup"; +import { + WaterConstraintsSpecificationsCreateSchema, + WaterConstraintsSpecificationsSchema, +} from "../specifications/water_constraints"; +import { + TrainingO2DSpecificationsCreateSchema, + TrainingS3DSpecificationsCreateSchema, + TrainingO2DSpecificationsSchema, + TrainingS3DSpecificationsSchema, +} from "../specifications/training"; + +import { + GaussianSplatsSpecificationsCreateSchema, + GaussianSplatsSpecificationsSchema, +} from "../specifications/gaussian_splats"; import { URLSchema } from "./bucket"; -import { EvalO2DSpecificationsCreateSchema, EvalO2DSpecificationsSchema } from "../specifications/eval_o2d"; -import { EvalO3DSpecificationsCreateSchema, EvalO3DSpecificationsSchema } from "../specifications/eval_o3d"; -import { EvalS2DSpecificationsCreateSchema, EvalS2DSpecificationsSchema } from "../specifications/eval_s2d"; -import { EvalS3DSpecificationsCreateSchema, EvalS3DSpecificationsSchema } from "../specifications/eval_s3d"; -import { EvalSOrthoSpecificationsCreateSchema, EvalSOrthoSpecificationsSchema } from "../specifications/eval_sortho"; -import { ClearanceSpecificationsCreateSchema, ClearanceSpecificationsSchema } from "../specifications/clearance"; +import { + EvalO2DSpecificationsCreateSchema, + EvalO2DSpecificationsSchema, +} from "../specifications/eval_o2d"; +import { + EvalO3DSpecificationsCreateSchema, + EvalO3DSpecificationsSchema, +} from "../specifications/eval_o3d"; +import { + EvalS2DSpecificationsCreateSchema, + EvalS2DSpecificationsSchema, +} from "../specifications/eval_s2d"; +import { + EvalS3DSpecificationsCreateSchema, + EvalS3DSpecificationsSchema, +} from "../specifications/eval_s3d"; +import { + EvalSOrthoSpecificationsCreateSchema, + EvalSOrthoSpecificationsSchema, +} from "../specifications/eval_sortho"; +import { + ClearanceSpecificationsCreateSchema, + ClearanceSpecificationsSchema, +} from "../specifications/clearance"; +import { + PointCloudConversionSpecificationsCreateSchema, + PointCloudConversionSpecificationsSchema, +} from "../specifications/point_cloud_conversion"; +import { + MeshSamplingSpecificationsCreateSchema, + MeshSamplingSpecificationsSchema, +} from "../specifications/mesh_sampling"; +import { + PCOptimizationSpecificationsCreateSchema, + PCOptimizationSpecificationsSchema, +} from "../specifications/point_cloud_optimization"; +import { + VectorOptimizationSpecificationsCreateSchema, + VectorOptimizationSpecificationsSchema, +} from "../specifications/vector_optimization"; +import { + TileMapOptimizationSpecificationsCreateSchema, + TileMapOptimizationSpecificationsSchema, +} from "../specifications/tile_map_optimization"; + +// import { +// ContextSceneTilerSpecificationsCreateSchema, +// ContextSceneTilerSpecificationsSchema, +// } from "../specifications/cs_tiler"; export enum JobType { CALIBRATION = "Calibration", @@ -50,34 +145,69 @@ export enum JobType { TOUCH_UP_EXPORT = "TouchUpExport", WATER_CONSTRAINTS = "WaterConstraints", CLEARANCE_CALCULATION = "ClearanceCalculation", - //POINT_CLOUD_CONVERSION = "PointCloudConversion", + POINT_CLOUD_CONVERSION = "PointCloudConversion", + MESH_SAMPLING = "MeshSampling", + POINT_CLOUD_OPTIMIZATION = "PointCloudOptimization", + VECTOR_OPTIMIZATION = "VectorOptimization", + TILE_MAP_OPTIMIZATION = "TileMapOptimization", + // CONTEXTSCENE_TILER = "ContextSceneTiler", } export enum Service { MODELING = "Modeling", ANALYSIS = "Analysis", - // CONVERSION = "Conversion" + CONVERSION = "Conversion", } export function getAppropriateService(jt: JobType): Service { - if ([ - JobType.FILL_IMAGE_PROPERTIES, JobType.IMPORT_POINT_CLOUD, JobType.CALIBRATION, - JobType.TILING, JobType.PRODUCTION, JobType.RECONSTRUCTION, JobType.CONSTRAINTS, - JobType.TOUCH_UP_EXPORT, JobType.TOUCH_UP_IMPORT, JobType.WATER_CONSTRAINTS, - JobType.GAUSSIAN_SPLATS - ].includes(jt)) { + if ( + [ + JobType.FILL_IMAGE_PROPERTIES, + JobType.IMPORT_POINT_CLOUD, + JobType.CALIBRATION, + JobType.TILING, + JobType.PRODUCTION, + JobType.RECONSTRUCTION, + JobType.CONSTRAINTS, + JobType.TOUCH_UP_EXPORT, + JobType.TOUCH_UP_IMPORT, + JobType.WATER_CONSTRAINTS, + JobType.GAUSSIAN_SPLATS, + ].includes(jt) + ) { return Service.MODELING; } - if ([ - JobType.OBJECTS_2D, JobType.SEGMENTATION_2D, JobType.SEGMENTATION_3D, - JobType.SEGMENTATION_ORTHOPHOTO, JobType.CHANGE_DETECTION, JobType.TRAINING_O2D, - JobType.EVAL_O2D, JobType.EVAL_O3D, JobType.EVAL_S2D, JobType.EVAL_S3D, - JobType.EVAL_SORTHO, JobType.TRAINING_O2D, JobType.TRAINING_S3D, - JobType.CLEARANCE_CALCULATION - ].includes(jt)) { + if ( + [ + JobType.OBJECTS_2D, + JobType.SEGMENTATION_2D, + JobType.SEGMENTATION_3D, + JobType.SEGMENTATION_ORTHOPHOTO, + JobType.CHANGE_DETECTION, + JobType.EVAL_O2D, + JobType.EVAL_O3D, + JobType.EVAL_S2D, + JobType.EVAL_S3D, + JobType.EVAL_SORTHO, + JobType.TRAINING_O2D, + JobType.TRAINING_S3D, + JobType.CLEARANCE_CALCULATION, + ].includes(jt) + ) { return Service.ANALYSIS; } - // return Service.CONVERSION; + if ( + [ + JobType.POINT_CLOUD_CONVERSION, + JobType.MESH_SAMPLING, + JobType.POINT_CLOUD_OPTIMIZATION, + JobType.VECTOR_OPTIMIZATION, + JobType.TILE_MAP_OPTIMIZATION, + // JobType.CONTEXTSCENE_TILER, + ].includes(jt) + ) { + return Service.CONVERSION; + } throw new Error("Other job types are not implemented yet"); } @@ -88,57 +218,86 @@ export enum JobState { FAILED = "Failed", TERMINATING_ON_CANCEL = "TerminatingOnCancel", TERMINATING_ON_FAILURE = "TerminatingOnFailure", - CANCELLED = "Cancelled" + CANCELLED = "Cancelled", } export const JobCreateSchema = z.object({ name: z.string().min(3).optional().describe("Displayable job name."), type: z.nativeEnum(JobType).describe("Type of job."), - specifications: z.union([ - CalibrationSpecificationsCreateSchema, - ChangeDetectionSpecificationsCreateSchema, - ConstraintsSpecificationsCreateSchema, - EvalO2DSpecificationsCreateSchema, - EvalO3DSpecificationsCreateSchema, - EvalS2DSpecificationsCreateSchema, - EvalS3DSpecificationsCreateSchema, - EvalSOrthoSpecificationsCreateSchema, - FillImagePropertiesSpecificationsCreateSchema, - GaussianSplatsSpecificationsCreateSchema, - ImportPCSpecificationsCreateSchema, - Objects2DSpecificationsCreateSchema, - ProductionSpecificationsCreateSchema, - ReconstructionSpecificationsCreateSchema, - Segmentation2DSpecificationsCreateSchema, - Segmentation3DSpecificationsCreateSchema, - SegmentationOrthophotoSpecificationsCreateSchema, - TilingSpecificationsCreateSchema, - TouchUpExportSpecificationsCreateSchema, - TouchUpImportSpecificationsCreateSchema, - WaterConstraintsSpecificationsCreateSchema, - TrainingO2DSpecificationsCreateSchema, - //PointCloudConversionSpecificationsCreateSchema, - TrainingS3DSpecificationsCreateSchema, - ClearanceSpecificationsCreateSchema, - ]).describe("Specifications aligned with the job type."), - iTwinId: z.string().describe("iTwin ID, used by the service for finding input reality data and uploading output data."), + specifications: z + .union([ + CalibrationSpecificationsCreateSchema, + ChangeDetectionSpecificationsCreateSchema, + ConstraintsSpecificationsCreateSchema, + EvalO2DSpecificationsCreateSchema, + EvalO3DSpecificationsCreateSchema, + EvalS2DSpecificationsCreateSchema, + EvalS3DSpecificationsCreateSchema, + EvalSOrthoSpecificationsCreateSchema, + FillImagePropertiesSpecificationsCreateSchema, + GaussianSplatsSpecificationsCreateSchema, + ImportPCSpecificationsCreateSchema, + Objects2DSpecificationsCreateSchema, + ProductionSpecificationsCreateSchema, + ReconstructionSpecificationsCreateSchema, + Segmentation2DSpecificationsCreateSchema, + Segmentation3DSpecificationsCreateSchema, + SegmentationOrthophotoSpecificationsCreateSchema, + TilingSpecificationsCreateSchema, + TouchUpExportSpecificationsCreateSchema, + TouchUpImportSpecificationsCreateSchema, + WaterConstraintsSpecificationsCreateSchema, + TrainingO2DSpecificationsCreateSchema, + ClearanceSpecificationsCreateSchema, + TrainingS3DSpecificationsCreateSchema, + PointCloudConversionSpecificationsCreateSchema, + MeshSamplingSpecificationsCreateSchema, + PCOptimizationSpecificationsCreateSchema, + VectorOptimizationSpecificationsCreateSchema, + TileMapOptimizationSpecificationsCreateSchema, + // ContextSceneTilerSpecificationsCreateSchema, + ]) + .describe("Specifications aligned with the job type."), + iTwinId: z + .string() + .describe( + "iTwin ID, used by the service for finding input reality data and uploading output data.", + ), }); export type JobCreate = z.infer; export const ExecutionSchema = z.object({ createdDateTime: z.coerce.date().describe("Creation date time for the job."), - startedDateTime: z.coerce.date().nullable().optional().describe("Start date time for the job."), - endedDateTime: z.coerce.date().nullable().optional().describe("End date time for the job."), - processingUnits: z.number().nullable().optional().describe("Processing units consumed by the job."), + startedDateTime: z.coerce + .date() + .nullable() + .optional() + .describe("Start date time for the job."), + endedDateTime: z.coerce + .date() + .nullable() + .optional() + .describe("End date time for the job."), + processingUnits: z + .number() + .nullable() + .optional() + .describe("Processing units consumed by the job."), }); export type Execution = z.infer; const CommonFields = { id: z.string().describe("Job unique identifier."), name: z.string().min(3).optional().describe("Displayable job name."), - itwinId: z.string().describe("iTwin ID, used by the service for finding input reality data and uploading output data."), + itwinId: z + .string() + .describe( + "iTwin ID, used by the service for finding input reality data and uploading output data.", + ), state: z.nativeEnum(JobState).describe("State of the job."), - executionInfo: ExecutionSchema.describe("Known execution information for the job."), + executionInfo: ExecutionSchema.describe( + "Known execution information for the job.", + ), userId: z.string().describe("Identifier of the user that created the job."), }; @@ -193,11 +352,6 @@ export const JobSchema = z.discriminatedUnion("type", [ type: z.literal("GaussianSplats"), specifications: GaussianSplatsSpecificationsSchema, }), - /*z.object({ - ...CommonFields, - type: z.literal("ImportPointCloud"), - specifications: ImportPCSpecificationsSchema, - }),*/ z.object({ ...CommonFields, type: z.literal("Objects2D"), @@ -262,18 +416,50 @@ export const JobSchema = z.discriminatedUnion("type", [ ...CommonFields, type: z.literal("ClearanceCalculation"), specifications: ClearanceSpecificationsSchema, - }) + }), + z.object({ + ...CommonFields, + type: z.literal("PointCloudConversion"), + specifications: PointCloudConversionSpecificationsSchema, + }), + z.object({ + ...CommonFields, + type: z.literal("MeshSampling"), + specifications: MeshSamplingSpecificationsSchema, + }), + z.object({ + ...CommonFields, + type: z.literal("PointCloudOptimization"), + specifications: PCOptimizationSpecificationsSchema, + }), + z.object({ + ...CommonFields, + type: z.literal("VectorOptimization"), + specifications: VectorOptimizationSpecificationsSchema, + }), + z.object({ + ...CommonFields, + type: z.literal("TileMapOptimization"), + specifications: TileMapOptimizationSpecificationsSchema, + }), + // z.object({ + // ...CommonFields, + // type: z.literal("ContextSceneTiler"), + // specifications: ContextSceneTilerSpecificationsSchema, + // }), ]); export type Job = z.infer; export const NextLinkSchema = z.object({ - next: URLSchema.describe("URL for getting the next page of results.") + next: URLSchema.describe("URL for getting the next page of results."), }); export type NextLink = z.infer; export const JobsSchema = z.object({ jobs: z.array(JobSchema).describe("List of jobs."), - _links: NextLinkSchema.optional().describe("Contains the hyperlink to the next page of results, if applicable."), + _links: NextLinkSchema.optional().describe( + "Contains the hyperlink to the next page of results, if applicable.", + ), }); export type Jobs = z.infer; @@ -297,13 +483,21 @@ export const MessageSchema = z.object({ code: z.string().describe("Unique identifier for an error."), title: z.string().describe("Title of the error."), message: z.string().describe("Message of the error."), - params: z.array(z.string()).describe("Parameters to be placed in the message. Can be used for localization effort."), + params: z + .array(z.string()) + .describe( + "Parameters to be placed in the message. Can be used for localization effort.", + ), }); export type Message = z.infer; export const MessagesSchema = z.object({ - errors: z.array(MessageSchema).describe("List of potential errors from the job execution."), - warnings: z.array(MessageSchema).describe("List of potential warnings from the job execution."), + errors: z + .array(MessageSchema) + .describe("List of potential errors from the job execution."), + warnings: z + .array(MessageSchema) + .describe("List of potential warnings from the job execution."), }); export type Messages = z.infer; diff --git a/typescript/packages/reality-capture/src/service/service.ts b/typescript/packages/reality-capture/src/service/service.ts index d65673d3..c6b2aab0 100644 --- a/typescript/packages/reality-capture/src/service/service.ts +++ b/typescript/packages/reality-capture/src/service/service.ts @@ -5,7 +5,15 @@ import { DetectorBase, DetectorResponse, DetectorsMinimalResponse, DetectorUpdat import { CostEstimationCreate, CostEstimation } from "./estimation"; import { Files } from "./files"; import { Response } from "./response"; -import { JobCreate, Job, Progress, Messages, Service, getAppropriateService, Jobs } from "./job"; +import { + JobCreate, + Job, + Progress, + Messages, + Service, + getAppropriateService, + Jobs, +} from "./job"; import { DetailedErrorResponse, DetailedError } from "./error"; export class RealityCaptureService { @@ -14,7 +22,10 @@ export class RealityCaptureService { private _serviceUrl: string; private _additionalUserAgent: string; - constructor(authorizationClient: AuthorizationClient, kwargs?: { env?: string, user_agent?: string }) { + constructor( + authorizationClient: AuthorizationClient, + kwargs?: { env?: string; user_agent?: string }, + ) { this._authorizationClient = authorizationClient; this._axios = axios.create(); const env = kwargs?.env; @@ -35,7 +46,8 @@ export class RealityCaptureService { private async _getHeader(version: "v1" | "v2") { return { Authorization: await this._authorizationClient.getAccessToken(), - "User-Agent": "Reality Capture TypeScript SDK/" + this._additionalUserAgent, + "User-Agent": + "Reality Capture TypeScript SDK/" + this._additionalUserAgent, "Content-type": "application/json", Accept: `application/vnd.bentley.itwin-platform.${version}+json`, }; @@ -49,12 +61,18 @@ export class RealityCaptureService { return this._serviceUrl + "reality-analysis/"; } + private _getConversionUrl() { + return this._serviceUrl + "reality-conversion/"; + } + private _getCorrectUrl(service: Service): string { switch (service) { case Service.MODELING: return this._getModelingUrl(); case Service.ANALYSIS: return this._getAnalysisUrl(); + case Service.CONVERSION: + return this._getConversionUrl(); default: throw new Error("Other services not yet implemented"); } @@ -64,12 +82,17 @@ export class RealityCaptureService { return `Service response is ill-formed: ${JSON.stringify(response.data)}. Exception: ${exception}`; } - async getJobs(service: Service, filters: string, top: number = 100, continuationToken: string = ""): Promise> { + async getJobs( + service: Service, + filters: string, + top: number = 100, + continuationToken: string = "", + ): Promise> { const url = this._getCorrectUrl(service); try { const resp = await this._axios.get(url + "/jobs", { - params: {"$filter": filters, "$top": top, continuationToken }, - headers: await this._getHeader("v2") + params: { $filter: filters, $top: top, continuationToken }, + headers: await this._getHeader("v2"), }); return new Response(resp.status, null, resp.data as Jobs); } catch (error: any) { @@ -80,7 +103,9 @@ export class RealityCaptureService { async submitJob(job: JobCreate): Promise> { const url = this._getCorrectUrl(getAppropriateService(job.type)); try { - const resp = await this._axios.post(url + "/jobs", job, { headers: await this._getHeader("v2") }); + const resp = await this._axios.post(url + "/jobs", job, { + headers: await this._getHeader("v2"), + }); return new Response(resp.status, null, resp.data.job as Job); } catch (error: any) { return this._handleError(error); @@ -90,27 +115,39 @@ export class RealityCaptureService { async getJob(jobId: string, service: Service): Promise> { const url = this._getCorrectUrl(service); try { - const resp = await this._axios.get(url + "/jobs/" + jobId, { headers: await this._getHeader("v2") }); + const resp = await this._axios.get(url + "/jobs/" + jobId, { + headers: await this._getHeader("v2"), + }); return new Response(resp.status, null, resp.data.job as Job); } catch (error: any) { return this._handleError(error); } } - async getJobMessages(jobId: string, service: Service): Promise> { + async getJobMessages( + jobId: string, + service: Service, + ): Promise> { const url = this._getCorrectUrl(service); try { - const resp = await this._axios.get(url + `/jobs/${jobId}/messages`, { headers: await this._getHeader("v2") }); + const resp = await this._axios.get(url + `/jobs/${jobId}/messages`, { + headers: await this._getHeader("v2"), + }); return new Response(resp.status, null, resp.data.messages as Messages); } catch (error: any) { return this._handleError(error); } } - async getJobProgress(jobId: string, service: Service): Promise> { + async getJobProgress( + jobId: string, + service: Service, + ): Promise> { const url = this._getCorrectUrl(service); try { - const resp = await this._axios.get(url + `/jobs/${jobId}/progress`, { headers: await this._getHeader("v2") }); + const resp = await this._axios.get(url + `/jobs/${jobId}/progress`, { + headers: await this._getHeader("v2"), + }); return new Response(resp.status, null, resp.data.progress as Progress); } catch (error: any) { return this._handleError(error); @@ -120,18 +157,30 @@ export class RealityCaptureService { async cancelJob(jobId: string, service: Service): Promise> { const url = this._getCorrectUrl(service); try { - const resp = await this._axios.delete(url + `/jobs/${jobId}`, { headers: await this._getHeader("v2") }); + const resp = await this._axios.delete(url + `/jobs/${jobId}`, { + headers: await this._getHeader("v2"), + }); return new Response(resp.status, null, resp.data.job as Job); } catch (error: any) { return this._handleError(error); } } - async estimateCost(estimationCreate: CostEstimationCreate): Promise> { - const url = this._getCorrectUrl(getAppropriateService(estimationCreate.type)); + async estimateCost( + estimationCreate: CostEstimationCreate, + ): Promise> { + const url = this._getCorrectUrl( + getAppropriateService(estimationCreate.type), + ); try { - const resp = await this._axios.post(url + "/costs", estimationCreate, { headers: await this._getHeader("v2") }); - return new Response(resp.status, null, resp.data.costEstimation as CostEstimation); + const resp = await this._axios.post(url + "/costs", estimationCreate, { + headers: await this._getHeader("v2"), + }); + return new Response( + resp.status, + null, + resp.data.costEstimation as CostEstimation, + ); } catch (error: any) { return this._handleError(error); } @@ -140,7 +189,9 @@ export class RealityCaptureService { async getBucket(itwinId: string): Promise> { const url = this._getModelingUrl() + `itwins/${itwinId}/bucket`; try { - const resp = await this._axios.get(url, { headers: await this._getHeader("v2") }); + const resp = await this._axios.get(url, { + headers: await this._getHeader("v2"), + }); return new Response(resp.status, null, resp.data as BucketResponse); } catch (error: any) { return this._handleError(error); @@ -150,21 +201,29 @@ export class RealityCaptureService { async getServiceFiles(): Promise> { const url = this._getModelingUrl() + "files"; try { - const resp = await this._axios.get(url, { headers: await this._getHeader("v2") }); + const resp = await this._axios.get(url, { + headers: await this._getHeader("v2"), + }); return new Response(resp.status, null, resp.data as Files); } catch (error: any) { return this._handleError(error); } } - async getDetectors(detectorsFilter?: string): Promise> { + async getDetectors( + detectorsFilter?: string, + ): Promise> { const url = this._getAnalysisUrl() + "detectors"; try { const resp = await this._axios.get(url, { - params: detectorsFilter ? { "$filter": detectorsFilter } : undefined, - headers: await this._getHeader("v2") + params: detectorsFilter ? { $filter: detectorsFilter } : undefined, + headers: await this._getHeader("v2"), }); - return new Response(resp.status, null, resp.data as DetectorsMinimalResponse); + return new Response( + resp.status, + null, + resp.data as DetectorsMinimalResponse, + ); } catch (error: any) { return this._handleError(error); } @@ -173,7 +232,9 @@ export class RealityCaptureService { async getDetector(detectorName: string): Promise> { const url = this._getAnalysisUrl() + `detectors/${encodeURIComponent(detectorName)}`; try { - const resp = await this._axios.get(url, { headers: await this._getHeader("v2") }); + const resp = await this._axios.get(url, { + headers: await this._getHeader("v2"), + }); return new Response(resp.status, null, resp.data as DetectorResponse); } catch (error: any) { return this._handleError(error); @@ -266,11 +327,22 @@ export class RealityCaptureService { if (!data || typeof data !== "object" || !("error" in data)) { const detError = { code: "UnknownError", - message: this._getIllFormedMessage(error.response, "Malformed error data"), + message: this._getIllFormedMessage( + error.response, + "Malformed error data", + ), } as DetailedError; - return new Response(error.response.status, { error: detError }, null); + return new Response( + error.response.status, + { error: detError }, + null, + ); } - return new Response(error.response.status, data as DetailedErrorResponse, null); + return new Response( + error.response.status, + data as DetailedErrorResponse, + null, + ); } else { const detError = { code: "UnknownError", @@ -279,4 +351,4 @@ export class RealityCaptureService { return new Response(500, { error: detError }, null); } } -} \ No newline at end of file +} diff --git a/typescript/packages/reality-capture/src/specifications/cs_tiler.ts b/typescript/packages/reality-capture/src/specifications/cs_tiler.ts new file mode 100644 index 00000000..12cf781b --- /dev/null +++ b/typescript/packages/reality-capture/src/specifications/cs_tiler.ts @@ -0,0 +1,37 @@ +// import { z } from "zod"; + +// export const CSTilerInputsSchema = z.object({ +// scene: z.string().describe("ContextScene reality data id to tile"), +// }); +// export type CSTilerInputs = z.infer; + +// export enum CSObject { +// CAMERAS = "Cameras", +// TIE_POINTS = "TiePoints", +// ANNOTATIONS = "Annotations", +// } + +// export const CSTilerOptionsSchema = z.object({ +// objectToTile: z +// .nativeEnum(CSObject) +// .describe("Object to tile inside the ContextScene.") +// .optional(), +// }); +// export type CSTilerOptions = z.infer; + +// export const ContextSceneTilerSpecificationsCreateSchema = z.object({ +// inputs: CSTilerInputsSchema.describe("Inputs"), +// options: CSTilerOptionsSchema.describe("Options").optional(), +// }); +// export type ContextSceneTilerSpecificationsCreate = z.infer< +// typeof ContextSceneTilerSpecificationsCreateSchema +// >; + +// export const ContextSceneTilerSpecificationsSchema = z.object({ +// inputs: CSTilerInputsSchema.describe("Inputs"), +// output: z.string().describe("Reality Data id of tiled ContextScene"), +// options: CSTilerOptionsSchema.describe("Options").optional(), +// }); +// export type ContextSceneTilerSpecifications = z.infer< +// typeof ContextSceneTilerSpecificationsSchema +// >; diff --git a/typescript/packages/reality-capture/src/specifications/mesh_sampling.ts b/typescript/packages/reality-capture/src/specifications/mesh_sampling.ts new file mode 100644 index 00000000..405bdf7c --- /dev/null +++ b/typescript/packages/reality-capture/src/specifications/mesh_sampling.ts @@ -0,0 +1,44 @@ +import { z } from "zod"; + +export const MeshSamplingInputsSchema = z.object({ + meshes: z.array(z.string()).describe("Reality data Ids of meshes to sample"), +}); +export type MeshSamplingInputs = z.infer; + +export enum MeshSamplingFormat { + OPC = "OPC", + THREE_D_TILES_PNTS = "3DTilesPnts", + THREE_D_TILES_GLBC = "3DTilesGlbc", + LAS = "LAS", + LAZ = "LAZ", + E57 = "E57", + POD = "POD", +} + +export const MeshSamplingOptionsSchema = z.object({ + outputFormat: z + .nativeEnum(MeshSamplingFormat) + .describe("Output format for the conversion.") + .optional(), + inputCrs: z.string().describe("CRS for the input data").optional(), + outputCrs: z.string().describe("CRS for the output data").optional(), + sampling: z.number().describe("Sampling value in meter").optional(), +}); +export type MeshSamplingOptions = z.infer; + +export const MeshSamplingSpecificationsCreateSchema = z.object({ + inputs: MeshSamplingInputsSchema.describe("Inputs"), + options: MeshSamplingOptionsSchema.describe("Options").optional(), +}); +export type MeshSamplingSpecificationsCreate = z.infer< + typeof MeshSamplingSpecificationsCreateSchema +>; + +export const MeshSamplingSpecificationsSchema = z.object({ + inputs: MeshSamplingInputsSchema.describe("Inputs"), + output: z.string().describe("Reality Data id of the sampled point cloud"), + options: MeshSamplingOptionsSchema.describe("Options").optional(), +}); +export type MeshSamplingSpecifications = z.infer< + typeof MeshSamplingSpecificationsSchema +>; diff --git a/typescript/packages/reality-capture/src/specifications/point_cloud_conversion.ts b/typescript/packages/reality-capture/src/specifications/point_cloud_conversion.ts new file mode 100644 index 00000000..5483db7b --- /dev/null +++ b/typescript/packages/reality-capture/src/specifications/point_cloud_conversion.ts @@ -0,0 +1,43 @@ +import { z } from "zod"; + +export const PCConversionInputsSchema = z.object({ + pointCloud: z.string().describe("Reality data Id of point cloud to convert"), +}); +export type PCConversionInputs = z.infer; + +export enum PCConversionFormat { + OPC = "OPC", + THREE_D_TILES_PNTS = "3DTilesPnts", + THREE_D_TILES_GLBC = "3DTilesGlbc", + LAS = "LAS", + LAZ = "LAZ", + E57 = "E57", + POD = "POD", +} + +export const PCConversionOptionsSchema = z.object({ + outputFormat: z + .nativeEnum(PCConversionFormat) + .describe("Output format for the conversion.") + .optional(), + inputCrs: z.string().describe("CRS for the input data").optional(), + outputCrs: z.string().describe("CRS for the output data").optional(), +}); +export type PCConversionOptions = z.infer; + +export const PointCloudConversionSpecificationsCreateSchema = z.object({ + inputs: PCConversionInputsSchema.describe("Inputs"), + options: PCConversionOptionsSchema.describe("Options").optional(), +}); +export type PointCloudConversionSpecificationsCreate = z.infer< + typeof PointCloudConversionSpecificationsCreateSchema +>; + +export const PointCloudConversionSpecificationsSchema = z.object({ + inputs: PCConversionInputsSchema.describe("Inputs"), + output: z.string().describe("Reality Data id of the converted point cloud"), + options: PCConversionOptionsSchema.describe("Options").optional(), +}); +export type PointCloudConversionSpecifications = z.infer< + typeof PointCloudConversionSpecificationsSchema +>; diff --git a/typescript/packages/reality-capture/src/specifications/point_cloud_optimization.ts b/typescript/packages/reality-capture/src/specifications/point_cloud_optimization.ts new file mode 100644 index 00000000..0d4607a9 --- /dev/null +++ b/typescript/packages/reality-capture/src/specifications/point_cloud_optimization.ts @@ -0,0 +1,45 @@ +import { z } from "zod"; + +export const PCOptimizationInputsSchema = z.object({ + pointClouds: z + .array(z.string()) + .describe("Reality data Ids of point cloud(s) to convert"), +}); +export type PCOptimizationInputs = z.infer; + +export enum PCOptimizationFormat { + OPC = "OPC", + THREE_D_TILES_PNTS = "3DTilesPnts", + THREE_D_TILES_GLBC = "3DTilesGlbc", + LAS = "LAS", + LAZ = "LAZ", + E57 = "E57", + POD = "POD", +} + +export const PCOptimizationOptionsSchema = z.object({ + outputFormat: z + .nativeEnum(PCOptimizationFormat) + .describe("Output format for the conversion.") + .optional(), + inputCrs: z.string().describe("CRS for the input data").optional(), + outputCrs: z.string().describe("CRS for the output data").optional(), +}); +export type PCOptimizationOptions = z.infer; + +export const PCOptimizationSpecificationsCreateSchema = z.object({ + inputs: PCOptimizationInputsSchema.describe("Inputs"), + options: PCOptimizationOptionsSchema.describe("Options").optional(), +}); +export type PCOptimizationSpecificationsCreate = z.infer< + typeof PCOptimizationSpecificationsCreateSchema +>; + +export const PCOptimizationSpecificationsSchema = z.object({ + inputs: PCOptimizationInputsSchema.describe("Inputs"), + output: z.string().describe("Reality Data id of the converted point cloud"), + options: PCOptimizationOptionsSchema.describe("Options").optional(), +}); +export type PCOptimizationSpecifications = z.infer< + typeof PCOptimizationSpecificationsSchema +>; diff --git a/typescript/packages/reality-capture/src/specifications/tile_map_optimization.ts b/typescript/packages/reality-capture/src/specifications/tile_map_optimization.ts new file mode 100644 index 00000000..c1b6bef6 --- /dev/null +++ b/typescript/packages/reality-capture/src/specifications/tile_map_optimization.ts @@ -0,0 +1,71 @@ +import { z } from "zod"; + +export const TileMapOptimizationInputsSchema = z.object({ + tileMaps: z + .array(z.string()) + .describe("Reality data Ids of tile maps to convert"), +}); +export type TileMapOptimizationInputs = z.infer< + typeof TileMapOptimizationInputsSchema +>; + +export enum TileMapOptimizationFormat { + XYZ_TILE_MAP = "XYZTileMap", +} + +export enum TileMapImageFormat { + JPG = "JPG", + PNG = "PNG", +} + +export const TileMapOptimizationOptionsSchema = z.object({ + outputFormat: z + .nativeEnum(TileMapOptimizationFormat) + .describe("Output format for the conversion.") + .optional(), + inputCrs: z.string().describe("CRS for the input data").optional(), + outputCrs: z.string().describe("CRS for the output data").optional(), + topLevel: z + .number() + .describe("Top level of the tile map to generate.") + .optional(), + bottomLevel: z + .number() + .describe("Bottom level of the tile map to generate.") + .optional(), + imageFormat: z + .nativeEnum(TileMapImageFormat) + .describe("Image format") + .optional(), + jpgQuality: z + .number() + .min(10) + .max(99) + .describe("Quality of JPG tiles to generate") + .optional(), + backgroundColor: z + .string() + .regex(/^#[a-fA-F0-9]{6}$/) + .describe("Background color to use for tiles") + .optional(), +}); +export type TileMapOptimizationOptions = z.infer< + typeof TileMapOptimizationOptionsSchema +>; + +export const TileMapOptimizationSpecificationsCreateSchema = z.object({ + inputs: TileMapOptimizationInputsSchema.describe("Inputs"), + options: TileMapOptimizationOptionsSchema.describe("Options").optional(), +}); +export type TileMapOptimizationSpecificationsCreate = z.infer< + typeof TileMapOptimizationSpecificationsCreateSchema +>; + +export const TileMapOptimizationSpecificationsSchema = z.object({ + inputs: TileMapOptimizationInputsSchema.describe("Inputs"), + output: z.string().describe("Reality Data id of the tile map"), + options: TileMapOptimizationOptionsSchema.describe("Options").optional(), +}); +export type TileMapOptimizationSpecifications = z.infer< + typeof TileMapOptimizationSpecificationsSchema +>; diff --git a/typescript/packages/reality-capture/src/specifications/vector_optimization.ts b/typescript/packages/reality-capture/src/specifications/vector_optimization.ts new file mode 100644 index 00000000..b1012522 --- /dev/null +++ b/typescript/packages/reality-capture/src/specifications/vector_optimization.ts @@ -0,0 +1,47 @@ +import { z } from "zod"; + +export const VectorOptimizationInputsSchema = z.object({ + vectors: z + .array(z.string()) + .describe("Reality data Ids of vectors to consolidate"), +}); +export type VectorOptimizationInputs = z.infer< + typeof VectorOptimizationInputsSchema +>; + +export enum VectorOptimizationFormat { + GEO_JSON = "GeoJSON", + FEATURE_DB = "FeatureDB", +} + +export const VectorOptimizationOptionsSchema = z.object({ + outputFormat: z + .nativeEnum(VectorOptimizationFormat) + .describe("Output format for the conversion.") + .optional(), + inputCrs: z.string().describe("CRS for the input data").optional(), + outputCrs: z.string().describe("CRS for the output data").optional(), + featureClassDisplayName: z.string().describe("Display class name").optional(), +}); +export type VectorOptimizationOptions = z.infer< + typeof VectorOptimizationOptionsSchema +>; + +export const VectorOptimizationSpecificationsCreateSchema = z.object({ + inputs: VectorOptimizationInputsSchema.describe("Inputs"), + options: VectorOptimizationOptionsSchema.describe("Options").optional(), +}); +export type VectorOptimizationSpecificationsCreate = z.infer< + typeof VectorOptimizationSpecificationsCreateSchema +>; + +export const VectorOptimizationSpecificationsSchema = z.object({ + inputs: VectorOptimizationInputsSchema.describe("Inputs"), + output: z + .string() + .describe("Reality Data id of the vector data or Feature DB index (fdb:)"), + options: VectorOptimizationOptionsSchema.describe("Options").optional(), +}); +export type VectorOptimizationSpecifications = z.infer< + typeof VectorOptimizationSpecificationsSchema +>; diff --git a/typescript/packages/reality-capture/src/tests/service/job.test.ts b/typescript/packages/reality-capture/src/tests/service/job.test.ts index 00eed025..0b2ca944 100644 --- a/typescript/packages/reality-capture/src/tests/service/job.test.ts +++ b/typescript/packages/reality-capture/src/tests/service/job.test.ts @@ -28,29 +28,35 @@ function getCommonFields(type: JobType) { createdDateTime: new Date(), startedDateTime: new Date(), endedDateTime: new Date(), - processingUnits: 5 + processingUnits: 5, }, userId: "user42", type, specifications: { inputs: { scene: "scene" }, - outputs: [ImportPCOutputsCreate.SCAN_COLLECTION] - } + outputs: [ImportPCOutputsCreate.SCAN_COLLECTION], + }, }; } describe("getAppropriateService", () => { it("should return Service.MODELING for PRODUCTION", () => { - expect(getAppropriateService(JobType.PRODUCTION)).to.equal(Service.MODELING); + expect(getAppropriateService(JobType.PRODUCTION)).to.equal( + Service.MODELING, + ); }); it("should return Service.ANALYSIS for OBJECTS_2D", () => { - expect(getAppropriateService(JobType.OBJECTS_2D)).to.equal(Service.ANALYSIS); + expect(getAppropriateService(JobType.OBJECTS_2D)).to.equal( + Service.ANALYSIS, + ); }); - /*it("should return Service.CONVERSION for POINT_CLOUD_CONVERSION", () => { - expect(getAppropriateService(JobType.POINT_CLOUD_CONVERSION)).to.equal(Service.CONVERSION); - });*/ + it("should return Service.CONVERSION for POINT_CLOUD_CONVERSION", () => { + expect(getAppropriateService(JobType.POINT_CLOUD_CONVERSION)).to.equal( + Service.CONVERSION, + ); + }); }); describe("JobState Enum", () => { @@ -66,9 +72,9 @@ describe("JobCreateSchema", () => { type: JobType.PRODUCTION, specifications: { inputs: { scene: "scene" }, - outputs: [ImportPCOutputsCreate.SCAN_COLLECTION] + outputs: [ImportPCOutputsCreate.SCAN_COLLECTION], }, - iTwinId: "itwin123" + iTwinId: "itwin123", }; expect(() => JobCreateSchema.parse(data)).to.not.throw(); }); @@ -78,9 +84,9 @@ describe("JobCreateSchema", () => { name: "JobName", specifications: { inputs: { scene: "scene" }, - outputs: [ImportPCOutputsCreate.SCAN_COLLECTION] + outputs: [ImportPCOutputsCreate.SCAN_COLLECTION], }, - iTwinId: "itwin123" + iTwinId: "itwin123", }; expect(() => JobCreateSchema.parse(data)).to.throw(z.ZodError); }); @@ -91,7 +97,7 @@ describe("JobCreateSchema", () => { type: JobType.PRODUCTION, specifications: { inputs: { scene: "scene" }, - outputs: [ImportPCOutputsCreate.SCAN_COLLECTION] + outputs: [ImportPCOutputsCreate.SCAN_COLLECTION], }, }; expect(() => JobCreateSchema.parse(data)).to.throw(z.ZodError); @@ -103,9 +109,9 @@ describe("JobCreateSchema", () => { type: JobType.PRODUCTION, specifications: { inputs: { scene: "scene" }, - outputs: [ImportPCOutputsCreate.SCAN_COLLECTION] + outputs: [ImportPCOutputsCreate.SCAN_COLLECTION], }, - iTwinId: "itwin123" + iTwinId: "itwin123", }; expect(() => JobCreateSchema.parse(data)).to.throw(z.ZodError); }); @@ -117,7 +123,7 @@ describe("ExecutionSchema", () => { createdDateTime: new Date().toISOString(), startedDateTime: new Date().toISOString(), endedDateTime: new Date().toISOString(), - processingUnits: 9 + processingUnits: 9, }; expect(() => ExecutionSchema.parse(data)).to.not.throw(); }); @@ -125,7 +131,7 @@ describe("ExecutionSchema", () => { it("should allow missing startedDateTime and endedDateTime", () => { const data = { createdDateTime: new Date().toISOString(), - processingUnits: 4 + processingUnits: 4, }; expect(() => ExecutionSchema.parse(data)).to.not.throw(); }); @@ -133,14 +139,14 @@ describe("ExecutionSchema", () => { it("should allow nullable processingUnits", () => { const data = { createdDateTime: new Date().toISOString(), - processingUnits: null + processingUnits: null, }; expect(() => ExecutionSchema.parse(data)).to.not.throw(); }); it("should fail if createdDateTime is missing", () => { const data = { - processingUnits: 3 + processingUnits: 3, }; expect(() => ExecutionSchema.parse(data)).to.throw(z.ZodError); }); @@ -176,7 +182,7 @@ describe("ProgressSchema", () => { it("should validate correct progress info", () => { const data = { state: JobState.ACTIVE, - percentage: 55 + percentage: 55, }; expect(() => ProgressSchema.parse(data)).to.not.throw(); }); @@ -184,7 +190,7 @@ describe("ProgressSchema", () => { it("should fail if percentage is out of range", () => { const data = { state: JobState.ACTIVE, - percentage: 120 + percentage: 120, }; expect(() => ProgressSchema.parse(data)).to.throw(z.ZodError); }); @@ -195,8 +201,8 @@ describe("ProgressResponseSchema", () => { const data = { progress: { state: JobState.SUCCESS, - percentage: 100 - } + percentage: 100, + }, }; expect(() => ProgressResponseSchema.parse(data)).to.not.throw(); }); @@ -212,7 +218,7 @@ describe("MessageSchema", () => { code: "ERR1", title: "Error title", message: "Error message", - params: ["param1", "param2"] + params: ["param1", "param2"], }; expect(() => MessageSchema.parse(msg)).to.not.throw(); }); @@ -221,7 +227,7 @@ describe("MessageSchema", () => { const msg = { code: "ERR1", title: "Error title", - message: "Error message" + message: "Error message", }; expect(() => MessageSchema.parse(msg)).to.throw(z.ZodError); }); @@ -230,19 +236,29 @@ describe("MessageSchema", () => { describe("MessagesSchema", () => { it("should validate correct messages", () => { const data = { - errors: [{ - code: "E1", title: "t1", message: "m1", params: [] - }], - warnings: [{ - code: "W1", title: "t2", message: "m2", params: ["p"] - }] + errors: [ + { + code: "E1", + title: "t1", + message: "m1", + params: [], + }, + ], + warnings: [ + { + code: "W1", + title: "t2", + message: "m2", + params: ["p"], + }, + ], }; expect(() => MessagesSchema.parse(data)).to.not.throw(); }); it("should fail if errors field is missing", () => { const data = { - warnings: [] + warnings: [], }; expect(() => MessagesSchema.parse(data)).to.throw(z.ZodError); }); @@ -253,8 +269,8 @@ describe("MessagesResponseSchema", () => { const data = { messages: { errors: [{ code: "E1", title: "t1", message: "m1", params: [] }], - warnings: [] - } + warnings: [], + }, }; expect(() => MessagesResponseSchema.parse(data)).to.not.throw(); }); @@ -265,16 +281,16 @@ describe("MessagesResponseSchema", () => { }); describe("NextLinkSchema", () => { - it ("should validate correct next link", () => { + it("should validate correct next link", () => { const data = { next: { - href: "https://api.bentley.com/reality-modeling/jobs?$filter=iTwinId%20eq%202c8e4988-eb9b-4e5f-a903-8c7c18f3030a&$top=2&continuationToken=MTRmZDkwOGYtNWEzOS00YzY3LWFmMGYtMGMxMWQxYWNkMDhl" - } + href: "https://api.bentley.com/reality-modeling/jobs?$filter=iTwinId%20eq%202c8e4988-eb9b-4e5f-a903-8c7c18f3030a&$top=2&continuationToken=MTRmZDkwOGYtNWEzOS00YzY3LWFmMGYtMGMxMWQxYWNkMDhl", + }, }; expect(() => NextLinkSchema.parse(data)).to.not.throw(); }); - it ("should fail if empty", () => { + it("should fail if empty", () => { expect(() => NextLinkSchema.parse({})).to.throw(z.ZodError); }); -}); \ No newline at end of file +});