66import pathlib
77from dataclasses import asdict , is_dataclass
88from typing import Any , Iterable , Tuple , Dict , List
9- import os
109
1110from .class_definitions import (
1211 Job , BCGroup , GIF , VolumeZone ,
@@ -83,7 +82,9 @@ def rpm_to_omegab(rpm: float | None) -> float:
8382# ============================================================
8483def ensure_extension (path : str | pathlib .Path , ext : str ) -> str :
8584 p = pathlib .Path (path )
86- return str (p if p .suffix .lower () == ext .lower () else p .with_suffix (ext ))
85+ if p .suffix :
86+ return str (p )
87+ return str (p .with_suffix (ext ))
8788
8889def _asdict_soft (x ):
8990 if is_dataclass (x ):
@@ -146,26 +147,46 @@ def _export_namelist_block(header: str, obj: Any, *, exclude_names=()) -> str:
146147 inner = ", " .join (pairs )
147148 return f" &{ header } \n { inner } \n &END\n "
148149
150+ # ============================================================
151+ # GIF + Volume Zone writers (dict/object inputs supported)
152+ # ============================================================
153+ def _get_field (obj : Any , name : str , default : Any = None ) -> Any :
154+ if isinstance (obj , dict ):
155+ return obj .get (name , default )
156+ return getattr (obj , name , default )
157+
158+ def _set_field (obj : Any , name : str , value : Any ) -> None :
159+ if isinstance (obj , dict ):
160+ obj [name ] = value
161+ else :
162+ setattr (obj , name , value )
163+
164+ def _first_field (obj : Any , names : Iterable [str ], default : Any = None ) -> Any :
165+ for name in names :
166+ value = _get_field (obj , name )
167+ if value is not None :
168+ return value
169+ return default
170+
149171def _write_bsurf_spec (w , bc ):
150172 line = (
151173 f" &BSurf_Spec\n "
152- f"BSurfID={ bc .SurfaceID } , BCType={ int (bc .BCType )} , BSurfName='{ bc .Name } '"
174+ f"BSurfID={ _get_field (bc , 'SurfaceID' )} , "
175+ f"BCType={ int (_get_field (bc , 'BCType' ))} , "
176+ f"BSurfName='{ _get_field (bc , 'Name' )} '"
153177 )
154- if getattr (bc , "IsPostProcessing" , False ):
178+ if _get_field (bc , "IsPostProcessing" , False ):
155179 line += ", BRefCond=T"
156- if getattr (bc , "IsCalculateMassFlow" , False ) or getattr (bc , "ToggleProcessSurface" , False ):
180+ if _get_field (bc , "IsCalculateMassFlow" , False ) or _get_field (bc , "ToggleProcessSurface" , False ):
157181 line += ", BCalc=T"
158182 w .write (line + "\n &END\n \n " )
159183
160- # ============================================================
161- # GIF + Volume Zone writers (dict inputs supported)
162- # ============================================================
163- def _write_gif_from_dict (w , gdict : Dict [str , Any ]) -> None :
164- sid1 = int (gdict .get ("a" , 0 ))
165- sid2 = int (gdict .get ("b" , 0 ))
166- name1 = f"surface { sid1 } "
167- name2 = f"surface { sid2 } "
168- bctype = 4 # GIF
184+ def _write_gif_pair (w , pair : Any ) -> None :
185+ sid1 = int (_first_field (pair , ("GIFSurface1" , "id1" , "a" ), 0 ))
186+ sid2 = int (_first_field (pair , ("GIFSurface2" , "id2" , "b" ), 0 ))
187+ name1 = _first_field (pair , ("Name1" , "name1" ), f"surface { sid1 } " )
188+ name2 = _first_field (pair , ("Name2" , "name2" ), f"surface { sid2 } " )
189+ bctype = int (_first_field (pair , ("BCType" , "bctype" ), 4 ))
169190 w .write (
170191 f" &BSurf_Spec\n BSurfID={ sid1 } , BCType={ bctype } , BSurfName='{ name1 } '\n &END\n \n "
171192 )
@@ -174,14 +195,14 @@ def _write_gif_from_dict(w, gdict: Dict[str, Any]) -> None:
174195 )
175196 w .write (f" &GIF_Spec\n SurfID_1={ sid1 } , SurfID2={ sid2 } \n &END\n \n " )
176197
177- def _write_vzconditions (w , vz : Dict [ str , Any ] ) -> None :
198+ def _write_vzconditions (w , vz : Any ) -> None :
178199 """
179- Convert your dict style :
180- {"block_index": 1, "zone_type": "fluid"|"solid", "contiguous_id ": 1}
200+ Convert inputs like :
201+ {"block_index": 1, "zone_type": "fluid"|"solid", "contiguous_index ": 1}
181202 to the GHT namelist you showed (defaults baked in).
182203 """
183- vzid = int (vz . get ( " contiguous_id" , 0 ))
184- ztype = str (vz . get ( "zone_type" , "fluid" )).strip ().lower ()
204+ vzid = int (_first_field ( vz , ( "contiguous_index" , " contiguous_id") , 0 ))
205+ ztype = str (_get_field ( vz , "zone_type" , "fluid" )).strip ().lower ()
185206 vztype = 1 if ztype == "fluid" else 2
186207
187208 if vztype == 1 :
@@ -312,8 +333,8 @@ def export_to_boundary_condition(
312333 file_path_to_write : str ,
313334 job_settings : Job ,
314335 bc_group : BCGroup ,
315- gif_pairs : List [GIF ] | List [ Dict [ str , Any ] ],
316- volume_zones : List [VolumeZone ] | List [ Dict [ str , Any ] ],
336+ gif_pairs : List [Any ],
337+ volume_zones : List [Any ],
317338):
318339 file_path_to_write = ensure_extension (file_path_to_write , '.bcs' )
319340 path = pathlib .Path (file_path_to_write )
@@ -325,7 +346,7 @@ def export_to_boundary_condition(
325346 "outlets" : [_asdict_soft (x ) for x in bc_group .Outlets ],
326347 "slips" : [_asdict_soft (x ) for x in bc_group .SymmetricSlips ],
327348 "walls" : [_asdict_soft (x ) for x in bc_group .Walls ],
328- "volume_zones" : [x for x in volume_zones ],
349+ "volume_zones" : [_asdict_soft ( x ) for x in volume_zones ],
329350 "gif_pairs" : [_asdict_soft (x ) for x in gif_pairs ],
330351 "job_settings" : _asdict_soft (job_settings ),
331352 }
@@ -338,10 +359,10 @@ def _reference_inlet(inlets: List[Any]) -> Tuple[Any | None, float | None]:
338359 best = None
339360 best_pa : float | None = None
340361 for inlet in inlets :
341- p0 = getattr (inlet , "P0_const" , None )
362+ p0 = _get_field (inlet , "P0_const" )
342363 if p0 is None :
343364 continue
344- phys_pa = to_pa (p0 , getattr (inlet , "P0_const_unit" , "Pa" ))
365+ phys_pa = to_pa (p0 , _get_field (inlet , "P0_const_unit" , "Pa" ))
345366 if phys_pa is None :
346367 continue
347368 if best_pa is None or phys_pa > best_pa :
@@ -364,14 +385,14 @@ def _reference_inlet(inlets: List[Any]) -> Tuple[Any | None, float | None]:
364385
365386 # refT0: from first inlet if missing
366387 if getattr (ref , "refT0" , None ) in (None , 0 ):
367- if ref_inlet and getattr (ref_inlet , "T0_const" , None ) is not None :
368- ref .refT0 = ref_inlet . T0_const # type: ignore
388+ if ref_inlet and _get_field (ref_inlet , "T0_const" ) is not None :
389+ ref .refT0 = _get_field ( ref_inlet , " T0_const" ) # type: ignore
369390
370391 def _dedupe_by_bc_id (objs : Iterable [Any ]) -> List [Any ]:
371392 seen : set [int ] = set ()
372393 unique : List [Any ] = []
373394 for obj in objs :
374- sid = obj . get ( "id" ) if isinstance ( obj , dict ) else getattr (obj , "SurfaceID" , None )
395+ sid = _get_field ( obj , "id" , _get_field (obj , "SurfaceID" ) )
375396 if sid is None or sid in seen :
376397 continue
377398 seen .add (sid )
@@ -382,26 +403,26 @@ def _dedupe_by_bc_id(objs: Iterable[Any]) -> List[Any]:
382403 with path .open ("w" , encoding = "utf-8" ) as w :
383404 # INLETS (normalize to refP0, refT0, refLen)
384405 for inlet in _dedupe_by_bc_id (bc_group .Inlets ):
385- if getattr (inlet , "P0_const" , None ) is not None :
386- phys_pa = to_pa (inlet . P0_const , getattr (inlet , "P0_const_unit" , "Pa" ))
406+ if _get_field (inlet , "P0_const" ) is not None :
407+ phys_pa = to_pa (_get_field ( inlet , " P0_const" ), _get_field (inlet , "P0_const_unit" , "Pa" ))
387408 if phys_pa is not None and ref .refP0 not in (None , 0 ):
388- inlet . P0_const = phys_pa / ref .refP0
389- if getattr (inlet , "T0_const" , None ) is not None and ref .refT0 not in (None , 0 ):
390- inlet . T0_const = inlet . T0_const / ref .refT0 # type: ignore
391- if getattr (inlet , "twall_hub" , None ) is not None and ref .refT0 not in (None , 0 ):
392- inlet . twall_hub = inlet . twall_hub / ref .refT0 # type: ignore
393- if getattr (inlet , "twall_case" , None ) is not None and ref .refT0 not in (None , 0 ):
394- inlet . twall_case = inlet . twall_case / ref .refT0 # type: ignore
395- if getattr (inlet , "Ts_const" , None ) is not None and ref .reflen not in (None , 0 ):
396- inlet . Ts_const = inlet . Ts_const / ref .reflen # type: ignore
409+ _set_field ( inlet , " P0_const" , phys_pa / ref .refP0 )
410+ if _get_field (inlet , "T0_const" ) is not None and ref .refT0 not in (None , 0 ):
411+ _set_field ( inlet , " T0_const" , _get_field ( inlet , " T0_const" ) / ref .refT0 )
412+ if _get_field (inlet , "twall_hub" ) is not None and ref .refT0 not in (None , 0 ):
413+ _set_field ( inlet , " twall_hub" , _get_field ( inlet , " twall_hub" ) / ref .refT0 )
414+ if _get_field (inlet , "twall_case" ) is not None and ref .refT0 not in (None , 0 ):
415+ _set_field ( inlet , " twall_case" , _get_field ( inlet , " twall_case" ) / ref .refT0 )
416+ if _get_field (inlet , "Ts_const" ) is not None and ref .reflen not in (None , 0 ):
417+ _set_field ( inlet , " Ts_const" , _get_field ( inlet , " Ts_const" ) / ref .reflen )
397418 _write_bsurf_spec (w , inlet )
398419
399420 # OUTLETS (normalize back-pressure by refP0)
400421 for outlet in _dedupe_by_bc_id (bc_group .Outlets ):
401- if getattr (outlet , "Pback_const" , None ) is not None and ref .refP0 not in (None , 0 ):
402- phys_pa = to_pa (outlet . Pback_const , getattr (outlet , "Pback_const_unit" , "Pa" ))
422+ if _get_field (outlet , "Pback_const" ) is not None and ref .refP0 not in (None , 0 ):
423+ phys_pa = to_pa (_get_field ( outlet , " Pback_const" ), _get_field (outlet , "Pback_const_unit" , "Pa" ))
403424 if phys_pa is not None :
404- outlet . Pback_const = phys_pa / ref .refP0
425+ _set_field ( outlet , " Pback_const" , phys_pa / ref .refP0 )
405426 _write_bsurf_spec (w , outlet )
406427
407428 # SLIPS / WALLS
@@ -412,57 +433,41 @@ def _dedupe_by_bc_id(objs: Iterable[Any]) -> List[Any]:
412433
413434 # GIFS (dicts or dataclasses)
414435 for pair in gif_pairs :
415- if isinstance (pair , dict ):
416- _write_gif_from_dict (w , pair )
417- else :
418- name1 = getattr (pair , "Name1" , f"surface { pair .GIFSurface1 } " )
419- name2 = getattr (pair , "Name2" , f"surface { pair .GIFSurface2 } " )
420- w .write (
421- f" &BSurf_Spec\n BSurfID={ pair .GIFSurface1 } , BCType={ int (pair .BCType )} , "
422- f"BSurfName='{ name1 } '\n &END\n \n "
423- )
424- w .write (
425- f" &BSurf_Spec\n BSurfID={ pair .GIFSurface2 } , BCType={ int (pair .BCType )} , "
426- f"BSurfName='{ name2 } '\n &END\n \n "
427- )
428- w .write (f" &GIF_Spec\n SurfID_1={ pair .GIFSurface1 } , SurfID2={ pair .GIFSurface2 } \n &END\n \n " )
436+ _write_gif_pair (w , pair )
429437
430438 # VZConditions (dict templates)
431- volume_zone_unique = {d ["contiguous_index" ]: d for d in volume_zones }.values ()
432- for vz in volume_zone_unique :
433- if isinstance (vz , dict ):
439+ volume_zone_unique : Dict [Any , Any ] = {}
440+ for vz in volume_zones :
441+ key = _first_field (vz , ("contiguous_index" , "contiguous_id" ))
442+ volume_zone_unique [key ] = vz
443+ for vz in volume_zone_unique .values ():
444+ if _get_field (vz , "zone_type" ) is not None :
434445 _write_vzconditions (w , vz )
435446 else :
436447 w .write (_export_namelist_block ("VZConditions" , vz )); w .write ("\n " )
437-
438- # keep the *first* object for each unique obj.subtype
439- def first_by_subtype (objs : Iterable [T ], subtype_attr : str , * , include_none = False ) -> List [T ]:
440- seen = set ()
441- out : List [T ] = []
442- for o in objs :
443- # works for both objects and dicts
444- st = getattr (o , subtype_attr , None ) if not isinstance (o , dict ) else o .get (subtype_attr )
445- if st is None and not include_none :
446- continue
447- if st not in seen :
448- seen .add (st )
449- out .append (o )
450- return out
451-
452- inlet_bc = first_by_subtype (bc_group .Inlets ,'inlet_subType' )
453- outlet_bc = first_by_subtype (bc_group .Outlets ,'outlet_subType' )
454- slip_bc = first_by_subtype (bc_group .SymmetricSlips ,'slip_subType' )
455- wall_bc = first_by_subtype (bc_group .Walls ,'wall_subType' )
456448
449+ def _sync_detail_surface_id (obj : Any , field_name : str ) -> None :
450+ sid = _get_field (obj , "SurfaceID" )
451+ if sid is not None :
452+ _set_field (obj , field_name , sid )
453+
457454 # Detailed BC blocks (skip meta + *_unit)
458455 exclude = {"Name" , "SurfaceID" , "BCType" }
459- for inlet in inlet_bc :
456+ for inlet in bc_group .Inlets :
457+ _sync_detail_surface_id (inlet , "surfID_inlet" )
458+ w .write (f"! { inlet .Name } \n " )
460459 w .write (_export_namelist_block ("INLET_BC" , inlet , exclude_names = exclude )); w .write ("\n " )
461- for outlet in outlet_bc :
460+ for outlet in bc_group .Outlets :
461+ _sync_detail_surface_id (outlet , "surfID_outlet" )
462+ w .write (f"! { outlet .Name } \n " )
462463 w .write (_export_namelist_block ("OUTLET_BC" , outlet , exclude_names = exclude )); w .write ("\n " )
463- for slip in slip_bc :
464+ for slip in bc_group .SymmetricSlips :
465+ _sync_detail_surface_id (slip , "surfID_symmetricSlip" )
466+ w .write (f"! { slip .Name } \n " )
464467 w .write (_export_namelist_block ("SLIP_BC" , slip , exclude_names = exclude )); w .write ("\n " )
465- for wall in wall_bc :
468+ for wall in bc_group .Walls :
469+ _sync_detail_surface_id (wall , "surfID_wall" )
470+ w .write (f"! { wall .Name } \n " )
466471 w .write (_export_namelist_block ("WALL_BC" , wall , exclude_names = exclude )); w .write ("\n " )
467472
468473# ============================================================
@@ -616,11 +621,6 @@ def export_to_glennht_conn(matches:List[Dict[str, Dict[int, str]]],outer_faces:L
616621 with open (f'{ filename } ' ,'w' ) as fp :
617622 fp .writelines (lines )
618623
619- def ensure_extension (filename , default_ext = ".txt" ):
620- base , ext = os .path .splitext (filename )
621- if not ext : # no extension present
622- return filename + default_ext
623- return filename
624624# ============================================================
625625# ---- Quick self-test / example usage -----------------------
626626# ============================================================
0 commit comments