grid-apps 0.1.0__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.
grid_apps/__init__.py ADDED
@@ -0,0 +1,37 @@
1
+ # '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
2
+ # Copyright (c) 2022-2025 Mira Geoscience Ltd. '
3
+ # '
4
+ # This file is part of grid-apps package. '
5
+ # '
6
+ # grid-apps is distributed under the terms and conditions of the MIT License '
7
+ # (see LICENSE file at the root of this source code package). '
8
+ # '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
9
+
10
+ from __future__ import annotations
11
+
12
+ import logging
13
+ from pathlib import Path
14
+
15
+
16
+ try:
17
+ from ._version import __version__
18
+ except ModuleNotFoundError:
19
+ from datetime import datetime
20
+
21
+ __date_str = datetime.today().strftime("%Y%m%d")
22
+ __version__ = "0.0.0.dev0+" + __date_str
23
+
24
+
25
+ logging.basicConfig(level=logging.INFO)
26
+
27
+
28
+ def assets_path() -> Path:
29
+ """Return the path to the assets folder."""
30
+
31
+ parent = Path(__file__).parent
32
+ folder_name = f"{parent.name}-assets"
33
+ assets_folder = parent.parent / folder_name
34
+ if not assets_folder.is_dir():
35
+ raise RuntimeError(f"Assets folder not found: {assets_folder}")
36
+
37
+ return assets_folder
grid_apps/_version.py ADDED
@@ -0,0 +1,2 @@
1
+ # Version placeholder that will be replaced during substitution
2
+ __version__ = "0.1.0"
@@ -0,0 +1,8 @@
1
+ # '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
2
+ # Copyright (c) 2024-2025 Mira Geoscience Ltd. '
3
+ # '
4
+ # This file is part of grid-apps package. '
5
+ # '
6
+ # grid-apps is distributed under the terms and conditions of the MIT License '
7
+ # (see LICENSE file at the root of this source code package). '
8
+ # '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
@@ -0,0 +1,245 @@
1
+ # '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
2
+ # Copyright (c) 2024-2025 Mira Geoscience Ltd. '
3
+ # '
4
+ # This file is part of grid-apps package. '
5
+ # '
6
+ # grid-apps is distributed under the terms and conditions of the MIT License '
7
+ # (see LICENSE file at the root of this source code package). '
8
+ # '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
9
+
10
+ from __future__ import annotations
11
+
12
+ import logging
13
+ import sys
14
+ from pathlib import Path
15
+
16
+ import numpy as np
17
+ from discretize import TreeMesh
18
+ from geoapps_utils.base import Driver as BaseDriver
19
+ from geoh5py.data import FloatData, ReferencedData
20
+ from geoh5py.objects import BlockModel, Octree
21
+ from geoh5py.ui_json.utils import fetch_active_workspace
22
+ from scipy.spatial import cKDTree
23
+
24
+ from grid_apps.block_model_to_octree.options import BlockModel2OctreeOptions
25
+ from grid_apps.utils import (
26
+ block_model_to_discretize,
27
+ boundary_value_indices,
28
+ tensor_mesh_ordering,
29
+ treemesh_2_octree,
30
+ )
31
+
32
+
33
+ logger = logging.getLogger(__name__)
34
+
35
+
36
+ class Driver(BaseDriver):
37
+ """
38
+ Convert a BlockModel object to Octree with various refinement strategies.
39
+ """
40
+
41
+ _params_class = BlockModel2OctreeOptions
42
+
43
+ def run(self):
44
+ """Create an octree mesh from input values."""
45
+ with fetch_active_workspace(self.params.geoh5, mode="r+"):
46
+ logger.info("Converting BlockModel to Octree mesh . . .")
47
+ octree = self.make_grid()
48
+ output = self.params.out_group or octree
49
+ self.update_monitoring_directory(output)
50
+ logger.info("Done.")
51
+
52
+ return octree
53
+
54
+ @staticmethod
55
+ def block_model_to_treemesh(
56
+ entity: BlockModel, diagonal_balance=True, finalize=True
57
+ ) -> TreeMesh:
58
+ """
59
+ Convert a block model to an octree mesh with the same base cell size and
60
+ centered.
61
+
62
+ :param entity: BlockModel object to be converted
63
+ :param diagonal_balance: Whether to balance the mesh diagonally.
64
+ :param finalize: Whether to finalize the treemesh after creation.
65
+
66
+ :return: TreeMesh object.
67
+ """
68
+ origin = []
69
+ octree_cells = []
70
+ for ii, ax in zip("xyz", "uvz", strict=True):
71
+ cell_sizes = np.abs(getattr(entity, f"{ax}_cells"))
72
+ h_core = cell_sizes.min()
73
+
74
+ # Compute number of octree cells to span the extent
75
+ n_c = np.ceil(np.log2(np.sum(cell_sizes) / h_core))
76
+ cell_sizes_octree = np.ones(int(2**n_c)) * h_core
77
+ octree_cells.append(cell_sizes_octree)
78
+
79
+ # Colocate the center of the octree with the center of the block model
80
+ ind_core = np.where(cell_sizes == h_core)[0]
81
+ center = (
82
+ entity.origin[ii]
83
+ + entity.local_axis_centers(ax)[ind_core[len(ind_core) // 2]]
84
+ )
85
+
86
+ axis_center = len(cell_sizes_octree) // 2
87
+ origin.append(center - np.sum(cell_sizes_octree[:axis_center]) - h_core / 2)
88
+
89
+ treemesh = TreeMesh(
90
+ octree_cells,
91
+ x0=origin,
92
+ finalize=finalize,
93
+ diagonal_balance=diagonal_balance,
94
+ )
95
+
96
+ return treemesh
97
+
98
+ def make_grid(self) -> Octree:
99
+ """
100
+ Convert the block model and output the octree mesh.
101
+
102
+ :return: Octree object refined by the cell volumes or gradient of the data.
103
+ """
104
+ with fetch_active_workspace(self.params.geoh5, mode="r+"):
105
+ entity = self.params.entity
106
+
107
+ treemesh = Driver.block_model_to_treemesh(entity, finalize=False)
108
+ model = None
109
+ if self.params.data is None:
110
+ treemesh = Driver.refine_by_cell_volumes(
111
+ treemesh, entity, finalize=True
112
+ )
113
+ else:
114
+ treemesh = Driver.refine_by_values(
115
+ treemesh, self.params.data, finalize=True
116
+ )
117
+ # Transfer the model
118
+ ind = treemesh.get_containing_cells(entity.centroids)
119
+ model = (
120
+ np.ones(treemesh.n_cells, dtype=self.params.data.values.dtype)
121
+ * self.params.data.nan_value
122
+ )
123
+ model[ind] = self.params.data.values
124
+
125
+ nan_vals = (model == self.params.data.nan_value) | np.isnan(model)
126
+ if np.any(nan_vals):
127
+ tree = cKDTree(entity.centroids)
128
+ ind = tree.query(treemesh.cell_centers[nan_vals])[1]
129
+ model[nan_vals] = self.params.data.values[ind]
130
+
131
+ octree = treemesh_2_octree(
132
+ self.params.geoh5,
133
+ treemesh,
134
+ parent=self.params.output.out_group,
135
+ name=self.params.output.export_as or entity.name + "_octree",
136
+ )
137
+
138
+ if model is not None and self.params.data is not None:
139
+ octree.add_data(
140
+ {
141
+ self.params.data.name: {
142
+ "values": model,
143
+ "entity_type": self.params.data.entity_type,
144
+ }
145
+ }
146
+ )
147
+
148
+ return octree
149
+
150
+ @staticmethod
151
+ def refine_by_cell_volumes(
152
+ mesh: TreeMesh,
153
+ entity: BlockModel,
154
+ finalize: bool = True,
155
+ mask: np.ndarray | None = None,
156
+ ) -> TreeMesh:
157
+ """
158
+ Refine the octree mesh by the cell volumes of the block model.
159
+
160
+ :param mesh: TreeMesh object to be refined.
161
+ :param entity: BlockModel object to be used for refinement.
162
+ :param finalize: Whether to finalize the treemesh after refinement.
163
+ :param mask: Optional mask on the block model centroids to apply the refinement over.
164
+
165
+ :return: TreeMesh object with refined levels.
166
+ """
167
+ if not isinstance(entity, BlockModel):
168
+ raise TypeError("entity must be an instance of BlockModel.")
169
+
170
+ tensor_oct_level = []
171
+ for ax in "uvz":
172
+ cell_sizes = np.abs(getattr(entity, f"{ax}_cells"))
173
+ h_core = cell_sizes.min()
174
+ # Find the core region
175
+ tensor_oct_level.append(np.log2(cell_sizes / h_core).astype(int))
176
+
177
+ e_x, e_y, e_z = np.meshgrid(*tensor_oct_level)
178
+ max_level = np.c_[np.ravel(e_x), np.ravel(e_y), np.ravel(e_z)].max(axis=1)
179
+
180
+ locations = entity.centroids
181
+ if mask is not None:
182
+ locations = locations[mask]
183
+ max_level = max_level[mask]
184
+
185
+ mesh.insert_cells(locations, mesh.max_level - max_level, finalize=finalize)
186
+
187
+ return mesh
188
+
189
+ @staticmethod
190
+ def refine_by_values(
191
+ mesh: TreeMesh, data: FloatData | ReferencedData, finalize=True
192
+ ) -> TreeMesh:
193
+ """
194
+ Increase the mesh resolution based on the gradient of data values.
195
+
196
+ :param mesh: Input TreeMesh object.
197
+ :param data: FloatData or ReferencedData object containing the values to
198
+ be used for refinement.
199
+ :param finalize: Whether to finalize the treemesh after refinement.
200
+
201
+ :return: TreeMesh object with refined levels.
202
+ """
203
+ if not isinstance(data, FloatData | ReferencedData):
204
+ raise TypeError(
205
+ "Argument 'data' must be an instance of FloatData or ReferencedData."
206
+ )
207
+
208
+ entity = data.parent
209
+ if not isinstance(entity, BlockModel):
210
+ raise TypeError("The parent of 'data' must be an instance of BlockModel.")
211
+
212
+ tensor = block_model_to_discretize(entity)
213
+ indices = tensor_mesh_ordering(entity)
214
+
215
+ gradients = np.abs(tensor.cell_gradient @ data.values[indices])
216
+ levels = np.zeros(gradients.shape, dtype=int)
217
+ isnan = np.isnan(gradients)
218
+
219
+ if isinstance(data, FloatData):
220
+ actives = gradients[~isnan]
221
+ bins = np.percentile(
222
+ actives[actives > 0], np.linspace(5, 95, mesh.max_level)
223
+ )
224
+ levels[~isnan] = np.searchsorted(bins, actives)
225
+ else:
226
+ levels[gradients > 0] = mesh.max_level
227
+
228
+ # Refine on the value/nan interface, without boundary cells
229
+ if any(isnan):
230
+ horizon = boundary_value_indices(
231
+ tensor, data.values[indices], data.nan_value
232
+ )
233
+ mesh = Driver.refine_by_cell_volumes(
234
+ mesh, entity, finalize=False, mask=horizon[np.argsort(indices)]
235
+ )
236
+
237
+ locs = tensor.average_cell_to_face @ tensor.cell_centers
238
+ mesh.insert_cells(locs[~isnan], levels[~isnan].astype(int), finalize=finalize)
239
+
240
+ return mesh
241
+
242
+
243
+ if __name__ == "__main__":
244
+ file = Path(sys.argv[1]).resolve()
245
+ Driver.start(file)
@@ -0,0 +1,60 @@
1
+ # '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
2
+ # Copyright (c) 2024-2025 Mira Geoscience Ltd. '
3
+ # '
4
+ # This file is part of grid-apps package. '
5
+ # '
6
+ # grid-apps is distributed under the terms and conditions of the MIT License '
7
+ # (see LICENSE file at the root of this source code package). '
8
+ # '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
9
+
10
+ from __future__ import annotations
11
+
12
+ from pathlib import Path
13
+ from typing import ClassVar
14
+
15
+ from geoapps_utils.base import Options
16
+ from geoh5py.data import FloatData, ReferencedData
17
+ from geoh5py.groups import UIJsonGroup
18
+ from geoh5py.objects import BlockModel
19
+ from pydantic import BaseModel, ConfigDict
20
+
21
+ from grid_apps import assets_path
22
+
23
+
24
+ class OutputOptions(BaseModel):
25
+ """
26
+ Output parameters for block model creation.
27
+
28
+ :param export_as: Name of the output entity.
29
+ :param out_group: Output UIJson group.
30
+ """
31
+
32
+ model_config = ConfigDict(arbitrary_types_allowed=True)
33
+
34
+ export_as: str | None = None
35
+ out_group: UIJsonGroup | None = None
36
+
37
+
38
+ class BlockModel2OctreeOptions(Options):
39
+ """
40
+ Block model parameters for use with `block_models.driver`.
41
+
42
+ :param entity: BlockModel source object.
43
+ :param data: Optional data to refine the octree mesh.
44
+ :param output: Output options.
45
+ """
46
+
47
+ model_config = ConfigDict(arbitrary_types_allowed=True)
48
+
49
+ name: ClassVar[str] = "block_model_to_octree"
50
+ default_ui_json: ClassVar[Path] = (
51
+ assets_path() / "uijson/block_model_to_octree.ui.json"
52
+ )
53
+ title: ClassVar[str] = "Block Model to Octree Conversion"
54
+ run_command: ClassVar[str] = "grid_apps.block_model_to_octree.driver"
55
+ conda_environment: str = "grid_apps"
56
+
57
+ entity: BlockModel
58
+ data: FloatData | ReferencedData | None = None
59
+
60
+ output: OutputOptions = OutputOptions()
@@ -0,0 +1,8 @@
1
+ # '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
2
+ # Copyright (c) 2024-2025 Mira Geoscience Ltd. '
3
+ # '
4
+ # This file is part of grid-apps package. '
5
+ # '
6
+ # grid-apps is distributed under the terms and conditions of the MIT License '
7
+ # (see LICENSE file at the root of this source code package). '
8
+ # '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
@@ -0,0 +1,206 @@
1
+ # '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
2
+ # Copyright (c) 2024-2025 Mira Geoscience Ltd. '
3
+ # '
4
+ # This file is part of grid-apps package. '
5
+ # '
6
+ # grid-apps is distributed under the terms and conditions of the MIT License '
7
+ # (see LICENSE file at the root of this source code package). '
8
+ # '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
9
+
10
+ from __future__ import annotations
11
+
12
+ import logging
13
+ import sys
14
+ from pathlib import Path
15
+
16
+ import numpy as np
17
+ from discretize.utils import mesh_utils
18
+ from geoapps_utils.base import Driver as BaseDriver
19
+ from geoh5py.objects import BlockModel
20
+ from geoh5py.shared.utils import fetch_active_workspace
21
+ from geoh5py.workspace import Workspace
22
+ from scipy.spatial import cKDTree
23
+
24
+ from grid_apps.block_models.options import BlockModelOptions
25
+
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+
30
+ class Driver(BaseDriver):
31
+ """
32
+ Create BlockModel from parameters.
33
+
34
+ :param parameters: BlockModelOptions or InputFile containing the parameters.
35
+ """
36
+
37
+ _params_class = BlockModelOptions
38
+
39
+ def run(self):
40
+ """Create an octree mesh from input values."""
41
+ with fetch_active_workspace(self.params.geoh5, mode="r+"):
42
+ logger.info("Creating BlockModel mesh from parameters . . .")
43
+ block = self.make_grid()
44
+ output = self.params.out_group or block
45
+ self.update_monitoring_directory(output)
46
+ logger.info("Done.")
47
+
48
+ return block
49
+
50
+ def make_grid(self):
51
+ """
52
+ Make block model object from input data.
53
+ """
54
+ with fetch_active_workspace(self.params.geoh5, mode="r+"):
55
+ source_locations = self.params.source.objects.locations
56
+ if source_locations is None:
57
+ raise ValueError("Input object has no centroids or vertices.")
58
+
59
+ tree = cKDTree(source_locations)
60
+
61
+ logger.info("Creating block model . . .")
62
+
63
+ block_model = Driver.get_block_model(
64
+ workspace=self.params.geoh5,
65
+ locs=source_locations,
66
+ h=self.params.creation.cell_sizes,
67
+ depth_core=self.params.creation.depth_core,
68
+ pads=self.params.creation.padding,
69
+ expansion_factor=self.params.creation.expansion_factor,
70
+ name=self.params.output.export_as,
71
+ )
72
+
73
+ if self.params.output.out_group is not None:
74
+ block_model.parent = self.params.output.out_group
75
+
76
+ # Try to recenter on nearest
77
+ # Find nearest cells
78
+ if block_model.centroids is None:
79
+ raise ValueError("Block model has no centroids.")
80
+ # TODO: Remove once GEOPY-1602 is merged
81
+
82
+ neighbor_distances, neighbor_indices = tree.query(block_model.centroids)
83
+ nearest_neighbor = np.argmin(neighbor_distances)
84
+ source_to_nearest_neighbor = (
85
+ block_model.centroids[nearest_neighbor, :]
86
+ - source_locations[neighbor_indices[nearest_neighbor], :]
87
+ )
88
+ block_model.origin = (
89
+ np.r_[block_model.origin.tolist()] - source_to_nearest_neighbor
90
+ )
91
+
92
+ return block_model
93
+
94
+ @staticmethod
95
+ def truncate_locs_depths(locs: np.ndarray, depth_core: float) -> np.ndarray:
96
+ """
97
+ Sets locations below core to core bottom.
98
+
99
+ :param locs: Location points.
100
+ :param depth_core: Depth of core mesh below locs.
101
+
102
+ :return locs: locs with depths truncated.
103
+ """
104
+ zmax = locs[:, -1].max() # top of locs
105
+ below_core_ind = (zmax - locs[:, -1]) > depth_core
106
+ core_bottom_elev = zmax - depth_core
107
+ locs[below_core_ind, -1] = (
108
+ core_bottom_elev # sets locations below core to core bottom
109
+ )
110
+ return locs
111
+
112
+ @staticmethod
113
+ def minimum_depth_core(
114
+ locs: np.ndarray, depth_core: float, core_z_cell_size: int
115
+ ) -> float:
116
+ """
117
+ Get minimum depth core.
118
+
119
+ :param locs: Location points.
120
+ :param depth_core: Depth of core mesh below locs.
121
+ :param core_z_cell_size: Cell size in z direction.
122
+
123
+ :return depth_core: Minimum depth core.
124
+ """
125
+ zrange = locs[:, -1].max() - locs[:, -1].min() # locs z range
126
+ if depth_core >= zrange:
127
+ return depth_core - zrange + core_z_cell_size
128
+
129
+ return depth_core
130
+
131
+ @staticmethod
132
+ def find_top_padding(obj: BlockModel, core_z_cell_size: int) -> float:
133
+ """
134
+ Loop through cell spacing and sum until core_z_cell_size is reached.
135
+
136
+ :param obj: Block model.
137
+ :param core_z_cell_size: Cell size in z direction.
138
+
139
+ :return pad_sum: Top padding.
140
+ """
141
+ pad_sum = 0.0
142
+
143
+ if obj.z_cell_delimiters is None:
144
+ raise ValueError("Block model has no z_cell_delimiters.")
145
+
146
+ for h in np.abs(np.diff(obj.z_cell_delimiters)):
147
+ if h != core_z_cell_size:
148
+ pad_sum += h
149
+ else:
150
+ break
151
+
152
+ return pad_sum
153
+
154
+ @staticmethod
155
+ def get_block_model( # pylint: disable=too-many-arguments, too-many-positional-arguments
156
+ workspace: Workspace,
157
+ locs: np.ndarray,
158
+ h: list,
159
+ depth_core: float,
160
+ pads: list,
161
+ expansion_factor: float,
162
+ name: str = "BlockModel",
163
+ ) -> BlockModel:
164
+ """
165
+ Create a BlockModel object from parameters.
166
+
167
+ :param workspace: Workspace.
168
+ :param locs: Location points.
169
+ :param h: Cell size(s) for the core mesh.
170
+ :param depth_core: Depth of core mesh below locs.
171
+ :param pads: len(6) Padding distances [W, E, N, S, Down, Up]
172
+ :param expansion_factor: Expansion factor for padding cells.
173
+ :param name: Block model name.
174
+
175
+ :return object_out: Output block model.
176
+ """
177
+
178
+ locs = Driver.truncate_locs_depths(locs, depth_core)
179
+ depth_core = Driver.minimum_depth_core(locs, depth_core, h[2])
180
+ mesh = mesh_utils.mesh_builder_xyz(
181
+ locs,
182
+ h,
183
+ padding_distance=[
184
+ [pads[0], pads[1]],
185
+ [pads[2], pads[3]],
186
+ [pads[4], pads[5]],
187
+ ],
188
+ depth_core=depth_core,
189
+ expansion_factor=expansion_factor,
190
+ )
191
+
192
+ object_out = BlockModel.create(
193
+ workspace,
194
+ origin=[mesh.x0[0], mesh.x0[1], mesh.x0[2] + mesh.h[2].sum()],
195
+ u_cell_delimiters=mesh.nodes_x - mesh.x0[0],
196
+ v_cell_delimiters=mesh.nodes_y - mesh.x0[1],
197
+ z_cell_delimiters=-(mesh.x0[2] + mesh.h[2].sum() - mesh.nodes_z[::-1]),
198
+ name=name,
199
+ )
200
+
201
+ return object_out
202
+
203
+
204
+ if __name__ == "__main__":
205
+ file = Path(sys.argv[1]).resolve()
206
+ Driver.start(file)
@@ -0,0 +1,104 @@
1
+ # '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
2
+ # Copyright (c) 2024-2025 Mira Geoscience Ltd. '
3
+ # '
4
+ # This file is part of grid-apps package. '
5
+ # '
6
+ # grid-apps is distributed under the terms and conditions of the MIT License '
7
+ # (see LICENSE file at the root of this source code package). '
8
+ # '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
9
+
10
+ from __future__ import annotations
11
+
12
+ from pathlib import Path
13
+ from typing import ClassVar
14
+
15
+ from geoapps_utils.base import Options
16
+ from geoh5py.groups import UIJsonGroup
17
+ from geoh5py.objects import CellObject, Points
18
+ from geoh5py.objects.grid_object import GridObject
19
+ from pydantic import BaseModel, ConfigDict
20
+
21
+ from grid_apps import assets_path
22
+
23
+
24
+ class BlockModelSourceOptions(BaseModel):
25
+ """
26
+ Source parameters providing input data to the driver.
27
+
28
+ :param objects: A Grid2D, Octree, BlockModel, Points, Curve or
29
+ Surface source object.
30
+ """
31
+
32
+ model_config = ConfigDict(arbitrary_types_allowed=True)
33
+
34
+ objects: Points | CellObject | GridObject
35
+
36
+
37
+ class BlockModelCreationOptions(BaseModel):
38
+ """
39
+ Block model specification parameters.
40
+
41
+ :param cell_size_x: Cell size in x direction.
42
+ :param cell_size_y: Cell size in y direction.
43
+ :param cell_size_z: Cell size in z direction.
44
+ :param depth_core: Depth of core mesh below locs.
45
+ :param horizontal_padding: Horizontal padding.
46
+ :param bottom_padding: Bottom padding.
47
+ :param expansion_factor: Expansion factor for padding cells.
48
+ """
49
+
50
+ cell_size_x: float
51
+ cell_size_y: float
52
+ cell_size_z: float
53
+ depth_core: float
54
+ horizontal_padding: float
55
+ bottom_padding: float
56
+ expansion_factor: float
57
+
58
+ @property
59
+ def cell_sizes(self) -> list[float]:
60
+ """
61
+ Cell sizes in x, y and z directions.
62
+ """
63
+ return [self.cell_size_x, self.cell_size_y, self.cell_size_z]
64
+
65
+ @property
66
+ def padding(self) -> list[float]:
67
+ """
68
+ Padding distances in west, east, south, north, down and up directions.
69
+ """
70
+ return [self.horizontal_padding] * 4 + [self.bottom_padding, 0.0]
71
+
72
+
73
+ class BlockModelOutputOptions(BaseModel):
74
+ """
75
+ Output parameters for block model creation.
76
+
77
+ :param export_as: Name of the output entity.
78
+ :param out_group: Name of the output group.
79
+ """
80
+
81
+ model_config = ConfigDict(arbitrary_types_allowed=True)
82
+
83
+ export_as: str = "block_model"
84
+ out_group: UIJsonGroup | None = None
85
+
86
+
87
+ class BlockModelOptions(Options):
88
+ """
89
+ Block model parameters for use with `block_models.driver`.
90
+
91
+ :param source: Source data parameters.
92
+ :param creation: Block Model creation parameters.
93
+ :param output: Block Model output parameters.
94
+ """
95
+
96
+ name: ClassVar[str] = "block_model"
97
+ default_ui_json: ClassVar[Path] = assets_path() / "uijson/block_models.ui.json"
98
+ title: ClassVar[str] = "Block Model Creation"
99
+ run_command: ClassVar[str] = "grid_apps.block_models.driver"
100
+
101
+ conda_environment: str = "grid_apps"
102
+ source: BlockModelSourceOptions
103
+ creation: BlockModelCreationOptions
104
+ output: BlockModelOutputOptions = BlockModelOutputOptions()