From c0796cdbfe49a28052ebb26b2af195ec1fe60667 Mon Sep 17 00:00:00 2001 From: plu Date: Wed, 25 Jun 2025 20:48:39 +0100 Subject: [PATCH 01/23] add method to allow export simulation result as vtp files --- src/ansys/speos/core/simulation.py | 56 +++++++- src/ansys/speos/core/workflow/open_result.py | 129 ++++++++++++++++++- 2 files changed, 179 insertions(+), 6 deletions(-) diff --git a/src/ansys/speos/core/simulation.py b/src/ansys/speos/core/simulation.py index becacba8c..6ccdb7c38 100644 --- a/src/ansys/speos/core/simulation.py +++ b/src/ansys/speos/core/simulation.py @@ -32,11 +32,10 @@ import warnings from ansys.api.speos.job.v2 import job_pb2 +from ansys.api.speos.job.v2.job_pb2 import Result from ansys.api.speos.scene.v2 import scene_pb2 as messages from ansys.api.speos.simulation.v1 import simulation_template_pb2 from ansys.speos.core.generic.general_methods import min_speos_version - -# from ansys.speos.core.geo_ref import GeoRef from ansys.speos.core.kernel.job import ProtoJob from ansys.speos.core.kernel.proto_message_utils import protobuf_message_to_str from ansys.speos.core.kernel.scene import ProtoScene @@ -265,7 +264,53 @@ def export(self, export_path: Union[str, Path]) -> None: "Selected simulation is not the first simulation feature, it can't be exported." ) - def compute_CPU(self, threads_number: Optional[int] = None) -> List[job_pb2.Result]: + def _export_vtp(self) -> List[Path]: + """Export the simulation results into vtp files. + + Returns + ------- + List[Path] + list of vtp paths. + + """ + vtp_files = [] + export_data_xmp = [ + result.path for result in self.result_list if result.path.endswith(".xmp") + ] + if len(export_data_xmp) != 0: + from ansys.speos.core.workflow.open_result import export_xmp_vtp + + for data in export_data_xmp: + exported_vtp = export_xmp_vtp(data) + vtp_files.append(exported_vtp) + + export_data_xm3 = [ + result.path for result in self.result_list if result.path.endswith(".xm3") + ] + if len(export_data_xm3) != 0: + from ansys.speos.core.sensor import Face, Sensor3DIrradiance + from ansys.speos.core.workflow.open_result import export_xm3_vtp + + for sensor in self._project.find( + name=".*", name_regex=True, feature_type=Sensor3DIrradiance + ): + geo_paths = sensor.get(key="geo_paths") + geos_faces = [ + self._project.find(geo_path, feature_type=Face)[0]._face + for geo_path in geo_paths + ] + data = [ + result + for result in export_data_xm3 + if sensor.get(key="result_file_name") in result + ][0] + exported_vtp = export_xm3_vtp(geos_faces, data) + vtp_files.append(exported_vtp) + return vtp_files + + def compute_CPU( + self, threads_number: Optional[int] = None, export_vtp: Optional[bool] = False + ) -> tuple[list[Result], list[Path]] | list[Result]: """Compute the simulation on CPU. Parameters @@ -273,6 +318,8 @@ def compute_CPU(self, threads_number: Optional[int] = None) -> List[job_pb2.Resu threads_number : int, optional The number of threads used. By default, ``None``, means the number of processor available. + export_vtp: bool, optional + True to generate vtp from the simulation results. Returns ------- @@ -287,6 +334,9 @@ def compute_CPU(self, threads_number: Optional[int] = None) -> List[job_pb2.Resu ) self.result_list = self._run_job() + if export_vtp: + vtp_files = self._export_vtp() + return self.result_list, vtp_files return self.result_list def compute_GPU(self) -> List[job_pb2.Result]: diff --git a/src/ansys/speos/core/workflow/open_result.py b/src/ansys/speos/core/workflow/open_result.py index b36f77102..901b0ec27 100644 --- a/src/ansys/speos/core/workflow/open_result.py +++ b/src/ansys/speos/core/workflow/open_result.py @@ -24,17 +24,18 @@ import os from pathlib import Path import tempfile -from typing import Union +from typing import List, Union import ansys.api.speos.file.v1.file_transfer as file_transfer_helper__v1 import ansys.api.speos.file.v1.file_transfer_pb2_grpc as file_transfer__v1__pb2_grpc +from ansys.api.speos.part.v1 import face_pb2 if os.name == "nt": from comtypes.client import CreateObject import matplotlib.image as mpimg import matplotlib.pyplot as plt -from numpy import ndarray +import numpy from ansys.speos.core.simulation import ( SimulationDirect, @@ -43,6 +44,17 @@ ) +class _Speos3dData: + def __init__(self, x, y, z, illuminance=0.0, reflection=0.0, transmission=0.0, absorption=0.0): + self.x = float(x) + self.y = float(y) + self.z = float(z) + self.illuminance = float(illuminance) + self.reflection = float(reflection) + self.transmission = float(transmission) + self.absorption = float(absorption) + + def _find_correct_result( simulation_feature: Union[SimulationDirect, SimulationInverse, SimulationInteractive], result_name: str, @@ -77,7 +89,7 @@ def _find_correct_result( return file_path -def _display_image(img: ndarray): +def _display_image(img: numpy.ndarray): if img is not None: plt.imshow(img) plt.axis("off") # turns off axes @@ -142,3 +154,114 @@ def open_result_in_viewer( dpf_instance = CreateObject("HDRIViewer.Application") dpf_instance.OpenFile(file_path) dpf_instance.Show(1) + + def export_xmp_vtp(file_path: Union[str, Path]) -> Path: + """Export an XMP result into vtp file. + + Parameters + ---------- + file_path: Union[str, Path] + file path of an XMP result. + + Returns + ------- + Path + file path of exported vtp file. + + """ + import pyvista as pv + + if not str(file_path).lower().endswith("xmp"): + raise ValueError("Please specify a .xmp file.") + file_path = Path(file_path) + dpf_instance = CreateObject("XMPViewer.Application") + dpf_instance.OpenFile(str(file_path)) + tmp_txt = file_path.with_suffix(".txt") + dpf_instance.ExportTXT(str(tmp_txt)) + + file = tmp_txt.open("r") + content = file.readlines() + file.close() + dimension_x_min, dimension_x_max, dimension_y_min, dimension_y_max = ( + content[4].strip().split("\t") + ) + dimension_x = int(float(dimension_x_max)) - int(float(dimension_x_min)) + dimension_y = int(float(dimension_y_max)) - int(float(dimension_y_min)) + resolution_x, resolution_y = content[5].strip().split("\t") + resolution_x = int(resolution_x) + resolution_y = int(resolution_y) + xmp_data = numpy.loadtxt( + tmp_txt, + delimiter="\t", + skiprows=9, + max_rows=int(resolution_y), + usecols=range(0, int(resolution_x)), + ) + + # Create VTK ImageData structure + step_x = float(dimension_x) / resolution_x + step_y = float(dimension_y) / resolution_y + origin_x = -(resolution_x * step_x) / 2 + origin_y = -(resolution_y * step_y) / 2 + grid = pv.ImageData( + dimensions=(resolution_x, resolution_y, 1), + spacing=(step_x, step_y, 1), + origin=(origin_x, origin_y, 0), + ) + grid["Illuminance [lx]"] = numpy.ravel(xmp_data) + vtp_meshes = grid.extract_surface() + # Export file to VTP + vtp_meshes.save(str(file_path.with_suffix(".vtp"))) + return file_path.with_suffix(".vtp") + + def export_xm3_vtp(geo_faces: List[face_pb2.Face], file_path: Union[str, Path]) -> Path: + """Export an XMP result into vtp file. + + Parameters + ---------- + geo_faces: List[face_pb2.Face] + list of face geometries. + file_path: Union[str, Path] + file path of an XMP result. + + Returns + ------- + Path + file path of exported vtp file. + + """ + import pyvista as pv + + if not str(file_path).lower().endswith("xm3"): + raise ValueError("Please specify a .xm3 file.") + + dpf_instance = CreateObject("Xm3Viewer.Application") + dpf_instance.OpenFile(str(file_path)) + tmp_txt = file_path.with_suffix(".txt") + dpf_instance.Export(str(tmp_txt)) + + file = tmp_txt.open("r") + xm3_data = [] + content = file.readlines() + if "x" in content[0].split() and "y" in content[0].split() and "z" in content[0].split(): + for line in content[1:]: + xm3_data.append(_Speos3dData(*line.split())) + else: + print("reading other results") + + vtp_meshes = None + for geo in geo_faces: + vertices = numpy.array(geo.vertices).reshape(-1, 3) + facets = numpy.array(geo.facets).reshape(-1, 3) + temp = numpy.full(facets.shape[0], 3) + temp = numpy.vstack(temp) + facets = numpy.hstack((temp, facets)) + if vtp_meshes is None: + vtp_meshes = pv.PolyData(vertices, facets) + else: + vtp_meshes = vtp_meshes.append_polydata(pv.PolyData(vertices, facets)) + + vtp_meshes["Illuminance [lx]"] = [item.transmission for item in xm3_data] + vtp_meshes = vtp_meshes.point_data_to_cell_data() + vtp_meshes.save(str(file_path.with_suffix(".vtp"))) + return file_path.with_suffix(".vtp") From a35eca17161a40ad83f0a4470daac86a799038ed Mon Sep 17 00:00:00 2001 From: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com> Date: Wed, 25 Jun 2025 19:51:43 +0000 Subject: [PATCH 02/23] chore: adding changelog file 643.added.md [dependabot-skip] --- doc/changelog.d/643.added.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changelog.d/643.added.md diff --git a/doc/changelog.d/643.added.md b/doc/changelog.d/643.added.md new file mode 100644 index 000000000..0495abeca --- /dev/null +++ b/doc/changelog.d/643.added.md @@ -0,0 +1 @@ +Add export result as vtp files \ No newline at end of file From 697a84646e4881826bb9fe0a6b1147869028ecc8 Mon Sep 17 00:00:00 2001 From: plu Date: Sat, 28 Jun 2025 17:46:22 +0100 Subject: [PATCH 03/23] handle photometric result and radiometric data in xmp text --- src/ansys/speos/core/workflow/open_result.py | 22 +++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/ansys/speos/core/workflow/open_result.py b/src/ansys/speos/core/workflow/open_result.py index 901b0ec27..dc52278e3 100644 --- a/src/ansys/speos/core/workflow/open_result.py +++ b/src/ansys/speos/core/workflow/open_result.py @@ -190,13 +190,12 @@ def export_xmp_vtp(file_path: Union[str, Path]) -> Path: resolution_x, resolution_y = content[5].strip().split("\t") resolution_x = int(resolution_x) resolution_y = int(resolution_y) - xmp_data = numpy.loadtxt( - tmp_txt, - delimiter="\t", - skiprows=9, - max_rows=int(resolution_y), - usecols=range(0, int(resolution_x)), - ) + skip_lines = 9 if "SeparatedByLayer" in content[7] else 8 + xmp_data = [] + for line in content[skip_lines : skip_lines + resolution_y]: + parts = line.strip().split() + print(len(parts)) + xmp_data.append(list(map(float, parts))) # Create VTK ImageData structure step_x = float(dimension_x) / resolution_x @@ -208,7 +207,14 @@ def export_xmp_vtp(file_path: Union[str, Path]) -> Path: spacing=(step_x, step_y, 1), origin=(origin_x, origin_y, 0), ) - grid["Illuminance [lx]"] = numpy.ravel(xmp_data) + xmp_data = numpy.array(xmp_data) + if xmp_data.shape[1] == resolution_x: + grid["Illuminance [lx]"] = numpy.ravel(xmp_data) + else: + grid["X"] = numpy.ravel(xmp_data[:, 0::4]) + grid["Illuminance [lx]"] = numpy.ravel(xmp_data[:, 1::4]) + grid["Radiometric [W/m2]"] = numpy.ravel(xmp_data[:, 2::4]) + grid["Z"] = numpy.ravel(xmp_data[:, 3::4]) vtp_meshes = grid.extract_surface() # Export file to VTP vtp_meshes.save(str(file_path.with_suffix(".vtp"))) From f28bfd48cac23160ee33571b1d4557f3736b151e Mon Sep 17 00:00:00 2001 From: plu Date: Sat, 28 Jun 2025 17:46:56 +0100 Subject: [PATCH 04/23] put warning for spectral xmp --- src/ansys/speos/core/workflow/open_result.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/ansys/speos/core/workflow/open_result.py b/src/ansys/speos/core/workflow/open_result.py index dc52278e3..f990d7971 100644 --- a/src/ansys/speos/core/workflow/open_result.py +++ b/src/ansys/speos/core/workflow/open_result.py @@ -33,6 +33,8 @@ if os.name == "nt": from comtypes.client import CreateObject +import warnings + import matplotlib.image as mpimg import matplotlib.pyplot as plt import numpy @@ -182,6 +184,12 @@ def export_xmp_vtp(file_path: Union[str, Path]) -> Path: file = tmp_txt.open("r") content = file.readlines() file.close() + if "2" in content[0]: + warnings.warn( + message="Limitation : spectral type of XMP is currently not supported", + stacklevel=2, + ) + return Path() dimension_x_min, dimension_x_max, dimension_y_min, dimension_y_max = ( content[4].strip().split("\t") ) From e1a895ec70cfd0d1dbcfb49b787eedb305787e86 Mon Sep 17 00:00:00 2001 From: plu Date: Sat, 28 Jun 2025 17:47:59 +0100 Subject: [PATCH 05/23] fix issue export 3d sensor uses sensor_paths rather finding all 3d sensor features --- src/ansys/speos/core/simulation.py | 35 ++++++++++++++++-------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/src/ansys/speos/core/simulation.py b/src/ansys/speos/core/simulation.py index 6ccdb7c38..8071e4199 100644 --- a/src/ansys/speos/core/simulation.py +++ b/src/ansys/speos/core/simulation.py @@ -288,24 +288,27 @@ def _export_vtp(self) -> List[Path]: result.path for result in self.result_list if result.path.endswith(".xm3") ] if len(export_data_xm3) != 0: - from ansys.speos.core.sensor import Face, Sensor3DIrradiance + from ansys.speos.core import Face + from ansys.speos.core.sensor import Sensor3DIrradiance from ansys.speos.core.workflow.open_result import export_xm3_vtp - for sensor in self._project.find( - name=".*", name_regex=True, feature_type=Sensor3DIrradiance - ): - geo_paths = sensor.get(key="geo_paths") - geos_faces = [ - self._project.find(geo_path, feature_type=Face)[0]._face - for geo_path in geo_paths - ] - data = [ - result - for result in export_data_xm3 - if sensor.get(key="result_file_name") in result - ][0] - exported_vtp = export_xm3_vtp(geos_faces, data) - vtp_files.append(exported_vtp) + sensor_paths = self.get(key="sensor_paths") + for sensor_path in sensor_paths: + sensors = self._project.find(name=sensor_path, feature_type=Sensor3DIrradiance) + if len(sensors) != 0: + sensor = sensors[0] + geo_paths = sensor.get(key="geo_paths") + geos_faces = [ + self._project.find(name=geo_path, feature_type=Face)[0]._face + for geo_path in geo_paths + ] + data = [ + result + for result in export_data_xm3 + if sensor.get(key="result_file_name") in result + ][0] + exported_vtp = export_xm3_vtp(geos_faces, data) + vtp_files.append(exported_vtp) return vtp_files def compute_CPU( From 4497a078eb63f10d9b1a630de00e33239b4d6a47 Mon Sep 17 00:00:00 2001 From: plu Date: Sat, 28 Jun 2025 18:03:23 +0100 Subject: [PATCH 06/23] fix 3d sensor to handle different measurement settings --- src/ansys/speos/core/workflow/open_result.py | 24 +++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/ansys/speos/core/workflow/open_result.py b/src/ansys/speos/core/workflow/open_result.py index f990d7971..1875e0674 100644 --- a/src/ansys/speos/core/workflow/open_result.py +++ b/src/ansys/speos/core/workflow/open_result.py @@ -249,6 +249,7 @@ def export_xm3_vtp(geo_faces: List[face_pb2.Face], file_path: Union[str, Path]) if not str(file_path).lower().endswith("xm3"): raise ValueError("Please specify a .xm3 file.") + file_path = Path(file_path) dpf_instance = CreateObject("Xm3Viewer.Application") dpf_instance.OpenFile(str(file_path)) tmp_txt = file_path.with_suffix(".txt") @@ -257,11 +258,19 @@ def export_xm3_vtp(geo_faces: List[face_pb2.Face], file_path: Union[str, Path]) file = tmp_txt.open("r") xm3_data = [] content = file.readlines() - if "x" in content[0].split() and "y" in content[0].split() and "z" in content[0].split(): - for line in content[1:]: - xm3_data.append(_Speos3dData(*line.split())) - else: - print("reading other results") + for line in content[1:]: + line_content = line.split() + xm3_data.append( + _Speos3dData( + x=line_content[0], + y=line_content[1], + z=line_content[2], + illuminance=0.0 if "Illuminance" not in content[0] else line_content[3], + reflection=0.0 if "Reflection" not in content[0] else line_content[4], + transmission=0.0 if "Transmission" not in content[0] else line_content[5], + absorption=0.0 if "Absorption" not in content[0] else line_content[6], + ) + ) vtp_meshes = None for geo in geo_faces: @@ -275,7 +284,10 @@ def export_xm3_vtp(geo_faces: List[face_pb2.Face], file_path: Union[str, Path]) else: vtp_meshes = vtp_meshes.append_polydata(pv.PolyData(vertices, facets)) - vtp_meshes["Illuminance [lx]"] = [item.transmission for item in xm3_data] + vtp_meshes["Illuminance [lx]"] = [item.illuminance for item in xm3_data] + vtp_meshes["Reflection"] = [item.reflection for item in xm3_data] + vtp_meshes["Transmission"] = [item.transmission for item in xm3_data] + vtp_meshes["Absorption"] = [item.absorption for item in xm3_data] vtp_meshes = vtp_meshes.point_data_to_cell_data() vtp_meshes.save(str(file_path.with_suffix(".vtp"))) return file_path.with_suffix(".vtp") From 22c61651cf4440abebba19d44dad769749cc2d1e Mon Sep 17 00:00:00 2001 From: plu Date: Sun, 29 Jun 2025 23:07:00 +0100 Subject: [PATCH 07/23] support spectral data summing all the values into single data table --- src/ansys/speos/core/workflow/open_result.py | 29 ++++++++++++-------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/ansys/speos/core/workflow/open_result.py b/src/ansys/speos/core/workflow/open_result.py index 1875e0674..2ad32286e 100644 --- a/src/ansys/speos/core/workflow/open_result.py +++ b/src/ansys/speos/core/workflow/open_result.py @@ -33,7 +33,6 @@ if os.name == "nt": from comtypes.client import CreateObject -import warnings import matplotlib.image as mpimg import matplotlib.pyplot as plt @@ -184,12 +183,6 @@ def export_xmp_vtp(file_path: Union[str, Path]) -> Path: file = tmp_txt.open("r") content = file.readlines() file.close() - if "2" in content[0]: - warnings.warn( - message="Limitation : spectral type of XMP is currently not supported", - stacklevel=2, - ) - return Path() dimension_x_min, dimension_x_max, dimension_y_min, dimension_y_max = ( content[4].strip().split("\t") ) @@ -200,10 +193,24 @@ def export_xmp_vtp(file_path: Union[str, Path]) -> Path: resolution_y = int(resolution_y) skip_lines = 9 if "SeparatedByLayer" in content[7] else 8 xmp_data = [] - for line in content[skip_lines : skip_lines + resolution_y]: - parts = line.strip().split() - print(len(parts)) - xmp_data.append(list(map(float, parts))) + if "2" not in content[0]: # not spectral data + for line in content[skip_lines : skip_lines + resolution_y]: + line_content = line.strip().split() + xmp_data.append(list(map(float, line_content))) + else: # spectral data within number of data tables + spectral_tables = int(content[6].strip().split()[2]) + xmp_data = [ + [0 for _ in range(len(content[skip_lines].strip().split()))] + for _ in range(resolution_y) + ] + for _ in range(spectral_tables): + for i in range(resolution_y): + row = list(map(float, content[skip_lines].strip().split())) + for j in range(resolution_x): + xmp_data[i][j] += row[j] + skip_lines += 1 + # Skip one line between tables + skip_lines += 1 # Create VTK ImageData structure step_x = float(dimension_x) / resolution_x From e12d9617a26ae81aebc3296a679199eb23667bbb Mon Sep 17 00:00:00 2001 From: plu Date: Sun, 29 Jun 2025 23:21:05 +0100 Subject: [PATCH 08/23] update to use COM API method --- src/ansys/speos/core/workflow/open_result.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/ansys/speos/core/workflow/open_result.py b/src/ansys/speos/core/workflow/open_result.py index 2ad32286e..48e2d2e3e 100644 --- a/src/ansys/speos/core/workflow/open_result.py +++ b/src/ansys/speos/core/workflow/open_result.py @@ -177,23 +177,19 @@ def export_xmp_vtp(file_path: Union[str, Path]) -> Path: file_path = Path(file_path) dpf_instance = CreateObject("XMPViewer.Application") dpf_instance.OpenFile(str(file_path)) + dimension_x = dpf_instance.XWidth + dimension_y = dpf_instance.YHeight + resolution_x = dpf_instance.XNb + resolution_y = dpf_instance.YNb tmp_txt = file_path.with_suffix(".txt") dpf_instance.ExportTXT(str(tmp_txt)) file = tmp_txt.open("r") content = file.readlines() file.close() - dimension_x_min, dimension_x_max, dimension_y_min, dimension_y_max = ( - content[4].strip().split("\t") - ) - dimension_x = int(float(dimension_x_max)) - int(float(dimension_x_min)) - dimension_y = int(float(dimension_y_max)) - int(float(dimension_y_min)) - resolution_x, resolution_y = content[5].strip().split("\t") - resolution_x = int(resolution_x) - resolution_y = int(resolution_y) skip_lines = 9 if "SeparatedByLayer" in content[7] else 8 xmp_data = [] - if "2" not in content[0]: # not spectral data + if dpf_instance.Maptype == 2: # not spectral data for line in content[skip_lines : skip_lines + resolution_y]: line_content = line.strip().split() xmp_data.append(list(map(float, line_content))) From 3b267b3b726c9b1fd7a51fbe700d38b2a8e5d4b6 Mon Sep 17 00:00:00 2001 From: plu Date: Mon, 30 Jun 2025 14:49:32 +0100 Subject: [PATCH 09/23] add support when set_type_radiometric for 3d sensor --- src/ansys/speos/core/workflow/open_result.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/ansys/speos/core/workflow/open_result.py b/src/ansys/speos/core/workflow/open_result.py index 48e2d2e3e..57d96a8fe 100644 --- a/src/ansys/speos/core/workflow/open_result.py +++ b/src/ansys/speos/core/workflow/open_result.py @@ -46,11 +46,22 @@ class _Speos3dData: - def __init__(self, x, y, z, illuminance=0.0, reflection=0.0, transmission=0.0, absorption=0.0): + def __init__( + self, + x, + y, + z, + illuminance=0.0, + irradiance=0.0, + reflection=0.0, + transmission=0.0, + absorption=0.0, + ): self.x = float(x) self.y = float(y) self.z = float(z) self.illuminance = float(illuminance) + self.irradiance = float(irradiance) self.reflection = float(reflection) self.transmission = float(transmission) self.absorption = float(absorption) @@ -269,6 +280,7 @@ def export_xm3_vtp(geo_faces: List[face_pb2.Face], file_path: Union[str, Path]) y=line_content[1], z=line_content[2], illuminance=0.0 if "Illuminance" not in content[0] else line_content[3], + irradiance=0.0 if "Irradiance" not in content[0] else line_content[3], reflection=0.0 if "Reflection" not in content[0] else line_content[4], transmission=0.0 if "Transmission" not in content[0] else line_content[5], absorption=0.0 if "Absorption" not in content[0] else line_content[6], @@ -288,6 +300,7 @@ def export_xm3_vtp(geo_faces: List[face_pb2.Face], file_path: Union[str, Path]) vtp_meshes = vtp_meshes.append_polydata(pv.PolyData(vertices, facets)) vtp_meshes["Illuminance [lx]"] = [item.illuminance for item in xm3_data] + vtp_meshes["Irradiance [W/m2]"] = [item.irradiance for item in xm3_data] vtp_meshes["Reflection"] = [item.reflection for item in xm3_data] vtp_meshes["Transmission"] = [item.transmission for item in xm3_data] vtp_meshes["Absorption"] = [item.absorption for item in xm3_data] From 890257f35d87cf04184635a24a815bc78cb272cd Mon Sep 17 00:00:00 2001 From: plu Date: Mon, 30 Jun 2025 15:03:23 +0100 Subject: [PATCH 10/23] add unittest for export 3d sensor result as vtp --- tests/core/test_simulation.py | 235 +++++++++++++++++++++++++++++++++- 1 file changed, 233 insertions(+), 2 deletions(-) diff --git a/tests/core/test_simulation.py b/tests/core/test_simulation.py index af8d45d93..f1d31fd84 100644 --- a/tests/core/test_simulation.py +++ b/tests/core/test_simulation.py @@ -27,8 +27,8 @@ import pytest from ansys.api.speos.simulation.v1 import simulation_template_pb2 -from ansys.speos.core import GeoRef, Project, Speos -from ansys.speos.core.sensor import BaseSensor, SensorIrradiance +from ansys.speos.core import Body, GeoRef, Project, Speos +from ansys.speos.core.sensor import BaseSensor, Sensor3DIrradiance, SensorIrradiance from ansys.speos.core.simulation import ( SimulationDirect, SimulationInteractive, @@ -779,3 +779,234 @@ def test_export(speos: Speos): sim_second.export(export_path=str(Path(test_path) / "export_test")) remove_file(str(Path(test_path) / "export_test")) + + +def test_export_vtp(speos: Speos): + """Test export of xm3 and xmp as vtp files.""" + import numpy as np + import pyvista as pv + + from ansys.speos.core.workflow.open_result import _Speos3dData + + p = Project( + speos=speos, + path=str(Path(test_path) / "Prism.speos" / "Prism_3D.speos"), + ) + sim = p.find(name=".*", name_regex=True, feature_type=SimulationDirect)[0] + + ## ==== test 3d sensor photometric === + # verify illuminance, reflection, transmission, absorption are saved in vtp + # verify the vtp data is same as calculated + sensor_3d = p.find(name=".*", name_regex=True, feature_type=Sensor3DIrradiance)[0] + sensor_3d_geos = p.find(name="PrismBody", name_regex=True, feature_type=Body)[0]._geom_features + sensor_3d_mesh = [sensor_3d_geo._face for sensor_3d_geo in sensor_3d_geos] + sensor_3d.set_type_photometric() + sensor_3d.commit() + speos_results, vtp_results = sim.compute_CPU(export_vtp=True) + assert does_file_exist(vtp_results[1]) + + vtp_data = pv.read(vtp_results[1]).cell_data + assert not np.all(vtp_data.get("Reflection") == 0.0) + assert not np.all(vtp_data.get("Illuminance [lx]") == 0.0) + assert not np.all(vtp_data.get("Transmission") == 0.0) + assert np.all(vtp_data.get("Absorption") == 0.0) + + export_data_xm3 = [result.path for result in speos_results if result.path.endswith(".xm3")][0] + export_data_xm3_txt = Path(export_data_xm3).with_suffix(".txt") + file = export_data_xm3_txt.open("r") + xm3_data = [] + content = file.readlines() + for line in content[1:]: + line_content = line.split() + xm3_data.append( + _Speos3dData( + x=line_content[0], + y=line_content[1], + z=line_content[2], + illuminance=0.0 if "Illuminance" not in content[0] else line_content[3], + reflection=0.0 if "Reflection" not in content[0] else line_content[4], + transmission=0.0 if "Transmission" not in content[0] else line_content[5], + absorption=0.0 if "Absorption" not in content[0] else line_content[6], + ) + ) + vtp_meshes = None + for geo in sensor_3d_mesh: + vertices = np.array(geo.vertices).reshape(-1, 3) + facets = np.array(geo.facets).reshape(-1, 3) + temp = np.full(facets.shape[0], 3) + temp = np.vstack(temp) + facets = np.hstack((temp, facets)) + if vtp_meshes is None: + vtp_meshes = pv.PolyData(vertices, facets) + else: + vtp_meshes = vtp_meshes.append_polydata(pv.PolyData(vertices, facets)) + + vtp_meshes["Illuminance [lx]"] = [item.illuminance for item in xm3_data] + vtp_meshes["Reflection"] = [item.reflection for item in xm3_data] + vtp_meshes["Transmission"] = [item.transmission for item in xm3_data] + vtp_meshes["Absorption"] = [item.absorption for item in xm3_data] + vtp_meshes = vtp_meshes.point_data_to_cell_data() + assert all( + np.isclose( + vtp_meshes.cell_data.get("Illuminance [lx]"), + vtp_data.get("Illuminance [lx]"), + rtol=1e-5, + atol=1e-8, + ) + ) + assert all( + np.isclose( + vtp_meshes.cell_data.get("Reflection"), vtp_data.get("Reflection"), rtol=1e-5, atol=1e-8 + ) + ) + assert all( + np.isclose( + vtp_meshes.cell_data.get("Transmission"), + vtp_data.get("Transmission"), + rtol=1e-5, + atol=1e-8, + ) + ) + assert all( + np.isclose( + vtp_meshes.cell_data.get("Absorption"), vtp_data.get("Absorption"), rtol=1e-5, atol=1e-8 + ) + ) + + ## === test 3d sensor photometric with radial integration === + # only illuminance value is saved in vtp file + p2 = Project( + speos=speos, + path=str(Path(test_path) / "Prism.speos" / "Prism_3D.speos"), + ) + sim = p2.find(name=".*", name_regex=True, feature_type=SimulationDirect)[0] + + sensor_3d = p2.find(name=".*", name_regex=True, feature_type=Sensor3DIrradiance)[0] + sensor_3d.set_type_photometric().set_integration_radial() + sensor_3d.commit() + speos_results, vtp_results = sim.compute_CPU(export_vtp=True) + assert does_file_exist(vtp_results[1]) + + vtp_data = pv.read(vtp_results[1]).cell_data + assert np.allclose(vtp_data.get("Reflection"), 0.0) is True + assert np.allclose(vtp_data.get("Illuminance [lx]"), 0.0) is not True + assert np.allclose(vtp_data.get("Irradiance [W/m2]"), 0.0) is True + assert np.allclose(vtp_data.get("Transmission"), 0.0) is True + assert np.allclose(vtp_data.get("Absorption"), 0.0) is True + + ## === test 3d sensor radiometric === + # only irradiance, reflection, transmission, absorption value is saved in vtp file + # verify the vtp results are the same as calculated ones. + p3 = Project( + speos=speos, + path=str(Path(test_path) / "Prism.speos" / "Prism_3D.speos"), + ) + sim = p3.find(name=".*", name_regex=True, feature_type=SimulationDirect)[0] + + sensor_3d = p3.find(name=".*", name_regex=True, feature_type=Sensor3DIrradiance)[0] + sensor_3d_geos = p3.find(name="PrismBody", name_regex=True, feature_type=Body)[0]._geom_features + sensor_3d_mesh = [sensor_3d_geo._face for sensor_3d_geo in sensor_3d_geos] + sensor_3d.set_type_radiometric() + sensor_3d.commit() + speos_results, vtp_results = sim.compute_CPU(export_vtp=True) + assert does_file_exist(vtp_results[1]) + + vtp_data = pv.read(vtp_results[1]).cell_data + assert np.allclose(vtp_data.get("Reflection"), 0.0) is not True + assert np.allclose(vtp_data.get("Irradiance [W/m2]"), 0.0) is not True + assert np.allclose(vtp_data.get("Transmission"), 0.0) is not True + assert np.allclose(vtp_data.get("Absorption"), 0.0) is True + assert np.allclose(vtp_data.get("Illuminance [lx]"), 0.0) is True + + export_data_xm3 = [result.path for result in speos_results if result.path.endswith(".xm3")][0] + export_data_xm3_txt = Path(export_data_xm3).with_suffix(".txt") + file = export_data_xm3_txt.open("r") + xm3_data = [] + content = file.readlines() + for line in content[1:]: + line_content = line.split() + xm3_data.append( + _Speos3dData( + x=line_content[0], + y=line_content[1], + z=line_content[2], + illuminance=0.0 if "Illuminance" not in content[0] else line_content[3], + irradiance=0.0 if "Irradiance" not in content[0] else line_content[3], + reflection=0.0 if "Reflection" not in content[0] else line_content[4], + transmission=0.0 if "Transmission" not in content[0] else line_content[5], + absorption=0.0 if "Absorption" not in content[0] else line_content[6], + ) + ) + vtp_meshes = None + for geo in sensor_3d_mesh: + vertices = np.array(geo.vertices).reshape(-1, 3) + facets = np.array(geo.facets).reshape(-1, 3) + temp = np.full(facets.shape[0], 3) + temp = np.vstack(temp) + facets = np.hstack((temp, facets)) + if vtp_meshes is None: + vtp_meshes = pv.PolyData(vertices, facets) + else: + vtp_meshes = vtp_meshes.append_polydata(pv.PolyData(vertices, facets)) + + vtp_meshes["Illuminance [lx]"] = [item.illuminance for item in xm3_data] + vtp_meshes["Irradiance [W/m2]"] = [item.irradiance for item in xm3_data] + vtp_meshes["Reflection"] = [item.reflection for item in xm3_data] + vtp_meshes["Transmission"] = [item.transmission for item in xm3_data] + vtp_meshes["Absorption"] = [item.absorption for item in xm3_data] + vtp_meshes = vtp_meshes.point_data_to_cell_data() + assert all( + np.isclose( + vtp_meshes.cell_data.get("Illuminance [lx]"), + vtp_data.get("Illuminance [lx]"), + rtol=1e-5, + atol=1e-8, + ) + ) + assert all( + np.isclose( + vtp_meshes.cell_data.get("Irradiance [W/m2]"), + vtp_data.get("Irradiance [W/m2]"), + rtol=1e-5, + atol=1e-8, + ) + ) + assert all( + np.isclose( + vtp_meshes.cell_data.get("Reflection"), vtp_data.get("Reflection"), rtol=1e-5, atol=1e-8 + ) + ) + assert all( + np.isclose( + vtp_meshes.cell_data.get("Transmission"), + vtp_data.get("Transmission"), + rtol=1e-5, + atol=1e-8, + ) + ) + assert all( + np.isclose( + vtp_meshes.cell_data.get("Absorption"), vtp_data.get("Absorption"), rtol=1e-5, atol=1e-8 + ) + ) + + ## === test 3d sensor colorimetric === + ## verify if only illuminate data is saved in vtp + p4 = Project( + speos=speos, + path=str(Path(test_path) / "Prism.speos" / "Prism_3D.speos"), + ) + sim = p4.find(name=".*", name_regex=True, feature_type=SimulationDirect)[0] + + sensor_3d = p4.find(name=".*", name_regex=True, feature_type=Sensor3DIrradiance)[0] + sensor_3d.set_type_colorimetric() + sensor_3d.commit() + speos_results, vtp_results = sim.compute_CPU(export_vtp=True) + assert does_file_exist(vtp_results[1]) + + vtp_data = pv.read(vtp_results[1]).cell_data + assert np.allclose(vtp_data.get("Reflection"), 0.0) is True + assert np.allclose(vtp_data.get("Illuminance [lx]"), 0.0) is not True + assert np.allclose(vtp_data.get("Irradiance [W/m2]"), 0.0) is True + assert np.allclose(vtp_data.get("Transmission"), 0.0) is True + assert np.allclose(vtp_data.get("Absorption"), 0.0) is True From 2a94a399e18cb7b6d30ee2f01b3ee5f289a6e3b1 Mon Sep 17 00:00:00 2001 From: plu Date: Mon, 30 Jun 2025 15:52:41 +0100 Subject: [PATCH 11/23] fix typo issue for checking spectral type. remove the convert point to cell data --- src/ansys/speos/core/workflow/open_result.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ansys/speos/core/workflow/open_result.py b/src/ansys/speos/core/workflow/open_result.py index 57d96a8fe..80e8f0806 100644 --- a/src/ansys/speos/core/workflow/open_result.py +++ b/src/ansys/speos/core/workflow/open_result.py @@ -200,7 +200,7 @@ def export_xmp_vtp(file_path: Union[str, Path]) -> Path: file.close() skip_lines = 9 if "SeparatedByLayer" in content[7] else 8 xmp_data = [] - if dpf_instance.Maptype == 2: # not spectral data + if dpf_instance.Maptype != 2: # not spectral data for line in content[skip_lines : skip_lines + resolution_y]: line_content = line.strip().split() xmp_data.append(list(map(float, line_content))) @@ -304,6 +304,6 @@ def export_xm3_vtp(geo_faces: List[face_pb2.Face], file_path: Union[str, Path]) vtp_meshes["Reflection"] = [item.reflection for item in xm3_data] vtp_meshes["Transmission"] = [item.transmission for item in xm3_data] vtp_meshes["Absorption"] = [item.absorption for item in xm3_data] - vtp_meshes = vtp_meshes.point_data_to_cell_data() + # vtp_meshes = vtp_meshes.point_data_to_cell_data() vtp_meshes.save(str(file_path.with_suffix(".vtp"))) return file_path.with_suffix(".vtp") From cab2f130a9581b785242f47f38338999049478fe Mon Sep 17 00:00:00 2001 From: plu Date: Mon, 30 Jun 2025 15:53:14 +0100 Subject: [PATCH 12/23] use point_data --- tests/core/test_simulation.py | 48 +++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/tests/core/test_simulation.py b/tests/core/test_simulation.py index f1d31fd84..6833fdc44 100644 --- a/tests/core/test_simulation.py +++ b/tests/core/test_simulation.py @@ -805,11 +805,11 @@ def test_export_vtp(speos: Speos): speos_results, vtp_results = sim.compute_CPU(export_vtp=True) assert does_file_exist(vtp_results[1]) - vtp_data = pv.read(vtp_results[1]).cell_data - assert not np.all(vtp_data.get("Reflection") == 0.0) - assert not np.all(vtp_data.get("Illuminance [lx]") == 0.0) - assert not np.all(vtp_data.get("Transmission") == 0.0) - assert np.all(vtp_data.get("Absorption") == 0.0) + vtp_data = pv.read(vtp_results[1]).point_data + assert np.allclose(vtp_data.get("Reflection"), 0.0) is not True + assert np.allclose(vtp_data.get("Illuminance [lx]"), 0.0) is not True + assert np.allclose(vtp_data.get("Transmission"), 0.0) is not True + assert np.allclose(vtp_data.get("Absorption"), 0.0) is True export_data_xm3 = [result.path for result in speos_results if result.path.endswith(".xm3")][0] export_data_xm3_txt = Path(export_data_xm3).with_suffix(".txt") @@ -845,10 +845,9 @@ def test_export_vtp(speos: Speos): vtp_meshes["Reflection"] = [item.reflection for item in xm3_data] vtp_meshes["Transmission"] = [item.transmission for item in xm3_data] vtp_meshes["Absorption"] = [item.absorption for item in xm3_data] - vtp_meshes = vtp_meshes.point_data_to_cell_data() assert all( np.isclose( - vtp_meshes.cell_data.get("Illuminance [lx]"), + vtp_meshes.point_data.get("Illuminance [lx]"), vtp_data.get("Illuminance [lx]"), rtol=1e-5, atol=1e-8, @@ -856,12 +855,15 @@ def test_export_vtp(speos: Speos): ) assert all( np.isclose( - vtp_meshes.cell_data.get("Reflection"), vtp_data.get("Reflection"), rtol=1e-5, atol=1e-8 + vtp_meshes.point_data.get("Reflection"), + vtp_data.get("Reflection"), + rtol=1e-5, + atol=1e-8, ) ) assert all( np.isclose( - vtp_meshes.cell_data.get("Transmission"), + vtp_meshes.point_data.get("Transmission"), vtp_data.get("Transmission"), rtol=1e-5, atol=1e-8, @@ -869,7 +871,10 @@ def test_export_vtp(speos: Speos): ) assert all( np.isclose( - vtp_meshes.cell_data.get("Absorption"), vtp_data.get("Absorption"), rtol=1e-5, atol=1e-8 + vtp_meshes.point_data.get("Absorption"), + vtp_data.get("Absorption"), + rtol=1e-5, + atol=1e-8, ) ) @@ -887,7 +892,7 @@ def test_export_vtp(speos: Speos): speos_results, vtp_results = sim.compute_CPU(export_vtp=True) assert does_file_exist(vtp_results[1]) - vtp_data = pv.read(vtp_results[1]).cell_data + vtp_data = pv.read(vtp_results[1]).point_data assert np.allclose(vtp_data.get("Reflection"), 0.0) is True assert np.allclose(vtp_data.get("Illuminance [lx]"), 0.0) is not True assert np.allclose(vtp_data.get("Irradiance [W/m2]"), 0.0) is True @@ -911,7 +916,7 @@ def test_export_vtp(speos: Speos): speos_results, vtp_results = sim.compute_CPU(export_vtp=True) assert does_file_exist(vtp_results[1]) - vtp_data = pv.read(vtp_results[1]).cell_data + vtp_data = pv.read(vtp_results[1]).point_data assert np.allclose(vtp_data.get("Reflection"), 0.0) is not True assert np.allclose(vtp_data.get("Irradiance [W/m2]"), 0.0) is not True assert np.allclose(vtp_data.get("Transmission"), 0.0) is not True @@ -954,10 +959,9 @@ def test_export_vtp(speos: Speos): vtp_meshes["Reflection"] = [item.reflection for item in xm3_data] vtp_meshes["Transmission"] = [item.transmission for item in xm3_data] vtp_meshes["Absorption"] = [item.absorption for item in xm3_data] - vtp_meshes = vtp_meshes.point_data_to_cell_data() assert all( np.isclose( - vtp_meshes.cell_data.get("Illuminance [lx]"), + vtp_meshes.point_data.get("Illuminance [lx]"), vtp_data.get("Illuminance [lx]"), rtol=1e-5, atol=1e-8, @@ -965,7 +969,7 @@ def test_export_vtp(speos: Speos): ) assert all( np.isclose( - vtp_meshes.cell_data.get("Irradiance [W/m2]"), + vtp_meshes.point_data.get("Irradiance [W/m2]"), vtp_data.get("Irradiance [W/m2]"), rtol=1e-5, atol=1e-8, @@ -973,12 +977,15 @@ def test_export_vtp(speos: Speos): ) assert all( np.isclose( - vtp_meshes.cell_data.get("Reflection"), vtp_data.get("Reflection"), rtol=1e-5, atol=1e-8 + vtp_meshes.point_data.get("Reflection"), + vtp_data.get("Reflection"), + rtol=1e-5, + atol=1e-8, ) ) assert all( np.isclose( - vtp_meshes.cell_data.get("Transmission"), + vtp_meshes.point_data.get("Transmission"), vtp_data.get("Transmission"), rtol=1e-5, atol=1e-8, @@ -986,7 +993,10 @@ def test_export_vtp(speos: Speos): ) assert all( np.isclose( - vtp_meshes.cell_data.get("Absorption"), vtp_data.get("Absorption"), rtol=1e-5, atol=1e-8 + vtp_meshes.point_data.get("Absorption"), + vtp_data.get("Absorption"), + rtol=1e-5, + atol=1e-8, ) ) @@ -1004,7 +1014,7 @@ def test_export_vtp(speos: Speos): speos_results, vtp_results = sim.compute_CPU(export_vtp=True) assert does_file_exist(vtp_results[1]) - vtp_data = pv.read(vtp_results[1]).cell_data + vtp_data = pv.read(vtp_results[1]).point_data assert np.allclose(vtp_data.get("Reflection"), 0.0) is True assert np.allclose(vtp_data.get("Illuminance [lx]"), 0.0) is not True assert np.allclose(vtp_data.get("Irradiance [W/m2]"), 0.0) is True From f3418303163ac4d5fe40f4004eac41ab609279ae Mon Sep 17 00:00:00 2001 From: plu Date: Mon, 30 Jun 2025 17:05:54 +0100 Subject: [PATCH 13/23] change the name to avoid confusing between radiance or irradiance results --- src/ansys/speos/core/workflow/open_result.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/ansys/speos/core/workflow/open_result.py b/src/ansys/speos/core/workflow/open_result.py index 80e8f0806..e5e71ab59 100644 --- a/src/ansys/speos/core/workflow/open_result.py +++ b/src/ansys/speos/core/workflow/open_result.py @@ -231,11 +231,14 @@ def export_xmp_vtp(file_path: Union[str, Path]) -> Path: ) xmp_data = numpy.array(xmp_data) if xmp_data.shape[1] == resolution_x: - grid["Illuminance [lx]"] = numpy.ravel(xmp_data) + if dpf_instance.UnitType == 0: + grid["Radiometric"] = numpy.ravel(xmp_data) + if dpf_instance.UnitType == 1: + grid["Photometric"] = numpy.ravel(xmp_data) else: grid["X"] = numpy.ravel(xmp_data[:, 0::4]) - grid["Illuminance [lx]"] = numpy.ravel(xmp_data[:, 1::4]) - grid["Radiometric [W/m2]"] = numpy.ravel(xmp_data[:, 2::4]) + grid["Photometric"] = numpy.ravel(xmp_data[:, 1::4]) + grid["Radiometric"] = numpy.ravel(xmp_data[:, 2::4]) grid["Z"] = numpy.ravel(xmp_data[:, 3::4]) vtp_meshes = grid.extract_surface() # Export file to VTP From 1f5b421805d782b1b8c6faa9000471ee4a455980 Mon Sep 17 00:00:00 2001 From: plu Date: Mon, 30 Jun 2025 17:06:32 +0100 Subject: [PATCH 14/23] refactor the spectral data text file reading --- src/ansys/speos/core/workflow/open_result.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/ansys/speos/core/workflow/open_result.py b/src/ansys/speos/core/workflow/open_result.py index e5e71ab59..6bb183c77 100644 --- a/src/ansys/speos/core/workflow/open_result.py +++ b/src/ansys/speos/core/workflow/open_result.py @@ -200,11 +200,8 @@ def export_xmp_vtp(file_path: Union[str, Path]) -> Path: file.close() skip_lines = 9 if "SeparatedByLayer" in content[7] else 8 xmp_data = [] - if dpf_instance.Maptype != 2: # not spectral data - for line in content[skip_lines : skip_lines + resolution_y]: - line_content = line.strip().split() - xmp_data.append(list(map(float, line_content))) - else: # spectral data within number of data tables + if dpf_instance.Maptype == 2 and len(content[6].strip().split()) == 3: + # spectral data within number of data tables spectral_tables = int(content[6].strip().split()[2]) xmp_data = [ [0 for _ in range(len(content[skip_lines].strip().split()))] @@ -218,6 +215,11 @@ def export_xmp_vtp(file_path: Union[str, Path]) -> Path: skip_lines += 1 # Skip one line between tables skip_lines += 1 + else: + # not spectral data + for line in content[skip_lines : skip_lines + resolution_y]: + line_content = line.strip().split() + xmp_data.append(list(map(float, line_content))) # Create VTK ImageData structure step_x = float(dimension_x) / resolution_x From e5aee3f256275b1cbb7923a21f8e852e7e9ee134 Mon Sep 17 00:00:00 2001 From: plu Date: Mon, 30 Jun 2025 17:07:02 +0100 Subject: [PATCH 15/23] add unittest for irradiance xmp to vtp files --- tests/core/test_simulation.py | 118 +++++++++++++++++++++++++++++++++- 1 file changed, 116 insertions(+), 2 deletions(-) diff --git a/tests/core/test_simulation.py b/tests/core/test_simulation.py index 6833fdc44..b40fa9dc3 100644 --- a/tests/core/test_simulation.py +++ b/tests/core/test_simulation.py @@ -1000,8 +1000,8 @@ def test_export_vtp(speos: Speos): ) ) - ## === test 3d sensor colorimetric === - ## verify if only illuminate data is saved in vtp + # === test 3d sensor colorimetric === + # verify if only illuminate data is saved in vtp p4 = Project( speos=speos, path=str(Path(test_path) / "Prism.speos" / "Prism_3D.speos"), @@ -1020,3 +1020,117 @@ def test_export_vtp(speos: Speos): assert np.allclose(vtp_data.get("Irradiance [W/m2]"), 0.0) is True assert np.allclose(vtp_data.get("Transmission"), 0.0) is True assert np.allclose(vtp_data.get("Absorption"), 0.0) is True + + # === test irradiance xmp photometric === + # verify the result is photometric + p5 = Project( + speos=speos, + path=str(Path(test_path) / "Prism.speos" / "Prism.speos"), + ) + sim = p5.find(name=".*", name_regex=True, feature_type=SimulationDirect)[0] + sensor_irra = p5.find(name=".*", name_regex=True, feature_type=SensorIrradiance)[0] + sensor_irra.set_dimensions().set_x_sampling(10).set_y_sampling(10) + sensor_irra.set_type_photometric() + sensor_irra.commit() + speos_results, vtp_results = sim.compute_CPU(export_vtp=True) + assert does_file_exist(vtp_results[0]) + + vtp_data = pv.read(vtp_results[0]).point_data + assert np.allclose(vtp_data.get("Photometric"), 0.0) is not True + + export_data_xmp = [result.path for result in speos_results if result.path.endswith(".xmp")][0] + export_data_xmp_txt = Path(export_data_xmp).with_suffix(".txt") + file = export_data_xmp_txt.open("r") + content = file.readlines() + file.close() + skip_lines = 9 if "SeparatedByLayer" in content[7] else 8 + resolution_x = 10 + resolution_y = 10 + xmp_data = [] + if "2" not in content[0]: # not spectral data + for line in content[skip_lines : skip_lines + resolution_y]: + line_content = line.strip().split() + xmp_data.append(list(map(float, line_content))) + else: # spectral data within number of data tables + spectral_tables = int(content[6].strip().split()[2]) + xmp_data = [ + [0 for _ in range(len(content[skip_lines].strip().split()))] + for _ in range(resolution_y) + ] + for _ in range(spectral_tables): + for i in range(resolution_y): + row = list(map(float, content[skip_lines].strip().split())) + for j in range(resolution_x): + xmp_data[i][j] += row[j] + skip_lines += 1 + # Skip one line between tables + skip_lines += 1 + assert np.all( + np.isclose( + np.array(xmp_data), + vtp_data.get("Photometric").reshape((10, 10)).T, + rtol=1e-5, + atol=1e-8, + ) + ) + + ## === test irradiance xmp radiometric === + # verify the result is radiometric + p6 = Project( + speos=speos, + path=str(Path(test_path) / "Prism.speos" / "Prism.speos"), + ) + sim = p6.find(name=".*", name_regex=True, feature_type=SimulationDirect)[0] + sensor_irra = p6.find(name=".*", name_regex=True, feature_type=SensorIrradiance)[0] + sensor_irra.set_dimensions().set_x_sampling(10).set_y_sampling(10) + sensor_irra.set_type_radiometric() + sensor_irra.commit() + speos_results, vtp_results = sim.compute_CPU(export_vtp=True) + assert does_file_exist(vtp_results[0]) + + vtp_data = pv.read(vtp_results[0]).point_data + assert np.allclose(vtp_data.get("Radiometric"), 0.0) is not True + + ## === test irradiance colorimetric === + # verify it has x, photometric, radiometric, z value in vtp file + p7 = Project( + speos=speos, + path=str(Path(test_path) / "Prism.speos" / "Prism.speos"), + ) + sim = p7.find(name=".*", name_regex=True, feature_type=SimulationDirect)[0] + sensor_irra = p7.find(name=".*", name_regex=True, feature_type=SensorIrradiance)[0] + sensor_irra.set_dimensions().set_x_sampling(10).set_y_sampling(10) + sensor_irra.set_type_colorimetric() + sensor_irra.commit() + + speos_results, vtp_results = sim.compute_CPU(export_vtp=True) + assert does_file_exist(vtp_results[0]) + + vtp_data = pv.read(vtp_results[0]).point_data + assert np.allclose(vtp_data.get("X"), 0.0) is not True + assert np.allclose(vtp_data.get("Photometric"), 0.0) is not True + assert np.allclose(vtp_data.get("Radiometric"), 0.0) is not True + assert np.allclose(vtp_data.get("Z"), 0.0) is not True + + ## === test irradiance spectral === + # verify it has x, photometric, radiometric, z value in vtp file + # verify the summing up per spectral layer + p8 = Project( + speos=speos, + path=str(Path(test_path) / "Prism.speos" / "Prism.speos"), + ) + sim = p8.find(name=".*", name_regex=True, feature_type=SimulationDirect)[0] + sensor_irra = p8.find(name=".*", name_regex=True, feature_type=SensorIrradiance)[0] + sensor_irra.set_dimensions().set_x_sampling(10).set_y_sampling(10) + sensor_irra.set_type_spectral() + sensor_irra.commit() + speos_results, vtp_results = sim.compute_CPU(export_vtp=True) + assert does_file_exist(vtp_results[0]) + + vtp_data = pv.read(vtp_results[0]).point_data + assert np.allclose(vtp_data.get("Radiometric"), 0.0) is not True + + # # test radiance photometric + # # test radiance radiometric + # # test radiance colorimetric + # # test radiance spectral From 9af23a7e2643986010dd9cd75e6cbfc9c25a09b1 Mon Sep 17 00:00:00 2001 From: plu Date: Mon, 30 Jun 2025 17:20:24 +0100 Subject: [PATCH 16/23] add skip for unittest as COM API is used as not available on docker --- tests/core/test_simulation.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/core/test_simulation.py b/tests/core/test_simulation.py index b40fa9dc3..3f86c0763 100644 --- a/tests/core/test_simulation.py +++ b/tests/core/test_simulation.py @@ -35,9 +35,11 @@ SimulationInverse, ) from ansys.speos.core.source import SourceLuminaire -from tests.conftest import test_path +from tests.conftest import config, test_path from tests.helper import does_file_exist, remove_file +IS_DOCKER = config.get("SpeosServerOnDocker") + def test_create_direct(speos: Speos): """Test creation of Direct Simulation.""" @@ -781,6 +783,7 @@ def test_export(speos: Speos): remove_file(str(Path(test_path) / "export_test")) +@pytest.mark.skipif(IS_DOCKER, reason="COMAPI is only available locally") def test_export_vtp(speos: Speos): """Test export of xm3 and xmp as vtp files.""" import numpy as np From 24f6b0b6829ced2c2ff9667a6595cd30fa77e144 Mon Sep 17 00:00:00 2001 From: plu Date: Mon, 30 Jun 2025 17:20:51 +0100 Subject: [PATCH 17/23] typo --- tests/core/test_simulation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/test_simulation.py b/tests/core/test_simulation.py index 3f86c0763..e8122a744 100644 --- a/tests/core/test_simulation.py +++ b/tests/core/test_simulation.py @@ -783,7 +783,7 @@ def test_export(speos: Speos): remove_file(str(Path(test_path) / "export_test")) -@pytest.mark.skipif(IS_DOCKER, reason="COMAPI is only available locally") +@pytest.mark.skipif(IS_DOCKER, reason="COM API is only available locally") def test_export_vtp(speos: Speos): """Test export of xm3 and xmp as vtp files.""" import numpy as np From d4e855a8f68228f570350a9a0da94fb807af2c19 Mon Sep 17 00:00:00 2001 From: plu Date: Tue, 1 Jul 2025 22:26:24 +0100 Subject: [PATCH 18/23] fix issue when reading 3d sensor txt file when certain measurements are not activated. --- src/ansys/speos/core/workflow/open_result.py | 29 ++++++++++++-------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/ansys/speos/core/workflow/open_result.py b/src/ansys/speos/core/workflow/open_result.py index 6bb183c77..f7aa36407 100644 --- a/src/ansys/speos/core/workflow/open_result.py +++ b/src/ansys/speos/core/workflow/open_result.py @@ -279,18 +279,25 @@ def export_xm3_vtp(geo_faces: List[face_pb2.Face], file_path: Union[str, Path]) content = file.readlines() for line in content[1:]: line_content = line.split() - xm3_data.append( - _Speos3dData( - x=line_content[0], - y=line_content[1], - z=line_content[2], - illuminance=0.0 if "Illuminance" not in content[0] else line_content[3], - irradiance=0.0 if "Irradiance" not in content[0] else line_content[3], - reflection=0.0 if "Reflection" not in content[0] else line_content[4], - transmission=0.0 if "Transmission" not in content[0] else line_content[5], - absorption=0.0 if "Absorption" not in content[0] else line_content[6], - ) + xm3_data_point = _Speos3dData( + x=float(line_content[0]), + y=float(line_content[1]), + z=float(line_content[2]), ) + for line_content_item in line_content[3:]: + if "Illuminance" in content[0]: + xm3_data_point.illuminance = line_content_item + elif "Irradiance" in content[0]: + xm3_data_point.irradiance = line_content_item + elif "Reflection" in content[0]: + xm3_data_point.reflection = line_content_item + elif "Transmission" in content[0]: + xm3_data_point.transmission = line_content_item + elif "Absorption" in content[0]: + xm3_data_point.absorption = line_content_item + else: + raise ValueError(".xm3 exported text file contains invalid format.") + xm3_data.append(xm3_data_point) vtp_meshes = None for geo in geo_faces: From a95f09f69fdd020b4664d2999c82396865fd2fe6 Mon Sep 17 00:00:00 2001 From: plu Date: Tue, 1 Jul 2025 22:52:25 +0100 Subject: [PATCH 19/23] fix issue when having multiple layers in xm3 --- src/ansys/speos/core/workflow/open_result.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/ansys/speos/core/workflow/open_result.py b/src/ansys/speos/core/workflow/open_result.py index f7aa36407..e74bcfee2 100644 --- a/src/ansys/speos/core/workflow/open_result.py +++ b/src/ansys/speos/core/workflow/open_result.py @@ -277,7 +277,13 @@ def export_xm3_vtp(geo_faces: List[face_pb2.Face], file_path: Union[str, Path]) file = tmp_txt.open("r") xm3_data = [] content = file.readlines() - for line in content[1:]: + skip_line = 1 + try: + float(float(content[1].strip().split()[0])) + skip_line = 1 # only single layer + except ValueError: + skip_line = 2 # separated layer + for line in content[skip_line:]: line_content = line.split() xm3_data_point = _Speos3dData( x=float(line_content[0]), @@ -286,15 +292,15 @@ def export_xm3_vtp(geo_faces: List[face_pb2.Face], file_path: Union[str, Path]) ) for line_content_item in line_content[3:]: if "Illuminance" in content[0]: - xm3_data_point.illuminance = line_content_item + xm3_data_point.illuminance += float(line_content_item) elif "Irradiance" in content[0]: - xm3_data_point.irradiance = line_content_item + xm3_data_point.irradiance += float(line_content_item) elif "Reflection" in content[0]: - xm3_data_point.reflection = line_content_item + xm3_data_point.reflection += float(line_content_item) elif "Transmission" in content[0]: - xm3_data_point.transmission = line_content_item + xm3_data_point.transmission += float(line_content_item) elif "Absorption" in content[0]: - xm3_data_point.absorption = line_content_item + xm3_data_point.absorption += float(line_content_item) else: raise ValueError(".xm3 exported text file contains invalid format.") xm3_data.append(xm3_data_point) From 96dc8f47ea2d9d760661612478020baf6b1d84d3 Mon Sep 17 00:00:00 2001 From: plu Date: Tue, 1 Jul 2025 23:50:57 +0100 Subject: [PATCH 20/23] fix reading multiple layers info in 3d sensor text --- src/ansys/speos/core/workflow/open_result.py | 82 +++++++++++++++----- 1 file changed, 63 insertions(+), 19 deletions(-) diff --git a/src/ansys/speos/core/workflow/open_result.py b/src/ansys/speos/core/workflow/open_result.py index e74bcfee2..3e75f7d5c 100644 --- a/src/ansys/speos/core/workflow/open_result.py +++ b/src/ansys/speos/core/workflow/open_result.py @@ -277,6 +277,7 @@ def export_xm3_vtp(geo_faces: List[face_pb2.Face], file_path: Union[str, Path]) file = tmp_txt.open("r") xm3_data = [] content = file.readlines() + header = content[0].strip().split("\t") skip_line = 1 try: float(float(content[1].strip().split()[0])) @@ -284,26 +285,69 @@ def export_xm3_vtp(geo_faces: List[face_pb2.Face], file_path: Union[str, Path]) except ValueError: skip_line = 2 # separated layer for line in content[skip_line:]: - line_content = line.split() - xm3_data_point = _Speos3dData( - x=float(line_content[0]), - y=float(line_content[1]), - z=float(line_content[2]), + line_content = line.strip().split("\t") + illuminance_indices = [ + i for i, header_item in enumerate(header) if header_item == "Illuminance" + ] + irradiance_indices = [ + i for i, header_item in enumerate(header) if header_item == "Irradiance" + ] + reflection_indices = [ + i for i, header_item in enumerate(header) if "Reflection" in header_item + ] + transmission_indices = [ + i for i, header_item in enumerate(header) if "Transmission" in header_item + ] + absorption_indices = [ + i for i, header_item in enumerate(header) if "Absorption" in header_item + ] + xm3_data.append( + _Speos3dData( + x=float(line_content[0]), + y=float(line_content[1]), + z=float(line_content[2]), + illuminance=sum( + [ + float(item) + for i, item in enumerate(line_content) + if i in illuminance_indices + ], + 0.0, + ), + irradiance=sum( + [ + float(item) + for i, item in enumerate(line_content) + if i in irradiance_indices + ], + 0.0, + ), + reflection=sum( + [ + float(item) + for i, item in enumerate(line_content) + if i in reflection_indices + ], + 0.0, + ), + transmission=sum( + [ + float(item) + for i, item in enumerate(line_content) + if i in transmission_indices + ], + 0.0, + ), + absorption=sum( + [ + float(item) + for i, item in enumerate(line_content) + if i in absorption_indices + ], + 0.0, + ), + ) ) - for line_content_item in line_content[3:]: - if "Illuminance" in content[0]: - xm3_data_point.illuminance += float(line_content_item) - elif "Irradiance" in content[0]: - xm3_data_point.irradiance += float(line_content_item) - elif "Reflection" in content[0]: - xm3_data_point.reflection += float(line_content_item) - elif "Transmission" in content[0]: - xm3_data_point.transmission += float(line_content_item) - elif "Absorption" in content[0]: - xm3_data_point.absorption += float(line_content_item) - else: - raise ValueError(".xm3 exported text file contains invalid format.") - xm3_data.append(xm3_data_point) vtp_meshes = None for geo in geo_faces: From dddb83526e2eabee113807632fbcc04e18081ad9 Mon Sep 17 00:00:00 2001 From: plu Date: Thu, 3 Jul 2025 22:16:00 +0100 Subject: [PATCH 21/23] add support exporting vtp when running compute_GPU avoid getting the path when filter result --- src/ansys/speos/core/simulation.py | 36 +++++++++++++++++------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/src/ansys/speos/core/simulation.py b/src/ansys/speos/core/simulation.py index 8071e4199..41fbf8d2b 100644 --- a/src/ansys/speos/core/simulation.py +++ b/src/ansys/speos/core/simulation.py @@ -274,20 +274,16 @@ def _export_vtp(self) -> List[Path]: """ vtp_files = [] - export_data_xmp = [ - result.path for result in self.result_list if result.path.endswith(".xmp") - ] - if len(export_data_xmp) != 0: + export_data_xmps = [result for result in self.result_list if result.path.endswith(".xmp")] + if len(export_data_xmps) != 0: from ansys.speos.core.workflow.open_result import export_xmp_vtp - for data in export_data_xmp: - exported_vtp = export_xmp_vtp(data) + for data_xmp in export_data_xmps: + exported_vtp = export_xmp_vtp(data_xmp.path) vtp_files.append(exported_vtp) - export_data_xm3 = [ - result.path for result in self.result_list if result.path.endswith(".xm3") - ] - if len(export_data_xm3) != 0: + export_data_xm3s = [result for result in self.result_list if result.path.endswith(".xm3")] + if len(export_data_xm3s) != 0: from ansys.speos.core import Face from ansys.speos.core.sensor import Sensor3DIrradiance from ansys.speos.core.workflow.open_result import export_xm3_vtp @@ -302,12 +298,12 @@ def _export_vtp(self) -> List[Path]: self._project.find(name=geo_path, feature_type=Face)[0]._face for geo_path in geo_paths ] - data = [ + data_xm3 = [ result - for result in export_data_xm3 - if sensor.get(key="result_file_name") in result + for result in export_data_xm3s + if sensor.get(key="result_file_name") in result.path ][0] - exported_vtp = export_xm3_vtp(geos_faces, data) + exported_vtp = export_xm3_vtp(geos_faces, data_xm3.path) vtp_files.append(exported_vtp) return vtp_files @@ -342,9 +338,16 @@ def compute_CPU( return self.result_list, vtp_files return self.result_list - def compute_GPU(self) -> List[job_pb2.Result]: + def compute_GPU( + self, export_vtp: Optional[bool] = False + ) -> tuple[list[Result], list[Path]] | list[Result]: """Compute the simulation on GPU. + Parameters + ---------- + export_vtp: bool, optional + True to generate vtp from the simulation results. + Returns ------- List[ansys.api.speos.job.v2.job_pb2.Result] @@ -352,6 +355,9 @@ def compute_GPU(self) -> List[job_pb2.Result]: """ self._job.job_type = ProtoJob.Type.GPU self.result_list = self._run_job() + if export_vtp: + vtp_files = self._export_vtp() + return self.result_list, vtp_files return self.result_list def _run_job(self) -> List[job_pb2.Result]: From acaec43a75ee03ae5cd73167adde4b5f8ef844fa Mon Sep 17 00:00:00 2001 From: plu Date: Fri, 4 Jul 2025 14:11:18 +0100 Subject: [PATCH 22/23] avoid using .path in the result processing --- src/ansys/speos/core/simulation.py | 48 ++++++++++++++---------------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/src/ansys/speos/core/simulation.py b/src/ansys/speos/core/simulation.py index 41fbf8d2b..1d997e8d8 100644 --- a/src/ansys/speos/core/simulation.py +++ b/src/ansys/speos/core/simulation.py @@ -274,37 +274,33 @@ def _export_vtp(self) -> List[Path]: """ vtp_files = [] - export_data_xmps = [result for result in self.result_list if result.path.endswith(".xmp")] - if len(export_data_xmps) != 0: - from ansys.speos.core.workflow.open_result import export_xmp_vtp - - for data_xmp in export_data_xmps: - exported_vtp = export_xmp_vtp(data_xmp.path) - vtp_files.append(exported_vtp) - - export_data_xm3s = [result for result in self.result_list if result.path.endswith(".xm3")] - if len(export_data_xm3s) != 0: - from ansys.speos.core import Face - from ansys.speos.core.sensor import Sensor3DIrradiance - from ansys.speos.core.workflow.open_result import export_xm3_vtp - - sensor_paths = self.get(key="sensor_paths") - for sensor_path in sensor_paths: - sensors = self._project.find(name=sensor_path, feature_type=Sensor3DIrradiance) - if len(sensors) != 0: - sensor = sensors[0] - geo_paths = sensor.get(key="geo_paths") + from ansys.speos.core import Face + from ansys.speos.core.sensor import Sensor3DIrradiance, SensorIrradiance + from ansys.speos.core.workflow.open_result import export_xm3_vtp, export_xmp_vtp + + sensor_paths = self.get(key="sensor_paths") + for feature in self._project._features: + if feature._name not in sensor_paths: + continue + match feature: + case SensorIrradiance(): + xmp_data = feature.get(key="result_file_name") + exported_vtp = export_xmp_vtp(self, xmp_data) + vtp_files.append(exported_vtp) + case Sensor3DIrradiance(): + xm3_data = feature.get(key="result_file_name") + geo_paths = feature.get(key="geo_paths") geos_faces = [ self._project.find(name=geo_path, feature_type=Face)[0]._face for geo_path in geo_paths ] - data_xm3 = [ - result - for result in export_data_xm3s - if sensor.get(key="result_file_name") in result.path - ][0] - exported_vtp = export_xm3_vtp(geos_faces, data_xm3.path) + exported_vtp = export_xm3_vtp(self, geos_faces, xm3_data) vtp_files.append(exported_vtp) + case _: + warnings.warn( + "feature {} result currently not supported".format(feature._name), + stacklevel=2, + ) return vtp_files def compute_CPU( From c9979760aa1f467fe941614c3f590fe54179687a Mon Sep 17 00:00:00 2001 From: plu Date: Fri, 4 Jul 2025 14:12:00 +0100 Subject: [PATCH 23/23] use _find_correct_result rather than using .path --- src/ansys/speos/core/workflow/open_result.py | 79 ++++++++++++++------ 1 file changed, 56 insertions(+), 23 deletions(-) diff --git a/src/ansys/speos/core/workflow/open_result.py b/src/ansys/speos/core/workflow/open_result.py index 3e75f7d5c..cf08c0c18 100644 --- a/src/ansys/speos/core/workflow/open_result.py +++ b/src/ansys/speos/core/workflow/open_result.py @@ -167,12 +167,17 @@ def open_result_in_viewer( dpf_instance.OpenFile(file_path) dpf_instance.Show(1) - def export_xmp_vtp(file_path: Union[str, Path]) -> Path: + def export_xmp_vtp( + simulation_feature: Union[SimulationDirect, SimulationInverse], + result_name: Union[str, Path], + ) -> Path: """Export an XMP result into vtp file. Parameters ---------- - file_path: Union[str, Path] + simulation_feature : ansys.speos.core.simulation.Simulation + The simulation feature. + result_name: Union[str, Path] file path of an XMP result. Returns @@ -183,8 +188,19 @@ def export_xmp_vtp(file_path: Union[str, Path]) -> Path: """ import pyvista as pv - if not str(file_path).lower().endswith("xmp"): - raise ValueError("Please specify a .xmp file.") + result_name = Path(result_name) + if not str(result_name).lower().endswith(".xmp"): + result_name = result_name.with_name(result_name.name + ".xmp") + file_path = _find_correct_result(simulation_feature, str(result_name)) + + if file_path == "": + raise ValueError( + "No result corresponding to " + + str(result_name) + + " is found in " + + simulation_feature._name + ) + file_path = Path(file_path) dpf_instance = CreateObject("XMPViewer.Application") dpf_instance.OpenFile(str(file_path)) @@ -247,14 +263,20 @@ def export_xmp_vtp(file_path: Union[str, Path]) -> Path: vtp_meshes.save(str(file_path.with_suffix(".vtp"))) return file_path.with_suffix(".vtp") - def export_xm3_vtp(geo_faces: List[face_pb2.Face], file_path: Union[str, Path]) -> Path: + def export_xm3_vtp( + simulation_feature: Union[SimulationDirect, SimulationInverse], + geo_faces: List[face_pb2.Face], + result_name: Union[str, Path], + ) -> Path: """Export an XMP result into vtp file. Parameters ---------- + simulation_feature : ansys.speos.core.simulation.Simulation + The simulation feature. geo_faces: List[face_pb2.Face] list of face geometries. - file_path: Union[str, Path] + result_name: Union[str, Path] file path of an XMP result. Returns @@ -265,8 +287,18 @@ def export_xm3_vtp(geo_faces: List[face_pb2.Face], file_path: Union[str, Path]) """ import pyvista as pv - if not str(file_path).lower().endswith("xm3"): - raise ValueError("Please specify a .xm3 file.") + result_name = Path(result_name) + if not str(result_name).lower().endswith(".xm3"): + result_name = result_name.with_name(result_name.name + ".xm3") + file_path = _find_correct_result(simulation_feature, str(result_name)) + + if file_path == "": + raise ValueError( + "No result corresponding to " + + str(result_name) + + " is found in " + + simulation_feature._name + ) file_path = Path(file_path) dpf_instance = CreateObject("Xm3Viewer.Application") @@ -278,6 +310,22 @@ def export_xm3_vtp(geo_faces: List[face_pb2.Face], file_path: Union[str, Path]) xm3_data = [] content = file.readlines() header = content[0].strip().split("\t") + illuminance_indices = [ + i for i, header_item in enumerate(header) if header_item == "Illuminance" + ] + irradiance_indices = [ + i for i, header_item in enumerate(header) if header_item == "Irradiance" + ] + reflection_indices = [ + i for i, header_item in enumerate(header) if "Reflection" in header_item + ] + transmission_indices = [ + i for i, header_item in enumerate(header) if "Transmission" in header_item + ] + absorption_indices = [ + i for i, header_item in enumerate(header) if "Absorption" in header_item + ] + skip_line = 1 try: float(float(content[1].strip().split()[0])) @@ -286,21 +334,6 @@ def export_xm3_vtp(geo_faces: List[face_pb2.Face], file_path: Union[str, Path]) skip_line = 2 # separated layer for line in content[skip_line:]: line_content = line.strip().split("\t") - illuminance_indices = [ - i for i, header_item in enumerate(header) if header_item == "Illuminance" - ] - irradiance_indices = [ - i for i, header_item in enumerate(header) if header_item == "Irradiance" - ] - reflection_indices = [ - i for i, header_item in enumerate(header) if "Reflection" in header_item - ] - transmission_indices = [ - i for i, header_item in enumerate(header) if "Transmission" in header_item - ] - absorption_indices = [ - i for i, header_item in enumerate(header) if "Absorption" in header_item - ] xm3_data.append( _Speos3dData( x=float(line_content[0]),