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.
Files changed (82) hide show
  1. subsurface/__init__.py +31 -31
  2. subsurface/_version.py +34 -21
  3. subsurface/api/__init__.py +13 -13
  4. subsurface/api/interfaces/__init__.py +3 -3
  5. subsurface/api/interfaces/stream.py +136 -136
  6. subsurface/api/reader/read_wells.py +78 -78
  7. subsurface/core/geological_formats/boreholes/_combine_trajectories.py +117 -117
  8. subsurface/core/geological_formats/boreholes/_map_attrs_to_survey.py +236 -234
  9. subsurface/core/geological_formats/boreholes/_survey_to_unstruct.py +163 -163
  10. subsurface/core/geological_formats/boreholes/boreholes.py +140 -140
  11. subsurface/core/geological_formats/boreholes/collars.py +26 -26
  12. subsurface/core/geological_formats/boreholes/survey.py +86 -86
  13. subsurface/core/geological_formats/fault.py +47 -47
  14. subsurface/core/reader_helpers/reader_unstruct.py +11 -11
  15. subsurface/core/reader_helpers/readers_data.py +130 -130
  16. subsurface/core/reader_helpers/readers_wells.py +13 -13
  17. subsurface/core/structs/__init__.py +3 -3
  18. subsurface/core/structs/base_structures/__init__.py +2 -2
  19. subsurface/core/structs/base_structures/_aux.py +69 -0
  20. subsurface/core/structs/base_structures/_liquid_earth_mesh.py +121 -121
  21. subsurface/core/structs/base_structures/_unstructured_data_constructor.py +70 -70
  22. subsurface/core/structs/base_structures/base_structures_enum.py +6 -6
  23. subsurface/core/structs/base_structures/structured_data.py +282 -282
  24. subsurface/core/structs/base_structures/unstructured_data.py +338 -319
  25. subsurface/core/structs/structured_elements/octree_mesh.py +10 -10
  26. subsurface/core/structs/structured_elements/structured_grid.py +59 -59
  27. subsurface/core/structs/structured_elements/structured_mesh.py +9 -9
  28. subsurface/core/structs/unstructured_elements/__init__.py +3 -3
  29. subsurface/core/structs/unstructured_elements/line_set.py +72 -72
  30. subsurface/core/structs/unstructured_elements/point_set.py +43 -43
  31. subsurface/core/structs/unstructured_elements/tetrahedron_mesh.py +35 -35
  32. subsurface/core/structs/unstructured_elements/triangular_surface.py +62 -62
  33. subsurface/core/utils/utils_core.py +38 -38
  34. subsurface/modules/reader/__init__.py +13 -13
  35. subsurface/modules/reader/faults/faults.py +80 -80
  36. subsurface/modules/reader/from_binary.py +46 -46
  37. subsurface/modules/reader/mesh/_GOCAD_mesh.py +82 -82
  38. subsurface/modules/reader/mesh/_trimesh_reader.py +447 -447
  39. subsurface/modules/reader/mesh/csv_mesh_reader.py +53 -53
  40. subsurface/modules/reader/mesh/dxf_reader.py +177 -177
  41. subsurface/modules/reader/mesh/glb_reader.py +30 -30
  42. subsurface/modules/reader/mesh/mx_reader.py +232 -232
  43. subsurface/modules/reader/mesh/obj_reader.py +53 -53
  44. subsurface/modules/reader/mesh/omf_mesh_reader.py +43 -43
  45. subsurface/modules/reader/mesh/surface_reader.py +56 -56
  46. subsurface/modules/reader/mesh/surfaces_api.py +41 -41
  47. subsurface/modules/reader/profiles/__init__.py +3 -3
  48. subsurface/modules/reader/profiles/profiles_core.py +197 -197
  49. subsurface/modules/reader/read_netcdf.py +38 -38
  50. subsurface/modules/reader/topography/__init__.py +7 -7
  51. subsurface/modules/reader/topography/topo_core.py +100 -100
  52. subsurface/modules/reader/volume/read_grav3d.py +447 -428
  53. subsurface/modules/reader/volume/read_volume.py +327 -230
  54. subsurface/modules/reader/volume/segy_reader.py +105 -105
  55. subsurface/modules/reader/volume/seismic.py +173 -173
  56. subsurface/modules/reader/volume/volume_utils.py +43 -43
  57. subsurface/modules/reader/wells/DEP/__init__.py +43 -43
  58. subsurface/modules/reader/wells/DEP/_well_files_reader.py +167 -167
  59. subsurface/modules/reader/wells/DEP/_wells_api.py +61 -61
  60. subsurface/modules/reader/wells/DEP/_welly_reader.py +180 -180
  61. subsurface/modules/reader/wells/DEP/pandas_to_welly.py +212 -212
  62. subsurface/modules/reader/wells/_read_to_df.py +57 -57
  63. subsurface/modules/reader/wells/read_borehole_interface.py +148 -148
  64. subsurface/modules/reader/wells/wells_utils.py +68 -68
  65. subsurface/modules/tools/mocking_aux.py +104 -104
  66. subsurface/modules/visualization/__init__.py +2 -2
  67. subsurface/modules/visualization/to_pyvista.py +320 -320
  68. subsurface/modules/writer/to_binary.py +12 -12
  69. subsurface/modules/writer/to_rex/common.py +78 -78
  70. subsurface/modules/writer/to_rex/data_struct.py +74 -74
  71. subsurface/modules/writer/to_rex/gempy_to_rexfile.py +791 -791
  72. subsurface/modules/writer/to_rex/material_encoder.py +44 -44
  73. subsurface/modules/writer/to_rex/mesh_encoder.py +152 -152
  74. subsurface/modules/writer/to_rex/to_rex.py +115 -115
  75. subsurface/modules/writer/to_rex/utils.py +15 -15
  76. subsurface/optional_requirements.py +116 -116
  77. {subsurface_terra-2025.1.0rc15.dist-info → subsurface_terra-2025.1.0rc17.dist-info}/METADATA +194 -194
  78. subsurface_terra-2025.1.0rc17.dist-info/RECORD +99 -0
  79. {subsurface_terra-2025.1.0rc15.dist-info → subsurface_terra-2025.1.0rc17.dist-info}/WHEEL +1 -1
  80. {subsurface_terra-2025.1.0rc15.dist-info → subsurface_terra-2025.1.0rc17.dist-info}/licenses/LICENSE +203 -203
  81. subsurface_terra-2025.1.0rc15.dist-info/RECORD +0 -98
  82. {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