cc.par + boxes pipeline
This walkthrough shows the full chimera-component pipeline: generate two independent meshes (a body-fitted cubed-sphere wall + a flat-caps cylinder background), produce one .grd and one cc.par per component, generate the marker boxes that hole-cut the body, then merge the two pairs into a single Xall-ready assembly.
The reference example is at examples/05-chimera-assembly/chimera_assembly.py — every step in this guide maps to a function call in that script, which runs end-to-end without external data.
CLI-only path
The same pipeline runs end-to-end through shore subcommands alone — see the bash equivalent at examples/05-chimera-assembly/chimera_assembly.sh. It produces byte-identical assembly.{grd, cc.par, proc.input} outputs by chaining shore mesh, shore primitive flat-caps, shore boxes-from-stl, shore grd, shore cc-par, shore proc-input and the three *-merge commands. Pass --adjacency-json PATH to shore mesh and shore primitive flat-caps to emit the .adjacency.json sidecar that shore cc-par needs.
What you'll build
Two meshes covering the same domain [-10, 10]² × [-10, 50]:
| Mesh | Topology | Blocks | Role |
|---|---|---|---|
| Near-body wall | Cubed sphere | 6 | Wraps a unit-radius (diameter 1.0) sphere body at the origin, marched outward to wall-normal extent ~0.2. |
| Background | Flat-caps cylinder | 5 | Cylindrical domain with no central hole — central square + 4 trapezoidal sub-blocks. |
Plus, packaged for the solver:
- 2 binary
.grdfiles (one per mesh) → 1 mergedassembly.grd. - 2 ASCII
cc.parfiles (one per mesh, the wall one with marker boxes) → 1 mergedassembly.cc.par. - 1 ParaView
.vtmper mesh (and optionally a standalone boxes.vtm) for visual inspection.
Step 1 — Generate the meshes
The two meshes are exactly what assembly_sphere_in_cylinder.py builds — see that walkthrough for the topology, parameter choices, and ParaView inspection. The short form:
from shore.mesh import CubedSphereTopology, Mesh
from shore.primitive import FlatCapsCylinder
# Near-body wall: 6 cubed-sphere blocks
wall = Mesh.from_stl(
"sphere.stl",
topology=CubedSphereTopology(),
ni=30, nj=40, nk=13,
theta_cap_deg=30.0,
)
wall.march(ds=0.01, growth=1.1)
wall.write_geo("wall") # wall_sub0.geo .. wall_cap_south.geo
# Background: 5 flat-caps blocks
bg = FlatCapsCylinder.build(
r_out=10.0, z0=-10.0, z1=50.0,
ni=16, nc=24, nk=60,
inner_frac=0.4,
)
bg.write_geo("background") # background_center.geo .. background_sub_s.geoStep 2 — Per-component .grd
Pack each mesh's .geo files into a binary .grd. The block ordering you choose here is the block ordering the companion cc.par will reference, so be consistent.
from shore.io.grd import write_grd
write_grd("wall.grd", [
"wall_sub0.geo", "wall_sub1.geo",
"wall_sub2.geo", "wall_sub3.geo",
"wall_cap_north.geo", "wall_cap_south.geo",
])
write_grd("background.grd", [
"background_center.geo",
"background_sub_e.geo", "background_sub_n.geo",
"background_sub_w.geo", "background_sub_s.geo",
])CLI equivalent:
shore grd wall_sub0.geo wall_sub1.geo wall_sub2.geo wall_sub3.geo \
wall_cap_north.geo wall_cap_south.geo -o wall.grd
shore grd background_center.geo background_sub_e.geo background_sub_n.geo \
background_sub_w.geo background_sub_s.geo -o background.grdStep 3 — Per-component adjacency sidecars
shore cc-par needs one .adjacency.json per mesh — the JSON sidecar that captures every face's BC, partner, and axis_map. Both Mesh (cubed sphere) and FlatCapsCylinder already wire their internal seams; emit the sidecar with write_adjacency_json:
from shore.io.adjacency import write_adjacency_json
write_adjacency_json(wall.blocks, "wall.adjacency.json")
write_adjacency_json(bg.blocks, "background.adjacency.json")If you need to subdivide blocks (parallel decomposition), call shore split before this step — it produces the same JSON sidecar on the split block list.
Step 4 — Generate marker boxes from the body STL
Boxes hole-cut the background mesh's body-interior cells. Generate them from the STL with shore boxes-from-stl:
from shore.boxes import boxes_from_stl
boxes = boxes_from_stl(
"sphere.stl",
associated_block="sub0", # any wall block; level/group inherited
max_outward_gap=0.10, # < wall-normal extent (~0.2)
method="voxel-fill", # default; covers body interior + surface
)max_outward_gap must be smaller than the wall mesh's wall-normal thickness (so the boxes fit inside the wall). The voxel-fill algorithm + greedy merge produces a small number of slab-shaped boxes that cover the entire body. See Algorithm — marker boxes for the derivation.
CLI equivalent:
shore boxes-from-stl sphere.stl --block sub0 --max-outward-gap 0.10 \
-o sphere_boxes.jsonStep 5 — Per-component cc.par
For the wall mesh (body-fitted), FREE faces are chimera fringes → BC code 40 (offgen, the default). The cubed-sphere caps can use a different overset level if you want them to take priority over the equatorial sub-blocks. Bake the boxes into the wall's cc.par:
from shore.io.cc_par import BlockMeta, write_cc_par
write_cc_par(
"wall.adjacency.json",
"wall.cc.par",
grd_filename="wall.grd",
block_metadata={
"sub0": BlockMeta(level=1, group=0, priority=1, free_face_bc=40),
"sub1": BlockMeta(level=1, group=0, priority=1, free_face_bc=40),
"sub2": BlockMeta(level=1, group=0, priority=1, free_face_bc=40),
"sub3": BlockMeta(level=1, group=0, priority=1, free_face_bc=40),
"cap_north": BlockMeta(level=2, group=0, priority=1, free_face_bc=40),
"cap_south": BlockMeta(level=2, group=0, priority=1, free_face_bc=40),
},
boxes=boxes,
)For the background mesh (far-field), FREE faces are extrapolation → BC code -9. No boxes needed (boxes live with the body):
write_cc_par(
"background.adjacency.json",
"background.cc.par",
grd_filename="background.grd",
block_metadata={
"center": BlockMeta(level=0, group=1, priority=1, free_face_bc=-9),
"sub_e": BlockMeta(level=0, group=1, priority=1, free_face_bc=-9),
"sub_n": BlockMeta(level=0, group=1, priority=1, free_face_bc=-9),
"sub_w": BlockMeta(level=0, group=1, priority=1, free_face_bc=-9),
"sub_s": BlockMeta(level=0, group=1, priority=1, free_face_bc=-9),
},
)CLI equivalent:
shore cc-par wall.adjacency.json wall.cc.par --grd wall.grd \
--boxes sphere_boxes.json \
--meta '{"cap_north": {"level": 2}, "cap_south": {"level": 2}}'
shore cc-par background.adjacency.json background.cc.par \
--grd background.grd --free-bc -9Step 6 — Per-component proc.input
cc.par is read by the overset preprocessor, not by Xall directly. Xall reads proc.input at MPI startup to learn which block lives on which MPI rank and what group / body indices each block carries. This sibling file is generated from the .grd (block ordering and weights):
from shore.io.proc_input import write_proc_input
# 4 MPI ranks; greedy weight-balanced assignment
write_proc_input("wall.grd", "wall.proc.input", np=4)
write_proc_input("background.grd", "background.proc.input", np=4)CLI equivalent:
shore proc-input wall.grd wall.proc.input --np 4
shore proc-input background.grd background.proc.input --np 4For --np > 1 a balance summary is printed on stderr; if imbalance exceeds 5%, a warning recommends shore balance to plan a topology-safe set of splits. See shore proc-input for the full command reference and the --meta flag for explicit per-block proc / group / body overrides.
Want better balance?
If the imbalance summary fires the warning, run shore balance before going to merge:
shore balance wall.grd -o wall.splits.toml --np 16 \
--adjacency wall.adjacency.json
shore split -c wall.splits.toml wall_*.geo # apply the plan
shore grd wall_split_*.geo -o wall.grd # repack
shore proc-input wall.grd wall.proc.input --np 16 # re-balanceThen go back to step 5 (cc.par) and step 7 (merge) with the larger block list.
Step 7 — Merge into the assembly
Three coordinated calls — merge_grd, merge_cc_par, and merge_proc_input must use the same input ordering. The cc.par patch block fields, the proc.input block indices, and the .grd block list all share a single 1-based numbering, so the three merged files only agree if they were merged with the same ordering.
from shore.io.grd import merge_grd
from shore.io.cc_par import merge_cc_par
from shore.io.proc_input import merge_proc_input
merge_grd(
["background.grd", "wall.grd"],
"assembly.grd",
)
merge_cc_par(
["background.cc.par", "wall.cc.par"],
"assembly.cc.par",
grd_filename="assembly.grd",
)
merge_proc_input(
["background.proc.input", "wall.proc.input"],
"assembly.proc.input",
)CLI equivalent:
shore grd-merge background.grd wall.grd -o assembly.grd
shore cc-par-merge background.cc.par wall.cc.par -o assembly.cc.par --grd assembly.grd
shore proc-input-merge background.proc.input wall.proc.input -o assembly.proc.inputThe merged files describe one 11-block Xall job: 5 background blocks (indices 1..5) + 6 wall blocks (indices 6..11).
Step 8 — Visualise
ParaView is the cheapest sanity check — opening the merged .vtm shows that the wall sits where you think it does and that the boxes actually cover the body:
from shore.io.vtk import write_caps_vtm
# Wall + boxes in one MultiBlock
write_caps_vtm(
"wall.vtm",
[b.nodes for b in wall.blocks],
labels=[b.label for b in wall.blocks],
boxes=boxes,
)
# Background alone
write_caps_vtm(
"background.vtm",
[b.nodes for b in bg.blocks],
labels=[b.label for b in bg.blocks],
)Open both in ParaView. Switch the boxes to wireframe (the default is translucent solid) for a clearer view of how the boxes intersect the wall and the body STL.
Reference
examples/05-chimera-assembly/chimera_assembly.py— the full Python script.examples/05-chimera-assembly/chimera_assembly.sh— bash equivalent using onlyshoreCLI calls (byte-identicalassembly.{grd, cc.par, proc.input}outputs).shore cc-par— file format reference.shore cc-par-merge— multi-component merge.shore grdandshore grd-merge— binary mesh writers.- Marker boxes — full reference.
- Algorithm — marker boxes — voxel-fill + greedy-merge derivation.
- Assembly walkthrough — the underlying mesh build, without
cc.par.