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,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
|
-
|
10
|
-
|
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
|
-
"
|
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")
|