subsurface-terra 2025.1.0rc15__py3-none-any.whl → 2025.1.0rc16__py3-none-any.whl

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 (81) hide show
  1. subsurface/__init__.py +31 -31
  2. subsurface/_version.py +34 -21
  3. subsurface/api/__init__.py +13 -13
  4. subsurface/api/interfaces/__init__.py +3 -3
  5. subsurface/api/interfaces/stream.py +136 -136
  6. subsurface/api/reader/read_wells.py +78 -78
  7. subsurface/core/geological_formats/boreholes/_combine_trajectories.py +117 -117
  8. subsurface/core/geological_formats/boreholes/_map_attrs_to_survey.py +236 -234
  9. subsurface/core/geological_formats/boreholes/_survey_to_unstruct.py +163 -163
  10. subsurface/core/geological_formats/boreholes/boreholes.py +140 -140
  11. subsurface/core/geological_formats/boreholes/collars.py +26 -26
  12. subsurface/core/geological_formats/boreholes/survey.py +86 -86
  13. subsurface/core/geological_formats/fault.py +47 -47
  14. subsurface/core/reader_helpers/reader_unstruct.py +11 -11
  15. subsurface/core/reader_helpers/readers_data.py +130 -130
  16. subsurface/core/reader_helpers/readers_wells.py +13 -13
  17. subsurface/core/structs/__init__.py +3 -3
  18. subsurface/core/structs/base_structures/__init__.py +2 -2
  19. subsurface/core/structs/base_structures/_liquid_earth_mesh.py +121 -121
  20. subsurface/core/structs/base_structures/_unstructured_data_constructor.py +70 -70
  21. subsurface/core/structs/base_structures/base_structures_enum.py +6 -6
  22. subsurface/core/structs/base_structures/structured_data.py +282 -282
  23. subsurface/core/structs/base_structures/unstructured_data.py +319 -319
  24. subsurface/core/structs/structured_elements/octree_mesh.py +10 -10
  25. subsurface/core/structs/structured_elements/structured_grid.py +59 -59
  26. subsurface/core/structs/structured_elements/structured_mesh.py +9 -9
  27. subsurface/core/structs/unstructured_elements/__init__.py +3 -3
  28. subsurface/core/structs/unstructured_elements/line_set.py +72 -72
  29. subsurface/core/structs/unstructured_elements/point_set.py +43 -43
  30. subsurface/core/structs/unstructured_elements/tetrahedron_mesh.py +35 -35
  31. subsurface/core/structs/unstructured_elements/triangular_surface.py +62 -62
  32. subsurface/core/utils/utils_core.py +38 -38
  33. subsurface/modules/reader/__init__.py +13 -13
  34. subsurface/modules/reader/faults/faults.py +80 -80
  35. subsurface/modules/reader/from_binary.py +46 -46
  36. subsurface/modules/reader/mesh/_GOCAD_mesh.py +82 -82
  37. subsurface/modules/reader/mesh/_trimesh_reader.py +447 -447
  38. subsurface/modules/reader/mesh/csv_mesh_reader.py +53 -53
  39. subsurface/modules/reader/mesh/dxf_reader.py +177 -177
  40. subsurface/modules/reader/mesh/glb_reader.py +30 -30
  41. subsurface/modules/reader/mesh/mx_reader.py +232 -232
  42. subsurface/modules/reader/mesh/obj_reader.py +53 -53
  43. subsurface/modules/reader/mesh/omf_mesh_reader.py +43 -43
  44. subsurface/modules/reader/mesh/surface_reader.py +56 -56
  45. subsurface/modules/reader/mesh/surfaces_api.py +41 -41
  46. subsurface/modules/reader/profiles/__init__.py +3 -3
  47. subsurface/modules/reader/profiles/profiles_core.py +197 -197
  48. subsurface/modules/reader/read_netcdf.py +38 -38
  49. subsurface/modules/reader/topography/__init__.py +7 -7
  50. subsurface/modules/reader/topography/topo_core.py +100 -100
  51. subsurface/modules/reader/volume/read_grav3d.py +478 -428
  52. subsurface/modules/reader/volume/read_volume.py +327 -230
  53. subsurface/modules/reader/volume/segy_reader.py +105 -105
  54. subsurface/modules/reader/volume/seismic.py +173 -173
  55. subsurface/modules/reader/volume/volume_utils.py +43 -43
  56. subsurface/modules/reader/wells/DEP/__init__.py +43 -43
  57. subsurface/modules/reader/wells/DEP/_well_files_reader.py +167 -167
  58. subsurface/modules/reader/wells/DEP/_wells_api.py +61 -61
  59. subsurface/modules/reader/wells/DEP/_welly_reader.py +180 -180
  60. subsurface/modules/reader/wells/DEP/pandas_to_welly.py +212 -212
  61. subsurface/modules/reader/wells/_read_to_df.py +57 -57
  62. subsurface/modules/reader/wells/read_borehole_interface.py +148 -148
  63. subsurface/modules/reader/wells/wells_utils.py +68 -68
  64. subsurface/modules/tools/mocking_aux.py +104 -104
  65. subsurface/modules/visualization/__init__.py +2 -2
  66. subsurface/modules/visualization/to_pyvista.py +320 -320
  67. subsurface/modules/writer/to_binary.py +12 -12
  68. subsurface/modules/writer/to_rex/common.py +78 -78
  69. subsurface/modules/writer/to_rex/data_struct.py +74 -74
  70. subsurface/modules/writer/to_rex/gempy_to_rexfile.py +791 -791
  71. subsurface/modules/writer/to_rex/material_encoder.py +44 -44
  72. subsurface/modules/writer/to_rex/mesh_encoder.py +152 -152
  73. subsurface/modules/writer/to_rex/to_rex.py +115 -115
  74. subsurface/modules/writer/to_rex/utils.py +15 -15
  75. subsurface/optional_requirements.py +116 -116
  76. {subsurface_terra-2025.1.0rc15.dist-info → subsurface_terra-2025.1.0rc16.dist-info}/METADATA +194 -194
  77. subsurface_terra-2025.1.0rc16.dist-info/RECORD +98 -0
  78. {subsurface_terra-2025.1.0rc15.dist-info → subsurface_terra-2025.1.0rc16.dist-info}/WHEEL +1 -1
  79. {subsurface_terra-2025.1.0rc15.dist-info → subsurface_terra-2025.1.0rc16.dist-info}/licenses/LICENSE +203 -203
  80. subsurface_terra-2025.1.0rc15.dist-info/RECORD +0 -98
  81. {subsurface_terra-2025.1.0rc15.dist-info → subsurface_terra-2025.1.0rc16.dist-info}/top_level.txt +0 -0
@@ -1,56 +1,56 @@
1
- from .csv_mesh_reader import mesh_csv_to_vertex, mesh_csv_to_cells, mesh_csv_to_attributes
2
- from .dxf_reader import dxf_from_file_to_vertex, dxf_from_stream_to_vertex, DXFEntityType
3
- from subsurface.core.reader_helpers.readers_data import GenericReaderFilesHelper, SupportedFormats
4
- import numpy as np
5
-
6
- __all__ = ['read_mesh_file_to_vertex', 'read_mesh_file_to_cells',
7
- 'read_mesh_file_to_attr', 'cells_from_delaunay']
8
-
9
-
10
- def read_mesh_file_to_vertex(reader_args: GenericReaderFilesHelper) -> np.ndarray:
11
- if reader_args.format is SupportedFormats.CSV:
12
- vertex = mesh_csv_to_vertex(reader_args.file_or_buffer, reader_args.columns_map,
13
- **reader_args.pandas_reader_kwargs)
14
- elif reader_args.format is SupportedFormats.DXF:
15
- vertex = dxf_from_file_to_vertex(
16
- file_path=reader_args.file_or_buffer,
17
- entity_type=reader_args.additional_reader_kwargs.get('entity_type', DXFEntityType.ALL)
18
- )
19
- elif reader_args.format is SupportedFormats.DXFStream:
20
- vertex = dxf_from_stream_to_vertex(reader_args.file_or_buffer)
21
- else:
22
- raise ValueError(f"Subsurface is not able to read the following extension: {reader_args.format}")
23
- return vertex
24
-
25
-
26
- def read_mesh_file_to_cells(reader_args: GenericReaderFilesHelper) -> np.ndarray:
27
- extension = reader_args.format
28
-
29
- if extension == SupportedFormats.CSV:
30
- cells = mesh_csv_to_cells(
31
- path_to_file=reader_args.file_or_buffer,
32
- columns_map=reader_args.columns_map,
33
- **reader_args.pandas_reader_kwargs
34
- )
35
- else:
36
- raise ValueError(f"Subsurface is not able to read the following extension: {extension}")
37
- return cells
38
-
39
-
40
- def read_mesh_file_to_attr(reader_args: GenericReaderFilesHelper):
41
- extension = reader_args.format
42
- if extension == SupportedFormats.CSV:
43
- attr = mesh_csv_to_attributes(reader_args.file_or_buffer,
44
- reader_args.columns_map,
45
- **reader_args.pandas_reader_kwargs)
46
- else:
47
- raise ValueError(f"Subsurface is not able to read the following extension: {extension}")
48
- return attr
49
-
50
-
51
- def cells_from_delaunay(vertex):
52
- import pyvista as pv
53
- a = pv.PolyData(vertex)
54
- b = a.delaunay_2d().faces
55
- cells = b.reshape(-1, 4)[:, 1:]
56
- return cells
1
+ from .csv_mesh_reader import mesh_csv_to_vertex, mesh_csv_to_cells, mesh_csv_to_attributes
2
+ from .dxf_reader import dxf_from_file_to_vertex, dxf_from_stream_to_vertex, DXFEntityType
3
+ from subsurface.core.reader_helpers.readers_data import GenericReaderFilesHelper, SupportedFormats
4
+ import numpy as np
5
+
6
+ __all__ = ['read_mesh_file_to_vertex', 'read_mesh_file_to_cells',
7
+ 'read_mesh_file_to_attr', 'cells_from_delaunay']
8
+
9
+
10
+ def read_mesh_file_to_vertex(reader_args: GenericReaderFilesHelper) -> np.ndarray:
11
+ if reader_args.format is SupportedFormats.CSV:
12
+ vertex = mesh_csv_to_vertex(reader_args.file_or_buffer, reader_args.columns_map,
13
+ **reader_args.pandas_reader_kwargs)
14
+ elif reader_args.format is SupportedFormats.DXF:
15
+ vertex = dxf_from_file_to_vertex(
16
+ file_path=reader_args.file_or_buffer,
17
+ entity_type=reader_args.additional_reader_kwargs.get('entity_type', DXFEntityType.ALL)
18
+ )
19
+ elif reader_args.format is SupportedFormats.DXFStream:
20
+ vertex = dxf_from_stream_to_vertex(reader_args.file_or_buffer)
21
+ else:
22
+ raise ValueError(f"Subsurface is not able to read the following extension: {reader_args.format}")
23
+ return vertex
24
+
25
+
26
+ def read_mesh_file_to_cells(reader_args: GenericReaderFilesHelper) -> np.ndarray:
27
+ extension = reader_args.format
28
+
29
+ if extension == SupportedFormats.CSV:
30
+ cells = mesh_csv_to_cells(
31
+ path_to_file=reader_args.file_or_buffer,
32
+ columns_map=reader_args.columns_map,
33
+ **reader_args.pandas_reader_kwargs
34
+ )
35
+ else:
36
+ raise ValueError(f"Subsurface is not able to read the following extension: {extension}")
37
+ return cells
38
+
39
+
40
+ def read_mesh_file_to_attr(reader_args: GenericReaderFilesHelper):
41
+ extension = reader_args.format
42
+ if extension == SupportedFormats.CSV:
43
+ attr = mesh_csv_to_attributes(reader_args.file_or_buffer,
44
+ reader_args.columns_map,
45
+ **reader_args.pandas_reader_kwargs)
46
+ else:
47
+ raise ValueError(f"Subsurface is not able to read the following extension: {extension}")
48
+ return attr
49
+
50
+
51
+ def cells_from_delaunay(vertex):
52
+ import pyvista as pv
53
+ a = pv.PolyData(vertex)
54
+ b = a.delaunay_2d().faces
55
+ cells = b.reshape(-1, 4)[:, 1:]
56
+ return cells
@@ -1,41 +1,41 @@
1
- import pandas as pd
2
- from typing import Union
3
-
4
- import numpy as np
5
- import warnings
6
-
7
- from .surface_reader import read_mesh_file_to_vertex, read_mesh_file_to_cells, cells_from_delaunay, read_mesh_file_to_attr
8
- from ....core.reader_helpers.reader_unstruct import ReaderUnstructuredHelper
9
- from ....core.structs import UnstructuredData
10
-
11
- from ....core.structs.base_structures.base_structures_enum import SpecialCellCase
12
-
13
-
14
- def read_2d_mesh_to_unstruct(
15
- reader_args: ReaderUnstructuredHelper,
16
- delaunay: bool = True
17
- ) -> UnstructuredData:
18
-
19
- vertex: np.ndarray = read_mesh_file_to_vertex(reader_args.reader_vertex_args)
20
- cells: Union[np.ndarray, SpecialCellCase]
21
- cells_attr: Union[pd.DataFrame, None] = None
22
- vertex_attr: Union[pd.DataFrame, None] = None
23
- if reader_args.reader_cells_args is not None:
24
- cells = read_mesh_file_to_cells(reader_args.reader_cells_args)
25
- elif delaunay:
26
- cells = cells_from_delaunay(vertex)
27
- else:
28
- warnings.warn("No arguments to compute cell")
29
- cells = SpecialCellCase.POINTS
30
- if reader_args.reader_cells_attr_args is not None:
31
- cells_attr: pd.DataFrame = read_mesh_file_to_attr(reader_args.reader_cells_attr_args)
32
- if reader_args.reader_vertex_attr_args is not None:
33
- vertex_attr = read_mesh_file_to_attr(reader_args.reader_vertex_attr_args)
34
-
35
- ud = UnstructuredData.from_array(
36
- vertex=vertex,
37
- cells=cells,
38
- cells_attr=cells_attr,
39
- vertex_attr=vertex_attr,
40
- )
41
- return ud
1
+ import pandas as pd
2
+ from typing import Union
3
+
4
+ import numpy as np
5
+ import warnings
6
+
7
+ from .surface_reader import read_mesh_file_to_vertex, read_mesh_file_to_cells, cells_from_delaunay, read_mesh_file_to_attr
8
+ from ....core.reader_helpers.reader_unstruct import ReaderUnstructuredHelper
9
+ from ....core.structs import UnstructuredData
10
+
11
+ from ....core.structs.base_structures.base_structures_enum import SpecialCellCase
12
+
13
+
14
+ def read_2d_mesh_to_unstruct(
15
+ reader_args: ReaderUnstructuredHelper,
16
+ delaunay: bool = True
17
+ ) -> UnstructuredData:
18
+
19
+ vertex: np.ndarray = read_mesh_file_to_vertex(reader_args.reader_vertex_args)
20
+ cells: Union[np.ndarray, SpecialCellCase]
21
+ cells_attr: Union[pd.DataFrame, None] = None
22
+ vertex_attr: Union[pd.DataFrame, None] = None
23
+ if reader_args.reader_cells_args is not None:
24
+ cells = read_mesh_file_to_cells(reader_args.reader_cells_args)
25
+ elif delaunay:
26
+ cells = cells_from_delaunay(vertex)
27
+ else:
28
+ warnings.warn("No arguments to compute cell")
29
+ cells = SpecialCellCase.POINTS
30
+ if reader_args.reader_cells_attr_args is not None:
31
+ cells_attr: pd.DataFrame = read_mesh_file_to_attr(reader_args.reader_cells_attr_args)
32
+ if reader_args.reader_vertex_attr_args is not None:
33
+ vertex_attr = read_mesh_file_to_attr(reader_args.reader_vertex_attr_args)
34
+
35
+ ud = UnstructuredData.from_array(
36
+ vertex=vertex,
37
+ cells=cells,
38
+ cells_attr=cells_attr,
39
+ vertex_attr=vertex_attr,
40
+ )
41
+ return ud
@@ -1,3 +1,3 @@
1
-
2
-
3
-
1
+
2
+
3
+
@@ -1,197 +1,197 @@
1
- from dataclasses import dataclass
2
-
3
- from typing import Union, List
4
-
5
- import numpy as np
6
- import pandas as pd
7
-
8
- import subsurface
9
- from subsurface import optional_requirements
10
- from subsurface.core.structs.unstructured_elements import TriSurf
11
- from ...visualization import to_pyvista_line, to_pyvista_mesh
12
-
13
-
14
- def create_vertical_mesh(coords: np.ndarray, zmin: float, zmax: float):
15
- """
16
- Create a 'vertical curtain' triangular mesh by extruding the line of points
17
- in coords from zmin to zmax.
18
-
19
- Parameters
20
- ----------
21
- coords : np.ndarray
22
- An Nx2 array of (x, y) points along the profile.
23
- zmin : float
24
- The lower z-value (bottom of the curtain).
25
- zmax : float
26
- The upper z-value (top of the curtain).
27
-
28
- Returns
29
- -------
30
- vertices : np.ndarray
31
- An array of shape (2N, 3) with xyz-coordinates for all vertices.
32
- faces : np.ndarray
33
- An array of shape (2*(N-1), 3) with triangle indices into `vertices`.
34
- Each row is one triangle [vi, vj, vk].
35
- """
36
- n = len(coords)
37
- if n < 2:
38
- raise ValueError("Need at least 2 points to form a mesh.")
39
-
40
- # Create 2 * n vertices
41
- # “Bottom ring”: z = zmin
42
- # “Top ring”: z = zmax
43
- vertices = np.zeros((2 * n, 3), dtype=float)
44
- vertices[:n, 0:2] = coords
45
- vertices[:n, 2] = zmin
46
- vertices[n:, 0:2] = coords
47
- vertices[n:, 2] = zmax
48
-
49
- # Build faces (2 triangles per segment along the line)
50
- # Bottom ring indices: 0..(n-1)
51
- # Top ring indices: n..(2n-1)
52
- faces = []
53
- for i in range(n - 1):
54
- # Triangle 1: (bottom i, bottom i+1, top i)
55
- faces.append([i, i + 1, i + n])
56
- # Triangle 2: (top i, bottom i+1, top i+1)
57
- faces.append([i + n, i + 1, i + 1 + n])
58
-
59
- faces = np.array(faces, dtype=np.int64)
60
- return vertices, faces
61
-
62
- @dataclass
63
- class TexturedMesh:
64
- unstruct: subsurface.UnstructuredData
65
- struct: subsurface.StructuredData
66
- origin_vector3: np.array
67
- point_u_vector3: np.array
68
- point_v_vector3: np.array
69
-
70
-
71
- def create_mesh_from_trace(linestring, zmax: Union[float, int], zmin: Union[float, int]):
72
- from scipy.spatial.qhull import Delaunay
73
- if isinstance(linestring, list):
74
- linestring_coords = linestring
75
- else: # ! For now I leave it as this because I am not sure with object geopandas returns
76
- linestring_coords = linestring.coords # * Geopandas branch
77
- n = len(list(linestring_coords))
78
- coords = np.array([[x[0] for x in list(linestring_coords)],
79
- [y[1] for y in list(linestring_coords)]]).T
80
- # duplicating the line, once with z=lower and another with z=upper values
81
- vertices = np.zeros((2 * n, 3))
82
- vertices[:n, :2] = coords
83
- vertices[:n, 2] = zmin
84
- vertices[n:, :2] = coords
85
- vertices[n:, 2] = zmax
86
- # i+n --- i+n+1
87
- # |\ |
88
- # | \ |
89
- # | \ |
90
- # | \ |
91
- # i --- i+1
92
-
93
- tri = Delaunay(vertices[:, [0, 2]])
94
- faces = tri.simplices
95
- return vertices, faces
96
-
97
-
98
- def create_tri_surf_from_traces_texture(
99
- path_to_trace,
100
- path_to_texture: Union[List[str]],
101
- idx=None,
102
- uv=None
103
- ):
104
- tri_surf: list[subsurface.TriSurf] = _traces_texture_to_sub_structs(
105
- path_to_trace=path_to_trace,
106
- path_to_texture=path_to_texture,
107
- idx=idx,
108
- uv=uv
109
- )
110
-
111
- pyvista_mesh = [to_pyvista_mesh(i) for i in tri_surf]
112
-
113
- return tri_surf, pyvista_mesh
114
-
115
-
116
- def lineset_from_trace(path_to_trace, idx=None):
117
- import geopandas as gpd
118
-
119
- traces = gpd.read_file(path_to_trace)
120
- traces = _select_traces_by_index(idx, traces)
121
-
122
- mesh_list = []
123
- for index, row in traces.iterrows():
124
- s = len(row['geometry'].coords.xy[0])
125
- vertex = np.array((*row['geometry'].coords.xy,
126
- np.zeros(s))).T
127
- unstruct = subsurface.UnstructuredData.from_array(vertex, 'lines')
128
- ls = subsurface.LineSet(unstruct)
129
- mesh_list.append(to_pyvista_line(ls, radius=10))
130
-
131
- return mesh_list
132
-
133
-
134
- def _traces_texture_to_sub_structs(path_to_trace, path_to_texture, idx, uv=None) -> list[TriSurf]:
135
- gpd = optional_requirements.require_geopandas()
136
- imageio = optional_requirements.require_imageio()
137
-
138
- traces = gpd.read_file(path_to_trace)
139
- traces = _select_traces_by_index(idx, traces)
140
-
141
- textured_mesh: list[TriSurf] = []
142
- n = 0
143
- for index, row in traces.iterrows():
144
- v, e = create_mesh_from_trace(row['geometry'], row['zmax'], row['zmin'])
145
- unstruct_mesh = subsurface.UnstructuredData.from_array(
146
- vertex=v,
147
- cells=e,
148
- vertex_attr=pd.DataFrame(np.zeros((v.shape[0], 2)), columns=['u', 'v'])
149
- )
150
- structured_data_texture = subsurface.StructuredData.from_numpy(
151
- array=np.array(imageio.v3.imread(path_to_texture[index]))
152
- )
153
-
154
- tri_surf = TriSurf(
155
- mesh=unstruct_mesh,
156
- texture=structured_data_texture,
157
- texture_origin=np.array([row['geometry'].xy[0][0], row['geometry'].xy[1][0], row['zmin']]),
158
- texture_point_u=np.array([row['geometry'].xy[0][-1], row['geometry'].xy[1][-1], row['zmin']]),
159
- texture_point_v=np.array([row['geometry'].xy[0][0], row['geometry'].xy[1][0], row['zmax']])
160
- )
161
-
162
- # * Set uv
163
- if uv is not None:
164
- uv_item = pd.DataFrame(uv[n], columns=['u', 'v'])
165
- else:
166
- uv_item = get_uv_from_pyvista(tri_surf)
167
- tri_surf.mesh.points_attributes = uv_item
168
-
169
- textured_mesh.append(tri_surf)
170
- n += 1
171
-
172
- return textured_mesh
173
-
174
-
175
- def get_uv_from_pyvista(tri_surf: TriSurf) -> pd.DataFrame:
176
- pv = optional_requirements.require_pyvista()
177
- _mesh = to_pyvista_mesh(tri_surf)
178
- if tri_surf.texture is None:
179
- raise ValueError('unstructured_element needs texture data to be mapped.')
180
- _mesh.texture_map_to_plane(
181
- inplace=True,
182
- origin=tri_surf.texture_origin,
183
- point_u=tri_surf.texture_point_u,
184
- point_v=tri_surf.texture_point_v
185
- )
186
- tex = pv.numpy_to_texture(tri_surf.texture.values)
187
- _mesh._textures = {0: tex}
188
- from vtkmodules.util.numpy_support import vtk_to_numpy
189
- uv = vtk_to_numpy(_mesh.GetPointData().GetTCoords())
190
-
191
- return pd.DataFrame(uv, columns=['u', 'v'])
192
-
193
-
194
- def _select_traces_by_index(idx, traces):
195
- if idx is not None:
196
- traces = traces.loc[idx]
197
- return traces
1
+ from dataclasses import dataclass
2
+
3
+ from typing import Union, List
4
+
5
+ import numpy as np
6
+ import pandas as pd
7
+
8
+ import subsurface
9
+ from subsurface import optional_requirements
10
+ from subsurface.core.structs.unstructured_elements import TriSurf
11
+ from ...visualization import to_pyvista_line, to_pyvista_mesh
12
+
13
+
14
+ def create_vertical_mesh(coords: np.ndarray, zmin: float, zmax: float):
15
+ """
16
+ Create a 'vertical curtain' triangular mesh by extruding the line of points
17
+ in coords from zmin to zmax.
18
+
19
+ Parameters
20
+ ----------
21
+ coords : np.ndarray
22
+ An Nx2 array of (x, y) points along the profile.
23
+ zmin : float
24
+ The lower z-value (bottom of the curtain).
25
+ zmax : float
26
+ The upper z-value (top of the curtain).
27
+
28
+ Returns
29
+ -------
30
+ vertices : np.ndarray
31
+ An array of shape (2N, 3) with xyz-coordinates for all vertices.
32
+ faces : np.ndarray
33
+ An array of shape (2*(N-1), 3) with triangle indices into `vertices`.
34
+ Each row is one triangle [vi, vj, vk].
35
+ """
36
+ n = len(coords)
37
+ if n < 2:
38
+ raise ValueError("Need at least 2 points to form a mesh.")
39
+
40
+ # Create 2 * n vertices
41
+ # “Bottom ring”: z = zmin
42
+ # “Top ring”: z = zmax
43
+ vertices = np.zeros((2 * n, 3), dtype=float)
44
+ vertices[:n, 0:2] = coords
45
+ vertices[:n, 2] = zmin
46
+ vertices[n:, 0:2] = coords
47
+ vertices[n:, 2] = zmax
48
+
49
+ # Build faces (2 triangles per segment along the line)
50
+ # Bottom ring indices: 0..(n-1)
51
+ # Top ring indices: n..(2n-1)
52
+ faces = []
53
+ for i in range(n - 1):
54
+ # Triangle 1: (bottom i, bottom i+1, top i)
55
+ faces.append([i, i + 1, i + n])
56
+ # Triangle 2: (top i, bottom i+1, top i+1)
57
+ faces.append([i + n, i + 1, i + 1 + n])
58
+
59
+ faces = np.array(faces, dtype=np.int64)
60
+ return vertices, faces
61
+
62
+ @dataclass
63
+ class TexturedMesh:
64
+ unstruct: subsurface.UnstructuredData
65
+ struct: subsurface.StructuredData
66
+ origin_vector3: np.array
67
+ point_u_vector3: np.array
68
+ point_v_vector3: np.array
69
+
70
+
71
+ def create_mesh_from_trace(linestring, zmax: Union[float, int], zmin: Union[float, int]):
72
+ from scipy.spatial.qhull import Delaunay
73
+ if isinstance(linestring, list):
74
+ linestring_coords = linestring
75
+ else: # ! For now I leave it as this because I am not sure with object geopandas returns
76
+ linestring_coords = linestring.coords # * Geopandas branch
77
+ n = len(list(linestring_coords))
78
+ coords = np.array([[x[0] for x in list(linestring_coords)],
79
+ [y[1] for y in list(linestring_coords)]]).T
80
+ # duplicating the line, once with z=lower and another with z=upper values
81
+ vertices = np.zeros((2 * n, 3))
82
+ vertices[:n, :2] = coords
83
+ vertices[:n, 2] = zmin
84
+ vertices[n:, :2] = coords
85
+ vertices[n:, 2] = zmax
86
+ # i+n --- i+n+1
87
+ # |\ |
88
+ # | \ |
89
+ # | \ |
90
+ # | \ |
91
+ # i --- i+1
92
+
93
+ tri = Delaunay(vertices[:, [0, 2]])
94
+ faces = tri.simplices
95
+ return vertices, faces
96
+
97
+
98
+ def create_tri_surf_from_traces_texture(
99
+ path_to_trace,
100
+ path_to_texture: Union[List[str]],
101
+ idx=None,
102
+ uv=None
103
+ ):
104
+ tri_surf: list[subsurface.TriSurf] = _traces_texture_to_sub_structs(
105
+ path_to_trace=path_to_trace,
106
+ path_to_texture=path_to_texture,
107
+ idx=idx,
108
+ uv=uv
109
+ )
110
+
111
+ pyvista_mesh = [to_pyvista_mesh(i) for i in tri_surf]
112
+
113
+ return tri_surf, pyvista_mesh
114
+
115
+
116
+ def lineset_from_trace(path_to_trace, idx=None):
117
+ import geopandas as gpd
118
+
119
+ traces = gpd.read_file(path_to_trace)
120
+ traces = _select_traces_by_index(idx, traces)
121
+
122
+ mesh_list = []
123
+ for index, row in traces.iterrows():
124
+ s = len(row['geometry'].coords.xy[0])
125
+ vertex = np.array((*row['geometry'].coords.xy,
126
+ np.zeros(s))).T
127
+ unstruct = subsurface.UnstructuredData.from_array(vertex, 'lines')
128
+ ls = subsurface.LineSet(unstruct)
129
+ mesh_list.append(to_pyvista_line(ls, radius=10))
130
+
131
+ return mesh_list
132
+
133
+
134
+ def _traces_texture_to_sub_structs(path_to_trace, path_to_texture, idx, uv=None) -> list[TriSurf]:
135
+ gpd = optional_requirements.require_geopandas()
136
+ imageio = optional_requirements.require_imageio()
137
+
138
+ traces = gpd.read_file(path_to_trace)
139
+ traces = _select_traces_by_index(idx, traces)
140
+
141
+ textured_mesh: list[TriSurf] = []
142
+ n = 0
143
+ for index, row in traces.iterrows():
144
+ v, e = create_mesh_from_trace(row['geometry'], row['zmax'], row['zmin'])
145
+ unstruct_mesh = subsurface.UnstructuredData.from_array(
146
+ vertex=v,
147
+ cells=e,
148
+ vertex_attr=pd.DataFrame(np.zeros((v.shape[0], 2)), columns=['u', 'v'])
149
+ )
150
+ structured_data_texture = subsurface.StructuredData.from_numpy(
151
+ array=np.array(imageio.v3.imread(path_to_texture[index]))
152
+ )
153
+
154
+ tri_surf = TriSurf(
155
+ mesh=unstruct_mesh,
156
+ texture=structured_data_texture,
157
+ texture_origin=np.array([row['geometry'].xy[0][0], row['geometry'].xy[1][0], row['zmin']]),
158
+ texture_point_u=np.array([row['geometry'].xy[0][-1], row['geometry'].xy[1][-1], row['zmin']]),
159
+ texture_point_v=np.array([row['geometry'].xy[0][0], row['geometry'].xy[1][0], row['zmax']])
160
+ )
161
+
162
+ # * Set uv
163
+ if uv is not None:
164
+ uv_item = pd.DataFrame(uv[n], columns=['u', 'v'])
165
+ else:
166
+ uv_item = get_uv_from_pyvista(tri_surf)
167
+ tri_surf.mesh.points_attributes = uv_item
168
+
169
+ textured_mesh.append(tri_surf)
170
+ n += 1
171
+
172
+ return textured_mesh
173
+
174
+
175
+ def get_uv_from_pyvista(tri_surf: TriSurf) -> pd.DataFrame:
176
+ pv = optional_requirements.require_pyvista()
177
+ _mesh = to_pyvista_mesh(tri_surf)
178
+ if tri_surf.texture is None:
179
+ raise ValueError('unstructured_element needs texture data to be mapped.')
180
+ _mesh.texture_map_to_plane(
181
+ inplace=True,
182
+ origin=tri_surf.texture_origin,
183
+ point_u=tri_surf.texture_point_u,
184
+ point_v=tri_surf.texture_point_v
185
+ )
186
+ tex = pv.numpy_to_texture(tri_surf.texture.values)
187
+ _mesh._textures = {0: tex}
188
+ from vtkmodules.util.numpy_support import vtk_to_numpy
189
+ uv = vtk_to_numpy(_mesh.GetPointData().GetTCoords())
190
+
191
+ return pd.DataFrame(uv, columns=['u', 'v'])
192
+
193
+
194
+ def _select_traces_by_index(idx, traces):
195
+ if idx is not None:
196
+ traces = traces.loc[idx]
197
+ return traces