pycphy 0.1.0__py3-none-any.whl → 0.2.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.
- pycphy/__init__.py +11 -0
- pycphy/cli.py +145 -0
- pycphy/config_manager.py +373 -0
- pycphy/foamCaseDeveloper/__init__.py +60 -41
- pycphy/foamCaseDeveloper/config/__init__.py +69 -26
- pycphy/foamCaseDeveloper/config/cad_mesh_config.py +62 -0
- pycphy/foamCaseDeveloper/config/config_hfdibdem.py +193 -0
- pycphy/foamCaseDeveloper/config/constant/__init__.py +23 -0
- pycphy/foamCaseDeveloper/config/constant/dynamic_mesh_config.py +208 -0
- pycphy/foamCaseDeveloper/config/constant/gravity_field_config.py +379 -0
- pycphy/foamCaseDeveloper/config/constant/transport_properties_config.py +225 -0
- pycphy/foamCaseDeveloper/config/constant/turbulence_config.py +617 -0
- pycphy/foamCaseDeveloper/config/csv_boundary_reader.py +219 -0
- pycphy/foamCaseDeveloper/config/system/__init__.py +31 -0
- pycphy/foamCaseDeveloper/config/system/block_mesh_config.py +184 -0
- pycphy/foamCaseDeveloper/config/{control_config.py → system/control_config.py} +113 -1
- pycphy/foamCaseDeveloper/config/system/decompose_par_config.py +525 -0
- pycphy/foamCaseDeveloper/config/system/fv_options_config.py +575 -0
- pycphy/foamCaseDeveloper/config/system/fv_schemes_config.py +363 -0
- pycphy/foamCaseDeveloper/config/system/set_fields_config.py +640 -0
- pycphy/foamCaseDeveloper/config/system/snappy_hex_mesh_config.py +241 -0
- pycphy/foamCaseDeveloper/config/zero/U_config.py +135 -0
- pycphy/foamCaseDeveloper/config/zero/__init__.py +22 -0
- pycphy/foamCaseDeveloper/config/zero/f_config.py +140 -0
- pycphy/foamCaseDeveloper/config/zero/lambda_config.py +157 -0
- pycphy/foamCaseDeveloper/config/zero/p_config.py +97 -0
- pycphy/foamCaseDeveloper/core/__init__.py +30 -18
- pycphy/foamCaseDeveloper/core/block_mesh_developer.py +1 -1
- pycphy/foamCaseDeveloper/core/cad_block_mesh_developer.py +463 -0
- pycphy/foamCaseDeveloper/core/case_builder.py +1217 -0
- pycphy/foamCaseDeveloper/core/foam_case_manager.py +370 -111
- pycphy/foamCaseDeveloper/develop_case.py +640 -0
- pycphy/foamCaseDeveloper/main.py +260 -260
- pycphy/foamCaseDeveloper/utils/myAutoCAD.py +418 -0
- pycphy/foamCaseDeveloper/writers/__init__.py +37 -4
- pycphy/foamCaseDeveloper/writers/constant/__init__.py +25 -0
- pycphy/foamCaseDeveloper/writers/constant/dynamic_mesh_dict_writer.py +75 -0
- pycphy/foamCaseDeveloper/writers/constant/gravity_field_writer.py +88 -0
- pycphy/foamCaseDeveloper/writers/constant/hfdibdem_dict_writer.py +81 -0
- pycphy/foamCaseDeveloper/writers/constant/transport_properties_writer.py +202 -0
- pycphy/foamCaseDeveloper/writers/{turbulence_properties_writer.py → constant/turbulence_properties_writer.py} +49 -1
- pycphy/foamCaseDeveloper/writers/system/__init__.py +31 -0
- pycphy/foamCaseDeveloper/writers/{block_mesh_writer.py → system/block_mesh_writer.py} +1 -1
- pycphy/foamCaseDeveloper/writers/{control_dict_writer.py → system/control_dict_writer.py} +37 -1
- pycphy/foamCaseDeveloper/writers/system/decompose_par_writer.py +228 -0
- pycphy/foamCaseDeveloper/writers/system/fv_options_writer.py +188 -0
- pycphy/foamCaseDeveloper/writers/system/fv_schemes_writer.py +155 -0
- pycphy/foamCaseDeveloper/writers/system/set_fields_writer.py +191 -0
- pycphy/foamCaseDeveloper/writers/system/snappy_hex_mesh_writer.py +123 -0
- pycphy/foamCaseDeveloper/writers/zero/__init__.py +24 -0
- pycphy/foamCaseDeveloper/writers/zero/f_field_writer.py +89 -0
- pycphy/foamCaseDeveloper/writers/zero/lambda_field_writer.py +84 -0
- pycphy/foamCaseDeveloper/writers/zero/p_field_writer.py +89 -0
- pycphy/foamCaseDeveloper/writers/zero/u_field_writer.py +96 -0
- pycphy/foamCaseDeveloper/writers/zero/zero_field_factory.py +388 -0
- {pycphy-0.1.0.dist-info → pycphy-0.2.0.dist-info}/METADATA +154 -6
- pycphy-0.2.0.dist-info/RECORD +63 -0
- pycphy-0.2.0.dist-info/entry_points.txt +3 -0
- pycphy/foamCaseDeveloper/config/block_mesh_config.py +0 -90
- pycphy/foamCaseDeveloper/config/turbulence_config.py +0 -187
- pycphy/foamCaseDeveloper/core/control_dict_writer.py +0 -55
- pycphy/foamCaseDeveloper/core/turbulence_properties_writer.py +0 -68
- pycphy-0.1.0.dist-info/RECORD +0 -24
- pycphy-0.1.0.dist-info/entry_points.txt +0 -2
- {pycphy-0.1.0.dist-info → pycphy-0.2.0.dist-info}/WHEEL +0 -0
- {pycphy-0.1.0.dist-info → pycphy-0.2.0.dist-info}/licenses/LICENSE +0 -0
- {pycphy-0.1.0.dist-info → pycphy-0.2.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1217 @@
|
|
1
|
+
# case_builder.py
|
2
|
+
|
3
|
+
"""
|
4
|
+
Case builder module for OpenFOAM case creation.
|
5
|
+
|
6
|
+
This module provides a more modular approach to building OpenFOAM cases
|
7
|
+
with clear separation of concerns and better error handling.
|
8
|
+
"""
|
9
|
+
|
10
|
+
import os
|
11
|
+
from typing import Dict, Any
|
12
|
+
from abc import ABC, abstractmethod
|
13
|
+
|
14
|
+
from ..writers.system.block_mesh_writer import BlockMeshWriter
|
15
|
+
from ..writers.system.control_dict_writer import ControlDictWriter
|
16
|
+
from ..writers.system.fv_schemes_writer import FvSchemesWriter
|
17
|
+
from ..writers.system.fv_options_writer import FvOptionsWriter
|
18
|
+
from ..writers.system.set_fields_writer import SetFieldsWriter
|
19
|
+
from ..writers.system.decompose_par_writer import DecomposeParWriter
|
20
|
+
from ..writers.system.snappy_hex_mesh_writer import SnappyHexMeshWriter
|
21
|
+
|
22
|
+
from ..writers.constant.turbulence_properties_writer import TurbulencePropertiesWriter
|
23
|
+
from ..writers.constant.dynamic_mesh_dict_writer import DynamicMeshDictWriter
|
24
|
+
from ..writers.constant.hfdibdem_dict_writer import HFDIBDEMDictWriter
|
25
|
+
from ..writers.constant.transport_properties_writer import TransportPropertiesWriter
|
26
|
+
from ..writers.constant.gravity_field_writer import GravityFieldWriter
|
27
|
+
|
28
|
+
from ..writers.zero.p_field_writer import PFieldWriter
|
29
|
+
from ..writers.zero.u_field_writer import UFieldWriter
|
30
|
+
from ..writers.zero.f_field_writer import FFieldWriter
|
31
|
+
from ..writers.zero.lambda_field_writer import LambdaFieldWriter
|
32
|
+
from .block_mesh_developer import BlockMeshDeveloper
|
33
|
+
|
34
|
+
|
35
|
+
class CaseComponent(ABC):
|
36
|
+
"""
|
37
|
+
Abstract base class for OpenFOAM case components.
|
38
|
+
|
39
|
+
Each component represents a specific part of an OpenFOAM case
|
40
|
+
(e.g., mesh, control, turbulence, etc.).
|
41
|
+
"""
|
42
|
+
|
43
|
+
def __init__(self, name: str, description: str = ""):
|
44
|
+
"""
|
45
|
+
Initialize the case component.
|
46
|
+
|
47
|
+
Args:
|
48
|
+
name (str): Name of the component.
|
49
|
+
description (str): Description of the component.
|
50
|
+
"""
|
51
|
+
self.name = name
|
52
|
+
self.description = description
|
53
|
+
self.is_configured = False
|
54
|
+
self.is_required = True
|
55
|
+
|
56
|
+
@abstractmethod
|
57
|
+
def configure(self, **kwargs) -> bool:
|
58
|
+
"""
|
59
|
+
Configure the component with the provided parameters.
|
60
|
+
|
61
|
+
Args:
|
62
|
+
**kwargs: Configuration parameters.
|
63
|
+
|
64
|
+
Returns:
|
65
|
+
bool: True if configuration successful, False otherwise.
|
66
|
+
"""
|
67
|
+
pass
|
68
|
+
|
69
|
+
@abstractmethod
|
70
|
+
def build(self, case_dir: str) -> bool:
|
71
|
+
"""
|
72
|
+
Build the component (create files, etc.).
|
73
|
+
|
74
|
+
Args:
|
75
|
+
case_dir (str): Path to the OpenFOAM case directory.
|
76
|
+
|
77
|
+
Returns:
|
78
|
+
bool: True if build successful, False otherwise.
|
79
|
+
"""
|
80
|
+
pass
|
81
|
+
|
82
|
+
@abstractmethod
|
83
|
+
def validate(self) -> bool:
|
84
|
+
"""
|
85
|
+
Validate the component configuration.
|
86
|
+
|
87
|
+
Returns:
|
88
|
+
bool: True if validation passes, False otherwise.
|
89
|
+
"""
|
90
|
+
pass
|
91
|
+
|
92
|
+
def get_status(self) -> Dict[str, Any]:
|
93
|
+
"""
|
94
|
+
Get the current status of the component.
|
95
|
+
|
96
|
+
Returns:
|
97
|
+
Dict containing status information.
|
98
|
+
"""
|
99
|
+
return {
|
100
|
+
"name": self.name,
|
101
|
+
"description": self.description,
|
102
|
+
"is_configured": self.is_configured,
|
103
|
+
"is_required": self.is_required,
|
104
|
+
"is_valid": self.validate() if self.is_configured else False
|
105
|
+
}
|
106
|
+
|
107
|
+
|
108
|
+
class GeometryComponent(CaseComponent):
|
109
|
+
"""Component for handling geometry and mesh configuration."""
|
110
|
+
|
111
|
+
def __init__(self):
|
112
|
+
super().__init__("Geometry", "Mesh geometry and blockMeshDict configuration")
|
113
|
+
self.geometry_config = {}
|
114
|
+
self.developer = None
|
115
|
+
|
116
|
+
def configure(self, p0: tuple, p1: tuple, cells: tuple,
|
117
|
+
patch_names: Dict[str, str], scale: float = 1.0) -> bool:
|
118
|
+
"""
|
119
|
+
Configure the geometry component.
|
120
|
+
|
121
|
+
Args:
|
122
|
+
p0 (tuple): Minimum corner coordinates (x0, y0, z0).
|
123
|
+
p1 (tuple): Maximum corner coordinates (x1, y1, z1).
|
124
|
+
cells (tuple): Number of cells in each direction (nx, ny, nz).
|
125
|
+
patch_names (Dict): Mapping of face identifiers to custom names.
|
126
|
+
scale (float): Scaling factor for the mesh.
|
127
|
+
|
128
|
+
Returns:
|
129
|
+
bool: True if configuration successful, False otherwise.
|
130
|
+
"""
|
131
|
+
try:
|
132
|
+
self.geometry_config = {
|
133
|
+
'p0': p0,
|
134
|
+
'p1': p1,
|
135
|
+
'cells': cells,
|
136
|
+
'patch_names': patch_names,
|
137
|
+
'scale': scale
|
138
|
+
}
|
139
|
+
|
140
|
+
self.developer = BlockMeshDeveloper(
|
141
|
+
p0=p0, p1=p1, cells=cells,
|
142
|
+
patch_names=patch_names, scale=scale
|
143
|
+
)
|
144
|
+
|
145
|
+
self.is_configured = True
|
146
|
+
return True
|
147
|
+
|
148
|
+
except Exception as e:
|
149
|
+
print(f"Error configuring geometry: {e}")
|
150
|
+
return False
|
151
|
+
|
152
|
+
def build(self, case_dir: str) -> bool:
|
153
|
+
"""Build the blockMeshDict file."""
|
154
|
+
if not self.is_configured:
|
155
|
+
print("Error: Geometry not configured")
|
156
|
+
return False
|
157
|
+
|
158
|
+
try:
|
159
|
+
system_dir = os.path.join(case_dir, "system")
|
160
|
+
os.makedirs(system_dir, exist_ok=True)
|
161
|
+
|
162
|
+
bmd_path = os.path.join(system_dir, "blockMeshDict")
|
163
|
+
|
164
|
+
# Check if this is CAD-based geometry (developer is None but blockMeshDict exists)
|
165
|
+
if self.developer is None:
|
166
|
+
# For CAD-based generation, the blockMeshDict should already exist
|
167
|
+
if os.path.exists(bmd_path):
|
168
|
+
print(f"Using existing blockMeshDict from CAD generation: {bmd_path}")
|
169
|
+
return True
|
170
|
+
else:
|
171
|
+
print(f"Error: CAD-based geometry configured but blockMeshDict not found at {bmd_path}")
|
172
|
+
return False
|
173
|
+
else:
|
174
|
+
# Traditional geometry generation
|
175
|
+
self.developer.create_blockmesh_dict(file_path=bmd_path)
|
176
|
+
return True
|
177
|
+
|
178
|
+
except Exception as e:
|
179
|
+
print(f"Error building geometry: {e}")
|
180
|
+
return False
|
181
|
+
|
182
|
+
def validate(self) -> bool:
|
183
|
+
"""Validate the geometry configuration."""
|
184
|
+
if not self.is_configured:
|
185
|
+
return False
|
186
|
+
|
187
|
+
# Check if this is CAD-based geometry (has CAD mesh summary)
|
188
|
+
if 'total_blocks' in self.geometry_config and 'total_patches' in self.geometry_config:
|
189
|
+
# CAD-based geometry validation
|
190
|
+
total_blocks = self.geometry_config.get('total_blocks', 0)
|
191
|
+
total_patches = self.geometry_config.get('total_patches', 0)
|
192
|
+
total_vertices = self.geometry_config.get('total_vertices', 0)
|
193
|
+
|
194
|
+
if total_blocks <= 0:
|
195
|
+
print(f"Invalid CAD geometry: {total_blocks} blocks")
|
196
|
+
return False
|
197
|
+
|
198
|
+
if total_patches <= 0:
|
199
|
+
print(f"Invalid CAD geometry: {total_patches} patches")
|
200
|
+
return False
|
201
|
+
|
202
|
+
if total_vertices <= 0:
|
203
|
+
print(f"Invalid CAD geometry: {total_vertices} vertices")
|
204
|
+
return False
|
205
|
+
|
206
|
+
return True
|
207
|
+
|
208
|
+
# Traditional geometry validation
|
209
|
+
required_keys = ['p0', 'p1', 'cells', 'patch_names']
|
210
|
+
for key in required_keys:
|
211
|
+
if key not in self.geometry_config:
|
212
|
+
print(f"Missing required geometry parameter: {key}")
|
213
|
+
return False
|
214
|
+
|
215
|
+
# Check coordinate validity
|
216
|
+
p0, p1, cells = self.geometry_config['p0'], self.geometry_config['p1'], self.geometry_config['cells']
|
217
|
+
|
218
|
+
for i, (coord0, coord1) in enumerate(zip(p0, p1)):
|
219
|
+
if coord1 <= coord0:
|
220
|
+
print(f"Invalid geometry: p1[{i}] ({coord1}) must be > p0[{i}] ({coord0})")
|
221
|
+
return False
|
222
|
+
|
223
|
+
for i, cell_count in enumerate(cells):
|
224
|
+
if not isinstance(cell_count, int) or cell_count <= 0:
|
225
|
+
print(f"Invalid cell count in dimension {i}: {cell_count}")
|
226
|
+
return False
|
227
|
+
|
228
|
+
return True
|
229
|
+
|
230
|
+
|
231
|
+
class ControlComponent(CaseComponent):
|
232
|
+
"""Component for handling control dictionary configuration."""
|
233
|
+
|
234
|
+
def __init__(self):
|
235
|
+
super().__init__("Control", "Solver control and time stepping configuration")
|
236
|
+
self.control_params = {}
|
237
|
+
self.writer = None
|
238
|
+
|
239
|
+
def configure(self, control_params: Dict[str, Any]) -> bool:
|
240
|
+
"""
|
241
|
+
Configure the control component.
|
242
|
+
|
243
|
+
Args:
|
244
|
+
control_params (Dict): Dictionary of control parameters.
|
245
|
+
|
246
|
+
Returns:
|
247
|
+
bool: True if configuration successful, False otherwise.
|
248
|
+
"""
|
249
|
+
try:
|
250
|
+
self.control_params = control_params.copy()
|
251
|
+
self.is_configured = True
|
252
|
+
return True
|
253
|
+
|
254
|
+
except Exception as e:
|
255
|
+
print(f"Error configuring control: {e}")
|
256
|
+
return False
|
257
|
+
|
258
|
+
def build(self, case_dir: str) -> bool:
|
259
|
+
"""Build the controlDict file."""
|
260
|
+
if not self.is_configured:
|
261
|
+
print("Error: Control not configured")
|
262
|
+
return False
|
263
|
+
|
264
|
+
try:
|
265
|
+
system_dir = os.path.join(case_dir, "system")
|
266
|
+
os.makedirs(system_dir, exist_ok=True)
|
267
|
+
|
268
|
+
cd_path = os.path.join(system_dir, "controlDict")
|
269
|
+
self.writer = ControlDictWriter(file_path=cd_path, params=self.control_params)
|
270
|
+
|
271
|
+
# Validate parameters before writing
|
272
|
+
if not self.writer.validate_params():
|
273
|
+
print("Warning: Control parameters validation failed, but proceeding anyway.")
|
274
|
+
|
275
|
+
self.writer.write()
|
276
|
+
return True
|
277
|
+
|
278
|
+
except Exception as e:
|
279
|
+
print(f"Error building control: {e}")
|
280
|
+
return False
|
281
|
+
|
282
|
+
def validate(self) -> bool:
|
283
|
+
"""Validate the control configuration."""
|
284
|
+
if not self.is_configured:
|
285
|
+
return False
|
286
|
+
|
287
|
+
required_params = ['application', 'startFrom', 'stopAt']
|
288
|
+
for param in required_params:
|
289
|
+
if param not in self.control_params:
|
290
|
+
print(f"Missing required control parameter: {param}")
|
291
|
+
return False
|
292
|
+
|
293
|
+
return True
|
294
|
+
|
295
|
+
|
296
|
+
class TurbulenceComponent(CaseComponent):
|
297
|
+
"""Component for handling turbulence model configuration."""
|
298
|
+
|
299
|
+
def __init__(self):
|
300
|
+
super().__init__("Turbulence", "Turbulence model and properties configuration")
|
301
|
+
self.simulation_type = ""
|
302
|
+
self.model_properties = {}
|
303
|
+
self.writer = None
|
304
|
+
|
305
|
+
def configure(self, simulation_type: str, model_properties: Dict[str, Any]) -> bool:
|
306
|
+
"""
|
307
|
+
Configure the turbulence component.
|
308
|
+
|
309
|
+
Args:
|
310
|
+
simulation_type (str): Type of turbulence simulation.
|
311
|
+
model_properties (Dict): Properties for the turbulence model.
|
312
|
+
|
313
|
+
Returns:
|
314
|
+
bool: True if configuration successful, False otherwise.
|
315
|
+
"""
|
316
|
+
try:
|
317
|
+
self.simulation_type = simulation_type
|
318
|
+
self.model_properties = model_properties.copy()
|
319
|
+
self.is_configured = True
|
320
|
+
return True
|
321
|
+
|
322
|
+
except Exception as e:
|
323
|
+
print(f"Error configuring turbulence: {e}")
|
324
|
+
return False
|
325
|
+
|
326
|
+
def build(self, case_dir: str) -> bool:
|
327
|
+
"""Build the turbulenceProperties file."""
|
328
|
+
if not self.is_configured:
|
329
|
+
print("Error: Turbulence not configured")
|
330
|
+
return False
|
331
|
+
|
332
|
+
try:
|
333
|
+
constant_dir = os.path.join(case_dir, "constant")
|
334
|
+
os.makedirs(constant_dir, exist_ok=True)
|
335
|
+
|
336
|
+
tp_path = os.path.join(constant_dir, "turbulenceProperties")
|
337
|
+
self.writer = TurbulencePropertiesWriter(
|
338
|
+
file_path=tp_path,
|
339
|
+
simulation_type=self.simulation_type,
|
340
|
+
model_properties=self.model_properties
|
341
|
+
)
|
342
|
+
|
343
|
+
# Validate configuration before writing
|
344
|
+
if not self.writer.validate_simulation_type():
|
345
|
+
print("Warning: Simulation type validation failed, but proceeding anyway.")
|
346
|
+
|
347
|
+
if not self.writer.validate_model_properties():
|
348
|
+
print("Warning: Model properties validation failed, but proceeding anyway.")
|
349
|
+
|
350
|
+
self.writer.write()
|
351
|
+
return True
|
352
|
+
|
353
|
+
except Exception as e:
|
354
|
+
print(f"Error building turbulence: {e}")
|
355
|
+
return False
|
356
|
+
|
357
|
+
def validate(self) -> bool:
|
358
|
+
"""Validate the turbulence configuration."""
|
359
|
+
if not self.is_configured:
|
360
|
+
return False
|
361
|
+
|
362
|
+
valid_types = ['RAS', 'LES', 'laminar']
|
363
|
+
if self.simulation_type not in valid_types:
|
364
|
+
print(f"Invalid simulation type: {self.simulation_type}")
|
365
|
+
return False
|
366
|
+
|
367
|
+
return True
|
368
|
+
|
369
|
+
|
370
|
+
class DynamicMeshComponent(CaseComponent):
|
371
|
+
"""Component for handling dynamic mesh configuration."""
|
372
|
+
|
373
|
+
def __init__(self):
|
374
|
+
super().__init__("DynamicMesh", "Dynamic mesh configuration")
|
375
|
+
self.is_required = False # Optional component
|
376
|
+
self.write_dynamic_mesh_dict = False
|
377
|
+
self.mesh_type = ""
|
378
|
+
self.mesh_properties = {}
|
379
|
+
self.writer = None
|
380
|
+
|
381
|
+
def configure(self, write_dynamic_mesh_dict: bool, mesh_type: str,
|
382
|
+
mesh_properties: Dict[str, Any]) -> bool:
|
383
|
+
"""
|
384
|
+
Configure the dynamic mesh component.
|
385
|
+
|
386
|
+
Args:
|
387
|
+
write_dynamic_mesh_dict (bool): Whether to write dynamicMeshDict.
|
388
|
+
mesh_type (str): Type of dynamic mesh.
|
389
|
+
mesh_properties (Dict): Properties for the dynamic mesh.
|
390
|
+
|
391
|
+
Returns:
|
392
|
+
bool: True if configuration successful, False otherwise.
|
393
|
+
"""
|
394
|
+
try:
|
395
|
+
self.write_dynamic_mesh_dict = write_dynamic_mesh_dict
|
396
|
+
self.mesh_type = mesh_type
|
397
|
+
self.mesh_properties = mesh_properties.copy()
|
398
|
+
self.is_configured = True
|
399
|
+
return True
|
400
|
+
|
401
|
+
except Exception as e:
|
402
|
+
print(f"Error configuring dynamic mesh: {e}")
|
403
|
+
return False
|
404
|
+
|
405
|
+
def build(self, case_dir: str) -> bool:
|
406
|
+
"""Build the dynamicMeshDict file (if enabled)."""
|
407
|
+
if not self.is_configured:
|
408
|
+
print("Error: Dynamic mesh not configured")
|
409
|
+
return False
|
410
|
+
|
411
|
+
if not self.write_dynamic_mesh_dict:
|
412
|
+
print("Dynamic mesh dictionary creation skipped (write_dynamic_mesh_dict is False).")
|
413
|
+
return True
|
414
|
+
|
415
|
+
try:
|
416
|
+
constant_dir = os.path.join(case_dir, "constant")
|
417
|
+
os.makedirs(constant_dir, exist_ok=True)
|
418
|
+
|
419
|
+
dmd_path = os.path.join(constant_dir, "dynamicMeshDict")
|
420
|
+
self.writer = DynamicMeshDictWriter(
|
421
|
+
file_path=dmd_path,
|
422
|
+
properties=self.mesh_properties
|
423
|
+
)
|
424
|
+
self.writer.write()
|
425
|
+
return True
|
426
|
+
|
427
|
+
except Exception as e:
|
428
|
+
print(f"Error building dynamic mesh: {e}")
|
429
|
+
return False
|
430
|
+
|
431
|
+
def validate(self) -> bool:
|
432
|
+
"""Validate the dynamic mesh configuration."""
|
433
|
+
if not self.is_configured:
|
434
|
+
return False
|
435
|
+
|
436
|
+
if not self.write_dynamic_mesh_dict:
|
437
|
+
return True # Skip validation if not enabled
|
438
|
+
|
439
|
+
valid_types = ['solidBodyMotion', 'multiBodyOverset', 'adaptiveRefinement', 'morphingMesh']
|
440
|
+
if self.mesh_type not in valid_types:
|
441
|
+
print(f"Invalid mesh type: {self.mesh_type}")
|
442
|
+
return False
|
443
|
+
|
444
|
+
return True
|
445
|
+
|
446
|
+
|
447
|
+
class HFDIBDEMComponent(CaseComponent):
|
448
|
+
"""Component for handling HFDIBDEM configuration."""
|
449
|
+
|
450
|
+
def __init__(self):
|
451
|
+
super().__init__("HFDIBDEM", "Immersed Boundary DEM configuration")
|
452
|
+
self.is_required = False # Optional component
|
453
|
+
self.write_hfdibdem_dict = False
|
454
|
+
self.hfdibdem_properties = {}
|
455
|
+
self.writer = None
|
456
|
+
|
457
|
+
def configure(self, write_hfdibdem_dict: bool, hfdibdem_properties: Dict[str, Any]) -> bool:
|
458
|
+
"""
|
459
|
+
Configure the HFDIBDEM component.
|
460
|
+
|
461
|
+
Args:
|
462
|
+
write_hfdibdem_dict (bool): Whether to write HFDIBDEMDict.
|
463
|
+
hfdibdem_properties (Dict): Properties for the HFDIBDEM configuration.
|
464
|
+
|
465
|
+
Returns:
|
466
|
+
bool: True if configuration successful, False otherwise.
|
467
|
+
"""
|
468
|
+
try:
|
469
|
+
self.write_hfdibdem_dict = write_hfdibdem_dict
|
470
|
+
self.hfdibdem_properties = hfdibdem_properties.copy()
|
471
|
+
self.is_configured = True
|
472
|
+
return True
|
473
|
+
|
474
|
+
except Exception as e:
|
475
|
+
print(f"Error configuring HFDIBDEM: {e}")
|
476
|
+
return False
|
477
|
+
|
478
|
+
def build(self, case_dir: str) -> bool:
|
479
|
+
"""Build the HFDIBDEMDict file (if enabled)."""
|
480
|
+
if not self.is_configured:
|
481
|
+
print("Error: HFDIBDEM not configured")
|
482
|
+
return False
|
483
|
+
|
484
|
+
if not self.write_hfdibdem_dict:
|
485
|
+
print("HFDIBDEM dictionary creation skipped (write_hfdibdem_dict is False).")
|
486
|
+
return True
|
487
|
+
|
488
|
+
try:
|
489
|
+
constant_dir = os.path.join(case_dir, "constant")
|
490
|
+
os.makedirs(constant_dir, exist_ok=True)
|
491
|
+
|
492
|
+
hfdibdem_path = os.path.join(constant_dir, "HFDIBDEMDict")
|
493
|
+
self.writer = HFDIBDEMDictWriter(
|
494
|
+
file_path=hfdibdem_path,
|
495
|
+
properties=self.hfdibdem_properties
|
496
|
+
)
|
497
|
+
self.writer.write()
|
498
|
+
return True
|
499
|
+
|
500
|
+
except Exception as e:
|
501
|
+
print(f"Error building HFDIBDEM: {e}")
|
502
|
+
return False
|
503
|
+
|
504
|
+
def validate(self) -> bool:
|
505
|
+
"""Validate the HFDIBDEM configuration."""
|
506
|
+
if not self.is_configured:
|
507
|
+
return False
|
508
|
+
|
509
|
+
if not self.write_hfdibdem_dict:
|
510
|
+
return True # Skip validation if not enabled
|
511
|
+
|
512
|
+
# Basic validation for HFDIBDEM properties
|
513
|
+
required_keys = ['bodyNames', 'DEM', 'virtualMesh']
|
514
|
+
for key in required_keys:
|
515
|
+
if key not in self.hfdibdem_properties:
|
516
|
+
print(f"Missing required HFDIBDEM parameter: {key}")
|
517
|
+
return False
|
518
|
+
|
519
|
+
return True
|
520
|
+
|
521
|
+
|
522
|
+
class TransportPropertiesComponent(CaseComponent):
|
523
|
+
"""Component for handling transport properties configuration."""
|
524
|
+
|
525
|
+
def __init__(self):
|
526
|
+
super().__init__("TransportProperties", "Fluid transport properties configuration")
|
527
|
+
self.is_required = False # Optional component
|
528
|
+
self.write_transport_properties = False
|
529
|
+
self.transport_model = ""
|
530
|
+
self.model_properties = {}
|
531
|
+
self.thermal_properties = {}
|
532
|
+
self.species_properties = {}
|
533
|
+
self.advanced_properties = {}
|
534
|
+
self.writer = None
|
535
|
+
|
536
|
+
def configure(self, write_transport_properties: bool, transport_model: str,
|
537
|
+
model_properties: Dict[str, Any], thermal_properties: Dict[str, Any] = None,
|
538
|
+
species_properties: Dict[str, Any] = None, advanced_properties: Dict[str, Any] = None) -> bool:
|
539
|
+
"""Configure the transport properties component."""
|
540
|
+
try:
|
541
|
+
self.write_transport_properties = write_transport_properties
|
542
|
+
self.transport_model = transport_model
|
543
|
+
self.model_properties = model_properties.copy()
|
544
|
+
self.thermal_properties = thermal_properties or {}
|
545
|
+
self.species_properties = species_properties or {}
|
546
|
+
self.advanced_properties = advanced_properties or {}
|
547
|
+
self.is_configured = True
|
548
|
+
return True
|
549
|
+
except Exception as e:
|
550
|
+
print(f"Error configuring transport properties: {e}")
|
551
|
+
return False
|
552
|
+
|
553
|
+
def build(self, case_dir: str) -> bool:
|
554
|
+
"""Build the transportProperties file (if enabled)."""
|
555
|
+
if not self.is_configured or not self.write_transport_properties:
|
556
|
+
return True
|
557
|
+
|
558
|
+
try:
|
559
|
+
constant_dir = os.path.join(case_dir, "constant")
|
560
|
+
os.makedirs(constant_dir, exist_ok=True)
|
561
|
+
|
562
|
+
tp_path = os.path.join(constant_dir, "transportProperties")
|
563
|
+
self.writer = TransportPropertiesWriter(
|
564
|
+
file_path=tp_path,
|
565
|
+
transport_model=self.transport_model,
|
566
|
+
model_properties=self.model_properties,
|
567
|
+
thermal_properties=self.thermal_properties,
|
568
|
+
species_properties=self.species_properties,
|
569
|
+
advanced_properties=self.advanced_properties
|
570
|
+
)
|
571
|
+
self.writer.write()
|
572
|
+
return True
|
573
|
+
except Exception as e:
|
574
|
+
print(f"Error building transport properties: {e}")
|
575
|
+
return False
|
576
|
+
|
577
|
+
def validate(self) -> bool:
|
578
|
+
"""Validate the transport properties configuration."""
|
579
|
+
if not self.is_configured or not self.write_transport_properties:
|
580
|
+
return True
|
581
|
+
valid_models = ['Newtonian', 'NonNewtonian', 'BirdCarreau', 'CrossPowerLaw',
|
582
|
+
'HerschelBulkley', 'PowerLaw', 'Casson', 'GeneralizedNewtonian']
|
583
|
+
if self.transport_model not in valid_models:
|
584
|
+
print(f"Invalid transport model: {self.transport_model}")
|
585
|
+
return False
|
586
|
+
return True
|
587
|
+
|
588
|
+
|
589
|
+
class FvSchemesComponent(CaseComponent):
|
590
|
+
"""Component for handling finite volume schemes configuration."""
|
591
|
+
|
592
|
+
def __init__(self):
|
593
|
+
super().__init__("FvSchemes", "Finite volume discretization schemes configuration")
|
594
|
+
self.is_required = False
|
595
|
+
self.write_fv_schemes = False
|
596
|
+
self.ddt_schemes = {}
|
597
|
+
self.grad_schemes = {}
|
598
|
+
self.div_schemes = {}
|
599
|
+
self.laplacian_schemes = {}
|
600
|
+
self.interpolation_schemes = {}
|
601
|
+
self.sn_grad_schemes = {}
|
602
|
+
self.flux_required = {}
|
603
|
+
self.writer = None
|
604
|
+
|
605
|
+
def configure(self, write_fv_schemes: bool, ddt_schemes: Dict[str, Any],
|
606
|
+
grad_schemes: Dict[str, Any], div_schemes: Dict[str, Any],
|
607
|
+
laplacian_schemes: Dict[str, Any], interpolation_schemes: Dict[str, Any],
|
608
|
+
sn_grad_schemes: Dict[str, Any], flux_required: Dict[str, Any] = None) -> bool:
|
609
|
+
"""Configure the fvSchemes component."""
|
610
|
+
try:
|
611
|
+
self.write_fv_schemes = write_fv_schemes
|
612
|
+
self.ddt_schemes = ddt_schemes.copy()
|
613
|
+
self.grad_schemes = grad_schemes.copy()
|
614
|
+
self.div_schemes = div_schemes.copy()
|
615
|
+
self.laplacian_schemes = laplacian_schemes.copy()
|
616
|
+
self.interpolation_schemes = interpolation_schemes.copy()
|
617
|
+
self.sn_grad_schemes = sn_grad_schemes.copy()
|
618
|
+
self.flux_required = flux_required or {}
|
619
|
+
self.is_configured = True
|
620
|
+
return True
|
621
|
+
except Exception as e:
|
622
|
+
print(f"Error configuring fvSchemes: {e}")
|
623
|
+
return False
|
624
|
+
|
625
|
+
def build(self, case_dir: str) -> bool:
|
626
|
+
"""Build the fvSchemes file (if enabled)."""
|
627
|
+
if not self.is_configured or not self.write_fv_schemes:
|
628
|
+
return True
|
629
|
+
|
630
|
+
try:
|
631
|
+
system_dir = os.path.join(case_dir, "system")
|
632
|
+
os.makedirs(system_dir, exist_ok=True)
|
633
|
+
|
634
|
+
fvs_path = os.path.join(system_dir, "fvSchemes")
|
635
|
+
self.writer = FvSchemesWriter(
|
636
|
+
file_path=fvs_path,
|
637
|
+
ddt_schemes=self.ddt_schemes,
|
638
|
+
grad_schemes=self.grad_schemes,
|
639
|
+
div_schemes=self.div_schemes,
|
640
|
+
laplacian_schemes=self.laplacian_schemes,
|
641
|
+
interpolation_schemes=self.interpolation_schemes,
|
642
|
+
sn_grad_schemes=self.sn_grad_schemes,
|
643
|
+
flux_required=self.flux_required
|
644
|
+
)
|
645
|
+
self.writer.write()
|
646
|
+
return True
|
647
|
+
except Exception as e:
|
648
|
+
print(f"Error building fvSchemes: {e}")
|
649
|
+
return False
|
650
|
+
|
651
|
+
def validate(self) -> bool:
|
652
|
+
"""Validate the fvSchemes configuration."""
|
653
|
+
return True
|
654
|
+
|
655
|
+
|
656
|
+
class FvOptionsComponent(CaseComponent):
|
657
|
+
"""Component for handling finite volume options configuration."""
|
658
|
+
|
659
|
+
def __init__(self):
|
660
|
+
super().__init__("FvOptions", "Finite volume source terms and constraints configuration")
|
661
|
+
self.is_required = False
|
662
|
+
self.write_fv_options = False
|
663
|
+
self.momentum_sources = {}
|
664
|
+
self.thermal_sources = {}
|
665
|
+
self.species_sources = {}
|
666
|
+
self.turbulence_sources = {}
|
667
|
+
self.pressure_sources = {}
|
668
|
+
self.volume_fraction_sources = {}
|
669
|
+
self.advanced_sources = {}
|
670
|
+
self.writer = None
|
671
|
+
|
672
|
+
def configure(self, write_fv_options: bool, momentum_sources: Dict[str, Any] = None,
|
673
|
+
thermal_sources: Dict[str, Any] = None, species_sources: Dict[str, Any] = None,
|
674
|
+
turbulence_sources: Dict[str, Any] = None, pressure_sources: Dict[str, Any] = None,
|
675
|
+
volume_fraction_sources: Dict[str, Any] = None, advanced_sources: Dict[str, Any] = None) -> bool:
|
676
|
+
"""Configure the fvOptions component."""
|
677
|
+
try:
|
678
|
+
self.write_fv_options = write_fv_options
|
679
|
+
self.momentum_sources = momentum_sources or {}
|
680
|
+
self.thermal_sources = thermal_sources or {}
|
681
|
+
self.species_sources = species_sources or {}
|
682
|
+
self.turbulence_sources = turbulence_sources or {}
|
683
|
+
self.pressure_sources = pressure_sources or {}
|
684
|
+
self.volume_fraction_sources = volume_fraction_sources or {}
|
685
|
+
self.advanced_sources = advanced_sources or {}
|
686
|
+
self.is_configured = True
|
687
|
+
return True
|
688
|
+
except Exception as e:
|
689
|
+
print(f"Error configuring fvOptions: {e}")
|
690
|
+
return False
|
691
|
+
|
692
|
+
def build(self, case_dir: str) -> bool:
|
693
|
+
"""Build the fvOptions file (if enabled)."""
|
694
|
+
if not self.is_configured or not self.write_fv_options:
|
695
|
+
return True
|
696
|
+
|
697
|
+
try:
|
698
|
+
system_dir = os.path.join(case_dir, "system")
|
699
|
+
os.makedirs(system_dir, exist_ok=True)
|
700
|
+
|
701
|
+
fvo_path = os.path.join(system_dir, "fvOptions")
|
702
|
+
self.writer = FvOptionsWriter(
|
703
|
+
file_path=fvo_path,
|
704
|
+
momentum_sources=self.momentum_sources,
|
705
|
+
thermal_sources=self.thermal_sources,
|
706
|
+
species_sources=self.species_sources,
|
707
|
+
turbulence_sources=self.turbulence_sources,
|
708
|
+
pressure_sources=self.pressure_sources,
|
709
|
+
volume_fraction_sources=self.volume_fraction_sources,
|
710
|
+
advanced_sources=self.advanced_sources
|
711
|
+
)
|
712
|
+
self.writer.write()
|
713
|
+
return True
|
714
|
+
except Exception as e:
|
715
|
+
print(f"Error building fvOptions: {e}")
|
716
|
+
return False
|
717
|
+
|
718
|
+
def validate(self) -> bool:
|
719
|
+
"""Validate the fvOptions configuration."""
|
720
|
+
return True
|
721
|
+
|
722
|
+
|
723
|
+
class GravityFieldComponent(CaseComponent):
|
724
|
+
"""Component for handling gravity field configuration."""
|
725
|
+
|
726
|
+
def __init__(self):
|
727
|
+
super().__init__("GravityField", "Gravity field configuration")
|
728
|
+
self.is_required = False
|
729
|
+
self.write_gravity_field = False
|
730
|
+
self.gravity_value = (0, 0, -9.81)
|
731
|
+
self.dimensions = [0, 1, -2, 0, 0, 0, 0]
|
732
|
+
self.writer = None
|
733
|
+
|
734
|
+
def configure(self, write_gravity_field: bool, gravity_value: tuple,
|
735
|
+
dimensions: list = None) -> bool:
|
736
|
+
"""Configure the gravity field component."""
|
737
|
+
try:
|
738
|
+
self.write_gravity_field = write_gravity_field
|
739
|
+
self.gravity_value = gravity_value
|
740
|
+
self.dimensions = dimensions or [0, 1, -2, 0, 0, 0, 0]
|
741
|
+
self.is_configured = True
|
742
|
+
return True
|
743
|
+
except Exception as e:
|
744
|
+
print(f"Error configuring gravity field: {e}")
|
745
|
+
return False
|
746
|
+
|
747
|
+
def build(self, case_dir: str) -> bool:
|
748
|
+
"""Build the gravity field file (if enabled)."""
|
749
|
+
if not self.is_configured or not self.write_gravity_field:
|
750
|
+
return True
|
751
|
+
|
752
|
+
try:
|
753
|
+
constant_dir = os.path.join(case_dir, "constant")
|
754
|
+
os.makedirs(constant_dir, exist_ok=True)
|
755
|
+
|
756
|
+
g_path = os.path.join(constant_dir, "g")
|
757
|
+
self.writer = GravityFieldWriter(
|
758
|
+
file_path=g_path,
|
759
|
+
gravity_value=self.gravity_value,
|
760
|
+
dimensions=self.dimensions
|
761
|
+
)
|
762
|
+
self.writer.write()
|
763
|
+
return True
|
764
|
+
except Exception as e:
|
765
|
+
print(f"Error building gravity field: {e}")
|
766
|
+
return False
|
767
|
+
|
768
|
+
def validate(self) -> bool:
|
769
|
+
"""Validate the gravity field configuration."""
|
770
|
+
if not self.is_configured or not self.write_gravity_field:
|
771
|
+
return True
|
772
|
+
if not isinstance(self.gravity_value, (tuple, list)) or len(self.gravity_value) != 3:
|
773
|
+
print("Gravity value must be a tuple/list with 3 components")
|
774
|
+
return False
|
775
|
+
return True
|
776
|
+
|
777
|
+
|
778
|
+
class SetFieldsComponent(CaseComponent):
|
779
|
+
"""Component for handling setFields configuration."""
|
780
|
+
|
781
|
+
def __init__(self):
|
782
|
+
super().__init__("SetFields", "Field initialization configuration")
|
783
|
+
self.is_required = False
|
784
|
+
self.write_set_fields_dict = False
|
785
|
+
self.default_field_values = {}
|
786
|
+
self.regions = {}
|
787
|
+
self.writer = None
|
788
|
+
|
789
|
+
def configure(self, write_set_fields_dict: bool, default_field_values: Dict[str, Any] = None,
|
790
|
+
regions: Dict[str, Any] = None) -> bool:
|
791
|
+
"""Configure the setFields component."""
|
792
|
+
try:
|
793
|
+
self.write_set_fields_dict = write_set_fields_dict
|
794
|
+
self.default_field_values = default_field_values or {}
|
795
|
+
self.regions = regions or {}
|
796
|
+
self.is_configured = True
|
797
|
+
return True
|
798
|
+
except Exception as e:
|
799
|
+
print(f"Error configuring setFields: {e}")
|
800
|
+
return False
|
801
|
+
|
802
|
+
def build(self, case_dir: str) -> bool:
|
803
|
+
"""Build the setFieldsDict file (if enabled)."""
|
804
|
+
if not self.is_configured or not self.write_set_fields_dict:
|
805
|
+
return True
|
806
|
+
|
807
|
+
try:
|
808
|
+
system_dir = os.path.join(case_dir, "system")
|
809
|
+
os.makedirs(system_dir, exist_ok=True)
|
810
|
+
|
811
|
+
sf_path = os.path.join(system_dir, "setFieldsDict")
|
812
|
+
self.writer = SetFieldsWriter(
|
813
|
+
file_path=sf_path,
|
814
|
+
default_field_values=self.default_field_values,
|
815
|
+
regions=self.regions
|
816
|
+
)
|
817
|
+
self.writer.write()
|
818
|
+
return True
|
819
|
+
except Exception as e:
|
820
|
+
print(f"Error building setFields: {e}")
|
821
|
+
return False
|
822
|
+
|
823
|
+
def validate(self) -> bool:
|
824
|
+
"""Validate the setFields configuration."""
|
825
|
+
return True
|
826
|
+
|
827
|
+
|
828
|
+
class DecomposeParComponent(CaseComponent):
|
829
|
+
"""Component for handling decomposePar configuration."""
|
830
|
+
|
831
|
+
def __init__(self):
|
832
|
+
super().__init__("DecomposePar", "Parallel decomposition configuration")
|
833
|
+
self.is_required = False
|
834
|
+
self.write_decompose_par_dict = False
|
835
|
+
self.number_of_subdomains = 1
|
836
|
+
self.method = "scotch"
|
837
|
+
self.coeffs = {}
|
838
|
+
self.options = {}
|
839
|
+
self.fields = []
|
840
|
+
self.preserve_patches = []
|
841
|
+
self.preserve_cell_zones = []
|
842
|
+
self.preserve_face_zones = []
|
843
|
+
self.preserve_point_zones = []
|
844
|
+
self.writer = None
|
845
|
+
|
846
|
+
def configure(self, write_decompose_par_dict: bool, number_of_subdomains: int,
|
847
|
+
method: str, coeffs: Dict[str, Any] = None, options: Dict[str, Any] = None,
|
848
|
+
fields: list = None, preserve_patches: list = None, preserve_cell_zones: list = None,
|
849
|
+
preserve_face_zones: list = None, preserve_point_zones: list = None) -> bool:
|
850
|
+
"""Configure the decomposePar component."""
|
851
|
+
try:
|
852
|
+
self.write_decompose_par_dict = write_decompose_par_dict
|
853
|
+
self.number_of_subdomains = number_of_subdomains
|
854
|
+
self.method = method
|
855
|
+
self.coeffs = coeffs or {}
|
856
|
+
self.options = options or {}
|
857
|
+
self.fields = fields or []
|
858
|
+
self.preserve_patches = preserve_patches or []
|
859
|
+
self.preserve_cell_zones = preserve_cell_zones or []
|
860
|
+
self.preserve_face_zones = preserve_face_zones or []
|
861
|
+
self.preserve_point_zones = preserve_point_zones or []
|
862
|
+
self.is_configured = True
|
863
|
+
return True
|
864
|
+
except Exception as e:
|
865
|
+
print(f"Error configuring decomposePar: {e}")
|
866
|
+
return False
|
867
|
+
|
868
|
+
def build(self, case_dir: str) -> bool:
|
869
|
+
"""Build the decomposeParDict file (if enabled)."""
|
870
|
+
if not self.is_configured or not self.write_decompose_par_dict:
|
871
|
+
return True
|
872
|
+
|
873
|
+
try:
|
874
|
+
system_dir = os.path.join(case_dir, "system")
|
875
|
+
os.makedirs(system_dir, exist_ok=True)
|
876
|
+
|
877
|
+
dp_path = os.path.join(system_dir, "decomposeParDict")
|
878
|
+
self.writer = DecomposeParWriter(
|
879
|
+
file_path=dp_path,
|
880
|
+
number_of_subdomains=self.number_of_subdomains,
|
881
|
+
method=self.method,
|
882
|
+
coeffs=self.coeffs,
|
883
|
+
options=self.options,
|
884
|
+
fields=self.fields,
|
885
|
+
preserve_patches=self.preserve_patches,
|
886
|
+
preserve_cell_zones=self.preserve_cell_zones,
|
887
|
+
preserve_face_zones=self.preserve_face_zones,
|
888
|
+
preserve_point_zones=self.preserve_point_zones
|
889
|
+
)
|
890
|
+
self.writer.write()
|
891
|
+
return True
|
892
|
+
except Exception as e:
|
893
|
+
print(f"Error building decomposePar: {e}")
|
894
|
+
return False
|
895
|
+
|
896
|
+
def validate(self) -> bool:
|
897
|
+
"""Validate the decomposePar configuration."""
|
898
|
+
if not self.is_configured or not self.write_decompose_par_dict:
|
899
|
+
return True
|
900
|
+
valid_methods = ['simple', 'hierarchical', 'scotch', 'metis', 'manual',
|
901
|
+
'multiLevel', 'structured', 'kahip', 'ptscotch']
|
902
|
+
if self.method not in valid_methods:
|
903
|
+
print(f"Invalid decomposition method: {self.method}")
|
904
|
+
return False
|
905
|
+
if not isinstance(self.number_of_subdomains, int) or self.number_of_subdomains < 1:
|
906
|
+
print("numberOfSubdomains must be a positive integer")
|
907
|
+
return False
|
908
|
+
return True
|
909
|
+
|
910
|
+
|
911
|
+
class SnappyHexMeshComponent(CaseComponent):
|
912
|
+
"""Component for handling snappyHexMesh configuration."""
|
913
|
+
|
914
|
+
def __init__(self):
|
915
|
+
super().__init__("SnappyHexMesh", "Complex geometry mesh generation configuration")
|
916
|
+
self.is_required = False
|
917
|
+
self.write_snappy_hex_mesh_dict = False
|
918
|
+
self.geometry = {}
|
919
|
+
self.castellated_mesh_controls = {}
|
920
|
+
self.snap_controls = {}
|
921
|
+
self.add_layers_controls = {}
|
922
|
+
self.mesh_quality_controls = {}
|
923
|
+
self.merged_patches = []
|
924
|
+
self.write_flags = {}
|
925
|
+
self.writer = None
|
926
|
+
|
927
|
+
def configure(self, write_snappy_hex_mesh_dict: bool, geometry: Dict[str, Any],
|
928
|
+
castellated_mesh_controls: Dict[str, Any], snap_controls: Dict[str, Any],
|
929
|
+
add_layers_controls: Dict[str, Any] = None, mesh_quality_controls: Dict[str, Any] = None,
|
930
|
+
merged_patches: list = None, write_flags: Dict[str, Any] = None) -> bool:
|
931
|
+
"""Configure the snappyHexMesh component."""
|
932
|
+
try:
|
933
|
+
self.write_snappy_hex_mesh_dict = write_snappy_hex_mesh_dict
|
934
|
+
self.geometry = geometry.copy()
|
935
|
+
self.castellated_mesh_controls = castellated_mesh_controls.copy()
|
936
|
+
self.snap_controls = snap_controls.copy()
|
937
|
+
self.add_layers_controls = add_layers_controls or {}
|
938
|
+
self.mesh_quality_controls = mesh_quality_controls or {}
|
939
|
+
self.merged_patches = merged_patches or []
|
940
|
+
self.write_flags = write_flags or {}
|
941
|
+
self.is_configured = True
|
942
|
+
return True
|
943
|
+
except Exception as e:
|
944
|
+
print(f"Error configuring snappyHexMesh: {e}")
|
945
|
+
return False
|
946
|
+
|
947
|
+
def build(self, case_dir: str) -> bool:
|
948
|
+
"""Build the snappyHexMeshDict file (if enabled)."""
|
949
|
+
if not self.is_configured or not self.write_snappy_hex_mesh_dict:
|
950
|
+
return True
|
951
|
+
|
952
|
+
try:
|
953
|
+
system_dir = os.path.join(case_dir, "system")
|
954
|
+
os.makedirs(system_dir, exist_ok=True)
|
955
|
+
|
956
|
+
shm_path = os.path.join(system_dir, "snappyHexMeshDict")
|
957
|
+
self.writer = SnappyHexMeshWriter(
|
958
|
+
file_path=shm_path,
|
959
|
+
geometry=self.geometry,
|
960
|
+
castellated_mesh_controls=self.castellated_mesh_controls,
|
961
|
+
snap_controls=self.snap_controls,
|
962
|
+
add_layers_controls=self.add_layers_controls,
|
963
|
+
mesh_quality_controls=self.mesh_quality_controls,
|
964
|
+
merged_patches=self.merged_patches,
|
965
|
+
write_flags=self.write_flags
|
966
|
+
)
|
967
|
+
self.writer.write()
|
968
|
+
return True
|
969
|
+
except Exception as e:
|
970
|
+
print(f"Error building snappyHexMesh: {e}")
|
971
|
+
return False
|
972
|
+
|
973
|
+
def validate(self) -> bool:
|
974
|
+
"""Validate the snappyHexMesh configuration."""
|
975
|
+
if not self.is_configured or not self.write_snappy_hex_mesh_dict:
|
976
|
+
return True
|
977
|
+
if not self.geometry:
|
978
|
+
print("Geometry dictionary is required for snappyHexMesh")
|
979
|
+
return False
|
980
|
+
if not self.castellated_mesh_controls:
|
981
|
+
print("Castellated mesh controls are required for snappyHexMesh")
|
982
|
+
return False
|
983
|
+
if not self.snap_controls:
|
984
|
+
print("Snap controls are required for snappyHexMesh")
|
985
|
+
return False
|
986
|
+
return True
|
987
|
+
|
988
|
+
|
989
|
+
class PFieldComponent(CaseComponent):
|
990
|
+
"""Component for handling pressure field (p) configuration."""
|
991
|
+
|
992
|
+
def __init__(self):
|
993
|
+
super().__init__("PField", "Pressure field initialization")
|
994
|
+
self.is_required = False
|
995
|
+
self.write_p_field = False
|
996
|
+
self.internal_pressure = 0.0
|
997
|
+
self.boundary_conditions = {}
|
998
|
+
self.ref_pressure_cell = 0
|
999
|
+
self.ref_pressure_value = 0.0
|
1000
|
+
self.pressure_dimensions = [0, 2, -2, 0, 0, 0, 0]
|
1001
|
+
self.writer = None
|
1002
|
+
|
1003
|
+
def configure(self, write_p_field: bool, internal_pressure: float,
|
1004
|
+
boundary_conditions: Dict[str, Any], ref_pressure_cell: int = 0,
|
1005
|
+
ref_pressure_value: float = 0.0, pressure_dimensions: list = None) -> bool:
|
1006
|
+
"""Configure the pressure field component."""
|
1007
|
+
try:
|
1008
|
+
self.write_p_field = write_p_field
|
1009
|
+
self.internal_pressure = internal_pressure
|
1010
|
+
self.boundary_conditions = boundary_conditions.copy()
|
1011
|
+
self.ref_pressure_cell = ref_pressure_cell
|
1012
|
+
self.ref_pressure_value = ref_pressure_value
|
1013
|
+
if pressure_dimensions:
|
1014
|
+
self.pressure_dimensions = pressure_dimensions
|
1015
|
+
self.is_configured = True
|
1016
|
+
return True
|
1017
|
+
except Exception as e:
|
1018
|
+
print(f"Error configuring pressure field: {e}")
|
1019
|
+
return False
|
1020
|
+
|
1021
|
+
def build(self, case_dir: str) -> bool:
|
1022
|
+
"""Build the pressure field file (if enabled)."""
|
1023
|
+
if not self.is_configured or not self.write_p_field:
|
1024
|
+
return True
|
1025
|
+
|
1026
|
+
try:
|
1027
|
+
zero_dir = os.path.join(case_dir, "0")
|
1028
|
+
os.makedirs(zero_dir, exist_ok=True)
|
1029
|
+
|
1030
|
+
p_path = os.path.join(zero_dir, "p")
|
1031
|
+
self.writer = PFieldWriter(
|
1032
|
+
file_path=p_path,
|
1033
|
+
internal_pressure=self.internal_pressure,
|
1034
|
+
boundary_conditions=self.boundary_conditions,
|
1035
|
+
ref_pressure_cell=self.ref_pressure_cell,
|
1036
|
+
ref_pressure_value=self.ref_pressure_value,
|
1037
|
+
pressure_dimensions=self.pressure_dimensions
|
1038
|
+
)
|
1039
|
+
self.writer.write()
|
1040
|
+
return True
|
1041
|
+
except Exception as e:
|
1042
|
+
print(f"Error building pressure field: {e}")
|
1043
|
+
return False
|
1044
|
+
|
1045
|
+
def validate(self) -> bool:
|
1046
|
+
"""Validate the pressure field configuration."""
|
1047
|
+
if not self.is_configured or not self.write_p_field:
|
1048
|
+
return True
|
1049
|
+
return True
|
1050
|
+
|
1051
|
+
|
1052
|
+
class UFieldComponent(CaseComponent):
|
1053
|
+
"""Component for handling velocity field (U) configuration."""
|
1054
|
+
|
1055
|
+
def __init__(self):
|
1056
|
+
super().__init__("UField", "Velocity field initialization")
|
1057
|
+
self.is_required = False
|
1058
|
+
self.write_u_field = False
|
1059
|
+
self.internal_velocity = (0.0, 0.0, 0.0)
|
1060
|
+
self.boundary_conditions = {}
|
1061
|
+
self.velocity_dimensions = [0, 1, -1, 0, 0, 0, 0]
|
1062
|
+
self.writer = None
|
1063
|
+
|
1064
|
+
def configure(self, write_u_field: bool, internal_velocity: tuple,
|
1065
|
+
boundary_conditions: Dict[str, Any], velocity_dimensions: list = None) -> bool:
|
1066
|
+
"""Configure the velocity field component."""
|
1067
|
+
try:
|
1068
|
+
self.write_u_field = write_u_field
|
1069
|
+
self.internal_velocity = internal_velocity
|
1070
|
+
self.boundary_conditions = boundary_conditions.copy()
|
1071
|
+
if velocity_dimensions:
|
1072
|
+
self.velocity_dimensions = velocity_dimensions
|
1073
|
+
self.is_configured = True
|
1074
|
+
return True
|
1075
|
+
except Exception as e:
|
1076
|
+
print(f"Error configuring velocity field: {e}")
|
1077
|
+
return False
|
1078
|
+
|
1079
|
+
def build(self, case_dir: str) -> bool:
|
1080
|
+
"""Build the velocity field file (if enabled)."""
|
1081
|
+
if not self.is_configured or not self.write_u_field:
|
1082
|
+
return True
|
1083
|
+
|
1084
|
+
try:
|
1085
|
+
zero_dir = os.path.join(case_dir, "0")
|
1086
|
+
os.makedirs(zero_dir, exist_ok=True)
|
1087
|
+
|
1088
|
+
u_path = os.path.join(zero_dir, "U")
|
1089
|
+
self.writer = UFieldWriter(
|
1090
|
+
file_path=u_path,
|
1091
|
+
internal_velocity=self.internal_velocity,
|
1092
|
+
boundary_conditions=self.boundary_conditions,
|
1093
|
+
velocity_dimensions=self.velocity_dimensions
|
1094
|
+
)
|
1095
|
+
self.writer.write()
|
1096
|
+
return True
|
1097
|
+
except Exception as e:
|
1098
|
+
print(f"Error building velocity field: {e}")
|
1099
|
+
return False
|
1100
|
+
|
1101
|
+
def validate(self) -> bool:
|
1102
|
+
"""Validate the velocity field configuration."""
|
1103
|
+
if not self.is_configured or not self.write_u_field:
|
1104
|
+
return True
|
1105
|
+
return True
|
1106
|
+
|
1107
|
+
|
1108
|
+
class FFieldComponent(CaseComponent):
|
1109
|
+
"""Component for handling force field (f) configuration."""
|
1110
|
+
|
1111
|
+
def __init__(self):
|
1112
|
+
super().__init__("FField", "Force field initialization")
|
1113
|
+
self.is_required = False
|
1114
|
+
self.write_f_field = False
|
1115
|
+
self.internal_force = (0.0, 0.0, 0.0)
|
1116
|
+
self.boundary_conditions = {}
|
1117
|
+
self.force_dimensions = [0, 1, -2, 0, 0, 0, 0]
|
1118
|
+
self.writer = None
|
1119
|
+
|
1120
|
+
def configure(self, write_f_field: bool, internal_force: tuple,
|
1121
|
+
boundary_conditions: Dict[str, Any], force_dimensions: list = None) -> bool:
|
1122
|
+
"""Configure the force field component."""
|
1123
|
+
try:
|
1124
|
+
self.write_f_field = write_f_field
|
1125
|
+
self.internal_force = internal_force
|
1126
|
+
self.boundary_conditions = boundary_conditions.copy()
|
1127
|
+
if force_dimensions:
|
1128
|
+
self.force_dimensions = force_dimensions
|
1129
|
+
self.is_configured = True
|
1130
|
+
return True
|
1131
|
+
except Exception as e:
|
1132
|
+
print(f"Error configuring force field: {e}")
|
1133
|
+
return False
|
1134
|
+
|
1135
|
+
def build(self, case_dir: str) -> bool:
|
1136
|
+
"""Build the force field file (if enabled)."""
|
1137
|
+
if not self.is_configured or not self.write_f_field:
|
1138
|
+
return True
|
1139
|
+
|
1140
|
+
try:
|
1141
|
+
zero_dir = os.path.join(case_dir, "0")
|
1142
|
+
os.makedirs(zero_dir, exist_ok=True)
|
1143
|
+
|
1144
|
+
f_path = os.path.join(zero_dir, "f")
|
1145
|
+
self.writer = FFieldWriter(
|
1146
|
+
file_path=f_path,
|
1147
|
+
internal_force=self.internal_force,
|
1148
|
+
boundary_conditions=self.boundary_conditions,
|
1149
|
+
force_dimensions=self.force_dimensions
|
1150
|
+
)
|
1151
|
+
self.writer.write()
|
1152
|
+
return True
|
1153
|
+
except Exception as e:
|
1154
|
+
print(f"Error building force field: {e}")
|
1155
|
+
return False
|
1156
|
+
|
1157
|
+
def validate(self) -> bool:
|
1158
|
+
"""Validate the force field configuration."""
|
1159
|
+
if not self.is_configured or not self.write_f_field:
|
1160
|
+
return True
|
1161
|
+
return True
|
1162
|
+
|
1163
|
+
|
1164
|
+
class LambdaFieldComponent(CaseComponent):
|
1165
|
+
"""Component for handling lambda field (λ) configuration."""
|
1166
|
+
|
1167
|
+
def __init__(self):
|
1168
|
+
super().__init__("LambdaField", "Lambda field initialization")
|
1169
|
+
self.is_required = False
|
1170
|
+
self.write_lambda_field = False
|
1171
|
+
self.internal_lambda = 0.0
|
1172
|
+
self.boundary_conditions = {}
|
1173
|
+
self.lambda_dimensions = [0, 0, 0, 0, 0, 0, 0]
|
1174
|
+
self.writer = None
|
1175
|
+
|
1176
|
+
def configure(self, write_lambda_field: bool, internal_lambda: float,
|
1177
|
+
boundary_conditions: Dict[str, Any], lambda_dimensions: list = None) -> bool:
|
1178
|
+
"""Configure the lambda field component."""
|
1179
|
+
try:
|
1180
|
+
self.write_lambda_field = write_lambda_field
|
1181
|
+
self.internal_lambda = internal_lambda
|
1182
|
+
self.boundary_conditions = boundary_conditions.copy()
|
1183
|
+
if lambda_dimensions:
|
1184
|
+
self.lambda_dimensions = lambda_dimensions
|
1185
|
+
self.is_configured = True
|
1186
|
+
return True
|
1187
|
+
except Exception as e:
|
1188
|
+
print(f"Error configuring lambda field: {e}")
|
1189
|
+
return False
|
1190
|
+
|
1191
|
+
def build(self, case_dir: str) -> bool:
|
1192
|
+
"""Build the lambda field file (if enabled)."""
|
1193
|
+
if not self.is_configured or not self.write_lambda_field:
|
1194
|
+
return True
|
1195
|
+
|
1196
|
+
try:
|
1197
|
+
zero_dir = os.path.join(case_dir, "0")
|
1198
|
+
os.makedirs(zero_dir, exist_ok=True)
|
1199
|
+
|
1200
|
+
lambda_path = os.path.join(zero_dir, "lambda")
|
1201
|
+
self.writer = LambdaFieldWriter(
|
1202
|
+
file_path=lambda_path,
|
1203
|
+
internal_lambda=self.internal_lambda,
|
1204
|
+
boundary_conditions=self.boundary_conditions,
|
1205
|
+
lambda_dimensions=self.lambda_dimensions
|
1206
|
+
)
|
1207
|
+
self.writer.write()
|
1208
|
+
return True
|
1209
|
+
except Exception as e:
|
1210
|
+
print(f"Error building lambda field: {e}")
|
1211
|
+
return False
|
1212
|
+
|
1213
|
+
def validate(self) -> bool:
|
1214
|
+
"""Validate the lambda field configuration."""
|
1215
|
+
if not self.is_configured or not self.write_lambda_field:
|
1216
|
+
return True
|
1217
|
+
return True
|