1+ import re
2+ from pathlib import Path
3+
14import numpy as np
25
6+ from LoopStructural .utils import getLogger
7+
8+ logger = getLogger (__name__ )
9+
10+
11+ def _normalise_voxet_property (values , property_name , nsteps ):
12+ array = np .asarray (values )
13+ expected_shape = tuple (int (step ) for step in nsteps )
14+ expected_size = int (np .prod (expected_shape ))
15+
16+ if array .shape == expected_shape :
17+ flat_values = array .reshape (- 1 , order = "F" )
18+ else :
19+ flat_values = np .squeeze (array )
20+ if flat_values .shape == expected_shape :
21+ flat_values = flat_values .reshape (- 1 , order = "F" )
22+ elif flat_values .ndim == 1 and flat_values .size == expected_size :
23+ flat_values = flat_values
24+ else :
25+ raise ValueError (
26+ f"Property '{ property_name } ' must have shape { expected_shape } or size { expected_size } "
27+ )
28+
29+ if np .issubdtype (flat_values .dtype , np .integer ):
30+ if flat_values .size == 0 :
31+ export_dtype = np .int8
32+ storage_type = "Octet"
33+ element_size = 1
34+ elif flat_values .min () >= np .iinfo (np .int8 ).min and flat_values .max () <= np .iinfo (np .int8 ).max :
35+ export_dtype = np .int8
36+ storage_type = "Octet"
37+ element_size = 1
38+ else :
39+ export_dtype = np .dtype (">i4" )
40+ storage_type = "Integer"
41+ element_size = 4
42+ no_data_value = None
43+ elif np .issubdtype (flat_values .dtype , np .floating ):
44+ export_dtype = np .dtype (">f4" )
45+ storage_type = "Float"
46+ element_size = 4
47+ no_data_value = - 999999.0
48+ flat_values = np .nan_to_num (flat_values , nan = no_data_value )
49+ else :
50+ raise ValueError (f"Property '{ property_name } ' has unsupported dtype { flat_values .dtype } " )
51+
52+ return {
53+ "values" : np .asarray (flat_values , dtype = export_dtype ),
54+ "storage_type" : storage_type ,
55+ "element_size" : element_size ,
56+ "no_data_value" : no_data_value ,
57+ }
58+
59+
60+ def _write_structured_grid_gocad (grid , file_name ):
61+ """Write a StructuredGrid to GOCAD VOXET format."""
62+ vo_path = Path (file_name ).with_suffix (".vo" )
63+ axis_n = np .asarray (grid .nsteps , dtype = int )
64+ property_source = grid .properties
65+ axis_min = np .min (grid .nodes , axis = 0 )
66+ axis_max = np .max (grid .nodes , axis = 0 )
67+
68+ if property_source :
69+ if grid .cell_properties :
70+ logger .warning (
71+ "StructuredGrid GOCAD export uses point properties; cell_properties were not exported"
72+ )
73+ elif grid .cell_properties :
74+ axis_n = np .asarray (grid .nsteps , dtype = int ) - 1
75+ if np .any (axis_n <= 0 ):
76+ raise ValueError ("StructuredGrid cell_properties require at least two grid nodes per axis" )
77+ property_source = grid .cell_properties
78+ axis_min = np .min (grid .cell_centres , axis = 0 )
79+ axis_max = np .max (grid .cell_centres , axis = 0 )
80+ else :
81+ raise ValueError ("StructuredGrid has no properties to export to GOCAD" )
82+
83+ export_properties = []
84+ for index , (property_name , values ) in enumerate (property_source .items (), start = 1 ):
85+ export_info = _normalise_voxet_property (values , property_name , axis_n )
86+ safe_name = re .sub (r"[^0-9A-Za-z_-]+" , "_" , property_name ).strip ("_" ) or f"property_{ index } "
87+ data_path = vo_path .with_name (f"{ vo_path .stem } _{ safe_name } @@" )
88+ export_properties .append (
89+ {
90+ "index" : index ,
91+ "name" : property_name ,
92+ "data_path" : data_path ,
93+ ** export_info ,
94+ }
95+ )
96+
97+ with open (vo_path , "w" ) as fp :
98+ fp .write (
99+ f"""GOCAD Voxet 1
100+ HEADER {{
101+ name: { grid .name }
102+ }}
103+ GOCAD_ORIGINAL_COORDINATE_SYSTEM
104+ NAME Default
105+ AXIS_NAME \" X\" \" Y\" \" Z\"
106+ AXIS_UNIT \" m\" \" m\" \" m\"
107+ ZPOSITIVE Elevation
108+ END_ORIGINAL_COORDINATE_SYSTEM
109+ AXIS_O 0.000000 0.000000 0.000000
110+ AXIS_U 1.000000 0.000000 0.000000
111+ AXIS_V 0.000000 1.000000 0.000000
112+ AXIS_W 0.000000 0.000000 1.000000
113+ AXIS_MIN { axis_min [0 ]} { axis_min [1 ]} { axis_min [2 ]}
114+ AXIS_MAX { axis_max [0 ]} { axis_max [1 ]} { axis_max [2 ]}
115+ AXIS_N { axis_n [0 ]} { axis_n [1 ]} { axis_n [2 ]}
116+ AXIS_NAME \" X\" \" Y\" \" Z\"
117+ AXIS_UNIT \" m\" \" m\" \" m\"
118+ AXIS_TYPE even even even
119+ """
120+ )
121+ for export_property in export_properties :
122+ fp .write (
123+ f"""PROPERTY { export_property ['index' ]} { export_property ['name' ]}
124+ PROPERTY_CLASS { export_property ['index' ]} { export_property ['name' ]}
125+ PROP_UNIT { export_property ['index' ]} { export_property ['name' ]}
126+ PROPERTY_CLASS_HEADER { export_property ['index' ]} { export_property ['name' ]} {{
127+ }}
128+ PROPERTY_SUBCLASS { export_property ['index' ]} QUANTITY { export_property ['storage_type' ]}
129+ """
130+ )
131+ if export_property ["no_data_value" ] is not None :
132+ fp .write (
133+ f"PROP_NO_DATA_VALUE { export_property ['index' ]} { export_property ['no_data_value' ]} \n "
134+ )
135+ fp .write (
136+ f"""PROP_ETYPE { export_property ['index' ]} IEEE
137+ PROP_FORMAT { export_property ['index' ]} RAW
138+ PROP_ESIZE { export_property ['index' ]} { export_property ['element_size' ]}
139+ PROP_OFFSET { export_property ['index' ]} 0
140+ PROP_FILE { export_property ['index' ]} { export_property ['data_path' ].name }
141+ """
142+ )
143+ fp .write ("END\n " )
144+
145+ for export_property in export_properties :
146+ with open (export_property ["data_path" ], "wb" ) as fp :
147+ export_property ["values" ].tofile (fp )
148+
149+ return True
150+
3151
4152def _write_feat_surfs_gocad (surf , file_name ):
5153 """
@@ -23,8 +171,6 @@ def _write_feat_surfs_gocad(surf, file_name):
23171 True if successful
24172
25173 """
26- from pathlib import Path
27-
28174 properties_header = None
29175 if surf .properties :
30176
@@ -112,8 +258,6 @@ def _write_feat_surfs_gocad(surf, file_name):
112258# True if successful
113259
114260# """
115- # from pathlib import Path
116-
117261# file_name = Path(file_name).with_suffix(".vs")
118262# with open(f"{file_name}", "w") as fd:
119263# fd.write(
@@ -123,4 +267,4 @@ def _write_feat_surfs_gocad(surf, file_name):
123267# }}
124268# GOCAD_ORIGINAL_COORDINATE_SYSTEM
125269# NAME Default
126- # PROJECTION Unknown
270+ # PROJECTION Unknown
0 commit comments