Skip to content

Commit da7faae

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 a2ad134 commit da7faae

17 files changed

Lines changed: 3045 additions & 374 deletions

File tree

Detectors/CADSupport/README.md

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

Detectors/CADSupport/doc/ideas.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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.
32+
- Move the `RepBench*` cases out of `test/testBVHSurfaceSolid.cxx` into their own test target; they
33+
exercise `RepresentationBench.h`, not the solid.
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)