Back to home page

EIC code displayed by LXR

 
 

    


Warning, /firebird/packages/firebird-ng/README.md is written in an unsupported language. File is not indexed.

0001 # @dexvis/firebird-ng
0002 
0003 The Angular library of the Firebird event display: `provideFirebird()` and
0004 its `with*()` features, the display pages and `<firebird-display>`, the
0005 display services, and the code of the geometry and ROOT file web workers.
0006 An application (Firebird's own or an external experiment's) assembles its
0007 event display by composing features:
0008 
0009 ```ts
0010 // app.config.ts: provideFirebird() installs Firebird's built-ins first
0011 provideFirebird(
0012   withUrlAlias('data://', 'https://my.host/data/'),
0013 
0014   // an experiment's contributions:
0015   withEventPiece(CherenkovRingPieceFactory),                // DEX type decoder (worker-safe, @dexvis/firebird-core)
0016   withPainter(CherenkovRingPainter, { forPieceType: 'example.CherenkovRing' }),
0017   withThreeExtension(HoverInfoExtension),                   // machinery hook (scene/frame/input)
0018   withGeometryLoader(IgesGeometryLoader),                   // teach Firebird a file format
0019   withCommandHandler(MyCommandHandler),                     // extend the command vocabulary
0020   withDefaultGeometry('data://detector.root'),              // sugar for withConfigDefaults({'geometry.selectedGeometry': ...})
0021   withDataCatalog({ entries: [...], facets: [...] }),       // datasets for the data selector (presets, physics tags)
0022   withDataSelectorTab({ id: 'campaigns', label: 'Campaigns', load: () => import('./campaigns-tab') }), // a selector tab
0023   withRootGeometryRules(() => import('./rules').then(m => m.rules)),  // pre-build TGeo edits and cut lists (data)
0024   withGeometryTheme({ id: 'my-colors', load: () => import('./theme').then(m => m.ruleSets) }),
0025   withGeometryPostProcessor({ id: 'my.mirrors', load: () => import('./mirrors').then(m => m.Mirrors) }),
0026   withCameraPreset({ name: 'home', position: [0, 7000, 0], target: [0, 0, 0] }),  // pins the start view
0027   withCollisionIntro(() => import('./intro').then(m => m.BeamsIntro)),
0028   withToolbarAction({ id: 'intro', icon: 'close_fullscreen', label: 'Intro', command: { type: 'animate-collision' } }),
0029   withoutFeatures('firebird.navigation-cube'),              // drop a built-in part by feature id
0030 )
0031 ```
0032 
0033 One contribution per `with*()` call. Packs compose with
0034 `firebirdPack(id, ...)` (a replaceable, removable unit with an id) or
0035 `firebirdFeatures(...)` (no id): an experiment pack is one function returning
0036 a composed feature. See `@dexvis/firebird-epic` (`packages/epic/`, the ePIC
0037 pack the flagship installs), `src/lib/with-firebird-builtins.ts` (the
0038 built-ins as named sub-features, with their feature ids), and
0039 `@dexvis/firebird-example-extension` for a complete out-of-tree example with
0040 its own piece type and painter.
0041 
0042 ## Setting up an application
0043 
0044 The 0.1.0 betas publish under the npm dist-tag `next`. Install the library
0045 with its peers into an Angular 22 application:
0046 
0047 ```bash
0048 npm install @dexvis/firebird-ng@next @dexvis/firebird-core@next @dexvis/app-features @dexvis/shell \
0049   @angular/material @angular/cdk @angular/aria @angular/forms @angular/router three jsroot
0050 ```
0051 
0052 ```ts
0053 // app.config.ts
0054 import { provideFirebird, withAppVersion, withWorkers } from '@dexvis/firebird-ng';
0055 import { version } from '../../package.json';
0056 
0057 export const appConfig: ApplicationConfig = {
0058   providers: [
0059     provideZonelessChangeDetection(),
0060     provideRouter(routes),
0061     provideHttpClient(withFetch()),
0062     provideFirebird(
0063       withAppVersion(version),
0064       withWorkers({
0065         geometry: () => new Worker(new URL('./workers/geometry.worker', import.meta.url), { type: 'module' }),
0066         rootFile: () => new Worker(new URL('./workers/root-file.worker', import.meta.url), { type: 'module' }),
0067       }),
0068       withMyExperiment(),
0069     ),
0070   ],
0071 };
0072 
0073 // app.routes.ts: the pages load lazily from the display entry
0074 import { firebirdRoutes } from '@dexvis/firebird-ng';
0075 
0076 export const routes: Routes = [
0077   { path: '', redirectTo: '/display', pathMatch: 'full' },
0078   ...firebirdRoutes(),   // display, split-window, config
0079 ];
0080 ```
0081 
0082 - **Workers.** The application owns the two worker entry modules, so that its
0083   build bundles them (a `new Worker(new URL(...))` inside a prebuilt library
0084   is not bundled). Each entry is one call: `runGeometryWorker(self)` from
0085   `@dexvis/firebird-ng/workers/geometry`, `runRootFileWorker(self)` from
0086   `@dexvis/firebird-ng/workers/root-file`. Without `withWorkers()`, ROOT
0087   geometry and in-browser ROOT conversion fail with a message naming it.
0088 - **angular.json.** The geometry worker bundles jsroot's geometry painter,
0089   which can import node-only renderers: add
0090   `"externalDependencies": ["@resvg/resvg-js", "canvas"]` to the build
0091   options. jsroot and the display entry import CommonJS modules; to silence
0092   the build's CommonJS warnings, add
0093   `"allowedCommonJsDependencies": ["jszip", "@oneidentity/zstd-js", "tmp", "jsdom", "mathjax"]`.
0094 - **index.html.** The toolbars use Material Icons ligatures: load the font,
0095   for example
0096   `<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">`.
0097   Without it the buttons show the icon names as text.
0098 - **Server config.** At startup the application fetches
0099   `assets/config.jsonc`, the server tier of the config (see Configs).
0100   Without the file the defaults apply and the console shows one
0101   "Failed to load config" error; an empty object (`{}`) is a valid file.
0102 - **Your own page.** `<firebird-display>` (from `@dexvis/firebird-ng/display`)
0103   is the canvas alone: it attaches the display to its element, follows the
0104   element's size, loads the configured geometry and events and runs the
0105   startup commands; `(attached)` tells when the scene exists.
0106 
0107 ## Entry points
0108 
0109 | Import | Holds | Load |
0110 |--------|-------|------|
0111 | `@dexvis/firebird-ng` | `provideFirebird()`, the built-ins, `firebirdRoutes()`, and everything in `/api` | In the initial bundle: stays light |
0112 | `@dexvis/firebird-ng/api` | Tokens, `with*()` features, ThreeExtension and render view contracts, config keys, the light services | Re-exported by the root; the display entry builds on it |
0113 | `@dexvis/firebird-ng/display` | Pages, `<firebird-display>`, components, display services | Lazily only (routes, `import()`): pulls three.js and Material |
0114 | `@dexvis/firebird-ng/workers/geometry`, `/workers/root-file` | The worker code and its messages | From the application's worker entry modules |
0115 | `@dexvis/firebird-ng/geometry-palette` | Named colors for geometry themes | From lazily loaded theme rule sets |
0116 
0117 The root reaches the display entry through dynamic imports only, and the
0118 display entry imports `/api`: an entry may not import an entry that imports
0119 it, so the contracts both need live in `/api`. Each worker has its own entry
0120 because a worker bundle must hold only its own code: one shared entry made
0121 the ROOT file worker carry jsroot's geometry painter (1.17 MB instead of
0122 116 kB).
0123 
0124 ## Precedence
0125 
0126 One rule for every registry: contributions keep registration order (the
0127 `provideFirebird()` arguments, depth first, after the built-ins); a later
0128 contribution with the id of an earlier one replaces it at the earlier one's
0129 position; where one entry is picked among several, the earliest wins (the
0130 first loader that claims a source, the first painter registered for a piece
0131 type). The ids: feature ids, `DataLoaderMeta.id`, `CommandHandler.type`,
0132 `EventPieceFactory.type`, `PainterMeta.id`, and the `id`/`name` of tabs,
0133 themes, post-processors, camera presets and toolbar actions (`tokens.ts`).
0134 Registries whose ids are only known at runtime apply `resolveRegistry()`
0135 from `@dexvis/app-features`; read loaders through `injectEventLoaders()` /
0136 `injectGeometryLoaders()`, which apply the id rule the raw tokens skip.
0137 
0138 ## What lives where
0139 
0140 The composition mechanism is the generic `@dexvis/app-features` package
0141 (`dexvis/app-features-ng/`): the feature type and `appFeatures()`
0142 composition (`FirebirdFeature` and `firebirdFeatures()` are the same things
0143 under Firebird names), `contributeValue()`/`contributeClass()`, the config
0144 registry (`ConfigService`, `ConfigProperty`), server config loading
0145 (`ServerConfigService`), the command bus and URL startup parsing, and the
0146 generic features `withConfigDefaults`, `withCommandHandler`, `withUrlAlias`,
0147 `withUrlShorthand` and `withoutFeatures`, feature identity
0148 (`appFeaturePack()`, the `kind`/`id` of every feature) and
0149 `resolveRegistry()`. `provideFirebird()` (`src/lib/provide-firebird.ts`) is
0150 `provideAppFeatures()` plus the built-ins (`withFirebirdBuiltins()`, which
0151 include Firebird's server config defaults from
0152 `api/src/lib/services/server-config.ts` and its URL shorthands,
0153 `withFirebirdUrlShorthands()`) and event piece factory registration.
0154 
0155 This package keeps what is Firebird's:
0156 
0157 - `api/src/lib/`: the event display tokens (`tokens.ts`) and their `with*()`
0158   functions (`firebird-features.ts`), the geometry pipeline seams
0159   (`geometry-pipeline.ts`), `ThreeExtension` (`three-extension.ts`), the
0160   render view contracts and layer constants (`views.ts`), the config keys,
0161   `BatchStatusService` (`window.firebird`), and the light services
0162   (`services/`: data catalog, data selection, URL resolution).
0163 - `src/lib/`: `provideFirebird()`, the built-ins, the built-in loaders and
0164   command handlers, `firebirdRoutes()`.
0165 - `display/src/lib/`: the services that run the display (`services/`), the
0166   components and pages, the navigation cube extension (`extensions/`).
0167 - `workers/geometry/`, `workers/root-file/`: the worker code.
0168 - `geometry-palette/`: named colors for geometry themes; a subpath of its own
0169   because everything the package root exports lands in the initial bundle.
0170 
0171 The package root re-exports the generic parts, so extensions import
0172 everything from `@dexvis/firebird-ng`.
0173 
0174 ## Painter or ThreeExtension?
0175 
0176 If it turns **data** (event pieces, fields, geometry) into visuals, it is a
0177 **painter** — time-aware, per-piece, config-driven. If it hooks the
0178 **machinery** — scene lifecycle, frame loop, input, UI — it is a
0179 **ThreeExtension**. Field lines visualizing a field map are a painter, not an
0180 extension.
0181 
0182 ## ThreeExtension lifecycle
0183 
0184 - `onSceneInit(ctx)` runs strictly AFTER the async renderer init resolved.
0185   Never touch the scene before it. Attach pointer listeners to a view's
0186   container (`ctx.mainView.container`), not to `ctx.canvas`: on multi-view
0187   pages the view containers sit above the canvas and receive the events (an
0188   `AbortController` is the documented cleanup idiom).
0189 - `onFrame(ctx)` runs inside the render loop, on rendered frames. Keep it
0190   cheap: no allocation. One that throws is logged and not called again; the
0191   loop continues.
0192 - `onEventLoaded(event)` fires once for every event that becomes the painted
0193   one: the first event of each load, and each switch to another event.
0194 - `onDispose()` runs once at application teardown — remove listeners and
0195   objects. Leaving a display page does not call it: the scene and the
0196   extensions outlive pages, and the loop only stops until the next display
0197   page attaches.
0198 - Heavy extensions register with `withLazyThreeExtension(() => import(...))` —
0199   they load in their own chunk after the scene is up, and receive the
0200   `onSceneInit` call they missed.
0201 
0202 ## Render views and overlays
0203 
0204 The scene renders through **render views** (`ctx.views`, `ctx.mainView` on
0205 `SceneContext`; the `RenderView` interface). `ThreeService` keeps the one
0206 Scene and the one render loop; each view owns its DOM container, cameras,
0207 OrbitControls, picking and viewport rectangle inside the shared canvas. The
0208 main display is `views[0]`; the quad-projection page adds three orthographic
0209 views over the same scene.
0210 
0211 - **Add a view**: `ctx.addView({ name, container, orthographic,
0212   fixedDirection })`: the container must sit above the shared canvas (see
0213   `display/src/lib/pages/split-window` for the reference layout). Remove it
0214   with `ctx.removeView(view)` when your page or panel goes away.
0215 - **A view with its own geometry cut** (experimental): create the slice with
0216   `ctx.createGeometrySlice()` and pass `geometrySlice` and `clipPlane` to
0217   `addView`; the view then renders the slice copy (its camera gets
0218   `GEOMETRY_SLICE_LAYER`). Edits to the original geometry reach the copy
0219   only through `ctx.rebuildGeometrySlice()`.
0220 - **Draw on top of a view**: `view.addOverlay({ render, onViewResize,
0221   onViewContainerChange, dispose })`. `render(view)` runs every frame after
0222   the view's scene render; set your own viewport/scissor from
0223   `view.viewportRect` and restore what you change. The navigation cube
0224   (`display/src/lib/extensions/viewport-gizmo.extension.ts`) is the built-in
0225   consumer of this seam.
0226 - **Add objects to the event data**: `ctx.addEventObject(object)` puts them
0227   under `sceneEvent` on `EVENT_DATA_LAYER`, so every view draws them and
0228   tracks-on-top views draw them over the geometry. The layer constants
0229   (`GEOMETRY_MAIN_LAYER`, `GEOMETRY_SLICE_LAYER`, `EVENT_DATA_LAYER`) are
0230   exported.
0231 - Cameras and `camera.up` handling are per view. When you change a view's up
0232   vector, use `view.setCameraUp()`: OrbitControls captures the up axis at
0233   construction and must be resynced.
0234 
0235 ## Rendering rules (read before writing onFrame)
0236 
0237 1. **No per-frame polling as change propagation.** State changes travel
0238    through signals and effects; `onFrame` is for animation only. The loop
0239    renders ON DEMAND by default (config `rendering.mode`), so code that
0240    polls app state every frame does not merely waste cycles — it never runs
0241    while the display is idle.
0242 2. **Changed something renderable? Call `ctx.invalidate()`.** The next
0243    animation frame renders. Without it, your mutation appears only when
0244    something else triggers a render. For an animation: seed one
0245    `invalidate()` when it starts and call it again from every update — the
0246    chain ends by itself when the animation stops updating. `onFrame` runs
0247    only on frames that render; its `deltaTime` is the time since the
0248    previous rendered frame.
0249 3. **The render loop lives in exactly one place** (`ThreeService.renderLoop`).
0250    Do not start your own rAF chain against the same scene; use `onFrame`.
0251 
0252 ## Bundle discipline
0253 
0254 Anything referenced from `app.config.ts` lands in the initial bundle. Painter
0255 classes pull three.js material code: register built-in-style painters with
0256 `withLazyPainter(type, () => import(...))`. Loaders and command handlers that
0257 need display services load `@dexvis/firebird-ng/display` with a dynamic
0258 `import()` inside the method that needs them (see `src/lib/builtin-loaders.ts`
0259 for the pattern). A pack may inject `EventDisplayService`, `DataModelService`
0260 and `SelectionService` from the display entry that way, or statically in its
0261 own lazily loaded code.
0262 
0263 ## Loaders
0264 
0265 A loader returns data; the display shows it.
0266 
0267 - `canLoad(source)` claims a source by its name (extension, scheme); the
0268   first registered loader that claims it loads it. A `.root` file is
0269   ambiguous (geometry or events): the data selector probes it and asks
0270   `canLoadContent(probe)` to pick the field, geometry or events.
0271 - `load(source, context)` (geometry) resolves to the geometry root, in the
0272   loader's `millimetersPerUnit` (default 1; ROOT TGeo: 10, centimeters).
0273   `loadEvents(source, context)` (events) resolves to a `DataExchange`.
0274 - `context.resolveUrl(url)` turns aliases (`asset://`, a pack's alias) and
0275   server paths into a URL `fetch()` reads. `context.signal` aborts when a
0276   newer load of the same kind starts: stop and reject with its reason.
0277 - Reject with an `Error` whose message says what went wrong (HTTP status,
0278   unsupported version): the display shows it to the user and lists it in
0279   `window.firebird.errors`.
0280 
0281 `EventDisplayService.openGeometry(source)` and `openEvents(source)` run this
0282 for every source: the config keys, picked files, the data selector, the
0283 `open-geometry`/`open-dex` commands. They resolve to null when a newer load
0284 replaced theirs.
0285 
0286 ## Configs
0287 
0288 Declare configs through `ConfigService.declare({ key, default, label, ... })`.
0289 Every declared key obeys the source precedence
0290 `defaults < server(config.jsonc) < localStorage < URL (?config.key=value) < runtime`,
0291 where URL values win for the session without being persisted. Read typed
0292 server config fields through `ServerConfigService<ServerConfig>`
0293 (`inject<ServerConfigService<ServerConfig>>(ServerConfigService)`).
0294 
0295 - Declare each key from ONE exported schema object and import it wherever
0296   the key is read or written. The first declaration decides the default and
0297   the validator; in development builds a later declaration with a different
0298   default or validator logs a `[ConfigService]` warning. Firebird's own keys
0299   live in `config-keys.ts` (`ROOT_EVENT_RANGE_CONFIG`, `GEOMETRY_URL_CONFIG`,
0300   ...), exported from this package.
0301 - Give URL-valued keys the `isPersistableUrl` validator: it rejects `blob:`
0302   and `data:` URLs from every source, including values an older build saved.
0303 - A reset (`property.setDefault()`) removes the saved value and the URL
0304   override; the value falls back to the server value or the default.
0305 
0306 ## Painter metadata and knobs
0307 
0308 A painter declares itself with `static meta: PainterMeta`:
0309 
0310 ```ts
0311 export class TrajectoryPainter extends EventPiecePainter {
0312   static meta: PainterMeta = {
0313     id: 'trajectory-lines',
0314     forPieceTypes: ['PointTrajectory'],
0315     label: 'Trajectory lines',
0316     configs: [
0317       { key: 'colorMode', default: 'pid', options: ['pid', 'momentum', 'solid'], label: 'Coloring' },
0318       { key: 'lineWidth', default: 30, min: 1, max: 300, label: 'Line width [mm]' },
0319     ],
0320   };
0321 }
0322 ```
0323 
0324 - **Selection**: several painters may register for one piece type; the config
0325   key `painters.byPiece.<pieceName>` picks which one paints each piece: from
0326   the panel, a yaml file, or a URL. The default is the first painter in
0327   registration order, whatever order lazy painter chunks load in.
0328 - **Knobs** live under `painters.byPiece.<pieceName>.<key>` with normal config
0329   precedence. The right-pane painter panel auto-renders them from the meta —
0330   no per-painter UI code. The knob names `visible` and `time` are reserved
0331   (`RESERVED_PAINTER_KNOB_KEYS`); registering a painter that declares one
0332   throws.
0333 - The instance reads knobs through `this.config` (a `PainterConfigView`) and
0334   restyles existing objects in `onConfigChanged()` — knob changes must apply
0335   live, never require a rebuild. Without a config system (workers, scripts)
0336   the declared defaults apply; painter code stays DI-free.
0337 
0338 ## Selection
0339 
0340 Painters register their scene objects per entity as they build
0341 (`registerEntityObject(index, object)`), which powers `SelectionService`:
0342 a 3D click resolves the picked object back to `(pieceName, entityIndex)`,
0343 and a model-tree click resolves the entity to its objects for highlighting
0344 (`highlightEntity`/`unhighlightEntity`). Pieces describe their entities to
0345 the model tree via `entityCount`, `entityLabel(i)` and `entityRefs(i)` —
0346 override them in custom piece types to get meaningful labels and navigable
0347 reference links for free.
0348 
0349 ## Commands
0350 
0351 Serializable commands drive the display from URLs, server config and batch:
0352 `?dex=<url>&event=2`, `?cmd=type:arg;type:arg`, config.jsonc
0353 `startupCommands`, `pyrobird screenshot --commands "..."`. Add your own with
0354 `withCommandHandler` — implement `type`, `execute(cmd)`, and optionally
0355 `fromUrlArg(arg)` for the URL grammar. A URL shorthand
0356 (`withUrlShorthand('dex', 'open-dex')`) is an alias for `?cmd=<type>:<value>`,
0357 so `fromUrlArg` builds the shorthand's command too. Batch tools await
0358 `window.firebird.ready` (geometry loaded, startup commands done).