plot3d 1.7.7__tar.gz → 1.7.9__tar.gz

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.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: plot3d
3
- Version: 1.7.7
3
+ Version: 1.7.9
4
4
  Summary: Plot3D python utilities for reading and writing and also finding connectivity between blocks
5
5
  Author: Paht Juangphanich
6
6
  Author-email: paht.juangphanich@nasa.gov
@@ -8,6 +8,8 @@ Requires-Python: >=3.10.12,<4.0.0
8
8
  Classifier: Programming Language :: Python :: 3
9
9
  Classifier: Programming Language :: Python :: 3.11
10
10
  Classifier: Programming Language :: Python :: 3.12
11
+ Classifier: Programming Language :: Python :: 3.13
12
+ Classifier: Programming Language :: Python :: 3.14
11
13
  Requires-Dist: matplotlib
12
14
  Requires-Dist: networkx
13
15
  Requires-Dist: numpy
@@ -8,11 +8,11 @@ from typing import Any, Dict, List, Optional
8
8
  # Enums (mirror your C#)
9
9
  # ----------------------------
10
10
  class BoundaryConditionType(IntEnum):
11
- Inlet = 0
12
- Outlet = 1
13
- SymmetryOrSlip = 2
14
- Wall = 3
15
- GIF = 4
11
+ Inlet = 1
12
+ Outlet = 2
13
+ SymmetryOrSlip = 3
14
+ Wall = 4
15
+ GIF = 1000
16
16
 
17
17
  class InletBC_Subtype(IntEnum):
18
18
  Normal = 0
@@ -331,29 +331,57 @@ def export_to_boundary_condition(
331
331
  }
332
332
  json_path.write_text(json.dumps(json_payload, indent=4))
333
333
 
334
- # ---- Robust reference defaults (use inlet values if missing)
334
+ # ---- Robust reference defaults (use highest-pressure inlet if available)
335
335
  ref = job_settings.ReferenceCondFull
336
- first_inlet = bc_group.Inlets[0] if bc_group.Inlets else None
336
+
337
+ def _reference_inlet(inlets: List[Any]) -> Tuple[Any | None, float | None]:
338
+ best = None
339
+ best_pa: float | None = None
340
+ for inlet in inlets:
341
+ p0 = getattr(inlet, "P0_const", None)
342
+ if p0 is None:
343
+ continue
344
+ phys_pa = to_pa(p0, getattr(inlet, "P0_const_unit", "Pa"))
345
+ if phys_pa is None:
346
+ continue
347
+ if best_pa is None or phys_pa > best_pa:
348
+ best = inlet
349
+ best_pa = phys_pa
350
+ if best is None and inlets:
351
+ return inlets[0], None
352
+ return best, best_pa
353
+
354
+ ref_inlet, ref_inlet_pa = _reference_inlet(bc_group.Inlets)
337
355
 
338
356
  # refLen: default 1.0 if missing
339
357
  if getattr(ref, "reflen", None) in (None, 0):
340
358
  ref.reflen = 1.0 # type: ignore
341
359
 
342
- # refP0: from first inlet (convert to Pa) if missing
360
+ # refP0: from highest-pressure inlet (convert to Pa) if missing
343
361
  if getattr(ref, "refP0", None) in (None, 0):
344
- if first_inlet and getattr(first_inlet, "P0_const", None) is not None:
345
- phys_pa = to_pa(first_inlet.P0_const, getattr(first_inlet, "P0_const_unit", "Pa"))
346
- ref.refP0 = phys_pa # type: ignore
362
+ if ref_inlet_pa not in (None, 0):
363
+ ref.refP0 = ref_inlet_pa # type: ignore
347
364
 
348
365
  # refT0: from first inlet if missing
349
366
  if getattr(ref, "refT0", None) in (None, 0):
350
- if first_inlet and getattr(first_inlet, "T0_const", None) is not None:
351
- ref.refT0 = first_inlet.T0_const # type: ignore
367
+ if ref_inlet and getattr(ref_inlet, "T0_const", None) is not None:
368
+ ref.refT0 = ref_inlet.T0_const # type: ignore
369
+
370
+ def _dedupe_by_bc_id(objs: Iterable[Any]) -> List[Any]:
371
+ seen: set[int] = set()
372
+ unique: List[Any] = []
373
+ for obj in objs:
374
+ sid = obj.get("id") if isinstance(obj, dict) else getattr(obj, "SurfaceID", None)
375
+ if sid is None or sid in seen:
376
+ continue
377
+ seen.add(sid)
378
+ unique.append(obj)
379
+ return unique
352
380
 
353
381
  # ---- Write .bcs file
354
382
  with path.open("w", encoding="utf-8") as w:
355
383
  # INLETS (normalize to refP0, refT0, refLen)
356
- for inlet in bc_group.Inlets:
384
+ for inlet in _dedupe_by_bc_id(bc_group.Inlets):
357
385
  if getattr(inlet, "P0_const", None) is not None:
358
386
  phys_pa = to_pa(inlet.P0_const, getattr(inlet, "P0_const_unit", "Pa"))
359
387
  if phys_pa is not None and ref.refP0 not in (None, 0):
@@ -369,7 +397,7 @@ def export_to_boundary_condition(
369
397
  _write_bsurf_spec(w, inlet)
370
398
 
371
399
  # OUTLETS (normalize back-pressure by refP0)
372
- for outlet in bc_group.Outlets:
400
+ for outlet in _dedupe_by_bc_id(bc_group.Outlets):
373
401
  if getattr(outlet, "Pback_const", None) is not None and ref.refP0 not in (None, 0):
374
402
  phys_pa = to_pa(outlet.Pback_const, getattr(outlet, "Pback_const_unit", "Pa"))
375
403
  if phys_pa is not None:
@@ -377,9 +405,9 @@ def export_to_boundary_condition(
377
405
  _write_bsurf_spec(w, outlet)
378
406
 
379
407
  # SLIPS / WALLS
380
- for slip in bc_group.SymmetricSlips:
408
+ for slip in _dedupe_by_bc_id(bc_group.SymmetricSlips):
381
409
  _write_bsurf_spec(w, slip)
382
- for wall in bc_group.Walls:
410
+ for wall in _dedupe_by_bc_id(bc_group.Walls):
383
411
  _write_bsurf_spec(w, wall)
384
412
 
385
413
  # GIFS (dicts or dataclasses)
@@ -636,5 +664,3 @@ if __name__ == "__main__":
636
664
 
637
665
  export_to_boundary_condition("boundary_conditions.bcs", job, bcg, gifs=gifs, volume_zones=volume_zones)
638
666
  export_to_job_file(job, "jobfile", title="SingleBlade")
639
-
640
-
@@ -182,7 +182,7 @@ def _metis_part_graph_compat(
182
182
  # ---------------------------------------------------------------------------
183
183
  def partition_from_face_matches(
184
184
  face_matches: List[dict],
185
- blocks: Sequence[Block],
185
+ block_sizes: List[int],
186
186
  nparts: int,
187
187
  favor_blocksize: bool = True,
188
188
  aggregate: str = "sum",
@@ -216,7 +216,7 @@ def partition_from_face_matches(
216
216
  "Install pymetis (Linux/macOS) or run on a platform where it is supported."
217
217
  )
218
218
 
219
- n_blocks = len(blocks)
219
+ n_blocks = len(block_sizes)
220
220
  adj_list, edge_w = build_weighted_graph_from_face_matches(
221
221
  face_matches, n_blocks,
222
222
  aggregate=aggregate,
@@ -226,7 +226,7 @@ def partition_from_face_matches(
226
226
 
227
227
  vwgt: Optional[List[int]] = None
228
228
  if favor_blocksize:
229
- vwgt = [b.size for b in blocks]
229
+ vwgt = block_sizes
230
230
 
231
231
  _edgecut, parts = _metis_part_graph_compat(
232
232
  nparts=nparts,
@@ -243,7 +243,7 @@ def partition_from_face_matches(
243
243
  # ---------------------------------------------------------------------------
244
244
  def write_ddcmp(
245
245
  parts: Sequence[int],
246
- blocks: Sequence[Block],
246
+ blocksizes: List[int],
247
247
  adj_list: Dict[int, List[int]],
248
248
  edge_weights: Optional[Dict[int, Dict[int, int]]] = None,
249
249
  filename: str = "ddcmp.dat",
@@ -272,12 +272,12 @@ def write_ddcmp(
272
272
  partition_edge_weights = [0] * n_proc
273
273
  volume_nodes = [0] * n_proc
274
274
 
275
- for b, blk in enumerate(blocks):
275
+ for b, bsize in enumerate(blocksizes):
276
276
  pid = parts[b]
277
- volume_nodes[pid] += blk.size
277
+ volume_nodes[pid] += bsize
278
278
 
279
279
  ew = edge_weights or {}
280
- for b in range(len(blocks)):
280
+ for b in range(len(blocksizes)):
281
281
  pid = parts[b]
282
282
  for nbr in adj_list.get(b, []):
283
283
  nbr_pid = parts[nbr]
@@ -0,0 +1,6 @@
1
+ from __future__ import absolute_import
2
+ from .import_functions import (
3
+ read_gridpro_to_blocks,
4
+ read_gridpro_connectivity,
5
+ bc_faces_by_type,
6
+ )
@@ -1,5 +1,6 @@
1
- from typing import Dict, List, Tuple, Optional
1
+ from typing import Dict, List, Tuple, Optional, Union, Set
2
2
  import numpy as np
3
+ import pandas as pd
3
4
  from tqdm import tqdm
4
5
  from typing import List, Tuple, Optional, Any
5
6
  from plot3d import Block
@@ -15,19 +16,23 @@ def _parse_header(line: str) -> Optional[Tuple[int,int,int]]:
15
16
  except ValueError:
16
17
  return None
17
18
 
18
- def read_gridpro_to_blocks(filename: str,encoding: str = "utf-8", comment_prefixes: Tuple[str, ...] = ("#", "//")) -> List[Block]:
19
- """_summary_
19
+ def read_gridpro_to_blocks(
20
+ filename: str,
21
+ encoding: str = "utf-8",
22
+ comment_prefixes: Tuple[str, ...] = ("#", "//"),
23
+ ) -> List[Block]:
24
+ """Read a structured GridPro text grid into Plot3D Block objects.
20
25
 
21
26
  Args:
22
- filename (str): _description_
23
- encoding (str, optional): _description_. Defaults to "utf-8".
24
- comment_prefixes (Tuple[str, ...], optional): _description_. Defaults to ("#", "//").
27
+ filename: Path to the GridPro grid file.
28
+ encoding: Encoding to use while reading the text file.
29
+ comment_prefixes: Line prefixes that should be treated as comments.
25
30
 
26
31
  Raises:
27
- ValueError: _description_
32
+ ValueError: If a block does not contain the expected number of floats.
28
33
 
29
34
  Returns:
30
- List[Block]: _description_
35
+ List[Block]: A list of Block objects representing each GridPro block.
31
36
  """
32
37
  blocks = []
33
38
 
@@ -82,10 +87,10 @@ def read_gridpro_connectivity(
82
87
  file_path: str,
83
88
  sb_zero_based_in_file: bool = True, # False if sb1/sb2 are 1-based in file
84
89
  index_zero_based_in_file: bool = True, # False if IMIN..KMAX are 1-based in file
85
- inlet_ids: Optional[List[int]] = None,
86
- outlet_ids: Optional[List[int]] = None,
87
- wall_ids: Optional[List[int]] = None,
88
- symm_slip_ids: Optional[List[int]] = None,
90
+ inlet_ids: Optional[Union[List[int], Dict[str, Any]]] = None,
91
+ outlet_ids: Optional[Union[List[int], Dict[str, Any]]] = None,
92
+ wall_ids: Optional[Union[List[int], Dict[str, Any]]] = None,
93
+ symm_slip_ids: Optional[Union[List[int], Dict[str, Any]]] = None,
89
94
  custom_bc_ids: Optional[Dict[str, List[int]]] = None,
90
95
  ) -> Dict[str, object]:
91
96
  """Parse a GridPro connectivity file into plot3d.connectivity_fast-like structures.
@@ -94,33 +99,45 @@ def read_gridpro_connectivity(
94
99
  ``P pid sb1 sf1 sb2 sf2 fmap L1i L1j L1k H1i H1j H1k L2i L2j L2k H2i H2j H2k pty lbid``
95
100
 
96
101
  Boundary-condition IDs default to GridPro's PTY values but can be overridden
97
- via the provided *_ids lists or extended via ``custom_bc_ids={"name": [ids]}``.
102
+ via the provided *_ids arguments (either a list of PTY ints or
103
+ ``{"name": str, "ids": [...]}``) or extended via
104
+ ``custom_bc_ids={"name": [ids]}``.
98
105
 
99
106
  Args:
100
107
  file_path: Path to the GridPro connectivity file.
101
108
  sb_zero_based_in_file: Set False if ``sb1/sb2`` are 1-based in the file.
102
109
  index_zero_based_in_file: Set False if IMIN..KMAX are 1-based in the file.
103
110
  inlet_ids: PTY ids to treat as inlet; defaults to ``[5]``.
104
- outlet_ids: PTY ids to treat as outlet; defaults to ``[6]``.
105
- wall_ids: PTY ids to treat as wall; defaults to ``[2]``.
106
- symm_slip_ids: PTY ids to treat as symmetry/slip; defaults to ``[4]``.
111
+ Pass a dict ``{"name": "...", "ids": [...]}`` to rename the group, or
112
+ provide a mapping like ``{"Inlet": [...], "Pressure Inlet": [...]}``
113
+ to emit multiple named inlet groups. Each named group remembers its
114
+ base type in the returned ``bc_group`` mapping.
115
+ outlet_ids: PTY ids to treat as outlet; defaults to ``[6]`` (same dict
116
+ options as ``inlet_ids``).
117
+ wall_ids: PTY ids to treat as wall; defaults to ``[2]`` (same dict options).
118
+ symm_slip_ids: PTY ids to treat as symmetry/slip; defaults to ``[4]`` (same dict options).
107
119
  custom_bc_ids: Optional mapping of ``group_name -> [pty ids]`` for any
108
120
  additional boundary-condition groupings (e.g., ``{"cooling": [500]}``).
109
121
 
110
122
  Returns:
111
123
  Dict[str, object]: A dictionary with keys:
112
124
  - ``face_matches``: list of face-pair dictionaries for connected blocks.
113
- - ``outer_faces``: list of faces on the exterior (no neighbor).
114
- - ``bc_group``: mapping of bc name to list of faces (inlet/outlet/etc.).
125
+ - ``all_surfaces``: list of faces on the exterior (no neighbor).
126
+ - ``ungrouped_surfaces``: exterior faces whose PTY does not belong to any BC group.
127
+ - ``bc_group``: mapping of bc name to a metadata dict
128
+ ``{"type": base_type, "pty_ids": [...], "faces": [...]}``. Each
129
+ face dict also includes ``bc_name`` (group label) and ``bc_type``.
115
130
  - ``gif_faces``: list of faces tagged as GIF (pty 12..21 or 1000).
116
131
  - ``periodic_faces``: list of paired periodic faces (pty 3).
117
132
  - ``volume_zones``: list describing volume zone type per superblock.
133
+ - ``blocksizes``: list of per-superblock cell counts (I*J*K).
134
+ - ``patches``: pandas DataFrame of raw patch records.
118
135
 
119
136
  Examples:
120
137
  Basic usage with defaults::
121
138
 
122
139
  data = read_gridpro_connectivity("connectivity.dat")
123
- inlet_faces = data["bc_group"]["inlet"]
140
+ inlet_faces = data["bc_group"]["inlet"]["faces"]
124
141
 
125
142
  Override built-in BC ids and add a custom group::
126
143
 
@@ -129,11 +146,12 @@ def read_gridpro_connectivity(
129
146
  inlet_ids=[5, 105],
130
147
  custom_bc_ids={"cooling_hole1": [500]},
131
148
  )
132
- cooling_faces = data["bc_group"]["cooling_hole1"]
149
+ cooling_faces = data["bc_group"]["cooling_hole1"]["faces"]
133
150
  """
134
151
  # ---------------------------- parsing ----------------------------
135
152
  superblock_ptys: List[int] = []
136
- patches: List[Dict[str, object]] = []
153
+ superblock_sizes: List[int] = []
154
+ patch_rows: List[Dict[str, object]] = []
137
155
 
138
156
  sb_offset = 0 if sb_zero_based_in_file else -1
139
157
  idx_offset = 0 if index_zero_based_in_file else -1 # convert to 0-based if file is 1-based
@@ -175,7 +193,7 @@ def read_gridpro_connectivity(
175
193
  except Exception as e:
176
194
  raise ValueError(f"Failed to parse patch line: {' '.join(tokens)}") from e
177
195
 
178
- patches.append({
196
+ patch_rows.append({
179
197
  "pid": pid, "sb1": sb1, "sf1": sf1, "sb2": sb2, "sf2": sf2,
180
198
  "fmap": fmap,
181
199
  "L1i": L1i, "L1j": L1j, "L1k": L1k, "H1i": H1i, "H1j": H1j, "H1k": H1k,
@@ -192,9 +210,18 @@ def read_gridpro_connectivity(
192
210
  tag = toks[0]
193
211
  if tag == "SB":
194
212
  superblock_ptys.append(int(toks[-2]))
213
+ try:
214
+ I_dim = int(toks[2])
215
+ J_dim = int(toks[3])
216
+ K_dim = int(toks[4])
217
+ superblock_sizes.append(I_dim * J_dim * K_dim)
218
+ except (IndexError, ValueError):
219
+ superblock_sizes.append(0)
195
220
  elif tag == "P":
196
221
  parse_patch(toks)
197
222
 
223
+ patches_df = pd.DataFrame(patch_rows)
224
+
198
225
  # ---------------------------- helpers ----------------------------
199
226
  def face_dict(sb: int, imin: int, jmin: int, kmin: int, imax: int, jmax: int, kmax: int, pty:int=-1) -> Dict[str, int]:
200
227
  # Matches plot3d.connectivity_fast single-face dict shape
@@ -217,66 +244,123 @@ def read_gridpro_connectivity(
217
244
  # ------------------------- build outputs -------------------------
218
245
  # Connections: sb2 != -1 (i.e., not "none") and pty in {1,3}
219
246
  connections: List[Dict[str, Dict[str, int]]] = []
220
- for p in patches:
221
- if p["sb2"] != -1 and (p["pty"] in (1, 3)):
247
+ for p in patches_df.itertuples(index=False):
248
+ if p.sb2 != -1 and (p.pty in (1, 3)):
222
249
  connections.append(
223
250
  pair_dict(
224
- p["sb1"], (p["L1i"], p["L1j"], p["L1k"], p["H1i"], p["H1j"], p["H1k"]), # type: ignore
225
- p["sb2"], (p["L2i"], p["L2j"], p["L2k"], p["H2i"], p["H2j"], p["H2k"]), # type: ignore
251
+ p.sb1, (p.L1i, p.L1j, p.L1k, p.H1i, p.H1j, p.H1k),
252
+ p.sb2, (p.L2i, p.L2j, p.L2k, p.H2i, p.H2j, p.H2k),
226
253
  )
227
254
  )
228
255
 
229
- # Outer faces: sb2 == -1 (means '0' in file when sb_zero_based_in_file=False; or literal 0 if already zero-based)
230
- # To cover both cases, treat original token value 0 as "no neighbor".
231
- # Since we already applied sb_offset, "no neighbor" now appears as -1.
232
- outer_faces: List[Dict[str, int]] = []
233
- pty_exclude = [6,5,4,2]
234
- for p in patches:
235
- if p["sb2"] == -1 and (p["pty"] not in pty_exclude):
236
- outer_faces.append(
237
- face_dict(p["sb1"], p["L1i"], p["L1j"], p["L1k"], p["H1i"], p["H1j"], p["H1k"], p["pty"]) # type: ignore
238
- )
239
-
240
256
  # Boundary-condition groups (by pty)
241
- def bc_faces_for(pty_vals: List[int]) -> List[Dict[str, int]]:
257
+ def bc_faces_for(name: str, pty_vals: List[int], bc_type: str) -> List[Dict[str, int]]:
242
258
  targets = set(pty_vals)
243
- return [
244
- face_dict(p["sb1"], p["L1i"], p["L1j"], p["L1k"], p["H1i"], p["H1j"], p["H1k"], p["pty"]) # type: ignore
245
- for p in patches if p["pty"] in targets
246
- ]
247
-
248
- bc_group = {
249
- "inlet": bc_faces_for(inlet_ids or [5]),
250
- "outlet": bc_faces_for(outlet_ids or [6]),
251
- "symm_slip": bc_faces_for(symm_slip_ids or [4]),
252
- "wall": bc_faces_for(wall_ids or [2]),
253
- }
259
+ faces: List[Dict[str, int]] = []
260
+ for p in patches_df.itertuples(index=False):
261
+ if p.pty in targets:
262
+ face = face_dict(p.sb1, p.L1i, p.L1j, p.L1k, p.H1i, p.H1j, p.H1k, p.pty)
263
+ face["bc_name"] = name # type: ignore
264
+ face["bc_type"] = bc_type # type: ignore
265
+ faces.append(face)
266
+ return faces
267
+
268
+ def normalize_bc_arg(
269
+ arg: Optional[Union[List[int], Dict[str, Any]]],
270
+ bc_type: str,
271
+ default_ids: List[int],
272
+ ) -> List[Tuple[str, List[int]]]:
273
+ def coerce_ids(values: Any) -> List[int]:
274
+ if values is None:
275
+ return []
276
+ # Allow nested {"ids": [...]} dictionaries to be passed directly
277
+ if isinstance(values, dict):
278
+ return coerce_ids(values.get("ids"))
279
+ if isinstance(values, (list, tuple, set)):
280
+ return [int(v) for v in values if v is not None]
281
+ return [int(values)]
282
+
283
+ if arg is None:
284
+ return [(bc_type, coerce_ids(default_ids))]
285
+
286
+ if isinstance(arg, dict):
287
+ # Support legacy {"name": str, "ids": [...]} as well as
288
+ # {"CustomName": [...], "AnotherName": [...]} mappings.
289
+ if "ids" in arg or "name" in arg:
290
+ ids = coerce_ids(arg.get("ids", default_ids))
291
+ name = str(arg.get("name", bc_type) or bc_type)
292
+ return [(name, ids)]
293
+
294
+ normalized: List[Tuple[str, List[int]]] = []
295
+ for name, ids_val in arg.items():
296
+ ids_list = coerce_ids(ids_val)
297
+ if ids_list:
298
+ normalized.append((str(name), ids_list))
299
+ return normalized
300
+
301
+ ids = coerce_ids(arg)
302
+ return [(bc_type, ids)]
303
+
304
+ bc_group: Dict[str, Dict[str, Any]] = {}
305
+ grouped_bc_ids: Set[int] = set()
306
+
307
+ def add_bc_group(name: str, ids: List[int], bc_type: str) -> None:
308
+ normalized_ids = sorted({int(i) for i in ids if i is not None})
309
+ faces = bc_faces_for(name, normalized_ids, bc_type) if normalized_ids else []
310
+ if not faces:
311
+ return
312
+ bc_group[name] = {
313
+ "type": bc_type,
314
+ "pty_ids": normalized_ids,
315
+ "faces": faces,
316
+ }
317
+ grouped_bc_ids.update(normalized_ids)
318
+
319
+ for bc_type, group in [
320
+ ("inlet", normalize_bc_arg(inlet_ids, "inlet", [5])),
321
+ ("outlet", normalize_bc_arg(outlet_ids, "outlet", [6])),
322
+ ("symm_slip", normalize_bc_arg(symm_slip_ids, "symm_slip", [4])),
323
+ ("wall", normalize_bc_arg(wall_ids, "wall", [2])),
324
+ ]:
325
+ for name, ids in group:
326
+ add_bc_group(name, ids, bc_type)
254
327
 
255
328
  # add any user-defined boundary groups keyed by name -> list of pty ids
256
329
  if custom_bc_ids:
257
330
  for name, ids in custom_bc_ids.items():
258
- if ids:
259
- bc_group[name] = bc_faces_for(ids)
260
-
331
+ add_bc_group(name, ids or [], name)
332
+
333
+ # Outer faces: sb2 == -1 (means '0' in file when sb_zero_based_in_file=False; or literal 0 if already zero-based)
334
+ # To cover both cases, treat original token value 0 as "no neighbor".
335
+ # Since we already applied sb_offset, "no neighbor" now appears as -1.
336
+ all_surfaces: List[Dict[str, int]] = []
337
+ ungrouped_surfaces: List[Dict[str, int]] = []
338
+ for p in patches_df.itertuples(index=False):
339
+ if p.sb2 == -1:
340
+ face = face_dict(p.sb1, p.L1i, p.L1j, p.L1k, p.H1i, p.H1j, p.H1k, p.pty)
341
+ all_surfaces.append(face)
342
+ if p.pty not in grouped_bc_ids:
343
+ ungrouped_surfaces.append(face.copy())
344
+
261
345
  # Periodic faces: pty == 3 (explicit pairs)
262
346
  periodic_faces: List[Dict[str, Dict[str, int]]] = []
263
- for p in patches:
264
- if p["pty"] == 3 and p["sb2"] != -1:
347
+ for p in patches_df.itertuples(index=False):
348
+ if p.pty == 3 and p.sb2 != -1:
265
349
  periodic_faces.append(
266
350
  pair_dict(
267
- p["sb1"], (p["L1i"], p["L1j"], p["L1k"], p["H1i"], p["H1j"], p["H1k"]), # type: ignore
268
- p["sb2"], (p["L2i"], p["L2j"], p["L2k"], p["H2i"], p["H2j"], p["H2k"]), # type: ignore
351
+ p.sb1, (p.L1i, p.L1j, p.L1k, p.H1i, p.H1j, p.H1k),
352
+ p.sb2, (p.L2i, p.L2j, p.L2k, p.H2i, p.H2j, p.H2k),
269
353
  )
270
354
  )
271
355
 
272
356
  # GIF faces grouped by sf1 for pty in 12..21 or == 1000
273
357
  gif_faces: List[Dict[str, int]] = []
274
- for p in patches:
358
+ for p in patches_df.itertuples(index=False):
275
359
  #? This is how we identify gifs inside of a grid pro connectivity file.
276
360
  #? If the PTY is between 12 and 21 these are gifs
277
- if (12 <= p["pty"] <= 21) or (p["pty"] == 1000): # type: ignore
278
- face_temp = face_dict(p["sb1"], p["L1i"], p["L1j"], p["L1k"], p["H1i"], p["H1j"], p["H1k"]) # type: ignore
279
- face_temp["id"] = p["pty"] # type: ignore
361
+ if (12 <= p.pty <= 21) or (p.pty == 1000):
362
+ face_temp = face_dict(p.sb1, p.L1i, p.L1j, p.L1k, p.H1i, p.H1j, p.H1k)
363
+ face_temp["id"] = p.pty # type: ignore
280
364
  gif_faces.append(face_temp)
281
365
 
282
366
  # Volume zones (unique superblock ptys; odd→fluid, even→solid) with contiguous ids
@@ -293,9 +377,59 @@ def read_gridpro_connectivity(
293
377
 
294
378
  return {
295
379
  "face_matches": connections,
296
- "outer_faces": outer_faces,
380
+ "all_surfaces": all_surfaces,
381
+ "ungrouped_surfaces": ungrouped_surfaces,
297
382
  "bc_group": bc_group,
298
383
  "gif_faces": gif_faces,
299
384
  "periodic_faces": periodic_faces,
300
385
  "volume_zones": volume_zones,
386
+ "blocksizes": superblock_sizes,
387
+ "patches": patches_df,
301
388
  }
389
+
390
+
391
+ def bc_faces_by_type(
392
+ bc_group: Dict[str, Any],
393
+ bc_type: str,
394
+ *,
395
+ unique: bool = False,
396
+ ) -> List[Dict[str, int]]:
397
+ """Collect faces for a specific boundary-condition type across all named groups.
398
+
399
+ Args:
400
+ bc_group: Mapping returned by :func:`read_gridpro_connectivity` (supports
401
+ both the legacy ``{name: [faces]}`` and the new
402
+ ``{name: {"type": ..., "faces": [...]}}`` shapes).
403
+ bc_type: Base BC type (``"inlet"``, ``"outlet"``, ``"wall"``, ``"symm_slip"``).
404
+ unique: When True, keep only the first face per unique ``id``.
405
+
406
+ Returns:
407
+ List of face dictionaries belonging to the requested base type.
408
+ """
409
+ faces: List[Dict[str, int]] = []
410
+ seen: Set[int] = set()
411
+ target = (bc_type or "").lower()
412
+
413
+ for entry in bc_group.values():
414
+ entry_type = None
415
+ entry_faces: List[Dict[str, int]] = []
416
+
417
+ if isinstance(entry, dict) and "faces" in entry:
418
+ entry_faces = entry.get("faces", []) or []
419
+ entry_type = entry.get("type") or entry.get("bc_type")
420
+ elif isinstance(entry, list):
421
+ entry_faces = entry
422
+ else:
423
+ continue
424
+
425
+ for face in entry_faces:
426
+ ftype = entry_type or face.get("bc_type") or face.get("bc_name")
427
+ if not isinstance(ftype, str) or ftype.lower() != target:
428
+ continue
429
+ fid = face.get("id")
430
+ if unique and isinstance(fid, int):
431
+ if fid in seen:
432
+ continue
433
+ seen.add(fid)
434
+ faces.append(face)
435
+ return faces
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "plot3d"
3
- version = "1.7.7"
3
+ version = "1.7.9"
4
4
  description = "Plot3D python utilities for reading and writing and also finding connectivity between blocks"
5
5
  authors = ["Paht Juangphanich <paht.juangphanich@nasa.gov>"]
6
6
 
@@ -1,2 +0,0 @@
1
- from __future__ import absolute_import
2
- from .import_functions import read_gridpro_to_blocks, read_gridpro_connectivity
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes