From 3eea9bdf274a24d66c0f2042db7b3226d148e3b8 Mon Sep 17 00:00:00 2001 From: Sandro Wenzel Date: Sat, 12 Sep 2026 20:48:35 +0200 Subject: [PATCH] Add the CAD simulation tutorial as a generated section This adds a new documentation section, CAD simulation tutorial, and the job that keeps it in step with its sources. - docs/cadtutorial holds the section. It is generated, not edited here. - The sources live in AliceO2, Detectors/CADSupport/doc/tutorial, next to the converter they document. - .github/scripts/sync_cad_tutorial.py converts them: GitHub alerts become the theme's note and warning fences, and each page gets its front matter. - .github/workflows/cad-tutorial-sync.yml runs it weekly and on demand, and commits when the result changes. https://github.com/AliceO2Group/AliceO2/pull/15790 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_017gNpas1pZ9DNBhVesoM5QZ --- .github/scripts/sync_cad_tutorial.py | 141 ++++++++++++ .github/workflows/cad-tutorial-sync.yml | 49 ++++ docs/cadtutorial/README.md | 70 ++++++ docs/cadtutorial/checks.md | 70 ++++++ docs/cadtutorial/field-and-cuts.md | 48 ++++ docs/cadtutorial/first-conversion.md | 97 ++++++++ docs/cadtutorial/geom-c.md | 40 ++++ docs/cadtutorial/hits.md | 122 ++++++++++ docs/cadtutorial/images/excavator_cascade.png | Bin 0 -> 41077 bytes .../images/excavator_mesh_only.png | Bin 0 -> 23119 bytes docs/cadtutorial/images/excavator_render.png | Bin 0 -> 42209 bytes docs/cadtutorial/install.md | 77 +++++++ docs/cadtutorial/its-round-trip.md | 213 ++++++++++++++++++ docs/cadtutorial/limits.md | 33 +++ docs/cadtutorial/materials.md | 61 +++++ docs/cadtutorial/partial.md | 44 ++++ docs/cadtutorial/passive.md | 63 ++++++ docs/cadtutorial/real-detector.md | 41 ++++ docs/cadtutorial/representation.md | 83 +++++++ 19 files changed, 1252 insertions(+) create mode 100755 .github/scripts/sync_cad_tutorial.py create mode 100644 .github/workflows/cad-tutorial-sync.yml create mode 100644 docs/cadtutorial/README.md create mode 100644 docs/cadtutorial/checks.md create mode 100644 docs/cadtutorial/field-and-cuts.md create mode 100644 docs/cadtutorial/first-conversion.md create mode 100644 docs/cadtutorial/geom-c.md create mode 100644 docs/cadtutorial/hits.md create mode 100644 docs/cadtutorial/images/excavator_cascade.png create mode 100644 docs/cadtutorial/images/excavator_mesh_only.png create mode 100644 docs/cadtutorial/images/excavator_render.png create mode 100644 docs/cadtutorial/install.md create mode 100644 docs/cadtutorial/its-round-trip.md create mode 100644 docs/cadtutorial/limits.md create mode 100644 docs/cadtutorial/materials.md create mode 100644 docs/cadtutorial/partial.md create mode 100644 docs/cadtutorial/passive.md create mode 100644 docs/cadtutorial/real-detector.md create mode 100644 docs/cadtutorial/representation.md diff --git a/.github/scripts/sync_cad_tutorial.py b/.github/scripts/sync_cad_tutorial.py new file mode 100755 index 0000000..4854af5 --- /dev/null +++ b/.github/scripts/sync_cad_tutorial.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python3 + +"""Generate the CAD simulation tutorial section from its sources in AliceO2. + +The tutorial is written and reviewed in AliceO2, under +`Detectors/CADSupport/doc/tutorial/docs`, so that it stays next to the code it documents. +This script converts those pages into what this Jekyll site expects and writes them to +`docs/cadtutorial`. Do not edit that directory by hand: the next run overwrites it. + +Two things are converted. + + * GitHub alerts (`> [!NOTE]`) become fenced `note` and `warning` blocks, which the theme + renders as callouts. The bold line under the marker stays as the callout's own title, + because the theme's title is fixed. + * Every page gets the `sort`/`title` front matter the navigation is built from. + +Usage: + sync_cad_tutorial.py --source [--out docs/cadtutorial] +""" + +import argparse +import os +import re +import shutil +import sys + +SECTION_SORT = 9 +SECTION_TITLE = "CAD simulation tutorial" + +# The reading order of the tutorial, and the title each page carries here. +PAGES = [ + ("index.md", "README.md", "CAD simulation tutorial"), + ("install.md", "install.md", "Install the software"), + ("first-conversion.md", "first-conversion.md", "Convert your first model"), + ("representation.md", "representation.md", "How a part is represented"), + ("partial.md", "partial.md", "Convert only part of a model"), + ("materials.md", "materials.md", "Give it materials"), + ("field-and-cuts.md", "field-and-cuts.md", "Field and cuts"), + ("geom-c.md", "geom-c.md", "The geom.C file"), + ("passive.md", "passive.md", "Add passive geometry"), + ("hits.md", "hits.md", "Make it produce hits"), + ("real-detector.md", "real-detector.md", "Grow it into a real detector"), + ("its-round-trip.md", "its-round-trip.md", "The ITS, out and back again"), + ("checks.md", "checks.md", "Check your geometry"), + ("limits.md", "limits.md", "Limits and pain points"), +] + +ALERT = {"NOTE": "note", "TIP": "tip", "IMPORTANT": "note", + "WARNING": "warning", "CAUTION": "danger"} + +SOURCE_URL = ("https://github.com/AliceO2Group/AliceO2/tree/dev/" + "Detectors/CADSupport/doc/tutorial") + +PROVENANCE = f""" + +--- + +*These pages are generated from the tutorial sources in AliceO2, +[Detectors/CADSupport/doc/tutorial]({SOURCE_URL}), which is where corrections belong.* +""" + + +def alerts_to_fences(text): + """`> [!WARNING]` blocks become ```warning fences, whose body the theme markdownifies.""" + lines, out, i = text.split("\n"), [], 0 + while i < len(lines): + m = re.match(r"^> \[!(\w+)\]\s*$", lines[i]) + if not m or m.group(1) not in ALERT: + out.append(lines[i]) + i += 1 + continue + kind = ALERT[m.group(1)] + i += 1 + body = [] + while i < len(lines) and lines[i].startswith(">"): + body.append(lines[i][2:] if lines[i].startswith("> ") else lines[i][1:]) + i += 1 + if any(b.startswith("```") for b in body): + raise SystemExit("a code fence inside an alert cannot be carried into a " + "fenced callout; rewrite the source page") + out.append("```" + kind) + out.extend(body) + out.append("```") + return "\n".join(out) + + +def relink(text, names): + """Links between tutorial pages: index.md is README.md here, the rest keep their names.""" + text = text.replace("(index.md)", "(README.md)") + return text + + +def front_matter(sort, title): + return f"---\nsort: {sort}\ntitle: {title}\n---\n\n" + + +def main(): + p = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + p.add_argument("--source", required=True, + help="an AliceO2 checkout, or its Detectors/CADSupport/doc/tutorial directory") + p.add_argument("--out", default="docs/cadtutorial") + args = p.parse_args() + + src = args.source + if os.path.isdir(os.path.join(src, "Detectors")): + src = os.path.join(src, "Detectors/CADSupport/doc/tutorial") + docs = os.path.join(src, "docs") + if not os.path.isdir(docs): + raise SystemExit(f"no tutorial sources under {docs}") + + os.makedirs(args.out, exist_ok=True) + names = {a for a, _, _ in PAGES} + written = [] + for i, (source, target, title) in enumerate(PAGES): + path = os.path.join(docs, source) + if not os.path.exists(path): + raise SystemExit(f"missing tutorial page: {path}") + body = relink(alerts_to_fences(open(path).read()), names) + sort = SECTION_SORT if target == "README.md" else i + with open(os.path.join(args.out, target), "w") as fh: + fh.write(front_matter(sort, SECTION_TITLE if target == "README.md" else title)) + fh.write(body) + if target == "README.md": + fh.write(PROVENANCE) + written.append(target) + + images_in = os.path.join(docs, "images") + if os.path.isdir(images_in): + images_out = os.path.join(args.out, "images") + os.makedirs(images_out, exist_ok=True) + for f in sorted(os.listdir(images_in)): + shutil.copy2(os.path.join(images_in, f), os.path.join(images_out, f)) + written.append(os.path.join("images", f)) + + print(f"wrote {len(written)} file(s) to {args.out}") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/.github/workflows/cad-tutorial-sync.yml b/.github/workflows/cad-tutorial-sync.yml new file mode 100644 index 0000000..61108d9 --- /dev/null +++ b/.github/workflows/cad-tutorial-sync.yml @@ -0,0 +1,49 @@ +--- +# Regenerate docs/cadtutorial from the tutorial sources in AliceO2. +# +# The tutorial is written and reviewed next to the code it documents, in +# AliceO2 under Detectors/CADSupport/doc/tutorial. This job converts those +# pages into the form this site expects, so the section here never drifts +# from the converter it describes. + +name: CAD tutorial + +on: + schedule: + - cron: "17 4 * * 1" + workflow_dispatch: + +permissions: + contents: write + +jobs: + sync: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Fetch the tutorial sources + working-directory: ${{ runner.temp }} + run: | + git clone --depth 1 --filter=blob:none --sparse \ + https://github.com/AliceO2Group/AliceO2.git aliceo2 + cd aliceo2 + git sparse-checkout set Detectors/CADSupport/doc/tutorial + + - name: Regenerate the section + run: | + python3 .github/scripts/sync_cad_tutorial.py \ + --source "${{ runner.temp }}/aliceo2" + + - name: Commit if anything changed + run: | + if git diff --quiet -- docs/cadtutorial; then + echo "already up to date" + exit 0 + fi + git config user.name "github-actions[bot]" + git config user.email \ + "41898282+github-actions[bot]@users.noreply.github.com" + git add docs/cadtutorial + git commit -m "Regenerate the CAD simulation tutorial from AliceO2" + git push diff --git a/docs/cadtutorial/README.md b/docs/cadtutorial/README.md new file mode 100644 index 0000000..5f19016 --- /dev/null +++ b/docs/cadtutorial/README.md @@ -0,0 +1,70 @@ +--- +sort: 9 +title: CAD simulation tutorial +--- + +# Simulating ALICE geometries that come from CAD + +Detectors are designed in CAD, but Geant transports particles through ROOT's TGeo geometry. This +guide is about crossing that gap automatically — taking an engineering model as it comes out of the +design office and turning it into something particles can be simulated through, all the way to hits +you can plot. + +The usual way of crossing that gap is to read the drawings and write the geometry again by hand, in +C++, volume by volume. That works, and most of ALICE was built this way, but it is slow, it is easy +to get subtly wrong, and every time the engineers move a bracket the translation has to be redone. +For a detector that is still being designed — which is exactly the situation during an upgrade study +— the hand-written geometry is out of date almost as soon as it is written. + +So instead we convert the CAD file directly. You export the assembly as STEP, run one converter over +it, and you get a ROOT macro that builds the geometry. From there a small JSON file tells `o2-sim` to +load that macro and place it in the ALICE world. Nothing is recompiled at any point, so the loop from +a new CAD revision to a new simulation takes minutes rather than weeks. + +Getting the geometry in is only half of it, though. A shape that particles fly through is a passive +obstacle; to do physics you want it to *record* something. The second half of this guide is therefore +about the external-detector mechanism, which lets you declare parts of your imported geometry +sensitive and have them write hits — again with no detector class and no rebuild. That is usually +enough to answer the first questions an upgrade study asks: does this thing get hit, how often, and +where. + +## What you will be able to do by the end + +- Install the converter and check that it works. +- Convert a STEP assembly and look at the result. +- Understand and control how faithfully each part is represented. +- Attach materials, and know what the magnetic field and physics cuts will and will not do. +- Place the geometry inside ALICE as passive material. +- Make parts of it sensitive, run a simulation, and count hits. +- Take an existing ALICE detector out to CAD and back, and simulate the result. +- Know where the system's limits are, so you do not discover them in your results. + +We assume you can run `o2-sim`, and nothing more. No CAD experience is needed, and no knowledge of +OpenCascade, which does the heavy lifting underneath but never has to be addressed directly. + +## Where the code lives + +Everything in this guide is in `Detectors/CADSupport` in [AliceO2](https://github.com/AliceO2Group/AliceO2). +`README.md` there is the complete option reference, and `doc/reference/` documents the solids, their +file formats and the recognition pipeline. + +## Contents + +**Start** — [Install the software](install.md) · [Convert your first model](first-conversion.md) + +**Converting** — [How a part is represented](representation.md) · +[Convert only part of a model](partial.md) · [Give it materials](materials.md) · +[Field and cuts](field-and-cuts.md) · [The geom.C file](geom-c.md) + +**Simulating** — [Add passive geometry](passive.md) · [Make it produce hits](hits.md) · +[Grow it into a real detector](real-detector.md) + +**Worked example** — [The ITS, out and back again](its-round-trip.md) + +**Reference** — [Check your geometry](checks.md) · [Limits and pain points](limits.md) + + +--- + +*These pages are generated from the tutorial sources in AliceO2, +[Detectors/CADSupport/doc/tutorial](https://github.com/AliceO2Group/AliceO2/tree/dev/Detectors/CADSupport/doc/tutorial), which is where corrections belong.* diff --git a/docs/cadtutorial/checks.md b/docs/cadtutorial/checks.md new file mode 100644 index 0000000..7da723a --- /dev/null +++ b/docs/cadtutorial/checks.md @@ -0,0 +1,70 @@ +--- +sort: 12 +title: Check your geometry +--- + +# Check your geometry + +Before trusting any physics that came out of a conversion, it is worth spending a few minutes on four +checks. They are ordered cheapest first, and in practice the first two catch most problems. + +## 1 · Read the cascade table + +The converter already told you what it decided for every part, and wrote the same information to +`csg_report.json`. A part that declined CSG says which test it failed and by how much, which is often +enough to see that a model is nearly-but-not-quite a primitive. A large tessellated count on a model +you expected to be analytic is the signal to look at `--recognize-surfaces` and the surface report +below. + +## 2 · Look for overlaps + +Run `build_and_export("geom.root", true, true)` to get `CheckOverlaps`; zero illegal overlaps is what +you want to see. A non-zero count is worth taking seriously, but do not assume it is the conversion's +fault: engineering assemblies are drawn for manufacture, not for particle transport, and slightly +interpenetrating parts are common in perfectly good CAD models. + +## 3 · Confirm the exact solids really load + +Successfully extracting a solid's surfaces does not guarantee the result is a usable, watertight body. +This macro loads every `surfaces_*.bin` in a directory the same way the transport does, and reports +closure, orientation consistency and enclosed volume: + +```bash +# $O2_SRC is your AliceO2 source directory +root -l -b -q "$O2_SRC/Detectors/CADSupport/test/checkSurfaceSidecars.macro(\"cad_out/excavator\")" +``` + +```text +OK surfaces_Bucket_0_1_1_6.bin surfaces= 97 closed=1 orient=1 capacity=58.3121 +OK surfaces_Base_0_1_1_3.bin surfaces= 44 closed=1 orient=1 capacity=241.281 +... +SUMMARY cad_out/excavator + sidecars found : 13 + loaded : 13 + rejected by the reader : 0 + loaded but not IsClosed() : 0 + orientation inconsistent : 0 +``` + +`closed=1` means the solid is a watertight manifold, which is precisely what navigation requires. Any +non-zero number on the last three summary lines identifies a part that will not transport correctly. + +## 4 · Find out what the geometry really is + +A subtlety worth knowing: the surface type stored in a STEP file describes the *exporter*, not the +geometry. CAD kernels routinely write an exact cylinder as a rational B-spline, which is an exact +representation rather than an approximation — but dispatching on the stored type would throw that +exactness away. The converter therefore classifies faces by their actual shape, and its surface report +shows the effect: + +```bash +# a per-face classification, written alongside a normal conversion +--surface-report cad_out/mydet/surface_report.json +``` + +## Going further + +`Detectors/CADSupport/validation/` holds the tools the development of this system is validated with: +an acceptance gate that scores converted parts against the OpenCascade oracle, an overlap census, a +round-trip report, and the closure test that the [ITS example](its-round-trip.md) follows. They are +not installed — run them from the source tree. `README.md` lists them all. diff --git a/docs/cadtutorial/field-and-cuts.md b/docs/cadtutorial/field-and-cuts.md new file mode 100644 index 0000000..1897ed1 --- /dev/null +++ b/docs/cadtutorial/field-and-cuts.md @@ -0,0 +1,48 @@ +--- +sort: 6 +title: Field and cuts +--- + +# Field and cuts + +There is one place where the converter cannot give you everything, and it is worth being explicit +about rather than discovering later. A CAD file describes a *part*. It cannot describe how you want +that part simulated — how the magnetic field should be integrated through it, how long a step may be, +which secondaries are worth producing. Those are simulation choices, and no CAD format has anywhere +to record them. + +## Magnetic field + +For the field there is a clean answer. Pass `--in-field` when the module sits inside the magnet, and +the emitted macro will ask the **live** field for its integration method and maximum field strength +at the moment the geometry is built — which is exactly what a hand-written O2 detector does from its +own `createMaterials()`. Nothing is baked into the file: + +`geom.C · emitted` + +```cpp +int cad_ifield = 2; +float cad_fieldm = 10; +cadFieldTrackingParams(cad_ifield, cad_fieldm); // queries the loaded field +med_Stainless_Steel->SetParam(1, cad_ifield); // ifield, from the live field +med_Stainless_Steel->SetParam(2, cad_fieldm); // fieldm, from the live field +``` + +The `2,10` you see there is only a seed, used if no field happens to be loaded, and `--in-field 1,5.5` +overrides it. To confirm that the query really happened, check `fieldm` rather than `ifield`: +`ifield = 2` is also the seed value and therefore proves nothing, whereas a `fieldm` the seed could +not have produced — ALICE reports 15 — proves the live field answered. + +## Step control and physics cuts + +```warning +**These silently default to nothing** + +Without `--in-field`, a CAD-authored medium is built through ROOT's three-argument `TGeoMedium` +constructor, which **zeroes every parameter** — including `ifield`, meaning no field tracking at +all. Step control (`tmaxfd stemax deemax epsil stmin`) stays at the transport default in every +case, and special physics cuts are never applied, because there is no `simcuts.dat` for a module +with no detector directory to hold one. None of this is loud: the simulation runs and the numbers +look plausible. So set `--in-field` deliberately, and treat cuts as a known open item until your +study grows into a [real detector](real-detector.md), which is where they come back. +``` diff --git a/docs/cadtutorial/first-conversion.md b/docs/cadtutorial/first-conversion.md new file mode 100644 index 0000000..fefcd65 --- /dev/null +++ b/docs/cadtutorial/first-conversion.md @@ -0,0 +1,97 @@ +--- +sort: 2 +title: Convert your first model +--- + +# Convert your first model + +Rather than start on your own detector, it is worth converting something small and known-good first, +so that anything odd later is clearly your model and not your installation. A toy excavator arm is +committed to the repository for exactly this purpose: + +```text +$O2_ROOT/share/CADSupport/examples/ExcavatorArm.step # 13 leaf solids, ~500 kB +``` + +It converts in seconds and is varied enough to be interesting: the hydraulic rams and pivot pins are +plain cylinders, the boom and stick are machined bodies full of concave features, and the bucket has +a torus in it. Run the converter over it, asking for all three representations at once — we come back +to what those are in the next section: + +```bash +mkdir -p cad_out/excavator +o2-cad-to-tgeo \ + $O2_ROOT/share/CADSupport/examples/ExcavatorArm.step \ + --output-folder cad_out/excavator \ + -o geom.C \ + --step-unit auto \ + --csg auto --exact-surfaces auto --mesh --mesh-prec 0.05 +``` + +That takes about thirteen seconds. Along the way the converter prints three lines worth reading on +*every* run, because each one catches a different common mistake: + +```text +Detected STEP length unit: mm (scale to cm = 0.1) +Placement check: 13 leaf placement(s), all at distinct world transforms. +Emitting 13/13 logical volumes as exact O2BVHSurfaceSolid +``` + +The unit line bites hardest. TGeo works in centimetres and most CAD systems export millimetres, so a +silent unit error gives you a detector ten times too big and a simulation that still looks almost +plausible. `--step-unit auto` reads the declaration in the file; pass `--step-unit mm` explicitly when +the file declares something you do not believe. The placement line then tells you whether two leaves +landed on the same world transform, which almost always means a duplicated part in the CAD model +rather than a real coincidence. + +Finally the converter prints what it decided for each part, ending in a one-line summary: + +```text +=== REPRESENTATION CASCADE (per leaf solid) === + volume carried by evidence + BasePin csg TGeoTube(rmin=0, rmax=1, dz=5) [tier1-tube], dV_sym=0 cm^3 + Base surface declined CSG: 7 axis clusters: beyond the recogniser's scope ... + BoomCylinderOuter csg TGeoTube(0.6,1,7.991) u TGeoTube(0.7,1.5,1.5), dV_sym=0 cm^3 + ... + tiers: CSG 7, exact surfaces 6, tessellated 0 (of 13 leaf solids) +``` + +Seven parts came out as ordinary ROOT shapes, six as exact surface solids, and none had to fall back +to an approximate mesh. The `dV_sym=0` is the reassuring part: it is the symmetric-difference volume +between what was emitted and the original CAD solid, so zero means the conversion is exact rather +than merely close. + +## Look at what you made + +Numbers in a terminal are no substitute for seeing the thing. The macro can build the geometry and +write it out as an ordinary ROOT file: + +```bash +cd cad_out/excavator +root -l -b -q -e '.L geom.C' -e 'build_and_export("geom.root");' +``` + +![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) + +*The converted model, drawn by casting one ray per pixel through the TGeo navigator — so this is the +geometry as the transport sees it, not a separate preview mesh.* + +The simplest interactive way to inspect the result is ROOT's own web display, which renders the +geometry with JSROOT in your browser and lets you rotate it, hide volumes and click through the tree: + +```bash +root --web geom.root +``` + +If you are on a remote machine where opening a browser is awkward, export the geometry as a JSROOT +document instead and open that file locally. It is a self-contained 32 kB for this model, and can be +dragged straight onto [root.cern/js](https://root.cern/js/): + +```bash +root -l -b -q -e 'TGeoManager::Import("geom.root");' \ + -e 'TBufferJSON::ExportToFile("excavator.json.gz", gGeoManager);' +``` + +Spend a minute here. Turning the model around is the fastest way to notice that a subassembly is +missing, that something sits at the wrong scale, or that the part you care about was quietly filtered +out. diff --git a/docs/cadtutorial/geom-c.md b/docs/cadtutorial/geom-c.md new file mode 100644 index 0000000..b3c4d95 --- /dev/null +++ b/docs/cadtutorial/geom-c.md @@ -0,0 +1,40 @@ +--- +sort: 7 +title: The geom.C file +--- + +# The geom.C file + +Everything the converter does ends up in one ROOT macro, and it is the artefact worth caring about. +It exports two functions: `get_builder_hook_unchecked()`, which is what `o2-sim` calls when it loads +your geometry, and `build_and_export()`, which you already used to look at the model on its own. + +Alongside it, the output folder holds the binary payloads the macro reads — `facets_*.bin` for meshed +parts, `surfaces_*.bin` for exact ones and `flatcsg_*.bin` for flat CSG solids — plus +`csg_report.json`, which records what each part became and why. + +```warning +**The macro and its binaries travel together** + +`geom.C` loads those `.bin` files **relative to its own location**. Move or copy the macro without +the rest of its folder and it will build an empty geometry without complaining. Always move the +directory. +``` + +`build_and_export()` runs `CheckOverlaps` only when asked, because on large models it is slow: + +```bash +root -l -b -q -e '.L geom.C' -e 'build_and_export("geom.root", true, true);' +``` + +```text +Info in : 14 nodes/ 14 volume UID's in geom +Info in : Checking overlaps for Assembly and daughters within 0.1 +Info in : Number of illegal overlaps/extrusions : 0 +``` + +Finally, a structural point that shapes how you organise your work: each converted directory holds +exactly one `geom.C`, and each `geom.C` describes one thing you hook into the simulation. If your +study involves three CAD subsystems, you run the converter three times into three folders. They +coexist without trouble, because the loader compiles each macro into its own namespace at run time, +so the identical function names inside them never collide. diff --git a/docs/cadtutorial/hits.md b/docs/cadtutorial/hits.md new file mode 100644 index 0000000..92a7864 --- /dev/null +++ b/docs/cadtutorial/hits.md @@ -0,0 +1,122 @@ +--- +sort: 9 +title: Make it produce hits +--- + +# Make it produce hits + +Passive geometry answers questions about material budget. To ask whether your detector is actually +hit, and how often, some of its volumes need to be sensitive. This is the fastest route from a CAD +file to plottable hits, and it still needs no detector class and no rebuild — we simply change the +array name to `externalDetectors` and say which volumes should record: + +`externalGeometry.json` + +```json +{ + "externalDetectors": [ + { + "name": "EXCV", + "title": "Excavator as a sensitive detector", + "macro": "cad_out/excavator/geom.C", + "anchor": "barrel", + "detID": "TST", + "sensitiveVolumes": ["Bucket"], + "placement": { "translation": [21.01, -13.22, -19.66] } + } + ] +} +``` + +## Choosing the sensitive volumes + +There are two ways of selecting them, and you may use either or both as long as at least one is +non-empty. `sensitiveVolumes` matches against TGeo volume names, and `sensitiveMedia` matches against +medium names — the latter being a convenient way to make every silicon part in an assembly sensitive +at once, however the parts happen to be named. + +```warning +**Both match substrings, not whole names** + +This catches people out. On the excavator model, `"sensitiveVolumes": ["Bucket"]` selects **five** +volumes rather than one — `Bucket`, `BucketLink1`, `BucketLink2`, `BucketCylinderInner` and +`BucketCylinderOuter`. The startup log prints every volume it registered, so read it and tighten +the string if that was not what you meant. +``` + +## Choosing a DetID + +The `detID` field ties your detector to an existing O2 detector identity, which is what determines +where the hits are filed. Pick a slot no active built-in detector is using: + +- `TST` is the general-purpose test slot, and the right default for a quick study. +- An upgrade study normally borrows the slot it stands in for — `TRK` for an ALICE 3 tracker, for + instance — because it is semantically honest and keeps downstream tooling happy. + +The hit branch keeps *your* module name rather than the borrowed one, so the configuration above +produces a branch called `EXCVHit`. + +## Running it + +```bash +o2-sim-serial -n 3 -g boxgen --seed 42 \ + --detectorList EXTCAD:detectorlist.json \ + --extGeomFile externalGeometry.json \ + --configKeyValues 'BoxGun.number=500;BoxGun.pdg=211;BoxGun.eta[0]=-1;BoxGun.eta[1]=1;BoxGun.prange[0]=2.0;BoxGun.prange[1]=5.0' +``` + +```text +External detector EXCV: 5 sensitive volume(s) selected +External detector EXCV: registered sensitive volume 'Bucket' (MC volID 8, sensor 0) +CREATING BRANCH EXCVHit +External detector EXCV EndOfEvent: 681 sensitive step(s) -> 94 hit(s) +External detector EXCV EndOfEvent: 402 sensitive step(s) -> 59 hit(s) +External detector EXCV EndOfEvent: 927 sensitive step(s) -> 124 hit(s) +``` + +The hits land in `o2sim.root`, one entry per event: + +```bash +root -l -b -q -e 'TFile f("o2sim.root"); TTree *t=(TTree*)f.Get("o2sim"); + t->Draw("EXCVHit@.size()");' +``` + +```note +**Zero hits is usually aim, not breakage** + +The most common first result is `0 sensitive step(s)`, and the instinct is to suspect the +conversion. Check where the particles are going first. The run above produces nothing at all at +the default multiplicity of 10, simply because the excavator is a 40 cm object sitting 40 cm +off-axis and is a small target. Raise the multiplicity or aim the gun. To rule out the geometry +independently, shoot a ray through it in ROOT with `gGeoManager->FindNextBoundaryAndStep()` and +print the volume names you cross — if they appear, navigation is fine and the problem is aim. +``` + +## Custom sensitive actions + +With no further configuration, every sensitive volume records a charged-track entrance and exit hit in +the generic `o2::ext::Hit` format: position in and out, momentum, energy loss, PDG code and track +length. That is enough for occupancy, acceptance and material studies, which covers most first +questions. + +When you need something else — a different hit definition, a cut applied at scoring time, extra +quantities — you can point at a macro returning an `o2::ext::ExternalDetector::SensitiveFcn`. It is +compiled at run time and can query `TVirtualMC::GetMC()` and call helpers such as `currentSensorID()`, +`currentTrackID()` and `addHit()`: + +`externalGeometry.json · fragment` + +```json +"sensitiveMedia": ["Silicon"], +"sensitiveMacro": "sensitive_action.macro", +"sensitiveFunction": "sensitiveAction()" +``` + +```note +**A worked example that needs no CAD file** + +`run/SimExamples/External_Sensitive_Detectors` defines two artificial detectors entirely from data +— one using the built-in action, one with a custom action compiled at run time — from hand-written +macros that mimic converter output. Running `./run.sh` in that directory shows both hit branches +appearing. +``` diff --git a/docs/cadtutorial/images/excavator_cascade.png b/docs/cadtutorial/images/excavator_cascade.png new file mode 100644 index 0000000000000000000000000000000000000000..be16f319eba19908c6122f1fa8a110c990d47385 GIT binary patch literal 41077 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 literal 0 HcmV?d00001 diff --git a/docs/cadtutorial/images/excavator_mesh_only.png b/docs/cadtutorial/images/excavator_mesh_only.png new file mode 100644 index 0000000000000000000000000000000000000000..02077135dfe0e3af2c58660db27a015388f888ae GIT binary patch literal 23119 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

%wP!RLH@LCst)&J<6^~P*PaSvt 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 bQaVG}syVVO(-RppWA>O@?Rsc@^uoUYm^Q>f literal 0 HcmV?d00001 diff --git a/docs/cadtutorial/images/excavator_render.png b/docs/cadtutorial/images/excavator_render.png new file mode 100644 index 0000000000000000000000000000000000000000..55e6d4b55da59827761359acea0ff60b163ad7b1 GIT binary patch literal 42209 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.root"] --> B["o2-tgeo-to-cad
ITS.step + media sidecar"] + B --> C["o2-cad-to-tgeo
conv/geom.C"] + C --> D["o2-sim
external detector → hits"] +``` + +## 1 · The source geometry + +`-n 0` builds the geometry, writes it and transports nothing: + +```bash +mkdir -p its_roundtrip && cd its_roundtrip +o2-sim-serial -n 0 -g boxgen -m ITS -o o2sim +``` + +That leaves `o2sim_geometry.root`, which is the input to the export. + +## 2 · TGeo to STEP + +```bash +o2-tgeo-to-cad o2sim_geometry.root ITS.step \ + --top barrel \ + --hollow-volume barrel --hollow-tag ITS \ + --media-json ITS_media.json \ + --report ITS_writer_report.json +``` + +```text +Step File Name : ITS.step(278254 ents) Write Done +261 solids, 84 volumes with daughters, 29 pure assemblies, 1996 components, 2 volumes declined +capacity check: max relative deviation 2.012e-02, median 3.365e-16 +report: ITS_writer_report.json (28.22 s, 16.17 MB) +media: ITS_media.json (33 media over 261 parts) +``` + +Three of those options deserve a word. + +`--top barrel` converts the subtree under `barrel`, which is where `o2-sim` hangs the ITS. Converting +from the world root instead would drag the experiment hall along with it. + +`--hollow-volume barrel` emits `barrel` as a pure assembly: its daughters keep their own transforms, +but the volume itself contributes no body. This matters because `o2-sim` always builds `cave`, +`barrel` and `caveRB24` itself, whatever module list it is given — shipping a second copy would put +two coincident air boxes in the world. `--hollow-tag ITS` then suffixes the hollowed name, so two +modules exported from the same world do not collide when they are placed together. + +`--media-json` is the sidecar that makes this a *round trip* rather than a one-way conversion. It +records every medium as O2 built it, so the back-conversion can rebuild them verbatim instead of +guessing materials from part names. + +The `capacity check` line is the writer's own verification: it compares the volume of each solid it +wrote against the volume ROOT reports for the original shape. A median deviation of 3.4e-16 is machine +precision. + +## 3 · STEP back to TGeo + +```bash +o2-cad-to-tgeo ITS.step -o geom.C --output-folder conv \ + --csg auto --exact-surfaces auto --mesh \ + --media-json ITS_media.json +``` + +This one takes about five minutes — the ITS is 261 solids, several of which are deep boolean +constructions. + +```text +Detected STEP length unit: mm (scale to cm = 0.1) +Placement check: 296716 leaf placement(s), all at distinct world transforms. + tessellation is EXACT (every face a planar polygon) for 142 of 261 part(s) -- 54.4 % + tiers: CSG 252, exact surfaces 9, tessellated 0 (of 261 leaf solids) +Media from sidecar: 261/261 volumes carry their source medium +Wrote ROOT macro: .../conv/geom.C +``` + +Two lines to read carefully. `tiers: CSG 252, exact surfaces 9, tessellated 0` says the whole ITS came +back exactly: 252 parts as ordinary ROOT shapes, nine as exact surface solids, and nothing at all fell +through to the approximate mesh. `Media from sidecar: 261/261` says every volume got its original +medium back rather than a placeholder. + +You can check the media independently: + +```bash +python3 $O2_SRC/Detectors/CADSupport/validation/closure/check_media.py \ + --original o2sim_geometry.root --macro conv/geom.C --rtol 1e-6 \ + --writer-report ITS_writer_report.json +``` + +```text +converted volumes with a medium: 261 + media identical to the source: 261 + left on the Default placeholder (transparent): 0 + disagreeing with the source: 0 +VERDICT: every volume carries its source medium +``` + +```note +**One shell or two** + +The converter and `o2-sim` share one `alienv enter O2sim/latest,pythonOCC/latest` shell. If your +`pythonOCC` modulefile still has the `PYTHONPATH` defect described in +[Install the software](install.md), that same path makes `o2-sim` segfault at startup — run the +converter in a shell of its own until the modulefile is fixed. +``` + +## 4 · Hits from the converted ITS + +Now hook it in. The sensitive volumes are the seven ITS sensor volumes, `ITSUSensor0` … `ITSUSensor6`, +which one substring selects. Because the geometry was converted from `barrel` with `barrel` hollowed, +it goes back into the real `barrel` with no placement at all — every part lands at exactly the +transform the source geometry gave it: + +`externalGeometry.json` + +```json +{ + "externalDetectors": [ + { + "name": "CITS", + "title": "CAD round-tripped ITS", + "macro": "conv/geom.C", + "anchor": "barrel", + "detID": "ITS", + "sensitiveVolumes": ["ITSUSensor"] + } + ] +} +``` + +`detectorlist.json` + +```json +{ "CADITS": ["CITS"] } +``` + +```bash +o2-sim-serial -n 3 -g boxgen --seed 42 \ + --detectorList CADITS:detectorlist.json \ + --extGeomFile externalGeometry.json \ + --configKeyValues 'SimCutParams.trackSeed=true;BoxGun.number=100;BoxGun.pdg=211;BoxGun.eta[0]=-1;BoxGun.eta[1]=1;BoxGun.prange[0]=2.0;BoxGun.prange[1]=5.0' +``` + +```text +External detector CITS: 7 sensitive volume(s) selected +External detector CITS: registered sensitive volume 'ITSUSensor0' (MC volID 13, sensor 0) +External detector CITS: registered sensitive volume 'ITSUSensor1' (MC volID 61, sensor 1) +... +External detector CITS: registered sensitive volume 'ITSUSensor6' (MC volID 264, sensor 6) +CREATING BRANCH CITSHit +External detector CITS EndOfEvent: 1825 sensitive step(s) -> 849 hit(s) +External detector CITS EndOfEvent: 1862 sensitive step(s) -> 887 hit(s) +External detector CITS EndOfEvent: 1754 sensitive step(s) -> 869 hit(s) +``` + +The ITS that came back from CAD is producing hits, on the `ITS` DetID slot, in a branch called +`CITSHit`. No detector class was written and nothing was recompiled. + +## Is it the same detector? + +The cheapest answer is the radius of the hits. Run the native ITS with the same gun and the same seed + +```bash +o2-sim-serial -n 3 -g boxgen --seed 42 -m ITS -o native \ + --configKeyValues 'SimCutParams.trackSeed=true;BoxGun.number=100;BoxGun.pdg=211;BoxGun.eta[0]=-1;BoxGun.eta[1]=1;BoxGun.prange[0]=2.0;BoxGun.prange[1]=5.0' +``` + +and histogram the hit radius on both sides: + +```cpp +sqrt(ITSHit.mPos.fCoordinates.fX**2 + ITSHit.mPos.fCoordinates.fY**2) // native, in native_HitsITS.root +sqrt(CITSHit.mPos.fCoordinates.fX**2 + CITSHit.mPos.fCoordinates.fY**2) // CAD, in o2sim.root +``` + +| r (cm) | 1.9 | 2.6 | 3.4 | 4.1 | 19.1 | 19.9 | 24.4 | 25.1 | 34.1 | 34.9 | 38.6 | 39.4 | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| native | 14 | 158 | 171 | 185 | 69 | 201 | 265 | 73 | 273 | 101 | 58 | 299 | +| CAD | 74 | 270 | 346 | 362 | 166 | 207 | 348 | 53 | 197 | 187 | — | 395 | + +Every populated radius is populated on both sides, and no hit appears anywhere else: the three inner +barrel layers and the four outer ones are exactly where the native ITS puts them, to the bin. That is +the geometry check, and it passes. + +The *counts* are not the same, and should not be read as one. The two runs are not on identical +physics: a module loaded through the JSON mechanism has no detector directory and therefore no +`simcuts.dat`, so its production cuts are not the ones the ITS sets for itself, and it makes more +low-energy secondaries. Carrying the cuts across takes a cut dump from the baseline, a probe run to +learn the CAD run's own medium indices, and a remap by medium name — which is exactly what +`validation/closure/` does: + +```bash +$O2_SRC/Detectors/CADSupport/validation/closure/run_closure.sh +``` + +It runs PIPE, ITS, TPC and MAG through the same round trip, remaps the cuts, and then compares hits +and material budget between the two sides properly. Use it when you need a number; use the radius +histogram above when you need to know, in a minute, that your geometry arrived where it should. diff --git a/docs/cadtutorial/limits.md b/docs/cadtutorial/limits.md new file mode 100644 index 0000000..cbda32c --- /dev/null +++ b/docs/cadtutorial/limits.md @@ -0,0 +1,33 @@ +--- +sort: 13 +title: Limits and pain points +--- + +# Limits and pain points + +The honest list. These are the things known to catch people today, roughly in order of how often they +do it. None is a reason not to use the system, but all of them are cheaper to read about here than to +rediscover in a result. + +| What | Why it happens | What to do | +| --- | --- | --- | +| One `geom.C` per hooked thing | The macro exports a single builder hook, and that hook is what the JSON refers to. | Run the converter once per subsystem, into its own folder. They coexist happily in one JSON. | +| Media, cuts and field default to zero | A CAD file carries a material, never a medium, and the emitter uses a three-argument `TGeoMedium` which zeroes every parameter. | Pass `--in-field`. Accept transport defaults for step control, and treat production cuts as unset until you write a real detector. | +| The anchor volume must already exist | Placement is expressed inside the frame of an existing O2 volume. | Use `barrel` unless you have a reason not to, and remember it sits at cave `(0, -30, 0)`. | +| Free-form surfaces stay tessellated | Genuine B-spline *surfaces* are not supported by the exact tier at all. | Check the surface report. Recognition already recovers quadrics written as NURBS, which is the large majority of them. | +| Illegal overlaps in the CAD model | Engineering assemblies are not drawn as legal transport worlds, and parts routinely interpenetrate. | Read `CheckOverlaps`, then fix in CAD or clip the offending region. | +| Degenerate facets at coarse precision | `O2Tessellated` drops triangles that collapse to a line. | Treat it as a mesh-quality signal: lower `--mesh-prec`, or move the part onto an exact tier. | +| A surprisingly huge output directory | Meshing a metre-scale curved part at a fine chord tolerance. | Convert large models without `--mesh`, and never use the default `--mesh-prec` on something metre-sized. | +| `o2-sim` complains about a missing `externalModules` array | Cosmetic. The message is emitted even when your JSON correctly contains only `externalDetectors`. | Ignore it. | + +## One rule that is not a preference + +Run `--csg auto` conversions **strictly serially**. Parallel runs race each other and silently lose +shapes, which produces a geometry that looks complete and is not — the worst possible failure mode, +and the hardest to notice afterwards. + +--- + +Deeper material lives in `Detectors/CADSupport`: `README.md` for the complete option reference, +`doc/reference/` for the exact-surface solid, its file format and the CSG pipeline, and +`doc/known-issues.md` for open defects. diff --git a/docs/cadtutorial/materials.md b/docs/cadtutorial/materials.md new file mode 100644 index 0000000..7af9c41 --- /dev/null +++ b/docs/cadtutorial/materials.md @@ -0,0 +1,61 @@ +--- +sort: 5 +title: Give it materials +--- + +# Give it materials + +So far the geometry has shape but no substance. Without material information every volume is assigned +a dummy medium called `Default`, which is fine while you are checking that things are in the right +place and quite wrong the moment you want physics out of it. + +The normal route is the **bill of materials** that the CAD system can export alongside the geometry. +We hand that to the converter as a CSV and it matches each part's material name against a Geant4 NIST +database. The rows it looks for are mechanical part rows in this shape: + +`detector_bom.csv` + +```csv +Type,...,Part Number,Version,Name,Mass (kg),Material +CAD,Mechanical/Part,Base,AA.01,Base,,Stainless Steel +CAD,Mechanical/Part,BasePin,AA.01,BasePin,,Stainless Steel +``` + +Adding both files to the conversion is all that is required: + +```bash +o2-cad-to-tgeo my.step \ + --output-folder cad_out/mydet -o geom.C \ + --csg auto --exact-surfaces auto --mesh --mesh-prec 0.05 \ + --materials-csv detector_bom.csv \ + --bom-mass-unit kg \ + --g4-nist-json $O2_ROOT/share/CADSupport/tools/g4_nist_database/G4_NIST_DB.json +``` + +```text +Loaded Geant4 NIST DB with 309 materials from: .../G4_NIST_DB.json +Loaded 13 BOM entries from: detector_bom.csv +``` + +Matching uses a combined score of name similarity and density plausibility, which handles the fact +that engineers write “Stainless Steel” where Geant4 says `G4_STAINLESS-STEEL`. A confident match +becomes a real `TGeoMixture` carrying its element composition, radiation length and interaction +length. An ambiguous or missing one falls back to a simple material and leaves a comment in `geom.C` +naming the part — so unresolved materials stay visible and greppable rather than silently wrong. The +scoring thresholds are adjustable (`--mat-min-score`, `--mat-ambiguity-delta` and a few others), but +the defaults are usually right, and it is better to fix an ambiguous name in the BOM than to loosen +the matcher. + +One nice consequence of feeding in the BOM: where both a part mass and a CAD volume are available, +the converter derives an effective density from them. That is how a perforated bracket or a +partly-filled cable tray ends up with an honest average density instead of the density of solid +metal. + +```note +**If your model came from TGeo in the first place** + +Geometry exported out of ALICE with `o2-tgeo-to-cad` and coming back should use `--media-json` +instead. That rebuilds the original media verbatim, field by field, rather than guessing them from +names, and takes precedence over the BOM for every part it names. The +[ITS worked example](its-round-trip.md) does exactly this. +``` diff --git a/docs/cadtutorial/partial.md b/docs/cadtutorial/partial.md new file mode 100644 index 0000000..60d0184 --- /dev/null +++ b/docs/cadtutorial/partial.md @@ -0,0 +1,44 @@ +--- +sort: 4 +title: Convert only part of a model +--- + +# Convert only part of a model + +Real engineering assemblies contain far more than you want to simulate — the mounting frame, the +trolley it sits on, sometimes the building. Converting all of it wastes time and fills your geometry +with volumes no particle will ever reach, so the converter offers two independent ways of cutting a +model down. They combine freely. + +## Selecting by name + +The first is by name. `--include-name` and `--exclude-name` take regular expressions matched against +the part name stored in the CAD file, case-insensitively, and either may be repeated. Matching an +assembly takes its whole subtree along with it, which is usually what you want: + +```bash +--include-name 'Bucket' --exclude-name '^SOLID\b' +``` + +Add `--name-filter-case-sensitive` if you need the matching to respect case. + +## Selecting by region + +The second is geometric. `--clip-box` restricts the conversion to an axis-aligned box, given as +`xmin ymin zmin xmax ymax zmax` in the assembly's global frame. Note that these are **STEP file +units**, before the conversion to centimetres — so if your file is in millimetres, so is your clip +box: + +```bash +--clip-box -50 -50 -20 50 50 20 +``` + +Every solid is then classified against that box before any meshing happens. Solids fully outside are +dropped; solids fully inside are kept unchanged; and solids straddling the boundary are cut against +it with a boolean intersection, so only the part inside survives. Assemblies left with no surviving +children disappear from the output tree altogether. + +By default, subtrees that end up entirely inside the box keep their shared logical definitions, which +keeps the output compact when a part is repeated many times. If you need one distinct volume per +surviving occurrence instead — say because you want to name them individually later — pass +`--clip-deduplicate none`. diff --git a/docs/cadtutorial/passive.md b/docs/cadtutorial/passive.md new file mode 100644 index 0000000..42c67aa --- /dev/null +++ b/docs/cadtutorial/passive.md @@ -0,0 +1,63 @@ +--- +sort: 8 +title: Add passive geometry +--- + +# Add passive geometry + +With a macro in hand we can put the geometry into ALICE. The mechanism is deliberately data-driven: +two small JSON files, no code and no rebuild. We start with the simpler case — passive material such +as supports, cooling or cabling, which should scatter particles but does not record anything. That +goes into an `externalModules` array: + +`externalGeometry.json` + +```json +{ + "externalModules": [ + { + "name": "EXCV", + "title": "Excavator support structure from CAD", + "macro": "cad_out/excavator/geom.C", + "anchor": "barrel", + "placement": { + "translation": [21.01, -13.22, -19.66], + "rotation_deg": [0.0, 0.0, 0.0] + } + } + ] +} +``` + +| field | meaning | +| --- | --- | +| `name` | a short tag for the module. It must also appear in the module list below, or the module is silently skipped. | +| `macro` | the path to the `geom.C` you produced. | +| `anchor` | a volume that already exists in the ALICE geometry. `barrel` is the usual choice, and it sits at cave coordinates `(0, -30, 0)`. | +| `placement` | translation and rotation **within the anchor's frame**, in centimetres and degrees. | + +The second file is the module list, which is what actually switches the module on. The split exists +so that you can describe several modules in one geometry file and enable them individually: + +`detectorlist.json` + +```json +{ "EXTCAD": ["EXCV"] } +``` + +Then run the simulation, pointing at both: + +```bash +o2-sim-serial -n 1 -g boxgen \ + --detectorList EXTCAD:detectorlist.json \ + --extGeomFile externalGeometry.json +``` + +```text +Configured external module 'EXCV' from macro 'cad_out/excavator/geom.C' anchored to volume 'barrel' +Activating EXCV module +Setting special cuts for passive module EXCV +``` + +Those three lines mean your CAD geometry is in the simulation and particles are being transported +through it. You can list as many modules in the same array as you like. diff --git a/docs/cadtutorial/real-detector.md b/docs/cadtutorial/real-detector.md new file mode 100644 index 0000000..477f789 --- /dev/null +++ b/docs/cadtutorial/real-detector.md @@ -0,0 +1,41 @@ +--- +sort: 10 +title: Grow it into a real detector +--- + +# Grow it into a real detector + +```warning +**Not yet exercised end to end** + +Everything before this page has been run, with its output pasted from a real terminal. This route +follows from how `ExternalDetector` and the built-in detectors are written, but no detector has +yet been built this way. Treat it as a design rather than a recipe, and expect to debug it. +``` + +The external-detector route deliberately trades flexibility for speed: you get one generic hit type +and a borrowed `DetID`, and in exchange you get results the same afternoon. Once a study turns into a +real subdetector you will want your own hit class, your own digitisation and a `DetID` of your own — +and none of that requires giving up the CAD import. The generated geometry simply becomes one step +inside an ordinary O2 detector. + +Three changes to a normal detector implementation are involved: + +1. **Build the geometry from the macro instead of by hand.** Copy `geom.C` into your detector's + simulation directory and call its builder hook from `ConstructGeometry()`, in place of the + `new TGeoTube(...)` code you would otherwise write. Keep the `.bin` payloads beside it and install + them with the detector's data files, since the macro resolves them relative to itself. +2. **Register your own sensitive volumes.** Call `AddSensitiveVolume()` for the volumes the macro + created, using the names the converter derived from the CAD part names. Print them once from + `geom.root` and pin them down in code, because a rename in CAD would otherwise quietly unregister a + sensor. +3. **Write your own hits.** Implement `ProcessHits()` with your own hit class and your own `DetID`, + exactly as any hand-written detector does. Nothing about the geometry's CAD origin constrains this. + +Two things come back the moment you take this step, both of which the external-detector route cannot +offer: `initFieldTrackingParams()` called from your own `createMaterials()`, and +`SetSpecialPhysicsCuts()` reading a real `simcuts.dat` from your detector's data directory. That +closes the gap described under [Field and cuts](field-and-cuts.md). + +The payoff is that re-running the converter after a CAD change regenerates only the geometry. Your +detector code stays untouched, which is the whole point of importing rather than transcribing. diff --git a/docs/cadtutorial/representation.md b/docs/cadtutorial/representation.md new file mode 100644 index 0000000..79b9606 --- /dev/null +++ b/docs/cadtutorial/representation.md @@ -0,0 +1,83 @@ +--- +sort: 3 +title: How a part is represented +--- + +# How a part is represented + +You have just run a conversion where every part came out exact, which is a good outcome but not an +automatic one. It is worth understanding what the converter was choosing between, because on a real +detector those choices decide both how faithful your simulation is and how fast it runs. + +The difficulty is that CAD and TGeo describe solids in different languages. CAD describes a body by +its boundary surfaces — this face is a piece of a cylinder, trimmed by these curves. TGeo describes a +body by combining primitives — a tube minus a box, say. Neither language is a superset of the other, +so there is no single translation that always works. The converter therefore carries three different +answers and picks the best available one **for each leaf solid independently**. + +```mermaid +flowchart TD + A["my.step
CAD assembly"] --> B["o2-cad-to-tgeo
per leaf solid"] + B --> C["1 · CSG primitives
TGeoTube, booleans — exact"] + B --> D["2 · Exact surfaces
O2BVHSurfaceSolid — exact"] + B --> E["3 · Triangle mesh
O2Tessellated — fallback"] + C --> F["geom.C
+ binary payloads"] + D --> F + E --> F +``` + +The three are complementary rather than competing, and all of them end up in the same `geom.C`. +Nothing is ever lost along the way: a part that resists exact description still ships as a mesh, so a +conversion always produces a complete geometry. + +| Tier | What it is | Exact | Covers | Flag | +| --- | --- | --- | --- | --- | +| **CSG** | Native ROOT shapes — `TGeoTube`, `TGeoBBox`, `TGeoCone` and booleans of them | Yes | Mechanical parts that really are primitives. Fastest to navigate and smallest on disk, so it is tried first. | `--csg auto` | +| **Surfaces** | The part's real trimmed boundary faces carried into TGeo as `O2BVHSurfaceSolid`, with a bounding-volume hierarchy for ray queries | Yes | Anything whose faces are planes, cylinders, cones, spheres or tori, however complicatedly trimmed. | `--exact-surfaces auto` | +| **Mesh** | A triangle mesh as `O2Tessellated` | No | Everything else, as the fallback. Genuinely free-form surfaces end up here. | `--mesh` | + +The difference is easiest to see rather than describe. Below, the same model is converted twice: once +to triangles alone at a coarse tolerance, and once with the full cascade, coloured by which tier +carried each part. + +| Tessellated only | The cascade, by tier | +| --- | --- | +| ![The excavator arm converted to triangles only, showing faceted, polygonal silhouettes on the cylindrical rams.](images/excavator_mesh_only.png) | ![The same model with the full cascade: hydraulic rams and pins in green for CSG, machined bodies in blue for exact surfaces.](images/excavator_cascade.png) | + +On the left the cylinders have visibly polygonal silhouettes and flat shading bands — that is the +approximation you are accepting. On the right the rams and pivot pins were recognised as unions of +tubes and the machined bodies carried as their exact trimmed surfaces, so the curves are curves. Both +images are cast through the TGeo navigator with the same camera. + +In practice one asks for all three and lets the converter decide, which is what the `auto` values in +the earlier command did. Each of `--csg` and `--exact-surfaces` accepts three settings, and the third +is more useful than it looks: + +- `off` — never use this tier. This is the default for both, so a bare conversion gives you meshes + only, which is the left-hand picture above. +- `auto` — use it wherever it is accepted, and fall through quietly elsewhere. +- `required` — stop with a report if any part cannot be represented this way. Use it when you want to + *know* your geometry is exact rather than hope so. + +One thing to trust here: a part is only accepted as CSG when OpenCascade's symmetric-difference volume +against the original solid falls inside the model's own tolerance. The recogniser is never allowed to +be approximately right, which is why `dV_sym=0` keeps appearing in the evidence column. + +## Mesh precision, and one way to fill a disk + +When a part does fall through to the mesh tier, `--mesh-prec` sets both the linear deflection (in +model units) and the angular deflection (in radians) of the mesher: lower is finer and slower. For a +desk-scale part `0.05` is a reasonable default. For anything metre-scale you should be careful, +because the cost grows quickly with size — the default `0.1` applied to a two-metre sphere has +produced a **22.9 GB** output directory. The right move for large models is to leave `--mesh` off +entirely and let the two exact tiers carry them. + +```warning +**`--mesh-solid tgeo` does not navigate** + +The mesh tier defaults to `--mesh-solid o2`, which emits `o2::base::O2Tessellated` and needs the +O2 environment to load. The alternative, `--mesh-solid tgeo`, emits ROOT's own `TGeoTessellated`, +which implements none of `Contains`, `DistFromInside`, `DistFromOutside` or `Safety`. Every such +volume is then transported as its **filled bounding box**, silently and with no warning. Only +reach for it when the macro must load outside O2 and will never have a particle sent through it. +```