Skip to content

Shared today

Both apps — SlicerApp and SimulatorApp — are already thin shells over two shared assemblies. Nps.Shell owns window chrome, panels, viewport plumbing, keybindings, and the paint helpers. Nps.EngineHost owns the managed API of the native engine: the ABI surface plus two session-shaped facades. Everything on this page is HOW IT IS (verified code truth); each claim carries a file:line citation.

The framing that holds across all of it: these are operational concerns — host lifecycle, layout, input plumbing, diagnostics, engine interop — and they are correctly owned by one deep module each. Apps call the shared interfaces; they do not re-implement windowing glue or P/Invoke.

Shell inventory

Every control, helper, and facade in Nps.Shell, with the public surface each app actually touches. All paths are under /home/christoph/github/nps/src/Nps.Shell/.

Type Path Public surface
MainWindowShell Controls/MainWindowShell.axaml.cs Region properties LeftRegion / ViewportRegion / InspectorRegion / StatusRegion (lines 44–78); TitleText / StatusText (81–97); IsLeftExpanded / IsRightExpanded with width memory (100–116, 324–438); IsPlaybackTabVisible (123–131); owns Activity + Panels (40–41); ViewportPointer* events (35–37); AttachViewportHandlers() (256–276); auto-discovers inspector panels by name at load (218–247) and shows "overlays" by default (250).
ActivityBarHost Controls/ActivityBarHost.cs Register(key, button) (66), HandleClick (80), TabClicked event (58).
PanelHost Controls/PanelHost.cs Register(key, panel) (58), Show(key, onShown?) (73).
ViewportToolbar Controls/ViewportToolbar.cs Camera preset / ZoomFit / travels / extrudes / layer up-down events (18–27); button and toggle accessors for paint-flag state (29–38).
OverlayPanel Controls/OverlayPanel.axaml.cs Title, LegendLow, LegendHigh, StatsText, IsOverlayActive, SelectedMode styled properties (12–64); ModeChanged event (66).
FindingsPanel Controls/FindingsPanel.axaml.cs SetFindings (94), TryGetCachedItem (123), UpdateDetail (142), FindingSelected event (165); detail styled properties for severity/category/location/message/suggestion/warp (183–262).
PlaybackPanel Controls/PlaybackPanel.axaml.cs Play/step/speed/auto-pause/scrubber events (91–121); MoveCount (198), SimIndex (231), IsPlaying (256), Speed (272), AutoPause (283), SetMarkers (303). A pure UI surface — no timer, no engine calls.
FindingItem Controls/FindingItem.cs View-model row: FindingId, Layer, MoveIndex accessors (104–116) projected from the ABI summary.
FindingFormatter Controls/FindingFormatter.cs ProjectFromSession (175), RefreshDetail (206), FromSummaryAndDetail (222); severity/category label and brush helpers (74–144).
FindingsFilterState Controls/FindingsFilterState.cs Immutable filter-chips model; Matches(item) (95), severity/category toggles (113–122).
GcodeToolpathPainter Controls/GcodeToolpathPainter.cs RenderTo(canvas, viewport, w, h, maxLayer, showTravels, showExtrudes, maxMoveIndexExclusive, findingMoveIndices?) (85–94). The maxMoveIndexExclusive parameter exists so Simulator can cap the path at the playhead.
MeshCanvasPainter Controls/MeshCanvasPainter.cs RenderTo(canvas, mesh, …) (112); MaxTrianglesPerPlate cap (87). Simulator's mesh background path.
ShellKeybindings Controls/ShellKeybindings.cs Handle(e, actions) (82) with an Actions struct of app-supplied delegates: TogglePlay, StepForward, StepBackward, ToggleInspector (58–70).
TimelineMarkerHelper Controls/TimelineMarkerHelper.cs Fraction(moveIndex, moveCount) (58), BuildMarkers (87) — scrubber marker geometry.
ViewportController ViewportController.cs Camera/sim/overlay/build/moves facade over the engine viewport (117): SetMoves (240), SetLayerVisibility (252), SetCameraPreset (294), ComputeZoomFit (380), SetSimulationState (489), ApplyOverlayOverlayStats (564), BuildBuffers (657), TryGetLastMovePosition (694). Value types CameraState (72), CameraPreset (82), OverlayStats (99).
FixtureLocator FixtureLocator.cs FindPrimaryBenchy3Mf() (60), PrimaryBenchyFileName const (41).
StatusSink StatusSink.cs Emit (57), Format (44), Prefix = "[SlicerUI]" (36).
ShellTrace ShellTrace.cs InstallStderrSink() (45) — stderr/process plumbing.
Themes Themes/ Colors.axaml, Brushes.axaml, Spacing.axaml, Icons.axaml, SeverityPalette.cs, merged by Theme.axaml.

A known branding leak inside the shared surface

StatusSink.Prefix is the constant "[SlicerUI]" (StatusSink.cs:36) and both apps emit through it — Simulator's status lines carry the other app's name. The constant itself is the right kind of sharing (one place to fix); the value is wrong for one consumer. See the duplication analysis for the full list of mis-shares.

EngineHost facades

Nps.EngineHost is the host border: the single managed assembly that knows the native library exists.

  • NpsAbi (/home/christoph/github/nps/src/Nps.EngineHost/NpsAbi.cs:41) is the sole P/Invoke surface. Every one of the 62 [DllImport] attributes in src/ lives in this file, along with every Nps* POD mirror, named ABI enum, and wire-id constant (file header, lines 1–28). Mirrors are kept in lockstep with the C headers and verified by Marshal.SizeOf tests.
  • EngineSession (EngineSession.cs:67) is the session-shaped facade over one engine instance: lifecycle, LoadModel (202), GenerateDemoToolpath (253), Analyze (332), findings summary/detail (376, 407), warp grid (421), overlay value lookup (486). Its header states the scope rule explicitly: no viewport creation, no UI types (lines 33–39).
  • MeshController (MeshController.cs) wraps the engine's mesh query surface so the render loop iterates plate geometry from owned managed buffers instead of touching the raw handle (file header, lines 1–50). It holds a reference to the session without owning its lifetime.
  • SafeEngineHandle / SafeGcodeViewportHandle release native handles deterministically on dispose.

Both apps reference the same three pieces — there is one EngineSession facade and two app shells. Apps never see the wire format; the engine's design decisions (struct layouts, step order) stay hidden behind the facades.

flowchart LR
    Slicer[SlicerApp] --> Shell[Nps.Shell]
    Sim[SimulatorApp] --> Shell
    Shell --> Host[Nps.EngineHost]
    Slicer --> Host
    Sim --> Host
    Host --> ABI["NpsAbi: sole DllImport home"]
    ABI --> Engine[NPSEngine native]

Six parameterization points

The shell is shared; the apps differ through exactly six knobs. These are the complete set of per-app adjustments — everything else an app does is event wiring and region content, which is the app's own product code.

# Knob Slicer Simulator Citation
1 IsPlaybackTabVisible False — hides the playback activity tab and skips PanelHost registration for "playback" default True SlicerApp/MainWindow.axaml:20; MainWindowShell.axaml.cs:123–131, 172–188, 241–246
2 TitleText "NPS Slicer | G-code Preview + Analysis | Mod+L:inspector" "NPS Simulator | Viewport + Overlays + Findings + Playback (P:play Mod+L:inspector)" SlicerApp/MainWindow.axaml:21; SimulatorApp/MainWindow.axaml:21
3 Region fills Thick left sidebar (slice params, warp grids); viewport; two inspector panels Thin left sidebar; viewport; three inspector panels SlicerApp/MainWindow.axaml:23–92; SimulatorApp/MainWindow.axaml:23–60
4 Panel naming contract PanelOverlays (line 83), PanelFindings (line 87); no playback panel PanelOverlays (48), PanelFindings (52), PanelPlayback (58) auto-discovery probes those exact names at MainWindowShell.axaml.cs:222–229
5 Keybinding actions status-message stubs for play/step (honest product difference) real play/step handlers both call ShellKeybindings.Handle with their own Actions (ShellKeybindings.cs:58–82)
6 Event wiring apps wire toolbar, overlay, findings, selection handlers themselves same the shell exposes events; it deliberately does not auto-wire app behavior

One known gap in the naming contract

FindingsPanel is probed as "PanelFindings" with a fallback to the legacy "FindingsList" name (MainWindowShell.axaml.cs:227–228) for apps that have not migrated. Both current apps use the new name.

Themes and tokens

Both apps merge the identical resource dictionary: avares://Nps.Shell/Themes/Theme.axaml in SlicerApp/App.axaml:10 and SimulatorApp/App.axaml:10. Theme.axaml merges Colors.axaml, Brushes.axaml, Spacing.axaml, and Icons.axaml; SeverityPalette.cs resolves the Severity.Brush.* resources that FindingFormatter consumes via SeverityPalette.BrushFor (Controls/FindingFormatter.cs:230), while the category brushes come from the Category.Brush.* keys in Brushes.axaml via FindingFormatter.CategoryBrush (109). One token set, two apps, zero per-app theme files.