Table of Contents

STL Preview Pane

The STL Preview Pane is the server-rendered 3D view that takes over the File Explorer's editor column while an .stl file is open. It is neither a route nor a dialog of its own: it renders inside the right-hand slave panel of /util/file-explorer, and of the modal file picker built on the same browser component, replacing the text editor's bar and body for as long as an STL is selected. It carries its own rendering connection, its own copy of the file's triangles, and a Transform section that can bake a pose back into the file on disk.

Where It Renders

The File Explorer mounts the pane on the root-relative path of the STL being previewed; the named root and that path are both pinned when the preview opens, so switching the explorer's root closes the preview rather than silently re-targeting it. Three gestures open one, and all key on the stl extension alone:

  • Selecting the row, but only while the editor slave panel is showing.
  • Submitting the file's path in the toolbar's path field, under that same panel-showing guard.
  • Double-clicking the row header, which shows the panel first.

Panel visibility starts from the device-local File Explorer preferences on the page and defaults to shown. Inside the modal picker it always starts hidden and is never written back, so there the double-click is the only gesture that reaches a preview until the toolbar's editor-panel toggle is pressed — and in a save-mode picker, where the path field names the save target rather than a place to navigate to, submitting it never opens a preview at all.

Four gestures close one: the bar's own close button, opening a text file in the same panel, a root switch, and the toolbar's editor-panel toggle. The first three leave the panel showing the text editor; the last settles the text buffer, hides the whole panel and drops the preview together.

Layout

  • STL Preview Pane
    • Preview Bar
      • Location Label — the root's display name and the root-relative path, ellipsized, with the full string as its hover title.
      • Triangle Count Badge — an outlined badge rendered only once a load has reported a count, and cleared again at the start of every new load. The number is formatted for the app locale.
      • Scene ▾ Menu — the shared display-options dropdown, carrying one checkbox here, Origin axes.
      • View ▾ Menu — the shared RenderingCanvas Tool Bar, bound to this pane's canvas.
      • Transform Button — shows and hides the Transform section; it reads primary-coloured while the section is open, and starts off on every mount.
      • Close Button — a round close icon titled Close preview.
    • Transform Section — rendered only while the Transform button is on.
      • Header Row — a Transform caption and a Save button. Save stays disabled until a load has published a transform key, and shows a loading state while the write is in flight.
      • General Transform Editor — the standard transformer editor, scrolling inside a capped height so the canvas keeps most of the pane:
        • Scale numeric field.
        • Rotation sub-transformer card — Rotation axis as three components with a normalize button, Angle in degrees, and Pivot in mm.
        • Translation sub-transformer card — a Translation caption over three mm components.
        • A caption naming the composed transform as T × R × scale × I.
        • Every field here commits on blur or on Enter, one write per field.
      • Failure Text — with no transform key held, the section reads “Failed to load STL preview.” in place of the editor.
    • Canvas Body
      • Rendering Canvas — fills the rest of the pane.
      • Loading Overlay — a centred spinner over a dim, click-through wash. The previously loaded model stays visible beneath it rather than being cleared.
      • Error Banner — pinned to the foot of the canvas, carrying the failed load's message.

Loading a Preview

One request path serves every trigger: the canvas reporting a connection id, and any change to the root or the relative path. Each request clears the banner and the triangle badge, raises the spinner, and posts the named root and the relative path to show.

Server side, the controller resolves the root name, re-combines it with the relative path, refuses a result that is not a descendant of that root, and refuses a path that does not exist. The read itself runs on a thread-pool task rather than the request thread, and the format is sniffed from the file's first two lines rather than from its extension — ASCII and binary STL both load. The file is opened for shared read and write, so a load does not lock it against other readers.

A successful read builds the native topology, commits it into the connection's slot, and only then touches the engine: the displayee is re-pointed at the new composition, the camera is reset to the engine's home view and the render cache cleared, and the geometry the commit replaced is disposed afterwards, so the render thread never spends a frame on a dead reference.

The response carries the triangle count, the bounding box, the connection's origin-axes flag and the transform key. The pane reads all of those except the bounding box, which it ignores — the bounding-box readout a reader sees belongs to the StlFile Editor's info dialog, not to this pane.

A rejected load — unknown or unavailable root, a path escaping the root, a missing file, an unreadable file — answers a non-2xx status carrying the server's message, and the shared response helper folds the status into the text; that composite is what the banner shows. The message itself is passed through as the server wrote it: unlike the File Explorer's own endpoints, this controller runs no root-prefix scrub over an exception message, so a read or write that fails down in the filesystem layer can surface a server path in the banner.

Cancelling a Load

Cancellation is the pane's defining behaviour: clicking down a folder of large STLs does not stack multi-second reads behind each other, and the tree stays fully interactive throughout.

Three mechanisms compose:

  • A monotonic show token in the pane. Every request takes the next number; a response arriving under a stale number is discarded, and the spinner is lowered only by the newest request. Without it the slower of two racing round-trips would win the badge.
  • An abort controller in the pane. Each request aborts the previous one's controller before issuing its own, and swallows the resulting abort error. The same controller is aborted when the pane unmounts.
  • Supersede in the preview service. Before reading anything the controller asks the service to supersede the connection, which cancels whatever load is still running and installs a fresh cancellation source. That source doubles as the commit ticket. The request then reads under a token linked from the ticket and from the request-aborted token, so a browser abort and a newer selection cancel the same read.

The read is cooperatively cancellable rather than interruptible: the STL reader checks the token once every 4096 triangles, and once every 4096 lines of the pre-scan an ASCII file needs, which bounds the latency to a few thousand parses rather than a whole file.

The commit is gated on the ticket. A load that finishes after a newer one superseded it is refused, disposes the geometry it has just built, and answers HTTP 200 with a canceled flag; the pane drops that response without touching the badge or the banner, and lowers the spinner only when no newer request has taken the pane over in the meantime.

The Rendering Connection

The pane embeds one rendering canvas, so it opens its own SignalR connection to /renderingHub and the server gives that connection its own display engine — a second live engine beside the host page's canvas whenever the preview is running inside the modal picker. See Rendering Canvas on Web Service Application for the transport itself.

The connection id is the key for everything on the server side: the display engine, and the preview service's per-connection slot. The canvas re-emits it after every automatic reconnect, and the pane answers by re-issuing the show — which is what restores the preview after a connection drop. The reconnected slot starts empty, so the pane re-pushes its own Origin axes preference whenever the server's answer disagrees with it. The pose is not restored: every commit mints a fresh identity transform.

Release runs off the connection's death rather than off an explicit teardown call. Closing the preview unmounts the pane, which aborts the in-flight fetch and disconnects the canvas; the hub's disconnect handler disposes the engine and raises its engine-removed event; the preview service's subscriber then cancels any load still running, disposes the STL topology and the origin-axes drawing, and withdraws the transform key. That subscriber exists because a display engine disposes only itself — never the displayee it was pointed at.

The clear endpoint performs most of that detach on demand — it cancels the load in flight, empties the canvas, disposes the topology and withdraws the transform key — but it keeps the connection's slot registered, with its origin-axes drawing and flag intact for the next preview; only the connection's death frees those. It is also the one entry in this surface that ships without a caller in the SPA.

Server-Side Composition

The slot a connection owns holds a small display graph rather than a bare mesh:

  • NativeTopoStl3d — the native topology built from the file's triangles, and one of the two objects here that have to be disposed by hand.
  • TransformationWrapper — wraps that topology and carries the pose matrix.
  • CoordinateDrawing — the origin axes, created on first enable and kept across file switches, because the flag is a per-connection viewing preference rather than a property of the file. It is the other hand-disposed object, and only the connection's death frees it.
  • DispList — the composition assigned to Displayee: the axes drawing, when shown, followed by the wrapper.
  • GeneralTransform — the pose the Transform section drives.

Stl is not among them. The reader's output is a local of the show request, alive only long enough for the native topology to copy its triangles into its own buffer and for the response to report the triangle count and the bounding box; the save path then mints a second one out of the topology's own snapshot, and it is that second object the save transforms and writes.

The committed topology always holds the file's as-loaded triangles. The editor's pose lives only in the wrapper, which is why it can be re-applied, reset or saved repeatedly without drift.

Transform and Save

Each committed load registers a fresh GeneralTransform — identity, and a new object rather than a reset one — in the server's keyed object store, and returns its key. The pane hands that key to the standard general-transform editor, the same component and the same REST surface the Transformer panels use; there is no preview-specific transform schema. After every committed edit the pane posts apply-transform, which re-reads the transform's matrix into the display wrapper and clears the render cache. That is display only — the file is untouched.

Save asks for confirmation naming the root-relative path, then bakes. The service snapshots the native triangles under the topology's own dispose lock, multiplies them by the current matrix and writes the result over the previewed file as a binary STL, so an ASCII source is rewritten in binary form. Because the shown topology keeps the as-loaded triangles and the editor keeps its values, screen and disk agree afterwards — both are the original geometry times the current matrix — and saving twice with the same values rewrites the same file rather than compounding the pose. Resetting the editor's fields returns to the as-loaded pose.

A save that finds the topology already disposed by a newer selection reports that it was superseded instead of writing. On success the pane raises a toast and the File Explorer re-lists the containing folder, so the row's size and modified columns catch up. Nothing else is refreshed: a StlFile geometry elsewhere in the project that references the same path keeps the triangles it already loaded.

Switching files re-mints the transform — a new key, an identity pose, and an editor that re-reads cleanly because the key changed. The old key is withdrawn from the keyed store on replacement, on clear and on connection death; the two derived keys the editor mints beneath it for the rotation and translation sub-transformers are not withdrawn with it.

Endpoints

Every entry is a POST under /api/stl-preview, addressed by the rendering connection id. The four that touch the canvas resolve the display engine through the non-creating lookup, so a disconnected id cannot orphan a fresh engine; save needs no engine at all and writes straight from the slot.

Path Body Purpose
show/{renderingConnectionId} { rootName, relFile } Supersede, read, commit, re-point the engine. Answers the triangle count, the bounding box, the origin-axes flag and the transform key — or a canceled flag.
set-coordinate/{renderingConnectionId} a bare boolean Show or hide the origin axes. With nothing on the canvas yet it stores the flag for the next commit.
apply-transform/{renderingConnectionId} none Re-read the transform's matrix into the display wrapper.
save/{renderingConnectionId} none Bake the matrix into the geometry and overwrite the file.
clear/{renderingConnectionId} none Cancel the load in flight, empty the canvas, free the STL geometry; the slot and its origin-axes drawing survive.

rootName is one of the three named roots — the admin directory, the loaded project directory and the shared resource directory — resolved server-side; the project root is unavailable until a project is loaded. Addressing is root-relative in both directions and the absolute path is formed and kept on the server; the unscrubbed error text noted above is the one place it can escape.

The Text Buffer Underneath

Opening a preview does not disturb the text editor beneath it. There is no flush, no discard prompt and no reload: the loaded path, the buffer, its saved snapshot and its dirty mark are all left as they were, and the bar's close button brings them straight back, dirty star included. Clicking a text file while a preview is open closes the preview first and then loads that file through the editor's normal open path, which settles the outgoing buffer the way any file switch does.

Two consequences follow from the buffer staying live. The toolbar's editor-panel toggle remains reachable while the preview is up, and it settles the buffer before hiding the panel — so a dirty buffer in manual mode raises its discard confirm at that point, even though the visible pane is a 3D canvas. And an auto-save debounce armed just before the preview opened still fires underneath it.

Source Code Path

See HiNC App Anatomy for git repository links.

Web Application

HiNC-2025-webservice (Quasar CLI SPA):

  • wwwroot-src/src/components/StlPreviewPane.vue — this pane: the bar, the Transform section, the canvas body with its overlay and banner, the show token and abort controller, and the optimistic origin-axes toggle.
  • wwwroot-src/src/components/FileExplorer.vue — the sole host: the preview slot in the editor slave panel, the extension test and the three opening gestures, the pinned root, the close paths, and the folder refresh after a save.
  • wwwroot-src/src/components/widgets/FileExplorerDialog.vue — the modal wrapper, whose picker props are what leave the editor panel hidden inside the picker.
  • wwwroot-src/src/pages/FileExplorerPage.vue — the routed host at /util/file-explorer.
  • wwwroot-src/src/api/stlPreview.ts — the typed wrapper over the five endpoints, the result shape, and the abort signal threaded into show.
  • wwwroot-src/src/api/http.ts — the shared response helper that wrapper uses: a non-2xx throws an error whose text is the status followed by the server's message, while a 200 body carrying success: false reaches the caller as data, which is what lets the canceled answer be ignored silently.
  • wwwroot-src/src/components/RenderingCanvas.vue — the connection the pane owns, its reconnect loop and the re-emitted connection id.
  • wwwroot-src/src/components/RenderingCanvasToolBar.vue — the View ▾ menu in the bar.
  • wwwroot-src/src/components/widgets/DisplayOptionsMenu.vue — the Scene ▾ dropdown; the pane passes one checkbox group and no label, so the button falls back to the localized “Scene”.
  • wwwroot-src/src/components/topo/GeneralTransformEditor.vue, wwwroot-src/src/components/topo/StaticRotationEditor.vue and wwwroot-src/src/components/topo/StaticTranslationEditor.vue — the Transform section's editor and its two sub-transformer cards.
  • wwwroot-src/src/components/widgets/NumericInput.vue and wwwroot-src/src/components/widgets/Vec3Input.vue — the fields inside them, committing on blur or Enter.
  • wwwroot-src/src/api/transformer.ts, wwwroot-src/src/api/geometry.ts and wwwroot-src/src/api/index-service.ts — the standard transform surface those editors write through.
  • wwwroot-src/src/i18n/en/explorer.ts — the explorer.preview.* strings: the close title, the failure text, Origin axes, Transform, and the save-confirm dialog.
  • wwwroot-src/src/i18n/en/geom.ts — the pluralized triangle-count phrase in the badge.
  • wwwroot-src/src/i18n/en/topo.ts and wwwroot-src/src/i18n/en/widgets.ts — the transform-editor captions, and the Scene and View menu labels.
  • Disp/StlPreviewController.cs — the five endpoints, the root resolution and descendant check, the off-thread read, the commit-then-swap ordering, the canceled answer, and the failure replies that pass an exception message through unaltered.
  • Common/FileExplorerController.cs — the root-prefix scrub over exception text that the File Explorer's own endpoints apply and this surface does not share.
  • Disp/StlPreviewService.cs — the per-connection slot: supersede and the commit ticket, the display composition, the origin-axes flag that survives file switches, the bake-and-write save, and the engine-removed handler that frees everything.
  • Disp/RenderingService.cs — the per-connection engine store, the non-creating lookup HTTP endpoints must use, and the engine-removed event.
  • Disp/RenderingHub.cs — the hub whose disconnect disposes the engine and raises that event.
  • Common/NamedRootResolver.cs — the three named roots and their availability rules.
  • Common/IndexService.cs — the keyed object store the transform is published in.
  • Mech/Topo/GeneralTransformController.cs — the standard transform endpoints, including the two that index the rotation and translation sub-transformers under derived keys.

HiAPI Engine

  • HiGeom/Geom/Stl.cs — the reader: content-sniffed ASCII / binary detection, the cancellation check every 4096 triangles or pre-scan lines, and the binary writer the save uses.
  • HiDisp/Geom/Topo/NativeTopoStl3d.cs — the native topology, and the locked triangle snapshot the save takes, which answers nothing once the topology has been disposed.
  • HiDisp/Disp/Treat/TransformationWrapper.cs — the display-time pose applied to that topology.
  • HiDisp/Disp/Flag/CoordinateDrawing.cs — the origin-axes drawing.
  • HiDisp/Disp/DispList.cs — the one- or two-element composition the engine is pointed at.
  • HiDisp/Disp/DispEngine.cs — the engine: its displayee, its cache clear, its home view, and a dispose that frees the native engine alone.
  • HiMech/Mech/Topo/GeneralTransform.cs — the pose model, its uniform scale and its two sub-transformers.

See Also

  • File Explorer — the one screen that mounts this pane, and the editor column it takes over
  • StlFile Editor — the geometry editor whose file picker is where this preview is most often met, and the surface that owns the bounding-box readout this pane does not show
  • RenderingCanvas Tool Bar — the View ▾ and Scene ▾ menus the preview bar embeds
  • Rendering Canvas on Web Service Application — the hub connection the pane opens, and the engine-removed event that frees the preview's native geometry