nodehammer
Drive the nodehammer CLI and author detector-specific TOML/Lua scene configs for geometry selection, materials, tessellation, and export. Convert and inspect HEP geometry (DD4hep XML, ROOT TGeo, .nhb), validate configs, package .nhproj archives, and render the viewer. Use for nodehammer, its config format, nhb, nhr, .nhproj, nodehammer.toml, detector geometry conversion, wedge cuts, NH#### diagnostics, or `uvx nodehammer`.
- License 许可
- MIT
- 最近更新
- 2026-11-08
- Stars
- ★ 2
- Forks
- 1
01 前言 Front Matter
- name
- nodehammer
- description
- Drive the nodehammer CLI and author detector-specific TOML/Lua scene configs for geometry selection, materials, tessellation, and export. Convert and inspect HEP geometry (DD4hep XML, ROOT TGeo, .nhb), validate configs, package .nhproj archives, and render the viewer. Use for nodehammer, its config format, nhb, nhr, .nhproj, nodehammer.toml, detector geometry conversion, wedge cuts, NH#### diagnostics, or `uvx nodehammer`.
02 资源文件 3
- 参考文档references/config-format.md23.2 KB
- 参考文档references/detector-config.md10.7 KB
- 参考文档references/lua-config.md5.0 KB
03 SKILL.md 正文
nodehammer
A pipeline that imports detector geometry, applies a config that selects and styles parts of it, and writes it out — as a mesh (glTF/OBJ), as an intermediate scene, as a JSON report, or into an interactive viewer.
Same program either way: the compiled nodehammer executable and the
nodehammer command from the Python wheel are one implementation (cli::run
inside libnodehammer), so every command here behaves identically under
uvx nodehammer. Differences that do exist are noted as such.
The output contract — read this before parsing anything
stdout is the answer. stderr is everything about producing it.
stdout carries the thing you would substitute into the next command: the flattened document, the JSON report, the path to the archive that was written, the URL being served. stderr carries diagnostics, progress and summaries.
archive=$(nodehammer project pack -c scene.toml -i det.root -o det.nhproj)
That yields the archive path, not a report about one. Two consequences:
- Never parse stderr. It is prose for a human and is not a stable format.
-qsilences narration only. Diagnostics, and the line a failing command prints before it exits non-zero, are never affected. A flag that hid errors would be a trap.-vrestores narration;-q/-vmay be typed before or after the subcommand.
When calling nodehammer from a script, quiet is already the default for the
library entry point; the executable defaults to narrating because a person typed
it.
Commands
Five groups. Every one takes -h.
convert |
import → config → write. The output extension decides how far the pipeline runs |
inspect summary|tree|tags |
report on a geometry file; --output-format json |
config validate|flatten |
check a config, or resolve it to one self-contained TOML |
project pack|publish|info |
build, publish and read .nhproj archives |
viewer open|serve|shot|bench |
the 3D viewer, in four postures |
Option spellings are consistent across the tree: -i/--input, -c/--config,
-o/--output, --input-format, --output-format, --color, --depth/-d,
--filter/-f mean the same thing everywhere they appear.
Commands that block — do not run these unprompted
| Command | What it does | Use instead |
|---|---|---|
viewer open |
opens a native window and blocks until the user closes it | viewer shot -o out.png |
viewer (bare, with a path) |
same — open is the default mode |
viewer shot |
viewer serve |
starts an HTTP server, opens a browser, and blocks | viewer serve --no-browser in a background job, or project publish for static output |
viewer bench |
runs a GPU benchmark; needs a display | only when the user asked for numbers |
To see what a scene looks like, nodehammer viewer shot -i det.nhb -c scene.toml -o shot.png --shot-width 1920 renders one PNG once the scene settles
and quits. That is the agent-appropriate way to check a geometry visually. It
still needs a working GPU context, so it will fail on a headless machine with no
display.
shot and bench render from -i geometry plus an optional -c config, not
from a project: they reject a call without --input (NH0900 … needs --input)
even when a .nhproj positional is given. To screenshot an archive, pass the
config and geometry it was packed from.
Everything else in the tree runs to completion and exits.
convert
nodehammer convert -i det.root -c scene.toml -o det.glb
nodehammer convert -i det.root -o scene.nhb # stop at the semantic scene
nodehammer convert -i det.root -o a.glb -o b.obj # several outputs, one import
The output extension picks the writer and the depth of the pipeline:
.nhb,.json— the semantic scene. No tessellation. Fast..glb/.gltf,.obj,.nhr, render-JSON — tessellated first.
--timing prints per-step wall clock, --size-report a payload breakdown; both
to stderr, and both survive -q because naming a report is asking for it.
--strict turns warnings into errors. --wedge-cut START END performs a real
Manifold boolean, removing the azimuthal sector from START to END degrees
measured from +x counter-clockwise — this is geometry, not a display effect.
inspect — the machine-readable one
nodehammer inspect --output-format json summary -i det.root
nodehammer inspect --output-format json tree -i det.root --depth 3 --filter '*/Pixel*'
nodehammer inspect --output-format json tags -i det.root
Text and JSON are not the same content in two skins. The text view draws
tree lines, samples a tag key down to five values once it passes ten, and
appends a "(n shown, m filtered)" footer. Parse the JSON, which carries the
whole set and no drawing. Every document is stamped "schema": 1.
// summary
{ "schema": 1, "kind": "summary", "format": "tgeo",
"nodes": 12043, "shapes": { "Box": 900, ... },
"materials": ["Air", ...], // sorted
"diagnostics": { "warnings": 3, "errors": 0 } }
// tree
{ "schema": 1, "kind": "tree", "shown": 40, "filtered": 12003,
"maxDepth": 3, // null when no limit applied — present, not absent
"filter": "*/Pixel*", // null when none
"nodes": [ { "path": "/World/Det", "name": "Det", "depth": 1,
"children": 4, "leaf": false, "tags": {...} } ] }
// tags
{ "schema": 1, "kind": "tags", "nodeCount": 12043, "nodesWithTags": 800,
"keys": { "subdetector": ["pixel", "strip"] } } // every value, not a sample
maxDepth and filter are present-and-null rather than absent, so read them
unconditionally. tags is absent from a tree node that has none.
config
Configs come in two spellings, and the file decides which: .toml (with an
include chain) or .lua (a script that is evaluated). Both commands take
either — do not look for a separate Lua command, there isn't one.
nodehammer config validate -c scene.lua
nodehammer config flatten -c scene.toml -o flat.toml # -o optional; stdout otherwise
config flatten is the tool to reach for whenever you need to know what a config
actually says: it resolves the include tree (or runs the script) and emits one
self-contained TOML that needs no companion files. Its output is round-trippable.
For writing or editing a config, read the config format: TOML structure, predicates, selection, rule precedence, materials, tessellation, exports, and includes. For Lua, also read the Lua builder, including its composition rules and a runnable equivalent of the TOML example.
For a config for a particular detector, start with the detector authoring workflow. Inspect the actual geometry before choosing paths or tags; a valid config can match nothing. Use TOML for direct declarations and Lua when repetition benefits from loops or helpers, preserving an existing format or the user's preference. When adding internal detail or investigating flicker/z-fighting, follow that workflow’s overlap checks: material-filled mothers and their daughters can render competing surfaces even when conversion reports no diagnostics.
convert -c takes either spelling too, so a Lua config needs no flattening first.
project — .nhproj archives
A .nhproj is a ZIP holding a config, the geometry it names, and a manifest.
nodehammer project pack -c scene.toml -i det.root -o det.nhproj
nodehammer project info det.nhproj
nodehammer project publish det.nhproj -o site/ # static site, no server
nodehammer.toml inside an archive is a reserved key. It is the manifest
naming the entry config and geometry. project pack refuses rather than
overwriting when a config is actually named that, or an include resolves to it.
Do not hand-write it, and do not unzip an archive, edit, and re-zip — repack from
sources instead.
project publish produces a directory you can serve with any static file server
(GitHub Pages included). Prefer it over viewer serve for anything an agent
produces, because it terminates.
Inherent limitations — not bugs to work around
The importable set is smaller than the file types people assume. Built in
unconditionally: .nhb / .nhb.zst, .json / .json.zst, and the synthetic
generator. Compile-time gated: .root (TGeo), DD4hep XML, and .gdml through
Geant4 (--input-format gdml). GDML requires NODEHAMMER_WITH_GEANT4=ON and a
Geant4 installation with GDML support. It uses G4GDMLParser, converts lengths
to centimetres, preserves shared volumes and copyNumber tags, and keeps
booleans analytic. Other solids use Geant4 polyhedra when no direct mapping
exists. Replicas and parameterisations are rejected explicitly. Schema validation
is disabled so a remote GDML schema is not needed. Geant4 material/element tables
remain process-owned; repeated imports can grow those tables.
Heavy importers are compile-time options, not runtime ones. NODEHAMMER_WITH_TGEO,
NODEHAMMER_WITH_DD4HEP, NODEHAMMER_WITH_GEANT4, NODEHAMMER_WITH_GEOMODEL are
off by default, and the released Python wheel is built without them. So
cannot determine input format for 'det.root' from a wheel install is the build
configuration, not a broken file and not a bug — the fix is a build with the
backend enabled, never a different flag. Check with
nodehammer convert -i x.root -o /dev/null and read the code, not the extension.
Some DD4hep plugins require the global detector. ALLEGRO's k4geo readout segmentation is one example. In a DD4hep-enabled native build, opt in with:
nodehammer convert -i detector.xml --dd4hep-global -c scene.toml -o detector.glb
Source the experiment's plugin environment first. The flag loads one compact
into a fresh default detector; a populated detector or repeated global import
is rejected. The Python console script and runCli expose the same flag.
readSemantic accepts .importerOptions = {{"dd4hep.useGlobalDetector", true}};
the default remains private. Python uses read_semantic(path, importer_options={"dd4hep.useGlobalDetector": True}). Unknown keys, incorrect
primitive types, and options for a different backend raise NH0105.
Applications with an existing
detector can use fromDD4hep(detector). Only the native executable installs
the exit warning and redirects plugin stdout.
If a plugin calls exit() during XML loading, the native CLI prints NH0302.
For a private import it suggests --dd4hep-global. This warning uses atexit,
so it does not cover abort(), _Exit(), or fatal signals. Successful imports
and caught exceptions do not trigger it. No separate executable is needed.
DD4hep XML has no extension of its own. It is recognised by sniffing for
<lccdd in the first 512 bytes, or by --input-format dd4hep when the sniff
fails. A generic .xml that is not DD4hep will not resolve.
viewer serve needs a wasm runtime this build cannot produce. It comes from
the separate nodehammer-web distribution, which the Python package depends on.
NH1000 means it was not found; the command prints the search ladder it walked.
--web-assets DIR points at a locally built one.
A wasm build has no project or viewer serve. There is no host to serve
from — the wasm module is the viewer.
Diagnostics
Every failure carries a stable NH#### code, greppable and safe to match on.
Ranges: NH00xx config · NH01xx import · NH02xx scene ops · NH03xx TGeo ·
NH04xx selection · NH05xx tessellation · NH06xx export · NH08xx public
API · NH09xx CLI usage · NH10xx web runtime · NH11xx project archives.
The prefix in the code's name says how far it goes: fatal (the call could not deliver), error (the result exists and part of it is wrong), warning (complete, something was assumed).
| You see | It means | Do |
|---|---|---|
NH0101 cannot determine input format for '…' |
extension unknown or backend not compiled in | check the limitations above before anything else |
NH0900 positional path must be a .nhproj archive or a directory |
viewer <path> got something else |
pass a .nhproj or a directory |
NH0900 `viewer shot` needs --input |
shot/bench were given a project, or nothing |
pass -i geometry (and -c config) |
NH0901 path not found / input file not found |
a typo, before any window opens | — |
NH1000 |
wasm runtime missing | pip install nodehammer-web, or --web-assets DIR |
NH1001 |
runtime and library disagree | rebuild both halves together |
NH1100 |
archive could not be assembled or read | project info on it; do not unzip by hand |
NH0008 drop_coincident_faces … without merge_descendants |
the option is silently a no-op | enable merge_descendants in the same scope |
Recipes
"What is in this file?" → nodehammer inspect --output-format json summary -i FILE
"What does this config actually do?" → nodehammer config flatten -c FILE
(resolves includes and Lua), then read the TOML.
"Show me the geometry." → nodehammer viewer shot -i FILE [-c CONFIG] -o /tmp/shot.png --shot-width 1920, then look at the PNG. Never viewer open.
"Make something I can share." → project pack then project publish -o site/.
"Is this config valid?" → nodehammer config validate -c FILE; exit code is
the verdict, diagnostics are on stderr, and the verdict text is on stdout.
"Write a config for this detector." → follow detector config authoring, then validate, flatten, and check the selected geometry and rendered output.
"Convert for a web page." → -o out.glb (glTF binary), not .obj.
Do not
- Do not run
viewer open, bareviewer <path>, orviewer servewithout being asked. They block until a human intervenes. - Do not parse stderr, or the text form of
inspect. Use--output-format json. - Do not unzip, edit and re-zip a
.nhproj. Repack from sources. - Do not hand-write
nodehammer.tomlinside an archive —project packowns that key. - Do not treat a missing importer as a broken file. Check the compile-time gates first.
- Do not add
-qto make output parseable. It only removes narration from stderr; stdout was already clean.
Getting this skill
It ships inside the binary and is installed with:
nodehammer skills install # into ~/.agents/skills, linked from ~/.claude
nodehammer skills install --scope project
nodehammer skills list