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 depthnk_wrap + nk_channel - 1per wrap segment); the 4 (or 5) outflow sub-blockschan_outflow_top_upper / wake_upper / wake_lower / top_lower(+wake_basefor 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 infill_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 andexamples/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. Whencap_tips != "none": (a) the body's TE is rounded over its full span bycap_te_thickness(orcap_te_thickness_frac * chord, default 0.2%); (b) the C-grid is auto-promoted from 3-block to 4-block (withwake_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 boththe total block count is 16 (4 C-grid + 12 cap). Replaces the prior chimera-fringe-only path for cap construction. New CLI flags onshore 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 andexamples/06-c-grid/visualize_naca0012_cap_tip.py. - Standalone C-grid wing-tip caps (
shore.mesh.CGridTipCap+shore cap-tipCLI): 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 byte_thicknessalong 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 withtheta_cap = pi/4andphi_offset = pi/4so 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 callsOHTopology.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 meshCLI integration (--topology oh-hybrid) is on the roadmap. See OH hybrid topology andexamples/08-oh-hybrid/. shore.profilesmodule +shore profile nacaCLI command: generate 2D NACA airfoil section coordinates analytically, write as Selig-format.datfiles (UIUC-compatible). Supports the full 4-digit family (naca4) and the 5-digit family with both standard and reflexed mean lines (naca5); thenacadispatcher picks the right one by digit count. Pure NumPy, no external deps. See Algorithm — airfoil profiles.shore.shapesmodule +shore bodyCLI command: generate 3D body STL files. Analytic primitives (sphere,cylinder,torus,box,capsule) wraptrimesh.creation;extrude_sectionlifts any 2D Selig profile to a 3D wing with optional taper, sweep, and twist.shore body extrudeconsumes Selig.datfiles (fromshore profile nacaor 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-capsfor a watertight body.shore --versionflag: printsshore <version>and exits.install.shcurl | bashinstaller for Linux / macOS / WSL: detects Python ≥ 3.11, installspipxif missing (apt on Debian/Ubuntu, pip elsewhere — handles PEP 668), thenpipx install shore-mesh. Supports--visfor the pyvista extras and--version X.Y.Zto pin.--adjacency-json PATHflag onshore meshandshore primitive {box, annulus, flat-caps}: emit the.adjacency.jsonsidecar at generation time. Closes the CLI gap that previously forced users to drop into Python to feedshore cc-parfor 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 ofchimera_assembly.py, using onlyshoreCLI calls. Produces a byte-identicalassembly.{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 ofvisualize_naca0012.py. Chainsshore profile naca->shore body extrude->shore mesh --topology cgrid->shore exportto build a NACA 0012 C-grid + ParaView.vtmfrom scratch, no Python imports.shore exportnow writes a single-block.vtm(instead of a.vts) when given one input and an--outputending 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):
CGridTopologynow 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_baseblock W bridging the TE-thickness gap between upper and lower wake. W'si_hiface 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 onshore 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 concatenatednodesarray. 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-tubeCLI): single-block H-grids for internal flow without an internal body.h-grid-rectis a Cartesian duct (no singular axis);h-grid-tubeis 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 andexamples/07-h-grid/.
Changed
- C-grid topology default switched from 1 block to 3 blocks.
shore mesh --topology cgrid(andCGridTopology(...)from Python) now emit the Xall-correct 3-block layout by default and write per-block.geofiles (<stem>_upper.geo,<stem>_body.geo,<stem>_lower.geo) instead of a single concatenated<stem>.geo. Migration: pass--cgrid-blocks 1to opt into the legacy single-block layout. Existing tests that covered the single-block path are kept; the new tests inTestThreeBlockCGridandTestFourBlockCGridcover the new defaults. shore.surface.cgrid.slice_by_spanfor 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 keepbody.i_hiat 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 thenaca0012_stltest fixture both now build their NACA 0012 STL viashore.profiles+shore.shapesinstead 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
.georeader/writer (ASCII, Fortran column-major ordering). - Rich progress bar with live
jminand step display. - VitePress documentation site (this site).
- Per-edge spacing data model:
Edge.first_step/Edge.last_stepfields,Spacing1D(law='pinned'), publicpinned_distribution(lifted fromsurface/equator.py). - Per-block k-spacing override through
Mesh.march()— replace the four k-edges'Spacing1Dinstance 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
BoxMeshand i/j onAnnularCylinderMeshviaedge_pinskwarg; 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 fromper_edge_k_spacing.py),per_edge_k_spacing_ogrid.py. shore cc-parcommand +shore.io.cc_parmodule: translate the SHORE.adjacency.jsonsidecar into the Xallcc.parchimera pre-processor input format. WALL/FREE/SHARED/DIRICHLET map to Xall BC codes; SHARED/DIRICHLET orientation codes are derived from each face'saxis_map(no per-topology lookup tables).BlockMetadataclass exposes per-block level / group / priority / comment +free_face_bcfor 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_bcvalidates 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 forAxisMapinterpretation.shore grd-merge+shore.io.grd.merge_grd: concatenate two-or-more.grdfiles 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-morecc.parfiles with the right block-index and patch-familyshifts so the merged file is internally consistent. Header taken frominputs[0](with--grdoverriding 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 matchinggrd-mergeandcc-par-mergecalls must use the same input ordering.shore.io.boxes.Box+cc-par --boxesflag +write_cc_par(boxes=...): emit thecc.parboxes 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-stlCLI: 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 withinmax_outward_gapof the surface),aabbandobb(single-box, best-effort withUserWarningwhen the budget is exceeded).voxel-fillrequires a watertight STL;aabb/obbdo not. Output is a JSON list compatible withshore cc-par --boxes.shore.io.vtk.write_boxes_vtm+shore boxes-exportCLI: ParaView visualisation for marker boxes. Each box becomes onevtkUnstructuredGridcell (VTK_HEXAHEDRON) with vertex order remapped from SHORE's canonical(i, j, k)bit layout to VTK's hex winding; cell data carriesassociated_block(string) andbox_index(int) for colouring / filtering.write_caps_vtmalso gains aboxes=kwarg that layers boxes onto the per-topology.vtmso wall + near-mesh + boxes appear in one MultiBlock.shore proc-input+shore proc-input-merge+shore.io.proc_input: write the Xallproc.inputruntime work-distribution descriptor from a.grd. Block ordering and weights come from the.grd(cell countsni*nj*nk); ranks are assigned by greedy weight-balanced descent across--npMPI ranks (the same algorithm LoadBalance and overset-exploded use).--metaaccepts inline JSON or a file path with per-blockproc/group/bodyoverrides keyed by 1-based block index. A balance summary is printed on stderr for--np > 1; imbalance > 5% triggers a warning recommendingshore balance. The merger concatenates multipleproc.inputfiles with block-index shifts mirroringgrd-mergeandcc-par-merge;group/body/proccolumns pass through verbatim.shore.io.grd.read_grd_metadatais a new public helper used to read just the.grdheader 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 passshore split's validation. Constrained to one axis per original block to matchshore splitv1's per-block-per-call contract. Output is asplit.tomlready to feed toshore 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 sweepingupper-wake-tip → upper-TE → suction → LE → pressure → lower-TE → lower-wake-tipas 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_planewith 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 anaxis_mapfield 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.parorientation codes derived fromaxis_map(no per-topology tables). The previously hard-coded_CAP_EQUATOR_PARTNERtable inshore.io.cc_paris gone. Orientation codes are now derived purely from the face'saxis_mapplus the partner face name; any topology that stamps validaxis_mapvalues on its seams produces correctcc.paroutput without writer changes. As a side effect, same-axis seams between two blocks now emit asymmetric codes (135on the*_loside,145on the*_hiside) — 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.partnerfor a sub-block'si_lo/i_hiis now the cap's matching lateral face (i_lo/i_hi/j_lo/j_hi), not the cap'sk_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
FlatCapsCylinderadjacency 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), sobackground.adjacency.jsonhad every face asFREE/WALLandbackground.cc.paremitted zero connection patches. All 8 seams are now wired with the correct partners andaxis_mapvalues.boxes_from_stlleft the body surface uncovered. Voxel-fill only emitted voxels with centre strictly inside the body, leaving an uncovered shell of width up toside/2along 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 tosigned_distance(centre) > -half_diagso body-shell voxels are included; voxel side halved tomax_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_stlignored the corner-fit constraint. The oldoutward_offsetparameter expanded the OBB axially, which controls face-centre gap but not corner gap — for a sphere the corners stuck out byr * (sqrt(3) - 1)past the body, often outside the body's near-mesh.
Documented
- Per-edge spacing section in
algorithm/spacing-laws.mdwith the full support table by topology / primitive. topologies/cubed-sphere.md,topologies/o-grid.md,topologies/primitives.md,reference/primitive.mdupdated with per-edge guidance and the structural reasons each axis is or isn't wired.- New
reference/cc-par.mdcovering theshore cc-parcommand, BC-code mapping, orientation-digit decoder, and the cubed-sphere cap-equator orientation table. reference/split.mdandreference/cc-par.mdupdated for the v2 adjacency JSON schema and axis_map-driven orientation codes.