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.
Files changed (67) hide show
  1. pycphy/__init__.py +11 -0
  2. pycphy/cli.py +145 -0
  3. pycphy/config_manager.py +373 -0
  4. pycphy/foamCaseDeveloper/__init__.py +60 -41
  5. pycphy/foamCaseDeveloper/config/__init__.py +69 -26
  6. pycphy/foamCaseDeveloper/config/cad_mesh_config.py +62 -0
  7. pycphy/foamCaseDeveloper/config/config_hfdibdem.py +193 -0
  8. pycphy/foamCaseDeveloper/config/constant/__init__.py +23 -0
  9. pycphy/foamCaseDeveloper/config/constant/dynamic_mesh_config.py +208 -0
  10. pycphy/foamCaseDeveloper/config/constant/gravity_field_config.py +379 -0
  11. pycphy/foamCaseDeveloper/config/constant/transport_properties_config.py +225 -0
  12. pycphy/foamCaseDeveloper/config/constant/turbulence_config.py +617 -0
  13. pycphy/foamCaseDeveloper/config/csv_boundary_reader.py +219 -0
  14. pycphy/foamCaseDeveloper/config/system/__init__.py +31 -0
  15. pycphy/foamCaseDeveloper/config/system/block_mesh_config.py +184 -0
  16. pycphy/foamCaseDeveloper/config/{control_config.py → system/control_config.py} +113 -1
  17. pycphy/foamCaseDeveloper/config/system/decompose_par_config.py +525 -0
  18. pycphy/foamCaseDeveloper/config/system/fv_options_config.py +575 -0
  19. pycphy/foamCaseDeveloper/config/system/fv_schemes_config.py +363 -0
  20. pycphy/foamCaseDeveloper/config/system/set_fields_config.py +640 -0
  21. pycphy/foamCaseDeveloper/config/system/snappy_hex_mesh_config.py +241 -0
  22. pycphy/foamCaseDeveloper/config/zero/U_config.py +135 -0
  23. pycphy/foamCaseDeveloper/config/zero/__init__.py +22 -0
  24. pycphy/foamCaseDeveloper/config/zero/f_config.py +140 -0
  25. pycphy/foamCaseDeveloper/config/zero/lambda_config.py +157 -0
  26. pycphy/foamCaseDeveloper/config/zero/p_config.py +97 -0
  27. pycphy/foamCaseDeveloper/core/__init__.py +30 -18
  28. pycphy/foamCaseDeveloper/core/block_mesh_developer.py +1 -1
  29. pycphy/foamCaseDeveloper/core/cad_block_mesh_developer.py +463 -0
  30. pycphy/foamCaseDeveloper/core/case_builder.py +1217 -0
  31. pycphy/foamCaseDeveloper/core/foam_case_manager.py +370 -111
  32. pycphy/foamCaseDeveloper/develop_case.py +640 -0
  33. pycphy/foamCaseDeveloper/main.py +260 -260
  34. pycphy/foamCaseDeveloper/utils/myAutoCAD.py +418 -0
  35. pycphy/foamCaseDeveloper/writers/__init__.py +37 -4
  36. pycphy/foamCaseDeveloper/writers/constant/__init__.py +25 -0
  37. pycphy/foamCaseDeveloper/writers/constant/dynamic_mesh_dict_writer.py +75 -0
  38. pycphy/foamCaseDeveloper/writers/constant/gravity_field_writer.py +88 -0
  39. pycphy/foamCaseDeveloper/writers/constant/hfdibdem_dict_writer.py +81 -0
  40. pycphy/foamCaseDeveloper/writers/constant/transport_properties_writer.py +202 -0
  41. pycphy/foamCaseDeveloper/writers/{turbulence_properties_writer.py → constant/turbulence_properties_writer.py} +49 -1
  42. pycphy/foamCaseDeveloper/writers/system/__init__.py +31 -0
  43. pycphy/foamCaseDeveloper/writers/{block_mesh_writer.py → system/block_mesh_writer.py} +1 -1
  44. pycphy/foamCaseDeveloper/writers/{control_dict_writer.py → system/control_dict_writer.py} +37 -1
  45. pycphy/foamCaseDeveloper/writers/system/decompose_par_writer.py +228 -0
  46. pycphy/foamCaseDeveloper/writers/system/fv_options_writer.py +188 -0
  47. pycphy/foamCaseDeveloper/writers/system/fv_schemes_writer.py +155 -0
  48. pycphy/foamCaseDeveloper/writers/system/set_fields_writer.py +191 -0
  49. pycphy/foamCaseDeveloper/writers/system/snappy_hex_mesh_writer.py +123 -0
  50. pycphy/foamCaseDeveloper/writers/zero/__init__.py +24 -0
  51. pycphy/foamCaseDeveloper/writers/zero/f_field_writer.py +89 -0
  52. pycphy/foamCaseDeveloper/writers/zero/lambda_field_writer.py +84 -0
  53. pycphy/foamCaseDeveloper/writers/zero/p_field_writer.py +89 -0
  54. pycphy/foamCaseDeveloper/writers/zero/u_field_writer.py +96 -0
  55. pycphy/foamCaseDeveloper/writers/zero/zero_field_factory.py +388 -0
  56. {pycphy-0.1.0.dist-info → pycphy-0.2.0.dist-info}/METADATA +154 -6
  57. pycphy-0.2.0.dist-info/RECORD +63 -0
  58. pycphy-0.2.0.dist-info/entry_points.txt +3 -0
  59. pycphy/foamCaseDeveloper/config/block_mesh_config.py +0 -90
  60. pycphy/foamCaseDeveloper/config/turbulence_config.py +0 -187
  61. pycphy/foamCaseDeveloper/core/control_dict_writer.py +0 -55
  62. pycphy/foamCaseDeveloper/core/turbulence_properties_writer.py +0 -68
  63. pycphy-0.1.0.dist-info/RECORD +0 -24
  64. pycphy-0.1.0.dist-info/entry_points.txt +0 -2
  65. {pycphy-0.1.0.dist-info → pycphy-0.2.0.dist-info}/WHEEL +0 -0
  66. {pycphy-0.1.0.dist-info → pycphy-0.2.0.dist-info}/licenses/LICENSE +0 -0
  67. {pycphy-0.1.0.dist-info → pycphy-0.2.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,418 @@
1
+ import math
2
+ import pythoncom
3
+ import numpy as np
4
+ from pyautocad import Autocad, APoint
5
+ import win32com.client
6
+ from win32com.client import VARIANT
7
+
8
+ class myAutoCAD:
9
+ def __init__(self, create_if_not_exists=True):
10
+ print("Connecting to AutoCAD...")
11
+ # Use direct win32com for better COM compatibility
12
+ try:
13
+ self.acad = win32com.client.GetActiveObject("AutoCAD.Application")
14
+ self.doc = self.acad.ActiveDocument
15
+ self.model = self.doc.ModelSpace
16
+ print(f"Connected to: {self.doc.Name}")
17
+ except Exception as e:
18
+ print(f"Failed to connect to AutoCAD: {e}")
19
+ # Fallback to pyautocad
20
+ self.acad = Autocad(create_if_not_exists=create_if_not_exists)
21
+ self.model = self.acad.model
22
+ self.doc = self.acad.doc
23
+ print(f"Connected to: {self.doc.Name}")
24
+
25
+ def setup_layers(self, layers):
26
+ for name, color in layers.items():
27
+ try:
28
+ self.doc.Layers.Item(name)
29
+ except Exception:
30
+ layer = self.doc.Layers.Add(name)
31
+ layer.color = color
32
+
33
+ @staticmethod
34
+ def calculate_points_on_circle(center, radius, count, start_angle=0):
35
+ points = []
36
+ angle_step = 2 * math.pi / count
37
+ for i in range(count):
38
+ angle = start_angle + i * angle_step
39
+ x = center[0] + radius * math.cos(angle)
40
+ y = center[1] + radius * math.sin(angle)
41
+ points.append((x, y))
42
+ return points
43
+
44
+ def draw_circle(self, center, radius, color=7, layer=None):
45
+ if layer:
46
+ self.doc.ActiveLayer = self.doc.Layers.Item(layer)
47
+ circle = self.model.AddCircle(APoint(center[0], center[1]), radius)
48
+ circle.color = color
49
+ return circle
50
+
51
+ def draw_text(self, position, text, height=0.1, color=7, layer=None):
52
+ if layer:
53
+ self.doc.ActiveLayer = self.doc.Layers.Item(layer)
54
+ text_obj = self.model.AddText(text, APoint(position[0], position[1]), height)
55
+ text_obj.color = color
56
+ return text_obj
57
+
58
+ def zoom_extents(self):
59
+ self.acad.app.ZoomExtents()
60
+
61
+ def get_solid_vertices(self, solid_obj):
62
+ """
63
+ Extracts and sorts the 8 vertices of a 3DSOLID for blockMesh compatibility.
64
+ Uses XEDGES method as reliable workaround for entities without Coordinates attribute.
65
+ """
66
+ try:
67
+ # Try direct coordinates first (for entities that support it)
68
+ try:
69
+ coords = np.array(solid_obj.Coordinates).reshape(-1, 3)
70
+ return self._sort_vertices_for_blockmesh(coords)
71
+ except:
72
+ pass
73
+
74
+ # Use XEDGES method as fallback
75
+ return self._get_vertices_via_xedges(solid_obj)
76
+
77
+ except Exception as e:
78
+ print(f"Error extracting solid vertices: {e}")
79
+ return None
80
+
81
+ def _sort_vertices_for_blockmesh(self, coords):
82
+ """Sort vertices for OpenFOAM blockMesh compatibility."""
83
+ if len(coords) == 0:
84
+ return np.array([])
85
+
86
+ # If we have exactly 8 vertices, sort them for blockMesh
87
+ if len(coords) == 8:
88
+ ind = np.lexsort((coords[:, 0], coords[:, 1], coords[:, 2]))
89
+ sorted_coords = coords[ind]
90
+ bottom_face = sorted_coords[:4]
91
+ top_face = sorted_coords[4:]
92
+ bottom_ind = np.lexsort((bottom_face[:, 1], bottom_face[:, 0]))
93
+ b_temp = bottom_face[bottom_ind]
94
+ bottom_reordered = np.array([b_temp[0], b_temp[2], b_temp[3], b_temp[1]])
95
+ top_ind = np.lexsort((top_face[:, 1], top_face[:, 0]))
96
+ t_temp = top_face[top_ind]
97
+ top_reordered = np.array([t_temp[0], t_temp[2], t_temp[3], t_temp[1]])
98
+ return np.vstack([bottom_reordered, top_reordered])
99
+ else:
100
+ # For other numbers of vertices (like regions), just return sorted coordinates
101
+ print(f" Warning: Expected 8 vertices for blockMesh, got {len(coords)}")
102
+ ind = np.lexsort((coords[:, 0], coords[:, 1], coords[:, 2]))
103
+ return coords[ind]
104
+
105
+ def _get_vertices_via_xedges(self, original_solid):
106
+ """
107
+ Extracts 3D Solid vertices using XEDGES command on a temporary copy.
108
+ This method is more reliable than explode and works with complex solids.
109
+ The original solid remains completely untouched.
110
+ """
111
+ try:
112
+ print(f" Using XEDGES method...")
113
+
114
+ # 1. Create a copy of the solid
115
+ copied_solid = original_solid.Copy()
116
+ handle = copied_solid.Handle
117
+ print(f" Created temporary copy (Handle: {handle})")
118
+
119
+ # 2. Count entities before XEDGES
120
+ pre_count = self.model.Count
121
+
122
+ # 3. Execute XEDGES command on the copy
123
+ self.doc.SendCommand(f'_XEDGES (handent "{handle}") \n')
124
+
125
+ # 4. Gather new edges created by XEDGES
126
+ edges = []
127
+ for i in range(pre_count, self.model.Count):
128
+ try:
129
+ edge = self.model.Item(i)
130
+ edges.append(edge)
131
+ except:
132
+ pass
133
+
134
+ print(f" XEDGES created {len(edges)} new edges")
135
+
136
+ # 5. Extract vertices from edges and clean up
137
+ vertices_set = set()
138
+ for edge in edges:
139
+ try:
140
+ # Handle different edge types
141
+ if edge.ObjectName == 'AcDbLine':
142
+ # Simple line - has StartPoint and EndPoint
143
+ start_point = tuple(round(c, 8) for c in edge.StartPoint)
144
+ end_point = tuple(round(c, 8) for c in edge.EndPoint)
145
+ vertices_set.add(start_point)
146
+ vertices_set.add(end_point)
147
+ elif edge.ObjectName == 'AcDb3dPolyline':
148
+ # 3D Polyline - extract coordinates
149
+ try:
150
+ coords = np.array(edge.Coordinates).reshape(-1, 3)
151
+ for coord in coords:
152
+ vertex = tuple(round(c, 8) for c in coord)
153
+ vertices_set.add(vertex)
154
+ except:
155
+ # If Coordinates fails, try other methods
156
+ pass
157
+ elif hasattr(edge, 'StartPoint') and hasattr(edge, 'EndPoint'):
158
+ # Other curve types with StartPoint/EndPoint
159
+ start_point = tuple(round(c, 8) for c in edge.StartPoint)
160
+ end_point = tuple(round(c, 8) for c in edge.EndPoint)
161
+ vertices_set.add(start_point)
162
+ vertices_set.add(end_point)
163
+ else:
164
+ print(f" Skipping unsupported edge type: {edge.ObjectName}")
165
+
166
+ except Exception as e:
167
+ print(f" Error processing edge {edge.ObjectName}: {e}")
168
+ finally:
169
+ # Clean up the edge immediately
170
+ try:
171
+ edge.Delete()
172
+ except:
173
+ pass
174
+
175
+ # 6. Clean up the copied solid
176
+ try:
177
+ copied_solid.Delete()
178
+ except:
179
+ pass
180
+
181
+ if not vertices_set:
182
+ print(" No vertices could be extracted")
183
+ return None
184
+
185
+ # Convert to numpy array and sort for blockMesh
186
+ vertex_list = sorted(list(vertices_set))
187
+ coords = np.array(vertex_list)
188
+
189
+ print(f" Extracted {len(vertices_set)} unique vertices")
190
+ return self._sort_vertices_for_blockmesh(coords)
191
+
192
+ except Exception as e:
193
+ print(f" XEDGES method failed: {e}")
194
+ # Try bounding box method as fallback
195
+ return self._get_vertices_via_bounding_box(original_solid)
196
+
197
+ def _get_vertices_via_bounding_box(self, solid_obj):
198
+ """
199
+ Alternative method to get vertices using bounding box.
200
+ This creates a rectangular box approximation of the solid.
201
+ """
202
+ try:
203
+ print(f" Trying bounding box method...")
204
+
205
+ # Get bounding box
206
+ try:
207
+ # Method 1: Direct GetBoundingBox
208
+ min_point, max_point = solid_obj.GetBoundingBox()
209
+ except:
210
+ try:
211
+ # Method 2: Through utility
212
+ util = self.acad.Utility
213
+ min_point, max_point = util.GetBoundingBox(solid_obj)
214
+ except:
215
+ try:
216
+ # Method 3: Manual calculation
217
+ min_point = [float('inf'), float('inf'), float('inf')]
218
+ max_point = [float('-inf'), float('-inf'), float('-inf')]
219
+ # This is a simplified approach - in reality we'd need more sophisticated methods
220
+ raise Exception("Manual bounding box not implemented")
221
+ except:
222
+ raise Exception("Could not get bounding box")
223
+
224
+ # Create 8 vertices of a rectangular box
225
+ x_min, y_min, z_min = min_point
226
+ x_max, y_max, z_max = max_point
227
+
228
+ vertices = np.array([
229
+ [x_min, y_min, z_min], # 0: bottom-left-back
230
+ [x_max, y_min, z_min], # 1: bottom-right-back
231
+ [x_max, y_max, z_min], # 2: bottom-right-front
232
+ [x_min, y_max, z_min], # 3: bottom-left-front
233
+ [x_min, y_min, z_max], # 4: top-left-back
234
+ [x_max, y_min, z_max], # 5: top-right-back
235
+ [x_max, y_max, z_max], # 6: top-right-front
236
+ [x_min, y_max, z_max] # 7: top-left-front
237
+ ])
238
+
239
+ print(f" Created bounding box with vertices at:")
240
+ print(f" Min: ({x_min:.3f}, {y_min:.3f}, {z_min:.3f})")
241
+ print(f" Max: ({x_max:.3f}, {y_max:.3f}, {z_max:.3f})")
242
+
243
+ return self._sort_vertices_for_blockmesh(vertices)
244
+
245
+ except Exception as e:
246
+ print(f" Bounding box method failed: {e}")
247
+ return None
248
+
249
+ def get_region_vertices(self, region_obj):
250
+ """Extracts the vertices of a REGION object."""
251
+ try:
252
+ # Try direct coordinates first
253
+ try:
254
+ return np.array(region_obj.Coordinates).reshape(-1, 3)
255
+ except:
256
+ pass
257
+
258
+ # Try XEDGES method for regions as well
259
+ return self._get_vertices_via_xedges(region_obj)
260
+
261
+ except Exception as e:
262
+ print(f"Error extracting region vertices: {e}")
263
+ return None
264
+
265
+ def are_faces_coincident(self, face1_verts, face2_verts, tol):
266
+ """Checks if two faces (sets of vertices) are the same within a tolerance."""
267
+ if face1_verts.shape != face2_verts.shape:
268
+ return False
269
+ for v1 in face1_verts:
270
+ if not np.any(np.linalg.norm(face2_verts - v1, axis=1) < tol):
271
+ return False
272
+ return True
273
+
274
+ def get_xdata(self, entity, app_name=""):
275
+ """
276
+ Get XData from an AutoCAD entity - SIMPLIFIED VERSION
277
+
278
+ Parameters:
279
+ entity - AutoCAD entity object
280
+ app_name - Application name (e.g., "BLOCKDATA"). Empty string gets all xdata.
281
+
282
+ Returns:
283
+ List of tuples (type_code, value) or None if no xdata found
284
+ """
285
+ try:
286
+ # Simple direct call - much cleaner!
287
+ types, values = entity.GetXData(app_name)
288
+
289
+ # Check if we got data
290
+ if not types or not values:
291
+ return None
292
+
293
+ # Convert to tuples for easier processing
294
+ xdata_list = []
295
+ for i in range(len(types)):
296
+ xdata_list.append((types[i], values[i]))
297
+
298
+ return xdata_list
299
+
300
+ except Exception as e:
301
+ print(f"Error getting XData: {e}")
302
+ return None
303
+
304
+ def set_xdata(self, entity, app_name, data):
305
+ """Sets XData on an AutoCAD entity."""
306
+ try:
307
+ entity.SetXData(app_name, data)
308
+ return True
309
+ except (pythoncom.com_error, TypeError, IndexError):
310
+ return False
311
+
312
+ def get_xdata_type_description(self, type_code):
313
+ """Get human-readable description of XData type code"""
314
+ type_map = {
315
+ 1000: "String",
316
+ 1001: "Registered App Name",
317
+ 1002: "Control String",
318
+ 1003: "Layer Name",
319
+ 1004: "Binary Data",
320
+ 1005: "Database Handle",
321
+ 1010: "3D Point",
322
+ 1011: "3D Position",
323
+ 1012: "3D Displacement",
324
+ 1013: "3D Direction",
325
+ 1040: "Real Number",
326
+ 1041: "Distance",
327
+ 1042: "Scale Factor",
328
+ 1070: "16-bit Integer",
329
+ 1071: "32-bit Integer"
330
+ }
331
+ return type_map.get(type_code, f"Unknown ({type_code})")
332
+
333
+ def parse_xdata(self, xdata_list):
334
+ """
335
+ Parse raw xdata list into readable format
336
+ """
337
+ if not xdata_list:
338
+ return None
339
+
340
+ parsed = []
341
+ for type_code, value in xdata_list:
342
+ entry = {
343
+ 'type_code': type_code,
344
+ 'value': value,
345
+ 'description': self.get_xdata_type_description(type_code)
346
+ }
347
+ parsed.append(entry)
348
+
349
+ return parsed
350
+
351
+ def print_xdata(self, xdata_list):
352
+ """Print xdata in a formatted way"""
353
+ if not xdata_list:
354
+ print("No XData to display")
355
+ return
356
+
357
+ print("\nXData Contents:")
358
+ print("-" * 70)
359
+ for i, (type_code, value) in enumerate(xdata_list):
360
+ desc = self.get_xdata_type_description(type_code)
361
+ print(f"[{i}] Type: {type_code} ({desc})")
362
+ print(f" Value: {value}")
363
+ print("-" * 70)
364
+
365
+ def get_blockdata_from_3dsolid(self, entity):
366
+ """Get Block ID and Description from 3D solid with BLOCKDATA"""
367
+ xdata = self.get_xdata(entity, "BLOCKDATA")
368
+
369
+ if not xdata:
370
+ return None
371
+
372
+ # Parse the data - structure should be:
373
+ # [0] (1001, "BLOCKDATA") - App name
374
+ # [1] (1000, "block_id_value") - Block ID
375
+ # [2] (1000, "description_value") - Description
376
+
377
+ result = {}
378
+ if len(xdata) > 1 and xdata[1][0] == 1000:
379
+ result['block_id'] = xdata[1][1]
380
+ if len(xdata) > 2 and xdata[2][0] == 1000:
381
+ result['description'] = xdata[2][1]
382
+
383
+ return result if result else None
384
+
385
+ def get_regionname_from_region(self, entity):
386
+ """Get Region Name from region object with REGIONDATA"""
387
+ xdata = self.get_xdata(entity, "REGIONDATA")
388
+
389
+ if not xdata:
390
+ return None
391
+
392
+ # Parse the data - structure should be:
393
+ # [0] (1001, "REGIONDATA") - App name
394
+ # [1] (1000, "region_name_value") - Region Name
395
+
396
+ result = {}
397
+ if len(xdata) > 1 and xdata[1][0] == 1000:
398
+ result['region_name'] = xdata[1][1]
399
+
400
+ return result if result else None
401
+
402
+ def list_entity_xdata(self, entity):
403
+ """Lists all XData applications for an entity (for debugging)."""
404
+ try:
405
+ # Get all XData first
406
+ all_xdata = self.get_xdata(entity, "")
407
+ if not all_xdata:
408
+ return []
409
+
410
+ # Extract application names from XData
411
+ xdata_apps = []
412
+ for type_code, value in all_xdata:
413
+ if type_code == 1001: # Registered application name
414
+ xdata_apps.append(value)
415
+
416
+ return xdata_apps
417
+ except:
418
+ return []
@@ -6,13 +6,46 @@ for different OpenFOAM dictionary files.
6
6
  """
7
7
 
8
8
  from .foam_writer import FoamWriter
9
- from .block_mesh_writer import BlockMeshWriter
10
- from .control_dict_writer import ControlDictWriter
11
- from .turbulence_properties_writer import TurbulencePropertiesWriter
9
+
10
+ # Import writers from subdirectories
11
+ from .constant.turbulence_properties_writer import TurbulencePropertiesWriter
12
+ from .constant.transport_properties_writer import TransportPropertiesWriter
13
+ from .constant.dynamic_mesh_dict_writer import DynamicMeshDictWriter
14
+ from .constant.gravity_field_writer import GravityFieldWriter
15
+ from .constant.hfdibdem_dict_writer import HFDIBDEMDictWriter
16
+
17
+ from .system.block_mesh_writer import BlockMeshWriter
18
+ from .system.control_dict_writer import ControlDictWriter
19
+ from .system.fv_schemes_writer import FvSchemesWriter
20
+ from .system.fv_options_writer import FvOptionsWriter
21
+ from .system.set_fields_writer import SetFieldsWriter
22
+ from .system.decompose_par_writer import DecomposeParWriter
23
+ from .system.snappy_hex_mesh_writer import SnappyHexMeshWriter
24
+
25
+ from .zero.p_field_writer import PFieldWriter
26
+ from .zero.u_field_writer import UFieldWriter
27
+ from .zero.f_field_writer import FFieldWriter
28
+ from .zero.lambda_field_writer import LambdaFieldWriter
12
29
 
13
30
  __all__ = [
14
31
  "FoamWriter",
32
+ # Constant directory writers
33
+ "TurbulencePropertiesWriter",
34
+ "TransportPropertiesWriter",
35
+ "DynamicMeshDictWriter",
36
+ "GravityFieldWriter",
37
+ "HFDIBDEMDictWriter",
38
+ # System directory writers
15
39
  "BlockMeshWriter",
16
40
  "ControlDictWriter",
17
- "TurbulencePropertiesWriter",
41
+ "FvSchemesWriter",
42
+ "FvOptionsWriter",
43
+ "SetFieldsWriter",
44
+ "DecomposeParWriter",
45
+ "SnappyHexMeshWriter",
46
+ # Zero directory writers
47
+ "PFieldWriter",
48
+ "UFieldWriter",
49
+ "FFieldWriter",
50
+ "LambdaFieldWriter",
18
51
  ]
@@ -0,0 +1,25 @@
1
+ # constant/__init__.py
2
+ """
3
+ Constant directory writer modules.
4
+
5
+ This package contains writer modules for OpenFOAM constant directory files:
6
+ - turbulenceProperties
7
+ - transportProperties
8
+ - dynamicMeshDict
9
+ - gravity field (g)
10
+ - HFDIBDEMDict
11
+ """
12
+
13
+ from . import turbulence_properties_writer
14
+ from . import transport_properties_writer
15
+ from . import dynamic_mesh_dict_writer
16
+ from . import gravity_field_writer
17
+ from . import hfdibdem_dict_writer
18
+
19
+ __all__ = [
20
+ 'turbulence_properties_writer',
21
+ 'transport_properties_writer',
22
+ 'dynamic_mesh_dict_writer',
23
+ 'gravity_field_writer',
24
+ 'hfdibdem_dict_writer'
25
+ ]
@@ -0,0 +1,75 @@
1
+ # dynamic_mesh_dict_writer.py
2
+
3
+ from ..foam_writer import FoamWriter
4
+
5
+ class DynamicMeshDictWriter(FoamWriter):
6
+ """
7
+ A class to write an OpenFOAM dynamicMeshDict file.
8
+
9
+ This writer is highly flexible and can handle the diverse and nested
10
+ structures required for various dynamic mesh types by recursively
11
+ processing a Python dictionary.
12
+ """
13
+
14
+ def __init__(self, file_path, properties):
15
+ """
16
+ Initializes the DynamicMeshDictWriter.
17
+
18
+ Args:
19
+ file_path (str): The full path to the output file 'dynamicMeshDict'.
20
+ properties (dict): A dictionary containing the full content of the
21
+ dynamicMeshDict file.
22
+ """
23
+ super().__init__(file_path, foam_class="dictionary", foam_object="dynamicMeshDict")
24
+ self.properties = properties
25
+
26
+ def _write_recursively(self, data, indent_level):
27
+ """
28
+ Recursively writes dictionary contents, handling nested dictionaries and lists.
29
+
30
+ Args:
31
+ data (dict): The dictionary to write.
32
+ indent_level (int): The current level of indentation.
33
+ """
34
+ indent = " " * indent_level
35
+ max_key_len = max((len(k) for k in data.keys()), default=0)
36
+
37
+ for key, value in data.items():
38
+ if isinstance(value, dict):
39
+ # If value is a dictionary, write a new block
40
+ self.file_handle.write(f"{indent}{key}\n")
41
+ self.file_handle.write(f"{indent}{{\n")
42
+ self._write_recursively(value, indent_level + 1)
43
+ self.file_handle.write(f"{indent}}}\n")
44
+ elif isinstance(value, list):
45
+ # If value is a list, format as (item1 item2 ...)
46
+ list_str = ' '.join(map(str, value))
47
+ self.file_handle.write(f"{indent}{key:<{max_key_len}} ({list_str});\n")
48
+ elif isinstance(value, tuple):
49
+ # If value is a tuple, format as (item1 item2 ...) without quotes
50
+ tuple_str = ' '.join(map(str, value))
51
+ self.file_handle.write(f"{indent}{key:<{max_key_len}} ({tuple_str});\n")
52
+ else:
53
+ # Otherwise, it's a simple key-value pair
54
+ self.file_handle.write(f"{indent}{key:<{max_key_len}} {value};\n")
55
+
56
+ def _write_properties(self):
57
+ """Writes the main content of the dynamicMeshDict file."""
58
+ self._write_recursively(self.properties, indent_level=0)
59
+
60
+ def write(self):
61
+ """
62
+ Writes the complete dynamicMeshDict file.
63
+ """
64
+ print(f"Writing dynamicMeshDict to: {self.file_path}")
65
+ with open(self.file_path, 'w') as f:
66
+ self.file_handle = f
67
+ self._write_header()
68
+ self._write_foamfile_dict()
69
+ self._write_separator()
70
+
71
+ self._write_properties()
72
+
73
+ self._write_footer()
74
+ self.file_handle = None
75
+ print("...Done")
@@ -0,0 +1,88 @@
1
+ # gravity_field_writer.py
2
+
3
+ """
4
+ Gravity Field Writer for OpenFOAM cases.
5
+
6
+ This writer handles the creation of gravity field (g) files with support for
7
+ various gravity configurations including standard Earth gravity, custom gravity
8
+ vectors, and specialized gravity fields for different simulation types.
9
+ """
10
+
11
+ from ..foam_writer import FoamWriter
12
+
13
+
14
+ class GravityFieldWriter(FoamWriter):
15
+ """
16
+ A class to write an OpenFOAM gravity field (g) file.
17
+
18
+ This writer supports various gravity configurations including standard
19
+ gravitational acceleration, custom gravity vectors, and specialized
20
+ gravity fields for different simulation types.
21
+ """
22
+
23
+ def __init__(self, file_path, gravity_value, dimensions=None):
24
+ """
25
+ Initialize the GravityFieldWriter.
26
+
27
+ Args:
28
+ file_path (str): The full path to the output file 'g'.
29
+ gravity_value (tuple): Gravity vector (gx, gy, gz) [m/s^2].
30
+ dimensions (list, optional): Dimensions of the gravity field.
31
+ """
32
+ super().__init__(file_path, foam_class="uniformDimensionedVectorField", foam_object="g")
33
+ self.gravity_value = gravity_value
34
+ self.dimensions = dimensions or [0, 1, -2, 0, 0, 0, 0] # Default: acceleration dimensions
35
+
36
+ def validate_gravity_field(self):
37
+ """
38
+ Validate the gravity field configuration.
39
+
40
+ Returns:
41
+ bool: True if validation passes, False otherwise.
42
+ """
43
+ valid = True
44
+
45
+ # Check if gravity_value is a tuple/list with 3 components
46
+ if not isinstance(self.gravity_value, (tuple, list)) or len(self.gravity_value) != 3:
47
+ print("Warning: Gravity value must be a tuple/list with 3 components (gx, gy, gz)")
48
+ valid = False
49
+
50
+ # Check if all components are numeric
51
+ for i, component in enumerate(self.gravity_value):
52
+ if not isinstance(component, (int, float)):
53
+ print(f"Warning: Gravity component {i} must be numeric")
54
+ valid = False
55
+
56
+ # Check dimensions
57
+ if not isinstance(self.dimensions, (tuple, list)) or len(self.dimensions) != 7:
58
+ print("Warning: Dimensions must be a tuple/list with 7 components")
59
+ valid = False
60
+
61
+ return valid
62
+
63
+ def _write_properties(self):
64
+ """Writes the main content of the gravity field file."""
65
+ # Write dimensions
66
+ dimensions_str = ' '.join(map(str, self.dimensions))
67
+ self.file_handle.write(f"dimensions [{dimensions_str}];\n")
68
+
69
+ # Write gravity vector
70
+ gravity_str = ' '.join(map(str, self.gravity_value))
71
+ self.file_handle.write(f"value ({gravity_str});\n")
72
+
73
+ def write(self):
74
+ """
75
+ Writes the complete gravity field file.
76
+ """
77
+ print(f"Writing gravity field (g) to: {self.file_path}")
78
+ with open(self.file_path, 'w') as f:
79
+ self.file_handle = f
80
+ self._write_header()
81
+ self._write_foamfile_dict()
82
+ self._write_separator()
83
+
84
+ self._write_properties()
85
+
86
+ self._write_footer()
87
+ self.file_handle = None
88
+ print("...Done")