subsurface-terra 2025.1.0rc15__py3-none-any.whl → 2025.1.0rc17__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 -234
- subsurface/core/geological_formats/boreholes/_survey_to_unstruct.py +163 -163
- subsurface/core/geological_formats/boreholes/boreholes.py +140 -140
- subsurface/core/geological_formats/boreholes/collars.py +26 -26
- subsurface/core/geological_formats/boreholes/survey.py +86 -86
- 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/_aux.py +69 -0
- 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 +338 -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 +447 -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.0rc15.dist-info → subsurface_terra-2025.1.0rc17.dist-info}/METADATA +194 -194
- subsurface_terra-2025.1.0rc17.dist-info/RECORD +99 -0
- {subsurface_terra-2025.1.0rc15.dist-info → subsurface_terra-2025.1.0rc17.dist-info}/WHEEL +1 -1
- {subsurface_terra-2025.1.0rc15.dist-info → subsurface_terra-2025.1.0rc17.dist-info}/licenses/LICENSE +203 -203
- subsurface_terra-2025.1.0rc15.dist-info/RECORD +0 -98
- {subsurface_terra-2025.1.0rc15.dist-info → subsurface_terra-2025.1.0rc17.dist-info}/top_level.txt +0 -0
|
@@ -1,791 +1,791 @@
|
|
|
1
|
-
"""
|
|
2
|
-
This file is part of gempy.
|
|
3
|
-
|
|
4
|
-
Created on 21/02/2020
|
|
5
|
-
|
|
6
|
-
@author: Miguel de la Varga
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
import numpy as np
|
|
10
|
-
import matplotlib.colors as mcolors
|
|
11
|
-
import pandas as pd
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
__all__ = ['GemPyToRex', 'encode', 'write_file_header_block',
|
|
15
|
-
'write_data_block_header', 'write_mesh_header',
|
|
16
|
-
'write_mesh_coordinates', 'write_material_data', 'hex_to_rgb',
|
|
17
|
-
'geomodel_to_rex', 'mesh_preprocess', 'write_file', 'write_rex', ]
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
rexFileHeaderSize = 64
|
|
21
|
-
rexCoordSize = 22
|
|
22
|
-
|
|
23
|
-
file_header_size = 86
|
|
24
|
-
rexDataBlockHeaderSize = 16
|
|
25
|
-
|
|
26
|
-
file_header_and_data_header = 102
|
|
27
|
-
mesh_header_size = 128
|
|
28
|
-
all_header_size = 230
|
|
29
|
-
|
|
30
|
-
# Supported block types
|
|
31
|
-
# typeLineSet = 0
|
|
32
|
-
# typeText = 1
|
|
33
|
-
# typePointList = 2
|
|
34
|
-
typeMesh = 3
|
|
35
|
-
# typeImage = 4
|
|
36
|
-
# typeMaterial = 5
|
|
37
|
-
# typePeopleSimulation = 6
|
|
38
|
-
# typeUnityPackage = 7
|
|
39
|
-
# typeSceneNode = 8
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
n_bytes = 0
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
class GemPyToRex:
|
|
46
|
-
def __init__(self, geo_model=None):
|
|
47
|
-
"""Writes GemPy data structures into binary Rex
|
|
48
|
-
|
|
49
|
-
https://github.com/roboticeyes/openrex/blob/master/doc/rex-spec-v1.md
|
|
50
|
-
"""
|
|
51
|
-
self.rex_bytes = bytearray()
|
|
52
|
-
self.n_bytes = 0
|
|
53
|
-
|
|
54
|
-
self.data_id = 0
|
|
55
|
-
self.geo_model = geo_model
|
|
56
|
-
|
|
57
|
-
def __call__(self, geo_model=None, meshes=True, material=True,
|
|
58
|
-
surfaces=None, topography=True, app='GemPlay'):
|
|
59
|
-
"""
|
|
60
|
-
|
|
61
|
-
Args:
|
|
62
|
-
meshes:
|
|
63
|
-
surfaces (list): Subset of surfaces to send to the client
|
|
64
|
-
app (str): Either RexViewer or GemPlay. Set of default values
|
|
65
|
-
|
|
66
|
-
Returns:
|
|
67
|
-
|
|
68
|
-
"""
|
|
69
|
-
|
|
70
|
-
byte_array = bytearray()
|
|
71
|
-
byte_size = 0
|
|
72
|
-
self.data_id = 0
|
|
73
|
-
|
|
74
|
-
if geo_model is None:
|
|
75
|
-
geo_model = self.geo_model
|
|
76
|
-
else:
|
|
77
|
-
self.geo_model = geo_model
|
|
78
|
-
|
|
79
|
-
if surfaces is not None:
|
|
80
|
-
raise NotImplementedError
|
|
81
|
-
|
|
82
|
-
flip_yz, backside, vertex_color = self.default_values(app)
|
|
83
|
-
# flip_yz, backside, vertex_color = False, True, False
|
|
84
|
-
|
|
85
|
-
surface_df = self.grab_meshes(geo_model)
|
|
86
|
-
if topography:
|
|
87
|
-
topography_dict = self.grab_topography(geo_model)
|
|
88
|
-
else:
|
|
89
|
-
topography_dict = None
|
|
90
|
-
|
|
91
|
-
# Data Blocks
|
|
92
|
-
# -----------
|
|
93
|
-
if material is True:
|
|
94
|
-
# Material
|
|
95
|
-
byte_array += self.gempy_color_to_rex_material(surface_df, topography)
|
|
96
|
-
|
|
97
|
-
if meshes is True:
|
|
98
|
-
# Mesh
|
|
99
|
-
byte_array += self.gempy_meshes_to_rex(
|
|
100
|
-
surface_df,
|
|
101
|
-
topography_dict=topography_dict,
|
|
102
|
-
flip_yz=flip_yz,
|
|
103
|
-
backside=backside,
|
|
104
|
-
vertex_color=vertex_color)
|
|
105
|
-
|
|
106
|
-
# Size of all data blocks together
|
|
107
|
-
byte_size += len(byte_array)
|
|
108
|
-
|
|
109
|
-
# Write file header
|
|
110
|
-
# -----------------
|
|
111
|
-
n_data_blocks = self.data_id
|
|
112
|
-
|
|
113
|
-
header_bytes = write_file_header_block(
|
|
114
|
-
n_data_blocks=n_data_blocks,
|
|
115
|
-
size_data_blocks=byte_size,
|
|
116
|
-
start_data=file_header_size)
|
|
117
|
-
|
|
118
|
-
return header_bytes + byte_array
|
|
119
|
-
|
|
120
|
-
@staticmethod
|
|
121
|
-
def grab_meshes(geo_model):
|
|
122
|
-
"""Check if surfaces are computed. And return a pandas.DataFrame with
|
|
123
|
-
the meshes to be converted
|
|
124
|
-
|
|
125
|
-
Args:
|
|
126
|
-
geo_model:
|
|
127
|
-
|
|
128
|
-
Returns:
|
|
129
|
-
|
|
130
|
-
"""
|
|
131
|
-
|
|
132
|
-
try:
|
|
133
|
-
# Drop basement
|
|
134
|
-
surface_df = geo_model._surfaces.df.groupby(
|
|
135
|
-
['isActive', 'isBasement']).get_group((True, False))
|
|
136
|
-
except (IndexError, KeyError):
|
|
137
|
-
raise RuntimeError('No computed surfaces yet.')
|
|
138
|
-
|
|
139
|
-
return surface_df[['surface', 'vertices', 'edges', 'color']]
|
|
140
|
-
|
|
141
|
-
@staticmethod
|
|
142
|
-
def grab_topography(geo_model):
|
|
143
|
-
from scipy.spatial import Delaunay
|
|
144
|
-
|
|
145
|
-
if geo_model._grid.topography is None or geo_model._grid.topography.values.shape[0] == 0:
|
|
146
|
-
return None
|
|
147
|
-
else:
|
|
148
|
-
topography_dict = dict()
|
|
149
|
-
topography_dict['surface'] = "Topography"
|
|
150
|
-
topography_dict['vertices'] = geo_model._grid.topography.values
|
|
151
|
-
# tri = Delaunay(geo_model._grid.topography.values)
|
|
152
|
-
topography_dict['edges'] = Delaunay(geo_model._grid.topography.values[:, :2]).simplices
|
|
153
|
-
topography_dict['color'] = geo_model.solutions.geological_map
|
|
154
|
-
|
|
155
|
-
return topography_dict
|
|
156
|
-
|
|
157
|
-
@staticmethod
|
|
158
|
-
def hex_to_rgb(hex: str, normalize: bool = True) -> np.ndarray:
|
|
159
|
-
"""Transform colors from hex to rgb"""
|
|
160
|
-
hex = hex.lstrip('#')
|
|
161
|
-
hlen = len(hex)
|
|
162
|
-
rgb = np.array([int(hex[i:i + hlen // 3], 16) for i in range(0, hlen, hlen // 3)])
|
|
163
|
-
if normalize is True:
|
|
164
|
-
rgb = rgb / 255
|
|
165
|
-
return rgb
|
|
166
|
-
|
|
167
|
-
@staticmethod
|
|
168
|
-
def default_values(app):
|
|
169
|
-
"""
|
|
170
|
-
|
|
171
|
-
Args:
|
|
172
|
-
app:
|
|
173
|
-
|
|
174
|
-
Returns:
|
|
175
|
-
list: flip_yz, backside, vertex_color
|
|
176
|
-
"""
|
|
177
|
-
if app == 'GemPlay':
|
|
178
|
-
return (False, True, False)
|
|
179
|
-
elif app == 'RexView':
|
|
180
|
-
return (True, True, True)
|
|
181
|
-
else:
|
|
182
|
-
raise AttributeError('app must be either GemPlay or RexView')
|
|
183
|
-
|
|
184
|
-
def gempy_meshes_to_rex(self,
|
|
185
|
-
surface_df,
|
|
186
|
-
topography_dict=None,
|
|
187
|
-
flip_yz=False,
|
|
188
|
-
backside=True,
|
|
189
|
-
vertex_color=False):
|
|
190
|
-
"""Write mesh to Rexfile.
|
|
191
|
-
|
|
192
|
-
Args:
|
|
193
|
-
surface_df:
|
|
194
|
-
topography_dict:
|
|
195
|
-
flip_yz: Fliping YZ coordinates. Rexview need this
|
|
196
|
-
backside: If True, create a second set of triangles on the backside of the mesh
|
|
197
|
-
vertex_color
|
|
198
|
-
|
|
199
|
-
Returns:
|
|
200
|
-
|
|
201
|
-
Notes:
|
|
202
|
-
At the moment 14.07.2020 it is not possible to write normals or texture
|
|
203
|
-
|
|
204
|
-
"""
|
|
205
|
-
rex_bytes = bytearray()
|
|
206
|
-
mesh_number = 0
|
|
207
|
-
|
|
208
|
-
# Loop geological surfaces surfaces
|
|
209
|
-
for idx, surface_vals in surface_df.iterrows():
|
|
210
|
-
|
|
211
|
-
tri = surface_vals['edges']
|
|
212
|
-
if tri is np.nan:
|
|
213
|
-
continue
|
|
214
|
-
|
|
215
|
-
ver = surface_vals['vertices']
|
|
216
|
-
surface_name = surface_vals['surface']
|
|
217
|
-
if vertex_color:
|
|
218
|
-
# Hex Colors
|
|
219
|
-
col_ = surface_vals['color']
|
|
220
|
-
else:
|
|
221
|
-
col_ = None
|
|
222
|
-
|
|
223
|
-
rex_bytes = self.mesh_prepare_and_encode(rex_bytes, mesh_number, ver, tri,
|
|
224
|
-
surface_name, col_=col_,
|
|
225
|
-
flip_yz=flip_yz, backside=backside,
|
|
226
|
-
vertex_color=False)
|
|
227
|
-
mesh_number += 1
|
|
228
|
-
|
|
229
|
-
# Add topography
|
|
230
|
-
if topography_dict is not None:
|
|
231
|
-
|
|
232
|
-
rex_bytes = self.mesh_prepare_and_encode(rex_bytes, n_surface=-1,
|
|
233
|
-
ver=topography_dict['vertices'],
|
|
234
|
-
tri=topography_dict['edges'],
|
|
235
|
-
surface_name=topography_dict['surface'],
|
|
236
|
-
col_=topography_dict['color'],
|
|
237
|
-
flip_yz=flip_yz, backside=backside,
|
|
238
|
-
vertex_color=True)
|
|
239
|
-
|
|
240
|
-
return rex_bytes
|
|
241
|
-
|
|
242
|
-
def mesh_prepare_and_encode(self, rex_bytes, n_surface, ver, tri, surface_name,
|
|
243
|
-
col_=None,
|
|
244
|
-
flip_yz=False,
|
|
245
|
-
backside=True,
|
|
246
|
-
vertex_color=False):
|
|
247
|
-
|
|
248
|
-
if flip_yz:
|
|
249
|
-
# This depends. For RexViewer we need to flip XYZ. For GemPlay not really
|
|
250
|
-
ver_ = np.copy(ver)
|
|
251
|
-
ver[:, 2] = ver_[:, 1]
|
|
252
|
-
ver[:, 1] = ver_[:, 2]
|
|
253
|
-
|
|
254
|
-
# Pre-processing GemPy output
|
|
255
|
-
ver_ravel, tri_ravel, n_vtx_coord, n_triangles = mesh_preprocess(ver, tri)
|
|
256
|
-
|
|
257
|
-
# Number of vertex colors
|
|
258
|
-
if vertex_color:
|
|
259
|
-
n_vtx_colors = n_vtx_coord
|
|
260
|
-
|
|
261
|
-
# Give color to each vertex
|
|
262
|
-
# TODO: Is this necessary if I pass a material
|
|
263
|
-
if type(col_) is str:
|
|
264
|
-
colors = np.zeros_like(ver) + self.hex_to_rgb(col_, normalize=True)
|
|
265
|
-
c_r = colors.ravel()
|
|
266
|
-
|
|
267
|
-
elif type(col_) is np.ndarray:
|
|
268
|
-
surf_df = self.geo_model._surfaces.df.set_index('id')
|
|
269
|
-
colors_hex = surf_df.groupby(
|
|
270
|
-
['isActive', 'isFault']).get_group((True, False))['color']
|
|
271
|
-
|
|
272
|
-
colors_rgb_ = colors_hex.apply(lambda val: list(mcolors.hex2color(val)))
|
|
273
|
-
colors_rgb = pd.DataFrame(colors_rgb_.to_list(), index=colors_hex.index)
|
|
274
|
-
|
|
275
|
-
sel = np.round(col_[0]).astype(int)[0]
|
|
276
|
-
c_r = colors_rgb.loc[sel].values.ravel()
|
|
277
|
-
else:
|
|
278
|
-
raise AttributeError("col_ must be either hex string or rgb array")
|
|
279
|
-
else:
|
|
280
|
-
n_vtx_colors = 0
|
|
281
|
-
c_r = None
|
|
282
|
-
|
|
283
|
-
rex_bytes = self._mesh_encode(
|
|
284
|
-
rex_bytes, n_surface,
|
|
285
|
-
n_vtx_coord, n_triangles, n_vtx_colors,
|
|
286
|
-
surface_name, ver_ravel, tri_ravel, c_r)
|
|
287
|
-
|
|
288
|
-
if backside:
|
|
289
|
-
# Coping triangles to create the backside normal of the layers
|
|
290
|
-
tri_ = np.copy(tri)
|
|
291
|
-
# TURN normals - One side of the normals
|
|
292
|
-
tri_[:, 2] = tri[:, 1]
|
|
293
|
-
tri_[:, 1] = tri[:, 2]
|
|
294
|
-
# Pre-processing GemPy output
|
|
295
|
-
ver_ravel, tri_ravel, n_vtx_coord, n_triangles = mesh_preprocess(ver, tri_)
|
|
296
|
-
# tri = np.append(tri, tri_)
|
|
297
|
-
|
|
298
|
-
rex_bytes = self._mesh_encode(
|
|
299
|
-
rex_bytes, n_surface,
|
|
300
|
-
n_vtx_coord, n_triangles, n_vtx_colors,
|
|
301
|
-
surface_name, ver_ravel, tri_ravel, c_r)
|
|
302
|
-
|
|
303
|
-
return rex_bytes
|
|
304
|
-
|
|
305
|
-
def _mesh_encode(self, rex_bytes, material_id,
|
|
306
|
-
n_vtx_coord, n_triangles, n_vtx_colors,
|
|
307
|
-
surface_name, ver_ravel, tri_ravel, c_r):
|
|
308
|
-
# Write Mesh block - header
|
|
309
|
-
mesh_header_bytes = write_mesh_header(
|
|
310
|
-
n_vtx_coord / 3, n_triangles / 3,
|
|
311
|
-
n_vtx_colors=n_vtx_colors / 3,
|
|
312
|
-
start_vtx_coord=mesh_header_size,
|
|
313
|
-
start_nor_coord=mesh_header_size + n_vtx_coord * 4,
|
|
314
|
-
start_tex_coord=mesh_header_size + n_vtx_coord * 4,
|
|
315
|
-
start_vtx_colors=mesh_header_size + n_vtx_coord * 4,
|
|
316
|
-
start_triangles=mesh_header_size +
|
|
317
|
-
((n_vtx_coord + n_vtx_colors) * 4),
|
|
318
|
-
name=surface_name,
|
|
319
|
-
material_id=material_id # self.data_id + surface_df.shape[0]
|
|
320
|
-
)
|
|
321
|
-
|
|
322
|
-
# Write Mesh block - Vertex, triangles
|
|
323
|
-
mesh_block_bytes = write_mesh_coordinates(ver_ravel, tri_ravel,
|
|
324
|
-
colors=c_r # When using
|
|
325
|
-
# material we can avoid this
|
|
326
|
-
)
|
|
327
|
-
|
|
328
|
-
# Calculate the size of the mesh block
|
|
329
|
-
mesh_block_size_no_data_block_header = len(mesh_header_bytes) + \
|
|
330
|
-
len(mesh_block_bytes) # This is cte 128
|
|
331
|
-
|
|
332
|
-
# Write data block header for Mesh 1
|
|
333
|
-
data_header_bytes = write_data_block_header(
|
|
334
|
-
size_data=mesh_block_size_no_data_block_header,
|
|
335
|
-
data_id=self.data_id,
|
|
336
|
-
data_type=3, # 3 for mesh
|
|
337
|
-
version_data=1 # Probably useful for counting
|
|
338
|
-
# the operation number
|
|
339
|
-
)
|
|
340
|
-
self.data_id += 1
|
|
341
|
-
rex_bytes += data_header_bytes + mesh_header_bytes + mesh_block_bytes
|
|
342
|
-
|
|
343
|
-
return rex_bytes
|
|
344
|
-
|
|
345
|
-
def gempy_color_to_rex_material(self, surface_df, topography=False):
|
|
346
|
-
rex_bytes = bytearray()
|
|
347
|
-
|
|
348
|
-
for idx, surface_vals in surface_df.iterrows():
|
|
349
|
-
# Write data block header for Material 1
|
|
350
|
-
data_header_bytes = write_data_block_header(
|
|
351
|
-
data_type=5, # Material data type
|
|
352
|
-
version_data=1, # Version. Probably useful for operation counter
|
|
353
|
-
size_data=68, # Size of the block is FIXED
|
|
354
|
-
data_id=self.data_id # self.data_id
|
|
355
|
-
)
|
|
356
|
-
self.data_id += 1
|
|
357
|
-
|
|
358
|
-
rgb_color = self.hex_to_rgb(surface_vals['color'], normalize=True)
|
|
359
|
-
# rgb_color = [1, 1, 1]
|
|
360
|
-
# Write Material
|
|
361
|
-
material_bytes = write_material_data(
|
|
362
|
-
ka_red=rgb_color[0], ka_green=rgb_color[1], ka_blue=rgb_color[2],
|
|
363
|
-
ka_texture_ID=9223372036854775807, # ambient
|
|
364
|
-
ks_red=rgb_color[0], ks_green=rgb_color[1], ks_blue=rgb_color[2],
|
|
365
|
-
ks_texture_ID=9223372036854775807, # specular
|
|
366
|
-
kd_red=rgb_color[0], kd_green=rgb_color[1], kd_blue=rgb_color[2],
|
|
367
|
-
kd_texture_ID=9223372036854775807, # diffuse
|
|
368
|
-
ns=0.1, # specular exponent
|
|
369
|
-
alpha=1 # opacity
|
|
370
|
-
)
|
|
371
|
-
|
|
372
|
-
rex_bytes += data_header_bytes + material_bytes
|
|
373
|
-
|
|
374
|
-
if topography is True:
|
|
375
|
-
# Write data block header for Material 1
|
|
376
|
-
data_header_bytes = write_data_block_header(
|
|
377
|
-
data_type=5, # Material data type
|
|
378
|
-
version_data=1, # Version. Probably useful for operation counter
|
|
379
|
-
size_data=68, # Size of the block is FIXED
|
|
380
|
-
data_id=-1 # self.data_id
|
|
381
|
-
)
|
|
382
|
-
|
|
383
|
-
rgb_color = [1, 1, 1]
|
|
384
|
-
# Write Material
|
|
385
|
-
material_bytes = write_material_data(
|
|
386
|
-
ka_red=rgb_color[0], ka_green=rgb_color[1], ka_blue=rgb_color[2],
|
|
387
|
-
ka_texture_ID=9223372036854775807, # ambient
|
|
388
|
-
ks_red=rgb_color[0], ks_green=rgb_color[1], ks_blue=rgb_color[2],
|
|
389
|
-
ks_texture_ID=9223372036854775807, # specular
|
|
390
|
-
kd_red=rgb_color[0], kd_green=rgb_color[1], kd_blue=rgb_color[2],
|
|
391
|
-
kd_texture_ID=9223372036854775807, # diffuse
|
|
392
|
-
ns=0.1, # specular exponent
|
|
393
|
-
alpha=1 # opacity
|
|
394
|
-
)
|
|
395
|
-
|
|
396
|
-
self.data_id += 1
|
|
397
|
-
rex_bytes += data_header_bytes + material_bytes
|
|
398
|
-
|
|
399
|
-
return rex_bytes
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
def encode(input_: list):
|
|
403
|
-
"""Encode python objects - normally Python primitives or numpy arrays - into
|
|
404
|
-
its correspondent byte representation
|
|
405
|
-
|
|
406
|
-
Args:
|
|
407
|
-
input_ (List[tuples]): List of tuples: (object, type)
|
|
408
|
-
|
|
409
|
-
Returns:
|
|
410
|
-
byte: Array of bytes
|
|
411
|
-
"""
|
|
412
|
-
global n_bytes
|
|
413
|
-
block = bytearray()
|
|
414
|
-
|
|
415
|
-
for tup in input_:
|
|
416
|
-
arr = np.array(tup[0], dtype=tup[1]).tobytes()
|
|
417
|
-
n_bytes += len(arr)
|
|
418
|
-
block += arr
|
|
419
|
-
|
|
420
|
-
return block
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
def write_file_header_block(n_data_blocks, size_data_blocks, version=1,
|
|
424
|
-
start_data=86, srid=3876, offsets=None):
|
|
425
|
-
"""
|
|
426
|
-
Function that writes the header block of a rexfile:
|
|
427
|
-
|
|
428
|
-
Args:
|
|
429
|
-
n_data_blocks:
|
|
430
|
-
size_data_blocks:
|
|
431
|
-
version (int): Version of the file
|
|
432
|
-
start_data (int): Position where data start. This is after the header
|
|
433
|
-
and coordinate system. If everything works fine it should be 86
|
|
434
|
-
srid (int): Spatial reference system identifier (srid)
|
|
435
|
-
offsets:
|
|
436
|
-
|
|
437
|
-
Returns:
|
|
438
|
-
|
|
439
|
-
"""
|
|
440
|
-
reserved = '0' * 42
|
|
441
|
-
if offsets is None:
|
|
442
|
-
offsets = [0, 0, 0]
|
|
443
|
-
|
|
444
|
-
input_ = [('REX1', 'bytes'), # REX1
|
|
445
|
-
(version, 'uint16'), # file version
|
|
446
|
-
(0, 'uint32'), # CRC32
|
|
447
|
-
(n_data_blocks, 'uint16'), # Number of DATA BLOCKS
|
|
448
|
-
(start_data, 'uint16'), # StartData
|
|
449
|
-
(size_data_blocks, 'uint64'), # Size of all data blocks
|
|
450
|
-
(reserved, 'bytes'), # Reserved
|
|
451
|
-
# Coordinate system block
|
|
452
|
-
(srid, 'uint32'), # Spatial reference system identifier (srid)
|
|
453
|
-
(4, 'uint16'), # Size of the name of the used system.
|
|
454
|
-
('EPSG', 'bytes'), # name of the used system.
|
|
455
|
-
(offsets, 'float32')] # Global x, y, z offset
|
|
456
|
-
|
|
457
|
-
block_bytes = encode(input_)
|
|
458
|
-
return block_bytes
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
def write_data_block_header(size_data, data_id=1, data_type=3, version_data=1):
|
|
462
|
-
"""Function to write a DATA BLOCK header.
|
|
463
|
-
|
|
464
|
-
Args:
|
|
465
|
-
size_data: data block size (without header)
|
|
466
|
-
data_id: id which is used in the database
|
|
467
|
-
data_type (int): Type of data the data block contains:
|
|
468
|
-
* 0 LineSet A list of vertices which get connected by line segments
|
|
469
|
-
* 1 Text A position information and the actual text
|
|
470
|
-
* 2 PointList A list of 3D points with color information (e.g. point cloud)
|
|
471
|
-
* 3 Mesh A triangle mesh datastructure️
|
|
472
|
-
* 4 Image A single of arbitrary format can be stored in this block
|
|
473
|
-
* 5 MaterialStandard A standard (mesh) material definition
|
|
474
|
-
* 6 SceneNode A wrapper around a data block which can be used in the scenegraph
|
|
475
|
-
* 7 Track A track is a tracked position and orientation of an AR device
|
|
476
|
-
version_data: version for this data block
|
|
477
|
-
|
|
478
|
-
Returns:
|
|
479
|
-
|
|
480
|
-
"""
|
|
481
|
-
|
|
482
|
-
input_ = [(data_type, 'uint16'), # data type
|
|
483
|
-
(version_data, 'uint16'), # version for this data block
|
|
484
|
-
(size_data, 'uint32'), # data block size (without header)
|
|
485
|
-
(data_id, 'uint64')] # id which is used in the database
|
|
486
|
-
|
|
487
|
-
block_bytes = encode(input_)
|
|
488
|
-
return block_bytes
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
def write_mesh_header(n_vtx_coord, n_triangles,
|
|
492
|
-
start_vtx_coord, start_nor_coord, start_tex_coord, start_vtx_colors,
|
|
493
|
-
start_triangles,
|
|
494
|
-
name, material_id=1, # material_id=9223372036854775807
|
|
495
|
-
n_nor_coord=0, n_tex_coord=0, n_vtx_colors=0,
|
|
496
|
-
lod=1, max_lod=1):
|
|
497
|
-
"""Function to write MESH DATA BLOCK header. The header size is fixed at 128 bytes.
|
|
498
|
-
|
|
499
|
-
Args:
|
|
500
|
-
n_vtx_coord: number of vertex coordinates
|
|
501
|
-
n_triangles: number of triangles
|
|
502
|
-
start_vtx_coord: start vertex coordinate block (relative to mesh block start)
|
|
503
|
-
start_nor_coord: start vertex normals block (relative to mesh block start)
|
|
504
|
-
start_tex_coord: start of texture coordinate block (relative to mesh block start)
|
|
505
|
-
start_vtx_colors: start of colors block (relative to mesh block start)
|
|
506
|
-
start_triangles: start triangle block for vertices (relative to mesh block start)
|
|
507
|
-
name (str): Name of the mesh
|
|
508
|
-
material_id (int): id which refers to the corresponding material block in this file
|
|
509
|
-
n_nor_coord: number of normal coordinates (can be zero)
|
|
510
|
-
n_tex_coord: number of texture coordinates (can be zero)
|
|
511
|
-
n_vtx_colors: number of vertex colors (can be zero)
|
|
512
|
-
lod (int): level of detail for the given geometry
|
|
513
|
-
max_lod (int): maximal level of detail for given geometry
|
|
514
|
-
|
|
515
|
-
Returns:
|
|
516
|
-
bytes: array of bytes
|
|
517
|
-
"""
|
|
518
|
-
|
|
519
|
-
# Strings are immutable so there is no way to modify them in place
|
|
520
|
-
str_size = len(name) # Size of the actual name of the mesh
|
|
521
|
-
rest_name = ' ' * (74 - str_size) #
|
|
522
|
-
full_name = name + rest_name
|
|
523
|
-
|
|
524
|
-
input_ = [([lod, max_lod], 'uint16'), # Level of detail
|
|
525
|
-
([n_vtx_coord, # number of vertex coordinates
|
|
526
|
-
n_nor_coord, # number of normal coordinates (can be zero)
|
|
527
|
-
n_tex_coord, # number of texture coordinates (can be zero)
|
|
528
|
-
n_vtx_colors, # number of vertex colors (can be zero)
|
|
529
|
-
n_triangles, # number of triangles
|
|
530
|
-
start_vtx_coord, # start vertex coordinate block (relative to mesh block start)
|
|
531
|
-
start_nor_coord, # start vertex normals block (relative to mesh block start)
|
|
532
|
-
start_tex_coord, # start of texture coordinate block (relative to mesh block start)
|
|
533
|
-
start_vtx_colors, # start of colors block (relative to mesh block start)
|
|
534
|
-
start_triangles # start triangle block for vertices (relative to mesh block start)
|
|
535
|
-
],
|
|
536
|
-
'uint32'),
|
|
537
|
-
(material_id, 'uint64'),
|
|
538
|
-
# id which refers to the corresponding material block in this file
|
|
539
|
-
(str_size, 'uint16'), # size of the following string name
|
|
540
|
-
(full_name, 'bytes')] # name of the mesh (this is user-readable)
|
|
541
|
-
|
|
542
|
-
block_bytes = encode(input_)
|
|
543
|
-
return block_bytes
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
def write_mesh_coordinates(vertex, triangles, normal=None, texture=None, colors=None):
|
|
547
|
-
"""Block with the coordinates of a mesh. This has to go with a header!
|
|
548
|
-
|
|
549
|
-
Args:
|
|
550
|
-
vertex (numpy.ndarray[float32]): Array of vertex XYZXYZ...
|
|
551
|
-
triangles (numpy.ndarray[int32]): This is a list of integers which form
|
|
552
|
-
one triangle. Please make sure that normal and texture coordinates are inline with the
|
|
553
|
-
vertex coordinates. One index refers to the same normal and texture position. The
|
|
554
|
-
triangle orientation is required to be counter-clockwise (CCW)
|
|
555
|
-
normal (numpy.ndarray):
|
|
556
|
-
texture (numpy.ndarray):
|
|
557
|
-
colors (numpy.ndarray):
|
|
558
|
-
|
|
559
|
-
Returns:
|
|
560
|
-
|
|
561
|
-
"""
|
|
562
|
-
|
|
563
|
-
ver = vertex.ravel()
|
|
564
|
-
tri = triangles.ravel()
|
|
565
|
-
if normal is None:
|
|
566
|
-
normal = []
|
|
567
|
-
if texture is None:
|
|
568
|
-
texture = []
|
|
569
|
-
if colors is None:
|
|
570
|
-
colors = []
|
|
571
|
-
|
|
572
|
-
input_ = [(ver, 'float32'),
|
|
573
|
-
(normal, 'float32'),
|
|
574
|
-
(texture, 'float32'),
|
|
575
|
-
(colors, 'float32'),
|
|
576
|
-
(tri, 'uint32')]
|
|
577
|
-
|
|
578
|
-
block_bytes = encode(input_)
|
|
579
|
-
return block_bytes
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
def write_material_data(ka_red=255.0 / 255, ka_green=255.0 / 255, ka_blue=255.0 / 255,
|
|
583
|
-
ka_texture_ID=9223372036854775807, # ambient
|
|
584
|
-
ks_red=255.0 / 255, ks_green=255.0 / 255, ks_blue=255.0 / 255,
|
|
585
|
-
ks_texture_ID=9223372036854775807, # specular
|
|
586
|
-
kd_red=255.0 / 255, kd_green=255.0 / 255, kd_blue=255.0 / 255,
|
|
587
|
-
kd_texture_ID=9223372036854775807, # diffuse
|
|
588
|
-
ns=0.1, # specular exponent
|
|
589
|
-
alpha=1 # opacity
|
|
590
|
-
):
|
|
591
|
-
"""Writes a standard material definition block
|
|
592
|
-
|
|
593
|
-
Returns: bytes (size:68) representation of the material
|
|
594
|
-
|
|
595
|
-
"""
|
|
596
|
-
|
|
597
|
-
input_ = [(ka_red, 'float32'), (ka_green, 'float32'), (ka_blue, 'float32'),
|
|
598
|
-
(ka_texture_ID, 'uint64'),
|
|
599
|
-
(ks_red, 'float32'), (ks_green, 'float32'), (ks_blue, 'float32'),
|
|
600
|
-
(ks_texture_ID, 'uint64'),
|
|
601
|
-
(kd_red, 'float32'), (kd_green, 'float32'), (kd_blue, 'float32'),
|
|
602
|
-
(kd_texture_ID, 'uint64'),
|
|
603
|
-
(ns, 'float32'), (alpha, 'float32')]
|
|
604
|
-
|
|
605
|
-
block_bytes = encode(input_)
|
|
606
|
-
return block_bytes
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
# TODO Move to utils
|
|
610
|
-
def hex_to_rgb(hex):
|
|
611
|
-
"""Transform colors from hex to rgb"""
|
|
612
|
-
hex = hex.lstrip('#')
|
|
613
|
-
hlen = len(hex)
|
|
614
|
-
return tuple(int(hex[i:i + hlen // 3], 16) for i in range(0, hlen, hlen // 3))
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
def geomodel_to_rex(geo_model, backside=True):
|
|
618
|
-
"""
|
|
619
|
-
|
|
620
|
-
Args:
|
|
621
|
-
geo_model (gempy.Model):
|
|
622
|
-
"""
|
|
623
|
-
|
|
624
|
-
# Fixed sizes
|
|
625
|
-
mesh_header_size = 128
|
|
626
|
-
file_header_size = 86
|
|
627
|
-
|
|
628
|
-
# Init dict
|
|
629
|
-
rex_bytes = {}
|
|
630
|
-
|
|
631
|
-
# Check if surfaces are computed
|
|
632
|
-
try:
|
|
633
|
-
# Drop basement
|
|
634
|
-
surface_df = geo_model._surfaces.df.groupby(
|
|
635
|
-
['isActive', 'isBasement']).get_group((True, False))
|
|
636
|
-
except (IndexError, KeyError):
|
|
637
|
-
raise RuntimeError('No computed surfaces yet.')
|
|
638
|
-
|
|
639
|
-
# Loop surfaces
|
|
640
|
-
for idx, surface_vals in surface_df.iterrows():
|
|
641
|
-
ver = surface_vals['vertices']
|
|
642
|
-
tri = surface_vals['edges']
|
|
643
|
-
if tri is np.nan:
|
|
644
|
-
break
|
|
645
|
-
|
|
646
|
-
# Grab surface color
|
|
647
|
-
col = surface_vals['color']
|
|
648
|
-
|
|
649
|
-
# Give color to each vertex
|
|
650
|
-
colors = (np.zeros_like(ver) + hex_to_rgb(col)) / 255
|
|
651
|
-
|
|
652
|
-
# This depends. For RexViewer we need to flip XYZ. For GemPlay not really
|
|
653
|
-
ver_ = np.copy(ver)
|
|
654
|
-
ver_[:, 2] = ver[:, 1]
|
|
655
|
-
ver_[:, 1] = ver[:, 2]
|
|
656
|
-
# ----------------
|
|
657
|
-
|
|
658
|
-
# Coping triangles to create the backside normal of the layers
|
|
659
|
-
tri_ = np.copy(tri)
|
|
660
|
-
|
|
661
|
-
# Preprocessing GemPy output
|
|
662
|
-
ver_ravel, tri_ravel, n_vtx_coord, n_triangles = mesh_preprocess(ver_, tri_)
|
|
663
|
-
|
|
664
|
-
# Calculate the size of the mesh block
|
|
665
|
-
if backside is True:
|
|
666
|
-
n_sides = 2
|
|
667
|
-
else:
|
|
668
|
-
n_sides = 1
|
|
669
|
-
|
|
670
|
-
mesh_block_size_no_data_block_header = (2 * # Coordinates and colors
|
|
671
|
-
n_vtx_coord + n_triangles) * 4 + \
|
|
672
|
-
mesh_header_size # This is cte 128
|
|
673
|
-
|
|
674
|
-
# Size of a MATERIAL DATA BLOCK is cte
|
|
675
|
-
material_block_size_no_data_block_header = 68
|
|
676
|
-
|
|
677
|
-
# Write file header
|
|
678
|
-
if backside is True:
|
|
679
|
-
n_data_blocks = 3
|
|
680
|
-
else:
|
|
681
|
-
n_data_blocks = 2
|
|
682
|
-
header_bytes = write_file_header_block(n_data_blocks=n_data_blocks,
|
|
683
|
-
size_data_blocks=
|
|
684
|
-
n_sides * mesh_block_size_no_data_block_header +
|
|
685
|
-
rexDataBlockHeaderSize +
|
|
686
|
-
material_block_size_no_data_block_header,
|
|
687
|
-
start_data=file_header_size)
|
|
688
|
-
|
|
689
|
-
# Write data block header for Mesh 1
|
|
690
|
-
data_bytes = write_data_block_header(size_data=mesh_block_size_no_data_block_header,
|
|
691
|
-
data_id=1, data_type=3, version_data=1)
|
|
692
|
-
|
|
693
|
-
# Write Mesh 1 block - header
|
|
694
|
-
mesh_header_bytes = write_mesh_header(n_vtx_coord / 3, n_triangles / 3,
|
|
695
|
-
n_vtx_colors=n_vtx_coord / 3,
|
|
696
|
-
start_vtx_coord=mesh_header_size,
|
|
697
|
-
start_nor_coord=mesh_header_size + n_vtx_coord * 4,
|
|
698
|
-
start_tex_coord=mesh_header_size + n_vtx_coord * 4,
|
|
699
|
-
start_vtx_colors=mesh_header_size + n_vtx_coord * 4,
|
|
700
|
-
start_triangles=mesh_header_size + 2 *
|
|
701
|
-
(n_vtx_coord * 4),
|
|
702
|
-
name='rock1', material_id=0)
|
|
703
|
-
|
|
704
|
-
# Write Mesh 1 block - header
|
|
705
|
-
mesh_block_bytes = write_mesh_coordinates(ver_ravel, tri_ravel, colors=colors.ravel())
|
|
706
|
-
|
|
707
|
-
if backside:
|
|
708
|
-
# Write data block header for Mesh 2
|
|
709
|
-
data_bytes_r = write_data_block_header(size_data=mesh_block_size_no_data_block_header,
|
|
710
|
-
data_id=2, data_type=3, version_data=1)
|
|
711
|
-
|
|
712
|
-
# TURN normals - One side of the normals
|
|
713
|
-
tri_[:, 2] = tri[:, 1]
|
|
714
|
-
tri_[:, 1] = tri[:, 2]
|
|
715
|
-
|
|
716
|
-
ver_ravel, tri_ravel, n_vtx_coord, n_triangles = mesh_preprocess(ver_, tri_)
|
|
717
|
-
|
|
718
|
-
# Write Mesh 2 block - header
|
|
719
|
-
mesh_header_bytes_r = write_mesh_header(n_vtx_coord / 3, n_triangles / 3,
|
|
720
|
-
n_vtx_colors=n_vtx_coord / 3,
|
|
721
|
-
start_vtx_coord=mesh_header_size,
|
|
722
|
-
start_nor_coord=mesh_header_size + n_vtx_coord * 4,
|
|
723
|
-
start_tex_coord=mesh_header_size + n_vtx_coord * 4,
|
|
724
|
-
start_vtx_colors=mesh_header_size + n_vtx_coord * 4,
|
|
725
|
-
start_triangles=mesh_header_size + 2 *
|
|
726
|
-
(n_vtx_coord * 4),
|
|
727
|
-
name='test_a', material_id=0)
|
|
728
|
-
|
|
729
|
-
# Write Mesh 2 block - header
|
|
730
|
-
mesh_block_bytes_r = write_mesh_coordinates(ver_ravel, tri_ravel,
|
|
731
|
-
colors=colors.ravel())
|
|
732
|
-
|
|
733
|
-
# Write data block header for Material 1
|
|
734
|
-
material_header_bytes = write_data_block_header(data_type=5, version_data=1, size_data=68,
|
|
735
|
-
data_id=0)
|
|
736
|
-
|
|
737
|
-
# Write Material 1
|
|
738
|
-
material_bytes = write_material_data()
|
|
739
|
-
|
|
740
|
-
# Putting all data together
|
|
741
|
-
if backside is True:
|
|
742
|
-
all_bytes = header_bytes + data_bytes + mesh_header_bytes + mesh_block_bytes + \
|
|
743
|
-
data_bytes_r + mesh_header_bytes_r + mesh_block_bytes_r + \
|
|
744
|
-
material_header_bytes + material_bytes
|
|
745
|
-
|
|
746
|
-
else:
|
|
747
|
-
all_bytes = header_bytes + data_bytes + mesh_header_bytes + mesh_block_bytes + \
|
|
748
|
-
material_header_bytes + material_bytes
|
|
749
|
-
|
|
750
|
-
# FOR REXView Saving each surface is a rexfile
|
|
751
|
-
rex_bytes[surface_vals['surface']] = all_bytes
|
|
752
|
-
return rex_bytes
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
def mesh_preprocess(ver, tri):
|
|
756
|
-
"""Prepare GemPy Output to be converted to rex
|
|
757
|
-
|
|
758
|
-
Args:
|
|
759
|
-
ver (numpy.ndarray):
|
|
760
|
-
tri (numpy.ndarray):
|
|
761
|
-
|
|
762
|
-
Returns:
|
|
763
|
-
list: vertices raveled, triangels ravel, n vertex, n triangles
|
|
764
|
-
"""
|
|
765
|
-
|
|
766
|
-
# TODO: Remove the type transform. Technically it does nothing
|
|
767
|
-
ver_ravel = ver.ravel().astype('float32')
|
|
768
|
-
tri_ravel = tri.ravel().astype('int32')
|
|
769
|
-
n_vtx_coord = ver_ravel.shape[0]
|
|
770
|
-
n_triangles = tri_ravel.shape[0]
|
|
771
|
-
return ver_ravel, tri_ravel, n_vtx_coord, n_triangles
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
def write_file(bytes, path: str):
|
|
775
|
-
"""Write to disk a rexfile from its binary format"""
|
|
776
|
-
|
|
777
|
-
newFile = open(path + ".rex", "wb")
|
|
778
|
-
newFile.write(bytes)
|
|
779
|
-
return True
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
def write_rex(rex_bytes: dict, path='./gempy_rex'):
|
|
783
|
-
file_names = []
|
|
784
|
-
e = 0
|
|
785
|
-
for key, value in rex_bytes.items():
|
|
786
|
-
file_name = path + key
|
|
787
|
-
write_file(value, file_name)
|
|
788
|
-
file_names.append(file_name + '.rex')
|
|
789
|
-
e += 1
|
|
790
|
-
|
|
791
|
-
return file_names
|
|
1
|
+
"""
|
|
2
|
+
This file is part of gempy.
|
|
3
|
+
|
|
4
|
+
Created on 21/02/2020
|
|
5
|
+
|
|
6
|
+
@author: Miguel de la Varga
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
import matplotlib.colors as mcolors
|
|
11
|
+
import pandas as pd
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
__all__ = ['GemPyToRex', 'encode', 'write_file_header_block',
|
|
15
|
+
'write_data_block_header', 'write_mesh_header',
|
|
16
|
+
'write_mesh_coordinates', 'write_material_data', 'hex_to_rgb',
|
|
17
|
+
'geomodel_to_rex', 'mesh_preprocess', 'write_file', 'write_rex', ]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
rexFileHeaderSize = 64
|
|
21
|
+
rexCoordSize = 22
|
|
22
|
+
|
|
23
|
+
file_header_size = 86
|
|
24
|
+
rexDataBlockHeaderSize = 16
|
|
25
|
+
|
|
26
|
+
file_header_and_data_header = 102
|
|
27
|
+
mesh_header_size = 128
|
|
28
|
+
all_header_size = 230
|
|
29
|
+
|
|
30
|
+
# Supported block types
|
|
31
|
+
# typeLineSet = 0
|
|
32
|
+
# typeText = 1
|
|
33
|
+
# typePointList = 2
|
|
34
|
+
typeMesh = 3
|
|
35
|
+
# typeImage = 4
|
|
36
|
+
# typeMaterial = 5
|
|
37
|
+
# typePeopleSimulation = 6
|
|
38
|
+
# typeUnityPackage = 7
|
|
39
|
+
# typeSceneNode = 8
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
n_bytes = 0
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class GemPyToRex:
|
|
46
|
+
def __init__(self, geo_model=None):
|
|
47
|
+
"""Writes GemPy data structures into binary Rex
|
|
48
|
+
|
|
49
|
+
https://github.com/roboticeyes/openrex/blob/master/doc/rex-spec-v1.md
|
|
50
|
+
"""
|
|
51
|
+
self.rex_bytes = bytearray()
|
|
52
|
+
self.n_bytes = 0
|
|
53
|
+
|
|
54
|
+
self.data_id = 0
|
|
55
|
+
self.geo_model = geo_model
|
|
56
|
+
|
|
57
|
+
def __call__(self, geo_model=None, meshes=True, material=True,
|
|
58
|
+
surfaces=None, topography=True, app='GemPlay'):
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
meshes:
|
|
63
|
+
surfaces (list): Subset of surfaces to send to the client
|
|
64
|
+
app (str): Either RexViewer or GemPlay. Set of default values
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
byte_array = bytearray()
|
|
71
|
+
byte_size = 0
|
|
72
|
+
self.data_id = 0
|
|
73
|
+
|
|
74
|
+
if geo_model is None:
|
|
75
|
+
geo_model = self.geo_model
|
|
76
|
+
else:
|
|
77
|
+
self.geo_model = geo_model
|
|
78
|
+
|
|
79
|
+
if surfaces is not None:
|
|
80
|
+
raise NotImplementedError
|
|
81
|
+
|
|
82
|
+
flip_yz, backside, vertex_color = self.default_values(app)
|
|
83
|
+
# flip_yz, backside, vertex_color = False, True, False
|
|
84
|
+
|
|
85
|
+
surface_df = self.grab_meshes(geo_model)
|
|
86
|
+
if topography:
|
|
87
|
+
topography_dict = self.grab_topography(geo_model)
|
|
88
|
+
else:
|
|
89
|
+
topography_dict = None
|
|
90
|
+
|
|
91
|
+
# Data Blocks
|
|
92
|
+
# -----------
|
|
93
|
+
if material is True:
|
|
94
|
+
# Material
|
|
95
|
+
byte_array += self.gempy_color_to_rex_material(surface_df, topography)
|
|
96
|
+
|
|
97
|
+
if meshes is True:
|
|
98
|
+
# Mesh
|
|
99
|
+
byte_array += self.gempy_meshes_to_rex(
|
|
100
|
+
surface_df,
|
|
101
|
+
topography_dict=topography_dict,
|
|
102
|
+
flip_yz=flip_yz,
|
|
103
|
+
backside=backside,
|
|
104
|
+
vertex_color=vertex_color)
|
|
105
|
+
|
|
106
|
+
# Size of all data blocks together
|
|
107
|
+
byte_size += len(byte_array)
|
|
108
|
+
|
|
109
|
+
# Write file header
|
|
110
|
+
# -----------------
|
|
111
|
+
n_data_blocks = self.data_id
|
|
112
|
+
|
|
113
|
+
header_bytes = write_file_header_block(
|
|
114
|
+
n_data_blocks=n_data_blocks,
|
|
115
|
+
size_data_blocks=byte_size,
|
|
116
|
+
start_data=file_header_size)
|
|
117
|
+
|
|
118
|
+
return header_bytes + byte_array
|
|
119
|
+
|
|
120
|
+
@staticmethod
|
|
121
|
+
def grab_meshes(geo_model):
|
|
122
|
+
"""Check if surfaces are computed. And return a pandas.DataFrame with
|
|
123
|
+
the meshes to be converted
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
geo_model:
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
try:
|
|
133
|
+
# Drop basement
|
|
134
|
+
surface_df = geo_model._surfaces.df.groupby(
|
|
135
|
+
['isActive', 'isBasement']).get_group((True, False))
|
|
136
|
+
except (IndexError, KeyError):
|
|
137
|
+
raise RuntimeError('No computed surfaces yet.')
|
|
138
|
+
|
|
139
|
+
return surface_df[['surface', 'vertices', 'edges', 'color']]
|
|
140
|
+
|
|
141
|
+
@staticmethod
|
|
142
|
+
def grab_topography(geo_model):
|
|
143
|
+
from scipy.spatial import Delaunay
|
|
144
|
+
|
|
145
|
+
if geo_model._grid.topography is None or geo_model._grid.topography.values.shape[0] == 0:
|
|
146
|
+
return None
|
|
147
|
+
else:
|
|
148
|
+
topography_dict = dict()
|
|
149
|
+
topography_dict['surface'] = "Topography"
|
|
150
|
+
topography_dict['vertices'] = geo_model._grid.topography.values
|
|
151
|
+
# tri = Delaunay(geo_model._grid.topography.values)
|
|
152
|
+
topography_dict['edges'] = Delaunay(geo_model._grid.topography.values[:, :2]).simplices
|
|
153
|
+
topography_dict['color'] = geo_model.solutions.geological_map
|
|
154
|
+
|
|
155
|
+
return topography_dict
|
|
156
|
+
|
|
157
|
+
@staticmethod
|
|
158
|
+
def hex_to_rgb(hex: str, normalize: bool = True) -> np.ndarray:
|
|
159
|
+
"""Transform colors from hex to rgb"""
|
|
160
|
+
hex = hex.lstrip('#')
|
|
161
|
+
hlen = len(hex)
|
|
162
|
+
rgb = np.array([int(hex[i:i + hlen // 3], 16) for i in range(0, hlen, hlen // 3)])
|
|
163
|
+
if normalize is True:
|
|
164
|
+
rgb = rgb / 255
|
|
165
|
+
return rgb
|
|
166
|
+
|
|
167
|
+
@staticmethod
|
|
168
|
+
def default_values(app):
|
|
169
|
+
"""
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
app:
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
list: flip_yz, backside, vertex_color
|
|
176
|
+
"""
|
|
177
|
+
if app == 'GemPlay':
|
|
178
|
+
return (False, True, False)
|
|
179
|
+
elif app == 'RexView':
|
|
180
|
+
return (True, True, True)
|
|
181
|
+
else:
|
|
182
|
+
raise AttributeError('app must be either GemPlay or RexView')
|
|
183
|
+
|
|
184
|
+
def gempy_meshes_to_rex(self,
|
|
185
|
+
surface_df,
|
|
186
|
+
topography_dict=None,
|
|
187
|
+
flip_yz=False,
|
|
188
|
+
backside=True,
|
|
189
|
+
vertex_color=False):
|
|
190
|
+
"""Write mesh to Rexfile.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
surface_df:
|
|
194
|
+
topography_dict:
|
|
195
|
+
flip_yz: Fliping YZ coordinates. Rexview need this
|
|
196
|
+
backside: If True, create a second set of triangles on the backside of the mesh
|
|
197
|
+
vertex_color
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
|
|
201
|
+
Notes:
|
|
202
|
+
At the moment 14.07.2020 it is not possible to write normals or texture
|
|
203
|
+
|
|
204
|
+
"""
|
|
205
|
+
rex_bytes = bytearray()
|
|
206
|
+
mesh_number = 0
|
|
207
|
+
|
|
208
|
+
# Loop geological surfaces surfaces
|
|
209
|
+
for idx, surface_vals in surface_df.iterrows():
|
|
210
|
+
|
|
211
|
+
tri = surface_vals['edges']
|
|
212
|
+
if tri is np.nan:
|
|
213
|
+
continue
|
|
214
|
+
|
|
215
|
+
ver = surface_vals['vertices']
|
|
216
|
+
surface_name = surface_vals['surface']
|
|
217
|
+
if vertex_color:
|
|
218
|
+
# Hex Colors
|
|
219
|
+
col_ = surface_vals['color']
|
|
220
|
+
else:
|
|
221
|
+
col_ = None
|
|
222
|
+
|
|
223
|
+
rex_bytes = self.mesh_prepare_and_encode(rex_bytes, mesh_number, ver, tri,
|
|
224
|
+
surface_name, col_=col_,
|
|
225
|
+
flip_yz=flip_yz, backside=backside,
|
|
226
|
+
vertex_color=False)
|
|
227
|
+
mesh_number += 1
|
|
228
|
+
|
|
229
|
+
# Add topography
|
|
230
|
+
if topography_dict is not None:
|
|
231
|
+
|
|
232
|
+
rex_bytes = self.mesh_prepare_and_encode(rex_bytes, n_surface=-1,
|
|
233
|
+
ver=topography_dict['vertices'],
|
|
234
|
+
tri=topography_dict['edges'],
|
|
235
|
+
surface_name=topography_dict['surface'],
|
|
236
|
+
col_=topography_dict['color'],
|
|
237
|
+
flip_yz=flip_yz, backside=backside,
|
|
238
|
+
vertex_color=True)
|
|
239
|
+
|
|
240
|
+
return rex_bytes
|
|
241
|
+
|
|
242
|
+
def mesh_prepare_and_encode(self, rex_bytes, n_surface, ver, tri, surface_name,
|
|
243
|
+
col_=None,
|
|
244
|
+
flip_yz=False,
|
|
245
|
+
backside=True,
|
|
246
|
+
vertex_color=False):
|
|
247
|
+
|
|
248
|
+
if flip_yz:
|
|
249
|
+
# This depends. For RexViewer we need to flip XYZ. For GemPlay not really
|
|
250
|
+
ver_ = np.copy(ver)
|
|
251
|
+
ver[:, 2] = ver_[:, 1]
|
|
252
|
+
ver[:, 1] = ver_[:, 2]
|
|
253
|
+
|
|
254
|
+
# Pre-processing GemPy output
|
|
255
|
+
ver_ravel, tri_ravel, n_vtx_coord, n_triangles = mesh_preprocess(ver, tri)
|
|
256
|
+
|
|
257
|
+
# Number of vertex colors
|
|
258
|
+
if vertex_color:
|
|
259
|
+
n_vtx_colors = n_vtx_coord
|
|
260
|
+
|
|
261
|
+
# Give color to each vertex
|
|
262
|
+
# TODO: Is this necessary if I pass a material
|
|
263
|
+
if type(col_) is str:
|
|
264
|
+
colors = np.zeros_like(ver) + self.hex_to_rgb(col_, normalize=True)
|
|
265
|
+
c_r = colors.ravel()
|
|
266
|
+
|
|
267
|
+
elif type(col_) is np.ndarray:
|
|
268
|
+
surf_df = self.geo_model._surfaces.df.set_index('id')
|
|
269
|
+
colors_hex = surf_df.groupby(
|
|
270
|
+
['isActive', 'isFault']).get_group((True, False))['color']
|
|
271
|
+
|
|
272
|
+
colors_rgb_ = colors_hex.apply(lambda val: list(mcolors.hex2color(val)))
|
|
273
|
+
colors_rgb = pd.DataFrame(colors_rgb_.to_list(), index=colors_hex.index)
|
|
274
|
+
|
|
275
|
+
sel = np.round(col_[0]).astype(int)[0]
|
|
276
|
+
c_r = colors_rgb.loc[sel].values.ravel()
|
|
277
|
+
else:
|
|
278
|
+
raise AttributeError("col_ must be either hex string or rgb array")
|
|
279
|
+
else:
|
|
280
|
+
n_vtx_colors = 0
|
|
281
|
+
c_r = None
|
|
282
|
+
|
|
283
|
+
rex_bytes = self._mesh_encode(
|
|
284
|
+
rex_bytes, n_surface,
|
|
285
|
+
n_vtx_coord, n_triangles, n_vtx_colors,
|
|
286
|
+
surface_name, ver_ravel, tri_ravel, c_r)
|
|
287
|
+
|
|
288
|
+
if backside:
|
|
289
|
+
# Coping triangles to create the backside normal of the layers
|
|
290
|
+
tri_ = np.copy(tri)
|
|
291
|
+
# TURN normals - One side of the normals
|
|
292
|
+
tri_[:, 2] = tri[:, 1]
|
|
293
|
+
tri_[:, 1] = tri[:, 2]
|
|
294
|
+
# Pre-processing GemPy output
|
|
295
|
+
ver_ravel, tri_ravel, n_vtx_coord, n_triangles = mesh_preprocess(ver, tri_)
|
|
296
|
+
# tri = np.append(tri, tri_)
|
|
297
|
+
|
|
298
|
+
rex_bytes = self._mesh_encode(
|
|
299
|
+
rex_bytes, n_surface,
|
|
300
|
+
n_vtx_coord, n_triangles, n_vtx_colors,
|
|
301
|
+
surface_name, ver_ravel, tri_ravel, c_r)
|
|
302
|
+
|
|
303
|
+
return rex_bytes
|
|
304
|
+
|
|
305
|
+
def _mesh_encode(self, rex_bytes, material_id,
|
|
306
|
+
n_vtx_coord, n_triangles, n_vtx_colors,
|
|
307
|
+
surface_name, ver_ravel, tri_ravel, c_r):
|
|
308
|
+
# Write Mesh block - header
|
|
309
|
+
mesh_header_bytes = write_mesh_header(
|
|
310
|
+
n_vtx_coord / 3, n_triangles / 3,
|
|
311
|
+
n_vtx_colors=n_vtx_colors / 3,
|
|
312
|
+
start_vtx_coord=mesh_header_size,
|
|
313
|
+
start_nor_coord=mesh_header_size + n_vtx_coord * 4,
|
|
314
|
+
start_tex_coord=mesh_header_size + n_vtx_coord * 4,
|
|
315
|
+
start_vtx_colors=mesh_header_size + n_vtx_coord * 4,
|
|
316
|
+
start_triangles=mesh_header_size +
|
|
317
|
+
((n_vtx_coord + n_vtx_colors) * 4),
|
|
318
|
+
name=surface_name,
|
|
319
|
+
material_id=material_id # self.data_id + surface_df.shape[0]
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
# Write Mesh block - Vertex, triangles
|
|
323
|
+
mesh_block_bytes = write_mesh_coordinates(ver_ravel, tri_ravel,
|
|
324
|
+
colors=c_r # When using
|
|
325
|
+
# material we can avoid this
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
# Calculate the size of the mesh block
|
|
329
|
+
mesh_block_size_no_data_block_header = len(mesh_header_bytes) + \
|
|
330
|
+
len(mesh_block_bytes) # This is cte 128
|
|
331
|
+
|
|
332
|
+
# Write data block header for Mesh 1
|
|
333
|
+
data_header_bytes = write_data_block_header(
|
|
334
|
+
size_data=mesh_block_size_no_data_block_header,
|
|
335
|
+
data_id=self.data_id,
|
|
336
|
+
data_type=3, # 3 for mesh
|
|
337
|
+
version_data=1 # Probably useful for counting
|
|
338
|
+
# the operation number
|
|
339
|
+
)
|
|
340
|
+
self.data_id += 1
|
|
341
|
+
rex_bytes += data_header_bytes + mesh_header_bytes + mesh_block_bytes
|
|
342
|
+
|
|
343
|
+
return rex_bytes
|
|
344
|
+
|
|
345
|
+
def gempy_color_to_rex_material(self, surface_df, topography=False):
|
|
346
|
+
rex_bytes = bytearray()
|
|
347
|
+
|
|
348
|
+
for idx, surface_vals in surface_df.iterrows():
|
|
349
|
+
# Write data block header for Material 1
|
|
350
|
+
data_header_bytes = write_data_block_header(
|
|
351
|
+
data_type=5, # Material data type
|
|
352
|
+
version_data=1, # Version. Probably useful for operation counter
|
|
353
|
+
size_data=68, # Size of the block is FIXED
|
|
354
|
+
data_id=self.data_id # self.data_id
|
|
355
|
+
)
|
|
356
|
+
self.data_id += 1
|
|
357
|
+
|
|
358
|
+
rgb_color = self.hex_to_rgb(surface_vals['color'], normalize=True)
|
|
359
|
+
# rgb_color = [1, 1, 1]
|
|
360
|
+
# Write Material
|
|
361
|
+
material_bytes = write_material_data(
|
|
362
|
+
ka_red=rgb_color[0], ka_green=rgb_color[1], ka_blue=rgb_color[2],
|
|
363
|
+
ka_texture_ID=9223372036854775807, # ambient
|
|
364
|
+
ks_red=rgb_color[0], ks_green=rgb_color[1], ks_blue=rgb_color[2],
|
|
365
|
+
ks_texture_ID=9223372036854775807, # specular
|
|
366
|
+
kd_red=rgb_color[0], kd_green=rgb_color[1], kd_blue=rgb_color[2],
|
|
367
|
+
kd_texture_ID=9223372036854775807, # diffuse
|
|
368
|
+
ns=0.1, # specular exponent
|
|
369
|
+
alpha=1 # opacity
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
rex_bytes += data_header_bytes + material_bytes
|
|
373
|
+
|
|
374
|
+
if topography is True:
|
|
375
|
+
# Write data block header for Material 1
|
|
376
|
+
data_header_bytes = write_data_block_header(
|
|
377
|
+
data_type=5, # Material data type
|
|
378
|
+
version_data=1, # Version. Probably useful for operation counter
|
|
379
|
+
size_data=68, # Size of the block is FIXED
|
|
380
|
+
data_id=-1 # self.data_id
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
rgb_color = [1, 1, 1]
|
|
384
|
+
# Write Material
|
|
385
|
+
material_bytes = write_material_data(
|
|
386
|
+
ka_red=rgb_color[0], ka_green=rgb_color[1], ka_blue=rgb_color[2],
|
|
387
|
+
ka_texture_ID=9223372036854775807, # ambient
|
|
388
|
+
ks_red=rgb_color[0], ks_green=rgb_color[1], ks_blue=rgb_color[2],
|
|
389
|
+
ks_texture_ID=9223372036854775807, # specular
|
|
390
|
+
kd_red=rgb_color[0], kd_green=rgb_color[1], kd_blue=rgb_color[2],
|
|
391
|
+
kd_texture_ID=9223372036854775807, # diffuse
|
|
392
|
+
ns=0.1, # specular exponent
|
|
393
|
+
alpha=1 # opacity
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
self.data_id += 1
|
|
397
|
+
rex_bytes += data_header_bytes + material_bytes
|
|
398
|
+
|
|
399
|
+
return rex_bytes
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def encode(input_: list):
|
|
403
|
+
"""Encode python objects - normally Python primitives or numpy arrays - into
|
|
404
|
+
its correspondent byte representation
|
|
405
|
+
|
|
406
|
+
Args:
|
|
407
|
+
input_ (List[tuples]): List of tuples: (object, type)
|
|
408
|
+
|
|
409
|
+
Returns:
|
|
410
|
+
byte: Array of bytes
|
|
411
|
+
"""
|
|
412
|
+
global n_bytes
|
|
413
|
+
block = bytearray()
|
|
414
|
+
|
|
415
|
+
for tup in input_:
|
|
416
|
+
arr = np.array(tup[0], dtype=tup[1]).tobytes()
|
|
417
|
+
n_bytes += len(arr)
|
|
418
|
+
block += arr
|
|
419
|
+
|
|
420
|
+
return block
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def write_file_header_block(n_data_blocks, size_data_blocks, version=1,
|
|
424
|
+
start_data=86, srid=3876, offsets=None):
|
|
425
|
+
"""
|
|
426
|
+
Function that writes the header block of a rexfile:
|
|
427
|
+
|
|
428
|
+
Args:
|
|
429
|
+
n_data_blocks:
|
|
430
|
+
size_data_blocks:
|
|
431
|
+
version (int): Version of the file
|
|
432
|
+
start_data (int): Position where data start. This is after the header
|
|
433
|
+
and coordinate system. If everything works fine it should be 86
|
|
434
|
+
srid (int): Spatial reference system identifier (srid)
|
|
435
|
+
offsets:
|
|
436
|
+
|
|
437
|
+
Returns:
|
|
438
|
+
|
|
439
|
+
"""
|
|
440
|
+
reserved = '0' * 42
|
|
441
|
+
if offsets is None:
|
|
442
|
+
offsets = [0, 0, 0]
|
|
443
|
+
|
|
444
|
+
input_ = [('REX1', 'bytes'), # REX1
|
|
445
|
+
(version, 'uint16'), # file version
|
|
446
|
+
(0, 'uint32'), # CRC32
|
|
447
|
+
(n_data_blocks, 'uint16'), # Number of DATA BLOCKS
|
|
448
|
+
(start_data, 'uint16'), # StartData
|
|
449
|
+
(size_data_blocks, 'uint64'), # Size of all data blocks
|
|
450
|
+
(reserved, 'bytes'), # Reserved
|
|
451
|
+
# Coordinate system block
|
|
452
|
+
(srid, 'uint32'), # Spatial reference system identifier (srid)
|
|
453
|
+
(4, 'uint16'), # Size of the name of the used system.
|
|
454
|
+
('EPSG', 'bytes'), # name of the used system.
|
|
455
|
+
(offsets, 'float32')] # Global x, y, z offset
|
|
456
|
+
|
|
457
|
+
block_bytes = encode(input_)
|
|
458
|
+
return block_bytes
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def write_data_block_header(size_data, data_id=1, data_type=3, version_data=1):
|
|
462
|
+
"""Function to write a DATA BLOCK header.
|
|
463
|
+
|
|
464
|
+
Args:
|
|
465
|
+
size_data: data block size (without header)
|
|
466
|
+
data_id: id which is used in the database
|
|
467
|
+
data_type (int): Type of data the data block contains:
|
|
468
|
+
* 0 LineSet A list of vertices which get connected by line segments
|
|
469
|
+
* 1 Text A position information and the actual text
|
|
470
|
+
* 2 PointList A list of 3D points with color information (e.g. point cloud)
|
|
471
|
+
* 3 Mesh A triangle mesh datastructure️
|
|
472
|
+
* 4 Image A single of arbitrary format can be stored in this block
|
|
473
|
+
* 5 MaterialStandard A standard (mesh) material definition
|
|
474
|
+
* 6 SceneNode A wrapper around a data block which can be used in the scenegraph
|
|
475
|
+
* 7 Track A track is a tracked position and orientation of an AR device
|
|
476
|
+
version_data: version for this data block
|
|
477
|
+
|
|
478
|
+
Returns:
|
|
479
|
+
|
|
480
|
+
"""
|
|
481
|
+
|
|
482
|
+
input_ = [(data_type, 'uint16'), # data type
|
|
483
|
+
(version_data, 'uint16'), # version for this data block
|
|
484
|
+
(size_data, 'uint32'), # data block size (without header)
|
|
485
|
+
(data_id, 'uint64')] # id which is used in the database
|
|
486
|
+
|
|
487
|
+
block_bytes = encode(input_)
|
|
488
|
+
return block_bytes
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
def write_mesh_header(n_vtx_coord, n_triangles,
|
|
492
|
+
start_vtx_coord, start_nor_coord, start_tex_coord, start_vtx_colors,
|
|
493
|
+
start_triangles,
|
|
494
|
+
name, material_id=1, # material_id=9223372036854775807
|
|
495
|
+
n_nor_coord=0, n_tex_coord=0, n_vtx_colors=0,
|
|
496
|
+
lod=1, max_lod=1):
|
|
497
|
+
"""Function to write MESH DATA BLOCK header. The header size is fixed at 128 bytes.
|
|
498
|
+
|
|
499
|
+
Args:
|
|
500
|
+
n_vtx_coord: number of vertex coordinates
|
|
501
|
+
n_triangles: number of triangles
|
|
502
|
+
start_vtx_coord: start vertex coordinate block (relative to mesh block start)
|
|
503
|
+
start_nor_coord: start vertex normals block (relative to mesh block start)
|
|
504
|
+
start_tex_coord: start of texture coordinate block (relative to mesh block start)
|
|
505
|
+
start_vtx_colors: start of colors block (relative to mesh block start)
|
|
506
|
+
start_triangles: start triangle block for vertices (relative to mesh block start)
|
|
507
|
+
name (str): Name of the mesh
|
|
508
|
+
material_id (int): id which refers to the corresponding material block in this file
|
|
509
|
+
n_nor_coord: number of normal coordinates (can be zero)
|
|
510
|
+
n_tex_coord: number of texture coordinates (can be zero)
|
|
511
|
+
n_vtx_colors: number of vertex colors (can be zero)
|
|
512
|
+
lod (int): level of detail for the given geometry
|
|
513
|
+
max_lod (int): maximal level of detail for given geometry
|
|
514
|
+
|
|
515
|
+
Returns:
|
|
516
|
+
bytes: array of bytes
|
|
517
|
+
"""
|
|
518
|
+
|
|
519
|
+
# Strings are immutable so there is no way to modify them in place
|
|
520
|
+
str_size = len(name) # Size of the actual name of the mesh
|
|
521
|
+
rest_name = ' ' * (74 - str_size) #
|
|
522
|
+
full_name = name + rest_name
|
|
523
|
+
|
|
524
|
+
input_ = [([lod, max_lod], 'uint16'), # Level of detail
|
|
525
|
+
([n_vtx_coord, # number of vertex coordinates
|
|
526
|
+
n_nor_coord, # number of normal coordinates (can be zero)
|
|
527
|
+
n_tex_coord, # number of texture coordinates (can be zero)
|
|
528
|
+
n_vtx_colors, # number of vertex colors (can be zero)
|
|
529
|
+
n_triangles, # number of triangles
|
|
530
|
+
start_vtx_coord, # start vertex coordinate block (relative to mesh block start)
|
|
531
|
+
start_nor_coord, # start vertex normals block (relative to mesh block start)
|
|
532
|
+
start_tex_coord, # start of texture coordinate block (relative to mesh block start)
|
|
533
|
+
start_vtx_colors, # start of colors block (relative to mesh block start)
|
|
534
|
+
start_triangles # start triangle block for vertices (relative to mesh block start)
|
|
535
|
+
],
|
|
536
|
+
'uint32'),
|
|
537
|
+
(material_id, 'uint64'),
|
|
538
|
+
# id which refers to the corresponding material block in this file
|
|
539
|
+
(str_size, 'uint16'), # size of the following string name
|
|
540
|
+
(full_name, 'bytes')] # name of the mesh (this is user-readable)
|
|
541
|
+
|
|
542
|
+
block_bytes = encode(input_)
|
|
543
|
+
return block_bytes
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
def write_mesh_coordinates(vertex, triangles, normal=None, texture=None, colors=None):
|
|
547
|
+
"""Block with the coordinates of a mesh. This has to go with a header!
|
|
548
|
+
|
|
549
|
+
Args:
|
|
550
|
+
vertex (numpy.ndarray[float32]): Array of vertex XYZXYZ...
|
|
551
|
+
triangles (numpy.ndarray[int32]): This is a list of integers which form
|
|
552
|
+
one triangle. Please make sure that normal and texture coordinates are inline with the
|
|
553
|
+
vertex coordinates. One index refers to the same normal and texture position. The
|
|
554
|
+
triangle orientation is required to be counter-clockwise (CCW)
|
|
555
|
+
normal (numpy.ndarray):
|
|
556
|
+
texture (numpy.ndarray):
|
|
557
|
+
colors (numpy.ndarray):
|
|
558
|
+
|
|
559
|
+
Returns:
|
|
560
|
+
|
|
561
|
+
"""
|
|
562
|
+
|
|
563
|
+
ver = vertex.ravel()
|
|
564
|
+
tri = triangles.ravel()
|
|
565
|
+
if normal is None:
|
|
566
|
+
normal = []
|
|
567
|
+
if texture is None:
|
|
568
|
+
texture = []
|
|
569
|
+
if colors is None:
|
|
570
|
+
colors = []
|
|
571
|
+
|
|
572
|
+
input_ = [(ver, 'float32'),
|
|
573
|
+
(normal, 'float32'),
|
|
574
|
+
(texture, 'float32'),
|
|
575
|
+
(colors, 'float32'),
|
|
576
|
+
(tri, 'uint32')]
|
|
577
|
+
|
|
578
|
+
block_bytes = encode(input_)
|
|
579
|
+
return block_bytes
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
def write_material_data(ka_red=255.0 / 255, ka_green=255.0 / 255, ka_blue=255.0 / 255,
|
|
583
|
+
ka_texture_ID=9223372036854775807, # ambient
|
|
584
|
+
ks_red=255.0 / 255, ks_green=255.0 / 255, ks_blue=255.0 / 255,
|
|
585
|
+
ks_texture_ID=9223372036854775807, # specular
|
|
586
|
+
kd_red=255.0 / 255, kd_green=255.0 / 255, kd_blue=255.0 / 255,
|
|
587
|
+
kd_texture_ID=9223372036854775807, # diffuse
|
|
588
|
+
ns=0.1, # specular exponent
|
|
589
|
+
alpha=1 # opacity
|
|
590
|
+
):
|
|
591
|
+
"""Writes a standard material definition block
|
|
592
|
+
|
|
593
|
+
Returns: bytes (size:68) representation of the material
|
|
594
|
+
|
|
595
|
+
"""
|
|
596
|
+
|
|
597
|
+
input_ = [(ka_red, 'float32'), (ka_green, 'float32'), (ka_blue, 'float32'),
|
|
598
|
+
(ka_texture_ID, 'uint64'),
|
|
599
|
+
(ks_red, 'float32'), (ks_green, 'float32'), (ks_blue, 'float32'),
|
|
600
|
+
(ks_texture_ID, 'uint64'),
|
|
601
|
+
(kd_red, 'float32'), (kd_green, 'float32'), (kd_blue, 'float32'),
|
|
602
|
+
(kd_texture_ID, 'uint64'),
|
|
603
|
+
(ns, 'float32'), (alpha, 'float32')]
|
|
604
|
+
|
|
605
|
+
block_bytes = encode(input_)
|
|
606
|
+
return block_bytes
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
# TODO Move to utils
|
|
610
|
+
def hex_to_rgb(hex):
|
|
611
|
+
"""Transform colors from hex to rgb"""
|
|
612
|
+
hex = hex.lstrip('#')
|
|
613
|
+
hlen = len(hex)
|
|
614
|
+
return tuple(int(hex[i:i + hlen // 3], 16) for i in range(0, hlen, hlen // 3))
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
def geomodel_to_rex(geo_model, backside=True):
|
|
618
|
+
"""
|
|
619
|
+
|
|
620
|
+
Args:
|
|
621
|
+
geo_model (gempy.Model):
|
|
622
|
+
"""
|
|
623
|
+
|
|
624
|
+
# Fixed sizes
|
|
625
|
+
mesh_header_size = 128
|
|
626
|
+
file_header_size = 86
|
|
627
|
+
|
|
628
|
+
# Init dict
|
|
629
|
+
rex_bytes = {}
|
|
630
|
+
|
|
631
|
+
# Check if surfaces are computed
|
|
632
|
+
try:
|
|
633
|
+
# Drop basement
|
|
634
|
+
surface_df = geo_model._surfaces.df.groupby(
|
|
635
|
+
['isActive', 'isBasement']).get_group((True, False))
|
|
636
|
+
except (IndexError, KeyError):
|
|
637
|
+
raise RuntimeError('No computed surfaces yet.')
|
|
638
|
+
|
|
639
|
+
# Loop surfaces
|
|
640
|
+
for idx, surface_vals in surface_df.iterrows():
|
|
641
|
+
ver = surface_vals['vertices']
|
|
642
|
+
tri = surface_vals['edges']
|
|
643
|
+
if tri is np.nan:
|
|
644
|
+
break
|
|
645
|
+
|
|
646
|
+
# Grab surface color
|
|
647
|
+
col = surface_vals['color']
|
|
648
|
+
|
|
649
|
+
# Give color to each vertex
|
|
650
|
+
colors = (np.zeros_like(ver) + hex_to_rgb(col)) / 255
|
|
651
|
+
|
|
652
|
+
# This depends. For RexViewer we need to flip XYZ. For GemPlay not really
|
|
653
|
+
ver_ = np.copy(ver)
|
|
654
|
+
ver_[:, 2] = ver[:, 1]
|
|
655
|
+
ver_[:, 1] = ver[:, 2]
|
|
656
|
+
# ----------------
|
|
657
|
+
|
|
658
|
+
# Coping triangles to create the backside normal of the layers
|
|
659
|
+
tri_ = np.copy(tri)
|
|
660
|
+
|
|
661
|
+
# Preprocessing GemPy output
|
|
662
|
+
ver_ravel, tri_ravel, n_vtx_coord, n_triangles = mesh_preprocess(ver_, tri_)
|
|
663
|
+
|
|
664
|
+
# Calculate the size of the mesh block
|
|
665
|
+
if backside is True:
|
|
666
|
+
n_sides = 2
|
|
667
|
+
else:
|
|
668
|
+
n_sides = 1
|
|
669
|
+
|
|
670
|
+
mesh_block_size_no_data_block_header = (2 * # Coordinates and colors
|
|
671
|
+
n_vtx_coord + n_triangles) * 4 + \
|
|
672
|
+
mesh_header_size # This is cte 128
|
|
673
|
+
|
|
674
|
+
# Size of a MATERIAL DATA BLOCK is cte
|
|
675
|
+
material_block_size_no_data_block_header = 68
|
|
676
|
+
|
|
677
|
+
# Write file header
|
|
678
|
+
if backside is True:
|
|
679
|
+
n_data_blocks = 3
|
|
680
|
+
else:
|
|
681
|
+
n_data_blocks = 2
|
|
682
|
+
header_bytes = write_file_header_block(n_data_blocks=n_data_blocks,
|
|
683
|
+
size_data_blocks=
|
|
684
|
+
n_sides * mesh_block_size_no_data_block_header +
|
|
685
|
+
rexDataBlockHeaderSize +
|
|
686
|
+
material_block_size_no_data_block_header,
|
|
687
|
+
start_data=file_header_size)
|
|
688
|
+
|
|
689
|
+
# Write data block header for Mesh 1
|
|
690
|
+
data_bytes = write_data_block_header(size_data=mesh_block_size_no_data_block_header,
|
|
691
|
+
data_id=1, data_type=3, version_data=1)
|
|
692
|
+
|
|
693
|
+
# Write Mesh 1 block - header
|
|
694
|
+
mesh_header_bytes = write_mesh_header(n_vtx_coord / 3, n_triangles / 3,
|
|
695
|
+
n_vtx_colors=n_vtx_coord / 3,
|
|
696
|
+
start_vtx_coord=mesh_header_size,
|
|
697
|
+
start_nor_coord=mesh_header_size + n_vtx_coord * 4,
|
|
698
|
+
start_tex_coord=mesh_header_size + n_vtx_coord * 4,
|
|
699
|
+
start_vtx_colors=mesh_header_size + n_vtx_coord * 4,
|
|
700
|
+
start_triangles=mesh_header_size + 2 *
|
|
701
|
+
(n_vtx_coord * 4),
|
|
702
|
+
name='rock1', material_id=0)
|
|
703
|
+
|
|
704
|
+
# Write Mesh 1 block - header
|
|
705
|
+
mesh_block_bytes = write_mesh_coordinates(ver_ravel, tri_ravel, colors=colors.ravel())
|
|
706
|
+
|
|
707
|
+
if backside:
|
|
708
|
+
# Write data block header for Mesh 2
|
|
709
|
+
data_bytes_r = write_data_block_header(size_data=mesh_block_size_no_data_block_header,
|
|
710
|
+
data_id=2, data_type=3, version_data=1)
|
|
711
|
+
|
|
712
|
+
# TURN normals - One side of the normals
|
|
713
|
+
tri_[:, 2] = tri[:, 1]
|
|
714
|
+
tri_[:, 1] = tri[:, 2]
|
|
715
|
+
|
|
716
|
+
ver_ravel, tri_ravel, n_vtx_coord, n_triangles = mesh_preprocess(ver_, tri_)
|
|
717
|
+
|
|
718
|
+
# Write Mesh 2 block - header
|
|
719
|
+
mesh_header_bytes_r = write_mesh_header(n_vtx_coord / 3, n_triangles / 3,
|
|
720
|
+
n_vtx_colors=n_vtx_coord / 3,
|
|
721
|
+
start_vtx_coord=mesh_header_size,
|
|
722
|
+
start_nor_coord=mesh_header_size + n_vtx_coord * 4,
|
|
723
|
+
start_tex_coord=mesh_header_size + n_vtx_coord * 4,
|
|
724
|
+
start_vtx_colors=mesh_header_size + n_vtx_coord * 4,
|
|
725
|
+
start_triangles=mesh_header_size + 2 *
|
|
726
|
+
(n_vtx_coord * 4),
|
|
727
|
+
name='test_a', material_id=0)
|
|
728
|
+
|
|
729
|
+
# Write Mesh 2 block - header
|
|
730
|
+
mesh_block_bytes_r = write_mesh_coordinates(ver_ravel, tri_ravel,
|
|
731
|
+
colors=colors.ravel())
|
|
732
|
+
|
|
733
|
+
# Write data block header for Material 1
|
|
734
|
+
material_header_bytes = write_data_block_header(data_type=5, version_data=1, size_data=68,
|
|
735
|
+
data_id=0)
|
|
736
|
+
|
|
737
|
+
# Write Material 1
|
|
738
|
+
material_bytes = write_material_data()
|
|
739
|
+
|
|
740
|
+
# Putting all data together
|
|
741
|
+
if backside is True:
|
|
742
|
+
all_bytes = header_bytes + data_bytes + mesh_header_bytes + mesh_block_bytes + \
|
|
743
|
+
data_bytes_r + mesh_header_bytes_r + mesh_block_bytes_r + \
|
|
744
|
+
material_header_bytes + material_bytes
|
|
745
|
+
|
|
746
|
+
else:
|
|
747
|
+
all_bytes = header_bytes + data_bytes + mesh_header_bytes + mesh_block_bytes + \
|
|
748
|
+
material_header_bytes + material_bytes
|
|
749
|
+
|
|
750
|
+
# FOR REXView Saving each surface is a rexfile
|
|
751
|
+
rex_bytes[surface_vals['surface']] = all_bytes
|
|
752
|
+
return rex_bytes
|
|
753
|
+
|
|
754
|
+
|
|
755
|
+
def mesh_preprocess(ver, tri):
|
|
756
|
+
"""Prepare GemPy Output to be converted to rex
|
|
757
|
+
|
|
758
|
+
Args:
|
|
759
|
+
ver (numpy.ndarray):
|
|
760
|
+
tri (numpy.ndarray):
|
|
761
|
+
|
|
762
|
+
Returns:
|
|
763
|
+
list: vertices raveled, triangels ravel, n vertex, n triangles
|
|
764
|
+
"""
|
|
765
|
+
|
|
766
|
+
# TODO: Remove the type transform. Technically it does nothing
|
|
767
|
+
ver_ravel = ver.ravel().astype('float32')
|
|
768
|
+
tri_ravel = tri.ravel().astype('int32')
|
|
769
|
+
n_vtx_coord = ver_ravel.shape[0]
|
|
770
|
+
n_triangles = tri_ravel.shape[0]
|
|
771
|
+
return ver_ravel, tri_ravel, n_vtx_coord, n_triangles
|
|
772
|
+
|
|
773
|
+
|
|
774
|
+
def write_file(bytes, path: str):
|
|
775
|
+
"""Write to disk a rexfile from its binary format"""
|
|
776
|
+
|
|
777
|
+
newFile = open(path + ".rex", "wb")
|
|
778
|
+
newFile.write(bytes)
|
|
779
|
+
return True
|
|
780
|
+
|
|
781
|
+
|
|
782
|
+
def write_rex(rex_bytes: dict, path='./gempy_rex'):
|
|
783
|
+
file_names = []
|
|
784
|
+
e = 0
|
|
785
|
+
for key, value in rex_bytes.items():
|
|
786
|
+
file_name = path + key
|
|
787
|
+
write_file(value, file_name)
|
|
788
|
+
file_names.append(file_name + '.rex')
|
|
789
|
+
e += 1
|
|
790
|
+
|
|
791
|
+
return file_names
|