subsurface-terra 2025.1.0rc14__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.
- subsurface/__init__.py +31 -31
- subsurface/_version.py +34 -21
- subsurface/api/__init__.py +13 -13
- subsurface/api/interfaces/__init__.py +3 -3
- subsurface/api/interfaces/stream.py +136 -136
- subsurface/api/reader/read_wells.py +78 -78
- subsurface/core/geological_formats/boreholes/_combine_trajectories.py +117 -117
- subsurface/core/geological_formats/boreholes/_map_attrs_to_survey.py +236 -0
- subsurface/core/geological_formats/boreholes/_survey_to_unstruct.py +163 -0
- subsurface/core/geological_formats/boreholes/boreholes.py +140 -116
- subsurface/core/geological_formats/boreholes/collars.py +26 -26
- subsurface/core/geological_formats/boreholes/survey.py +86 -380
- subsurface/core/geological_formats/fault.py +47 -47
- subsurface/core/reader_helpers/reader_unstruct.py +11 -11
- subsurface/core/reader_helpers/readers_data.py +130 -130
- subsurface/core/reader_helpers/readers_wells.py +13 -13
- subsurface/core/structs/__init__.py +3 -3
- subsurface/core/structs/base_structures/__init__.py +2 -2
- subsurface/core/structs/base_structures/_liquid_earth_mesh.py +121 -121
- subsurface/core/structs/base_structures/_unstructured_data_constructor.py +70 -70
- subsurface/core/structs/base_structures/base_structures_enum.py +6 -6
- subsurface/core/structs/base_structures/structured_data.py +282 -282
- subsurface/core/structs/base_structures/unstructured_data.py +319 -319
- subsurface/core/structs/structured_elements/octree_mesh.py +10 -10
- subsurface/core/structs/structured_elements/structured_grid.py +59 -59
- subsurface/core/structs/structured_elements/structured_mesh.py +9 -9
- subsurface/core/structs/unstructured_elements/__init__.py +3 -3
- subsurface/core/structs/unstructured_elements/line_set.py +72 -72
- subsurface/core/structs/unstructured_elements/point_set.py +43 -43
- subsurface/core/structs/unstructured_elements/tetrahedron_mesh.py +35 -35
- subsurface/core/structs/unstructured_elements/triangular_surface.py +62 -62
- subsurface/core/utils/utils_core.py +38 -38
- subsurface/modules/reader/__init__.py +13 -13
- subsurface/modules/reader/faults/faults.py +80 -80
- subsurface/modules/reader/from_binary.py +46 -46
- subsurface/modules/reader/mesh/_GOCAD_mesh.py +82 -82
- subsurface/modules/reader/mesh/_trimesh_reader.py +447 -447
- subsurface/modules/reader/mesh/csv_mesh_reader.py +53 -53
- subsurface/modules/reader/mesh/dxf_reader.py +177 -177
- subsurface/modules/reader/mesh/glb_reader.py +30 -30
- subsurface/modules/reader/mesh/mx_reader.py +232 -232
- subsurface/modules/reader/mesh/obj_reader.py +53 -53
- subsurface/modules/reader/mesh/omf_mesh_reader.py +43 -43
- subsurface/modules/reader/mesh/surface_reader.py +56 -56
- subsurface/modules/reader/mesh/surfaces_api.py +41 -41
- subsurface/modules/reader/profiles/__init__.py +3 -3
- subsurface/modules/reader/profiles/profiles_core.py +197 -197
- subsurface/modules/reader/read_netcdf.py +38 -38
- subsurface/modules/reader/topography/__init__.py +7 -7
- subsurface/modules/reader/topography/topo_core.py +100 -100
- subsurface/modules/reader/volume/read_grav3d.py +478 -428
- subsurface/modules/reader/volume/read_volume.py +327 -230
- subsurface/modules/reader/volume/segy_reader.py +105 -105
- subsurface/modules/reader/volume/seismic.py +173 -173
- subsurface/modules/reader/volume/volume_utils.py +43 -43
- subsurface/modules/reader/wells/DEP/__init__.py +43 -43
- subsurface/modules/reader/wells/DEP/_well_files_reader.py +167 -167
- subsurface/modules/reader/wells/DEP/_wells_api.py +61 -61
- subsurface/modules/reader/wells/DEP/_welly_reader.py +180 -180
- subsurface/modules/reader/wells/DEP/pandas_to_welly.py +212 -212
- subsurface/modules/reader/wells/_read_to_df.py +57 -57
- subsurface/modules/reader/wells/read_borehole_interface.py +148 -148
- subsurface/modules/reader/wells/wells_utils.py +68 -68
- subsurface/modules/tools/mocking_aux.py +104 -104
- subsurface/modules/visualization/__init__.py +2 -2
- subsurface/modules/visualization/to_pyvista.py +320 -320
- subsurface/modules/writer/to_binary.py +12 -12
- subsurface/modules/writer/to_rex/common.py +78 -78
- subsurface/modules/writer/to_rex/data_struct.py +74 -74
- subsurface/modules/writer/to_rex/gempy_to_rexfile.py +791 -791
- subsurface/modules/writer/to_rex/material_encoder.py +44 -44
- subsurface/modules/writer/to_rex/mesh_encoder.py +152 -152
- subsurface/modules/writer/to_rex/to_rex.py +115 -115
- subsurface/modules/writer/to_rex/utils.py +15 -15
- subsurface/optional_requirements.py +116 -116
- {subsurface_terra-2025.1.0rc14.dist-info → subsurface_terra-2025.1.0rc16.dist-info}/METADATA +194 -194
- subsurface_terra-2025.1.0rc16.dist-info/RECORD +98 -0
- {subsurface_terra-2025.1.0rc14.dist-info → subsurface_terra-2025.1.0rc16.dist-info}/WHEEL +1 -1
- {subsurface_terra-2025.1.0rc14.dist-info → subsurface_terra-2025.1.0rc16.dist-info}/licenses/LICENSE +203 -203
- subsurface_terra-2025.1.0rc14.dist-info/RECORD +0 -96
- {subsurface_terra-2025.1.0rc14.dist-info → subsurface_terra-2025.1.0rc16.dist-info}/top_level.txt +0 -0
|
@@ -1,232 +1,232 @@
|
|
|
1
|
-
import re
|
|
2
|
-
import warnings
|
|
3
|
-
from typing import Optional, TextIO
|
|
4
|
-
import numpy as np
|
|
5
|
-
from subsurface.core.structs import UnstructuredData
|
|
6
|
-
from ._GOCAD_mesh import GOCADMesh
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def mx_to_unstruc_from_binary(stream: TextIO) -> UnstructuredData:
|
|
10
|
-
content = stream.read()
|
|
11
|
-
goCADMeshes = _parse_lines(content)
|
|
12
|
-
unstruct_data = _meshes_to_unstruct(goCADMeshes)
|
|
13
|
-
return unstruct_data
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def mx_to_unstruct_from_file(filename: str) -> UnstructuredData:
|
|
17
|
-
# Split the file into meshes
|
|
18
|
-
with open(filename, 'r') as f:
|
|
19
|
-
content: str = f.read()
|
|
20
|
-
goCAD_meshes = _parse_lines(content)
|
|
21
|
-
unstruct_data = _meshes_to_unstruct(goCAD_meshes)
|
|
22
|
-
|
|
23
|
-
return unstruct_data
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def _parse_lines(content: str) -> list[GOCADMesh]:
|
|
27
|
-
meshes = []
|
|
28
|
-
mesh_blocks = re.split(r'(?=GOCAD TSurf 1)', content)
|
|
29
|
-
# Remove any empty strings from the list
|
|
30
|
-
|
|
31
|
-
mesh_blocks = [block for block in mesh_blocks if block.strip()]
|
|
32
|
-
# Use multiprocessing Pool to parse meshes in parallel
|
|
33
|
-
# with Pool(processes=cpu_count()) as pool:
|
|
34
|
-
# meshes = pool.map(process_mesh, mesh_blocks)
|
|
35
|
-
for mesh_block in mesh_blocks:
|
|
36
|
-
mesh_lines = mesh_block.split('\n')
|
|
37
|
-
mesh = _process_mesh(mesh_lines)
|
|
38
|
-
if mesh:
|
|
39
|
-
meshes.append(mesh)
|
|
40
|
-
return meshes
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def _meshes_to_unstruct(meshes: list[GOCADMesh]) -> UnstructuredData:
|
|
44
|
-
# ? I added this function to the Solutions class
|
|
45
|
-
n_meshes = len(meshes)
|
|
46
|
-
|
|
47
|
-
vertex_array = np.concatenate([meshes[i].vertices for i in range(n_meshes)])
|
|
48
|
-
simplex_array = np.concatenate([meshes[i].vectorized_edges for i in range(n_meshes)])
|
|
49
|
-
unc, count = np.unique(simplex_array, axis=0, return_counts=True)
|
|
50
|
-
|
|
51
|
-
# * Prepare the simplex array
|
|
52
|
-
simplex_array = meshes[0].vectorized_edges
|
|
53
|
-
adder = 0
|
|
54
|
-
for i in range(1, n_meshes):
|
|
55
|
-
adder += np.max(meshes[i - 1].vectorized_edges) + 1
|
|
56
|
-
add_mesh = meshes[i].vectorized_edges + adder
|
|
57
|
-
simplex_array = np.append(simplex_array, add_mesh, axis=0)
|
|
58
|
-
|
|
59
|
-
# * Prepare the cells_attr array
|
|
60
|
-
ids_array = np.ones(simplex_array.shape[0])
|
|
61
|
-
l0 = 0
|
|
62
|
-
id = 1
|
|
63
|
-
for mesh in meshes:
|
|
64
|
-
l1 = l0 + mesh.vectorized_edges.shape[0]
|
|
65
|
-
ids_array[l0:l1] = id
|
|
66
|
-
l0 = l1
|
|
67
|
-
id += 1
|
|
68
|
-
|
|
69
|
-
# * Create the unstructured data
|
|
70
|
-
import pandas as pd
|
|
71
|
-
unstructured_data = UnstructuredData.from_array(
|
|
72
|
-
vertex=vertex_array,
|
|
73
|
-
cells=simplex_array,
|
|
74
|
-
cells_attr=pd.DataFrame(ids_array, columns=['id']) # TODO: We have to create an array with the shape of simplex array with the id of each simplex
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
return unstructured_data
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def _process_mesh(mesh_lines) -> Optional[GOCADMesh]:
|
|
81
|
-
mesh = GOCADMesh()
|
|
82
|
-
in_header = False
|
|
83
|
-
in_coord_sys = False
|
|
84
|
-
in_property_class_header = False
|
|
85
|
-
in_tface = False
|
|
86
|
-
current_property_class_header = {}
|
|
87
|
-
vertex_list = []
|
|
88
|
-
vertex_indices = []
|
|
89
|
-
triangle_list = []
|
|
90
|
-
vid_to_index = {} # Map from vertex id to index in vertex_list
|
|
91
|
-
|
|
92
|
-
for line in mesh_lines:
|
|
93
|
-
line = line.strip()
|
|
94
|
-
|
|
95
|
-
if line.startswith('HEADER {'):
|
|
96
|
-
in_header = True
|
|
97
|
-
continue
|
|
98
|
-
|
|
99
|
-
if in_header:
|
|
100
|
-
if line == '}':
|
|
101
|
-
in_header = False
|
|
102
|
-
else:
|
|
103
|
-
key_value = line.split(':', 1)
|
|
104
|
-
if len(key_value) == 2:
|
|
105
|
-
key, value = key_value
|
|
106
|
-
mesh.header[key.strip()] = value.strip()
|
|
107
|
-
else:
|
|
108
|
-
parts = line.split()
|
|
109
|
-
if len(parts) == 2:
|
|
110
|
-
key, value = parts
|
|
111
|
-
mesh.header[key.strip()] = value.strip()
|
|
112
|
-
else:
|
|
113
|
-
mesh.header[line.strip()] = None
|
|
114
|
-
continue
|
|
115
|
-
|
|
116
|
-
if line.startswith('GOCAD_ORIGINAL_COORDINATE_SYSTEM'):
|
|
117
|
-
in_coord_sys = True
|
|
118
|
-
continue
|
|
119
|
-
if in_coord_sys:
|
|
120
|
-
if line == 'END_ORIGINAL_COORDINATE_SYSTEM':
|
|
121
|
-
in_coord_sys = False
|
|
122
|
-
else:
|
|
123
|
-
key_value = line.split(' ', 1)
|
|
124
|
-
if len(key_value) == 2:
|
|
125
|
-
key, value = key_value
|
|
126
|
-
mesh.coordinate_system[key.strip()] = value.strip()
|
|
127
|
-
else:
|
|
128
|
-
mesh.coordinate_system[line.strip()] = None
|
|
129
|
-
continue
|
|
130
|
-
|
|
131
|
-
if line.startswith('PROPERTY_CLASS_HEADER'):
|
|
132
|
-
in_property_class_header = True
|
|
133
|
-
current_property_class_header = {'name': line[len('PROPERTY_CLASS_HEADER'):].strip()}
|
|
134
|
-
continue
|
|
135
|
-
|
|
136
|
-
if in_property_class_header:
|
|
137
|
-
if line == '}':
|
|
138
|
-
in_property_class_header = False
|
|
139
|
-
mesh.property_class_headers.append(current_property_class_header)
|
|
140
|
-
current_property_class_header = {}
|
|
141
|
-
else:
|
|
142
|
-
key_value = line.split(':', 1)
|
|
143
|
-
if len(key_value) == 2:
|
|
144
|
-
key, value = key_value
|
|
145
|
-
current_property_class_header[key.strip()] = value.strip()
|
|
146
|
-
else:
|
|
147
|
-
key_value = line.split(' ', 1)
|
|
148
|
-
if len(key_value) == 2:
|
|
149
|
-
key, value = key_value
|
|
150
|
-
current_property_class_header[key.strip()] = value.strip()
|
|
151
|
-
else:
|
|
152
|
-
current_property_class_header[line.strip()] = None
|
|
153
|
-
continue
|
|
154
|
-
|
|
155
|
-
if line == 'TFACE':
|
|
156
|
-
in_tface = True
|
|
157
|
-
continue
|
|
158
|
-
|
|
159
|
-
if in_tface:
|
|
160
|
-
if line.startswith('VRTX') or line.startswith('PVRTX'):
|
|
161
|
-
# Parse vertex line
|
|
162
|
-
parts = line.split()
|
|
163
|
-
if len(parts) >= 5:
|
|
164
|
-
_, vid, x, y, z = parts[:5]
|
|
165
|
-
vid = int(vid)
|
|
166
|
-
x, y, z = float(x), float(y), float(z)
|
|
167
|
-
vertex_indices.append(vid)
|
|
168
|
-
vertex_list.append([x, y, z])
|
|
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.
|
|
171
|
-
continue
|
|
172
|
-
elif line.startswith('ATOM'):
|
|
173
|
-
# Parse ATOM line
|
|
174
|
-
parts = line.split()
|
|
175
|
-
if len(parts) == 3:
|
|
176
|
-
_, vid, ref_vid = parts
|
|
177
|
-
vid = int(vid)
|
|
178
|
-
ref_vid = int(ref_vid)
|
|
179
|
-
if ref_vid in vid_to_index:
|
|
180
|
-
ref_index = vid_to_index[ref_vid]
|
|
181
|
-
coord = vertex_list[ref_index]
|
|
182
|
-
vertex_indices.append(vid)
|
|
183
|
-
vertex_list.append(coord)
|
|
184
|
-
vid_to_index[vid] = len(vertex_list) - 1
|
|
185
|
-
else:
|
|
186
|
-
warnings.warn(f"Reference vertex {ref_vid} not found for ATOM {vid}")
|
|
187
|
-
continue
|
|
188
|
-
elif line.startswith('TRGL'):
|
|
189
|
-
# Parse triangle line
|
|
190
|
-
parts = line.split()
|
|
191
|
-
if len(parts) == 4:
|
|
192
|
-
_, v1, v2, v3 = parts
|
|
193
|
-
elif len(parts) == 3:
|
|
194
|
-
v1, v2, v3 = parts
|
|
195
|
-
else:
|
|
196
|
-
continue
|
|
197
|
-
triangle_list.append([int(v1), int(v2), int(v3)])
|
|
198
|
-
continue
|
|
199
|
-
elif line.startswith('BSTONE'):
|
|
200
|
-
_, value = line.split()
|
|
201
|
-
mesh.bstones.append(int(value))
|
|
202
|
-
continue
|
|
203
|
-
elif line.startswith('BORDER'):
|
|
204
|
-
parts = line.split()
|
|
205
|
-
if len(parts) >= 4:
|
|
206
|
-
_, bid, v1, v2 = parts[:4]
|
|
207
|
-
mesh.borders.append({'id': int(bid), 'v1': int(v1), 'v2': int(v2)})
|
|
208
|
-
continue
|
|
209
|
-
elif line == 'END':
|
|
210
|
-
in_tface = False
|
|
211
|
-
continue
|
|
212
|
-
else:
|
|
213
|
-
pass
|
|
214
|
-
continue
|
|
215
|
-
|
|
216
|
-
# Other lines, possibly store as metadata
|
|
217
|
-
if line:
|
|
218
|
-
key_value = line.split(':', 1)
|
|
219
|
-
if len(key_value) == 2:
|
|
220
|
-
key, value = key_value
|
|
221
|
-
mesh.metadata[key.strip()] = value.strip()
|
|
222
|
-
else:
|
|
223
|
-
pass
|
|
224
|
-
|
|
225
|
-
# Convert lists to NumPy arrays
|
|
226
|
-
mesh.vertices = np.array(vertex_list)
|
|
227
|
-
mesh.vertex_indices = np.array(vertex_indices)
|
|
228
|
-
mesh.edges = np.array(triangle_list)
|
|
229
|
-
|
|
230
|
-
# Check the number of vertices, indices, and triangles to avoid errors (optional)
|
|
231
|
-
|
|
232
|
-
return mesh
|
|
1
|
+
import re
|
|
2
|
+
import warnings
|
|
3
|
+
from typing import Optional, TextIO
|
|
4
|
+
import numpy as np
|
|
5
|
+
from subsurface.core.structs import UnstructuredData
|
|
6
|
+
from ._GOCAD_mesh import GOCADMesh
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def mx_to_unstruc_from_binary(stream: TextIO) -> UnstructuredData:
|
|
10
|
+
content = stream.read()
|
|
11
|
+
goCADMeshes = _parse_lines(content)
|
|
12
|
+
unstruct_data = _meshes_to_unstruct(goCADMeshes)
|
|
13
|
+
return unstruct_data
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def mx_to_unstruct_from_file(filename: str) -> UnstructuredData:
|
|
17
|
+
# Split the file into meshes
|
|
18
|
+
with open(filename, 'r') as f:
|
|
19
|
+
content: str = f.read()
|
|
20
|
+
goCAD_meshes = _parse_lines(content)
|
|
21
|
+
unstruct_data = _meshes_to_unstruct(goCAD_meshes)
|
|
22
|
+
|
|
23
|
+
return unstruct_data
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _parse_lines(content: str) -> list[GOCADMesh]:
|
|
27
|
+
meshes = []
|
|
28
|
+
mesh_blocks = re.split(r'(?=GOCAD TSurf 1)', content)
|
|
29
|
+
# Remove any empty strings from the list
|
|
30
|
+
|
|
31
|
+
mesh_blocks = [block for block in mesh_blocks if block.strip()]
|
|
32
|
+
# Use multiprocessing Pool to parse meshes in parallel
|
|
33
|
+
# with Pool(processes=cpu_count()) as pool:
|
|
34
|
+
# meshes = pool.map(process_mesh, mesh_blocks)
|
|
35
|
+
for mesh_block in mesh_blocks:
|
|
36
|
+
mesh_lines = mesh_block.split('\n')
|
|
37
|
+
mesh = _process_mesh(mesh_lines)
|
|
38
|
+
if mesh:
|
|
39
|
+
meshes.append(mesh)
|
|
40
|
+
return meshes
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _meshes_to_unstruct(meshes: list[GOCADMesh]) -> UnstructuredData:
|
|
44
|
+
# ? I added this function to the Solutions class
|
|
45
|
+
n_meshes = len(meshes)
|
|
46
|
+
|
|
47
|
+
vertex_array = np.concatenate([meshes[i].vertices for i in range(n_meshes)])
|
|
48
|
+
simplex_array = np.concatenate([meshes[i].vectorized_edges for i in range(n_meshes)])
|
|
49
|
+
unc, count = np.unique(simplex_array, axis=0, return_counts=True)
|
|
50
|
+
|
|
51
|
+
# * Prepare the simplex array
|
|
52
|
+
simplex_array = meshes[0].vectorized_edges
|
|
53
|
+
adder = 0
|
|
54
|
+
for i in range(1, n_meshes):
|
|
55
|
+
adder += np.max(meshes[i - 1].vectorized_edges) + 1
|
|
56
|
+
add_mesh = meshes[i].vectorized_edges + adder
|
|
57
|
+
simplex_array = np.append(simplex_array, add_mesh, axis=0)
|
|
58
|
+
|
|
59
|
+
# * Prepare the cells_attr array
|
|
60
|
+
ids_array = np.ones(simplex_array.shape[0])
|
|
61
|
+
l0 = 0
|
|
62
|
+
id = 1
|
|
63
|
+
for mesh in meshes:
|
|
64
|
+
l1 = l0 + mesh.vectorized_edges.shape[0]
|
|
65
|
+
ids_array[l0:l1] = id
|
|
66
|
+
l0 = l1
|
|
67
|
+
id += 1
|
|
68
|
+
|
|
69
|
+
# * Create the unstructured data
|
|
70
|
+
import pandas as pd
|
|
71
|
+
unstructured_data = UnstructuredData.from_array(
|
|
72
|
+
vertex=vertex_array,
|
|
73
|
+
cells=simplex_array,
|
|
74
|
+
cells_attr=pd.DataFrame(ids_array, columns=['id']) # TODO: We have to create an array with the shape of simplex array with the id of each simplex
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
return unstructured_data
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _process_mesh(mesh_lines) -> Optional[GOCADMesh]:
|
|
81
|
+
mesh = GOCADMesh()
|
|
82
|
+
in_header = False
|
|
83
|
+
in_coord_sys = False
|
|
84
|
+
in_property_class_header = False
|
|
85
|
+
in_tface = False
|
|
86
|
+
current_property_class_header = {}
|
|
87
|
+
vertex_list = []
|
|
88
|
+
vertex_indices = []
|
|
89
|
+
triangle_list = []
|
|
90
|
+
vid_to_index = {} # Map from vertex id to index in vertex_list
|
|
91
|
+
|
|
92
|
+
for line in mesh_lines:
|
|
93
|
+
line = line.strip()
|
|
94
|
+
|
|
95
|
+
if line.startswith('HEADER {'):
|
|
96
|
+
in_header = True
|
|
97
|
+
continue
|
|
98
|
+
|
|
99
|
+
if in_header:
|
|
100
|
+
if line == '}':
|
|
101
|
+
in_header = False
|
|
102
|
+
else:
|
|
103
|
+
key_value = line.split(':', 1)
|
|
104
|
+
if len(key_value) == 2:
|
|
105
|
+
key, value = key_value
|
|
106
|
+
mesh.header[key.strip()] = value.strip()
|
|
107
|
+
else:
|
|
108
|
+
parts = line.split()
|
|
109
|
+
if len(parts) == 2:
|
|
110
|
+
key, value = parts
|
|
111
|
+
mesh.header[key.strip()] = value.strip()
|
|
112
|
+
else:
|
|
113
|
+
mesh.header[line.strip()] = None
|
|
114
|
+
continue
|
|
115
|
+
|
|
116
|
+
if line.startswith('GOCAD_ORIGINAL_COORDINATE_SYSTEM'):
|
|
117
|
+
in_coord_sys = True
|
|
118
|
+
continue
|
|
119
|
+
if in_coord_sys:
|
|
120
|
+
if line == 'END_ORIGINAL_COORDINATE_SYSTEM':
|
|
121
|
+
in_coord_sys = False
|
|
122
|
+
else:
|
|
123
|
+
key_value = line.split(' ', 1)
|
|
124
|
+
if len(key_value) == 2:
|
|
125
|
+
key, value = key_value
|
|
126
|
+
mesh.coordinate_system[key.strip()] = value.strip()
|
|
127
|
+
else:
|
|
128
|
+
mesh.coordinate_system[line.strip()] = None
|
|
129
|
+
continue
|
|
130
|
+
|
|
131
|
+
if line.startswith('PROPERTY_CLASS_HEADER'):
|
|
132
|
+
in_property_class_header = True
|
|
133
|
+
current_property_class_header = {'name': line[len('PROPERTY_CLASS_HEADER'):].strip()}
|
|
134
|
+
continue
|
|
135
|
+
|
|
136
|
+
if in_property_class_header:
|
|
137
|
+
if line == '}':
|
|
138
|
+
in_property_class_header = False
|
|
139
|
+
mesh.property_class_headers.append(current_property_class_header)
|
|
140
|
+
current_property_class_header = {}
|
|
141
|
+
else:
|
|
142
|
+
key_value = line.split(':', 1)
|
|
143
|
+
if len(key_value) == 2:
|
|
144
|
+
key, value = key_value
|
|
145
|
+
current_property_class_header[key.strip()] = value.strip()
|
|
146
|
+
else:
|
|
147
|
+
key_value = line.split(' ', 1)
|
|
148
|
+
if len(key_value) == 2:
|
|
149
|
+
key, value = key_value
|
|
150
|
+
current_property_class_header[key.strip()] = value.strip()
|
|
151
|
+
else:
|
|
152
|
+
current_property_class_header[line.strip()] = None
|
|
153
|
+
continue
|
|
154
|
+
|
|
155
|
+
if line == 'TFACE':
|
|
156
|
+
in_tface = True
|
|
157
|
+
continue
|
|
158
|
+
|
|
159
|
+
if in_tface:
|
|
160
|
+
if line.startswith('VRTX') or line.startswith('PVRTX'):
|
|
161
|
+
# Parse vertex line
|
|
162
|
+
parts = line.split()
|
|
163
|
+
if len(parts) >= 5:
|
|
164
|
+
_, vid, x, y, z = parts[:5]
|
|
165
|
+
vid = int(vid)
|
|
166
|
+
x, y, z = float(x), float(y), float(z)
|
|
167
|
+
vertex_indices.append(vid)
|
|
168
|
+
vertex_list.append([x, y, z])
|
|
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.
|
|
171
|
+
continue
|
|
172
|
+
elif line.startswith('ATOM'):
|
|
173
|
+
# Parse ATOM line
|
|
174
|
+
parts = line.split()
|
|
175
|
+
if len(parts) == 3:
|
|
176
|
+
_, vid, ref_vid = parts
|
|
177
|
+
vid = int(vid)
|
|
178
|
+
ref_vid = int(ref_vid)
|
|
179
|
+
if ref_vid in vid_to_index:
|
|
180
|
+
ref_index = vid_to_index[ref_vid]
|
|
181
|
+
coord = vertex_list[ref_index]
|
|
182
|
+
vertex_indices.append(vid)
|
|
183
|
+
vertex_list.append(coord)
|
|
184
|
+
vid_to_index[vid] = len(vertex_list) - 1
|
|
185
|
+
else:
|
|
186
|
+
warnings.warn(f"Reference vertex {ref_vid} not found for ATOM {vid}")
|
|
187
|
+
continue
|
|
188
|
+
elif line.startswith('TRGL'):
|
|
189
|
+
# Parse triangle line
|
|
190
|
+
parts = line.split()
|
|
191
|
+
if len(parts) == 4:
|
|
192
|
+
_, v1, v2, v3 = parts
|
|
193
|
+
elif len(parts) == 3:
|
|
194
|
+
v1, v2, v3 = parts
|
|
195
|
+
else:
|
|
196
|
+
continue
|
|
197
|
+
triangle_list.append([int(v1), int(v2), int(v3)])
|
|
198
|
+
continue
|
|
199
|
+
elif line.startswith('BSTONE'):
|
|
200
|
+
_, value = line.split()
|
|
201
|
+
mesh.bstones.append(int(value))
|
|
202
|
+
continue
|
|
203
|
+
elif line.startswith('BORDER'):
|
|
204
|
+
parts = line.split()
|
|
205
|
+
if len(parts) >= 4:
|
|
206
|
+
_, bid, v1, v2 = parts[:4]
|
|
207
|
+
mesh.borders.append({'id': int(bid), 'v1': int(v1), 'v2': int(v2)})
|
|
208
|
+
continue
|
|
209
|
+
elif line == 'END':
|
|
210
|
+
in_tface = False
|
|
211
|
+
continue
|
|
212
|
+
else:
|
|
213
|
+
pass
|
|
214
|
+
continue
|
|
215
|
+
|
|
216
|
+
# Other lines, possibly store as metadata
|
|
217
|
+
if line:
|
|
218
|
+
key_value = line.split(':', 1)
|
|
219
|
+
if len(key_value) == 2:
|
|
220
|
+
key, value = key_value
|
|
221
|
+
mesh.metadata[key.strip()] = value.strip()
|
|
222
|
+
else:
|
|
223
|
+
pass
|
|
224
|
+
|
|
225
|
+
# Convert lists to NumPy arrays
|
|
226
|
+
mesh.vertices = np.array(vertex_list)
|
|
227
|
+
mesh.vertex_indices = np.array(vertex_indices)
|
|
228
|
+
mesh.edges = np.array(triangle_list)
|
|
229
|
+
|
|
230
|
+
# Check the number of vertices, indices, and triangles to avoid errors (optional)
|
|
231
|
+
|
|
232
|
+
return mesh
|
|
@@ -1,53 +1,53 @@
|
|
|
1
|
-
from typing import Union, TextIO
|
|
2
|
-
import io
|
|
3
|
-
|
|
4
|
-
from ._trimesh_reader import load_with_trimesh, trimesh_to_unstruct, TriMeshReaderFromBlob, TriMeshTransformations
|
|
5
|
-
from ....core.structs import TriSurf
|
|
6
|
-
|
|
7
|
-
|
|
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:
|
|
22
|
-
"""
|
|
23
|
-
Load and process an OBJ file, returning trimesh-compatible objects.
|
|
24
|
-
|
|
25
|
-
This function loads an OBJ file using `trimesh`, optionally plots it,
|
|
26
|
-
and converts the loaded mesh or scene into a suitable unstructured format
|
|
27
|
-
using the `trimesh_to_unstruct` function. Depending on the input and the
|
|
28
|
-
contents of the OBJ file, it may return a Trimesh object or a Scene object.
|
|
29
|
-
|
|
30
|
-
Note:
|
|
31
|
-
This implementation does not include the capability to force a PNG as a
|
|
32
|
-
texture if the material does not already have an associated image.
|
|
33
|
-
`trimesh` ignores UVs when this condition occurs. Modifications to
|
|
34
|
-
`trimesh`'s loading function would be necessary to address this limitation.
|
|
35
|
-
|
|
36
|
-
Args:
|
|
37
|
-
path_to_obj: Path to the OBJ file to be loaded.
|
|
38
|
-
This must be a valid file path to a 3D object in OBJ format.
|
|
39
|
-
plot: Boolean flag indicating whether to visually plot the loaded model.
|
|
40
|
-
Defaults to False.
|
|
41
|
-
|
|
42
|
-
Returns:
|
|
43
|
-
A `trimesh.Trimesh` object if a single mesh is loaded, or a `trimesh.Scene`
|
|
44
|
-
object if the file contains multiple meshes or a scene.
|
|
45
|
-
|
|
46
|
-
Raises:
|
|
47
|
-
`FileNotFoundError`: If the provided file path does not exist.
|
|
48
|
-
`ValueError`: If the OBJ file could not be properly processed.
|
|
49
|
-
|
|
50
|
-
"""
|
|
51
|
-
trimesh = load_with_trimesh(path_to_obj, file_type="obj", plot=plot)
|
|
52
|
-
trisurf = trimesh_to_unstruct(trimesh)
|
|
53
|
-
return trisurf
|
|
1
|
+
from typing import Union, TextIO
|
|
2
|
+
import io
|
|
3
|
+
|
|
4
|
+
from ._trimesh_reader import load_with_trimesh, trimesh_to_unstruct, TriMeshReaderFromBlob, TriMeshTransformations
|
|
5
|
+
from ....core.structs import TriSurf
|
|
6
|
+
|
|
7
|
+
|
|
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:
|
|
22
|
+
"""
|
|
23
|
+
Load and process an OBJ file, returning trimesh-compatible objects.
|
|
24
|
+
|
|
25
|
+
This function loads an OBJ file using `trimesh`, optionally plots it,
|
|
26
|
+
and converts the loaded mesh or scene into a suitable unstructured format
|
|
27
|
+
using the `trimesh_to_unstruct` function. Depending on the input and the
|
|
28
|
+
contents of the OBJ file, it may return a Trimesh object or a Scene object.
|
|
29
|
+
|
|
30
|
+
Note:
|
|
31
|
+
This implementation does not include the capability to force a PNG as a
|
|
32
|
+
texture if the material does not already have an associated image.
|
|
33
|
+
`trimesh` ignores UVs when this condition occurs. Modifications to
|
|
34
|
+
`trimesh`'s loading function would be necessary to address this limitation.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
path_to_obj: Path to the OBJ file to be loaded.
|
|
38
|
+
This must be a valid file path to a 3D object in OBJ format.
|
|
39
|
+
plot: Boolean flag indicating whether to visually plot the loaded model.
|
|
40
|
+
Defaults to False.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
A `trimesh.Trimesh` object if a single mesh is loaded, or a `trimesh.Scene`
|
|
44
|
+
object if the file contains multiple meshes or a scene.
|
|
45
|
+
|
|
46
|
+
Raises:
|
|
47
|
+
`FileNotFoundError`: If the provided file path does not exist.
|
|
48
|
+
`ValueError`: If the OBJ file could not be properly processed.
|
|
49
|
+
|
|
50
|
+
"""
|
|
51
|
+
trimesh = load_with_trimesh(path_to_obj, file_type="obj", plot=plot)
|
|
52
|
+
trisurf = trimesh_to_unstruct(trimesh)
|
|
53
|
+
return trisurf
|
|
@@ -1,43 +1,43 @@
|
|
|
1
|
-
import io
|
|
2
|
-
|
|
3
|
-
import numpy as np
|
|
4
|
-
|
|
5
|
-
import subsurface
|
|
6
|
-
from ....optional_requirements import require_omf, require_pyvista
|
|
7
|
-
from ....core.structs.base_structures import UnstructuredData
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def omf_stream_to_unstructs(stream: io.BytesIO) -> list[UnstructuredData]:
|
|
11
|
-
pyvista = require_pyvista()
|
|
12
|
-
omfvista = require_omf()
|
|
13
|
-
omf = omfvista.load_project(stream)
|
|
14
|
-
|
|
15
|
-
all_vertex = []
|
|
16
|
-
all_cells = []
|
|
17
|
-
cell_attr = []
|
|
18
|
-
_last_cell: int = 0
|
|
19
|
-
|
|
20
|
-
for i in range(omf.n_blocks):
|
|
21
|
-
block: pyvista.PolyData = omf[i]
|
|
22
|
-
cell_type = block.get_cell(0).type
|
|
23
|
-
|
|
24
|
-
if cell_type == pyvista.CellType.TRIANGLE:
|
|
25
|
-
pyvista_unstructured_grid: pyvista.UnstructuredGrid = block.cast_to_unstructured_grid()
|
|
26
|
-
all_vertex.append(pyvista_unstructured_grid.points)
|
|
27
|
-
cells: np.ndarray = pyvista_unstructured_grid.cells.reshape(-1, 4)[:, 1:]
|
|
28
|
-
if len(all_cells) > 0:
|
|
29
|
-
cells = cells + _last_cell
|
|
30
|
-
|
|
31
|
-
all_cells.append(cells)
|
|
32
|
-
cell_attr.append(np.ones(len(all_cells[-1])) * i)
|
|
33
|
-
_last_cell = cells.max() + 1
|
|
34
|
-
|
|
35
|
-
# * Create the unstructured data
|
|
36
|
-
import pandas as pd
|
|
37
|
-
|
|
38
|
-
unstructured_data = subsurface.UnstructuredData.from_array(
|
|
39
|
-
vertex=np.vstack(all_vertex),
|
|
40
|
-
cells=np.vstack(all_cells),
|
|
41
|
-
cells_attr=pd.DataFrame(np.hstack(cell_attr), columns=["Block id"]),
|
|
42
|
-
)
|
|
43
|
-
return [unstructured_data]
|
|
1
|
+
import io
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
import subsurface
|
|
6
|
+
from ....optional_requirements import require_omf, require_pyvista
|
|
7
|
+
from ....core.structs.base_structures import UnstructuredData
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def omf_stream_to_unstructs(stream: io.BytesIO) -> list[UnstructuredData]:
|
|
11
|
+
pyvista = require_pyvista()
|
|
12
|
+
omfvista = require_omf()
|
|
13
|
+
omf = omfvista.load_project(stream)
|
|
14
|
+
|
|
15
|
+
all_vertex = []
|
|
16
|
+
all_cells = []
|
|
17
|
+
cell_attr = []
|
|
18
|
+
_last_cell: int = 0
|
|
19
|
+
|
|
20
|
+
for i in range(omf.n_blocks):
|
|
21
|
+
block: pyvista.PolyData = omf[i]
|
|
22
|
+
cell_type = block.get_cell(0).type
|
|
23
|
+
|
|
24
|
+
if cell_type == pyvista.CellType.TRIANGLE:
|
|
25
|
+
pyvista_unstructured_grid: pyvista.UnstructuredGrid = block.cast_to_unstructured_grid()
|
|
26
|
+
all_vertex.append(pyvista_unstructured_grid.points)
|
|
27
|
+
cells: np.ndarray = pyvista_unstructured_grid.cells.reshape(-1, 4)[:, 1:]
|
|
28
|
+
if len(all_cells) > 0:
|
|
29
|
+
cells = cells + _last_cell
|
|
30
|
+
|
|
31
|
+
all_cells.append(cells)
|
|
32
|
+
cell_attr.append(np.ones(len(all_cells[-1])) * i)
|
|
33
|
+
_last_cell = cells.max() + 1
|
|
34
|
+
|
|
35
|
+
# * Create the unstructured data
|
|
36
|
+
import pandas as pd
|
|
37
|
+
|
|
38
|
+
unstructured_data = subsurface.UnstructuredData.from_array(
|
|
39
|
+
vertex=np.vstack(all_vertex),
|
|
40
|
+
cells=np.vstack(all_cells),
|
|
41
|
+
cells_attr=pd.DataFrame(np.hstack(cell_attr), columns=["Block id"]),
|
|
42
|
+
)
|
|
43
|
+
return [unstructured_data]
|