Skip to content

Changelog

[Unreleased]

Added

  • CH hybrid topology (shore mesh --topology ch, shore.mesh.CHTopology): airfoil/hydrofoil analogue of OH — 12-block (sharp TE) or 14-block (blunt TE) conforming hybrid that extends the C-grid wrap with H-channel sub-blocks bridging the wrap to a chord/normal-aligned far-field frame. The 5 k_hi-side channels (chan_upper, chan_body_top/front/bot, chan_lower) are SHARED-by-view with their wrap partners (single contiguous numpy buffer of depth nk_wrap + nk_channel - 1 per wrap segment); the 4 (or 5) outflow sub-blocks chan_outflow_top_upper / wake_upper / wake_lower / top_lower (+ wake_base for blunt) are conformal SHARED to the wrap's wake far-tip faces (upper.i_lo, lower.i_hi, wake_base.i_lo) with identity AxisMap and bit-exact face-data copy in fill_channel_blocks. C1 spacing across the wrap-channel seam (c1_seam=True, default) inherits the OH design: each k_hi-side channel's first cell is pinned to the wrap's last marching step at every (i, j). New CLI flags: --ch-chord-min, --ch-chord-max, --ch-normal-min, --ch-normal-max, --nk-channel, --k-law-channel, --k-beta-channel, --c1-seam-channel/--no-c1-seam-channel. New Python API: shore.mesh.CHTopology + shore.topology.ch_channel.{CHFrame, resolve_frame, outer_quads_for_cgrid}. See CH hybrid topology and examples/09-ch-hybrid/.
  • Conformal C-grid wing-tip caps (shore mesh --topology cgrid --cap-tips {jlo|jhi|both}): close a finite-wing C-grid mesh with bit-coincident cap blocks integrated into the C-grid build. When cap_tips != "none": (a) the body's TE is rounded over its full span by cap_te_thickness (or cap_te_thickness_frac * chord, default 0.2%); (b) the C-grid is auto-promoted from 3-block to 4-block (with wake_base); (c) 6 cap blocks per requested tip are emitted (cap_<tip>_le, cap_<tip>_upper, cap_<tip>_lower, cap_<tip>_te_upper, cap_<tip>_te_lower, cap_<tip>_wake_base) with airfoil-wall nodes built from the same rounded body cross-section so the cap-body interface is bit-coincident, no chimera fringe needed. At --cap-tips both the total block count is 16 (4 C-grid + 12 cap). Replaces the prior chimera-fringe-only path for cap construction. New CLI flags on shore mesh: --cap-tips, --cap-te-thickness, --cap-te-thickness-frac, --cap-le-frac, --cap-te-frac, --cap-tip-inset, --cap-n-cap-j, --cap-j-spacing-cgrid, --cap-j-beta-cgrid, --cap-nk. Auto-promote and body-rounding announce themselves with a stderr note. See C-grid topology — Tip caps and examples/06-c-grid/visualize_naca0012_cap_tip.py.
  • Standalone C-grid wing-tip caps (shore.mesh.CGridTipCap + shore cap-tip CLI): an alternative chimera-fringe-overlap cap path that produces an independent overset cap component for a body wrap that stays sharp (no body-rounding). Useful when adding caps to an already-meshed C-grid or when chimera interpolation at the cap-body seam is acceptable. 5 cap blocks per tip with the same internal H-fill structure; auto-rounds the cap's local cross-section only (default 0.2% chord) — the body wrap is left untouched. See C-grid topology — Chimera-fringe cap.
  • Body-TE rounding helper (shore.surface.cgrid.round_body_te_in_surface): mutates a C-grid k=0 surface array in place, splitting i=0 and i=ni-1 columns apart by te_thickness along the airfoil-thickness direction at every spanwise station. Used internally by the conformal cap path; can be called directly for non-cap workflows that need a synthetic blunt TE on a sharp body.
  • OH hybrid topology (shore.mesh.OHTopology): 12-block conforming hybrid that extends the cubed-sphere wrap with a 6-block structured H-channel out to a user-supplied axis-aligned bounding box. Wrap and channel are SHARED-by-view (one numpy buffer per pair, bit-exact seam at no copy). Wrap is built with theta_cap = pi/4 and phi_offset = pi/4 so each of the 6 wrap blocks faces exactly one cube face; channel-channel lateral seams snap to cube edges. Channel-block construction is deferred until after marching: Mesh.march() runs the wrap then calls OHTopology.fill_channel_blocks(blocks) to fill channel interiors by ruled-surface frustum interpolation. C1 spacing across the wrap-channel seam (c1_seam=True, default): channel's first cell is pinned to the wrap's last marching step at every (i, j), then cells grow geometrically out to the box face — no second-cell jump, smooth continuation of the wrap's hyperbolic march. Python API only at this milestone; shore mesh CLI integration (--topology oh-hybrid) is on the roadmap. See OH hybrid topology and examples/08-oh-hybrid/.
  • shore.profiles module + shore profile naca CLI command: generate 2D NACA airfoil section coordinates analytically, write as Selig-format .dat files (UIUC-compatible). Supports the full 4-digit family (naca4) and the 5-digit family with both standard and reflexed mean lines (naca5); the naca dispatcher picks the right one by digit count. Pure NumPy, no external deps. See Algorithm — airfoil profiles.
  • shore.shapes module + shore body CLI command: generate 3D body STL files. Analytic primitives (sphere, cylinder, torus, box, capsule) wrap trimesh.creation; extrude_section lifts any 2D Selig profile to a 3D wing with optional taper, sweep, and twist. shore body extrude consumes Selig .dat files (from shore profile naca or any UIUC-style source). Default open ends are intentionally non-watertight so the C-grid topology's per-station slicer doesn't trip on end-cap triangles; pass --end-caps for a watertight body.
  • shore --version flag: prints shore <version> and exits.
  • install.sh curl | bash installer for Linux / macOS / WSL: detects Python ≥ 3.11, installs pipx if missing (apt on Debian/Ubuntu, pip elsewhere — handles PEP 668), then pipx install shore-mesh. Supports --vis for the pyvista extras and --version X.Y.Z to pin.
  • --adjacency-json PATH flag on shore mesh and shore primitive {box, annulus, flat-caps}: emit the .adjacency.json sidecar at generation time. Closes the CLI gap that previously forced users to drop into Python to feed shore cc-par for cubed-sphere walls and flat-caps backgrounds. Single-block primitives still emit a sidecar (no SHARED faces) for downstream-tool consistency.
  • examples/05-chimera-assembly/chimera_assembly.sh: bash equivalent of chimera_assembly.py, using only shore CLI calls. Produces a byte-identical assembly.{grd, cc.par, proc.input} triple. The canonical reference for end-to-end Chimera assembly via shell.
  • examples/06-c-grid/visualize_naca0012.sh: bash equivalent of visualize_naca0012.py. Chains shore profile naca -> shore body extrude -> shore mesh --topology cgrid -> shore export to build a NACA 0012 C-grid + ParaView .vtm from scratch, no Python imports.
  • shore export now writes a single-block .vtm (instead of a .vts) when given one input and an --output ending in .vtm. Lets single-block topologies (O-grid, C-grid) emit a MultiBlock for ParaView consistency with multi-block topologies.
  • 3-block C-grid topology (Xall-correct sharp-TE layout): CGridTopology now emits three hex blocks (upper / body / lower) wired with three SHARED face seams — upper-TE column, lower-TE column, and the wake cut. The wake-cut seam carries an ((i, True), (j, False)) AxisMap that records the i-direction reversal between upper and lower wake — the structurally-honest replacement for the 1-block layout's silent i-direction self-overlap that Xall couldn't apply BCs to. Marcher uses seam-aware ghost rows at the TE seams (per the cubed-sphere kernel).
  • 4-block C-grid topology (Xall-correct blunt-TE layout): adds a fourth wake_base block W bridging the TE-thickness gap between upper and lower wake. W's i_hi face is the bluff-base WALL — the airfoil's TE wall that the 1-block and 3-block layouts cannot represent. W is built once at construction time by linear interpolation across the gap and is not marched.
  • --cgrid-blocks {1,3,4} flag on shore mesh --topology cgrid: 3 (default, sharp-TE), 4 (blunt-TE), or 1 (legacy single-block, opt-in).
  • CGridK0Result.per_block_layers(): returns three numpy views (upper / body / lower) into the concatenated nodes array. Used internally by 3- and 4-block builders; exposed publicly for downstream pipelines that want the per-block segmentation without re-running the k=0 pipeline.
  • H-grid primitives (shore.primitive.HGridRectMesh / HGridTubeMesh + shore primitive h-grid-rect / h-grid-tube CLI): single-block H-grids for internal flow without an internal body. h-grid-rect is a Cartesian duct (no singular axis); h-grid-tube is a polar cross-section with a centerline singular axis (j-periodic, lat-lon style). Default BCs match the typical internal-flow configuration: streamwise faces FREE (inflow / outflow), cross-section faces WALL. Use case: prismatic ducts, simple SRM combustion chambers; foundation for future OH / CH hybrids and the planned STL-driven H-grid (variable-cross-section ducts). See H-grid topology and examples/07-h-grid/.

Changed

  • C-grid topology default switched from 1 block to 3 blocks. shore mesh --topology cgrid (and CGridTopology(...) from Python) now emit the Xall-correct 3-block layout by default and write per-block .geo files (<stem>_upper.geo, <stem>_body.geo, <stem>_lower.geo) instead of a single concatenated <stem>.geo. Migration: pass --cgrid-blocks 1 to opt into the legacy single-block layout. Existing tests that covered the single-block path are kept; the new tests in TestThreeBlockCGrid and TestFourBlockCGrid cover the new defaults.
  • shore.surface.cgrid.slice_by_span for blunt TE: the body wrap polyline now terminates at the lower-TE node (excluding the TE-base segment) when the input mesh has a blunt TE. Required for the 4-block layout to keep body.i_hi at the lower-TE so W can mesh the TE base separately. The 1-block legacy path is unaffected (still walks the full closed polyline).
  • The C-grid example (examples/06-c-grid/visualize_naca0012.py) and the naca0012_stl test fixture both now build their NACA 0012 STL via shore.profiles + shore.shapes instead of inline polynomial math. The two near-duplicate copies of the NACA polynomial are gone.

[0.1.0] — 2026-04-30

Added

  • Full mesh-generation pipeline driven by shore mesh: STL projection + hyperbolic normal extrusion → 6-block cubed-sphere or single-block O-grid, with per-layer Laplacian smoothing and Jacobian guard.
  • Sphere-projection surface remeshing via BVH-accelerated ray casting (trimesh + rtree).
  • Xall .geo reader/writer (ASCII, Fortran column-major ordering).
  • Rich progress bar with live jmin and step display.
  • VitePress documentation site (this site).
  • Per-edge spacing data model: Edge.first_step / Edge.last_step fields, Spacing1D(law='pinned'), public pinned_distribution (lifted from surface/equator.py).
  • Per-block k-spacing override through Mesh.march() — replace the four k-edges' Spacing1D instance to override (ds, growth). Identity-check resolver enforces a single schedule across all blocks (k-coupled SHARED / DIRICHLET seams). Works on cubed-sphere and O-grid topologies.
  • Per-edge i/j/k on BoxMesh and i/j on AnnularCylinderMesh via edge_pins kwarg; constructor builds a transfinite-interpolation (TFI) blend of the four parallel edges' distributions when they disagree, collapses to legacy tensor-product when they don't.
  • Examples: per_edge_ij_box.py, per_edge_k_spacing_cubed_sphere.py (renamed from per_edge_k_spacing.py), per_edge_k_spacing_ogrid.py.
  • shore cc-par command + shore.io.cc_par module: translate the SHORE .adjacency.json sidecar into the Xall cc.par chimera pre-processor input format. WALL/FREE/SHARED/DIRICHLET map to Xall BC codes; SHARED/DIRICHLET orientation codes are derived from each face's axis_map (no per-topology lookup tables). BlockMeta dataclass exposes per-block level / group / priority / comment + free_face_bc for body-fitted (40) vs background (-9, -3, -4) meshes.
  • shore.mesh.face.AxisMap: frozen dataclass describing how a face's along-seam axes map onto its partner's along-seam axes (axis correspondence + per-axis flip flags). Required on every connection face; _set_face_bc validates it against the partner face name at wiring time so a topology that omits the field fails loudly instead of silently producing wrong cc.par downstream.
  • shore.mesh.face.FACE_ALONG_AXES: canonical along-axis order per face name, used for AxisMap interpretation.
  • shore grd-merge + shore.io.grd.merge_grd: concatenate two-or-more .grd files for an Xall multi-mesh job (e.g. wall + background as one 11-block run). Block payloads are passed through byte-for-byte; the header block count is rewritten to the sum.
  • shore cc-par-merge + shore.io.cc_par.merge_cc_par: concatenate two-or-more cc.par files with the right block-index and patch-family shifts so the merged file is internally consistent. Header taken from inputs[0] (with --grd overriding header line 1); block comments auto-prefixed with each input's stem (background.center, wall.sub0, …). Header drift between inputs warns but does not block. The matching grd-merge and cc-par-merge calls must use the same input ordering.
  • shore.io.boxes.Box + cc-par --boxes flag + write_cc_par(boxes=...): emit the cc.par boxes section as type-9 hexahedra. Each box references a block by label (resolved to a 1-based index at write time); construction validates non-degenerate, planar-face, positive-volume hexahedra. The merger concatenates boxes across inputs and shifts their associated-block index by the cumulative block count of the preceding inputs, mirroring the patch-index shift.
  • shore.boxes.boxes_from_stl + shore boxes-from-stl CLI: generate marker boxes from a body STL. Three methods — voxel-fill (default; voxelises the body's AABB and greedy-merges body-overlapping voxels into slab-shaped AABBs that cover the whole body — interior + surface — while keeping every box vertex within max_outward_gap of the surface), aabb and obb (single-box, best-effort with UserWarning when the budget is exceeded). voxel-fill requires a watertight STL; aabb / obb do not. Output is a JSON list compatible with shore cc-par --boxes.
  • shore.io.vtk.write_boxes_vtm + shore boxes-export CLI: ParaView visualisation for marker boxes. Each box becomes one vtkUnstructuredGrid cell (VTK_HEXAHEDRON) with vertex order remapped from SHORE's canonical (i, j, k) bit layout to VTK's hex winding; cell data carries associated_block (string) and box_index (int) for colouring / filtering. write_caps_vtm also gains a boxes= kwarg that layers boxes onto the per-topology .vtm so wall + near-mesh + boxes appear in one MultiBlock.
  • shore proc-input + shore proc-input-merge + shore.io.proc_input: write the Xall proc.input runtime work-distribution descriptor from a .grd. Block ordering and weights come from the .grd (cell counts ni*nj*nk); ranks are assigned by greedy weight-balanced descent across --np MPI ranks (the same algorithm LoadBalance and overset-exploded use). --meta accepts inline JSON or a file path with per-block proc / group / body overrides keyed by 1-based block index. A balance summary is printed on stderr for --np > 1; imbalance > 5% triggers a warning recommending shore balance. The merger concatenates multiple proc.input files with block-index shifts mirroring grd-merge and cc-par-merge; group / body / proc columns pass through verbatim. shore.io.grd.read_grd_metadata is a new public helper used to read just the .grd header without decoding the coordinate payload.
  • shore balance + shore.balance.plan_balance: iterative split planner for MPI load balancing. Reads a .grd (and optionally an .adjacency.json), iteratively schedules cuts on the heaviest block at its cell-count midpoint until the predicted post-split imbalance drops below the user's tolerance (default 5%) or --max-iterations (default 200). Topology-aware: with adjacency, it prefers axes free of perpendicular SHARED / DIRICHLET partners (k-axis on body-fitted topologies) and emits paired co-splits when a coupled axis is required, producing plans that pass shore split's validation. Constrained to one axis per original block to match shore split v1's per-block-per-call contract. Output is a split.toml ready to feed to shore split; plan-then-execute keeps every step reviewable.
  • C-grid topology for sharp-TE bodies (airfoils, hydrofoils): shore.mesh.CGridTopology + shore.surface.cgrid + shore mesh --topology cgrid. Single-block hexahedral mesh that wraps the body and adds two wake branches downstream of the trailing edge, with the i-axis sweeping upper-wake-tip → upper-TE → suction → LE → pressure → lower-TE → lower-wake-tip as one continuous parametric line. Pipeline: detect_te_edges (dihedral-classification on triangle edges, span-axis filter, chord-extreme filter, sharp + blunt detection) → slice_by_span (trimesh.mesh_plane with arclength-fraction resampling so every span station shares a common parametrisation) → build_c_curve (geometric-spaced wake branches in the freestream direction) → build_cgrid_k0 (lifts the per-station 2D C-curves back into a 3D (ni_total, n_stations, 3) k=0 layer) → standard hyperbolic march with both axes non-periodic. New CLI flags: --ni-body, --ni-wake, --n-stations, --wake-length, --wake-growth, --span-axis, --te-dihedral-deg. Tested end-to-end on NACA 0012 (51 dedicated tests + CLI smoke).
  • Example: examples/06-c-grid/visualize_naca0012.py — standalone NACA 0012 STL builder + C-grid generator + ParaView export.

Changed

  • Adjacency JSON bumped from v1 to v2. Every connection face (SHARED / DIRICHLET / PERIODIC) now carries an axis_map field describing how its along-seam axes map onto the partner face's along-seam axes. v1 sidecars are rejected with a regenerate-the-sidecar message. Sidecars are derived artefacts — re-run the topology / split that produced them.
  • cc.par orientation codes derived from axis_map (no per-topology tables). The previously hard-coded _CAP_EQUATOR_PARTNER table in shore.io.cc_par is gone. Orientation codes are now derived purely from the face's axis_map plus the partner face name; any topology that stamps valid axis_map values on its seams produces correct cc.par output without writer changes. As a side effect, same-axis seams between two blocks now emit asymmetric codes (135 on the *_lo side, 145 on the *_hi side) — both correctly describe the same seam from each block's own frame.
  • Cubed-sphere cap-equator partners point at cap lateral faces. The runtime Face.partner for a sub-block's i_lo / i_hi is now the cap's matching lateral face (i_lo / i_hi / j_lo / j_hi), not the cap's k_lo. This matches the geometric face-to-face seam and lets a single rule cover both sub-side and cap-side patches in the cc.par writer.

Fixed

  • FlatCapsCylinder adjacency was empty. The 5-block flat-caps cylinder primitive built its blocks but never wired the 8 SHARED seams (4 center↔sub + 4 sub↔sub corners), so background.adjacency.json had every face as FREE / WALL and background.cc.par emitted zero connection patches. All 8 seams are now wired with the correct partners and axis_map values.
  • boxes_from_stl left the body surface uncovered. Voxel-fill only emitted voxels with centre strictly inside the body, leaving an uncovered shell of width up to side/2 along the surface — body protrusions like a sphere's polar and equatorial extremes sat outside the box union, defeating chimera hole-cutting on those cells. The inclusion test changed to signed_distance(centre) > -half_diag so body-shell voxels are included; voxel side halved to max_outward_gap / sqrt(3) keeps the corner-gap budget. A subsequent greedy AABB merge pass then collapses the cubic seed grid into slab-shaped boxes (~80x fewer on a sphere at gap=0.30) while preserving both invariants.
  • boxes_from_stl ignored the corner-fit constraint. The old outward_offset parameter expanded the OBB axially, which controls face-centre gap but not corner gap — for a sphere the corners stuck out by r * (sqrt(3) - 1) past the body, often outside the body's near-mesh.

Documented

  • Per-edge spacing section in algorithm/spacing-laws.md with the full support table by topology / primitive.
  • topologies/cubed-sphere.md, topologies/o-grid.md, topologies/primitives.md, reference/primitive.md updated with per-edge guidance and the structural reasons each axis is or isn't wired.
  • New reference/cc-par.md covering the shore cc-par command, BC-code mapping, orientation-digit decoder, and the cubed-sphere cap-equator orientation table.
  • reference/split.md and reference/cc-par.md updated for the v2 adjacency JSON schema and axis_map-driven orientation codes.

Released under the MIT License.