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,463 @@
1
+ """
2
+ CAD-based Block Mesh Developer
3
+
4
+ This module provides functionality to generate blockMeshDict files from AutoCAD CAD files
5
+ by reading 3DSOLID and REGION entities with XData containing mesh configuration information.
6
+ """
7
+
8
+ import os
9
+ import csv
10
+ import numpy as np
11
+ from typing import Dict, List, Tuple, Optional, Any
12
+
13
+ from ..writers.system.block_mesh_writer import BlockMeshWriter
14
+ from ..utils.myAutoCAD import myAutoCAD
15
+
16
+
17
+ class CADBlockMeshDeveloper:
18
+ """
19
+ A class to generate blockMeshDict files from AutoCAD CAD files.
20
+
21
+ This class reads 3DSOLID entities for block definitions and REGION entities for
22
+ patch definitions, using XData to identify and configure the mesh parameters.
23
+ """
24
+
25
+ def __init__(self,
26
+ blocks_csv_file: str = "Inputs/blocks.csv",
27
+ patches_csv_file: str = "Inputs/patches.csv",
28
+ tolerance: float = 1e-6,
29
+ block_xdata_app_name: str = "BLOCKDATA",
30
+ region_xdata_app_name: str = "REGIONDATA"):
31
+ """
32
+ Initialize the CAD Block Mesh Developer.
33
+
34
+ Args:
35
+ blocks_csv_file: Path to CSV file containing block parameters
36
+ patches_csv_file: Path to CSV file containing patch definitions
37
+ tolerance: Tolerance for face coincidence checking
38
+ block_xdata_app_name: XData application name for 3DSOLID entities
39
+ region_xdata_app_name: XData application name for REGION entities
40
+ """
41
+ self.blocks_csv_file = blocks_csv_file
42
+ self.patches_csv_file = patches_csv_file
43
+ self.tolerance = tolerance
44
+ self.block_xdata_app_name = block_xdata_app_name
45
+ self.region_xdata_app_name = region_xdata_app_name
46
+
47
+ # Data structures for mesh generation
48
+ self.all_vertices = []
49
+ self.block_definitions = []
50
+ self.patch_definitions = {}
51
+ self.solid_data = {}
52
+
53
+ # Configuration data
54
+ self.block_parameters = {}
55
+ self.patch_info = {}
56
+
57
+ # CAD connection
58
+ self.cad_app = None
59
+ self.modelspace = None
60
+
61
+ def _load_csv_data(self, filepath: str, required_columns: List[str], key_column: str) -> Optional[Dict]:
62
+ """
63
+ Load data from a CSV file into a dictionary.
64
+
65
+ Args:
66
+ filepath: Path to the CSV file
67
+ required_columns: List of required column names
68
+ key_column: Column to use as dictionary key
69
+
70
+ Returns:
71
+ Dictionary with loaded data or None if error
72
+ """
73
+ data = {}
74
+ try:
75
+ with open(filepath, mode='r', newline='') as infile:
76
+ reader = csv.DictReader(infile)
77
+ if not all(col in reader.fieldnames for col in required_columns):
78
+ missing = set(required_columns) - set(reader.fieldnames)
79
+ raise KeyError(f"CSV file '{filepath}' is missing required columns: {missing}")
80
+ for row in reader:
81
+ key = row[key_column]
82
+ data[key] = row
83
+ print(f"Successfully loaded {len(data)} definitions from '{filepath}'.")
84
+ return data
85
+ except FileNotFoundError:
86
+ print(f"Error: The file '{filepath}' was not found.")
87
+ return None
88
+ except KeyError as e:
89
+ print(f"Error: {e}")
90
+ return None
91
+
92
+ def _generate_solid_faces(self, vertex_indices: List[int]) -> List[List[int]]:
93
+ """
94
+ Generate face definitions for a hex block from vertex indices.
95
+
96
+ Args:
97
+ vertex_indices: List of 8 vertex indices in OpenFOAM ordering
98
+
99
+ Returns:
100
+ List of face definitions as lists of vertex indices
101
+ """
102
+ return [
103
+ # Bottom face (z=0)
104
+ [vertex_indices[0], vertex_indices[1], vertex_indices[2], vertex_indices[3]],
105
+ # Top face (z=1)
106
+ [vertex_indices[4], vertex_indices[5], vertex_indices[6], vertex_indices[7]],
107
+ # Side faces
108
+ [vertex_indices[0], vertex_indices[4], vertex_indices[7], vertex_indices[3]], # x=0
109
+ [vertex_indices[1], vertex_indices[2], vertex_indices[6], vertex_indices[5]], # x=1
110
+ [vertex_indices[0], vertex_indices[1], vertex_indices[5], vertex_indices[4]], # y=0
111
+ [vertex_indices[3], vertex_indices[7], vertex_indices[6], vertex_indices[2]] # y=1
112
+ ]
113
+
114
+ def connect_to_cad(self) -> bool:
115
+ """
116
+ Connect to AutoCAD and initialize the modelspace.
117
+
118
+ Returns:
119
+ True if connection successful, False otherwise
120
+ """
121
+ try:
122
+ self.cad_app = myAutoCAD(create_if_not_exists=False)
123
+ self.modelspace = self.cad_app.model
124
+ return True
125
+ except Exception as e:
126
+ print(f"Error: Failed to connect to AutoCAD. Details: {e}")
127
+ return False
128
+
129
+ def load_configuration(self) -> bool:
130
+ """
131
+ Load block and patch configuration from CSV files.
132
+
133
+ Returns:
134
+ True if configuration loaded successfully, False otherwise
135
+ """
136
+ # Load block parameters
137
+ self.block_parameters = self._load_csv_data(
138
+ self.blocks_csv_file,
139
+ ['BlockID', 'CellsX', 'CellsY', 'CellsZ', 'Grading'],
140
+ 'BlockID'
141
+ )
142
+
143
+ # Load patch information
144
+ self.patch_info = self._load_csv_data(
145
+ self.patches_csv_file,
146
+ ['RegionName', 'PatchName', 'PatchType'],
147
+ 'RegionName'
148
+ )
149
+
150
+ if not self.block_parameters or not self.patch_info:
151
+ print("Aborting due to missing or invalid configuration files.")
152
+ return False
153
+
154
+ return True
155
+
156
+ def debug_entities(self) -> None:
157
+ """Debug method to list all entities and their XData."""
158
+ print("\n=== DEBUG: Listing all entities ===")
159
+
160
+ entity_types = {}
161
+ for entity in self.modelspace:
162
+ entity_type = entity.ObjectName
163
+ if entity_type not in entity_types:
164
+ entity_types[entity_type] = []
165
+
166
+ # Try to get XData applications
167
+ xdata_apps = self.cad_app.list_entity_xdata(entity)
168
+ entity_info = {
169
+ 'handle': entity.Handle,
170
+ 'xdata_apps': xdata_apps
171
+ }
172
+ entity_types[entity_type].append(entity_info)
173
+
174
+ for entity_type, entities in entity_types.items():
175
+ print(f"\n{entity_type} entities ({len(entities)}):")
176
+ for entity_info in entities:
177
+ print(f" Handle: {entity_info['handle']}")
178
+ if entity_info['xdata_apps']:
179
+ print(f" XData apps: {entity_info['xdata_apps']}")
180
+ # Show detailed XData for debugging
181
+ try:
182
+ entity = self.modelspace.HandleToObject(entity_info['handle'])
183
+ all_xdata = self.cad_app.get_xdata(entity, "")
184
+ if all_xdata:
185
+ self.cad_app.print_xdata(all_xdata)
186
+ except AttributeError:
187
+ # HandleToObject might not be available, skip detailed XData display
188
+ print(f" (Detailed XData display not available)")
189
+ else:
190
+ print(f" No XData found")
191
+
192
+ def process_solids(self) -> None:
193
+ """Process 3DSOLID entities and extract block definitions."""
194
+ print("\nProcessing 3D Solids based on XData...")
195
+
196
+ for entity in self.modelspace:
197
+ if entity.ObjectName == 'AcDb3dSolid':
198
+ print(f" Found 3DSOLID entity: {entity.Handle}")
199
+ xdata = self.cad_app.get_xdata(entity, self.block_xdata_app_name)
200
+ if xdata is None:
201
+ print(f" No XData found for app '{self.block_xdata_app_name}'")
202
+ continue
203
+ else:
204
+ print(f" Found XData: {xdata}")
205
+
206
+ try:
207
+ # Use the new parsing method
208
+ block_data = self.cad_app.get_blockdata_from_3dsolid(entity)
209
+ if not block_data:
210
+ print(f" Could not parse block data from XData")
211
+ continue
212
+
213
+ block_id = block_data.get('block_id')
214
+ description = block_data.get('description', "")
215
+
216
+ if block_id in self.block_parameters:
217
+ props = self.block_parameters[block_id]
218
+ verts = self.cad_app.get_solid_vertices(entity)
219
+ if verts is None:
220
+ print(f" Could not extract vertices from 3DSOLID entity")
221
+ continue
222
+ vert_indices = [len(self.all_vertices) + i for i in range(len(verts))]
223
+ self.all_vertices.extend(verts)
224
+
225
+ # Create block definition
226
+ block_def = {
227
+ "id": block_id,
228
+ "vertices": tuple(vert_indices),
229
+ "cells": (int(props['CellsX']), int(props['CellsY']), int(props['CellsZ'])),
230
+ "grading": props['Grading']
231
+ }
232
+ self.block_definitions.append(block_def)
233
+
234
+ # Generate faces for patch matching
235
+ faces = self._generate_solid_faces(vert_indices)
236
+ face_coords = [np.array([self.all_vertices[i] for i in face]) for face in faces]
237
+
238
+ self.solid_data[entity.Handle] = {
239
+ "block_id": block_id,
240
+ "vertex_indices": vert_indices,
241
+ "faces_v_indices": faces,
242
+ "faces_v_coords": face_coords
243
+ }
244
+
245
+ print(f" Found and processed block with ID: '{block_id}'.")
246
+ else:
247
+ print(f" Found block ID '{block_id}' but it's not defined in '{self.blocks_csv_file}'. Skipping.")
248
+
249
+ except (IndexError, KeyError, ValueError) as e:
250
+ print(f" Error processing solid entity: {e}")
251
+ continue
252
+
253
+ def process_regions(self) -> None:
254
+ """Process REGION entities and match them to solid faces for patches."""
255
+ print("\nProcessing Regions for patches based on XData...")
256
+
257
+ for entity in self.modelspace:
258
+ if entity.ObjectName == 'AcDbRegion':
259
+ print(f" Found REGION entity: {entity.Handle}")
260
+ xdata = self.cad_app.get_xdata(entity, self.region_xdata_app_name)
261
+ if xdata is None:
262
+ print(f" No XData found for app '{self.region_xdata_app_name}'")
263
+ continue
264
+ else:
265
+ print(f" Found XData: {xdata}")
266
+
267
+ try:
268
+ # Use the new parsing method
269
+ region_data = self.cad_app.get_regionname_from_region(entity)
270
+ if not region_data:
271
+ print(f" Could not parse region data from XData")
272
+ continue
273
+
274
+ region_name = region_data.get('region_name')
275
+
276
+ if region_name in self.patch_info:
277
+ patch_config = self.patch_info[region_name]
278
+ patch_name = patch_config['PatchName']
279
+
280
+ region_verts = self.cad_app.get_region_vertices(entity)
281
+ if region_verts is None:
282
+ print(f" Could not extract vertices from REGION entity")
283
+ continue
284
+ match_found = False
285
+
286
+ # Try to match region to a solid face
287
+ for handle, data in self.solid_data.items():
288
+ for i, solid_face_coords in enumerate(data['faces_v_coords']):
289
+ if self.cad_app.are_faces_coincident(region_verts, solid_face_coords, self.tolerance):
290
+ if patch_name not in self.patch_definitions:
291
+ self.patch_definitions[patch_name] = {
292
+ 'type': patch_config['PatchType'],
293
+ 'faces': []
294
+ }
295
+ self.patch_definitions[patch_name]['faces'].append(data['faces_v_indices'][i])
296
+ print(f" Matched region '{region_name}' to a face on block '{data['block_id']}' -> Patch: '{patch_name}'")
297
+ match_found = True
298
+ break
299
+ if match_found:
300
+ break
301
+
302
+ if not match_found:
303
+ print(f" WARNING: Region '{region_name}' was found but could not be matched to any solid face.")
304
+ else:
305
+ print(f" Found region '{region_name}' but it's not defined in '{self.patches_csv_file}'. Skipping.")
306
+
307
+ except (IndexError, KeyError, ValueError) as e:
308
+ print(f" Error processing region entity: {e}")
309
+ continue
310
+
311
+ def _convert_to_blockmesh_format(self) -> Tuple[List, List, List]:
312
+ """
313
+ Convert internal data structures to BlockMeshWriter format.
314
+
315
+ Returns:
316
+ Tuple of (vertices, blocks, boundary) in BlockMeshWriter format
317
+ """
318
+ # Convert vertices to tuple format
319
+ vertices = [tuple(v) for v in self.all_vertices]
320
+
321
+ # Convert blocks to BlockMeshWriter format
322
+ blocks = []
323
+ for block in self.block_definitions:
324
+ block_tuple = (
325
+ 'hex',
326
+ list(block["vertices"]),
327
+ list(block["cells"]),
328
+ 'simpleGrading', # Default grading type
329
+ [1, 1, 1] # Default grading values
330
+ )
331
+ blocks.append(block_tuple)
332
+
333
+ # Convert patches to boundary format
334
+ boundary = []
335
+ for name, data in sorted(self.patch_definitions.items()):
336
+ patch_dict = {
337
+ 'name': name,
338
+ 'type': data['type'],
339
+ 'faces': data['faces']
340
+ }
341
+ boundary.append(patch_dict)
342
+
343
+ return vertices, blocks, boundary
344
+
345
+ def generate_blockmesh_dict(self, output_path: str = "system/blockMeshDict") -> bool:
346
+ """
347
+ Generate the complete blockMeshDict file from CAD data.
348
+
349
+ Args:
350
+ output_path: Path where to save the blockMeshDict file
351
+
352
+ Returns:
353
+ True if generation successful, False otherwise
354
+ """
355
+ # Ensure output directory exists
356
+ os.makedirs(os.path.dirname(output_path), exist_ok=True)
357
+
358
+ # Convert data to BlockMeshWriter format
359
+ vertices, blocks, boundary = self._convert_to_blockmesh_format()
360
+
361
+ # Create and use the BlockMeshWriter
362
+ writer = BlockMeshWriter(
363
+ file_path=output_path,
364
+ scale=1.0,
365
+ vertices=vertices,
366
+ blocks=blocks,
367
+ edges=[], # No edges for this implementation
368
+ boundary=boundary,
369
+ merge_patch_pairs=[] # No merge pairs for this implementation
370
+ )
371
+
372
+ try:
373
+ writer.write()
374
+ print(f"Successfully generated blockMeshDict at: {output_path}")
375
+ return True
376
+ except Exception as e:
377
+ print(f"Error writing blockMeshDict: {e}")
378
+ return False
379
+
380
+ def generate_zero_fields(self, output_dir: str = "0"):
381
+ """
382
+ Generate all zero field files using config files and CSV boundary conditions.
383
+
384
+ Args:
385
+ output_dir: Directory where to write the zero field files
386
+ """
387
+ from ..writers.zero.zero_field_factory import ZeroFieldFactory
388
+
389
+ print("\nGenerating zero field files using config + CSV boundary conditions...")
390
+ print("=" * 60)
391
+
392
+ # Create zero field factory
393
+ factory = ZeroFieldFactory(output_dir=output_dir)
394
+
395
+ # Show available fields
396
+ available_fields = factory.get_available_fields()
397
+ print("Field availability:")
398
+ for field_name, info in available_fields.items():
399
+ print(f" {field_name}: CSV={info['csv_available']}, BCs={len(info['boundary_conditions']) if info['boundary_conditions'] else 0} patches")
400
+
401
+ # Write all zero field files
402
+ written_fields = factory.write_all_fields(use_csv_boundaries=True)
403
+
404
+ print(f"Zero field files written to: {output_dir}/")
405
+ return written_fields
406
+
407
+ def process_cad_file(self, output_path: str = "system/blockMeshDict", debug: bool = False) -> bool:
408
+ """
409
+ Complete workflow to process CAD file and generate blockMeshDict.
410
+
411
+ Args:
412
+ output_path: Path where to save the blockMeshDict file
413
+ debug: If True, show debug information about entities and XData
414
+
415
+ Returns:
416
+ True if processing successful, False otherwise
417
+ """
418
+ # Step 1: Connect to CAD
419
+ if not self.connect_to_cad():
420
+ return False
421
+
422
+ # Step 2: Load configuration
423
+ if not self.load_configuration():
424
+ return False
425
+
426
+ # Step 2.5: Debug entities if requested
427
+ if debug:
428
+ self.debug_entities()
429
+
430
+ # Step 3: Process solids
431
+ self.process_solids()
432
+
433
+ # Step 4: Process regions
434
+ self.process_regions()
435
+
436
+ # Step 5: Generate blockMeshDict
437
+ success = self.generate_blockmesh_dict(output_path)
438
+
439
+ if success:
440
+ # Step 6: Generate zero field files
441
+ self.generate_zero_fields()
442
+
443
+ print("\n" + "="*60)
444
+ print("CAD Processing Summary:")
445
+ print("="*60)
446
+ self.get_summary()
447
+
448
+ return success
449
+
450
+ def get_summary(self) -> Dict[str, Any]:
451
+ """
452
+ Get a summary of the processed data.
453
+
454
+ Returns:
455
+ Dictionary with processing summary
456
+ """
457
+ return {
458
+ "total_vertices": len(self.all_vertices),
459
+ "total_blocks": len(self.block_definitions),
460
+ "total_patches": len(self.patch_definitions),
461
+ "blocks": [block["id"] for block in self.block_definitions],
462
+ "patches": list(self.patch_definitions.keys())
463
+ }