subsurface-terra 2025.1.0rc7__tar.gz → 2025.1.0rc10__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. {subsurface_terra-2025.1.0rc7/subsurface_terra.egg-info → subsurface_terra-2025.1.0rc10}/PKG-INFO +1 -1
  2. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/_version.py +1 -1
  3. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/api/__init__.py +2 -0
  4. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/api/interfaces/stream.py +19 -1
  5. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/reader/__init__.py +2 -0
  6. subsurface_terra-2025.1.0rc10/subsurface/modules/reader/mesh/_trimesh_reader.py +433 -0
  7. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/reader/mesh/glb_reader.py +7 -4
  8. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/reader/mesh/mx_reader.py +2 -1
  9. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/reader/mesh/obj_reader.py +19 -5
  10. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/reader/volume/read_volume.py +70 -1
  11. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/reader/wells/read_borehole_interface.py +12 -6
  12. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10/subsurface_terra.egg-info}/PKG-INFO +1 -1
  13. subsurface_terra-2025.1.0rc7/subsurface/modules/reader/mesh/_trimesh_reader.py +0 -229
  14. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/.env.example +0 -0
  15. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/LICENSE +0 -0
  16. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/README.rst +0 -0
  17. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/requirements/requirements.txt +0 -0
  18. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/requirements/requirements_all.txt +0 -0
  19. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/requirements/requirements_dev.txt +0 -0
  20. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/requirements/requirements_geospatial.txt +0 -0
  21. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/requirements/requirements_mesh.txt +0 -0
  22. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/requirements/requirements_opt.txt +0 -0
  23. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/requirements/requirements_plot.txt +0 -0
  24. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/requirements/requirements_traces.txt +0 -0
  25. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/requirements/requirements_volume.txt +0 -0
  26. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/requirements/requirements_wells.txt +0 -0
  27. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/setup.cfg +0 -0
  28. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/setup.py +0 -0
  29. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/__init__.py +0 -0
  30. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/api/interfaces/README.rst +0 -0
  31. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/api/interfaces/__init__.py +0 -0
  32. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/api/reader/__init__.py +0 -0
  33. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/api/reader/read_wells.py +0 -0
  34. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/core/__init__.py +0 -0
  35. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/core/geological_formats/__init__.py +0 -0
  36. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/core/geological_formats/boreholes/__init__.py +0 -0
  37. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/core/geological_formats/boreholes/_combine_trajectories.py +0 -0
  38. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/core/geological_formats/boreholes/boreholes.py +0 -0
  39. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/core/geological_formats/boreholes/collars.py +0 -0
  40. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/core/geological_formats/boreholes/survey.py +0 -0
  41. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/core/geological_formats/fault.py +0 -0
  42. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/core/reader_helpers/__init__.py +0 -0
  43. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/core/reader_helpers/reader_unstruct.py +0 -0
  44. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/core/reader_helpers/readers_data.py +0 -0
  45. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/core/reader_helpers/readers_wells.py +0 -0
  46. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/core/structs/README.rst +0 -0
  47. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/core/structs/__init__.py +0 -0
  48. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/core/structs/base_structures/__init__.py +0 -0
  49. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/core/structs/base_structures/_liquid_earth_mesh.py +0 -0
  50. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/core/structs/base_structures/_unstructured_data_constructor.py +0 -0
  51. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/core/structs/base_structures/base_structures_enum.py +0 -0
  52. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/core/structs/base_structures/structured_data.py +0 -0
  53. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/core/structs/base_structures/unstructured_data.py +0 -0
  54. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/core/structs/structured_elements/__init__.py +0 -0
  55. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/core/structs/structured_elements/octree_mesh.py +0 -0
  56. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/core/structs/structured_elements/structured_grid.py +0 -0
  57. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/core/structs/structured_elements/structured_mesh.py +0 -0
  58. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/core/structs/unstructured_elements/__init__.py +0 -0
  59. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/core/structs/unstructured_elements/line_set.py +0 -0
  60. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/core/structs/unstructured_elements/point_set.py +0 -0
  61. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/core/structs/unstructured_elements/tetrahedron_mesh.py +0 -0
  62. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/core/structs/unstructured_elements/triangular_surface.py +0 -0
  63. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/core/utils/__init__.py +0 -0
  64. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/core/utils/utils_core.py +0 -0
  65. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/__init__.py +0 -0
  66. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/reader/README.rst +0 -0
  67. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/reader/faults/__init__.py +0 -0
  68. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/reader/faults/faults.py +0 -0
  69. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/reader/from_binary.py +0 -0
  70. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/reader/geo_object/__init__.py +0 -0
  71. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/reader/mesh/NOTES.md +0 -0
  72. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/reader/mesh/_GOCAD_mesh.py +0 -0
  73. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/reader/mesh/__init__.py +0 -0
  74. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/reader/mesh/csv_mesh_reader.py +0 -0
  75. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/reader/mesh/dxf_reader.py +0 -0
  76. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/reader/mesh/omf_mesh_reader.py +0 -0
  77. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/reader/mesh/surface_reader.py +0 -0
  78. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/reader/mesh/surfaces_api.py +0 -0
  79. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/reader/petrel/__init__.py +0 -0
  80. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/reader/profiles/__init__.py +0 -0
  81. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/reader/profiles/profiles_core.py +0 -0
  82. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/reader/read_netcdf.py +0 -0
  83. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/reader/topography/__init__.py +0 -0
  84. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/reader/topography/topo_core.py +0 -0
  85. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/reader/volume/__init__.py +0 -0
  86. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/reader/volume/segy_reader.py +0 -0
  87. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/reader/volume/seismic.py +0 -0
  88. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/reader/volume/volume_utils.py +0 -0
  89. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/reader/wells/DEP/__init__.py +0 -0
  90. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/reader/wells/DEP/_well_files_reader.py +0 -0
  91. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/reader/wells/DEP/_wells_api.py +0 -0
  92. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/reader/wells/DEP/_welly_reader.py +0 -0
  93. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/reader/wells/DEP/pandas_to_welly.py +0 -0
  94. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/reader/wells/README.rst +0 -0
  95. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/reader/wells/__init__.py +0 -0
  96. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/reader/wells/_read_to_df.py +0 -0
  97. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/reader/wells/wells_utils.py +0 -0
  98. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/visualization/__init__.py +0 -0
  99. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/visualization/to_pyvista.py +0 -0
  100. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/writer/__init__.py +0 -0
  101. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/writer/to_binary.py +0 -0
  102. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/writer/to_liquid_earth/__init__.py +0 -0
  103. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/writer/to_rex/__init__.py +0 -0
  104. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/writer/to_rex/common.py +0 -0
  105. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/writer/to_rex/data_struct.py +0 -0
  106. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/writer/to_rex/doc/rex-spec-v1.md +0 -0
  107. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/writer/to_rex/doc/right-handed.png +0 -0
  108. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/writer/to_rex/doc/sketchup_example.jpg +0 -0
  109. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/writer/to_rex/gempy_to_rexfile.py +0 -0
  110. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/writer/to_rex/material_encoder.py +0 -0
  111. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/writer/to_rex/mesh_encoder.py +0 -0
  112. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/writer/to_rex/to_rex.py +0 -0
  113. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/modules/writer/to_rex/utils.py +0 -0
  114. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface/optional_requirements.py +0 -0
  115. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface_terra.egg-info/SOURCES.txt +0 -0
  116. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface_terra.egg-info/dependency_links.txt +0 -0
  117. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface_terra.egg-info/not-zip-safe +0 -0
  118. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface_terra.egg-info/requires.txt +0 -0
  119. {subsurface_terra-2025.1.0rc7 → subsurface_terra-2025.1.0rc10}/subsurface_terra.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: subsurface_terra
3
- Version: 2025.1.0rc7
3
+ Version: 2025.1.0rc10
4
4
  Summary: Subsurface data types and utilities. This version is the one used by Terranigma Solutions. Please feel free to take anything in this repository for the original one.
5
5
  Home-page: https://softwareunderground.github.io/subsurface
6
6
  Author: Software Underground
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '2025.1.0rc7'
20
+ __version__ = version = '2025.1.0rc10'
21
21
  __version_tuple__ = version_tuple = (2025, 1, 0)
@@ -7,4 +7,6 @@ from .interfaces.stream import (
7
7
  CSV_volume_stream_to_struct,
8
8
  VTK_stream_to_struct,
9
9
  MX_stream_to_unstruc,
10
+ OBJ_stream_to_trisurf,
11
+ GLTF_stream_to_trisurf
10
12
  )
@@ -1,17 +1,20 @@
1
+ import io
1
2
  from io import BytesIO
2
3
  from typing import TextIO
3
4
 
4
5
  import pandas
5
- from subsurface.modules.reader.volume.volume_utils import interpolate_unstructured_data_to_structured_data
6
6
 
7
+ from ...core.structs import TriSurf
7
8
  from ...core.reader_helpers.reader_unstruct import ReaderUnstructuredHelper
8
9
  from ...core.reader_helpers.readers_data import GenericReaderFilesHelper
9
10
  from ...core.geological_formats import BoreholeSet
10
11
  from ...core.structs.base_structures import UnstructuredData, StructuredData
11
12
 
12
13
  from ...modules import reader
14
+ from ...modules.reader.mesh._trimesh_reader import TriMeshTransformations
13
15
  from ...modules.reader.volume.read_volume import read_volumetric_mesh_to_subsurface, read_VTK_structured_grid
14
16
  from ...modules.reader.mesh.surfaces_api import read_2d_mesh_to_unstruct
17
+ from ...modules.reader.volume.volume_utils import interpolate_unstructured_data_to_structured_data
15
18
 
16
19
  from ..reader.read_wells import read_wells
17
20
 
@@ -39,6 +42,21 @@ def MX_stream_to_unstruc(stream: TextIO) -> list[UnstructuredData]:
39
42
  return list_unstruct
40
43
 
41
44
 
45
+ def OBJ_stream_to_trisurf(obj_stream: TextIO, mtl_stream: list[TextIO],
46
+ texture_stream: list[io.BytesIO], coordinate_system: TriMeshTransformations) -> TriSurf:
47
+ tri_mesh: TriSurf = reader.load_obj_with_trimesh_from_binary(
48
+ obj_stream=obj_stream,
49
+ mtl_stream=mtl_stream,
50
+ texture_stream=texture_stream,
51
+ coord_system=coordinate_system
52
+ )
53
+ return tri_mesh
54
+
55
+
56
+ def GLTF_stream_to_trisurf(gltf_stream: io.BytesIO, coordinate_system: TriMeshTransformations) -> TriSurf:
57
+ tri_mesh: TriSurf = reader.load_gltf_with_trimesh(gltf_stream, coordinate_system)
58
+ return tri_mesh
59
+
42
60
  def VTK_stream_to_struct(stream: BytesIO, attribute_name: str) -> list[StructuredData]:
43
61
  struct = read_VTK_structured_grid(stream, attribute_name)
44
62
  return [struct]
@@ -7,3 +7,5 @@ from .topography.topo_core import read_structured_topography, read_unstructured_
7
7
  from .mesh.omf_mesh_reader import omf_stream_to_unstructs
8
8
  from .mesh.dxf_reader import dxf_stream_to_unstruct_input, dxf_file_to_unstruct_input
9
9
  from .mesh.mx_reader import mx_to_unstruc_from_binary
10
+ from .mesh.obj_reader import load_obj_with_trimesh, load_obj_with_trimesh_from_binary
11
+ from .mesh.glb_reader import load_gltf_with_trimesh
@@ -0,0 +1,433 @@
1
+ import enum
2
+ from typing import Union, TextIO, Optional
3
+ import io
4
+ import os
5
+
6
+ import numpy as np
7
+ from ....core.structs import UnstructuredData
8
+ from .... import optional_requirements
9
+ from ....core.structs import TriSurf, StructuredData
10
+
11
+
12
+ class TriMeshTransformations(enum.Enum):
13
+ RIGHT_HANDED_Z_UP = "right_handed_z_up"
14
+ ORIGINAL = "original"
15
+
16
+
17
+ def load_with_trimesh(path_to_file_or_buffer, file_type: Optional[str] = None,
18
+ coordinate_system: TriMeshTransformations = TriMeshTransformations.RIGHT_HANDED_Z_UP, *, plot=False):
19
+ """
20
+ Load a mesh with trimesh and convert to the specified coordinate system.
21
+
22
+ """
23
+ trimesh = optional_requirements.require_trimesh()
24
+ scene_or_mesh = LoadWithTrimesh.load_with_trimesh(path_to_file_or_buffer, file_type, plot)
25
+
26
+ # Compute a -90° rotation around the X axis
27
+ angle_rad = np.deg2rad(-90)
28
+ transform = trimesh.transformations.rotation_matrix(angle_rad, [1, 0, 0])
29
+
30
+ match coordinate_system:
31
+ case TriMeshTransformations.ORIGINAL:
32
+ return scene_or_mesh
33
+ case TriMeshTransformations.RIGHT_HANDED_Z_UP:
34
+ # Transform from Y-up (modeling software) to Z-up (scientific)
35
+ # This rotates the model so that:
36
+ # Old Y axis → New Z axis (pointing up)
37
+ # Old Z axis → New -Y axis
38
+ # Old X axis → Remains as X axis
39
+ transform = np.array([
40
+ [1, 0, 0, 0], # X → X
41
+ [0, 0, 1, 0], # Y → Z
42
+ [0, 1, 0, 0], # Z → -Y
43
+ [0, 0, 0, 1]
44
+ ])
45
+
46
+ # Apply the coordinate transformation
47
+ if isinstance(scene_or_mesh, trimesh.Scene):
48
+ for geometry in scene_or_mesh.geometry.values():
49
+ geometry.apply_transform(transform)
50
+ else:
51
+ scene_or_mesh.apply_transform(transform)
52
+ case _:
53
+ raise ValueError(f"Invalid coordinate system: {coordinate_system}")
54
+
55
+ return scene_or_mesh
56
+
57
+
58
+ def trimesh_to_unstruct(scene_or_mesh: Union["trimesh.Trimesh", "trimesh.Scene"]) -> TriSurf:
59
+ return TrimeshToSubsurface.trimesh_to_unstruct(scene_or_mesh)
60
+
61
+
62
+ class LoadWithTrimesh:
63
+ @classmethod
64
+ def load_with_trimesh(cls, path_to_file_or_buffer, file_type: Optional[str] = None, plot=False):
65
+ trimesh = optional_requirements.require_trimesh()
66
+ # Load the OBJ with Trimesh using the specified options
67
+ scene_or_mesh = trimesh.load(
68
+ file_obj=path_to_file_or_buffer,
69
+ file_type=file_type,
70
+ force="mesh"
71
+ )
72
+ # Process single mesh vs. scene
73
+ if isinstance(scene_or_mesh, trimesh.Scene):
74
+ print("Loaded a Scene with multiple geometries.")
75
+ cls._process_scene(scene_or_mesh)
76
+ if plot:
77
+ scene_or_mesh.show()
78
+ else:
79
+ print("Loaded a single Trimesh object.")
80
+ print(f" - Vertices: {len(scene_or_mesh.vertices)}")
81
+ print(f" - Faces: {len(scene_or_mesh.faces)}")
82
+ cls.handle_material_info(scene_or_mesh)
83
+ if plot:
84
+ scene_or_mesh.show()
85
+
86
+ return scene_or_mesh
87
+
88
+ @classmethod
89
+ def handle_material_info(cls, geometry):
90
+ """
91
+ Handle and print material information for a single geometry,
92
+ explicitly injecting the PIL image if provided.
93
+ """
94
+ if geometry.visual and hasattr(geometry.visual, 'material'):
95
+ material = geometry.visual.material
96
+
97
+ print("Trimesh material:", material)
98
+
99
+ # If there's already an image reference in the material, let the user know
100
+ if hasattr(material, 'image') and material.image is not None:
101
+ print(" -> Material already has an image:", material.image)
102
+ else:
103
+ print("No material found or no 'material' attribute on this geometry.")
104
+
105
+ @classmethod
106
+ def _process_scene(cls, scene):
107
+ """Process a scene with multiple geometries."""
108
+ geometries = scene.geometry
109
+ assert len(geometries) > 0, "No geometries found in the scene."
110
+
111
+ print(f"Loaded a Scene with {len(scene.geometry)} geometry object(s).")
112
+ for geom_name, geom in geometries.items():
113
+ print(f" Submesh: {geom_name}")
114
+ print(f" - Vertices: {len(geom.vertices)}")
115
+ print(f" - Faces: {len(geom.faces)}")
116
+
117
+ print(f"Geometry '{geom_name}':")
118
+ cls.handle_material_info(geom)
119
+
120
+
121
+ class TrimeshToSubsurface:
122
+ @classmethod
123
+ def trimesh_to_unstruct(cls, scene_or_mesh: Union["trimesh.Trimesh", "trimesh.Scene"]) -> TriSurf:
124
+ """
125
+ Convert a Trimesh or Scene object to a subsurface TriSurf object.
126
+
127
+ This function takes either a `trimesh.Trimesh` object or a `trimesh.Scene`
128
+ object and converts it to a `subsurface.TriSurf` object. If the input is
129
+ a scene containing multiple geometries, it processes all geometries and
130
+ combines them into a single TriSurf object. If the input is a single
131
+ Trimesh object, it directly converts it to a TriSurf object. An error
132
+ is raised if the input is neither a `trimesh.Trimesh` nor a `trimesh.Scene`
133
+ object.
134
+
135
+ Parameters:
136
+ scene_or_mesh (Union[trimesh.Trimesh, trimesh.Scene]):
137
+ Input geometry data, either as a Trimesh object representing
138
+ a single mesh or a Scene object containing multiple geometries.
139
+
140
+ Note:
141
+ ! Multimesh with multiple materials will read the uvs but not the textures since in that case is better
142
+ ! to read directly the multiple images (compressed) whenever the user wants to work with them.
143
+
144
+ Returns:
145
+ subsurface.TriSurf: Converted subsurface representation of the
146
+ provided geometry data.
147
+
148
+ Raises:
149
+ ValueError: If the input is neither a `trimesh.Trimesh` object nor
150
+ a `trimesh.Scene` object.
151
+ """
152
+ trimesh = optional_requirements.require_trimesh()
153
+ if isinstance(scene_or_mesh, trimesh.Scene):
154
+ # Process scene with multiple geometries
155
+ ts = cls._trisurf_from_scene(scene_or_mesh, trimesh)
156
+
157
+ elif isinstance(scene_or_mesh, trimesh.Trimesh):
158
+ ts = cls._trisurf_from_trimesh(scene_or_mesh)
159
+
160
+
161
+ else:
162
+ raise ValueError("Input must be a Trimesh object or a Scene with multiple geometries.")
163
+
164
+ return ts
165
+
166
+ @classmethod
167
+ def _trisurf_from_trimesh(cls, scene_or_mesh):
168
+ # Process single mesh
169
+ tri = scene_or_mesh
170
+ pandas = optional_requirements.require_pandas()
171
+ frame = pandas.DataFrame(tri.face_attributes)
172
+ # Check frame has a valid shape for cells_attr if not make None
173
+ if frame.shape[0] != tri.faces.shape[0]:
174
+ frame = None
175
+ # Get UV coordinates if they exist
176
+ vertex_attr = None
177
+ if hasattr(tri.visual, 'uv') and tri.visual.uv is not None:
178
+ vertex_attr = pandas.DataFrame(
179
+ tri.visual.uv,
180
+ columns=['u', 'v']
181
+ )
182
+ unstruct = UnstructuredData.from_array(
183
+ np.array(tri.vertices),
184
+ np.array(tri.faces),
185
+ cells_attr=frame,
186
+ vertex_attr=vertex_attr,
187
+ xarray_attributes={
188
+ "bounds": tri.bounds.tolist(),
189
+ },
190
+ )
191
+
192
+ texture = cls._extract_texture_from_material(tri)
193
+
194
+ ts = TriSurf(
195
+ mesh=unstruct,
196
+ texture=texture,
197
+ )
198
+ return ts
199
+
200
+ @classmethod
201
+ def _trisurf_from_scene(cls, scene_or_mesh: 'Scene', trimesh: 'trimesh') -> TriSurf:
202
+ pandas = optional_requirements.require_pandas()
203
+ geometries = scene_or_mesh.geometry
204
+ assert len(geometries) > 0, "No geometries found in the scene."
205
+ all_vertex = []
206
+ all_cells = []
207
+ cell_attr = []
208
+ all_vertex_attr = []
209
+ _last_cell = 0
210
+ texture = None
211
+ for i, (geom_name, geom) in enumerate(geometries.items()):
212
+ geom: trimesh.Trimesh
213
+ LoadWithTrimesh.handle_material_info(geom)
214
+
215
+ # Append vertices
216
+ all_vertex.append(np.array(geom.vertices))
217
+
218
+ # Adjust cell indices and append
219
+ cells = np.array(geom.faces)
220
+ if len(all_cells) > 0:
221
+ cells = cells + _last_cell
222
+ all_cells.append(cells)
223
+
224
+ # Create attribute array for this geometry
225
+ cell_attr.append(np.ones(len(cells)) * i)
226
+
227
+ _last_cell = cells.max() + 1
228
+
229
+ # Get UV coordinates if they exist
230
+ if hasattr(geom.visual, 'uv') and geom.visual.uv is not None:
231
+ vertex_attr = pandas.DataFrame(
232
+ geom.visual.uv,
233
+ columns=['u', 'v']
234
+ )
235
+ all_vertex_attr.append(vertex_attr)
236
+
237
+ # Extract texture from material if it is only one geometry
238
+ if len(geometries) == 1:
239
+ texture = cls._extract_texture_from_material(geom)
240
+
241
+ # Create the combined UnstructuredData
242
+ unstruct = UnstructuredData.from_array(
243
+ vertex=np.vstack(all_vertex),
244
+ cells=np.vstack(all_cells),
245
+ vertex_attr=pandas.concat(all_vertex_attr, ignore_index=True) if len(all_vertex_attr) > 0 else None,
246
+ cells_attr=pandas.DataFrame(np.hstack(cell_attr), columns=["Geometry id"]),
247
+ xarray_attributes={
248
+ "bounds": scene_or_mesh.bounds.tolist(),
249
+ },
250
+ )
251
+
252
+ # If there is a texture
253
+ ts = TriSurf(
254
+ mesh=unstruct,
255
+ texture=texture,
256
+ )
257
+
258
+ return ts
259
+
260
+ @classmethod
261
+ def _extract_texture_from_material(cls, geom):
262
+ from PIL.JpegImagePlugin import JpegImageFile
263
+ from PIL.PngImagePlugin import PngImageFile
264
+ import trimesh
265
+
266
+ if geom.visual is None or getattr(geom.visual, 'material', None) is None:
267
+ return None
268
+
269
+ array = np.empty(0)
270
+ if isinstance(geom.visual.material, trimesh.visual.material.SimpleMaterial):
271
+ image: JpegImageFile = geom.visual.material.image
272
+ if image is None:
273
+ return None
274
+ array = np.array(image)
275
+ elif isinstance(geom.visual.material, trimesh.visual.material.PBRMaterial):
276
+ image: PngImageFile = geom.visual.material.baseColorTexture
277
+ array = np.array(image.convert('RGBA'))
278
+
279
+ if image is None:
280
+ return None
281
+ else:
282
+ raise ValueError(f"Unsupported material type: {type(geom.visual.material)}")
283
+
284
+ # Asser that image has 3 channels assert array.shape[2] == 3 from PIL.PngImagePlugin import PngImageFile
285
+ assert array.shape[2] == 3 or array.shape[2] == 4
286
+ texture = StructuredData.from_numpy(array)
287
+ return texture
288
+
289
+ @classmethod
290
+ def _validate_texture_path(cls, texture_path):
291
+ """Validate the texture file path."""
292
+ if texture_path and not texture_path.lower().endswith(('.png', '.jpg', '.jpeg')):
293
+ raise ValueError("Texture path must be a PNG or JPEG file")
294
+
295
+
296
+ class TriMeshReaderFromBlob:
297
+ @classmethod
298
+ def OBJ_stream_to_trisurf(cls, obj_stream: TextIO, mtl_stream: list[TextIO],
299
+ texture_stream: list[io.BytesIO], coord_system: TriMeshTransformations) -> TriSurf:
300
+ """
301
+ Load an OBJ file from a stream and convert it to a TriSurf object.
302
+
303
+ Parameters:
304
+ obj_stream: TextIO containing the OBJ file data (text format)
305
+ mtl_stream: TextIO containing the MTL file data (text format)
306
+ texture_stream: BytesIO containing the texture file data (binary format)
307
+
308
+ Returns:
309
+ TriSurf: The loaded mesh with textures if available
310
+ """
311
+ trimesh = optional_requirements.require_trimesh()
312
+ import tempfile
313
+
314
+ path_in = "file.obj"
315
+
316
+ # Create a temporary directory to store associated files
317
+ with tempfile.TemporaryDirectory() as temp_dir:
318
+ # Write the OBJ content to a temp file
319
+ obj_path = os.path.join(temp_dir, os.path.basename(path_in))
320
+ with open(obj_path, 'w') as f: # Use text mode 'w' for text files
321
+ obj_stream.seek(0)
322
+ f.write(obj_stream.read())
323
+ obj_stream.seek(0)
324
+
325
+ if mtl_stream is not None:
326
+ cls.write_material_files(
327
+ mtl_streams=mtl_stream,
328
+ obj_stream=obj_stream,
329
+ temp_dir=temp_dir,
330
+ texture_streams=texture_stream
331
+ )
332
+
333
+ # Now load the OBJ with all associated files available
334
+ scene_or_mesh = load_with_trimesh(
335
+ path_to_file_or_buffer=obj_path,
336
+ file_type="obj",
337
+ coordinate_system=coord_system
338
+ )
339
+
340
+ # Convert to a TriSurf object
341
+ tri_surf = TrimeshToSubsurface.trimesh_to_unstruct(scene_or_mesh)
342
+
343
+ return tri_surf
344
+
345
+ @classmethod
346
+ def write_material_files(cls, mtl_streams: list[TextIO], obj_stream: TextIO, temp_dir, texture_streams: list[io.BytesIO]):
347
+ # Extract mtl references from the OBJ file
348
+ mtl_files = cls._extract_mtl_references(obj_stream)
349
+ # Download and save MTL files
350
+ for e, mtl_file in enumerate(mtl_files):
351
+ mtl_path = f"{temp_dir}/{mtl_file}" if temp_dir else mtl_file
352
+ mtl_stream = mtl_streams[e] if mtl_streams else None
353
+ try:
354
+ # Save the MTL file to temp directory
355
+ mtl_temp_path = os.path.join(temp_dir, mtl_file)
356
+ with open(mtl_temp_path, 'w') as f: # Use text mode 'w' for text files
357
+ mtl_stream.seek(0)
358
+ f.write(mtl_stream.read())
359
+
360
+ # Extract texture references from MTL
361
+ mtl_stream.seek(0)
362
+ texture_files = cls._extract_texture_references(mtl_stream)
363
+
364
+ if texture_streams is None:
365
+ continue
366
+
367
+ # Download texture files
368
+ for ee, texture_file in enumerate(texture_files):
369
+ texture_path = f"{temp_dir}/{texture_file}" if temp_dir else texture_file
370
+ texture_stream = texture_streams[ee] if texture_streams else None
371
+ try:
372
+ # Save the texture file to temp directory
373
+ with open(os.path.join(temp_dir, texture_file), 'wb') as f: # Binary mode for textures
374
+ texture_stream.seek(0)
375
+ f.write(texture_stream.read())
376
+ except Exception as e:
377
+ print(f"Failed to load texture {texture_file}: {e}")
378
+ except Exception as e:
379
+ print(f"Failed to load MTL file {mtl_file}: {e}")
380
+
381
+ @classmethod
382
+ def _extract_mtl_references(cls, obj_stream):
383
+ """Extract MTL file references from an OBJ file."""
384
+ obj_stream.seek(0)
385
+ mtl_files = []
386
+
387
+ # TextIO stream already contains decoded text, so no need to decode
388
+ obj_text = obj_stream.read()
389
+ obj_stream.seek(0)
390
+
391
+ for line in obj_text.splitlines():
392
+ if line.startswith('mtllib '):
393
+ mtl_name = line.split(None, 1)[1].strip()
394
+ mtl_files.append(mtl_name)
395
+
396
+ return mtl_files
397
+
398
+ @classmethod
399
+ def _extract_texture_references(cls, mtl_stream):
400
+ """
401
+ Extract texture file references from an MTL file.
402
+ Works with both TextIO and BytesIO streams.
403
+
404
+ Parameters:
405
+ mtl_stream: TextIO or BytesIO containing the MTL file data
406
+
407
+ Returns:
408
+ list[str]: List of texture file names referenced in the MTL
409
+ """
410
+ mtl_stream.seek(0)
411
+ texture_files = []
412
+
413
+ # Handle both TextIO and BytesIO
414
+ if isinstance(mtl_stream, io.TextIOWrapper):
415
+ # TextIO stream already contains decoded text
416
+ mtl_text = mtl_stream.read()
417
+ else:
418
+ # BytesIO stream needs to be decoded
419
+ mtl_text = mtl_stream.read().decode('utf-8', errors='replace')
420
+
421
+ mtl_stream.seek(0)
422
+
423
+ for line in mtl_text.splitlines():
424
+ # Check for texture map definitions
425
+ for prefix in ['map_Kd ', 'map_Ka ', 'map_Ks ', 'map_Bump ', 'map_d ']:
426
+ if line.startswith(prefix):
427
+ parts = line.split(None, 1)
428
+ if len(parts) > 1:
429
+ texture_name = parts[1].strip()
430
+ texture_files.append(texture_name)
431
+ break
432
+
433
+ return texture_files
@@ -1,8 +1,11 @@
1
- import subsurface
2
- from subsurface.modules.reader.mesh._trimesh_reader import _load_with_trimesh, trimesh_to_unstruct
1
+ import io
2
+ from typing import Union
3
3
 
4
+ from ....core.structs import TriSurf
5
+ from ._trimesh_reader import load_with_trimesh, trimesh_to_unstruct, TriMeshTransformations
4
6
 
5
- def load_glb_with_trimesh(path_to_glb: str, plot: bool = False) -> subsurface.TriSurf:
7
+
8
+ def load_gltf_with_trimesh(path_to_glb: Union[str | io.BytesIO], coordinate_system: TriMeshTransformations) -> TriSurf:
6
9
  """
7
10
  load_obj_with_trimesh(path_to_glb, plot=False)
8
11
 
@@ -22,6 +25,6 @@ def load_glb_with_trimesh(path_to_glb: str, plot: bool = False) -> subsurface.Tr
22
25
  subsurface.TriSurf
23
26
  A TriSurf object representing the processed 3D surface geometry.
24
27
  """
25
- trimesh = _load_with_trimesh(path_to_glb, plot)
28
+ trimesh = load_with_trimesh(path_to_glb, file_type="glb", coordinate_system=coordinate_system, plot=False)
26
29
  trisurf = trimesh_to_unstruct(trimesh)
27
30
  return trisurf
@@ -157,7 +157,7 @@ def _process_mesh(mesh_lines) -> Optional[GOCADMesh]:
157
157
  continue
158
158
 
159
159
  if in_tface:
160
- if line.startswith('VRTX'):
160
+ if line.startswith('VRTX') or line.startswith('PVRTX'):
161
161
  # Parse vertex line
162
162
  parts = line.split()
163
163
  if len(parts) >= 5:
@@ -167,6 +167,7 @@ def _process_mesh(mesh_lines) -> Optional[GOCADMesh]:
167
167
  vertex_indices.append(vid)
168
168
  vertex_list.append([x, y, z])
169
169
  vid_to_index[vid] = len(vertex_list) - 1
170
+ # If PVRTX then there could be more columns with property values. For now, we are just parsing the vertex coordinates.
170
171
  continue
171
172
  elif line.startswith('ATOM'):
172
173
  # Parse ATOM line
@@ -1,10 +1,24 @@
1
- from typing import Union
1
+ from typing import Union, TextIO
2
+ import io
2
3
 
3
- import subsurface
4
- from subsurface.modules.reader.mesh._trimesh_reader import _load_with_trimesh, trimesh_to_unstruct
4
+ from ._trimesh_reader import load_with_trimesh, trimesh_to_unstruct, TriMeshReaderFromBlob, TriMeshTransformations
5
+ from ....core.structs import TriSurf
5
6
 
6
7
 
7
- def load_obj_with_trimesh(path_to_obj: str, plot: bool = False) -> subsurface.TriSurf:
8
+
9
+ def load_obj_with_trimesh_from_binary(obj_stream: TextIO, mtl_stream: list[TextIO],
10
+ texture_stream: list[io.BytesIO], coord_system: TriMeshTransformations) -> TriSurf:
11
+ tri_surf: TriSurf = TriMeshReaderFromBlob.OBJ_stream_to_trisurf(
12
+ obj_stream=obj_stream,
13
+ mtl_stream=mtl_stream,
14
+ texture_stream=texture_stream,
15
+ coord_system=coord_system
16
+ )
17
+
18
+ return tri_surf
19
+
20
+
21
+ def load_obj_with_trimesh(path_to_obj: str, plot: bool = False) -> TriSurf:
8
22
  """
9
23
  Load and process an OBJ file, returning trimesh-compatible objects.
10
24
 
@@ -34,6 +48,6 @@ def load_obj_with_trimesh(path_to_obj: str, plot: bool = False) -> subsurface.Tr
34
48
  `ValueError`: If the OBJ file could not be properly processed.
35
49
 
36
50
  """
37
- trimesh = _load_with_trimesh(path_to_obj, plot)
51
+ trimesh = load_with_trimesh(path_to_obj, file_type="obj", plot=plot)
38
52
  trisurf = trimesh_to_unstruct(trimesh)
39
53
  return trisurf
@@ -7,6 +7,7 @@ from subsurface.core.structs import StructuredData
7
7
  from .... import optional_requirements
8
8
  from ....core.structs import UnstructuredData
9
9
  from subsurface.core.reader_helpers.readers_data import GenericReaderFilesHelper
10
+ import numpy as np
10
11
  import pandas as pd
11
12
 
12
13
 
@@ -32,7 +33,7 @@ def read_VTK_structured_grid(file_or_buffer: Union[str, BytesIO], active_scalars
32
33
  # If it's a file path, read directly
33
34
  pyvista_obj = pv.read(file_or_buffer)
34
35
  try:
35
- pyvista_struct: pv.ExplicitStructuredGrid = pyvista_obj.cast_to_explicit_structured_grid()
36
+ pyvista_struct: pv.ExplicitStructuredGrid = pv_cast_to_explicit_structured_grid(pyvista_obj)
36
37
  except Exception as e:
37
38
  raise f"The file is not a structured grid: {e}"
38
39
 
@@ -99,3 +100,71 @@ def read_volumetric_mesh_attr_file(reader_helper: GenericReaderFilesHelper) -> p
99
100
  df = pd.read_table(reader_helper.file_or_buffer, **reader_helper.pandas_reader_kwargs)
100
101
  df.columns = df.columns.astype(str).str.strip()
101
102
  return df
103
+
104
+
105
+ def pv_cast_to_explicit_structured_grid(pyvista_object):
106
+
107
+ pv = optional_requirements.require_pyvista()
108
+
109
+ match pyvista_object:
110
+
111
+ case pv.RectilinearGrid() as rectl_grid:
112
+
113
+ return __pv_convert_rectilinear_to_explicit(rectl_grid)
114
+
115
+ case _:
116
+
117
+ return pyvista_object.cast_to_explicit_structured_grid()
118
+
119
+
120
+ def __pv_convert_rectilinear_to_explicit(rectl_grid):
121
+
122
+ pv = optional_requirements.require_pyvista()
123
+
124
+ # Extract the coordinate arrays from the input RectilinearGrid.
125
+ x = np.asarray(rectl_grid.x)
126
+ y = np.asarray(rectl_grid.y)
127
+ z = np.asarray(rectl_grid.z)
128
+
129
+ # Helper function: "double" the coordinates to produce an expanded set
130
+ # that, when processed internally via np.unique, returns the original nodal values.
131
+ def doubled_coords(arr):
132
+ return np.repeat(arr, 2)[1:-1]
133
+
134
+ # Double the coordinate arrays.
135
+ xcorn = doubled_coords(x)
136
+ ycorn = doubled_coords(y)
137
+ zcorn = doubled_coords(z)
138
+
139
+ # Build a complete grid of corner points via meshgrid. Fortran ('F') order ensures
140
+ # the connectivity ordering aligns with VTK's expectations.
141
+ xx, yy, zz = np.meshgrid(xcorn, ycorn, zcorn, indexing='ij')
142
+ corners = np.column_stack((xx.ravel(order='F'),
143
+ yy.ravel(order='F'),
144
+ zz.ravel(order='F')))
145
+
146
+ # The dimensions to pass to the ExplicitStructuredGrid constructor should be
147
+ # the counts of unique coordinates in each direction.
148
+ dims = (len(np.unique(xcorn)),
149
+ len(np.unique(ycorn)),
150
+ len(np.unique(zcorn)))
151
+
152
+ # Create the ExplicitStructuredGrid.
153
+ explicit_grid = pv.ExplicitStructuredGrid(dims, corners)
154
+ explicit_grid.compute_connectivity()
155
+
156
+ # --- Copy associated data arrays ---
157
+
158
+ # Transfer all cell data arrays.
159
+ for name, array in rectl_grid.cell_data.items():
160
+ explicit_grid.cell_data[name] = array.copy()
161
+
162
+ # Transfer all point data arrays.
163
+ for name, array in rectl_grid.point_data.items():
164
+ explicit_grid.point_data[name] = array.copy()
165
+
166
+ # (Optional) Transfer field data as well.
167
+ for name, array in rectl_grid.field_data.items():
168
+ explicit_grid.field_data[name] = array.copy()
169
+
170
+ return explicit_grid