Duplication audit¶
This page is the as-is clone audit of the two Avalonia MainWindows: what is copied between SlicerApp and SimulatorApp today, how much, and which of it is wrong sharing versus acceptable replication. Everything under How it is was verified against the code; share/split recommendations live on the sibling page Sharing plan.
How it is — sources compared in full:
/home/christoph/github/nps/src/SlicerApp/MainWindow.axaml.cs— 671 lines/home/christoph/github/nps/src/SimulatorApp/MainWindow.axaml.cs— 860 lines/home/christoph/github/nps/src/SlicerApp/MainWindow.axaml— 102 lines/home/christoph/github/nps/src/SimulatorApp/MainWindow.axaml— 70 lines
Method¶
The two code-behind files were compared block by block: each contiguous region of the
Slicer MainWindow.axaml.cs (field skeleton, shell initialization, toolbar wiring, load
chain, findings handling, render loop, and so on) was matched against its Simulator
counterpart and given one of three verdicts:
- Verbatim — identical or character-identical apart from whitespace.
- Near-verbatim — same control flow and same calls; only strings, a guard, or one extra statement differ.
- Partial / forked — a shared spine with app-specific forks, or a structural clone over a different API.
Fractions are estimated in lines, not characters: method-shaped clones with small string or type diffs count as duplicated.
Headline numbers¶
How it is (estimated from the block table below):
| Metric | Value |
|---|---|
| Slicer code-behind | 671 lines |
| Simulator code-behind | 860 lines |
| Slicer mirrored in Simulator | ~45–50% of Slicer's file |
| Simulator mirrored in Slicer | ~38–42% of Simulator's file (Sim is larger: playback + inlined paint + mesh) |
| Combined structural duplication | ~42–48% of the union of both files |
| Near-clone orchestration glue still in both MainWindows | ~300–350 lines |
The chrome (window, regions, panels, themes) is not the duplication problem — it is
already factored into MainWindowShell and Nps.Shell controls. What remains duplicated
is orchestration glue: camera math, toolbar wiring, layer slider, overlay apply,
findings selection skeleton, and the load spine.
Block-by-block table (A–R)¶
How it is — the heart of the audit. Line ranges cite
src/SlicerApp/MainWindow.axaml.cs and src/SimulatorApp/MainWindow.axaml.cs.
| # | Block | Slicer lines | Sim lines | Verdict |
|---|---|---|---|---|
| A | Field skeleton (_session, _viewport, drag, overlay mode, _maxLayer) |
25–57 | 26–75 | Partial — same core fields; Sim adds mesh/playback/ABI buffers, Slicer adds slice/warp/painter |
| B | InitializeShell (activity + pointer + toolbar + session/viewport + overlay) |
65–104 | 83–142 | Near-verbatim core; Sim adds timer + PlaybackPanel wiring (104–137) |
| C | Toolbar wiring only | 80–89 | 91–100 | Verbatim (identical lambdas) |
| D | OnActivityTabClicked |
106–115 | 144–153 | Verbatim |
| E | OnClosed disposal |
120–134 | 160–185 | Near-verbatim shape (viewport→session); Sim also stops timer and nulls _mesh |
| F | OnKeyDown via ShellKeybindings |
136–151 | 188–201 | Near-verbatim structure; actions differ (Slicer no-ops play/step, Sim binds real handlers) |
| G | Load chain (fixture → LoadModel → status/mesh label → generate → analyze → findings → thermal → apply → render) | 155–189 + 251–318 | 205–314 | Partial / forked — shared spine; Slicer splits Apply/RunGenerate/warp, Sim hard-codes gen opts + mesh reload + playback markers |
| H | Findings load + project | 366–410 | 316–357 | Structural clone over a different API — Slicer: FindingFormatter.ProjectFromSession; Sim: raw NpsAbi list + GetFindingDetail |
| I | OnFindingSelected |
421–481 | 359–431 | Near-verbatim control flow (cache → stamp → UpdateDetail → layer/sim → BuildBuffers → render); Sim also writes PlaybackPanel.SimIndex |
| J | Layer slider + label | 483–502 | 433–452 | Verbatim |
| K | OnActivityTabClick (dead thin wrapper) |
504–507 | 454–457 | Verbatim |
| L | Overlay mode + ApplyCurrentOverlay |
509–558 | 459–508 | Near-verbatim — legend/stats wiring identical, status strings differ |
| M | Pointer press / move / release (orbit + pan) | 560–593 | 661–692 | Verbatim (same dx/dy, clamp, pan formula) |
| N | RenderViewport |
597–638 | 695–825 | Diverged — Slicer delegates to GcodeToolpathPainter; Sim inlines the paint loop + mesh painter |
| O | ZoomFitCamera |
640–662 | 827–849 | Verbatim |
| P | Status helper |
665–669 | 851–858 | Near-verbatim (Shell.StatusText + StatusSink.Emit) |
| Q | Playback (timer, scrubber, auto-pause, step) | (absent) | 510–658 | Sim-only |
| R | Slice params harvest / Apply / warp grids | 192–246, 321–363 | (absent) | Slicer-only |
Notes on the load chain (block G)¶
How it is — the shared spine, in order, with citations for both apps:
- Guard session;
FixtureLocator.FindPrimaryBenchy3Mf()+ missing-file error (Slicer 162–167 / Sim 212–217) _session.LoadModel(Slicer 169 / Sim 219)- Plate/palette/unit status +
MeshInfoLabel(Slicer 178–186 / Sim 234–242) GenerateDemoToolpath(Slicer 258–261 viaSliceParams; Sim 245–250 hard-coded 5 layers)SetMoves/ camera /BuildBuffers(Slicer 274–287 / Sim 263–269)Analyze(PLA…)(Slicer 289 / Sim 272)- Findings + force thermal +
ApplyCurrentOverlay(Slicer 296–314 / Sim 280–312)
The forks are product-shaped, not accidental: Slicer owns _modelLoaded, a separate
Apply step, LoadWarpGrids, and SetSimulationState(false, last); Sim owns
_mesh?.Reload() on every load, playback MoveCount/SetMarkers, and
_simMoveIndex = 0. This is a diverging path, and forcing it into one shared
LoadAnalyzePipeline would be the wrong share — the divergence forecast and verdicts
are on the Sharing plan.
XAML duplication¶
How it is — src/SlicerApp/MainWindow.axaml (102 lines) vs
src/SimulatorApp/MainWindow.axaml (70 lines):
| Region | Slicer | Simulator | Verdict |
|---|---|---|---|
Window chrome (KeyDown, shell xmlns, design size) |
1–10 | 1–10 | Near-identical shell host |
MainWindowShell |
19–21, IsPlaybackTabVisible="False" + TitleText |
20–21, TitleText only | Shared control; one parameterization |
| LeftRegion | 23–69 (layers + SliceParams + Load + Apply + Mesh + Warp) | 23–34 (layers + Load + Mesh) | Partial — slider/label/load/mesh pattern shared |
| ViewportRegion | 71–78 (Border + Canvas + Toolbar) | 36–43 | Near-verbatim |
| InspectorRegion | 80–92 (Overlay + Findings) | 45–60 (+ PlaybackPanel) | Shared two panels; Sim-only third |
| StatusRegion | 94–99 (StatusBar bound to Shell.StatusText) | 62–67 | Verbatim pattern |
Roughly ~70% of the Simulator XAML is the shared shell composition; ~55–60% of the
Slicer XAML is the same skeleton with a much thicker left sidebar (slice params). The
chrome itself is already factored into MainWindowShell; what remains duplicated are
region fill patterns — viewport stack, status bar, dual inspector panels, layer
slider block.
The seven wrong-sharing findings¶
How it is — each item cites code. Red-flag labels follow the design language (repetition, information leakage, special–general mixture).
-
Sim inlines the paint loop despite
GcodeToolpathPainter(missing share; repetition). The shell painter exposesRenderTo(..., maxMoveIndexExclusive, ...)designed for both apps — the sim playhead is the intended argument. Slicer consumes it (MainWindow.axaml.cs:622–631); Sim still owns_projBufA/Band_heatBuf(SimulatorApp/MainWindow.axaml.cs:43–45) plus a ~70-line inlined loop (748–817) that mirrors the painter. This is the single largest piece of current sharing debt. -
Sim findings path leaks ABI types into the window (information leakage). Slicer loads findings through
FindingFormatter.ProjectFromSession(SlicerApp/MainWindow.axaml.cs:400–405); Sim still storesList<NpsAbi.NpsAnalysisFindingSummary>(SimulatorApp/MainWindow.axaml.cs:51) and calls_session.GetFindingsSummary/_session.GetFindingDetailfrom UI code (SimulatorApp/MainWindow.axaml.cs:332,353,386). The same "how findings become rows" knowledge lives in two different shapes. -
StatusSink.Prefix = "[SlicerUI]"is a forced brand on both apps (special–general mixture). The shared sink hard-codes the Slicer brand (Nps.Shell/StatusSink.cs:36), so Simulator status lines and smoke greps emit[SlicerUI]. A general mechanism (status emission) embeds one app's special case. -
FindingItem.Dataexposes the ABI POD to apps (information leakage, half-fixed).Nps.Shell/Controls/FindingItem.cs:98still surfacesNpsAbi.NpsAnalysisFindingSummaryas a public property. Slicer's convenience accessors (FindingId,Layer,MoveIndexat lines 104–116) hide it, but Sim still readsitem.Data.layerdirectly, so the ABI field layout remains UI-visible. -
Overlay mode keys are written in two styles (repetition without a single constant surface). Slicer uses string literals
"motion-type"/"thermal"(SlicerApp/MainWindow.axaml.cs:57,305); Sim usesNpsAbi.OverlayModeId.*(SimulatorApp/MainWindow.axaml.cs:72,288). Same keyspace, two spellings — a sign the shell owns no UI-side constant for overlay modes. -
Dead
OnActivityTabClicktwins (repetition of dead weight; pass-through). Both apps carry an identical handler (SlicerApp/MainWindow.axaml.cs:504–507,SimulatorApp/MainWindow.axaml.cs:454–457) that only forwards toShell.Activity.HandleClick. Activity tabs are shell-owned, so the wrapper adds interface surface and no function in both apps. -
Not wrong:
IsPlaybackTabVisible. Slicer hides the playback tab (SlicerApp/MainWindow.axaml:20), Sim uses the defaulttrue. This is correct product parameterization of one shared control, not forced sharing — flagged here only because it can look like item-3-style branding at a glance.
Note
Blocks Q (playback) and R (slice params / warp grids) are deliberately absent from the wrong-sharing list. They are single-app code with no clone counterpart — the honest kind of divergence. The forecast for what should stay split versus converge is on the Sharing plan.
What the clones are made of¶
INFERENCE — grouping the verbatim and near-verbatim blocks by knowledge rather than by file order shows the duplication is concentrated in four kinds of glue:
- Camera and input (blocks C, M, O): bit-identical math and event shapes.
- Viewport adornment (blocks J, L): slider clamp + label, overlay legend + stats.
- Findings skeleton (blocks D, I, and the control flow of H): cache-first selection, detail stamp, layer/sim navigation.
- Load spine (block G): the shared seven-step sequence above, before the forks.
None of these are domain rules (slicing semantics, sim physics); they are operational UI glue. That is what makes the ~300–350 cloned lines a candidate for shell-owned helpers rather than acceptable replication — and what makes blocks G's forks, Q, and R the opposite. Verdicts per block are on the Sharing plan.