Skip to content

Commit 690e0c2

Browse files
sawenzelclaude
andcommitted
Document the CAD support and add the tutorial
This adds the module's documentation: a README, a tutorial and six reference documents. - README.md is the entry point and the option reference for both converters, the install route and the validation tools. - doc/tutorial/index.html walks through the whole route on the shipped Bagger.step model. - doc/reference/ describes the two solids and their sidecar formats, the CSG pipeline, the tolerance policy, the navigation harness and the deferred work. - doc/known-issues.md and doc/ideas.md record the module's open defects and the proposals for it. - The CAD conversion material that lived in scripts/geometry is now carried by the module. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 4da4aab commit 690e0c2

17 files changed

Lines changed: 3037 additions & 374 deletions

File tree

Detectors/CADSupport/README.md

Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
# CAD support: STEP to TGeo and back
2+
3+
`Detectors/CADSupport` converts CAD geometry exported as STEP into ROOT TGeo geometry for
4+
simulation. It also exports TGeo geometry back to STEP.
5+
6+
The converter writes one ROOT macro, `geom.C`, together with its binary payloads. The macro can be
7+
loaded in ROOT on its own, or injected into `o2-sim` as a passive module or as a sensitive external
8+
detector. Injection is data-driven: a JSON file tells `o2-sim` which macro to load, where to anchor
9+
it and, for detectors, which volumes produce hits. Nothing is recompiled.
10+
11+
The tutorial `doc/tutorial/index.html` walks through the whole route on the shipped `Bagger.step`
12+
model. This file is the option reference.
13+
14+
## Software setup
15+
16+
The converter needs pythonOCC, which is a separate aliBuild package:
17+
18+
```bash
19+
aliBuild build pythonOCC --defaults o2 --no-system SWIG
20+
alienv enter O2sim/latest,pythonOCC/latest
21+
o2-cad-to-tgeo --help
22+
o2-cad-to-tgeo --self-test
23+
```
24+
25+
The installed wrappers `o2-cad-to-tgeo` and `o2-tgeo-to-cad` run
26+
`$O2_ROOT/share/CADSupport/tools/O2_CADtoTGeo.py` and `O2_TGeoToCAD.py`. The example models are
27+
installed in `$O2_ROOT/share/CADSupport/examples/`. The Geant4 NIST material table is
28+
`$O2_ROOT/share/CADSupport/tools/g4_nist_database/G4_NIST_DB.json`.
29+
30+
Outside the ALICE stack, a conda environment with `pythonocc-core` also works. There, run the
31+
script from the source tree:
32+
33+
```bash
34+
conda create -n occ -c conda-forge python=3.10 pythonocc-core -y
35+
conda activate occ
36+
python3 $O2_SRC/Detectors/CADSupport/tools/O2_CADtoTGeo.py --help
37+
```
38+
39+
## Convert a STEP file
40+
41+
```bash
42+
mkdir -p cad_out/excavator
43+
o2-cad-to-tgeo $O2_ROOT/share/CADSupport/examples/Bagger.step \
44+
--output-folder cad_out/excavator -o geom.C --step-unit auto \
45+
--csg auto --exact-surfaces auto --mesh --mesh-prec 0.05
46+
```
47+
48+
Each leaf solid is carried by the first representation that accepts it:
49+
50+
| representation | flag | shape class | payload |
51+
| --- | --- | --- | --- |
52+
| native ROOT CSG, or a flat CSG solid | `--csg auto\|required` | `TGeoBBox`, `TGeoTube`, ..., `TGeoCompositeShape`, `O2FlatCSG` | `shape_*.root`, `flatcsg_*.bin` |
53+
| exact trimmed surfaces | `--exact-surfaces auto\|required` | `O2BVHSurfaceSolid` | `surfaces_*.bin` |
54+
| triangle mesh | `--mesh` | `O2Tessellated` (`--mesh-solid o2`, default) | `facets_*.bin` |
55+
56+
`off` is the default for `--csg` and `--exact-surfaces`. `auto` uses a tier where it is accepted
57+
and falls through elsewhere. `required` stops with a report if any leaf cannot use it. Without
58+
`--mesh`, the fallback tier emits bounding boxes.
59+
60+
`--mesh-prec` sets both the linear and the angular deflection of the OCCT mesher; the default is
61+
0.1. `--mesh-solid tgeo` emits ROOT's `TGeoTessellated`, which does not implement navigation;
62+
use it only for a macro that must load outside O2.
63+
64+
The output folder holds:
65+
66+
- `geom.C`;
67+
- the payloads above;
68+
- `csg_report.json` (with `--csg`);
69+
- `brep_*.brep` (with `--dump-brep`);
70+
- `surface_report.json` (with `--surface-report PATH`).
71+
72+
The macro loads its payloads relative to its own location, so move the folder as a whole.
73+
74+
`geom.C` exports `get_builder_hook_unchecked()`, which `o2-sim` calls, and
75+
`build_and_export(const char* out_root = "geom.root", bool check = true, bool checkOverlaps = false)`
76+
for standalone use:
77+
78+
```bash
79+
(cd cad_out/excavator && root -l -b -q -e '.L geom.C' -e 'build_and_export("geom.root");') # build and export
80+
(cd cad_out/excavator && root -l -b -q -e '.L geom.C' -e 'build_and_export("geom.root", true, true);') # also CheckOverlaps
81+
```
82+
83+
Other conversion options:
84+
85+
| option | meaning |
86+
| --- | --- |
87+
| `--step-unit auto\|mm\|cm\|m\|in\|ft` | STEP length unit; `auto` reads the file's declaration |
88+
| `--recognize-surfaces exact\|off` | recover exact planes, spheres, cylinders and cones stored as NURBS (default `exact`) |
89+
| `--surface-report PATH` | per-face classification and exact-conversion eligibility, as JSON |
90+
| `--csg-report PATH` | where to write `csg_report.json` |
91+
| `--max-cells N`, `--max-splits N`, `--decompose-timeout S` | raise the CSG decomposition budgets (defaults 64, 256, 60 s) |
92+
| `--print-tree` | print the assembly tree and exit |
93+
| `--in-field [IFIELD,FIELDM]` | take field tracking parameters from the live field (seed `2,10`) |
94+
95+
## Convert part of a model
96+
97+
`--include-name RE` and `--exclude-name RE` select CAD labels by regular expression. Both may be
98+
repeated, and a matching assembly includes its whole subtree. Matching is case-insensitive unless
99+
`--name-filter-case-sensitive` is given.
100+
101+
`--clip-box XMIN YMIN ZMIN XMAX YMAX ZMAX` keeps only the geometry inside an axis-aligned box. The
102+
box is given in STEP file units, in the assembly's world frame, with each minimum below its
103+
maximum.
104+
105+
- Solids fully outside the box are dropped.
106+
- Solids fully inside are kept.
107+
- Solids that straddle the boundary are intersected with the box.
108+
- Assemblies left with no children are removed.
109+
110+
`--clip-deduplicate intact` (the default) reuses shared definitions for subtrees fully inside the
111+
box. `none` makes one volume per surviving occurrence.
112+
113+
## Materials
114+
115+
A bill-of-materials CSV assigns materials and, where masses and CAD volumes are both available,
116+
effective densities. Material names are matched against the Geant4 NIST table:
117+
118+
```bash
119+
o2-cad-to-tgeo $O2_ROOT/share/CADSupport/examples/Bagger.step \
120+
--output-folder cad_out/excavator -o geom.C --csg auto --exact-surfaces auto --mesh \
121+
--materials-csv $O2_ROOT/share/CADSupport/examples/Bagger_MATERIALS.csv \
122+
--bom-mass-unit kg \
123+
--g4-nist-json $O2_ROOT/share/CADSupport/tools/g4_nist_database/G4_NIST_DB.json
124+
```
125+
126+
Rows are read when the first two columns are `CAD,Mechanical/Part`. Their layout is
127+
`CAD,Mechanical/Part,<PartNumber>,<Revision>,<Name>,<Mass>,<Material>,...`.
128+
129+
An ambiguous or missing match falls back to a simple material and leaves a comment in `geom.C`. The
130+
matching is tuned by `--mat-min-score`, `--mat-ambiguity-delta`, `--mat-w-token`,
131+
`--mat-w-density`, `--mat-max-log-density-diff` and `--mat-compound-penalty`.
132+
133+
Geometry that came out of TGeo with `o2-tgeo-to-cad` should instead use `--media-json`. That
134+
rebuilds the original media verbatim and takes precedence over the BOM.
135+
136+
Without `--in-field`, a CAD medium has all tracking parameters zero, including `ifield`.
137+
138+
## Passive geometry in `o2-sim`
139+
140+
`externalGeometry.json`:
141+
142+
```json
143+
{
144+
"externalModules": [
145+
{
146+
"name": "EXCV",
147+
"title": "Excavator support structure from CAD",
148+
"macro": "cad_out/excavator/geom.C",
149+
"anchor": "barrel",
150+
"placement": { "translation": [21.01, -13.22, -19.66], "rotation_deg": [0.0, 0.0, 0.0] }
151+
}
152+
]
153+
}
154+
```
155+
156+
`detectorlist.json`:
157+
158+
```json
159+
{ "EXTCAD": ["EXCV"] }
160+
```
161+
162+
```bash
163+
o2-sim -n 1 -g boxgen --detectorList EXTCAD:detectorlist.json --extGeomFile externalGeometry.json
164+
```
165+
166+
A module is added only when its `name` is in the active module list. `anchor` must be an existing
167+
volume; `barrel` sits at (0, −30, 0) in the cave. `placement` is given in cm and degrees in the
168+
anchor's frame. Several modules, each from its own `geom.C`, can be listed together: the loader compiles
169+
each macro into its own namespace, so their identical function names do not collide.
170+
171+
## Sensitive external detectors
172+
173+
Use an `externalDetectors` array. It takes the same fields as a module, plus `detID` and at least
174+
one of `sensitiveVolumes` or `sensitiveMedia`:
175+
176+
```json
177+
{
178+
"externalDetectors": [
179+
{
180+
"name": "EXCV",
181+
"title": "Excavator as a sensitive detector",
182+
"macro": "cad_out/excavator/geom.C",
183+
"anchor": "barrel",
184+
"detID": "TST",
185+
"sensitiveVolumes": ["Bucket"],
186+
"placement": { "translation": [21.01, -13.22, -19.66] }
187+
}
188+
]
189+
}
190+
```
191+
192+
- `sensitiveVolumes` and `sensitiveMedia` match **substrings** of TGeo volume and medium names.
193+
`"Bucket"` above selects five volumes.
194+
- `detID` is an existing detector identity that no active built-in detector uses. The default is
195+
`ITS`. It decides the hit file, for example `o2sim_HitsTST.root`. The branch keeps the module
196+
name, here `EXCVHit`.
197+
- Without `sensitiveMacro`, the built-in action records one entrance/exit hit per charged track in
198+
`o2::ext::Hit`.
199+
- A custom action is a macro, named by `sensitiveMacro` and `sensitiveFunction`, that returns an
200+
`o2::ext::ExternalDetector::SensitiveFcn`. It is compiled at run time and can use
201+
`TVirtualMC::GetMC()`, `currentSensorID()`, `currentTrackID()` and `addHit()`. See
202+
`Detectors/External/macro/sensitiveActionExample.macro`.
203+
204+
In parallel mode, the hit merger reads the same `--extGeomFile` and persists the external hits.
205+
206+
`run/SimExamples/External_Sensitive_Detectors` defines two detectors, `ACYL` and `BDISK`, from
207+
hand-written macros. It needs no CAD input; run `./run.sh` there.
208+
209+
## TGeo to STEP
210+
211+
```bash
212+
o2-tgeo-to-cad geometry.root out.step [--top VOLUME] [--include-name RE] [--carve-mothers] \
213+
[--media-json out_media.json] [--report report.json]
214+
```
215+
216+
`o2-tgeo-to-cad --help` lists the remaining options. Converting the resulting STEP back with
217+
`--media-json` closes the round trip.
218+
219+
## Checks and validation tools
220+
221+
`validation/` is not installed. Run its scripts from `$O2_SRC/Detectors/CADSupport/validation/`.
222+
223+
- `root -l -b -q "$O2_SRC/Detectors/CADSupport/test/checkSurfaceSidecars.macro(\"cad_out/excavator\")"`
224+
loads every `surfaces_*.bin` in a folder and reports closure, orientation and capacity.
225+
- `--surface-report PATH` shows which faces are exact, recognised or unsupported.
226+
- `validation/makeTestPartDB.py` builds a database of parts held both as surfaces and as meshes.
227+
`o2-bench-cadsupport-solid-harness` validates and times them. See
228+
`doc/reference/SolidNavigationHarness.md`.
229+
- `validation/runOracleGate.py` is the acceptance gate: it converts models, samples each part and
230+
scores it against the OpenCascade oracle. `compareGateRuns.py` compares two gate reports.
231+
- The oracles answer from OpenCascade: `occtOracle.py` per solid, `xrayOracle.py` as crossing lists
232+
for the X-ray benchmark (`runXRayBench.py`), and `assemblyOracle.py` volume by volume along a ray
233+
through an assembly. `checkKnownSource.py` scores a part against the `TGeoShape` it came from.
234+
- `validation/overlapCensus.py` sorts every pair of placed solids in a STEP assembly into
235+
disjoint, touching or interpenetrating.
236+
- `validation/roundTripReport.py` reports what the TGeo → STEP → TGeo round trip made of each part;
237+
`exportSourceShapes.py` exports the source shapes it compares against.
238+
- `validation/renderTGeo.py` raytraces a TGeo geometry through the navigator into a PNG, coloured
239+
by representation with `--csg-report`.
240+
- `validation/closure/` runs the same events through a TGeo module and through its STEP round trip
241+
and compares the hits (`run_closure.sh`).
242+
- `validation/demo/` converts Bagger into exact and tessellated geometry and compares `o2-sim` runs
243+
over both (`convert_all.sh`, then `run_all.sh`).
244+
245+
Tests and benchmarks built with the module:
246+
247+
| binary | what |
248+
| --- | --- |
249+
| `o2-test-cadsupport-BVHSurfaceSolid` | unit tests of `O2BVHSurfaceSolid` and the sidecar reader |
250+
| `o2-test-cadsupport-BVHAssembly` | unit tests of `O2BVHAssembly` |
251+
| `o2-test-cadsupport-FlatCSG` | unit tests of `O2FlatCSG` |
252+
| `o2-bench-cadsupport-solid-harness` | per-part validation and timing |
253+
| `o2-bench-cadsupport-xray` | X-ray transport benchmark over a part database |
254+
| `o2-bench-cadsupport-overlap` | overlap census of a placed geometry |
255+
256+
## Reference documents
257+
258+
`doc/reference/`:
259+
260+
- `BVHSurfaceSolid.md`: the exact-surface solid and its sidecar format.
261+
- `Design_FlatCSGSolid.md`: the flat CSG solid and its sidecar format.
262+
- `CSG_Pipeline.md`: CSG recognition and acceptance.
263+
- `TolerancePolicy.md`: every tolerance, with its value and reason.
264+
- `SolidNavigationHarness.md`: the validation harness.
265+
- `Roadmap.md`: deferred work.
266+
267+
Beside them in `doc/`:
268+
269+
- `known-issues.md`: open defects and limitations.
270+
- `ideas.md`: proposals that are not yet decided.

Detectors/CADSupport/doc/ideas.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Ideas
2+
3+
Proposals for `Detectors/CADSupport` that are not yet decided. Work that has been decided on and
4+
deferred is in `reference/Roadmap.md`; open defects are in `known-issues.md`.
5+
6+
## Performance
7+
8+
- Give the hot entry points hidden visibility and inline them, to undo the indirect calls the
9+
library boundary adds. That is the standard remedy for the 4–5 % in `known-issues.md`.
10+
- Time a flat-CSG part through `o2-bench-cadsupport-solid-harness`, so the pruning gain on
11+
`DistFromInside` has a number of its own.
12+
- Report `O2FlatCSG::GetUnprunedRetryCount()` from a benchmark run, so it is visible how often the
13+
flat-CSG safety net falls back to an unpruned traversal.
14+
15+
## Reach
16+
17+
- Teach `tgeo2vecgeom` and VGM about the CAD solids. A converted geometry navigates under TGeo only,
18+
so it cannot use the VecGeom or the native Geant4 navigator.
19+
- Ship the browser viewer for the per-part reports, which lives outside this module today.
20+
- Support free-form surfaces that no exact representation covers, instead of falling back to a mesh.
21+
22+
## Testing
23+
24+
- Split `test/testBVHSurfaceSolid.cxx` along its own section banners; it is larger than the code it
25+
tests.
26+
- Add a unit test for the axis fallback in `O2OverlapCheck`'s `containmentFlips`, which only the
27+
overlap census exercises today.
28+
- Give `O2FlatCSG`'s flip-containment test an assertion independent of the sampler's own rule, for
29+
example that each sampled point lies within tolerance of a halfspace.
30+
- Use the edge-graze fixture for the direction-sensitive `Contains` overload, which a convex box
31+
cannot exercise.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Known issues
2+
3+
Open defects and limitations of `Detectors/CADSupport`. Work that has been decided on and deferred
4+
is in `reference/Roadmap.md`; proposals that are not yet decided are in `ideas.md`.
5+
6+
## Performance
7+
8+
- `O2BVHSurfaceSolid` answers 4–5 % slower per query than the same code did before it moved into
9+
`libO2CADSupport`. About half of that arrives with the library boundary itself; the remainder is
10+
unattributed. No algorithm and no answer changed: this is measured on one part in four
11+
representations, with every per-kernel checksum identical.
12+
- `o2-bench-cadsupport-xray` exits with status 1 when a run has lost crossings. It predates this
13+
module.
14+
- `o2-bench-cadsupport-overlap --self-test` crashes. It predates this module.
15+
16+
## Correctness and robustness
17+
18+
- `O2BVHAssembly` builds its BVH and its bounding box lazily inside const queries, through
19+
`EnsureBuilt`, so two threads navigating a shape read from a file can race.
20+
`O2BVHSurfaceSolid` fills its caches in `CloseShape` and does not have this problem.
21+
`O2BVHAssembly` has no production caller today.
22+
- `Detectors/Base`'s `O2Tessellated` switches its ray pruning off when the ray origin plus the root
23+
box exceeds `kMaxPruneScale` (about 2097 cm), and says nothing when it does.
24+
- `O2OverlapCheck`'s containment-flip filter applies to `O2FlatCSG` samples only. Exact shapes keep
25+
the safety-band filter, because a probe along a concave edge slides along the neighbouring face.
26+
- `Detectors/Base`'s `testMatBudLUT` fails in a development build because it looks for the TPC
27+
plugin in `lib` while the library is installed in `lib64`. It fails the same way on a clean `dev`.
28+
29+
## Documentation and tooling
30+
31+
- `validation/closure/roundtrip_module.sh` calls a Python interpreter through `$SW`, unlike the rest
32+
of the suite, which resolves its interpreter through `cadsupport.occ_env`.
33+
- `cadsupport.occ_env` picks the first architecture holding pythonOCC when `O2_ROOT`'s own
34+
architecture has none.

0 commit comments

Comments
 (0)