Skip to content

Slicer — how it is

Everything on this page is HOW IT IS: verified against src/SlicerApp code, cited file:line. Where the repo states an intent (README, parity notes), it is marked STATED. The Slicer is the lighter of the two apps; the Simulator is the active build.

Purpose

STATED (README): the Slicer should "Load a mesh, compute scalar fields, extract layers and toolpaths, preview and export G-code for non-planar printing" (README.md:14).

HOW IT IS today: the app loads one hard-wired fixture, generates a demo toolpath — not a production slice — and previews it with findings and overlays. The code says so itself, twice: a comment // Demo toolpath, not a production slice. (MainWindow.axaml.cs:260) and the final status string "Slicer parity loaded (DEMO TOOLPATH — not a production slice): …" (MainWindow.axaml.cs:316). The window title is "NPS Slicer - G-code Preview + Findings + Overlays (f8v.6.1 parity)" (MainWindow.axaml:7) — preview, findings, overlays. Export is not implemented.

Shell regions it fills

The window is the shared MainWindowShell with Slicer-owned content plugged into its regions (MainWindow.axaml; layout confirmed by the AXAML comment at MainWindow.axaml:11–14):

Window (SlicerApp.MainWindow)
└─ MainWindowShell [IsPlaybackTabVisible=False]
   ├─ LeftRegion:      layers slider + slice params + Load/Apply + mesh/warp labels
   ├─ ViewportRegion:  Canvas 600x600 + ViewportToolbar
   ├─ InspectorRegion: OverlayPanel + FindingsPanel (Playback omitted)
   └─ StatusRegion:    StatusBar bound to Shell.StatusText

Two things define the Slicer's silhouette:

  • A thick left bar. The left region carries the layer slider, all five slice parameters, both action buttons, and the mesh/warp info labels (MainWindow.axaml:23–69). It is bound to the params view model via left.DataContext = _sliceParams (MainWindow.axaml.cs:68–72).
  • No playback tab. IsPlaybackTabVisible="False" (MainWindow.axaml:20); the shell hides TabPlayback (MainWindowShell.axaml.cs:180) and the Slicer omits the panel entirely — comment: "Playback panel intentionally omitted" (MainWindow.axaml:89–90). The playback keys are stubbed, not removed (see limitations).

Slice params model

SliceParams (src/SlicerApp/SliceParams.cs) is the only view model in either app — the Simulator has no equivalent. Defaults match the pre-nps-3a3 hard-coded generate call, so a first Load and an untouched Apply produce the same toolpath (stated intent: SliceParams.cs:16–17, 34–36):

Property Default File:line
LayerHeight 0.2m SliceParams.cs:40
Feedrate 1800m SliceParams.cs:41
VolumetricFlowScale 1.0m SliceParams.cs:42
EnableNonPlanar false SliceParams.cs:43
NumLayers 5 SliceParams.cs:44

The AXAML sets no Value= on the NumericUpDowns; values arrive through two-way bindings, so the controls initialize from the VM:

Control Binding Min Max Step File:line
SliceLayerHeight LayerHeight 0.05 2 0.05 MainWindow.axaml:34–37
SliceFeedrate Feedrate 1 30000 100 MainWindow.axaml:39–42
SliceFlowScale VolumetricFlowScale 0.1 5 0.05 MainWindow.axaml:44–47
SliceNonPlanar (CheckBox) EnableNonPlanar MainWindow.axaml:48–51
SliceNumLayers NumLayers 1 500 1 MainWindow.axaml:53–56

Load chain

Load + Gen + Analyze (Benchy primary) (OnLoadAnalyzeClick, MainWindow.axaml.cs:155–189) is the whole app in one button. It does not harvest the UI first — it runs with whatever is already in _sliceParams (defaults on first click).

sequenceDiagram
  participant UI as MainWindow
  participant SP as SliceParams
  participant ES as EngineSession
  participant VP as ViewportController
  UI->>ES: LoadModel(FixtureLocator.FindPrimaryBenchy3Mf())
  alt load failed or fixture missing
    ES-->>UI: error → Status(...) → return
  end
  UI->>UI: _modelLoaded = true; MeshInfoLabel.Text
  UI->>SP: ToGcodeGenOptions()
  UI->>ES: GenerateDemoToolpath(options)
  ES-->>UI: GenerateResult (moves)
  UI->>VP: SetMoves / SetLayerVisibility / SetCamera / BuildBuffers
  UI->>ES: Analyze(MaterialProfileOptions("PLA", 210, 60))
  UI->>UI: LoadFindings() → FindingsPanel
  loop layers 0..5
    UI->>ES: GetWarpGrid(l)
  end
  UI->>UI: WarpGridLabel.Text
  UI->>UI: force _currentOverlayMode = "thermal"
  UI->>VP: ApplyOverlay(session, "thermal", false)
  UI->>UI: RenderViewport(); Status("Slicer parity loaded (DEMO TOOLPATH — ...)")

The full RunGenerate() sequence lives at MainWindow.axaml.cs:251–318. Three load-time behaviors are Slicer-specific and easy to miss:

  1. Warp grid load — layers 0..5 are sampled via _session.GetWarpGrid(l) and summarized into the sidebar label (MainWindow.axaml.cs:297, loop at 334–336).
  2. Forced thermal overlay — the mode is set to "thermal" on both the field and the panel, then applied (MainWindow.axaml.cs:305–312). The user did not choose it; load chooses it.
  3. Parity status string — the load ends by announcing itself as a parity demo (MainWindow.axaml.cs:315–317).

Apply / Regenerate flow

Apply / Regenerate (ApplySliceButton, OnApplySliceClick, MainWindow.axaml.cs:192–211) is a short gate-then-regenerate:

  1. Session ready? Else "Engine session not ready." (194–198).
  2. Model loaded? Else "Load a model first (Load + Gen + Analyze)." — the _modelLoaded gate (200–204).
  3. HarvestSliceParamsFromUi() (209, body at 215–246) pulls the live control values into _sliceParams.
  4. RunGenerate() (210) — the identical chain shown above.

So the two buttons differ only in when the UI is read: Apply harvests then generates; Load generates from defaults. That symmetry is deliberate (STATED: defaults match the old hard-coded call, SliceParams.cs:34–36).

The parameter mapping chain, end to end:

SliceParams GcodeGenOptions → ABI NpsGcodeGenParams
EnableNonPlanar EnableNonPlanar enableNonPlanar (1/0)
LayerHeight LayerHeight (float) layerHeight
VolumetricFlowScale VolumetricFlowScale (float) volumetricFlowScale
Feedrate Feedrate (float) feedrate
NumLayers NumLayers numLayers (0 = auto from mesh)

Mapping code: ToGcodeGenOptions() (SliceParams.cs:88–96), ABI packing in EngineSession.GenerateDemoToolpath (EngineSession.cs:258–269, struct at NpsAbi.cs:132–141). The app never names ABI types itself — it is a thin shell over EngineHost (MainWindow.axaml.cs:14–22, SlicerApp.csproj:20–24).

Warp grid label

The sidebar footer carries two info labels: MeshInfoLabel (MainWindow.axaml:66) and WarpGridLabel (MainWindow.axaml:67, initial text "WarpGrid: (analyze for full grid)"). After generation, LoadWarpGrids() samples GetWarpGrid for layers 0..5 only and updates the label (MainWindow.axaml.cs:334–336). The engine side documents this as the f8v.6.1 warp grid ABI feed (EngineSession.cs:419). There is no warp visualization — the label is the entire warp UI.

Limitations

All entries are HOW IT IS, each with code evidence. The share-vs-split plan discusses where these go next.

Limitation Evidence
Demo toolpath, not a production slice GenerateDemoToolpath call + comment (MainWindow.axaml.cs:260–261); status string (316); engine docs (EngineSession.cs:248–250)
No file picker — one mandatory fixture Only FixtureLocator.FindPrimaryBenchy3Mf(); missing file → error status (MainWindow.axaml.cs:162–167); no OpenFileDialog in SlicerApp
Playback tab hidden, panel omitted IsPlaybackTabVisible="False" (MainWindow.axaml:20); omission comment (MainWindow.axaml:89–90)
P / Left / Right are no-ops with status Keys route to ShellKeybindings.Handle"Playback unavailable in Slicer." (MainWindow.axaml.cs:136–151, ShellKeybindings.cs:82–113)
Layer slider hard-capped at 10 AXAML Maximum="10" (MainWindow.axaml:28); _maxLayer = 10 (MainWindow.axaml.cs:44) — not driven by mesh layer count
Material profile hard-coded Analyze(new MaterialProfileOptions("PLA", 210, 60)) (MainWindow.axaml.cs:289) — no UI
Warp grids sampled for layers 0..5 only for (int l = 0; l <= 5; l++) (MainWindow.axaml.cs:334–336)
Viewport canvas fixed 600x600 Width="600" Height="600" (MainWindow.axaml:74)
Apply requires a prior Load _modelLoaded gate (MainWindow.axaml.cs:200–204)
CLI preflight uses NumLayers: 0, UI defaults to 5 Program.cs:59–64 vs SliceParams.cs:44

No TODO markers

src/SlicerApp contains no TODO / FIXME / HACK comments. The honesty is in the status strings and parity comments instead (collected in the source audit, e.g. MainWindow.axaml:11–14, MainWindow.axaml.cs:552,556).

Where this page sits