Skip to content

Commit 9e02345

Browse files
sawenzelclaude
andcommitted
Publish the CAD support tutorial as MkDocs sources
This converts the single-page tutorial to Markdown, adds a worked example on the ITS and updates the README pointer. - doc/tutorial/index.html is replaced by a MkDocs project: mkdocs.yml, one page per section under docs/, and the figures as files in docs/images/. - The pages use GitHub's own alert and fence syntax, so they render in the repository file view without a published site. - hooks/github_alerts.py turns those alerts into Material admonitions at build time, so the site needs only mkdocs-material. - docs/its-round-trip.md is new: the ITS through o2-tgeo-to-cad and back, then placed as a sensitive external detector to produce hits. - The tutorial pointer in README.md now names the directory. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017gNpas1pZ9DNBhVesoM5QZ
1 parent da7faae commit 9e02345

23 files changed

Lines changed: 1143 additions & 1438 deletions

Detectors/CADSupport/README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ loaded in ROOT on its own, or injected into `o2-sim` as a passive module or as a
88
detector. Injection is data-driven: a JSON file tells `o2-sim` which macro to load, where to anchor
99
it and, for detectors, which volumes produce hits. Nothing is recompiled.
1010

11-
The tutorial `doc/tutorial/index.html` walks through the whole route on the shipped `ExcavatorArm.step`
12-
model. This file is the option reference.
11+
The tutorial in `doc/tutorial/` walks through the whole route on the shipped `ExcavatorArm.step`
12+
model, and takes the ITS out to STEP and back as a worked example. This file is the option
13+
reference.
1314

1415
## Software setup
1516

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
site/
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# CAD to Simulation — the `Detectors/CADSupport` tutorial
2+
3+
Start at **[docs/index.md](docs/index.md)**, or read the pages in order:
4+
5+
**Start**
6+
7+
1. [Install the software](docs/install.md)
8+
2. [Convert your first model](docs/first-conversion.md)
9+
10+
**Converting**
11+
12+
3. [How a part is represented](docs/representation.md)
13+
4. [Convert only part of a model](docs/partial.md)
14+
5. [Give it materials](docs/materials.md)
15+
6. [Field and cuts](docs/field-and-cuts.md)
16+
7. [The geom.C file](docs/geom-c.md)
17+
18+
**Simulating**
19+
20+
8. [Add passive geometry](docs/passive.md)
21+
9. [Make it produce hits](docs/hits.md)
22+
10. [Grow it into a real detector](docs/real-detector.md)
23+
24+
**Worked example**
25+
26+
11. [The ITS, out and back again](docs/its-round-trip.md)
27+
28+
**Reference**
29+
30+
12. [Check your geometry](docs/checks.md)
31+
13. [Limits and pain points](docs/limits.md)
32+
33+
## Reading it
34+
35+
Every page is plain Markdown and renders correctly in the GitHub file view: alerts use GitHub's own
36+
`> [!NOTE]` syntax, the diagrams are ```mermaid fences, and the figures are ordinary images in
37+
`docs/images/`. Nothing has to be published for someone to read this.
38+
39+
## Building the site
40+
41+
The same sources build a browsable site with search and a sidebar:
42+
43+
```bash
44+
pip install mkdocs-material
45+
mkdocs serve # http://127.0.0.1:8000
46+
mkdocs build # static site in ./site
47+
```
48+
49+
`hooks/github_alerts.py` turns the GitHub alerts into Material admonitions at build time, so the
50+
Markdown stays GitHub-native and no extra plugin is needed.
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Check your geometry
2+
3+
Before trusting any physics that came out of a conversion, it is worth spending a few minutes on four
4+
checks. They are ordered cheapest first, and in practice the first two catch most problems.
5+
6+
## 1 · Read the cascade table
7+
8+
The converter already told you what it decided for every part, and wrote the same information to
9+
`csg_report.json`. A part that declined CSG says which test it failed and by how much, which is often
10+
enough to see that a model is nearly-but-not-quite a primitive. A large tessellated count on a model
11+
you expected to be analytic is the signal to look at `--recognize-surfaces` and the surface report
12+
below.
13+
14+
## 2 · Look for overlaps
15+
16+
Run `build_and_export("geom.root", true, true)` to get `CheckOverlaps`; zero illegal overlaps is what
17+
you want to see. A non-zero count is worth taking seriously, but do not assume it is the conversion's
18+
fault: engineering assemblies are drawn for manufacture, not for particle transport, and slightly
19+
interpenetrating parts are common in perfectly good CAD models.
20+
21+
## 3 · Confirm the exact solids really load
22+
23+
Successfully extracting a solid's surfaces does not guarantee the result is a usable, watertight body.
24+
This macro loads every `surfaces_*.bin` in a directory the same way the transport does, and reports
25+
closure, orientation consistency and enclosed volume:
26+
27+
```bash
28+
# $O2_SRC is your AliceO2 source directory
29+
root -l -b -q "$O2_SRC/Detectors/CADSupport/test/checkSurfaceSidecars.macro(\"cad_out/excavator\")"
30+
```
31+
32+
```text
33+
OK surfaces_Bucket_0_1_1_6.bin surfaces= 97 closed=1 orient=1 capacity=58.3121
34+
OK surfaces_Base_0_1_1_3.bin surfaces= 44 closed=1 orient=1 capacity=241.281
35+
...
36+
SUMMARY cad_out/excavator
37+
sidecars found : 13
38+
loaded : 13
39+
rejected by the reader : 0
40+
loaded but not IsClosed() : 0
41+
orientation inconsistent : 0
42+
```
43+
44+
`closed=1` means the solid is a watertight manifold, which is precisely what navigation requires. Any
45+
non-zero number on the last three summary lines identifies a part that will not transport correctly.
46+
47+
## 4 · Find out what the geometry really is
48+
49+
A subtlety worth knowing: the surface type stored in a STEP file describes the *exporter*, not the
50+
geometry. CAD kernels routinely write an exact cylinder as a rational B-spline, which is an exact
51+
representation rather than an approximation — but dispatching on the stored type would throw that
52+
exactness away. The converter therefore classifies faces by their actual shape, and its surface report
53+
shows the effect:
54+
55+
```bash
56+
# a per-face classification, written alongside a normal conversion
57+
--surface-report cad_out/mydet/surface_report.json
58+
```
59+
60+
## Going further
61+
62+
`Detectors/CADSupport/validation/` holds the tools the development of this system is validated with:
63+
an acceptance gate that scores converted parts against the OpenCascade oracle, an overlap census, a
64+
round-trip report, and the closure test that the [ITS example](its-round-trip.md) follows. They are
65+
not installed — run them from the source tree. `README.md` lists them all.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Field and cuts
2+
3+
There is one place where the converter cannot give you everything, and it is worth being explicit
4+
about rather than discovering later. A CAD file describes a *part*. It cannot describe how you want
5+
that part simulated — how the magnetic field should be integrated through it, how long a step may be,
6+
which secondaries are worth producing. Those are simulation choices, and no CAD format has anywhere
7+
to record them.
8+
9+
## Magnetic field
10+
11+
For the field there is a clean answer. Pass `--in-field` when the module sits inside the magnet, and
12+
the emitted macro will ask the **live** field for its integration method and maximum field strength
13+
at the moment the geometry is built — which is exactly what a hand-written O2 detector does from its
14+
own `createMaterials()`. Nothing is baked into the file:
15+
16+
`geom.C · emitted`
17+
18+
```cpp
19+
int cad_ifield = 2;
20+
float cad_fieldm = 10;
21+
cadFieldTrackingParams(cad_ifield, cad_fieldm); // queries the loaded field
22+
med_Stainless_Steel->SetParam(1, cad_ifield); // ifield, from the live field
23+
med_Stainless_Steel->SetParam(2, cad_fieldm); // fieldm, from the live field
24+
```
25+
26+
The `2,10` you see there is only a seed, used if no field happens to be loaded, and `--in-field 1,5.5`
27+
overrides it. To confirm that the query really happened, check `fieldm` rather than `ifield`:
28+
`ifield = 2` is also the seed value and therefore proves nothing, whereas a `fieldm` the seed could
29+
not have produced — ALICE reports 15 — proves the live field answered.
30+
31+
## Step control and physics cuts
32+
33+
> [!WARNING]
34+
> **These silently default to nothing**
35+
>
36+
> Without `--in-field`, a CAD-authored medium is built through ROOT's three-argument `TGeoMedium`
37+
> constructor, which **zeroes every parameter** — including `ifield`, meaning no field tracking at
38+
> all. Step control (`tmaxfd stemax deemax epsil stmin`) stays at the transport default in every
39+
> case, and special physics cuts are never applied, because there is no `simcuts.dat` for a module
40+
> with no detector directory to hold one. None of this is loud: the simulation runs and the numbers
41+
> look plausible. So set `--in-field` deliberately, and treat cuts as a known open item until your
42+
> study grows into a [real detector](real-detector.md), which is where they come back.
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# Convert your first model
2+
3+
Rather than start on your own detector, it is worth converting something small and known-good first,
4+
so that anything odd later is clearly your model and not your installation. A toy excavator arm is
5+
committed to the repository for exactly this purpose:
6+
7+
```text
8+
$O2_ROOT/share/CADSupport/examples/ExcavatorArm.step # 13 leaf solids, ~500 kB
9+
```
10+
11+
It converts in seconds and is varied enough to be interesting: the hydraulic rams and pivot pins are
12+
plain cylinders, the boom and stick are machined bodies full of concave features, and the bucket has
13+
a torus in it. Run the converter over it, asking for all three representations at once — we come back
14+
to what those are in the next section:
15+
16+
```bash
17+
mkdir -p cad_out/excavator
18+
o2-cad-to-tgeo \
19+
$O2_ROOT/share/CADSupport/examples/ExcavatorArm.step \
20+
--output-folder cad_out/excavator \
21+
-o geom.C \
22+
--step-unit auto \
23+
--csg auto --exact-surfaces auto --mesh --mesh-prec 0.05
24+
```
25+
26+
That takes about thirteen seconds. Along the way the converter prints three lines worth reading on
27+
*every* run, because each one catches a different common mistake:
28+
29+
```text
30+
Detected STEP length unit: mm (scale to cm = 0.1)
31+
Placement check: 13 leaf placement(s), all at distinct world transforms.
32+
Emitting 13/13 logical volumes as exact O2BVHSurfaceSolid
33+
```
34+
35+
The unit line bites hardest. TGeo works in centimetres and most CAD systems export millimetres, so a
36+
silent unit error gives you a detector ten times too big and a simulation that still looks almost
37+
plausible. `--step-unit auto` reads the declaration in the file; pass `--step-unit mm` explicitly when
38+
the file declares something you do not believe. The placement line then tells you whether two leaves
39+
landed on the same world transform, which almost always means a duplicated part in the CAD model
40+
rather than a real coincidence.
41+
42+
Finally the converter prints what it decided for each part, ending in a one-line summary:
43+
44+
```text
45+
=== REPRESENTATION CASCADE (per leaf solid) ===
46+
volume carried by evidence
47+
BasePin csg TGeoTube(rmin=0, rmax=1, dz=5) [tier1-tube], dV_sym=0 cm^3
48+
Base surface declined CSG: 7 axis clusters: beyond the recogniser's scope ...
49+
BoomCylinderOuter csg TGeoTube(0.6,1,7.991) u TGeoTube(0.7,1.5,1.5), dV_sym=0 cm^3
50+
...
51+
tiers: CSG 7, exact surfaces 6, tessellated 0 (of 13 leaf solids)
52+
```
53+
54+
Seven parts came out as ordinary ROOT shapes, six as exact surface solids, and none had to fall back
55+
to an approximate mesh. The `dV_sym=0` is the reassuring part: it is the symmetric-difference volume
56+
between what was emitted and the original CAD solid, so zero means the conversion is exact rather
57+
than merely close.
58+
59+
## Look at what you made
60+
61+
Numbers in a terminal are no substitute for seeing the thing. The macro can build the geometry and
62+
write it out as an ordinary ROOT file:
63+
64+
```bash
65+
cd cad_out/excavator
66+
root -l -b -q -e '.L geom.C' -e 'build_and_export("geom.root");'
67+
```
68+
69+
![A shaded render of the converted excavator arm: bucket, stick, boom and hydraulic rams, seen from above and to the side.](images/excavator_render.png)
70+
71+
*The converted model, drawn by casting one ray per pixel through the TGeo navigator — so this is the
72+
geometry as the transport sees it, not a separate preview mesh.*
73+
74+
The simplest interactive way to inspect the result is ROOT's own web display, which renders the
75+
geometry with JSROOT in your browser and lets you rotate it, hide volumes and click through the tree:
76+
77+
```bash
78+
root --web geom.root
79+
```
80+
81+
If you are on a remote machine where opening a browser is awkward, export the geometry as a JSROOT
82+
document instead and open that file locally. It is a self-contained 32 kB for this model, and can be
83+
dragged straight onto [root.cern/js](https://root.cern/js/):
84+
85+
```bash
86+
root -l -b -q -e 'TGeoManager::Import("geom.root");' \
87+
-e 'TBufferJSON::ExportToFile("excavator.json.gz", gGeoManager);'
88+
```
89+
90+
Spend a minute here. Turning the model around is the fastest way to notice that a subassembly is
91+
missing, that something sits at the wrong scale, or that the part you care about was quietly filtered
92+
out.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# The geom.C file
2+
3+
Everything the converter does ends up in one ROOT macro, and it is the artefact worth caring about.
4+
It exports two functions: `get_builder_hook_unchecked()`, which is what `o2-sim` calls when it loads
5+
your geometry, and `build_and_export()`, which you already used to look at the model on its own.
6+
7+
Alongside it, the output folder holds the binary payloads the macro reads — `facets_*.bin` for meshed
8+
parts, `surfaces_*.bin` for exact ones and `flatcsg_*.bin` for flat CSG solids — plus
9+
`csg_report.json`, which records what each part became and why.
10+
11+
> [!WARNING]
12+
> **The macro and its binaries travel together**
13+
>
14+
> `geom.C` loads those `.bin` files **relative to its own location**. Move or copy the macro without
15+
> the rest of its folder and it will build an empty geometry without complaining. Always move the
16+
> directory.
17+
18+
`build_and_export()` runs `CheckOverlaps` only when asked, because on large models it is slow:
19+
20+
```bash
21+
root -l -b -q -e '.L geom.C' -e 'build_and_export("geom.root", true, true);'
22+
```
23+
24+
```text
25+
Info in <TGeoManager::CloseGeometry>: 14 nodes/ 14 volume UID's in geom
26+
Info in <TGeoNodeMatrix::CheckOverlaps>: Checking overlaps for Assembly and daughters within 0.1
27+
Info in <TGeoNodeMatrix::CheckOverlaps>: Number of illegal overlaps/extrusions : 0
28+
```
29+
30+
Finally, a structural point that shapes how you organise your work: each converted directory holds
31+
exactly one `geom.C`, and each `geom.C` describes one thing you hook into the simulation. If your
32+
study involves three CAD subsystems, you run the converter three times into three folders. They
33+
coexist without trouble, because the loader compiles each macro into its own namespace at run time,
34+
so the identical function names inside them never collide.

0 commit comments

Comments
 (0)