This commit is contained in:
2026-09-09 17:35:23 +08:00
parent 2a98ae7556
commit 161af68fe3
1271 changed files with 58251 additions and 98639 deletions
@@ -2,16 +2,16 @@
<html>
<head>
<meta charset="utf-8">
<title>Resizable Bar Component | HiAPI-C# 2025 </title>
<title>Anatomy by Source Directory | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Resizable Bar Component | HiAPI-C# 2025 ">
<meta name="title" content="Anatomy by Source Directory | HiAPI-C# 2025 ">
<link rel="icon" href="../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
@@ -84,36 +84,46 @@
<nav id="breadcrumb"></nav>
</div>
<article data-uid="">
<h1 id="resizable-bar-component">Resizable Bar Component</h1>
<article data-uid="Anatomy-BySource">
<h1 id="anatomy-by-source-directory">Anatomy by Source Directory</h1>
<p>A Vue component that provides draggable dividers for resizing adjacent panels in web applications.</p>
<h2 id="overview">Overview</h2>
<p>The ResizableBar component creates a draggable bar that allows users to resize panels by clicking and dragging. It supports both horizontal and vertical orientations.</p>
<h2 id="key-features">Key Features</h2>
<p>The rest of Anatomy is keyed on the shipped surface — a route, a Control-Tree branch, a reusable
control. This layer is keyed on the source tree instead, and it is the entry point for the reader who
knows which directory a change landed in but not which screen it shows up on.</p>
<p>Ordered the way a request travels: the front end first, then the process that serves it.</p>
<h2 id="directories">Directories</h2>
<ul>
<li><strong>Directional Support</strong>: Works in both horizontal (for width adjustment) and vertical (for height adjustment) orientations</li>
<li><strong>Unit Flexibility</strong>: Supports pixel, percentage, and custom unit systems through converters</li>
<li><strong>Visual Feedback</strong>: Changes appearance on hover and during drag operations</li>
<li><strong>Constraint System</strong>: Enforces minimum and maximum size limits</li>
<li><a href="webservice-spa/index.html">Web Service SPA Source Tree</a> — the Quasar front end under
<code>wwwroot-src/src</code>: how <code>components/</code> is grouped two ways at once, what the Control-Tree registry
actually is, and why half the route table is redirects</li>
<li><a href="webservice-backend/index.html">Web Service Backend Source Tree</a> — the ASP.NET Core half: how to read
a filename, why folder nesting does not predict a route, and which declared hub is never mapped</li>
</ul>
<h2 id="usage-pattern">Usage Pattern</h2>
<p>The component should be placed between two panels that need to be resizable. The resize events provide size information that parent components use to adjust panel dimensions.</p>
<h2 id="unit-modes">Unit Modes</h2>
<ol>
<li><strong>Pixel Mode</strong> (default): Direct pixel value manipulation</li>
<li><strong>Percentage Mode</strong>: Automatic calculation relative to parent container</li>
<li><strong>Custom Mode</strong>: User-defined unit converters for specialized requirements</li>
</ol>
<h2 id="integration-example">Integration Example</h2>
<p>See the player-panel.js implementation for a practical example of using ResizableBar to create adjustable layouts between rendering canvas and side panels.</p>
<h2 id="web-application-source-code-path">Web Application Source Code Path</h2>
<h2 id="what-this-layer-is-for">What This Layer Is For</h2>
<p>A page in the rest of Anatomy answers <em>what is this thing on my screen made of</em>. Both of these answer
the inverse: <em>this directory changed — what does it show up as, and which page is now wrong</em>. That
makes them the first stop after a refactor, and the reason each directory entry names the pages that
document it.</p>
<p>They are directory indexes, not file listings. A file worth naming is named on the page that
documents the surface it implements; what lives here is the shape of the tree, the conventions that
hold across it, and the traps that survive a careful reading of any single file.</p>
<h2 id="what-is-not-indexed-here">What Is Not Indexed Here</h2>
<p>The HiAPI engine repositories are <strong>not</strong> given a directory document. Anatomy cites engine source
where a shipped surface depends on it, but the engine's own structure is covered by the generated
<a href="../../api/Hi.Machining.html">API Reference</a>, which stays correct in a way a hand-written index could
not.</p>
<p>Neither is the outgoing Windows desktop client. It takes no new feature work, so a hand-written
index of its tree drifts with every flagship change and is read by nobody; the same reasoning
retired the page-by-page map of its files.</p>
<h2 id="see-also">See Also</h2>
<ul>
<li>common/resizable-bar</li>
<li>common/resizable-bar-example</li>
<li>player/player-panel</li>
<li><a class="xref" href="../index.html">HiNC App Anatomy</a> — the section index, and the surface-keyed way in</li>
<li><a class="xref" href="webservice-spa/index.html">Web Service SPA Source Tree</a> — the flagship front end</li>
<li><a class="xref" href="webservice-backend/index.html">Web Service Backend Source Tree</a> — the process that
serves it</li>
<li><a class="xref" href="../platform/index.html">Platform</a> — the machinery under every screen, entered by mechanism rather
than by directory</li>
</ul>
<p>See this page <a href="../index.html">~/app-anatomy/index.md</a> for git repository.</p>
</article>
@@ -0,0 +1,28 @@
<div id="sidetoggle">
<div>
<div class="sidefilter">
<form class="toc-filter">
<span class="glyphicon glyphicon-filter filter-icon"></span>
<span class="glyphicon glyphicon-remove clear-icon" id="toc_filter_clear"></span>
<input type="text" id="toc_filter_input" placeholder="Filter by title" onkeypress="if(event.keyCode==13) {return false;}">
</form>
</div>
<div class="sidetoc">
<div class="toc" id="toc">
<ul class="nav level1">
<li>
<a href="index.html" name="" title="Overview">Overview</a>
</li>
<li>
<a href="webservice-spa/index.html" name="" title="Web Service SPA">Web Service SPA</a>
</li>
<li>
<a href="webservice-backend/index.html" name="" title="Web Service Backend">Web Service Backend</a>
</li>
</ul>
</div>
</div>
</div>
</div>
@@ -0,0 +1,2 @@
{"items":[{"name":"Overview","href":"index.html","topicHref":"index.html"},{"name":"Web Service SPA","href":"webservice-spa/index.html","topicHref":"webservice-spa/index.html"},{"name":"Web Service Backend","href":"webservice-backend/index.html","topicHref":"webservice-backend/index.html"}]}
@@ -0,0 +1,300 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Web Service Backend Source Tree | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Web Service Backend Source Tree | HiAPI-C# 2025 ">
<link rel="icon" href="../../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../../public/docfx.min.css">
<link rel="stylesheet" href="../../../public/main.css">
<meta name="docfx:navrel" content="../../../toc.html">
<meta name="docfx:tocrel" content="../toc.html">
<meta name="docfx:rel" content="../../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../../index.html">
<img id="logo" class="svg" src="../../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-BySource-WebserviceBackend">
<h1 id="web-service-backend-source-tree">Web Service Backend Source Tree</h1>
<p>The C# half of <code>HiNC-2025-webservice</code> is an ASP.NET Core process that serves the SPA, answers its
REST calls, and pushes to it over SignalR. It is organised by the <strong>engine's domain vocabulary</strong>, not
by web-framework artifact type: a controller, its hub, its DI service and its non-web scene objects
sit together in one domain folder rather than in a <code>Controllers</code> / <code>Hubs</code> / <code>Services</code> split.</p>
<p>Folders below are ordered by how much of the shipped surface they answer for.</p>
<h2 id="reading-a-filename">Reading a Filename</h2>
<p>The role of a file is told by its suffix and base type, and the convention holds throughout:</p>
<table>
<thead>
<tr>
<th>Suffix</th>
<th>What it is</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>*Controller.cs</code></td>
<td>a REST controller with a route attribute</td>
</tr>
<tr>
<td><code>*Hub.cs</code></td>
<td>a SignalR hub — but see the two traps below</td>
</tr>
<tr>
<td><code>*Service.cs</code></td>
<td>a singleton registered in <code>Program.cs</code></td>
</tr>
<tr>
<td><code>*Displayee.cs</code></td>
<td>a scene-composition object handed to the engine; no HTTP surface at all</td>
</tr>
<tr>
<td><code>*Dtos.cs</code>, <code>*Config.cs</code></td>
<td>plain wire or settings types</td>
</tr>
</tbody>
</table>
<p>Two names deliberately break the reader's expectation. <code>Controller/ControllerController.cs</code> means
<strong>CNC controller</strong>, not an MVC controller. And <code>Mech/</code>'s <code>*DisplayController.cs</code> files are ordinary
REST controllers that attach a displayee to a rendering connection somebody else already owns — they
are not hubs.</p>
<div class="IMPORTANT">
<h5>Important</h5>
<p><strong>Folder nesting does not predict the route.</strong> <code>Mech/Topo/</code> and <code>Mech/MechBuilder/</code> expose routes
that carry no <code>api/mech/</code> prefix at all, and even inside <code>Mech/</code> two conventions coexist: the newer
ports use kebab-case paths under <code>api/mech/</code>, while the older keyed-object editors use the
controller-name default. Read the route attribute, never the path.</p>
</div>
<h2 id="composition-root">Composition Root</h2>
<p><code>Program.cs</code> is the only C# file at the repository root, and it is where the questions a reader
usually has are actually answered: which services are singletons, which hubs are mapped, and what the
middleware order is. Two facts about it are worth carrying:</p>
<ul>
<li><strong>A declared hub is not a mapped hub.</strong> <code>Program.cs</code> maps eight hub endpoints. Anything not in that
list is unreachable however complete its class looks — see the trap under <code>Execution/</code> below.</li>
<li><strong>The tree is not self-contained.</strong> The project service types <code>Program.cs</code> leans on hardest live in
the sibling HiNc engine repository, not here.</li>
</ul>
<p>Documented in <a class="xref" href="../../platform/program-and-hosting.html">Program and Hosting</a>.</p>
<h2 id="execution">Execution</h2>
<p><code>Execution/</code> is the run cockpit's whole backend: playback control, the strip and cycle-line charts,
the NC-program branch index, run-output queries, and the real-time push layer.</p>
<ul>
<li><code>Execution/ExecutionController.cs</code> — the playback surface.</li>
<li><code>Execution/ExecutionChartsController.cs</code> and <code>Execution/ClStripController.cs</code> — the chart data.
Three separate classes share the case-insensitive <code>api/execution</code> prefix on purpose, one of them
routed there rather than under Mission with a comment saying why.</li>
<li><code>Execution/SessionSinkHub.cs</code><strong>four</strong> mapped hubs declared in one file, one per message sink, so
hub-per-file does not hold here. Each pairs with a broadcast service that <code>Program.cs</code> resolves
eagerly, so it subscribes to its sink before the first client connects.</li>
<li><code>Execution/ClStripHub.cs</code>, <code>Execution/ExecutionStatusHub.cs</code> — the strip and status pushes.</li>
</ul>
<div class="WARNING">
<h5>Warning</h5>
<p><code>Execution/ExecutionCanvasHub.cs</code> declares a hub that <code>Program.cs</code> never maps and that nothing in
the repository references. The Execution canvas rides <code>Disp/RenderingHub.cs</code> like every other
canvas. A reader looking for &ldquo;the hub behind the Execution page&rdquo; by name will find this file first
and be wrong.</p>
</div>
<p>Documented in <a class="xref" href="../../execution/index.html">Execution Page</a>,
<a class="xref" href="../../execution/program.html">Program Branch</a>,
<a class="xref" href="../../execution/selected-step-info-panel.html">Selected-Step Info Panel</a>,
<a class="xref" href="../../execution/strip-charts.html">Strip Charts</a> and <a class="xref" href="../../execution/cycle-line-charts.html">Cycle-Line Charts</a>.</p>
<h2 id="mech">Mech</h2>
<p><code>Mech/</code> is the largest domain folder: the REST surfaces for the machine tool, the tool house and
cutter editing, fixtures and workpieces, spindle capability, background and coolant, and the three
runner suits. It also holds the display controllers that bind a mechanism scene onto a rendering
connection. <code>Mech/CutterDtoBuilder.cs</code> is the shared read-side DTO shape two controllers reuse, and
<code>Mech/NcSuitUsage.cs</code> is what lets the Control Tree show the CSV and CL runner branches only when the
loaded project actually plays them. No SignalR hub lives here.</p>
<ul>
<li><code>Mech/MechBuilder/</code> — the standalone mechanism-building session, held by a process-wide singleton
rather than per user. Documented in <a class="xref" href="../../util/mech-builder.html">Mechanism Builder Page</a>.</li>
<li><code>Mech/Topo/</code> — one thin controller per transformer kind, each editing an instance held in the
keyed object store. Documented in
<a class="xref" href="../../geom/transformer-panel.html">Transformer Select Panel</a>.</li>
<li><code>Mech/SoftNcRunnerController.cs</code> — the one endpoint family behind the Controller branch, and the
file whose snapshot decides which of that branch's nodes exist. Documented in
<a class="xref" href="../../general-setup/controller/index.html">Controller Branch</a>, with
<a class="xref" href="../../general-setup/controller/brand-matrix.html">Brand Matrix</a> for the snapshot flags themselves.</li>
<li><code>Mech/CsvRunnerController.cs</code> and <code>Mech/ClRunnerController.cs</code> — the two resident non-brand runner
suits, reached from tree branches the Preference menu hides by default. Documented in
<a class="xref" href="../../shell/preference-menu.html">Preference Menu Dropdown</a> until those branches have a page.</li>
</ul>
<p>Documented in <a class="xref" href="../../general-setup/index.html">General Setup Page</a> and
<a class="xref" href="../../tool-house/index.html">Tool House Page</a> and the panels beneath them.</p>
<h2 id="missions">Missions</h2>
<p><code>Missions/</code> is the mission command tree's backend. Two things here surprise readers:</p>
<ul>
<li>The <strong>folder is plural and the route is singular</strong> — and the SPA folder is singular too.</li>
<li><code>Missions/NcOptOptionEndpoints.cs</code> is not a minimal-API endpoint file despite the name. It is a
second file of the same partial controller class, which is why its routes resolve under the mission
prefix.</li>
</ul>
<p><code>Missions/MissionCommandCatalog.cs</code> reflects over every session command carrying the catalog
attribute once per process, replacing hand-maintained kind switches, and
<code>Missions/MissionCommandFields.cs</code> does the same for annotated scalars — which is how a simple
command gets an editor without a bespoke panel. <code>Missions/ScriptCompletionService.cs</code> and
<code>Missions/ScriptCompileCheckService.cs</code> are the Roslyn pair behind the script editor; the compile
check reuses the same options and globals type the script command uses at run time.</p>
<p>Documented in <a class="xref" href="../../execution/mission/mission-root-panel.html">Mission Root Panel</a> and the command panels under it.</p>
<h2 id="disp">Disp</h2>
<p><code>Disp/</code> is the rendering layer. <code>Disp/RenderingHub.cs</code> is the single transport surface for every 3D
canvas in the application — canvas initialization, pointer, key and touch input, resize, view
presets, cache clearing and snapshots — and the SPA's canvas component defaults to it, so all pages
share one hub rather than one hub per page. <code>Disp/RenderingService.cs</code> owns the per-connection
engines the hub resolves against. <code>Disp/StlPreviewController.cs</code> is the only other REST surface here.</p>
<p>The <code>*Displayee.cs</code> files are scene-graph composition objects rather than web types, and each has a
live instantiation site: the execution scene, the equipment-setup scene, the step-subtraction scene
and the two coordinate-frame displayees the first two compose in.</p>
<p>Documented in <a class="xref" href="../../conventions/rendering-canvas-web-service.html">Rendering Canvas on Web Service</a>,
<a class="xref" href="../../widget/renderingcanvas-tool-bar.html">RenderingCanvas Tool Bar</a> and — for
<code>Disp/StlPreviewController.cs</code> and the per-connection slot behind it —
<a class="xref" href="../../util/stl-preview-pane.html">STL Preview Pane</a>.</p>
<h2 id="environments">Environments</h2>
<p><code>Environments/</code> is session and environment scope: the project lifecycle, per-user preference
persistence, the Execution page's division flags, and the shipped localized step-present catalog. It
is also where the Log Viewer's data comes from — the log endpoints sit on the project controller
beside status, new, load, save, reload, save-as and close.</p>
<p>Documented in <a class="xref" href="../../shell/main-panel.html">Main Panel</a>,
<a class="xref" href="../../platform/session-state.html">Session State</a>,
<a class="xref" href="../../shell/preference-menu.html">Preference Menu Dropdown</a>,
<a class="xref" href="../../platform/i18n.html">Internationalization</a>,
<a class="xref" href="../../platform/log-viewer.html">Log Viewer Page</a> and
<a class="xref" href="../../execution/step-present-dialog.html">Step Present Dialog</a>.</p>
<h2 id="common-geom-and-widget">Common, Geom and Widget</h2>
<ul>
<li><code>Common/</code> — cross-cutting infrastructure with no single domain owner: the optional login gate, the
named-root file explorer, path guards, the daily file logger, and two pieces the whole application
rests on. <code>Common/IndexService.cs</code> is the keyed object store behind the <em>index a backend object,
then edit it by key</em> pattern every geometry, transformer and widget controller uses.
<code>Common/CleanupHub.cs</code> is not a messaging hub, and it is not what bounds that store either. Its
key registry is an ordinary instance property and SignalR builds a fresh hub instance for every
invocation, so the entry <code>Add</code> records is discarded with the instance that received it and the
disconnect handler always walks an empty registry. The bound comes from the browser instead:
<code>wwwroot-src/src/composables/useCleanupHub.ts</code> posts the index-remove endpoint on
<code>Common/IndexController.cs</code> for every key it holds when its host unmounts, and again whenever a
key it registered is replaced. Documented in
<a class="xref" href="../../conventions/dictionary-service-pattern.html">Dictionary Service Pattern</a>,
<a class="xref" href="../../conventions/webapi-hub-cleanup-pattern.html">WebAPI Hub Cleanup Pattern</a>,
<a class="xref" href="../../platform/login-and-auth.html">Login and Authentication</a> and
<a class="xref" href="../../platform/log-viewer.html">Log Viewer Page</a>.</li>
<li><code>Geom/</code> — one CRUD controller per geometry kind, on the same keyed-object pattern, and nothing
else: no hubs, no services. Documented in <a class="xref" href="../../geom/index.html">Geometry Panels</a>.</li>
<li><code>Widget/</code> — the generic value and lifecycle controllers the reusable inputs post to: the 3D vector,
the 4×4 matrix, and the object-management surface that handles file operations, XML editing and
copy-paste for indexed objects. Documented in
<a class="xref" href="../../widget/object-management-menu-button.html">Object Management Menu Button</a>,
<a class="xref" href="../../widget/vec3d-control.html">Vec3dControl Component</a> and
<a class="xref" href="../../widget/mat4d-control.html">Mat4dControl Component</a>.</li>
</ul>
<h2 id="controller-and-demo">Controller and Demo</h2>
<p><code>Controller/</code> is the single-file surface behind the legacy controller page. It has a successor in
<code>Mech/SoftNcRunnerController.cs</code>, whose own doc comment says so, but <strong>both are live</strong> — the legacy
page has not been removed. Documented in <a class="xref" href="../../legacy-controller.html">Legacy Controller</a>.</p>
<p><code>Demo/</code> is customer-facing sample code — a custom colour guide and a per-step optimization sweep —
compiled into the assembly with no call site in the running application. Read it as an example of
how to extend the engine, not as part of the shipped behaviour.</p>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="../index.html">Anatomy by Source Directory</a> — the other two source trees, and how this
layer is meant to be entered</li>
<li><a class="xref" href="../webservice-spa/index.html">Web Service SPA Source Tree</a> — the front end this process
serves and answers</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,270 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Web Service SPA Source Tree | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Web Service SPA Source Tree | HiAPI-C# 2025 ">
<link rel="icon" href="../../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../../public/docfx.min.css">
<link rel="stylesheet" href="../../../public/main.css">
<meta name="docfx:navrel" content="../../../toc.html">
<meta name="docfx:tocrel" content="../toc.html">
<meta name="docfx:rel" content="../../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../../index.html">
<img id="logo" class="svg" src="../../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-BySource-WebserviceSpa">
<h1 id="web-service-spa-source-tree">Web Service SPA Source Tree</h1>
<p><code>HiNC-2025-webservice/wwwroot-src/src</code> is the flagship front end: a Quasar CLI single-page
application in Vue 3, TypeScript and Pinia, served by the same ASP.NET Core process that answers its
REST calls. Every page in Anatomy outside the desktop map documents something in this tree.</p>
<p>Folders below follow the stock Quasar skeleton order, with the two places the skeleton breaks called
out where they occur.</p>
<h2 id="the-one-thing-to-know-first">The One Thing to Know First</h2>
<p><code>components/</code> is grouped <strong>both ways at the same level</strong>, and that is deliberate rather than untidy:</p>
<ul>
<li><strong>Page-scoped</strong><code>components/controller/</code>, <code>components/execution/</code> and half of <code>components/mech/</code>
are chrome for exactly one route.</li>
<li><strong>Domain-scoped</strong><code>components/geom/</code>, <code>components/topo/</code>, <code>components/toolhouse/</code>,
<code>components/spindle/</code>, <code>components/workpiece/</code> and <code>components/mission/</code> are pulled in from
wherever the domain surfaces, most often the Control Tree.</li>
<li><strong>Primitives</strong><code>components/widgets/</code> and <code>components/panels/</code> have no domain at all and are the
most-imported folders in the application.</li>
</ul>
<p>So a component's folder does not tell a reader who mounts it. <code>components/mission/</code> has no Mission
page — <code>/mission</code> redirects into the Execution page's tree — and <code>components/workpiece/</code> holds a
single dropdown while the real workpiece editors are Control-Tree panels.</p>
<p>The second break is history. The application has been re-architected repeatedly, and because
Control-Tree node ids ride in <code>?tree=</code> links they are a public surface: every regroup <strong>adds</strong> a
migration hop rather than rewriting the last one. The residue is visible in <code>wwwroot-src/src/router/routes.ts</code>, where a large share of the table is
redirects preserving URLs from earlier architectures — a route existing there does <strong>not</strong> mean a
page exists for it.</p>
<h2 id="root-and-boot">Root and Boot</h2>
<ul>
<li><code>wwwroot-src/src/App.vue</code> — not the shell. It is a bare router view plus the once-per-load wiring
that subscribes the project store to the execution-status hub. The real shell is
<code>wwwroot-src/src/layouts/MainLayout.vue</code>. Documented in
<a class="xref" href="../../platform/session-state.html">Session State</a>.</li>
<li><code>wwwroot-src/src/boot/auth.ts</code>, <code>wwwroot-src/src/boot/i18n.ts</code>,
<code>wwwroot-src/src/boot/routine-toast.ts</code> — Quasar boot files, run once before mount and in a
declared order. The auth boot file patches the global fetch and inspects every 401, which is why
no API module carries its own 401 handling — but the redirect it can raise is conditional: it
fires only while the auth store reports the login gate enabled, and not when the router is
already on the login route, so on a build with the gate off a 401 redirects nowhere. The toast
boot file patches the shared notify helper so every toast is mirrored into the footer history
without touching a call site. Documented in
<a class="xref" href="../../platform/login-and-auth.html">Login and Authentication</a> and
<a class="xref" href="../../platform/i18n.html">Internationalization</a>.</li>
<li><code>wwwroot-src/src/layouts/MainLayout.vue</code> — the shell: menu bar, the routed page container, footer.
It also owns the mechanism every page depends on and no page implements: a project epoch, bumped
when the loaded project changes, is the keep-alive key, so a project change destroys and rebuilds
every cached page. Documented in <a class="xref" href="../../shell/main-panel.html">Main Panel</a> and
<a class="xref" href="../../platform/session-state.html">Session State</a>.</li>
</ul>
<h2 id="the-rest-edge">The REST Edge</h2>
<p><code>wwwroot-src/src/api/</code> is one thin typed module per backend controller family — functions, DTO types
and kind unions, no Vue code. This is the tightest correspondence between the two halves of the
application: each module wraps one named controller almost one-to-one.</p>
<ul>
<li><code>wwwroot-src/src/api/http.ts</code> — the shared response layer. Its own header names the modules that
deliberately bypass it, so &ldquo;every API module goes through it&rdquo; would be wrong.</li>
<li><code>wwwroot-src/src/api/index-service.ts</code><strong>not</strong> a barrel file. It wraps the backend's keyed object
store, which is where the <code>key</code> string threaded through the whole application comes from.
Documented in <a class="xref" href="../../conventions/dictionary-service-pattern.html">Dictionary Service Pattern</a>.</li>
</ul>
<h2 id="components">Components</h2>
<ul>
<li><code>wwwroot-src/src/components/</code> — the shared top level: <code>wwwroot-src/src/components/AppMenuBar.vue</code>,
<code>wwwroot-src/src/components/AppFooter.vue</code>, <code>wwwroot-src/src/components/RenderingCanvas.vue</code>,
<code>wwwroot-src/src/components/FileExplorer.vue</code> and <code>wwwroot-src/src/components/StlPreviewPane.vue</code>.
The canvas does not render locally: it opens a SignalR connection and paints server-rendered
frames, which is why several pages each own a canvas bound to a different backend scene.
Documented in
<a class="xref" href="../../conventions/rendering-canvas-web-service.html">Rendering Canvas on Web Service</a>,
<a href="../../util/file-explorer.html">File Explorer</a> and
<a class="xref" href="../../util/stl-preview-pane.html">STL Preview Pane</a>.</li>
<li><code>wwwroot-src/src/components/controlTree/</code> — the largest folder in the SPA by a factor of two, and
a <strong>registry-driven panel system</strong> rather than a folder of tree widgets.
<code>wwwroot-src/src/components/controlTree/itemTypes.ts</code> and its domain siblings map an item-type
string onto a panel component and a child-building function;
<code>wwwroot-src/src/components/controlTree/useControlTreeHost.ts</code> is the state machine that builds the
tree, gates a dirty selection switch, and syncs <code>?tree=</code>; the many
panel components are the editors the registry resolves. It serves <strong>three</strong> consumers, not one —
the Execution and General Setup pages each instantiate their own scoped host, while the Tool House
page reuses the identical panels and registry through a tab cascade and never touches the host.
Documented in <a class="xref" href="../../platform/control-tree.html">Control Tree</a> — the folder's own page — with
<a class="xref" href="../../execution/index.html">Execution Page</a>, <a class="xref" href="../../general-setup/index.html">General Setup Page</a> and
<a class="xref" href="../../tool-house/index.html">Tool House Page</a> for the three consumers, and
<a class="xref" href="../../execution/program.html">Program Branch</a> for the NC-program item types and panels that live in
this folder. The nineteen <code>SoftNc*</code> panels and their registry are a wave of their own, documented
under <a class="xref" href="../../general-setup/controller/index.html">Controller Branch</a>: which of them the tree mounts at all is decided
by <a class="xref" href="../../general-setup/controller/brand-matrix.html">Brand Matrix</a>, and what they share is
<a class="xref" href="../../general-setup/controller/editing-contract.html">Editing Contract</a>.</li>
<li><code>wwwroot-src/src/components/controlTree/toolhouse/</code> — the Tool House branch's panels. Easy to
confuse with <code>wwwroot-src/src/components/toolhouse/</code>: these are the panels the registry mounts,
that folder holds
the content fragments those panels embed, and the dependency runs one way only.</li>
<li><code>wwwroot-src/src/components/execution/</code> — the run cockpit's own panels, plus its <code>charts/</code>
sub-folder, the uPlot charting layer. Not purely page-local:
<code>wwwroot-src/src/components/execution/ExecutionToolBar.vue</code> is mounted by the Control Tree's
primary panel, and the spindle contours chart imports from the charts folder. Documented in
<a class="xref" href="../../execution/index.html">Execution Page</a>, <a class="xref" href="../../execution/strip-charts.html">Strip Charts</a> and
<a class="xref" href="../../execution/cycle-line-charts.html">Cycle-Line Charts</a>.</li>
<li><code>wwwroot-src/src/components/geom/</code> and <code>wwwroot-src/src/components/topo/</code> — structural twins: one
editor per kind, the same <code>modelKey</code> prop and <code>changed</code> / <code>error</code> emits, and a single kind → editor
map — <code>wwwroot-src/src/components/geom/geometryEditors.ts</code> and
<code>wwwroot-src/src/components/topo/transformerEditors.ts</code> — that is the source of truth for both the
switchboard and the Control Tree. A new kind must be registered there,
not merely dropped in the folder. &ldquo;topo&rdquo; means coordinate <strong>transformers</strong>, not mesh topology.
Documented in <a class="xref" href="../../geom/index.html">Geometry Panels</a> and
<a class="xref" href="../../geom/transformer-panel.html">Transformer Select Panel</a>.</li>
<li><code>wwwroot-src/src/components/widgets/</code> — the reusable input library: numeric, vector and matrix
inputs, the file-path input and picker, the CodeMirror text editor, the display-options and
object-management menus. Documented in <a class="xref" href="../../widget/index.html">Widgets</a>.</li>
<li><code>wwwroot-src/src/components/panels/</code> — pure layout machinery with no domain: the collapsible
expansion panel and the resizable stack whose registration contract produces the &ldquo;rows collapse in
place&rdquo; behaviour the tree pages describe. Documented in
<a class="xref" href="../../platform/control-tree.html">Control Tree</a>, whose dock rows are both expansion rows, and
<a class="xref" href="../../platform/session-state.html">Session State</a> for the keep-mounted flag that decides whether a
collapse unmounts its content.</li>
<li><code>wwwroot-src/src/components/mission/</code>, <code>.../toolhouse/</code>, <code>.../spindle/</code>, <code>.../preference/</code>,
<code>.../mech/</code>, <code>.../workpiece/</code> and <code>.../controller/</code> — the domain and page folders named above. The
<code>*Div.vue</code> suffix inside the Tool House folder is a convention carried over from the legacy Blazor
components: a Div is an embeddable content fragment with no panel chrome.</li>
</ul>
<h2 id="state-routing-and-text">State, Routing and Text</h2>
<ul>
<li><code>wwwroot-src/src/composables/</code> — three different concerns in one folder: hub access, shared domain
state, and UI mechanics. <code>wwwroot-src/src/composables/useSharedHub.ts</code> is the reference-counted connection manager
behind every hub composable, so a hub opens only while something consumes it.
<code>wwwroot-src/src/composables/useToolHouse.ts</code> and <code>wwwroot-src/src/composables/useSpindleCapability.ts</code> are module-level
singletons, not per-component instances. <code>wwwroot-src/src/composables/useViewPrefs.ts</code> stores layout state in the
browser only — it is neither in the project file nor in the server's user config. Documented in
<a class="xref" href="../../platform/session-state.html">Session State</a>.</li>
<li><code>wwwroot-src/src/stores/</code> — four Pinia stores. <code>wwwroot-src/src/stores/index.ts</code> is <strong>not</strong> a barrel; it is the
Quasar factory. Most shared state lives in <code>composables/</code> instead. Documented in
<a class="xref" href="../../platform/session-state.html">Session State</a>.</li>
<li><code>wwwroot-src/src/router/</code><code>wwwroot-src/src/router/routes.ts</code> is the table plus the legacy redirects, and
<code>wwwroot-src/src/router/treeRoutes.ts</code> is the load-bearing file its name understates: it holds the accumulated
chain of tree-id renames and the id → page resolver, plus the tab-name constants both the router
and the tab composable consume. A route's <code>meta.title</code> holds an i18n key, not a title.
Documented in <a class="xref" href="../../platform/tree-ids-and-routes.html">Tree Ids and Routes</a>.</li>
<li><code>wwwroot-src/src/i18n/</code> — three locales shipped together, each a list of namespace files.
<strong>English is the schema</strong>, not merely a locale: the two Chinese bundles are typed against it, so a
key present in English and missing there is a build error. Keys are split by UI region rather than
by source file, so there is no one-to-one mapping between an i18n file and a components folder.
Documented in <a class="xref" href="../../platform/i18n.html">Internationalization</a> and
<a class="xref" href="../../conventions/translation-remarks.html">Translation Remarks</a>.</li>
<li><code>wwwroot-src/src/pages/</code> — one file per route, and page size is a poor guide to importance: the
General Setup page is small because it delegates almost everything to the Control Tree dock and the
equipment canvas, while the File Explorer page is a thin wrapper around a large shared component.</li>
<li><code>wwwroot-src/src/utils/</code>, <code>wwwroot-src/src/directives/</code>, <code>wwwroot-src/src/css/</code> — small and mostly
presentational. Two severity scales exist and must not be conflated:
<code>wwwroot-src/src/utils/messageSeverity.ts</code> maps the engine's diagnostic scale, while the footer's routine severity
is a separate, shorter toast scale.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="../index.html">Anatomy by Source Directory</a> — the other two source trees, and how this
layer is meant to be entered</li>
<li><a class="xref" href="../webservice-backend/index.html">Web Service Backend Source Tree</a> — the process that
serves this application and answers its REST and hub traffic</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -11,7 +11,7 @@
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
@@ -84,7 +84,7 @@
<nav id="breadcrumb"></nav>
</div>
<article data-uid="">
<article data-uid="Anatomy-DictionaryServicePattern">
<h1 id="dictionaryservice-and-dictionaryhub-pattern">DictionaryService and DictionaryHub Pattern</h1>
<h2 id="overview">Overview</h2>
@@ -11,7 +11,7 @@
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
@@ -84,7 +84,7 @@
<nav id="breadcrumb"></nav>
</div>
<article data-uid="">
<article data-uid="Anatomy-GuiFilePathAssignment">
<h1 id="gui-file-path-assignment">GUI File Path Assignment</h1>
<p>See the remarks of <a class="xref" href="../../api/Hi.Common.XmlUtils.IMakeXmlSource.html#Hi_Common_XmlUtils_IMakeXmlSource_MakeXmlSource_System_String_System_String_System_Boolean_">MakeXmlSource(string, string, bool)</a> to know the design pattern of file path treatment.</p>
@@ -2,16 +2,16 @@
<html>
<head>
<meta charset="utf-8">
<title>Graphic-Cache SubMenu | HiAPI-C# 2025 </title>
<title>Conventions | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Graphic-Cache SubMenu | HiAPI-C# 2025 ">
<meta name="title" content="Conventions | HiAPI-C# 2025 ">
<link rel="icon" href="../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
@@ -84,38 +84,50 @@
<nav id="breadcrumb"></nav>
</div>
<article data-uid="">
<h1 id="graphic-cache-submenu">Graphic-Cache SubMenu</h1>
<article data-uid="Anatomy-GeneralRules">
<h1 id="conventions">Conventions</h1>
<p>In the WPF application the submenu locates on the <a href="index.html">Preference Menu Dropdown</a>. The web application moved it onto the Player toolbar's <code>Workpiece ▾</code> dropdown, since it acts on the workpiece meshed geometry's rendering cache.</p>
<p>The model <a class="xref" href="../../sample/Sample.Common.UserService.html">UserService</a> is from its parent component.</p>
<h2 id="layout">Layout</h2>
<p>The rules and shared contracts every Anatomy page assumes. A page in this folder describes
something that is true across screens rather than something a reader can point at: how a message
reaches the user, how a control behaves when its model is wrong, how a file path is stored, how a
component releases what it indexed on the server.</p>
<p>Ordered from the rules that bind every page in both applications, through the two shared UI
contracts, down to the service patterns and the canvas transport beneath them.</p>
<h2 id="rules-that-bind-every-page">Rules That Bind Every Page</h2>
<h3 id="message-and-exception-handling">Message and Exception Handling</h3>
<p>HiNC uses three independent message categories: <strong>Diagnostic</strong> (<code>IProgress&lt;object&gt;</code>), <strong>UI
Notification</strong> (<code>MessageBoardUtil</code>), and <strong>App Log</strong> (<code>ILogger</code>). See
<a class="xref" href="../../technique/api-foundations/message-management.html">Message Management</a> for the full design pattern.</p>
<p>For async exception handling, use <code>CatchExceptions</code> with a caller-provided handler:</p>
<pre><code class="lang-csharp">await task.CatchExceptions(ex =&gt; progress?.Report(ex));
</code></pre>
<p>The <a class="xref" href="../shell/bottom-message-bar.html">Bottom Message Bar</a> displays UI-level notifications. The
<a class="xref" href="../shell/session-message-panel.html">Session Message Panel</a> displays session diagnostic messages.</p>
<h3 id="loose-manner">Loose Manner</h3>
<p>The Loose Manner pattern handles rapidly-called synchronous actions where only the last call needs
to be effective.</p>
<p>The <a class="xref" href="../../api/Hi.Common.LooseRunner.html">LooseRunner</a> class manages skippable rapid-calling synchronous actions. When an
action is called rapidly, only the last call is executed while previous calls are safely skipped.
The <a class="xref" href="../../api/Hi.Common.LooseRunner.html#Hi_Common_LooseRunner_TryRun_">TryRun</a> method is used to execute actions in this manner.</p>
<p>The <a class="xref" href="../../api/Hi.Common.LooseRunner.html">LooseRunner</a> should be disposed when its owner is disposed to ensure proper
resource cleanup.</p>
<h3 id="loose-couple">Loose Couple</h3>
<p>If the model of a UI component is null or mismatched, apply a status badge instead of throwing an
exception, so the rest of the UI keeps working.</p>
<h2 id="pages">Pages</h2>
<ul>
<li>Graphic-Cache SubMenu
<ul>
<li><code>Graphic-Cache Lower Limit Input Text Field</code></li>
<li><code>Graphic-Cache Upper Limit Input Text Field</code></li>
<li><code>Graphic-Cache Input Text Field</code></li>
<li><code>Graphic-Cache Slider</code></li>
<li><a href="translation-remarks.html">Translation Remarks</a> — The terminology every translated UI label is held to, so the same concept keeps the same word across screens and languages</li>
<li><a href="gui-file-path-assignment.html">GUI File Path Assignment</a> — How a control stores a chosen file: relative to the configuration directory when it sits inside it, absolute when it does not</li>
<li><a href="numeric-io-utilities.html">Numeric Input/Output Utilities</a> — Handling Infinity, -Infinity and NaN across the JavaScript / C# boundary, where plain JSON cannot carry them</li>
<li><a href="dictionary-service-pattern.html">DictionaryService and DictionaryHub Pattern</a> — Connection-scoped indexing that lets a hierarchical component reference a backend object across hub connections</li>
<li><a href="webapi-hub-cleanup-pattern.html">Webapi with Hub-Cleanup Assistance Pattern</a> — Which component owns an index key and where it releases it, so a component never cleans a key it did not create</li>
<li><a href="rendering-canvas-web-service.html">Rendering Canvas on the Web Service</a> — The SignalR transport that puts a server-rendered 3D canvas on a browser page</li>
</ul>
</li>
</ul>
<h2 id="behavior">Behavior</h2>
<p><code>Graphic-Cache Input Text Field</code> and <code>Graphic-Cache Slider</code> bind the <a class="xref" href="../../sample/Sample.Common.UserConfig.html#Sample_Common_UserConfig_GraphicCacheMb">GraphicCacheMb</a>. The limit text fields also bind to the properties of <a class="xref" href="../../sample/Sample.Common.UserConfig.html">UserConfig</a>.</p>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a href="../index.html">this page</a> for git repository.</p>
<h3 id="wpf-application-source-code-path">WPF Application Source Code Path</h3>
<h2 id="see-also">See Also</h2>
<ul>
<li>MainWindow (be included in preference menu)</li>
</ul>
<h3 id="web-page-application-source-code-path">Web Page Application Source Code Path</h3>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/preference/GraphicCacheMenu.vue</code> — nested <code>&lt;q-menu&gt;</code> under <code>Workpiece → Graphic Cache</code> with Lower / Upper (<code>NumericInput</code>) + Current (<code>NumericInput</code>) + <code>&lt;q-slider&gt;</code>. Commits on blur / change.</li>
<li><code>wwwroot-src/src/components/widgets/NumericInput.vue</code> — shared <code>Infinity</code>-friendly numeric field reused for the three limit inputs.</li>
<li><code>wwwroot-src/src/components/player/PlayerExtendedToolBar.vue</code> — hosts the Graphic Cache entry inside the Player toolbar's <code>Workpiece ▾</code> dropdown (next to Diff Visual Radius). The menu bar (<code>wwwroot-src/src/components/AppMenuBar.vue</code>) no longer carries it.</li>
<li><code>wwwroot-src/src/api/preference.ts</code> — typed wrapper over <code>GET/POST /api/preference/graphic-cache</code>.</li>
<li><code>Environments/PreferenceController.cs</code> — REST endpoints: <code>GET /api/preference/graphic-cache</code> returns <code>{ graphicCacheMb, lowerLimitMb, upperLimitMb }</code>; <code>POST</code> writes through <code>UserService.SaveUserConfig()</code><code>UserConfig.GraphicCacheMb</code><code>CubeTree.DispCacheMb</code>.</li>
<li><a href="../platform/index.html">Platform</a> — the other cross-screen folder: the machinery these rules are applied on top of, including the host process that starts HiAPI</li>
<li><a href="../shell/index.html">App Shell</a> — the window frame these conventions are applied inside</li>
<li><a href="../widget/index.html">Widgets</a> — the reusable controls that implement two of them</li>
</ul>
</article>
@@ -0,0 +1,202 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Numeric Input/Output | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Numeric Input/Output | HiAPI-C# 2025 ">
<link rel="icon" href="../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../index.html">
<img id="logo" class="svg" src="../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-NumericIoUtilities">
<h1 id="numeric-inputoutput">Numeric Input/Output</h1>
<p><code>NaN</code>, <code>Infinity</code> and <code>-Infinity</code> are ordinary values in a geometry model and ordinary things for a
user to type, but JSON has no encoding for any of them. This page describes how the web application
carries them across the boundary in both directions.</p>
<h2 id="the-server-half">The Server Half</h2>
<p>One line of configuration does it. <code>Program.cs</code> sets</p>
<pre><code class="lang-csharp">options.JsonSerializerOptions.NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals;
</code></pre>
<p>so the ASP.NET Core serializer reads and writes the three values as the JSON string literals
<code>&quot;NaN&quot;</code>, <code>&quot;Infinity&quot;</code> and <code>&quot;-Infinity&quot;</code> instead of failing. It sits on the <code>AddControllers</code> JSON
options, so every controller inherits it and no DTO, converter or endpoint has to opt in. Without it
a <code>double.NaN</code> anywhere in a response throws during serialization, which is why the setting is
load-bearing rather than a convenience.</p>
<p>The reach stops at the controllers. Hub payloads are serialized by SignalR's own protocol options,
and nothing configures them: <code>AddSignalR()</code> is registered bare and the service contains no
<code>AddJsonProtocol</code> call, so the named literals are a controller-JSON contract rather than a
service-wide one.</p>
<h2 id="the-client-half">The Client Half</h2>
<p>There is no shared numeric module. Each input widget formats and parses the special values itself,
and the three that ship do not agree on all of it. The widgets are also not the only readers of this
boundary: an API module that has to put a non-finite number on the wire converts it in place, as
<code>wwwroot-src/src/api/mission.ts</code> does for the mission command fields. That conversion is not
symmetric — it writes all three literals but recognises only the two infinity spellings on the way
back — so a <code>NaN</code> returned by those endpoints resolves to the caller's supplied default instead.</p>
<table>
<thead>
<tr>
<th></th>
<th><code>NumericInput.vue</code></th>
<th><code>Vec3Input.vue</code></th>
<th><code>Mat4Input.vue</code></th>
</tr>
</thead>
<tbody>
<tr>
<td><code>NaN</code> displays as</td>
<td>empty field</td>
<td><code>NaN</code></td>
<td><code>0</code></td>
</tr>
<tr>
<td><code>Infinity</code> / <code>-Infinity</code> display as</td>
<td>literal text</td>
<td>literal text</td>
<td>literal text</td>
</tr>
<tr>
<td>An empty field parses to</td>
<td><code>null</code>, under the default <code>allowEmpty</code></td>
<td><code>0</code></td>
<td><code>0</code></td>
</tr>
<tr>
<td>Unparseable text on blur</td>
<td>stays in the box under an error message</td>
<td>reverts to the last valid value</td>
<td>reverts to the last valid value</td>
</tr>
</tbody>
</table>
<p>All three accept the same spellings on the way in: <code>infinity</code>, <code>-infinity</code> and <code>nan</code>
case-insensitively. The <code></code> and <code>-∞</code> glyphs are where they part: the single-value field and the
three-axis editor take them, the matrix grid does not. All three commit on blur or Enter rather than
per keystroke, but only the vector and matrix editors compare the parsed value against the model
before emitting. The single-value field carries no such guard and emits on every accepted commit, so
committing with Enter and then leaving the field writes the same value twice.</p>
<p>The consequence worth knowing: <strong><code>NaN</code> does not survive a round trip through the matrix editor.</strong>
<code>Mat4Input.vue</code> renders it as <code>0</code>, so re-committing a cell that held <code>NaN</code> writes a real zero.</p>
<p><code>NumericInput.vue</code> is the input the rest of the app reaches for: no other component in the client is
embedded by anywhere near as many editors, so a new numeric field should embed it rather than repeat
the parse. Its own props, bounds and validation messages are documented at
<a class="xref" href="../widget/numeric-input.html">Numeric Input</a>.</p>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../index.html">HiNC App Anatomy</a> for git repository links.</p>
<h3 id="web-page-application-source-code-path">Web Page Application Source Code Path</h3>
<ul>
<li><code>wwwroot-src/src/components/widgets/NumericInput.vue</code> — the shared single-value numeric field: local <code>formatValue</code> / <code>parseValue</code>, blur-and-Enter commit, and the rejected text left standing under an error message when a commit fails.</li>
<li><code>wwwroot-src/src/components/widgets/Vec3Input.vue</code> — the three-axis editor; carries its own copy of the same format and parse pair, plus a <code>(x, y, z)</code> text form.</li>
<li><code>wwwroot-src/src/components/widgets/Mat4Input.vue</code> — the sixteen-cell matrix grid; the same pair again without the glyph spellings, and with the <code>NaN</code>-to-zero behaviour above.</li>
<li><code>wwwroot-src/src/api/mission.ts</code> — a non-widget reader of the same boundary: <code>numericToApiString</code> writes all three literals for the mission command fields, <code>parseMaybeInfiniteNumber</code> reads only the two infinity spellings back.</li>
<li><code>Program.cs</code> — the <code>AllowNamedFloatingPointLiterals</code> setting on the controller JSON options that lets the three values cross as JSON at all, and the bare <code>AddSignalR()</code> registration that does not share it.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="../widget/vec3d-control.html">Vec3dControl Component</a> — the three-axis editor, one of the three inputs that carries its own format and parse</li>
<li><a class="xref" href="../widget/mat4d-control.html">Mat4dControl Component</a> — the sixteen-cell grid, the one input that renders NaN as zero</li>
<li><a class="xref" href="../widget/numeric-input.html">Numeric Input</a> — the single-value field, and the widget that carries this contract to the most callers</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,241 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Rendering Canvas on Web Service Application | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Rendering Canvas on Web Service Application | HiAPI-C# 2025 ">
<link rel="icon" href="../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../index.html">
<img id="logo" class="svg" src="../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-RenderingCanvasWebService">
<h1 id="rendering-canvas-on-web-service-application">Rendering Canvas on Web Service Application</h1>
<h2 id="overview">Overview</h2>
<p>The web application's 3D canvas is rendered on the server and streamed to the browser over a
SignalR hub connection at <code>/renderingHub</code>. <code>Disp/RenderingHub.cs</code> serves that hub, <code>Program.cs</code>
maps it, and the singleton in <code>Disp/RenderingService.cs</code> owns one native display engine per hub
connection. The browser paints the frames that arrive and forwards the user's input back.</p>
<h2 id="core-component">Core Component</h2>
<ul>
<li><strong>Location</strong>: <code>wwwroot-src/src/components/RenderingCanvas.vue</code></li>
<li><strong>Purpose</strong>: owns one hub connection, forwards input to the server engine, and paints the frames
the server streams back</li>
</ul>
<p>Its <code>hubUrl</code> prop defaults to <code>/renderingHub</code>, and it exposes <code>connect</code>, <code>disconnect</code>, the eight
view commands and <code>clearCache</code> — the surface through which
<code>wwwroot-src/src/components/RenderingCanvasToolBar.vue</code> drives the canvas instance it is bound to.</p>
<h2 id="connection-management">Connection Management</h2>
<h3 id="signalr-hub-connection">SignalR Hub Connection</h3>
<p>Each <code>RenderingCanvas</code> instance opens its own hub connection and therefore owns one connection ID;
the server keeps one display engine per connection ID, so several canvases can be live at the same
time. On initialization the hub sends the connection ID back to the caller as <code>CanvasInitialized</code>,
and the component re-emits it as <code>serverInitialized</code>. It is re-emitted after every reconnect,
because the server disposes the engine on disconnect and a reconnect gets a new ID — the parent
page answers by registering its content against that new ID.</p>
<p>The connection ID is the index for every canvas operation. <code>Disp/RenderingService.cs</code> keys the
engine dictionary, the last-input time, the last lossless-frame hash and the negotiated frame
format by it, and <code>Disp/RenderingHub.cs</code> keys its own sketch-view cache the same way.</p>
<h3 id="engine-lifetime">Engine Lifetime</h3>
<p>The engine's life is the connection's: <code>GetOrCreateEngine</code> creates it when the canvas initializes,
and <code>RemoveEngine</code> — called from <code>OnDisconnectedAsync</code> — disposes it and raises <code>EngineRemoved</code>.
An HTTP endpoint therefore resolves an engine through the non-creating <code>GetEngine</code>; creating one
for an ID that has already disconnected would orphan an engine no disconnect can ever clean up.
<code>Disp/StlPreviewService.cs</code> subscribes to <code>EngineRemoved</code> to release the per-connection native
topology the engine itself does not own.</p>
<h3 id="frame-encoding">Frame Encoding</h3>
<p>The frame encoding is negotiated at initialization. <code>InitializeCanvasV2(width, height, formats)</code>
with <code>jpeg</code> in the accepted list selects SkiaSharp JPEG frames delivered on <code>ImageUpdateV2</code> at an
adaptive quality — interactive frames compress harder and a still frame refines once — and adding
<code>png</code> lets that refine be encoded lossless. A browser without <code>createImageBitmap</code>, or a server that
does not carry the V2 method, falls back to <code>InitializeCanvas</code> and the gzip-RGBA frames delivered
on <code>ImageUpdate</code>.</p>
<h3 id="connection-id-naming-convention">Connection ID Naming Convention</h3>
<p>One value carries two names on the frontend:</p>
<ul>
<li><code>renderingConnectionId</code> — the parameter name used throughout the API wrappers under
<code>wwwroot-src/src/api</code>, and the route-parameter name on most display controllers</li>
<li><code>renderingConnId</code> — how the Execution page holds it (<code>wwwroot-src/src/pages/ExecutionPage.vue</code>),
filled from the canvas's <code>@server-initialized</code> and passed to the typed wrappers in
<code>wwwroot-src/src/api/execution.ts</code>, whose parameter is <code>renderingConnectionId</code></li>
</ul>
<h2 id="data-flow-architecture">Data Flow Architecture</h2>
<h3 id="frontend-responsibilities">Frontend Responsibilities</h3>
<p>The component owns the connection and its reconnect schedule, forwards pointer, wheel, key, touch,
resize and visibility events to the hub, and paints each frame as it arrives. A canvas the layout
has hidden keeps its connection open and tells the engine to pause rendering instead, so a hidden
canvas costs nothing while a simulation runs.</p>
<h3 id="backend-integration">Backend Integration</h3>
<p>Rendering and frame encoding happen on the server; a controller decides only what the engine shows,
by resolving the engine from the connection ID and assigning its displayee. That is why any feature
can put its own content on a canvas without touching the transport.</p>
<h4 id="example-execution-controller">Example: Execution Controller</h4>
<ul>
<li><strong>File</strong>: <code>Execution/ExecutionController.cs</code></li>
<li><strong>Method</strong>: <code>InitializeExecution</code> (<code>POST /api/Execution/initialize/{connectionId}</code>)</li>
<li><strong>Purpose</strong>: resolves the engine with <code>RenderingService.GetEngine(connectionId)</code> and assigns the
Execution displayee to it</li>
</ul>
<p>Six further controllers bind content the same way — the STL preview, the Tool House, the General
Setup equipment, the machine tool, the Mech Builder general mechanism and the Controller page —
each with its own route and its own displayee, all reaching the engine through the same connection
ID.</p>
<h2 id="key-points">Key Points</h2>
<ul>
<li>Every canvas data-stream operation is indexed by the connection ID.</li>
<li>Rendering and frame encoding happen on the server; the frontend owns the connection, the input
events and the painting of received frames.</li>
<li>The engine is created and destroyed with the hub connection, so anything else that holds
per-connection resources releases them from <code>EngineRemoved</code>.</li>
</ul>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../index.html">HiNC App Anatomy</a> for git repository links.</p>
<h3 id="web-service-source-code-path">Web Service Source Code Path</h3>
<ul>
<li><code>Disp/RenderingHub.cs</code> — the hub: creates the per-connection engine, encodes and pumps frames,
handles mouse, wheel, key, touch, resize and visibility, and disposes the engine on disconnect.</li>
<li><code>Disp/RenderingService.cs</code> — the singleton holding the per-connection maps, the playback render
throttle and the <code>EngineRemoved</code> event.</li>
<li><code>Disp/StlPreviewService.cs</code> — the <code>EngineRemoved</code> subscriber that frees the File Explorer
preview's native topology.</li>
<li><code>Program.cs</code> — registers SignalR and maps the hub at <code>/renderingHub</code>.</li>
<li><code>Execution/ExecutionController.cs</code> — the worked example above: <code>InitializeExecution</code> assigns the
Execution displayee to the engine named by the connection ID.</li>
<li><code>Disp/StlPreviewController.cs</code> — binds an STL preview to a canvas (<code>api/stl-preview</code>).</li>
<li><code>Mech/ToolHouseDisplayController.cs</code> — the Tool House canvas binding
(<code>api/mech/tool-house-display</code>).</li>
<li><code>Mech/EquipmentSetupDisplayController.cs</code> — the General Setup canvas binding
(<code>api/mech/equipment-setup-display</code>).</li>
<li><code>Mech/MachineToolDisplayController.cs</code> — the machine-tool canvas binding
(<code>api/mech/machine-tool/display</code>).</li>
<li><code>Mech/MechBuilder/GeneralMechanismDisplayController.cs</code> — the Mech Builder canvas binding
(<code>api/general-mechanism/display</code>).</li>
<li><code>Controller/ControllerController.cs</code> — the Controller page's canvas binding
(<code>initialize-display/{connectionId}</code>).</li>
</ul>
<h3 id="web-page-application-source-code-path">Web Page Application Source Code Path</h3>
<ul>
<li><code>wwwroot-src/src/components/RenderingCanvas.vue</code> — the client half: one hub connection per
instance, encoding negotiation, frame painting, input forwarding and the reconnect loop.</li>
<li><code>wwwroot-src/src/components/RenderingCanvasToolBar.vue</code> — the view-control bar bound to one
canvas instance through its exposed methods.</li>
<li><code>wwwroot-src/src/pages/ExecutionPage.vue</code> — mounts the main canvas, stores the ID as
<code>renderingConnId</code> and initializes the Execution content from <code>@server-initialized</code>.</li>
<li><code>wwwroot-src/src/api/execution.ts</code> — the typed wrappers whose <code>renderingConnectionId</code> parameter
addresses that canvas.</li>
<li><code>wwwroot-src/src/components/execution/StepVolumePanel.vue</code> — a second canvas on the Execution
page, with its own connection and its own engine.</li>
<li><code>wwwroot-src/src/components/mech/EquipmentSetupPanel.vue</code> — the General Setup page's canvas
column.</li>
<li><code>wwwroot-src/src/components/toolhouse/ToolHouseSetupPanel.vue</code> — the Tool House page's canvas
column.</li>
<li><code>wwwroot-src/src/components/StlPreviewPane.vue</code> — the File Explorer STL preview canvas.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="../widget/renderingcanvas-tool-bar.html">RenderingCanvas Tool Bar</a> — the <code>View</code> menu that drives the hub, and the <code>Scene</code> menu beside it</li>
<li><a class="xref" href="../platform/program-and-hosting.html">Program and Hosting</a> — the host that maps this hub and registers the services behind it</li>
<li><a class="xref" href="../util/stl-preview-pane.html">STL Preview Pane</a> — the one caller that opens a rendering connection outside a project</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,40 @@
<div id="sidetoggle">
<div>
<div class="sidefilter">
<form class="toc-filter">
<span class="glyphicon glyphicon-filter filter-icon"></span>
<span class="glyphicon glyphicon-remove clear-icon" id="toc_filter_clear"></span>
<input type="text" id="toc_filter_input" placeholder="Filter by title" onkeypress="if(event.keyCode==13) {return false;}">
</form>
</div>
<div class="sidetoc">
<div class="toc" id="toc">
<ul class="nav level1">
<li>
<a href="index.html" name="" title="Overview">Overview</a>
</li>
<li>
<a href="translation-remarks.html" name="" title="Translation Remarks">Translation Remarks</a>
</li>
<li>
<a href="gui-file-path-assignment.html" name="" title="GUI File Path Assignment">GUI File Path Assignment</a>
</li>
<li>
<a href="numeric-io-utilities.html" name="" title="Numeric Input/Output Utilities">Numeric Input/Output Utilities</a>
</li>
<li>
<a href="dictionary-service-pattern.html" name="" title="DictionaryService and DictionaryHub Pattern">DictionaryService and DictionaryHub Pattern</a>
</li>
<li>
<a href="webapi-hub-cleanup-pattern.html" name="" title="Webapi with Hub-Cleanup Assistance Pattern">Webapi with Hub-Cleanup Assistance Pattern</a>
</li>
<li>
<a href="rendering-canvas-web-service.html" name="" title="Rendering Canvas on the Web Service">Rendering Canvas on the Web Service</a>
</li>
</ul>
</div>
</div>
</div>
</div>
@@ -0,0 +1,2 @@
{"items":[{"name":"Overview","href":"index.html","topicHref":"index.html"},{"name":"Translation Remarks","href":"translation-remarks.html","topicHref":"translation-remarks.html"},{"name":"GUI File Path Assignment","href":"gui-file-path-assignment.html","topicHref":"gui-file-path-assignment.html"},{"name":"Numeric Input/Output Utilities","href":"numeric-io-utilities.html","topicHref":"numeric-io-utilities.html"},{"name":"DictionaryService and DictionaryHub Pattern","href":"dictionary-service-pattern.html","topicHref":"dictionary-service-pattern.html"},{"name":"Webapi with Hub-Cleanup Assistance Pattern","href":"webapi-hub-cleanup-pattern.html","topicHref":"webapi-hub-cleanup-pattern.html"},{"name":"Rendering Canvas on the Web Service","href":"rendering-canvas-web-service.html","topicHref":"rendering-canvas-web-service.html"}]}
@@ -0,0 +1,230 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Translation Remarks | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Translation Remarks | HiAPI-C# 2025 ">
<link rel="icon" href="../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../index.html">
<img id="logo" class="svg" src="../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-TranslationRemarks">
<h1 id="translation-remarks">Translation Remarks</h1>
<p>Terminology is a contract: one concept keeps one word on every screen and in every locale. Three
locales ship together — <code>en</code>, <code>zh-Hant</code> and <code>zh-Hans</code> — and the English bundle is the schema the
Chinese bundles are typed against, so a key missing from a Chinese locale is a build error. The
readings below are the ones the zh-Hant bundle ships.</p>
<h2 id="tool-house-and-mechanism-terms">Tool House and Mechanism Terms</h2>
<ul>
<li><p>Anchor 錨點</p>
</li>
<li><p>Tool 刀具</p>
</li>
<li><p>Cutter 刀具本體</p>
<ul>
<li>Insert-End Cutter 刀片式刀具本體</li>
</ul>
</li>
<li><p>Holder 刀把</p>
<ul>
<li>Cylindroid Holder 柱狀刀把</li>
<li>Freeform Holder 任意形刀把</li>
</ul>
</li>
<li><p>Shank 刀柄</p>
</li>
<li><p>Fixture 夾具</p>
</li>
<li><p>Milling Cutter 銑刀</p>
</li>
<li><p>Freeform Remover 任意移除工具</p>
</li>
<li><p>Flute 刀刃</p>
<ul>
<li>Flute Profile 刃包絡形</li>
<li>Flute Contours 刃雕
<ul>
<li>Baseline Contour 基準刃雕</li>
<li>Side Contour 側向刃雕</li>
<li>Bottom Contour 底部刃雕</li>
</ul>
</li>
</ul>
</li>
<li><p>Upper Beam 夾持柱</p>
</li>
<li><p>Integral Mode 刀頭形式</p>
<ul>
<li>Solid End 一體式</li>
<li>Insert End 刀片式</li>
</ul>
</li>
<li><p>Preset 預設</p>
<ul>
<li>Datum Preset 基準點預設</li>
</ul>
</li>
<li><p>General Config 一般組態 — the mission tree's label for the <code>presetting</code> command kind</p>
</li>
<li><p>Meshed Geometry 網格幾何</p>
</li>
<li><p>Machining Resolution 加工解析度</p>
</li>
</ul>
<h2 id="words-that-carry-more-than-one-sense">Words That Carry More Than One Sense</h2>
<p><code>Contour</code> is the sharpest case: in the cutter namespace it is 刃雕 (Flute Contours 刃雕, Baseline
Contour 基準刃雕), and in the spindle-capability namespace it is 等值線 (Power Contours 功率等值線,
Torque Contours 扭矩等值線). Each sense keeps its own entry rather than one compromise word.</p>
<p><code>Fluting</code> is a cutter's whole set of flute contours — one shared baseline or one per flute — and
translates as 刃雕構型: Uniform Fluting 對稱刃雕構型, Free Fluting 自由刃雕構型.</p>
<p><code>Program</code> is the UI-layer word and does translate: 程式 in zh-Hant, 程序 in zh-Hans. <code>Control</code> in
the frontend names the Control Tree UI, 控制樹, and never takes 程式.</p>
<h2 id="never-translate">Never Translate</h2>
<p><code>HiNC</code> · <code>hincproj</code> · <code>SoftNc</code> · <code>MachiningStep</code> · brand names (Fanuc, Heidenhain, Mazak, Siemens,
Syntec) · NC codes (<code>G54</code>, <code>M128</code>, <code>CYCLE800</code>, <code>TRAORI</code>, <code>RTCP</code>, <code>TCPM</code>) · file extensions and
MIME-ish strings (<code>STL</code>, <code>*.hincproj</code>) · units (<code>mm</code>, <code>rpm</code>, <code>mm/min</code>, <code>N</code>, <code>Nm</code>, <code>kW</code>, <code>KB</code>, <code>MB</code>,
<code>GB</code>) · the keyboard shortcuts embedded in tooltip sentences · route names, enum keys,
localStorage keys and series identifiers — anything a machine compares. Language self-names
(English, 简体中文, 繁體中文) stay in their own language in every locale. The full list lives with
the bundles, in the i18n folder's README.</p>
<h2 id="terms-carried-by-the-desktop-client">Terms Carried by the Desktop Client</h2>
<p>Three milling-cutter surfaces exist only on the WPF client, and their terms belong to the same
contract, so none of the words is reused elsewhere:</p>
<ul>
<li>Insert-Cutter 刀片 — a tab on <code>Mech/ToolHouse/MillingCutterPanel.xaml</code></li>
<li>Flute-Inner-Beam 刃中芯 — the tab served by <code>Mech/ToolHouse/FluteInnerBeamPanel.xaml</code></li>
<li>Cutter Integral Mode 刀頭形式 — a labelled tab there as well; the web application carries the
same choice as the <code>integralMode</code> datum, which decides whether the Tool House material section
offers a separate shank material, and shows no label of its own</li>
</ul>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../index.html">HiNC App Anatomy</a> for git repository links.</p>
<ul>
<li><code>wwwroot-src/src/i18n/en/toolhouse.ts</code> and <code>wwwroot-src/src/i18n/zh-Hant/toolhouse.ts</code> — the Tool
House control-tree node names and editor labels: Cutter, Holder, Flute Profile, Flute Contours,
Upper Beam, and the cutter and holder types.</li>
<li><code>wwwroot-src/src/i18n/en/mech.ts</code> and <code>wwwroot-src/src/i18n/zh-Hant/mech.ts</code> — the General Setup
rendering flags and the anchor labels.</li>
<li><code>wwwroot-src/src/i18n/en/tree.ts</code> and <code>wwwroot-src/src/i18n/zh-Hant/tree.ts</code> — the shared
control-tree node names and the mission command kinds.</li>
<li><code>wwwroot-src/src/i18n/zh-Hant/mission.ts</code> — the PreSetting command fields, including Machining
Resolution and the meshed-geometry file picker.</li>
<li><code>wwwroot-src/src/i18n/zh-Hant/controller.ts</code> and <code>wwwroot-src/src/i18n/zh-Hant/softNc.ts</code> — the
Datum Preset tab, its table, and the runner's datum-preset row.</li>
<li><code>wwwroot-src/src/i18n/zh-Hant/execution.ts</code> — the Execution rendering flags, where Meshed
Geometry and Fixture appear together.</li>
<li><code>wwwroot-src/src/i18n/index.ts</code><code>createI18n()</code>, <code>applyLocale()</code> and <code>SUPPORTED_LOCALES</code>, the
only place a locale switch happens.</li>
<li><code>wwwroot-src/src/i18n/schema.ts</code> — the English bundle as the schema that types the Chinese ones.</li>
<li><code>wwwroot-src/src/i18n/glossary.yaml</code> — generated data, never imported by the application: the
harvested legacy corpus with an adjudicated head term per concept, used to settle a reading
before it is written into a bundle.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="../shell/language-selection-submenu.html">Language Selection SubMenu</a> — the sub-menu that switches the locale these terms are written for</li>
<li><a class="xref" href="../platform/i18n.html">Internationalization</a> — the bundle layout these terms are written into, and the lint that holds them to it</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -2,16 +2,16 @@
<html>
<head>
<meta charset="utf-8">
<title>Webapi with hub-cleapup assistence pattern | HiAPI-C# 2025 </title>
<title>Webapi with Hub-Cleanup Assistance Pattern | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Webapi with hub-cleapup assistence pattern | HiAPI-C# 2025 ">
<meta name="title" content="Webapi with Hub-Cleanup Assistance Pattern | HiAPI-C# 2025 ">
<link rel="icon" href="../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
@@ -84,12 +84,18 @@
<nav id="breadcrumb"></nav>
</div>
<article data-uid="">
<h1 id="webapi-with-hub-cleapup-assistence-pattern">Webapi with hub-cleapup assistence pattern</h1>
<article data-uid="Anatomy-WebapiHubCleanupPattern">
<h1 id="webapi-with-hub-cleanup-assistance-pattern">Webapi with Hub-Cleanup Assistance Pattern</h1>
<p>any of the index key should be registerForCleanup. i.e. any of indexXxx should follow the registerForCleanup.</p>
<p>clean the indexed key which indexed by the host component in beforeUnmount. And so that the component doesn't clean the key that doesn't create by the component itself.</p>
<p>Although cleanupHub clean them for sure, the code demonstrates example of pure web-api cleanup (So that it can be a complete web-api workflow).</p>
<p>The hub is not a second, redundant path that would make this web-api cleanup a mere demonstration.
<code>Common/CleanupHub.cs</code> keeps its key registry in an ordinary instance property, and SignalR builds a
fresh hub instance for every invocation, so the entry <code>Add</code> records is discarded with the instance
that received it and <code>OnDisconnectedAsync</code> always walks an empty registry. The index-remove calls
<code>wwwroot-src/src/composables/useCleanupHub.ts</code> makes — on unmount and on every key replacement — are
the only ones that actually free a key, which is why registering a key without releasing it there
leaks it for the life of the process.</p>
<h2 id="notice">Notice</h2>
<ol>
<li>before current key modified, the previous key should be called this.cleanupKey.</li>
@@ -11,7 +11,7 @@
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
@@ -84,94 +84,100 @@
<nav id="breadcrumb"></nav>
</div>
<article data-uid="">
<article data-uid="Anatomy-CycleLineCharts">
<h1 id="cycle-line-charts">Cycle-Line Charts</h1>
<p>The cycle-line charts visualise a <strong>per-step, per-cycle detail view</strong> of force / moment signals around the currently-selected step. They are re-fetched whenever <code>SelectedStepInfoHub</code> pushes a new step, so the operator can zoom into one spindle revolution (or the configured cycle window) as they scrub through the mission timeline on the strip charts.</p>
<p>The anatomy covers four cycle-line charts in the webservice, all sharing <code>BaseCycleLineChart.vue</code>:</p>
<p>The cycle-line charts visualise a <strong>per-step, per-cycle detail view</strong> of force / moment signals around the currently-selected step. They are re-fetched whenever the step selection changes, so the operator can zoom into one spindle revolution (or the configured cycle window) while scrubbing through the mission timeline on the strip charts.</p>
<p>They sit in the Step Info column of the <a href="index.html">Execution Page</a>, below Step Properties and CWE. The anatomy covers four, all sharing <code>BaseCycleLineChart.vue</code>, in the order the column stacks them:</p>
<table>
<thead>
<tr>
<th>Chart</th>
<th>Source Data</th>
<th>Cycle parameter</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
<tr>
<td>Force Cycle-Line Chart</td>
<td>Sim Cutting Force Cycle</td>
<td><code>MachiningStep.ForceToWorkpieceOnProgramCoordinate</code> or <code>ForceToToolOnToolRunningCoordinate</code></td>
<td>spindle angle (deg)</td>
<td>Header dropdown picks between the two flag values; swaps the <code>fetcher</code> prop so <code>BaseCycleLineChart</code> refetches.</td>
</tr>
<tr>
<td>Sim Spindle Moment Cycle-Line Chart</td>
<td>Sim Spindle Moment Cycle</td>
<td><code>MachiningStep.MomentsToToolAboutObservationPointOnSpindleRotationCoordinate_Nm</code></td>
<td>Simulated moment from the physics model.</td>
<td>spindle angle (deg)</td>
<td>Simulated moment from the physics model. Carries the locus (dartboard) mode.</td>
</tr>
<tr>
<td>Sensor Spindle Moment Cycle-Line Chart</td>
<td><code>IMomentShot</code> via <code>MachiningProject.TimeMapping.GetShots(stepIndex)</code></td>
<td>Sensor-measured moment. Rendered as a sibling card to the sim chart, not a twin overlay (see <em>Deferred</em>).</td>
</tr>
<tr>
<td>Dynamometer Force Cycle-Line Chart</td>
<td>Sensor Cutting Force Cycle</td>
<td><code>IForceShot</code> via <code>TimeMapping.GetShots(stepIndex)</code></td>
<td>Sensor-measured force. Returns empty shape when no dynamometer data.</td>
<td>time (s)</td>
<td>Dynamometer-measured force. Returns an empty shape when there is no dynamometer data.</td>
</tr>
<tr>
<td>Sensor Spindle Moment Cycle</td>
<td><code>IMomentShot</code> via <code>MachiningProject.TimeMapping.GetShots(stepIndex)</code></td>
<td>time (s)</td>
<td>Sensor-measured moment. Rendered as a sibling card to the sim chart rather than a twin overlay, which keeps the two uplot instances independent. Carries the locus (dartboard) mode.</td>
</tr>
</tbody>
</table>
<p>The parameter split is the reason the two pairs never share a cursor: a simulated cycle is indexed by phase, a measured one by clock.</p>
<p>Key Model: <a class="xref" href="../../api/Hi.MachiningProcs.MachiningProject.html">MachiningProject</a>.<a class="xref" href="../../api/Hi.MachiningProcs.MachiningProject.html#Hi_MachiningProcs_MachiningProject_TimeMapping">TimeMapping</a> + the selected <a class="xref" href="../../api/Hi.MachiningSteps.MachiningStep.html">MachiningStep</a>.</p>
<h2 id="layout">Layout</h2>
<p>Each cycle-line chart is a card:</p>
<p>Each cycle-line chart is a collapsible panel:</p>
<ul>
<li>Header Row
<ul>
<li>Title label.</li>
<li>Flag picker (Force chart only) — <code>&lt;q-btn-dropdown&gt;</code> between <code>ForceToWorkpieceOnProgramCoordinate</code> (default) and <code>ForceToToolOnToolRunningCoordinate</code>.</li>
<li>Flag picker (Sim Cutting Force chart only) — a <code>&lt;q-btn-dropdown&gt;</code> between <code>ForceToWorkpieceOnProgramCoordinate</code> (default) and <code>ForceToToolOnToolRunningCoordinate</code>.</li>
<li>Mode picker (<code>enableLocus</code> charts only — the two spindle-moment charts) — <code>Line</code> / <code>Dartboard</code>.</li>
<li>Value-boundary dropdown — <code>Auto</code>, or <code>Fixed</code> with a ± bound that pins the y range symmetrically so two steps can be compared without the axis moving. Its label reads the bound when one is set.</li>
<li>Reload.</li>
</ul>
</li>
<li>Body — <code>UplotChart.vue</code> rendering three series (X / Y / Z channels) over a shared time axis <code>ts</code>.</li>
<li>Empty-state overlay — when <code>hasData=false</code> (no selection, no shots), a placeholder is shown; the underlying series still carry a shape-preserving <code>ts=[0,360]</code>, <code>xs/ys/zs=[NaN,NaN]</code> payload so the canvas does not jump.</li>
<li>Body — <code>UplotChart.vue</code> rendering three series (X / Y / Z channels) over the cycle parameter <code>ts</code>, or <code>XyLocusChart.vue</code> in dartboard mode, which plots the same samples as a locus in the plane. The line-mode legend width is shared by every line-mode cycle chart, as the strip charts share theirs; the locus view has no legend divider and is unaffected.</li>
<li>Empty-state overlay — <code>No step selected</code> with nothing picked, <code>Not ready</code> before the payload arrives, <code>No data for step</code> when the step carries none. The underlying series still carry a shape-preserving <code>ts=[0,360]</code>, <code>xs/ys/zs=[NaN,NaN]</code> payload so the canvas does not jump. The two sensor charts read <code>No data for step</code> for every step of a project with no <code>TimeMapping</code> shot data, which is the normal state of a project that has not been measured.</li>
</ul>
<h2 id="behavior">Behavior</h2>
<ul>
<li><strong>Step-driven refetch.</strong> <code>BaseCycleLineChart</code> accepts a <code>fetcher: () =&gt; Promise&lt;CycleLineResponse&gt;</code> prop. On <code>SelectedStepInfoHub.StepChanged</code> the fetcher is re-invoked and the chart re-renders.</li>
<li><strong>Server-resolved step.</strong> Cycle-line endpoints resolve &ldquo;currently-selected step&rdquo; from <code>LocalProjectService.ClStrip.GetSelectedPos()</code> rather than accepting <code>stepIndex</code> as a query parameter. This keeps the contract simple and aligns with the event-driven model used by the rest of the player plumbing.</li>
<li><strong>Toolbar slot.</strong> The optional <code>#toolbar</code> slot lets callers embed per-chart controls (e.g. the Force chart's flag picker) without subclassing.</li>
<li><strong>Step-driven refetch.</strong> <code>BaseCycleLineChart</code> accepts a <code>fetcher: () =&gt; Promise&lt;CycleLineResponse&gt;</code> prop. The step-selection push arrives on <code>/clStripHub</code> as <code>StepSelected</code>; the fetcher is re-invoked and the chart re-renders.</li>
<li><strong>Server-resolved step.</strong> The cycle-line endpoints resolve &ldquo;currently-selected step&rdquo; from <code>LocalProjectService.ClStrip.GetSelectedPos()</code> rather than accepting <code>stepIndex</code> as a query parameter. This keeps the contract simple and matches the event-driven model used by the rest of the execution plumbing.</li>
<li><strong>Toolbar slot.</strong> The optional <code>#toolbar</code> slot lets callers embed per-chart controls (such as the force chart's flag picker) without subclassing.</li>
<li><strong>Shared cursor mark, in two groups.</strong> Clicking a point publishes that sample's cycle <em>parameter</em> — not its index — to a module-scoped mark shared by the group: <code>sim</code> for the two simulated charts (spindle angle), <code>sensor</code> for the two measured ones (seconds). Each chart draws the mark as a vertical line in line mode and a highlighted point in dartboard mode, and the mark survives both a mode switch and a change of selected step. The split is deliberate: a click on a simulated chart must not move a sensor-side cursor, because the two axes are not the same quantity.</li>
<li><strong>The <code>sim</code> mark reaches outside the chart family.</strong> The CWE panel of the <a href="index.html">Execution Page</a>'s Step Info column watches it and pushes the angle to its own displayee, so the flute overlay in the engagement view rotates to the phase clicked on a force or moment chart. The angle persists on that displayee across steps — the server re-applies it with each step's spin direction — so it is pushed only on (re)connect and on a new click.</li>
</ul>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a href="../index.html">HiNC App Anatomy</a> for git repository links.</p>
<h3 id="wpf-application-source-code-path">WPF Application Source Code Path</h3>
<ul>
<li>Not implemented.</li>
</ul>
<h3 id="web-page-application-source-code-path">Web Page Application Source Code Path</h3>
<p>See <a class="xref" href="../index.html">HiNC App Anatomy</a> for git repository links.</p>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/player/charts/BaseCycleLineChart.vue</code> — shared cycle-line skeleton.</li>
<li><code>wwwroot-src/src/components/player/charts/UplotChart.vue</code> — uplot wrapper (shared with strip charts).</li>
<li><code>wwwroot-src/src/components/player/charts/ForceCycleLineChart.vue</code> — force cycle-line chart with flag picker.</li>
<li><code>wwwroot-src/src/components/player/charts/SimSpindleMomentCycleLineChart.vue</code> — simulated spindle moment.</li>
<li><code>wwwroot-src/src/components/player/charts/SensorSpindleMomentCycleLineChart.vue</code> — sensor-measured spindle moment.</li>
<li><code>wwwroot-src/src/components/player/charts/DynamometerForceCycleLineChart.vue</code>dynamometer force.</li>
<li><code>Players/PlayerChartsController.cs</code> — cycle-line endpoints:
<li><code>wwwroot-src/src/components/execution/charts/BaseCycleLineChart.vue</code> — shared cycle-line skeleton.</li>
<li><code>wwwroot-src/src/components/execution/charts/UplotChart.vue</code> — uplot wrapper (shared with the strip charts).</li>
<li><code>wwwroot-src/src/components/execution/charts/XyLocusChart.vue</code> — the dartboard (locus) renderer used in place of the uplot chart in that mode.</li>
<li><code>wwwroot-src/src/composables/useCycleSyncMark.ts</code> — the two module-scoped cursor marks and their group keys.</li>
<li><code>wwwroot-src/src/components/execution/StepVolumePanel.vue</code> — the CWE panel that follows the <code>sim</code> mark to rotate its flute overlay.</li>
<li><code>wwwroot-src/src/components/execution/charts/ForceCycleLineChart.vue</code>force cycle-line chart with the flag picker.</li>
<li><code>wwwroot-src/src/components/execution/charts/SimSpindleMomentCycleLineChart.vue</code> — simulated spindle moment.</li>
<li><code>wwwroot-src/src/components/execution/charts/SensorSpindleMomentCycleLineChart.vue</code> — sensor-measured spindle moment.</li>
<li><code>wwwroot-src/src/components/execution/charts/DynamometerForceCycleLineChart.vue</code> — dynamometer force.</li>
<li><code>Execution/ExecutionChartsController.cs</code> — cycle-line endpoints:
<ul>
<li><code>GET /api/player/cycle-line/force?flag=ForceToWorkpieceOnProgramCoordinate|ForceToToolOnToolRunningCoordinate</code></li>
<li><code>GET /api/player/cycle-line/sim-spindle-moment</code></li>
<li><code>GET /api/player/cycle-line/sensor-spindle-moment</code></li>
<li><code>GET /api/player/cycle-line/dynamometer-force</code></li>
<li><code>GET /api/execution/cycle-line/force?flag=ForceToWorkpieceOnProgramCoordinate|ForceToToolOnToolRunningCoordinate</code></li>
<li><code>GET /api/execution/cycle-line/sim-spindle-moment</code></li>
<li><code>GET /api/execution/cycle-line/sensor-spindle-moment</code></li>
<li><code>GET /api/execution/cycle-line/dynamometer-force</code></li>
</ul>
</li>
</ul>
<p>All four endpoints return <code>{ hasData, ts, xs, ys, zs }</code> flattened for direct uplot consumption. Empty payloads use a shape-preserving <code>ts=[0,360]</code>, <code>xs/ys/zs=[NaN,NaN]</code> fill.</p>
<h2 id="deferred">Deferred</h2>
<h2 id="see-also">See Also</h2>
<ul>
<li><strong>True twin overlay</strong> (two series on one canvas) for Sim + Sensor Spindle Moment. Currently rendered as two sibling cards sharing the same chart grid, which keeps the uplot instances independent. Revisit if operators ask for overlap.</li>
</ul>
<h2 id="related-pages">Related Pages</h2>
<ul>
<li><a href="strip-charts.html">Strip Charts</a> — windowed mission-timeline charts that share the same <code>uplot</code> engine and drive step selection.</li>
<li><a href="index.html">Player Panel</a> — top-level layout that hosts the charts.</li>
<li><a href="index.html">Execution Page</a> — the page whose Step Info column hosts these charts</li>
<li><a href="strip-charts.html">Strip Charts</a> — windowed mission-timeline charts that share the same <code>uplot</code> engine and drive step selection</li>
<li><a href="../../manual/run/inspecting-a-step.html">Inspecting a Step</a> — the task these charts serve, with the two scales and the shared mark read as a procedure</li>
</ul>
</article>
@@ -0,0 +1,170 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Execution Extended RenderingCanvas Tool Bar | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Execution Extended RenderingCanvas Tool Bar | HiAPI-C# 2025 ">
<link rel="icon" href="../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../index.html">
<img id="logo" class="svg" src="../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-ExecutionExtendedToolBar">
<h1 id="execution-extended-renderingcanvas-tool-bar">Execution Extended RenderingCanvas Tool Bar</h1>
<p>The run-specific half of the canvas header on the <a href="index.html">Execution Page</a>: what the scene shows, and how the tool path is drawn. The generic view controls live next to it on the <a class="xref" href="../widget/renderingcanvas-tool-bar.html">RenderingCanvas Tool Bar</a>.</p>
<p>The model of the tool bar is <a class="xref" href="../../api/Hi.Disp.DispEngine.html">DispEngine</a>, assigned from the RenderingCanvas of the <a href="index.html">Execution Page</a>. The content of <a class="xref" href="../../api/Hi.Disp.DispEngine.html#Hi_Disp_DispEngine_Displayee">Displayee</a> here is the project displayee, whose key content is <a class="xref" href="../../api/Hi.MachiningProcs.MachiningProject.html">MachiningProject</a>.</p>
<h2 id="layout">Layout</h2>
<ul>
<li><code>Execution Extended RenderingCanvas Tool Bar</code>
<ul>
<li><p><code>Tool Path</code> — shows or hides the CL strip.</p>
</li>
<li><p><code>Path Points</code> — the CL-strip dot markers. Editable only while <a class="xref" href="../../api/Hi.MachiningProcs.RenderingFlag.html#Hi_MachiningProcs_RenderingFlag_ClStrip">ClStrip</a> is set in the displayee's rendering-flag bit array.</p>
</li>
<li><p><code>Scene ▾</code><a class="xref" href="../../api/Hi.MachiningProcs.RenderingFlag.html">RenderingFlag</a>-based checkboxes over that same bit array, in three groups:</p>
<ul>
<li><strong>Solid</strong> — Machine, Tool, Workpiece, Fixture</li>
<li><strong>Coordinate</strong> — Program Zero, ISO Coordinate, Heidenhain Coordinate</li>
<li><strong>Display Aids</strong> — Dimension Bar, Color Scale Bar</li>
</ul>
<p>The ClStrip flag is not offered here; the <code>Tool Path</code> button owns it. Heidenhain Coordinate appears only when <a class="xref" href="../../api/Hi.MachiningProcs.MachiningProject.html">MachiningProject</a>.<a class="xref" href="../../api/Hi.MachiningProcs.MachiningProject.html#Hi_MachiningProcs_MachiningProject_NcEnv">NcEnv</a>.<a class="xref" href="../../api/Hi.Numerical.HardNcEnv.html#Hi_Numerical_HardNcEnv_CncBrand">CncBrand</a> is <a class="xref" href="../../api/Hi.Numerical.CncBrand.html#Hi_Numerical_CncBrand_Heidenhain">Heidenhain</a>. The menu is a shared component because other screens use it too — see the <a class="xref" href="../legacy-controller.html">Legacy Controller Page</a>.</p>
</li>
<li><p><code>Meshed Geom ▾</code> — the workpiece's rendering cache and geometry-diff settings: the <a class="xref" href="graphic-cache-menu.html">Graphic-Cache SubMenu</a> and Diff Visual Radius, with a <code>Diff</code> badge when a difference is present.</p>
</li>
</ul>
</li>
</ul>
<p><strong>Fit View is not on this tool bar.</strong> It moved to the strip-chart group bar, which is where the whole-program time axis it fits against lives; see <a href="strip-charts.html">Strip Charts</a>.</p>
<h2 id="shipped-default-flags">Shipped Default Flags</h2>
<p>The displayee opens with four bits set — <code>DimensionBar</code>, <code>WorkpieceGeom</code>, <code>Fixture</code> and <code>ClStrip</code> — so a page that has just loaded draws the workpiece, the fixture, the dimension bar and the tool path, and draws neither the machine nor the tool until the operator ticks them. <code>Mech</code> is present in the initializer as a commented-out line rather than absent, so the default is a decision rather than an omission: a machine at its home view fills the frame and hides the cut. The flags live on the displayee, not in the project, so they are session state and every new rendering session starts from this set.</p>
<div class="TIP">
<h5>Tip</h5>
<p>The checkboxes in the <code>Scene ▾</code> menu are grouped by the category of <a class="xref" href="../../api/Hi.MachiningProcs.RenderingFlag.html">RenderingFlag</a> — follow the link for the categories.</p>
</div>
<h2 id="behavior-of-the-tool-path-buttons">Behavior of the Tool-Path Buttons</h2>
<p>Apply the displayee's rendering-flag bit array to set the project rendering items.</p>
<div class="TIP">
<h5>Tip</h5>
<p>Extract the <a class="xref" href="../../api/Hi.MachiningProcs.MachiningProject.html">MachiningProject</a> from the project displayee and use it to drive the behaviors.</p>
</div>
<h2 id="behavior-of-the-scene-menu">Behavior of the <code>Scene</code> Menu</h2>
<p>See the <code>DemoRenderingMachiningProcessAndStripPosSelection</code> sample in the <a class="xref" href="../../technique/api-foundations/packages-and-samples.html">Hi.Sample.Wpf repository</a> for code that completes the behavior of the buttons.</p>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../index.html">HiNC App Anatomy</a> for git repository links.</p>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/execution/ExecutionExtendedToolBar.vue</code> — the tool bar itself.</li>
<li><code>wwwroot-src/src/components/widgets/DisplayOptionsMenu.vue</code> — the shared <code>Scene ▾</code> menu.</li>
<li><code>wwwroot-src/src/components/preference/GraphicCacheMenu.vue</code> — the Graphic Cache entry under <code>Meshed Geom ▾</code>.</li>
<li><code>Execution/ExecutionController.cs</code><code>GET /api/Execution/cl-strip-dots</code> and <code>POST /api/Execution/update-cl-strip-dots</code>.</li>
<li><code>Common/RenderingFlagsController.cs</code> — the rendering-flag reads and writes behind the <code>Scene ▾</code> checkboxes.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a href="index.html">Execution Page</a> — the page whose canvas this tool bar decorates</li>
<li><a class="xref" href="../widget/renderingcanvas-tool-bar.html">RenderingCanvas Tool Bar</a> — the generic view controls beside it</li>
<li><a href="strip-charts.html">Strip Charts</a> — where Fit View now lives</li>
<li><a href="../../manual/run/watching-the-run.html">Watching the Run</a> — the task these controls serve, and what the canvas draws by default</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,199 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Execution Tool Bar | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Execution Tool Bar | HiAPI-C# 2025 ">
<link rel="icon" href="../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../index.html">
<img id="logo" class="svg" src="../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-ExecutionToolBar">
<h1 id="execution-tool-bar">Execution Tool Bar</h1>
<p>The transport controls for the run. In the web application they sit atop the <a href="index.html">Execution Page</a>'s primary editor panel, shown for the Execution root and every node under it, and this sole instance registers the F5F8 shortcuts.</p>
<h2 id="layout">Layout</h2>
<ul>
<li><code>Execution Tool Bar</code>
<ul>
<li><code>Status Text Field</code></li>
<li><code>Start Button</code> (F5; <strong>Resume</strong> once the run is paused)</li>
<li><code>Pause Button</code> (F6)</li>
<li><code>Run-One-Line Button</code> (F7 — one NC line)</li>
<li><code>Run-One-Step Button</code> (F8 — one machining step)</li>
<li><code>Stop Button</code></li>
<li><code>Reset Button</code></li>
</ul>
</li>
</ul>
<p>In the web application the status text moved out of the tool bar and onto the Execution tree item, which carries the run-state badge. The two single-advance buttons there share one <code>skip_next</code> icon and are separated by a letter drawn into the button's corner — <code>L</code> for the line button, <code>S</code> for the step button — rather than by colour; the tool-tips carry the key names.</p>
<h2 id="behavior">Behavior</h2>
<ul>
<li>Both the webservice and the win-desktop application watch <a class="xref" href="../../api/Hi.MachiningProcs.LocalProjectService.html">LocalProjectService</a> events to track <a class="xref" href="../../api/Hi.MachiningProcs.LocalProjectService.html#Hi_MachiningProcs_LocalProjectService_PacePlayer">PacePlayer</a> status changes.</li>
<li>In the webservice, <code>ExecutionStatusService</code> subscribes to those events and broadcasts status changes over SignalR through <code>ExecutionStatusHub</code>.</li>
<li>The win-desktop application subscribes to the same <a class="xref" href="../../api/Hi.MachiningProcs.LocalProjectService.html">LocalProjectService</a> events directly.</li>
<li>The frequently used buttons carry hotkeys: Start / Resume, Pause, Run One Line and Run One Step. The app's tool-tips are the only place those key bindings are written down.</li>
<li>The background color of the <code>Status Text Field</code> follows the status:
<ul>
<li>Warning style — Running</li>
<li>Secondary style — Paused, No Project</li>
<li>Success style — Finished, Ready</li>
</ul>
</li>
</ul>
<p>The action of <code>Reset Button</code> is async, so the UI stays responsive while the session unwinds; the button shows a busy state for the duration, and the flag behind it is shared with the keyboard path so the two agree.</p>
<h2 id="enable-rules">Enable Rules</h2>
<p>Every button is disabled unless a project is loaded <strong>and</strong> the page has finished initializing its rendering connection — the web transport reads that readiness from the Execution page, so a dropped canvas connection greys the whole bar with a project still open. On top of that gate each button follows the status:</p>
<table>
<thead>
<tr>
<th>Button</th>
<th>Enabled for status</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>Start</code> / <code>Resume</code></td>
<td><code>Ready</code>, <code>Paused</code></td>
</tr>
<tr>
<td><code>Pause</code></td>
<td><code>Running</code></td>
</tr>
<tr>
<td><code>Run-One-Line</code>, <code>Run-One-Step</code></td>
<td><code>Ready</code>, <code>Paused</code></td>
</tr>
<tr>
<td><code>Stop</code></td>
<td><code>Running</code>, <code>Paused</code>, <code>Finished</code></td>
</tr>
<tr>
<td><code>Reset</code></td>
<td>any, while no reset is in flight</td>
</tr>
</tbody>
</table>
<p><strong>Neither single-advance button is enabled while the run is <code>Running</code></strong> — stepping is a <code>Ready</code>-or-<code>Paused</code> operation, so a moving run has to be paused before it can be stepped.</p>
<p>The F5F8 handler applies the same predicates before acting, and declines in two more cases: when the event target is an <code>input</code>, a <code>textarea</code> or a contenteditable element, and when the hosting page is deactivated. The second matters because the shell keep-alives the routed pages, so leaving the Execution route deactivates rather than unmounts this component; without the detach, F5 elsewhere in the app would drive the transport instead of reloading the browser. Every declined key falls through to the browser.</p>
<div class="TIP">
<h5>Tip</h5>
<p>Use icons rather than text on the tool-bar buttons. Run One Line and Run One Step share an icon, so they need a second mark to tell them apart — the win-desktop application seasons the icon green and blue, the web application draws an <code>L</code> and an <code>S</code> in the button corner. Either works; the default colour is enough for the rest.</p>
</div>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../index.html">HiNC App Anatomy</a> for git repository links.</p>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/execution/ExecutionToolBar.vue</code> — the buttons. All state and handlers come from the shared <code>useExecutionTransport</code> composable, so the component is pure markup.</li>
<li><code>Execution/ExecutionController.cs</code><code>POST /api/Execution/start | pause | resume | run-line | run-step | stop | reset</code>, and <code>GET /api/Execution/status</code>.</li>
<li><code>Execution/ExecutionStatusHub.cs</code> + <code>Execution/ExecutionStatusService.cs</code> — the status broadcast.</li>
</ul>
<h4 id="signalr-implementation-web-service-only">SignalR Implementation (Web Service Only)</h4>
<p><code>ExecutionStatusHub</code> is mapped at <code>/executionStatusHub</code> and answers <code>GetExecutionStatus()</code>. <code>ExecutionStatusService</code> watches the <a class="xref" href="../../api/Hi.MachiningProcs.LocalProjectService.html#Hi_MachiningProcs_LocalProjectService_PacePlayer">PacePlayer</a> events — <a class="xref" href="../../api/Hi.Common.PacePlayer.html#Hi_Common_PacePlayer_IsRunningChangedEvent">IsRunningChangedEvent</a>, <a class="xref" href="../../api/Hi.Common.PacePlayer.html#Hi_Common_PacePlayer_IsLockedChangedEvent">IsLockedChangedEvent</a>, <a class="xref" href="../../api/Hi.Common.PacePlayer.html#Hi_Common_PacePlayer_IsFinishedChangedEvent">IsFinishedChangedEvent</a> and <a class="xref" href="../../api/Hi.Common.PacePlayer.html#Hi_Common_PacePlayer_ResetedEvent">ResetedEvent</a> — and broadcasts each change, so every connected client sees the same run state without polling.</p>
<h2 id="see-also">See Also</h2>
<ul>
<li><a href="index.html">Execution Page</a> — the page these controls drive</li>
<li><a href="../../manual/run/running-a-simulation.html">Starting and Stepping</a> — the task these controls serve, with the enable rules read as a procedure</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,155 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Graphic-Cache SubMenu | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Graphic-Cache SubMenu | HiAPI-C# 2025 ">
<link rel="icon" href="../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../index.html">
<img id="logo" class="svg" src="../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-GraphicCacheMenu">
<h1 id="graphic-cache-submenu">Graphic-Cache SubMenu</h1>
<h2 id="shipped-surface">Shipped Surface</h2>
<p>The panel has no route and no Control-Tree node of its own. It is chrome on the Execution page (<code>/execution</code>): the canvas panel's expansion header carries the <code>Meshed Geom ▾</code> dropdown — titled <em>Workpiece rendering cache and geometry-diff settings</em> — whose <code>Graphic Cache</code> row opens this panel in a nested menu. It hangs there because it acts on the workpiece meshed geometry's rendering cache, and its sibling row in the same dropdown is <code>Diff Visual Radius</code>. It is therefore only reachable while the Execution page's canvas column is shown.</p>
<p>In the WPF application the same submenu lives on the <a class="xref" href="../shell/preference-menu.html">Preference Menu Dropdown</a>.</p>
<p>The panel is self-contained: it takes no parent-supplied model. It holds local lower / upper / current values, loads them on mount, and commits each edit to <code>GET</code>/<code>POST /api/preference/graphic-cache</code>. <code>UserService</code> / <code>UserConfig</code> is the server-side model behind that endpoint.</p>
<h2 id="layout">Layout</h2>
<p>Titled <strong>Graphic Cache (MB)</strong>, with the caption &ldquo;Memory budget for rendering cache. Lower &amp; upper bound the slider.&rdquo;</p>
<ul>
<li>Graphic-Cache SubMenu
<ul>
<li><code>Lower</code> numeric field (unit MB)</li>
<li><code>Upper</code> numeric field (unit MB)</li>
<li><code>Current</code> numeric field (unit MB)</li>
<li>Slider</li>
</ul>
</li>
</ul>
<h2 id="behavior">Behavior</h2>
<ul>
<li>There is no apply step and no Save button: each field posts its own value the moment it is committed — on blur, or on Enter — and the whole panel is loaded once when it is mounted.</li>
<li>The <code>Current</code> field is bounded by the two limit fields, so a value below <code>Lower</code> or above <code>Upper</code> is refused with an inline message and never sent. The server clamps a submitted value into the limits regardless, and the panel re-applies the effective state that comes back, so the server's answer is always what ends up on screen.</li>
<li>The slider is integer-valued and reflects <code>Current</code> in MB. Its range tracks <code>Lower</code> / <code>Upper</code>, clamped defensively so the minimum never exceeds the maximum. Releasing the slider commits the value.</li>
<li>A failed write shows the message inline under the fields, raises a notification, and re-reads from the server so the panel never drifts from stored state.</li>
<li>A write lands on the live user configuration, so it takes effect at once — <code>GraphicCacheMb</code> is a pass-through onto <code>CubeTree.DispCacheMb</code>. All three values are part of the user-config XML, so they reach disk with the next save of that config.</li>
</ul>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../index.html">HiNC App Anatomy</a> for git repository links.</p>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/preference/GraphicCacheMenu.vue</code> — the panel itself: Lower / Upper / Current numeric fields plus the slider. Loads on mount, commits each edit to the REST endpoint, re-applies the server's clamped response, and reports failures inline plus a notification.</li>
<li><code>wwwroot-src/src/components/execution/ExecutionExtendedToolBar.vue</code> — where the entry hangs: the <code>Meshed Geom ▾</code> dropdown on the Execution page canvas panel's expansion header, whose <code>Graphic Cache</code> row opens this panel in a nested menu.</li>
<li><code>wwwroot-src/src/components/workpiece/WorkpieceDiffRadiusMenu.vue</code> — the sibling <code>Diff Visual Radius</code> row in the same dropdown.</li>
<li><code>wwwroot-src/src/components/widgets/NumericInput.vue</code> — the shared numeric field used for all three values.</li>
<li><code>wwwroot-src/src/api/preference.ts</code><code>getGraphicCache</code> / <code>setGraphicCache</code> over <code>GET</code>/<code>POST /api/preference/graphic-cache</code>, typed as <code>{ lowerLimit, upperLimit, value }</code>.</li>
<li><code>Environments/PreferenceController.cs</code><code>GetGraphicCacheSettings</code> returns <code>{ success, lowerLimit, upperLimit, value }</code>; <code>UpdateGraphicCacheSettings</code> takes the same three as nullable fields, clamps <code>value</code> into the limits, writes the live <code>UserConfig</code>, and returns the effective state.</li>
<li><code>Environments/UserConfig.cs</code><code>GraphicCacheLowerLimitMb</code> (default 10) and <code>GraphicCacheUpperLimitMb</code> (default 1200) are plain stored values; <code>GraphicCacheMb</code> is a pass-through whose getter and setter are <code>CubeTree.DispCacheMb</code>. All three round-trip through the config XML.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="../shell/preference-menu.html">Preference Menu Dropdown</a> — the dropdown that hosts this entry in the WPF client</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,254 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Execution Page | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Execution Page | HiAPI-C# 2025 ">
<link rel="icon" href="../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../index.html">
<img id="logo" class="svg" src="../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-Execution">
<h1 id="execution-page">Execution Page</h1>
<p>The Execution page is the run cockpit: the simulation plays here, and every panel on it reads the run. It is the app's landing route — <code>/</code> redirects to <code>/execution</code> — and its Control Tree, the <strong>Execution</strong> root with <strong>Mission</strong> and <strong>Program</strong> beneath it, rides the <code>?tree=</code> query, so <code>/execution?tree=execution/mission</code> deep-links the Mission editor.</p>
<div class="NOTE">
<h5>Note</h5>
<p>The app says <strong>Execution</strong> throughout: the route <code>/execution</code>, the REST endpoints (<code>/api/Execution/*</code>), the status hub (<code>/executionStatusHub</code>) and the stored layout (<code>UserConfig.ExecutionDivConfig</code>). The HiAPI engine keeps its own library vocabulary — <a class="xref" href="../../api/Hi.MachiningProcs.MachiningProject.html#Hi_MachiningProcs_MachiningProject_PlayerCommand">PlayerCommand</a> <small>(API)</small> and <a class="xref" href="../../api/Hi.Common.PacePlayer.html">PacePlayer</a> <small>(API)</small> — which is a different codebase's naming, not an inconsistency here.</p>
</div>
<h2 id="key-models">Key Models</h2>
<ul>
<li><a class="xref" href="../../api/Hi.MachiningProcs.LocalProjectService.html">LocalProjectService</a> — the project data service the whole page reads.</li>
<li><a class="xref" href="../../api/Hi.MachiningProcs.MachiningProject.html">MachiningProject</a> — reached through the displayee bound to the canvas.</li>
<li><code>UserService</code> — per-user configuration, including the stored panel layout.</li>
</ul>
<h2 id="layout">Layout</h2>
<p>Four columns, left to right. The two dock dividers are pixel-sized, so dragging one moves only that dock's edge while the flexible middle absorbs the change.</p>
<ul>
<li><strong>Left dock</strong> — the Control Tree over the primary editor panel for the selected tree node. The <a href="execution-tool-bar.html">Execution Tool Bar</a> — the transport buttons — sits atop that panel and is shown for the Execution root and every node under it.</li>
<li><strong>Main</strong> — the <a class="xref" href="../../technique/rendering/rendering-canvas.html">RenderingCanvas</a> over the <a class="xref" href="../shell/session-message-panel.html">Session Message Panel</a>, stacked as collapsible panels with a draggable divider between them. The canvas panel's header adopts the <a class="xref" href="../widget/renderingcanvas-tool-bar.html">RenderingCanvas Tool Bar</a> and the <a href="execution-extended-renderingcanvas-tool-bar.html">Execution Extended RenderingCanvas Tool Bar</a>.</li>
<li><strong>Strip Charts</strong> — the strip-chart group bar over the three <a href="strip-charts.html">Strip Charts</a>: Availability Chart, Surface Roughness Chart and Color Index Time Chart.</li>
<li><strong>Step Info</strong><em>Sentence Syntax</em> as its own region (an NC sentence need not map to any machining step, so it is not a member of the step-based panels), then the Step Info group bar, then <a href="selected-step-info-panel.html">Step Properties</a>, <em>CWE</em> (cutterworkpiece engagement) and the four <a href="cycle-line-charts.html">Cycle-Line Charts</a>.</li>
</ul>
<p>The four columns are toggled from the app menu bar; the panels inside them collapse in place from their own expansion headers. The canvas stays mounted while collapsed so its rendering connection survives, and its engine is paused rather than torn down. The page itself also stays mounted while the user is on another route: the shell wraps the router view in a <code>keep-alive</code> keyed on the project epoch, so navigating away pauses the canvas but keeps the page — and its connection — alive until the loaded project changes.</p>
<h2 id="control-tree">Control Tree</h2>
<p>The page hosts an <code>execution</code>-scoped Control Tree: an <strong>Execution</strong> root with the <strong>Mission</strong> branch above the <strong>Program</strong> branch. Selecting a node fills the primary editor pane below the tree, and the <a href="execution-tool-bar.html">Execution Tool Bar</a> is mounted on top of that pane whenever the selected id is <code>execution</code> or starts with <code>execution/</code> — so the transport stays reachable from every node of this tree.</p>
<p><strong>Execution root</strong> is the cockpit's home: a short orientation panel, because the controls it would otherwise hold live where they act — the transport on the primary pane's header, the panel switches on each panel's own expansion header, the column switches on the nav bar. The live run state rides the tree itself: the Execution tree item carries a status badge fed by <code>/executionStatusHub</code>.</p>
<p><strong>Mission</strong> (<code>execution/mission</code>) is the editable command list a run executes — the project's <a class="xref" href="../../api/Hi.MachiningProcs.MachiningProject.html#Hi_MachiningProcs_MachiningProject_PlayerCommand">PlayerCommand</a> <small>(API)</small>, always a list, with nested lists read as sub-trees. Its ItemType registry supplies the tree checkbox that decides whether a command runs. The branch root is the entry-list editor — Add Command over a drag-reorderable row list with up / down / duplicate / delete — each command entry gets a control bar over that kind's own editor, and each of the kind's remaining setting groups becomes a section child of the command node. Every command type has its own page under <a href="mission/index.html">Mission</a>.</p>
<p><strong>Program</strong> (<code>execution/program</code>) is the read-only inspection twin of Mission: one node per NC source file the session read, its passes and execution marks filled in from run data rather than typed in. Its nodes, its per-pass line and mark views, and the status changes that rebuild it are on <a href="program.html">Program Branch</a>.</p>
<p>Selection and URL are synced two ways through the <code>tree</code> query argument: the URL's <code>?tree=</code> is adopted once the tree is built, and every selection change replaces it. A <code>?tree=</code> id belonging to another page's tree redirects to that page (<code>routeForTreeId</code>), and <code>/mission</code> lands on <code>/execution?tree=execution/mission</code>.</p>
<h2 id="renderingcanvas-behavior">RenderingCanvas Behavior</h2>
<p>The canvas is split across the two sides: the browser component owns a canvas element and its connection, the server owns the displayee.</p>
<ol>
<li>The browser component opens a SignalR connection to <code>/renderingHub</code> and streams frames. It holds no displayee of its own.</li>
<li><code>POST /api/Execution/initialize/{connectionId}</code> resolves that connection's engine on the server, binds <code>ProjectDisplayeeService.ExecutionDisplayee</code> to the engine's <a class="xref" href="../../api/Hi.Disp.DispEngine.html">DispEngine</a>.<a class="xref" href="../../api/Hi.Disp.DispEngine.html#Hi_Disp_DispEngine_Displayee">Displayee</a> unless one is already bound there, and snaps the camera to the home view through <a class="xref" href="../../api/Hi.Disp.DispEngine.html#Hi_Disp_DispEngine_SetViewToHomeView">SetViewToHomeView()</a>.</li>
<li>The displayee receives project data from <a class="xref" href="../../api/Hi.MachiningProcs.LocalProjectService.html">LocalProjectService</a>.</li>
<li>The connection is per rendering session, so a collapsed canvas keeps its connection and only stops drawing; the engine is paused while the panel is collapsed or the page is off-screen.</li>
</ol>
<p>Isometric is one of the view presets the user picks from the <a class="xref" href="../widget/renderingcanvas-tool-bar.html">RenderingCanvas Tool Bar</a> — alongside front, back, right, left, top and bottom — not the load-time view.</p>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../index.html">HiNC App Anatomy</a> for git repository links.</p>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<p>Page, routing and Control Tree:</p>
<ul>
<li><code>wwwroot-src/src/pages/ExecutionPage.vue</code> — the routed page at <code>/execution</code>: the four columns, their pixel / ratio splitters, the panel-expansion stacks, and the <code>execution</code>-scoped Control Tree host it provides.</li>
<li><code>wwwroot-src/src/router/routes.ts</code> — the <code>/</code><code>execution</code> redirect, the <code>execution</code> route, and the <code>/mission</code> redirect to <code>/execution?tree=execution/mission</code>.</li>
<li><code>wwwroot-src/src/router/treeRoutes.ts</code><code>routeForTreeId</code>, which lands a <code>?tree=</code> id on the page that owns that branch.</li>
<li><code>wwwroot-src/src/components/controlTree/useControlTreeHost.ts</code> — the Control Tree state machine: <code>buildExecutionRoot()</code> builds the <code>execution</code> root holding <code>execution/mission</code> above <code>execution/program</code>, syncs <code>?tree=</code>, and re-reads the Program branch whenever the broadcast execution status changes.</li>
<li><code>wwwroot-src/src/components/controlTree/ControlTreeDock.vue</code> — the left dock: the Control Tree row over the primary editor row.</li>
<li><code>wwwroot-src/src/components/controlTree/ControlTreePanel.vue</code> — the tree pane; carries the live execution-status badge on the Execution tree item.</li>
<li><code>wwwroot-src/src/components/controlTree/PrimarySlavePanel.vue</code> — the primary editor pane for the selected node; mounts the Execution Tool Bar on top whenever the selection is <code>execution</code> or a descendant.</li>
<li><code>wwwroot-src/src/components/controlTree/ExecutionRootPanel.vue</code> — the editor panel of the Execution root node.</li>
</ul>
<p>Mission branch:</p>
<ul>
<li><code>wwwroot-src/src/components/controlTree/missionItemTypes.ts</code> — the branch's ItemType registry and the per-command enable ticks.</li>
<li><code>wwwroot-src/src/components/controlTree/MissionRootPanel.vue</code> — the entry-list editor of a list command, serving the branch root and every nested list.</li>
<li><code>wwwroot-src/src/components/controlTree/MissionCommandSlavePanel.vue</code> — the per-command editor host.</li>
<li><code>wwwroot-src/src/components/controlTree/MissionSectionPanel.vue</code> — one setting section of a command.</li>
</ul>
<p>Program branch (see the dedicated anatomy page for details):</p>
<ul>
<li><a href="program.html">Program Branch</a> — the branch's ItemType registry, its root, file and conversion panels, and the read-only endpoints behind them.</li>
</ul>
<p>Panels and canvas:</p>
<ul>
<li><code>wwwroot-src/src/components/RenderingCanvas.vue</code> — the browser canvas and its <code>/renderingHub</code> connection.</li>
<li><code>wwwroot-src/src/components/execution/ExecutionToolBar.vue</code> — the transport controls.</li>
<li><code>wwwroot-src/src/components/execution/ExecutionExtendedToolBar.vue</code> — the view and rendering-flag controls adopted onto the canvas panel's header.</li>
<li><code>wwwroot-src/src/components/execution/SelectedStepInfoPanel.vue</code> — Step Properties.</li>
<li><code>wwwroot-src/src/components/execution/StepVolumePanel.vue</code> — the CWE panel.</li>
<li><code>wwwroot-src/src/components/execution/SyntaxPiecePanel.vue</code> — Sentence Syntax.</li>
<li><code>wwwroot-src/src/components/execution/SessionMessagePanel.vue</code> — the message panel and its four sink tabs.</li>
<li><code>wwwroot-src/src/components/panels/PanelExpansion.vue</code> and <code>wwwroot-src/src/components/panels/ResizablePanelStack.vue</code> — the collapse-in-place panels and their draggable heights.</li>
<li><code>wwwroot-src/src/layouts/MainLayout.vue</code> — the shell whose keep-alive wrapper, keyed on the project epoch, holds this page mounted across navigation.</li>
</ul>
<p>Charts (see the dedicated anatomy pages for details):</p>
<ul>
<li><a href="strip-charts.html">Strip Charts</a> — three <code>uplot</code>-backed strip charts driven by <code>ClStrip</code>.</li>
<li><a href="cycle-line-charts.html">Cycle-Line Charts</a> — four <code>uplot</code>-backed per-step charts.</li>
</ul>
<p>Shared chart primitives under <code>wwwroot-src/src/components/execution/charts/</code>:</p>
<ul>
<li><code>wwwroot-src/src/components/execution/charts/UplotChart.vue</code> — thin uplot wrapper with <code>ResizeObserver</code> and reactive <code>data</code> / <code>series</code> / <code>bands</code> bindings.</li>
<li><code>wwwroot-src/src/components/execution/charts/BaseStripChart.vue</code> — strip-chart skeleton: min / max banded series, header aspect picker, wheel / drag / click pointer handlers.</li>
<li><code>wwwroot-src/src/components/execution/charts/BaseCycleLineChart.vue</code> — cycle-line skeleton that takes a <code>fetcher</code> prop and re-fetches when the step selection changes.</li>
</ul>
<p>Backends:</p>
<ul>
<li><code>Execution/ExecutionController.cs</code><code>POST /api/Execution/start | pause | resume | run-line | run-step | stop | reset</code>; <code>GET status</code>, <code>status/{connectionId}</code>, <code>project-status</code>, <code>selected-step-info</code> and <code>cl-strip-dots</code>; the canvas actions <code>initialize/{connectionId}</code> (binds the displayee and calls <code>SetViewToHomeView</code>) and <code>fit-view/{connectionId}</code>; and the <code>step-volume/{connectionId}</code> family behind the CWE panel.</li>
<li><code>Execution/ExecutionStatusHub.cs</code> + <code>Execution/ExecutionStatusService.cs</code><code>/executionStatusHub</code>, the run-state broadcast that drives the transport and the tree badge.</li>
<li><code>Execution/SessionSinkHub.cs</code> + <code>Execution/SessionSinkBroadcastService.cs</code> — one hub per message sink (<code>/shellMessageHub</code>, <code>/ncDiagnosticHub</code>, <code>/stepDiagnosticHub</code>, <code>/ncManipulationDiagnosticHub</code>), so a client subscribes to exactly the sink it wants.</li>
<li><code>Execution/SelectedStepInfoService.cs</code> — the selected-step payload. It has no hub of its own: the &ldquo;selection changed&rdquo; push rides <code>/clStripHub</code>.</li>
<li><code>Execution/ClStripController.cs</code> + <code>Execution/ClStripHub.cs</code> + <code>Execution/ClStripBroadcastService.cs</code> — CL-strip range state and the zoom / pan / select / enter broadcasts.</li>
<li><code>Execution/ExecutionChartsController.cs</code> — the strip-chart and cycle-line-chart data endpoints under <code>/api/execution</code>.</li>
<li><code>Disp/RenderingHub.cs</code> + <code>Disp/RenderingService.cs</code> — the shared <code>/renderingHub</code> every page's RenderingCanvas connects to, keyed per rendering connection id.</li>
<li><code>Disp/ExecutionDisplayee.cs</code> — the server-side displayee bound to the connection's engine.</li>
<li><code>Common/ProjectDisplayeeService.cs</code> — owns the <code>ExecutionDisplayee</code> instance handed to the engine on initialize.</li>
</ul>
<h2 id="building-an-equivalent-page">Building an Equivalent Page</h2>
<div class="TIP">
<h5>Tip</h5>
<p>When building an execution cockpit on top of HiAPI:</p>
<ol>
<li>Create the layout with a RenderingCanvas.</li>
<li>Set up the canvas behavior.</li>
<li>Add the <a href="execution-tool-bar.html">Execution Tool Bar</a>.</li>
<li>Add the <a href="execution-extended-renderingcanvas-tool-bar.html">Execution Extended RenderingCanvas Tool Bar</a> with the CL-strip, fit-view and rendering-items behaviors.</li>
<li>Reach the page from the navigation menu on <a class="xref" href="../shell/main-panel.html">Main Panel</a>.</li>
<li>Make it the landing page, with its tool bars.</li>
<li>Build the <a class="xref" href="../shell/session-message-panel.html">Session Message Panel</a> and the <a href="selected-step-info-panel.html">Selected-Step Info Panel</a>, and put the button that opens the <a class="xref" href="step-present-dialog.html">Step Present Dialog</a> on the latter's title bar.</li>
</ol>
</div>
<h2 id="pages">Pages</h2>
<p>Ordered as the cockpit reads: the two tool bars across the top, the step column and the charts down
the side, the menus and dialogs they open, then the Control-Tree branch the page hosts.</p>
<ul>
<li><a href="execution-tool-bar.html">Execution Tool Bar</a> — The transport controls on the primary panel header, and the status they read</li>
<li><a href="execution-extended-renderingcanvas-tool-bar.html">Execution Extended RenderingCanvas Tool Bar</a> — The run-specific canvas controls: the CL strip, fit view and the Scene menu</li>
<li><a href="selected-step-info-panel.html">Selected-Step Info Panel</a> — The Step Properties panel in the Step Info column</li>
<li><a href="strip-charts.html">Strip Charts</a> — The whole-program charts in the Strip Charts column</li>
<li><a href="cycle-line-charts.html">Cycle-Line Charts</a> — The per-step charts in the Step Info column</li>
<li><a href="graphic-cache-menu.html">Graphic-Cache Menu</a> — The meshed-geometry cache limits, edited from the canvas header</li>
<li><a href="step-present-dialog.html">Step Present Dialog</a> — The step-property presentation settings, opened from the Step Properties panel</li>
<li><a href="mission/index.html">Mission</a> — The command-list branch this page hosts, one page per command type</li>
<li><a href="program.html">Program Branch</a> — The read-only inspection twin of Mission: one node per NC source file the run read, its passes and its execution marks</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a href="execution-tool-bar.html">Execution Tool Bar</a> — the transport buttons and their status feed</li>
<li><a href="execution-extended-renderingcanvas-tool-bar.html">Execution Extended RenderingCanvas Tool Bar</a> — CL strip, fit view and the rendering-items menu</li>
<li><a href="selected-step-info-panel.html">Selected-Step Info Panel</a> — the Step Properties panel in the Step Info column</li>
<li><a href="strip-charts.html">Strip Charts</a> — the whole-program charts in the Strip Charts column</li>
<li><a href="cycle-line-charts.html">Cycle-Line Charts</a> — the per-step charts in the Step Info column</li>
<li><a class="xref" href="../shell/session-message-panel.html">Session Message Panel</a> — the run log under the canvas</li>
<li><a class="xref" href="../widget/renderingcanvas-tool-bar.html">RenderingCanvas Tool Bar</a> — the view controls the canvas header adopts</li>
<li><a class="xref" href="../shell/main-panel.html">Main Panel</a> — the shell that routes to this page</li>
<li><a href="mission/index.html">Mission</a> — the command-list branch this page hosts, one page per command type</li>
<li><a class="xref" href="../general-setup/index.html">General Setup Page</a> — the other Control-Tree page, and the equipment this run consumes</li>
<li><a href="program.html">Program Branch</a> — the read-only inspection twin of Mission this page hosts below it</li>
<li><a class="xref" href="../platform/control-tree.html">Control Tree</a> — the engine behind this page's tree, shared with General Setup</li>
<li><a class="xref" href="../platform/tree-ids-and-routes.html">Tree Ids and Routes</a> — the <code>?tree=</code> surface this page's selection rides on</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,322 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>List Command Panel | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="List Command Panel | HiAPI-C# 2025 ">
<link rel="icon" href="../../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../../public/docfx.min.css">
<link rel="stylesheet" href="../../../public/main.css">
<meta name="docfx:navrel" content="../../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../../index.html">
<img id="logo" class="svg" src="../../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-ListCommandPanel">
<h1 id="list-command-panel">List Command Panel</h1>
<p>A <a class="xref" href="../../../api/Hi.SessionCommands.ListCommand.html">ListCommand</a> is the container command: an optional title plus a list of
enable-wrapped commands. Running one walks
<a class="xref" href="../../../api/Hi.SessionCommands.ListCommand.html#Hi_SessionCommands_ListCommand_CommandEntryList">CommandEntryList</a> top-down and skips a disabled entry together
with everything nested under it — the wrapper yields nothing while its flag is clear. The mission's
own command is always a list, so the <a class="xref" href="mission-root-panel.html">Mission branch root</a> <em>is</em> a
list, and every nested one is the same shape one level down.</p>
<p><code>List</code> is an ordinary catalog kind — category Flow — so a list is added the way any command is, and
adding one grows a sub-tree: the node's children are its own entries, and its panel embeds the same
entry-list editor the branch root uses, scoped to the nested list.</p>
<p>This page covers what a list is, how its title reads, and what moving commands in and out of one
costs. The entry-list editor itself — Add Command, the row actions, the three drag landings — is
documented once, on <a class="xref" href="mission-root-panel.html">Mission Root Panel</a>.</p>
<h2 id="key-models">Key Models</h2>
<ul>
<li><strong>Primary</strong>: <a class="xref" href="../../../api/Hi.SessionCommands.ListCommand.html">ListCommand</a><a class="xref" href="../../../api/Hi.SessionCommands.ListCommand.html#Hi_SessionCommands_ListCommand_Title">Title</a>
and <a class="xref" href="../../../api/Hi.SessionCommands.ListCommand.html#Hi_SessionCommands_ListCommand_CommandEntryList">CommandEntryList</a>.</li>
<li><strong>Supporting</strong>:
<ul>
<li><a class="xref" href="../../../api/Hi.SessionCommands.EnablingWrapper.html">EnablingWrapper</a> — one entry:
<a class="xref" href="../../../api/Hi.SessionCommands.EnablingWrapper.html#Hi_SessionCommands_EnablingWrapper_Command">Command</a> plus
<a class="xref" href="../../../api/Hi.SessionCommands.EnablingWrapper.html#Hi_SessionCommands_EnablingWrapper_IsEnabled">IsEnabled</a>.</li>
<li><a class="xref" href="../../../api/Hi.SessionCommands.ITitleCommand.html">ITitleCommand</a><code>ListCommand</code> implements it, and
<a class="xref" href="../../../api/Hi.SessionCommands.ITitleCommand.html#Hi_SessionCommands_ITitleCommand_GetCommandTitle_">GetCommandTitle</a> composes the label a row shows.</li>
<li><a class="xref" href="../../../api/Hi.MachiningProcs.MachiningProject.html#Hi_MachiningProcs_MachiningProject_PlayerCommand">PlayerCommand</a> — the mission's command, a list.</li>
<li><a class="xref" href="../../../api/Hi.SessionCommands.CommandCatalogAttribute.html">CommandCatalogAttribute</a> and <a class="xref" href="../../../api/Hi.SessionCommands.CommandCategory.html">CommandCategory</a>
— what makes <code>List</code> addable, and the group it is offered under.</li>
</ul>
</li>
</ul>
<h2 id="the-panel-of-a-list-entry">The Panel of a List Entry</h2>
<p>Selecting a list command in the Control Tree opens, top to bottom:</p>
<ul>
<li>List Entry Panel
<ul>
<li>Control Bar
<ul>
<li>Up / Down / Duplicate / Delete — labelled buttons for the operations that rewrite the
<strong>parent</strong> list. Delete asks for confirmation in a dialog naming the command.</li>
<li>At the bar's left, a caption while the command is disabled: skipped during play, still
editable.</li>
</ul>
</li>
<li>Title (optional) Input</li>
<li>Embedded Entry-List Editor
<ul>
<li>This list's own entries, scoped to the node's path.</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>The Title input holds the <strong>raw</strong> title — empty when unset — while the label the row and the tree
show is composed by the engine: <code>List</code> when the title is blank, <code>List [title]</code> when it is not. A
titled list still says what it is. A whitespace-only title counts as unset and is never written into
the project file, so it cannot come back as a changed title on the next load.</p>
<p>Keystrokes debounce into one title save. The pending save is flushed before anything that shifts the
entry paths — a structural operation, a duplicate, a selection change — and cancelled on delete and
on unmount, so a late write cannot land on whichever command slid into the old path. Once it is
saved the tree label refreshes in place rather than by rebuilding the branch, which would remount
the open editor mid-edit.</p>
<p>The root list is the one list without a title input: the branch root reads <code>Mission</code>.</p>
<h2 id="enabling-and-what-a-disabled-list-skips">Enabling, and What a Disabled List Skips</h2>
<p>The enable switch is the checkbox on the command's own Control Tree item, not a control in the entry
row. It decides only whether the command <strong>runs</strong>: while it is clear the row and the tree node dim,
and the editor stays open and fully editable. Unticking a list dims its whole sub-tree — the dim
rides ancestor propagation in the tree while each nested entry keeps its own flag, which is exactly
what the run does: a disabled entry is skipped with everything under it.</p>
<h2 id="paths">Paths</h2>
<p>A command node's key is its Mission API path — <code>&quot;0&quot;</code>, <code>&quot;1&quot;</code>, nested <code>&quot;0.2&quot;</code> for entry 2 inside the
list at index 0. Every structural change re-mints them: deleting entry 2 slides the next command
into path <code>&quot;2&quot;</code>. Each branch build therefore stamps its nodes, so the editor panel remounts onto the
new path instead of going on showing the command that used to live there.</p>
<h2 id="moving-commands-in-and-out">Moving Commands In and Out</h2>
<ul>
<li><strong>Into a list</strong> — drag a row onto the middle band of a list row; that row's outer quarters still
reorder around it.</li>
<li><strong>Out of a list</strong> — drag onto the drop-out zone of a nested list's editor, which is visible only
while a row inside that editor is being dragged. The entry lands right after the list command
itself in the owning list.</li>
</ul>
<p>Both are the same reparent call. The server resolves the source list and the target list to object
references before it mutates either, so the index shift the removal causes cannot misroute the
insert, and it refuses to move a list into itself or into one of its own descendants. That guard
compares identity rather than path-string prefixes, because <code>&quot;02&quot;</code>, <code>&quot;+2&quot;</code> and <code>&quot; 2&quot;</code> all parse to
index 2.</p>
<p>Two more properties of a list follow from the same API:</p>
<ul>
<li><strong>A new command lands at the end</strong> of the list it was added to, root or nested. Nothing is pinned;
the order is entirely the user's, changed with the row's up / down buttons or by dragging.</li>
<li><strong>Duplicate is a deep copy.</strong> The entry is cloned through the same XML round-trip the project file
uses, so a nested list copies with its whole sub-tree, and the clone lands right after the source.</li>
</ul>
<p>Dragging serves rearrangement only: a drag carrying text or files is ignored, because the row's drag
handling returns as soon as the drag did not start on a row. To bring several program files in at
once, open a <a class="xref" href="NcFileCommand-panel.html">Program File</a> command's picker and pick them
together — the first pick lands on that command and each further pick becomes another Program File
command right after it, in pick order, inside the same list at any depth.</p>
<h2 id="which-commands-a-list-can-hold">Which Commands a List Can Hold</h2>
<p>The addable set is the server's command catalog: every engine command carrying <code>[CommandCatalog]</code>,
reflected into the picker Add Command opens, which searches by display label, kind key and alias.
<code>List</code> is one of those commands, which is what makes nesting an ordinary act rather than a special
case. The frontend contributes an icon per kind, and its own label where it has one; the server
supplies the localized label otherwise, so a newly attributed engine command becomes addable with no
frontend change.</p>
<p>A command without that attribute stays loadable from a project file but is never offered for
creation. General Config is the one such command with an editor of its own: it is the legacy
settings bundle, so a project file storing one loads as the individual setting commands it stands
for and saving never writes the bundle back — editable when a project still constructs one through
the API, and absent from the catalog.</p>
<p>Selecting an entry row selects that command's tree node, and the command's editor opens on that
node — the kind's own panel when one exists, otherwise a generic editor built from the scalar fields
the command declares. Selection is one row at a time: there is no multi-selection here, and every
action acts on the row that carries it. Because the server composes every entry label, a command
carrying no title of its own still reads as its localized display name.</p>
<p>There is no second column inside this editor: the entry list is a single column in the tree's editor
pane. The draggable vertical divider on screen belongs to the Execution page, between the left dock
— the Control Tree over the editor panel — and the central area; a second divider inside that dock
drags the height between the tree and the editor.</p>
<h2 id="the-wpf-clients-list-panel">The WPF Client's List Panel</h2>
<p>The WPF panel edits the <strong>root</strong> list only. It binds the project's player command as a list, its Add
menu offers no List item, and its content switch has no List arm, so a nested list entry selected
there renders the literal text &ldquo;No editor available for ListCommand&rdquo;. Nesting is a web surface.</p>
<ul>
<li>Two columns: a 400px entry column, a <code>GridSplitter</code>, and the selected command's content column,
both columns floored at 300px.</li>
<li>One toolbar over the list — Add, Remove, Move Up, Move Down — acting on the list selection. There
is no duplicate.</li>
<li>Add is a plain button that pops a fixed six-item context menu built in its click handler:
&ldquo;Pre-Setting Command&rdquo;, &ldquo;NC Opt Option Command&rdquo;, &ldquo;NC File Command&rdquo;, &ldquo;NC Code Command&rdquo;,
&ldquo;Script Command&rdquo; and &ldquo;Post Execution Command&rdquo;.</li>
<li>The list box is extended-select. Remove takes every selected entry, prompting with a count when
more than one is selected, and a drag can carry a whole selection; Move Up and Move Down stay
disabled unless exactly one entry is selected.</li>
<li>Each entry box carries a labelled Enable checkbox at its left, the command's title in bold, and a
pin icon with a &ldquo;Pin at beginning&rdquo; / &ldquo;Pin at end&rdquo; label — shown on the first entry when it is a
General Config command, and on the last when it is a Post-Execution command.</li>
<li>Insertion works around the ends: a new entry lands before a trailing Post-Execution when that is
the list's only one, and a General Config lands at the top unless the list already starts with
one. A drag that would move a pinned entry — or drop another entry onto it — is refused, the guard
testing that the entry is the only command of its kind and sits at that end. The Move Up / Move
Down buttons are not covered by that guard.</li>
<li>The content panel is greyed and made read-only while the selected entry's Enable box is clear.</li>
<li>Files dropped from the file explorer onto the entry list create one enabled NC File command per
file, taking a project-relative path when the file sits under the project directory.</li>
<li>The entry label falls back to the raw class name for a command that composes no title of its
own — <code>GeomDiffCommand</code>, <code>OptimizeToFilesCommand</code>, <code>WriteStepFilesCommand</code> and
<code>WriteShotFilesCommand</code>.</li>
</ul>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../../index.html">HiNC App Anatomy</a> for git repository links.</p>
<h3 id="web-application">Web Application</h3>
<ul>
<li><code>wwwroot-src/src/components/controlTree/MissionCommandSlavePanel.vue</code> — a list entry's panel: the
control bar, the optional Title input with its debounce / flush / cancel discipline, the embedded
entry-list editor of a list entry, and the bespoke-or-generic editor choice for every other kind.</li>
<li><code>wwwroot-src/src/components/controlTree/MissionRootPanel.vue</code> — the entry-list editor embedded
here and serving the branch root: the rows and their actions, the three drag landings, and the
drop-out zone shown only during a drag inside a nested editor.</li>
<li><code>wwwroot-src/src/components/controlTree/missionItemTypes.ts</code> — the child builders that recurse
into a list's own entries, the per-kind editor override map, the kind and category icons, and the
per-build stamp that forces the panel remount.</li>
<li><code>wwwroot-src/src/components/controlTree/useControlTreeHost.ts</code> — which nodes show a checkbox, the
dim that flows down a disabled command's sub-tree, the enable write, and the in-place label
refresh after a title edit or a language switch.</li>
<li><code>wwwroot-src/src/api/mission.ts</code> — the typed wrapper over the Mission API and the path convention
(<code>root</code> or empty = the root list, <code>0.2</code> = a nested entry).</li>
<li><code>wwwroot-src/src/components/mission/GenericCommandPanel.vue</code> — the editor a catalog kind gets when
it ships no bespoke panel, built from the command's declared scalar fields.</li>
<li><code>wwwroot-src/src/components/mission/NcFileCommandPanel.vue</code> — the multi-pick file dialog: the
first pick lands on the command, each further pick becomes another Program File command right
after it.</li>
<li><code>wwwroot-src/src/pages/ExecutionPage.vue</code> — the page-level splitter between the left dock and the
central area.</li>
<li><code>wwwroot-src/src/i18n/en/tree.ts</code> — the <code>tree.mission.*</code> strings: the kind names, the drop-out
zone, the run-order hint, the disabled hint and the operation labels.</li>
<li><code>Missions/MissionController.cs</code> — the entry lifecycle: <code>GET list-command/entries</code> (recursive),
<code>POST list-command/entries</code> and <code>POST list-command/entries/{path}</code> (append at the tail of the root
or of a nested list), <code>DELETE list-command/entries/{path}</code>,
<code>POST list-command/entries/{path}/move</code>, <code>PUT list-command/reorder</code>,
<code>POST list-command/entries/{path}/duplicate</code> (the XML-round-trip deep clone),
<code>POST list-command/entries/{path}/reparent</code> (drag-into-list and drop-out, with the self/descendant
guard), <code>PUT commands/{path}/listcommand</code> for the title, and <code>PUT commands/{path}/enabled</code> for an
entry's enable flag. <code>GetCommandTitle</code> composes every entry label the tree shows.</li>
<li><code>Missions/MissionCommandCatalog.cs</code> — reflects the <code>[CommandCatalog]</code> commands into the addable
set and constructs the picked kind.</li>
</ul>
<h3 id="hiapi-engine">HiAPI Engine</h3>
<ul>
<li><code>HiNc/SessionCommands/ListCommand.cs</code> — the container: the optional title, the entry list a run
walks top-down, the whitespace-only-title-is-unset XML rule, the Flow catalog attribute, and the
<code>List</code> / <code>List [title]</code> composition.</li>
<li><code>HiNc/SessionCommands/EnablingWrapper.cs</code> — the enable flag plus the wrapped command that together
make one entry.</li>
<li><code>HiNc/SessionCommands/ITitleCommand.cs</code> — the interface a command implements to compose its own
title from the caller's vocabulary.</li>
<li><code>HiNc/SessionCommands/CommandCatalogAttribute.cs</code><code>CommandCategory</code> and the attribute (category,
order, kind key, aliases) that makes the addable set backend-owned.</li>
<li><code>HiNc/SessionCommands/PreSettingCommand.cs</code> — the General Config bundle: no catalog attribute, and
a loaded project expands a stored one into the split setting commands.</li>
<li><code>HiNc/MachiningProcs/MachiningProject.cs</code> — declares the mission's command and keeps it a list.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="mission-root-panel.html">Mission Root Panel</a> — the entry-list editor this command embeds, documented once</li>
<li><a href="../../../manual/run/building-a-mission.html">Building a Mission</a> — the task this command serves: grouping a mission and moving entries between groups</li>
<li><a href="index.html">Mission</a> — the rest of the command panels</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,212 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>NcCodeCommand Panel | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="NcCodeCommand Panel | HiAPI-C# 2025 ">
<link rel="icon" href="../../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../../public/docfx.min.css">
<link rel="stylesheet" href="../../../public/main.css">
<meta name="docfx:navrel" content="../../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../../index.html">
<img id="logo" class="svg" src="../../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-NcCodeCommandPanel">
<h1 id="nccodecommand-panel">NcCodeCommand Panel</h1>
<p>The key model is <a class="xref" href="../../../api/Hi.SessionCommands.NcCodeCommand.html">NcCodeCommand</a>.</p>
<p>The command carries two values: <a class="xref" href="../../../api/Hi.SessionCommands.NcCodeCommand.html#Hi_SessionCommands_NcCodeCommand_NcText">NcText</a>, the NC program
itself, and <a class="xref" href="../../../api/Hi.SessionCommands.NcCodeCommand.html#Hi_SessionCommands_NcCodeCommand_Title">Title</a>, the name that program runs under. Both
are written into the project file — the command emits a title element and an NC-text element, and
the project nests the whole command list into its own XML — so the program travels with the project
instead of existing as a file beside it. That is the whole difference from
<a class="xref" href="NcFileCommand-panel.html">Program File</a>, which stores a path and leaves the program on disk.
A mission can therefore carry a short program without producing an artifact of its own.</p>
<p>Title is not decoration. <code>Run</code> passes it to <a class="xref" href="../../../api/Hi.MachiningProcs.SessionShell.html#Hi_MachiningProcs_SessionShell_RunNc_">RunNc</a> as the
program name the run log shows, which is why the model defaults it to the command's own name,
<code>NC Code</code>, rather than to an empty string. It is also the detail the mission row brackets: the row
reads <code>NC Code</code> while the title is empty or still that default, and <code>NC Code [title]</code> once it says
something else.</p>
<p>The panel renders on the command's own node in the Mission branch of the Execution page's Control
Tree, below the move / duplicate / delete control bar. A single-purpose kind like this one embeds
its whole editor on that node rather than growing section child nodes.</p>
<h2 id="layout">Layout</h2>
<ul>
<li>NcCodeCommand Panel
<ul>
<li>Title Field
<ul>
<li>The model is <a class="xref" href="../../../api/Hi.SessionCommands.NcCodeCommand.html#Hi_SessionCommands_NcCodeCommand_Title">Title</a>.</li>
<li>Labelled &quot;Title (optional)&quot;. A newly added command arrives with the model's default name,
<code>NC Code</code>, already in the field; renaming it renames the program in the run log and in the
mission row.</li>
</ul>
</li>
<li>NC Code Editor Area
<ul>
<li>The model is <a class="xref" href="../../../api/Hi.SessionCommands.NcCodeCommand.html#Hi_SessionCommands_NcCodeCommand_NcText">NcText</a>.</li>
<li>A plain monospace text area: on the web an 18-row field with a 360 px floor, on WPF a Consolas
text box in an NC Code group box that takes the panel's remaining height.</li>
<li>No line-number gutter and no syntax highlighting on either client. The rich editor in this
folder is the <a class="xref" href="script-command-panel.html">Script command</a>'s — that command ships
CodeMirror with a C# grammar, this one ships a text area.</li>
</ul>
</li>
<li>Stats and Actions Row (web)
<ul>
<li>Line count and character count, both recomputed as the text changes.</li>
<li>Trim Blank Lines Button — trims every line and drops the ones left empty, then saves at once.</li>
<li>Clear Button — confirms in a dialog before emptying the NC text.</li>
<li>Both buttons stay disabled while the code is empty.</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="saving">Saving</h2>
<p>Typing in either field schedules one save 400 ms after the last keystroke, so a burst of typing
costs one request instead of one per character. Trim Blank Lines and Clear save immediately rather
than through that timer.</p>
<p>The write is a single patch endpoint carrying the NC text and the title. Each field is applied only
when the body actually carries it, which is why the client sends the field it changed and a null for
the one it did not.</p>
<p>NC text is not validated while it is edited: there is no parse at edit time and no error surface on
the panel — the server stores whatever string arrives. Errors surface when the mission plays the
text.</p>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../../index.html">HiNC App Anatomy</a> for git repository links.</p>
<h3 id="web-application">Web Application</h3>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/mission/NcCodeCommandPanel.vue</code> — this panel: the Title input, the
18-row monospace text area, the 400 ms debounced saves, the live line and character stats, Trim
Blank Lines, and Clear behind a confirm dialog.</li>
<li><code>wwwroot-src/src/components/controlTree/missionItemTypes.ts</code> — maps the <code>nccode</code> kind to this
panel, and states the rule it belongs to: a single-purpose kind embeds its whole editor on the
command node.</li>
<li><code>wwwroot-src/src/api/mission.ts</code><code>loadNcCode</code> and <code>setNcCode</code>, and the command shape the panel
edits (NC code plus title).</li>
<li><code>wwwroot-src/src/i18n/en/mission.ts</code> — the panel's wording: the NC Code label, the character
count, Trim Blank Lines, and the line count it shares with the other panels.</li>
<li><code>wwwroot-src/src/i18n/en/dialog.ts</code> — the Clear confirmation's title and message.</li>
<li><code>Missions/MissionController.cs</code> — the single patch PUT for this command, whose NC-code and title
fields are each applied when the body carries them, and the command-snapshot arm that returns both
back to the panel.</li>
</ul>
<h3 id="hiapi-engine">HiAPI Engine</h3>
<ul>
<li><code>HiNc/SessionCommands/NcCodeCommand.cs</code> — the model: <code>NcText</code>, <code>Title</code> defaulting to the command's
own display name, the Program-category catalog registration, the XML round-trip that puts the NC
text into the project file, <code>Run</code> handing text and title to the session shell, and the row-label
rule that shows the bare name while the title is unset or still the default.</li>
<li><code>HiNc/MachiningProcs/SessionShell.cs</code><code>RunNc</code> takes the NC text plus an alternative file name,
and that name is the title this command supplies for the log.</li>
<li><code>HiNc/MachiningProcs/MachiningProject.cs</code> — nests the command list into the project XML, which is
how this command's NC text persists with the project.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="NcFileCommand-panel.html">NcFileCommand Panel</a> — the other Program command: a path to a file instead of text stored in the project</li>
<li><a class="xref" href="script-command-panel.html">Script Command Panel</a> — the rich editor this folder does have, and why this command ships a plain text area instead</li>
<li><a href="../../../manual/run/playing-a-program.html">Playing a Program</a> — the task these two commands serve</li>
<li><a href="index.html">Mission</a> — the rest of the command panels</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,273 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>NcFileCommand Panel (Program File) | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="NcFileCommand Panel (Program File) | HiAPI-C# 2025 ">
<link rel="icon" href="../../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../../public/docfx.min.css">
<link rel="stylesheet" href="../../../public/main.css">
<meta name="docfx:navrel" content="../../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../../index.html">
<img id="logo" class="svg" src="../../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-NcFileCommandPanel">
<h1 id="ncfilecommand-panel-program-file">NcFileCommand Panel (Program File)</h1>
<p>The key model is <a class="xref" href="../../../api/Hi.SessionCommands.NcFileCommand.html">NcFileCommand</a>.</p>
<p>The command carries exactly two values: <a class="xref" href="../../../api/Hi.SessionCommands.NcFileCommand.html#Hi_SessionCommands_NcFileCommand_NcFile">NcFile</a>, the path of
the program to play, and <a class="xref" href="../../../api/Hi.SessionCommands.NcFileCommand.html#Hi_SessionCommands_NcFileCommand_NcKind">NcKind</a>, the runner that plays it.
The program text stays a file on disk — the project stores the path and nothing else, which is what
separates this command from <a class="xref" href="NcCodeCommand-panel.html">NC Code</a>, whose text is serialized
into the project itself.</p>
<p><code>NcFile</code> is normally a path relative to the project folder — that is what the browse dialog yields —
and an absolute path on the server is accepted just as well. No base directory is stored with the
command: at play time <a class="xref" href="../../../api/Hi.MachiningProcs.SessionShell.html#Hi_MachiningProcs_SessionShell_RunNcFile_">RunNcFile</a> hands the stored path
together with the project's own base directory to the local project service, and that is where a
relative path acquires its root. The WPF panel's <code>BaseDirectory</code> is the code-behind assistant
property the <a class="xref" href="../../conventions/gui-file-path-assignment.html">Load Pattern</a> asks a file-assigning GUI to carry,
assigned to the panel by its parent — a panel member, not a model member.</p>
<p>The panel renders on the command's own node in the Mission branch of the Execution page's Control
Tree, below the move / duplicate / delete control bar. This command has no title of its own, so the
row and tree label read <code>Program File [path]</code>, the path being the detail the label brackets.</p>
<div class="NOTE">
<h5>Note</h5>
<p>The GUI labels this command &ldquo;Program File&rdquo;: it plays NC, CL (CLSF) and CSV files — the runner is
picked by <a class="xref" href="../../../api/Hi.SessionCommands.NcFileCommand.html#Hi_SessionCommands_NcFileCommand_NcKind">NcKind</a>, and
<a class="xref" href="../../../api/Hi.MachiningProcs.NcKind.html#Hi_MachiningProcs_NcKind_Auto">Auto</a> reads it off the extension
(<a class="xref" href="../../../api/Hi.MachiningProcs.NcKindUtil.html#Hi_MachiningProcs_NcKindUtil_DetectByPath_">DetectByPath</a>: <code>.cl</code>, <code>.cls</code> and <code>.clsf</code> play as CL, <code>.csv</code>
as CSV, and every other extension as brand NC).</p>
</div>
<h2 id="layout">Layout</h2>
<ul>
<li>NcFileCommand Panel
<ul>
<li>Head Line
<ul>
<li>Program File Path Field
<ul>
<li>The model is <a class="xref" href="../../../api/Hi.SessionCommands.NcFileCommand.html#Hi_SessionCommands_NcFileCommand_NcFile">NcFile</a>.</li>
<li>Its hint names both accepted forms: an absolute path on the server, or a path relative to
the project folder.</li>
<li>Every keystroke saves the path. Leaving the field or pressing Enter additionally refreshes
the file-info banner.</li>
<li>The web field flexes to fill the row beside the Browse button at any panel width; the WPF
field is a fixed 200 px column carrying its own label.</li>
</ul>
</li>
<li>Browse Button
<ul>
<li>Opens the shared file-explorer dialog — see <a href="#browsing-for-a-program">Browsing for a Program</a>.</li>
</ul>
</li>
</ul>
</li>
<li>Play As Select
<ul>
<li>The model is <a class="xref" href="../../../api/Hi.SessionCommands.NcFileCommand.html#Hi_SessionCommands_NcFileCommand_NcKind">NcKind</a>.</li>
<li>Four choices: Auto (by extension), Brand NC, CL (CLSF) and CSV. The hint restates what Auto
detects, so the runner can be pinned when the extension would route the file elsewhere.</li>
<li>This control is the web client's. The WPF panel ships no kind control, so a project edited
there keeps the kind it already holds — Auto until something else sets it.</li>
</ul>
</li>
<li>File Info Banner
<ul>
<li>Appears once the path field is non-empty. Every fact in it is read on the server.</li>
<li>Found: a green banner stating the file size, its modified stamp and its line count, with a
Preview button in the banner's action slot.</li>
<li>Missing: an orange banner saying the file is not found on the server, and no Preview button.</li>
</ul>
</li>
<li>Preview Dialog
<ul>
<li>Read-only. The server returns the file's first 100 lines and the dialog shows them in a
monospace block that scrolls within 60% of the viewport height, inside a card capped at 80%.
An empty file previews as &quot;(empty)&quot;.</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="browsing-for-a-program">Browsing for a Program</h2>
<p>Browse opens the shared server-side file explorer dialog. File reads happen on the server; the
browser never uploads file bytes.</p>
<p>The <a class="xref" href="../../conventions/gui-file-path-assignment.html">Load Pattern</a>'s general web convention offers the Admin,
Project and Resource roots. This picker narrows to one: it opens on the project directory and allows
no other root, so a pick always yields a project-relative path. A file outside the project is
reached by typing its absolute path into the path field, which the server accepts.</p>
<p>The dialog carries four filter groups rather than one catch-all:</p>
<ul>
<li><strong>NC Files</strong><code>.nc</code>, <code>.anc</code>, <code>.tap</code>, <code>.eia</code>, <code>.mpf</code>, <code>.spf</code>, <code>.cnc</code>, <code>.ptp</code>, <code>.h</code>. The open set:
brand controller extensions, of which these are the common ones rather than all of them.</li>
<li><strong>CL Files</strong><code>.cl</code>, <code>.cls</code>, <code>.clsf</code>.</li>
<li><strong>CSV Files</strong><code>.csv</code>.</li>
<li><strong>All Files</strong> — an empty extension list, which is the empty filter the Load Pattern asks every
browser to preserve. It is the backstop for a brand extension the NC group does not name.</li>
</ul>
<p>CL and CSV are split out because they are closed extension sets: those are exactly the extensions
Auto routes to the CL and CSV runners, and everything else falls through to brand NC.</p>
<p>The WPF Browse button opens an <code>OpenFileDialog</code> with the panel's file-filter resource, starting at
the project directory or at the current file's own directory. A pick under the project directory is
stored as a relative path, and a pick outside it as an absolute one.</p>
<h2 id="multi-pick-fan-out">Multi-Pick Fan-Out</h2>
<p>The dialog is multi-select, and this is the one command editor that grows the mission. The first
picked file lands on this command. Each further pick becomes a <strong>new</strong> Program File command, added
to the list this command sits in and moved into place directly after it, in pick order — the same
list, so a command inside a nested list fans out inside that nested list. The panel then reports a
structure change, which rebuilds the owning list's branch around the new rows and leaves the
selection on this command.</p>
<h2 id="where-the-program-text-is-edited">Where the Program Text Is Edited</h2>
<p>The two clients part company here, and the command is the same either way: it stores a path.</p>
<ul>
<li><strong>Web</strong> — the panel does not edit the file. It assigns the path, picks the runner, reports what
the server knows about the file, and previews its first 100 lines read-only. Nothing in the
browser writes NC bytes back; the two file endpoints this panel calls are both reads.</li>
<li><strong>WPF</strong> — the panel opens the file. A non-text file, or one the panel cannot find, disables the
editor and states why in the Head Message Place above it. A text file within 20000 lines opens for
editing, with its line count in the group-box header; a longer one shows its first 20000 lines
read-only and warns in the Head Message Place. Two seconds after the last keystroke the editor
writes the file back, and reports the save in that same message place. The editor is AvalonEdit
with line numbers where that assembly resolves, and a plain Consolas text box otherwise.</li>
</ul>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../../index.html">HiNC App Anatomy</a> for git repository links.</p>
<h3 id="web-application">Web Application</h3>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/mission/NcFileCommandPanel.vue</code> — this panel: the path field and
Browse, the &ldquo;Play As&rdquo; kind select, the file-info banner and its Preview button, the read-only
preview dialog, the four filter groups, and the multi-pick fan-out.</li>
<li><code>wwwroot-src/src/components/widgets/FileExplorerDialog.vue</code> — the shared server-side browser
Browse opens: the pickable / multi / filters / initial-root / allowed-roots props, and the pick
event whose emitted strings pair a root name with a relative path — that relative half is what the
command stores.</li>
<li><code>wwwroot-src/src/components/widgets/fileFilter.ts</code> — the filter shape the four groups are built
from; an empty extension list means all files.</li>
<li><code>wwwroot-src/src/components/controlTree/missionItemTypes.ts</code> — maps the <code>ncfile</code> kind to this
panel. A kind absent from that map is served by the generic field editor instead.</li>
<li><code>wwwroot-src/src/api/mission.ts</code><code>loadNcFile</code>, <code>setNcFilePath</code>, <code>setNcFileNcKind</code>,
<code>getNcFileInfo</code> and <code>previewNcFile</code>, plus the kind union and the file-info shape.</li>
<li><code>wwwroot-src/src/i18n/en/mission.ts</code> — the panel's wording: the path hint, the Play As hint, the
four filter labels, the banner's found and not-found lines, and the preview titles.</li>
<li><code>Missions/MissionController.cs</code> — four endpoints serve this command: a PUT for the path, a PUT for
the NC kind, and the two server-side reads behind the banner and the preview (both POSTs, both
resolving a relative path against the project folder). The command-snapshot builder emits the file
path and the NC kind for this kind of command.</li>
</ul>
<h3 id="hiapi-engine">HiAPI Engine</h3>
<ul>
<li><code>HiNc/SessionCommands/NcFileCommand.cs</code> — the model: <code>NcFile</code>, <code>NcKind</code> defaulting to Auto, the
&ldquo;Program File&rdquo; display name and its Program-category catalog registration, the XML round-trip that
writes the path verbatim, the <code>Program File [path]</code> row label, and <code>Run</code> handing path and kind to
the session shell.</li>
<li><code>HiMech/MachiningProcs/NcKind.cs</code> — the kind enum (Auto / BrandNc / Cl / Csv) and the
detect-by-path helper, the authority for what Auto does.</li>
<li><code>HiNc/MachiningProcs/SessionShell.cs</code><code>RunNcFile</code> passes the stored path together with the
project's base directory to the local project service.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="NcCodeCommand-panel.html">NcCodeCommand Panel</a> — the other Program command: NC text stored in the project instead of a path to a file</li>
<li><a href="../../../manual/run/playing-a-program.html">Playing a Program</a> — the task these two commands serve</li>
<li><a href="index.html">Mission</a> — the rest of the command panels</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,411 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>NC Optimization Option Panel (NC Optimization Config) | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="NC Optimization Option Panel (NC Optimization Config) | HiAPI-C# 2025 ">
<link rel="icon" href="../../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../../public/docfx.min.css">
<link rel="stylesheet" href="../../../public/main.css">
<meta name="docfx:navrel" content="../../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../../index.html">
<img id="logo" class="svg" src="../../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-NcOptOptionPanel">
<h1 id="nc-optimization-option-panel-nc-optimization-config">NC Optimization Option Panel (NC Optimization Config)</h1>
<p>The key model is <a class="xref" href="../../../api/Hi.SessionCommands.NcOptOptionCommand.html">NcOptOptionCommand</a>; the options it carries are a
<a class="xref" href="../../../api/Hi.NcOpt.NcOptOption.html">NcOptOption</a>.</p>
<p>The command exists so the optimizer's settings are a step in the mission rather than a global
preference. <code>Run</code> assigns the command's option object onto the session shell — the whole body is
<code>sessionShell.NcOptOption = NcOptOption</code> — so the settings take effect at the point the entry sits
in the list. Everything played below it optimizes under them, and a second NC Optimization Config
further down the list re-points them mid-mission.</p>
<p>The command carries no title on either client. Its row and its tree label always read
<code>NC Optimization Config</code>: <code>GetCommandTitle</code> returns that fixed name and takes nothing from the user.</p>
<h2 id="where-it-renders">Where It Renders</h2>
<p>The web client has one editor component for this command and mounts it once per tree node, each
mount scoped to one group of options:</p>
<ul>
<li>The <strong>NC Optimization Config</strong> node itself carries the four enable checkboxes, below the
move / duplicate / delete control bar every command node shows.</li>
<li>The node grows five <strong>section children</strong> — Distances, Feedrate, Motion Dynamics, Force &amp; Safety,
Compensation — and each mounts the same component scoped to its own section.</li>
</ul>
<p>Unlike the General Config and Post-Execution sections, none of these five section nodes carries an
enable checkbox: this kind declares no section enable flags, so the tree hides the tick on them. The
command node's own checkbox is the only switch, and it only decides whether the mission runs the
command — a disabled command is skipped during play and stays fully editable.</p>
<h2 id="layout">Layout</h2>
<ul>
<li><p>NC Optimization Config Node</p>
<ul>
<li>Enable Optimization CheckBox
<ul>
<li>The model is <a class="xref" href="../../../api/Hi.NcOpt.NcOptOption.html#Hi_NcOpt_NcOptOption_EnableOpt">EnableOpt</a>.</li>
</ul>
</li>
<li>Enable Feedrate Optimization CheckBox
<ul>
<li>The model is <a class="xref" href="../../../api/Hi.NcOpt.NcOptOption.html#Hi_NcOpt_NcOptOption_EnableOptFeedrate">EnableOptFeedrate</a>.</li>
</ul>
</li>
<li>Enable Depth Splition CheckBox
<ul>
<li>The model is <a class="xref" href="../../../api/Hi.NcOpt.NcOptOption.html#Hi_NcOpt_NcOptOption_EnableDepthSplition">EnableDepthSplition</a>.</li>
<li>The desktop client labels the same switch &ldquo;Enable Depth Splitting&rdquo;.</li>
</ul>
</li>
<li>Enable Interpolation CheckBox
<ul>
<li>The model is <a class="xref" href="../../../api/Hi.NcOpt.NcOptOption.html#Hi_NcOpt_NcOptOption_EnableInterpolation">EnableInterpolation</a>.</li>
</ul>
</li>
</ul>
</li>
<li><p>Distances Section</p>
<ul>
<li>Extended Pre Distance Numeric Field (mm)
<ul>
<li>The model is <a class="xref" href="../../../api/Hi.NcOpt.NcOptOption.html#Hi_NcOpt_NcOptOption_ExtendedPreDistance_mm">ExtendedPreDistance_mm</a>.</li>
</ul>
</li>
<li>Extended Post Distance Numeric Field (mm)
<ul>
<li>The model is <a class="xref" href="../../../api/Hi.NcOpt.NcOptOption.html#Hi_NcOpt_NcOptOption_ExtendedPostDistance_mm">ExtendedPostDistance_mm</a>.</li>
</ul>
</li>
</ul>
</li>
<li><p>Feedrate Section</p>
<ul>
<li>Min Feedrate Numeric Field (mm/min)
<ul>
<li>The model is <a class="xref" href="../../../api/Hi.NcOpt.NcOptOption.html#Hi_NcOpt_NcOptOption_MinFeedrate_mmdmin">MinFeedrate_mmdmin</a>.</li>
</ul>
</li>
<li>Max Feedrate Numeric Field (mm/min)
<ul>
<li>The model is <a class="xref" href="../../../api/Hi.NcOpt.NcOptOption.html#Hi_NcOpt_NcOptOption_MaxFeedrate_mmdmin">MaxFeedrate_mmdmin</a>.</li>
</ul>
</li>
<li>Rapid Feed Numeric Field (mm/min)
<ul>
<li>The model is <a class="xref" href="../../../api/Hi.NcOpt.NcOptOption.html#Hi_NcOpt_NcOptOption_RapidFeed_mmdmin">RapidFeed_mmdmin</a>.</li>
</ul>
</li>
<li>Min Feed Per Tooth Numeric Field (mm)
<ul>
<li>The model is <a class="xref" href="../../../api/Hi.NcOpt.NcOptOption.html#Hi_NcOpt_NcOptOption_MinFeedPerTooth_mm">MinFeedPerTooth_mm</a>.</li>
</ul>
</li>
<li>Max Feed Per Tooth Numeric Field (mm)
<ul>
<li>The model is <a class="xref" href="../../../api/Hi.NcOpt.NcOptOption.html#Hi_NcOpt_NcOptOption_MaxFeedPerTooth_mm">MaxFeedPerTooth_mm</a>.</li>
</ul>
</li>
<li>Feedrate Assignment Ratio Numeric Field
<ul>
<li>The model is <a class="xref" href="../../../api/Hi.NcOpt.NcOptOption.html#Hi_NcOpt_NcOptOption_FeedrateAssignmentRatio">FeedrateAssignmentRatio</a>.</li>
</ul>
</li>
</ul>
</li>
<li><p>Motion Dynamics Section</p>
<ul>
<li>Max Acceleration Numeric Field (mm/s²)
<ul>
<li>The model is <a class="xref" href="../../../api/Hi.NcOpt.NcOptOption.html#Hi_NcOpt_NcOptOption_MaxAcceleration_mmds2">MaxAcceleration_mmds2</a>.</li>
</ul>
</li>
<li>Max Jerk Numeric Field (mm/s³)
<ul>
<li>The model is <a class="xref" href="../../../api/Hi.NcOpt.NcOptOption.html#Hi_NcOpt_NcOptOption_MaxJerk_mmds3">MaxJerk_mmds3</a>.</li>
</ul>
</li>
</ul>
</li>
<li><p>Force &amp; Safety Section</p>
<ul>
<li>Preferred Force Numeric Field (N)
<ul>
<li>The model is <a class="xref" href="../../../api/Hi.NcOpt.NcOptOption.html#Hi_NcOpt_NcOptOption_PreferedForce_N">PreferedForce_N</a>.</li>
<li>The one field hinted &quot;Accepts Infinity.&quot;, and the one field with no lower bound.</li>
</ul>
</li>
<li>Yielding Safety Factor Numeric Field
<ul>
<li>The model is <a class="xref" href="../../../api/Hi.NcOpt.NcOptOption.html#Hi_NcOpt_NcOptOption_YieldingSafetyFactor">YieldingSafetyFactor</a>.</li>
</ul>
</li>
<li>Thermal Yield Safety Factor Numeric Field
<ul>
<li>The model is <a class="xref" href="../../../api/Hi.NcOpt.NcOptOption.html#Hi_NcOpt_NcOptOption_ThermalYieldSafetyFactor">ThermalYieldSafetyFactor</a>.</li>
</ul>
</li>
<li>Spindle Torque Safety Factor Numeric Field
<ul>
<li>The model is <a class="xref" href="../../../api/Hi.NcOpt.NcOptOption.html#Hi_NcOpt_NcOptOption_MaxSpindleTorqueSafetyFactor">MaxSpindleTorqueSafetyFactor</a>.</li>
</ul>
</li>
<li>Spindle Power Safety Factor Numeric Field
<ul>
<li>The model is <a class="xref" href="../../../api/Hi.NcOpt.NcOptOption.html#Hi_NcOpt_NcOptOption_MaxSpindlePowerSafetyFactor">MaxSpindlePowerSafetyFactor</a>.</li>
</ul>
</li>
</ul>
</li>
<li><p>Compensation Section</p>
<ul>
<li>Enable Forward Compensation CheckBox
<ul>
<li>The model is <a class="xref" href="../../../api/Hi.NcOpt.NcOptOption.html#Hi_NcOpt_NcOptOption_EnableForwardCompensation">EnableForwardCompensation</a>.</li>
</ul>
</li>
<li>Enable Side Compensation CheckBox
<ul>
<li>The model is <a class="xref" href="../../../api/Hi.NcOpt.NcOptOption.html#Hi_NcOpt_NcOptOption_EnableSideCompensation">EnableSideCompensation</a>.</li>
</ul>
</li>
<li>Enable Depth Compensation CheckBox
<ul>
<li>The model is <a class="xref" href="../../../api/Hi.NcOpt.NcOptOption.html#Hi_NcOpt_NcOptOption_EnableDepthCompensation">EnableDepthCompensation</a>.</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>The three compensation switches are bit accessors over the model's compensation mask — forward is
bit 0, side bit 1, depth bit 2 — so all three travel as one integer in the project file.</p>
<h2 id="saving">Saving</h2>
<p>Every edit writes at once. There is no Save button and no dirty state: a checkbox saves on click, a
numeric field saves when it loses focus or on Enter, and each write is one PUT for that one
property. The value is applied locally first; if the request fails, the panel re-reads the whole
command and raises a toast, so the field snaps back to what the server holds.</p>
<p>The read side is a single command snapshot rather than one GET per property: the panel loads the
command and takes its <code>ncOptOption</code> object. Two keys in that object drop the engine's <code>Max</code> prefix —
the spindle torque and spindle power safety factors travel as <code>spindleTorqueSafetyFactor</code> and
<code>spindlePowerSafetyFactor</code>.</p>
<h2 id="infinity-and-bounds">Infinity and Bounds</h2>
<p><code>Infinity</code> is a legal value for Preferred Force and Max Feed Per Tooth, and those two are exactly
the properties whose endpoints take a string body: the client sends the literal <code>Infinity</code> (or
<code>-Infinity</code>), and the reader parses the same spelling back. Every other property's endpoint takes a
typed <code>bool</code> or <code>double</code>.</p>
<p>Every numeric field except Preferred Force is bounded at 0 — a smaller number is refused in the
field with &ldquo;Must be ≥ 0&rdquo; and never reaches the server.</p>
<div class="TIP">
<h5>Tip</h5>
<p>The engine's XML form parses these values through <code>XmlConvert.ToDouble</code>, which is why an infinite
feed per tooth or preferred force survives a project save and reload.</p>
</div>
<h2 id="defaults">Defaults</h2>
<p>A freshly added command carries the option model's own initial values, so the panel opens on them:</p>
<table>
<thead>
<tr>
<th>Option</th>
<th>Default</th>
</tr>
</thead>
<tbody>
<tr>
<td>The four enable switches</td>
<td>all on</td>
</tr>
<tr>
<td>Extended Pre / Post Distance</td>
<td>2 mm</td>
</tr>
<tr>
<td>Min / Max Feedrate</td>
<td>1 / 20000 mm/min</td>
</tr>
<tr>
<td>Rapid Feed</td>
<td>20000 mm/min</td>
</tr>
<tr>
<td>Min / Max Feed Per Tooth</td>
<td>0 mm / <code>Infinity</code></td>
</tr>
<tr>
<td>Feedrate Assignment Ratio</td>
<td>0.01</td>
</tr>
<tr>
<td>Max Acceleration / Max Jerk</td>
<td>10 mm/s² / 100 mm/s³</td>
</tr>
<tr>
<td>Preferred Force</td>
<td><code>Infinity</code></td>
</tr>
<tr>
<td>Yielding / Thermal Yield Safety Factor</td>
<td>0</td>
</tr>
<tr>
<td>Spindle Torque / Power Safety Factor</td>
<td>1.5</td>
</tr>
<tr>
<td>The three compensation switches</td>
<td>all off</td>
</tr>
</tbody>
</table>
<h2 id="the-two-clients">The Two Clients</h2>
<p>The desktop client edits the same command in one scrolling panel of six group boxes, and it names 17
of the model's options — the four enable switches, the two distances, the three feedrates,
acceleration and jerk, Preferred Force with the two spindle safety factors, and the three
compensation switches.</p>
<p>The web client edits five more, and that is the whole difference between the two editors: Min Feed
Per Tooth, Max Feed Per Tooth and Feedrate Assignment Ratio in the Feedrate section, and Yielding
Safety Factor and Thermal Yield Safety Factor in Force &amp; Safety.</p>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../../index.html">HiNC App Anatomy</a> for git repository links.</p>
<h3 id="web-application">Web Application</h3>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/mission/NcOptOptionCommandPanel.vue</code> — this panel. A <code>section</code> prop
picks the one group it renders, a setter map routes each field to its API call, and a failed write
re-reads the command and notifies.</li>
<li><code>wwwroot-src/src/components/controlTree/missionItemTypes.ts</code> — registers the <code>ncoptoption</code> kind's
bespoke editor, declares its five section children, gives it the <code>tune</code> icon and the
<code>NC Optimization Config</code> display name, and leaves it out of the section-enable readers and writers
so its section nodes carry no checkbox.</li>
<li><code>wwwroot-src/src/components/controlTree/MissionCommandSlavePanel.vue</code> — the control bar above the
command node's checkboxes.</li>
<li><code>wwwroot-src/src/components/controlTree/MissionSectionPanel.vue</code> — mounts this panel once per
section node, scoped to that node's section.</li>
<li><code>wwwroot-src/src/api/mission.ts</code> — the option shape the panel edits, the reader that takes the
<code>ncOptOption</code> snapshot and parses <code>Infinity</code>, and one setter per property over
<code>commands/{path}/ncoptoption/{endpoint}</code>.</li>
<li><code>wwwroot-src/src/components/widgets/NumericInput.vue</code> — the field every non-boolean option uses:
it commits on blur or Enter, spells infinity as <code>Infinity</code>, and enforces the minimum the panel
passes.</li>
<li><code>wwwroot-src/src/i18n/en/mission.ts</code> — the option labels and the &ldquo;Accepts Infinity.&rdquo; hint.</li>
<li><code>wwwroot-src/src/i18n/en/tree.ts</code> — the kind name and the five section names the tree shows.</li>
<li><code>Missions/NcOptOptionEndpoints.cs</code> — a partial of the same <code>MissionController</code>: one PUT per
editable property, two of them string-bodied so <code>Infinity</code> round-trips. Each creates the option
object when the command has none, and answers a command-type mismatch when the path holds another
kind.</li>
<li><code>Missions/MissionController.cs</code> — builds the command snapshot the panel reads, re-keying the two
spindle safety factors onto their wire names, and owns the entry lifecycle — add, delete, move,
duplicate, reparent — that puts this command in the list.</li>
</ul>
<h3 id="hiapi-engine">HiAPI Engine</h3>
<ul>
<li><code>HiNc/SessionCommands/NcOptOptionCommand.cs</code> — the mission entry: the <code>NC Optimization Config</code>
display name, its Optimization catalog registration, the single option property, the fixed command
title, the XML round-trip that nests the options in the project file, and the <code>Run</code> that assigns
them onto the session shell.</li>
<li><code>HiMech/NcOpt/NcOptOption.cs</code> — the option model: the engine-side names
<code>MaxSpindleTorqueSafetyFactor</code> and <code>MaxSpindlePowerSafetyFactor</code>, the compensation booleans as bit
accessors over one mask, and the mm/min feedrates as conversions over mm/s storage.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="PostExecutionCommand-panel.html">PostExecutionCommand Panel</a> — where the optimized programs are written out, under the settings this command puts in force</li>
<li><a href="../../../manual/run/the-other-commands.html">The Other Commands</a> — the task this command serves</li>
<li><a href="index.html">Mission</a> — the rest of the command panels</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,310 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>PostExecutionCommand Panel (Post-Execution) | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="PostExecutionCommand Panel (Post-Execution) | HiAPI-C# 2025 ">
<link rel="icon" href="../../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../../public/docfx.min.css">
<link rel="stylesheet" href="../../../public/main.css">
<meta name="docfx:navrel" content="../../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../../index.html">
<img id="logo" class="svg" src="../../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-PostExecutionCommandPanel">
<h1 id="postexecutioncommand-panel-post-execution">PostExecutionCommand Panel (Post-Execution)</h1>
<p>The key model is <a class="xref" href="../../../api/Hi.SessionCommands.PostExecutionCommand.html">PostExecutionCommand</a>, the command both clients label
<strong>Post-Execution</strong>. It is the Output category's first catalog entry, and it carries five outputs
derived from what the session has played: step files, shot files, optimization files, a CL → NC
writeback, and a geometry-difference detection.</p>
<h2 id="when-it-runs-and-what-it-covers">When It Runs, and What It Covers</h2>
<p>Post-Execution is an ordinary entry in the command list, not an end-of-run hook. Its name describes
what it consumes, not a fixed position: the list runs its entries in order and skips any whose
Enable box is clear, and everything this command writes is derived from what the session has played
<strong>so far</strong>.</p>
<p>That makes its placement the whole question. Put it after two program entries and it writes both.
Put a second one halfway down the list and it writes an interim snapshot of the same accumulating
state. The CL → NC writeback says this outright in the panel — place the CL-playing Program File
command before this command — and it re-serializes every control file the session played, so an NC
play is written back too. Read this together with
<a class="xref" href="PreSettingCommand-panel.html">General Config</a>, the other half of the pair: those settings
apply from their position forward, these outputs cover everything up to this position.</p>
<p>The web client places the command anywhere in the list, like any other. The WPF client pins it to
the end: a new command is inserted before a trailing Post-Execution while that is the list's only
one, and the drag guard refuses to move it off the last position.</p>
<p><code>Run</code> executes the enabled outputs in an order of its own, which is <strong>not</strong> the order the tree and
the list below show them in: shot files, then step files, then optimization, then the CL → NC
writeback, then the geometry difference. Output timestamps follow that order rather than the layout.</p>
<h2 id="layout">Layout</h2>
<p>Every output is a section child node of the command, and each section's enable flag is that node's
own tree checkbox. The command node itself carries only a caption saying so; the section panels hold
the template and numeric fields, which stay editable whether or not the output is switched on —
the flag decides what runs, not what can be edited.</p>
<ul>
<li>Step Files Output Section
<ul>
<li>The node's checkbox is <a class="xref" href="../../../api/Hi.SessionCommands.PostExecutionCommand.html#Hi_SessionCommands_PostExecutionCommand_EnableWriteStepFiles">EnableWriteStepFiles</a>.</li>
<li>Step File Template Field
<ul>
<li>The model is <a class="xref" href="../../../api/Hi.SessionCommands.PostExecutionCommand.html#Hi_SessionCommands_PostExecutionCommand_StepFileTemplate">StepFileTemplate</a>.</li>
<li>Default value: <code>Output/[NcName].step.csv</code></li>
</ul>
</li>
<li>Writes the step-series data of what the session has played.</li>
</ul>
</li>
<li>Shot Files Output Section
<ul>
<li>The node's checkbox is <a class="xref" href="../../../api/Hi.SessionCommands.PostExecutionCommand.html#Hi_SessionCommands_PostExecutionCommand_EnableWriteShotFiles">EnableWriteShotFiles</a>.</li>
<li>Shot File Template Field
<ul>
<li>The model is <a class="xref" href="../../../api/Hi.SessionCommands.PostExecutionCommand.html#Hi_SessionCommands_PostExecutionCommand_ShotFileTemplate">ShotFileTemplate</a>.</li>
<li>Default value: <code>Output/[NcName].shot.csv</code></li>
</ul>
</li>
<li>Shot File Time Resolution Number Field (ms)
<ul>
<li>The model is <a class="xref" href="../../../api/Hi.SessionCommands.PostExecutionCommand.html#Hi_SessionCommands_PostExecutionCommand_ShotFileTimeResolution_ms">ShotFileTimeResolution_ms</a>.</li>
<li>Default value: 1</li>
<li>This sampling period — not the machining resolution — sets the accuracy ceiling of the
time-series data, and a fine period produces a large file: a six-cut program writes about
13 MB at 1 ms and about 128 MB at 0.1 ms.</li>
</ul>
</li>
</ul>
</li>
<li>Optimization Output Section
<ul>
<li>The node's checkbox is <a class="xref" href="../../../api/Hi.SessionCommands.PostExecutionCommand.html#Hi_SessionCommands_PostExecutionCommand_EnableOptimizeToFiles">EnableOptimizeToFiles</a>.</li>
<li>Optimization File Template Field
<ul>
<li>The model is <a class="xref" href="../../../api/Hi.SessionCommands.PostExecutionCommand.html#Hi_SessionCommands_PostExecutionCommand_OptimizationFileTemplate">OptimizationFileTemplate</a>.</li>
<li>Default value: <code>Output/Opt-[NcName]</code></li>
</ul>
</li>
<li>The SoftNc pipeline runs when the SoftNc runner is on and the session holds played syntax
layers; otherwise the HardNc path runs. Both clear every cutter's optimization-limit cache
first.</li>
</ul>
</li>
<li>CL → NC Writeback Section
<ul>
<li>The node's checkbox is <a class="xref" href="../../../api/Hi.SessionCommands.PostExecutionCommand.html#Hi_SessionCommands_PostExecutionCommand_EnableConvertClToNcFiles">EnableConvertClToNcFiles</a>.</li>
<li>NC Output Template Field
<ul>
<li>The model is <a class="xref" href="../../../api/Hi.SessionCommands.PostExecutionCommand.html#Hi_SessionCommands_PostExecutionCommand_ClToNcFileTemplate">ClToNcFileTemplate</a>.</li>
<li>Default value: <code>Output/[NcName].nc</code></li>
<li>Its hint states the substitution: <code>[NcName]</code> is replaced by the source file name, extension
kept.</li>
</ul>
</li>
<li>A caption below the field explains the synthesis: the MSYS frame becomes a <code>G68.2</code> tilted
working plane, tool posture becomes <code>G43.4</code> RTCP with rotary words, and motions become
<code>G00</code>/<code>G01</code>/<code>G02</code>/<code>G03</code>. The CL must have been played on an XYZABC machine chain first. The
converted files appear as <code>→ file.nc</code> nodes on the Execution Program branch, with source ⇄
output line jumps.</li>
</ul>
</li>
<li>Geometry Difference Detection Section
<ul>
<li>The node's checkbox is <a class="xref" href="../../../api/Hi.SessionCommands.PostExecutionCommand.html#Hi_SessionCommands_PostExecutionCommand_EnableGeomDiff">EnableGeomDiff</a>.</li>
<li>Detect Radius Number Field (mm)
<ul>
<li>The model is <a class="xref" href="../../../api/Hi.SessionCommands.PostExecutionCommand.html#Hi_SessionCommands_PostExecutionCommand_GeomDiffDetectRadius_mm">GeomDiffDetectRadius_mm</a>.</li>
<li>Default value: 1</li>
</ul>
</li>
<li>Compares the workpiece geometry at that radius. With no workpiece in the session it reports
&ldquo;No Workpiece exist&rdquo; and does nothing.</li>
</ul>
</li>
</ul>
<p>Shot Files Output and Optimization Output are the two physics-gated sections. The condition is the
physics preference <strong>and</strong> the advanced-physics licence, served to the client already combined; when
it is off, the tree builder omits those two nodes entirely, and a preference flip shows up on the
next branch rebuild. CL → NC Writeback is deliberately not gated — writeback is a SoftNc syntax
feature, not a physics one.</p>
<p>Every edit saves as it is made. A rejected save is reported and the panel reloads the command.</p>
<div class="NOTE">
<h5>Note</h5>
<p>Post-Execution writes run-derived files only. The meshed-geometry snapshot is a placeable command
of its own, because a snapshot can be taken at any spot in a mission: Record Meshed Geometry
(<a class="xref" href="../../../api/Hi.SessionCommands.RecordMeshedGeomCommand.html">RecordMeshedGeomCommand</a>, with its No Action / Read / Write / Read On
First Or Write modes) and Export Meshed Geometry (STL)
(<a class="xref" href="../../../api/Hi.SessionCommands.ExportMeshedGeomToStlCommand.html">ExportMeshedGeomToStlCommand</a>) are both Output-category catalog entries
with editors of their own. Opening a project that still carries an enabled meshed-geometry output
on this command raises a system warning naming the replacement, and re-saving the project drops
the element.</p>
</div>
<div class="NOTE">
<h5>Note</h5>
<p><a class="xref" href="../../../api/Hi.SessionCommands.GeomDiffCommand.html">GeomDiffCommand</a> exists as a standalone command, as do
<code>WriteStepFilesCommand</code>, <code>WriteShotFilesCommand</code> and <code>OptimizeToFilesCommand</code>. None of them
carries the catalog attribute, so they load from a project file but are not offered in the Add
Command dialog. Post-Execution is the catalogued carrier of these outputs, which is why they are
edited as its sections.</p>
</div>
<h2 id="the-wpf-clients-panel">The WPF Client's Panel</h2>
<p>There four of the outputs are group boxes on one panel, each an enable checkbox over a grid that is
disabled until the box is ticked: Step Files Output, Shot Files Output, Optimization Output and
Geometry Difference Detection. The physics condition collapses the Shot Files and Optimization group
boxes rather than omitting tree nodes.</p>
<p>CL → NC Writeback is the one output the web client has and this panel does not. Its two properties
live on the same engine command, and the web section and its two endpoints are where they are
edited.</p>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../../index.html">HiNC App Anatomy</a> for git repository links.</p>
<h3 id="web-application">Web Application</h3>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/mission/PostExecutionCommandPanel.vue</code> — the command node's caption
and one headerless block per section: step-files, shot-files, optimization, cl-nc-writeback and
geom-diff. No checkboxes live here.</li>
<li><code>wwwroot-src/src/components/controlTree/missionItemTypes.ts</code> — declares the five sections with
their labels, marks Shot Files and Optimization physics-gated with an explicit note that CL → NC
is not, filters the gated sections out of the tree, and maps each section id to its enable-flag
reader and writer.</li>
<li><code>wwwroot-src/src/components/controlTree/MissionSectionPanel.vue</code> — renders one section headerless,
the breadcrumb already naming it, and passes no enable state down because the flags decide only
what runs.</li>
<li><code>wwwroot-src/src/components/controlTree/useControlTreeHost.ts</code> — the ticks: a command node ticks
its own enable flag, a section node with an enable flag ticks that, and every other node hides the
box.</li>
<li><code>wwwroot-src/src/api/mission.ts</code><code>loadPostExecution</code> and the eleven <code>postexecution/*</code> PUTs.</li>
<li><code>wwwroot-src/src/i18n/en/mission.ts</code> — the field labels, the <code>[NcName]</code> hint, the command node's
overview caption and the writeback paragraph.</li>
<li><code>wwwroot-src/src/i18n/en/tree.ts</code> — the five section node labels.</li>
<li><code>wwwroot-src/src/stores/appState.ts</code> — the physics preference and licence flags the tree builder
reads.</li>
<li><code>Environments/PreferenceController.cs</code> — serves the physics preference already combined with the
advanced-physics licence, the same condition the WPF panel evaluates.</li>
<li><code>Missions/MissionController.cs</code> — the <code>commands/{path}/postexecution/*</code> endpoints: the five enable
flags, the four templates, the shot-file time resolution and the geom-diff detect radius.</li>
</ul>
<h3 id="hiapi-engine">HiAPI Engine</h3>
<ul>
<li><code>HiNc/SessionCommands/PostExecutionCommand.cs</code> — the model, its display name and Output-category
catalog registration, every default above, the run order, and the warning raised for a project
still carrying a meshed-geometry output.</li>
<li><code>HiNc/SessionCommands/ListCommand.cs</code> — the in-order run that skips disabled entries, which is
what makes this command's placement decide what it covers.</li>
<li><code>HiNc/MachiningProcs/SessionShell.cs</code> — what each output actually calls: the shot-file writer and
its sampling-period guidance, the step-file writer, the optimization routing between the SoftNc
and HardNc paths, the CL → NC writeback synthesis, and the workpiece difference.</li>
<li><code>HiNc/SessionCommands/GeomDiffCommand.cs</code>, <code>HiNc/SessionCommands/WriteStepFilesCommand.cs</code>,
<code>HiNc/SessionCommands/WriteShotFilesCommand.cs</code> and
<code>HiNc/SessionCommands/OptimizeToFilesCommand.cs</code> — the standalone commands behind the same four
outputs: loadable from a project file, absent from the catalog.</li>
<li><code>HiNc/SessionCommands/RecordMeshedGeomCommand.cs</code> and
<code>HiNc/SessionCommands/ExportMeshedGeomToStlCommand.cs</code> — the two catalogued Output commands that
carry the meshed-geometry snapshot.</li>
<li><code>HiNc/SessionCommands/CommandCatalogAttribute.cs</code> — the attribute that separates a loadable
command from an addable one.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="PreSettingCommand-panel.html">PreSettingCommand Panel</a> — the other half of the positional pair: those settings apply forward, these outputs cover everything up to here</li>
<li><a class="xref" href="NcOptOption-panel.html">NC Optimization Option Panel</a> — the settings the Optimization Output section writes its result under</li>
<li><a href="../../../manual/run/the-other-commands.html">The Other Commands</a> — the task this command serves</li>
<li><a href="index.html">Mission</a> — the rest of the command panels</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,316 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>PreSettingCommand Panel (General Config) | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="PreSettingCommand Panel (General Config) | HiAPI-C# 2025 ">
<link rel="icon" href="../../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../../public/docfx.min.css">
<link rel="stylesheet" href="../../../public/main.css">
<meta name="docfx:navrel" content="../../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../../index.html">
<img id="logo" class="svg" src="../../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-PreSettingCommandPanel">
<h1 id="presettingcommand-panel-general-config">PreSettingCommand Panel (General Config)</h1>
<p>The key model is <a class="xref" href="../../../api/Hi.SessionCommands.PreSettingCommand.html">PreSettingCommand</a>, the command both clients label
<strong>General Config</strong>. It is one command carrying a bundle of session settings — the machining
resolution, the machining motion resolution, collision detection, pause on failure, physics — plus
an optional read of a meshed-geometry file.</p>
<h2 id="where-a-general-config-comes-from">Where a General Config Comes From</h2>
<p>The two clients differ on how a mission acquires these settings, and that difference is the first
thing to know about this panel.</p>
<p>The WPF client creates the bundle. Its Add menu carries a hard-coded item that constructs a
<code>PreSettingCommand</code>, and the insert rule pins it to the top of the list: a new General Config lands
at index 0 unless the list already starts with one, and while it is the list's only General Config
the drag guard refuses to move it off that position.</p>
<p>The web client's Add Command dialog is served by the server's command catalog, which reflects every
engine command carrying <code>[CommandCatalog]</code>. <code>PreSettingCommand</code> carries no such attribute, so the
dialog cannot produce one — the catalog keeps <code>presetting</code> only as a readable kind key, and creating
a command looks the kind up in the addable set alone. What the web offers instead is the same
settings as five separate Setup commands, each placeable anywhere in the list:</p>
<ul>
<li>Machining Resolution — <a class="xref" href="../../../api/Hi.SessionCommands.MachiningResolutionCommand.html">MachiningResolutionCommand</a>, 0.125 mm by default,
reading <code>Machining Resolution [0.125 mm]</code> in the list.</li>
<li>Machining Motion Resolution — <a class="xref" href="../../../api/Hi.SessionCommands.MachiningMotionResolutionCommand.html">MachiningMotionResolutionCommand</a>, Feed Per
Cycle by default.</li>
<li>Collision Detection — <a class="xref" href="../../../api/Hi.SessionCommands.CollisionDetectionCommand.html">CollisionDetectionCommand</a>, on by default.</li>
<li>Pause on Failure — <a class="xref" href="../../../api/Hi.SessionCommands.PauseOnFailureCommand.html">PauseOnFailureCommand</a>, off by default.</li>
<li>Physics — <a class="xref" href="../../../api/Hi.SessionCommands.PhysicsCommand.html">PhysicsCommand</a>, on by default, its field declared
physics-licence gated.</li>
</ul>
<p>Machining Motion Resolution ships an editor of its own. The other four declare their one scalar with
<code>[CommandField]</code> and are served by the generic field editor, which builds their input and its
server-localized label from that declaration — so the web ships strictly more here than one bundle
panel, and none of the four needed frontend code.</p>
<p>A project file never brings a reader to this panel either. Reading a stored bundle materializes
those five commands in its place, in the order the bundle applies them, preceded by a Read-mode
Record Meshed Geometry entry whenever the meshed-geometry read is enabled or a file is set. The
bundle's own enable state is consumed into every entry the expansion produces, and the Record entry
additionally keeps the bundle's read flag. A bundle stored as the project's bare root command
expands the same way, and what the project saves from then on is the split commands.</p>
<p>That leaves one case this panel serves: a <code>PreSettingCommand</code> an API caller constructed into the
list in place, which has not yet round-tripped through a project file. It edits that command through
the <code>commands/{path}/presetting/*</code> endpoints, and every field is a PUT of its own.</p>
<h2 id="when-the-settings-take-effect">When the Settings Take Effect</h2>
<p>A list runs its entries in order and skips any entry whose Enable box is clear. General Config's
<code>Run</code> then assigns straight onto the session shell, in this order: the meshed-geometry read when it
is enabled, the machining resolution, the motion resolution, collision detection, pause on failure,
physics.</p>
<p>So these are positional settings, not global pre-run settings. A program entry <strong>above</strong> this
command plays under whatever was in force before it; entries <strong>below</strong> it play under these values,
until something further down changes the session again. The five Setup commands state the same
contract one setting at a time — each is documented as taking effect &ldquo;from this command on&rdquo; — and
each can sit at a different point in the list, which is what having them separate buys.</p>
<h2 id="layout">Layout</h2>
<p>In the web client the command owns two tree nodes: the command node carrying the machining settings,
and one section child carrying the meshed-geometry file reference.</p>
<ul>
<li>Command Node
<ul>
<li>Machining Resolution Number Field
<ul>
<li>The model is <a class="xref" href="../../../api/Hi.SessionCommands.PreSettingCommand.html#Hi_SessionCommands_PreSettingCommand_MachiningResolution_mm">MachiningResolution_mm</a>.</li>
<li>Unit mm, floored at 0, default 0.125. A free numeric field — the web offers no option list.</li>
</ul>
</li>
<li>Motion Resolution
<ul>
<li>The model is <a class="xref" href="../../../api/Hi.SessionCommands.PreSettingCommand.html#Hi_SessionCommands_PreSettingCommand_MachiningMotionResolution">MachiningMotionResolution</a>.</li>
<li>A caption over a select with three choices: Feed Per Cycle, Feed Per Tooth and Fixed.</li>
<li>Fixed adds a row of two number fields inline, both floored at 0: Linear Resolution (mm) and
Rotary Resolution (deg), the two values of
<a class="xref" href="../../../api/Hi.Numerical.MachiningMotionResolutionUtils.FixedMachiningMotionResolution.html">FixedMachiningMotionResolution</a>.</li>
<li>Switching the type to Fixed carries the outgoing resolution's current linear and rotary values
into the new fixed pair rather than resetting them.</li>
</ul>
</li>
<li>Enable Collision Detection CheckBox
<ul>
<li>The model is <a class="xref" href="../../../api/Hi.SessionCommands.PreSettingCommand.html#Hi_SessionCommands_PreSettingCommand_EnableCollisionDetection">EnableCollisionDetection</a>.</li>
<li>Default value: true</li>
</ul>
</li>
<li>Enable Pause On Failure CheckBox
<ul>
<li>The model is <a class="xref" href="../../../api/Hi.SessionCommands.PreSettingCommand.html#Hi_SessionCommands_PreSettingCommand_EnablePauseOnFailure">EnablePauseOnFailure</a>.</li>
<li>Default value: false</li>
</ul>
</li>
<li>Enable Physics CheckBox
<ul>
<li>The model is <a class="xref" href="../../../api/Hi.SessionCommands.PreSettingCommand.html#Hi_SessionCommands_PreSettingCommand_EnablePhysics">EnablePhysics</a>.</li>
<li>Default value: true</li>
<li>Disabled unless the advanced-physics licence is held. The engine declares the same gate on the
split Physics command's field, so both editors refuse the same edit for the same reason.</li>
</ul>
</li>
</ul>
</li>
<li>Meshed Geometry Section Node
<ul>
<li>The node's own tree checkbox is
<a class="xref" href="../../../api/Hi.SessionCommands.PreSettingCommand.html#Hi_SessionCommands_PreSettingCommand_EnableReadMeshedGeom">EnableReadMeshedGeom</a>; the panel below it holds the
file reference only. The flag decides what runs, not what can be edited, so the field stays
editable whether or not the box is ticked — a read can be prepared before it is switched on.</li>
<li>Meshed Geometry File Field
<ul>
<li>The model is <a class="xref" href="../../../api/Hi.SessionCommands.PreSettingCommand.html#Hi_SessionCommands_PreSettingCommand_MeshedGeomFile">MeshedGeomFile</a>.</li>
<li>Hinted as a <code>.wct</code> or <code>.stl</code> path relative to the project folder.</li>
</ul>
</li>
<li>Browse Button
<ul>
<li>Opens the shared server-side file explorer, filtered to <code>.wct</code> / <code>.stl</code> with an All Files
fallback. It opens on the project directory and allows no other root, so a pick always yields
a project-relative path.</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>Every edit saves as it is made. A rejected save is reported and the panel reloads the command, so
what is on screen is what the server holds.</p>
<div class="NOTE">
<h5>Note</h5>
<p>The session's machining resolution is seeded from the workpiece's initial resolution when a
project is loaded or assigned — not on a runtime reset and not when the workpiece is swapped, so
an explicit setting survives both. This command carries its own value, and overwrites the
session's with it at the moment the command runs.</p>
</div>
<h2 id="the-wpf-clients-panel">The WPF Client's Panel</h2>
<p>There the whole bundle is one panel, in two group boxes.</p>
<ul>
<li><strong>Meshed Geometry Settings</strong> — an Enable Read Meshed Geometry checkbox over a Geometry File text
box and a Browse button; the grid beneath the checkbox is disabled until it is ticked. Browse is
an <code>OpenFileDialog</code> filtered to <code>*.wct;*.stl</code>, storing a pick under the project directory as a
relative path and one outside it as an absolute path.</li>
<li><strong>Machining Settings</strong> — the Machining Resolution control is an editable ComboBox pre-filled with
fifteen powers of two, from 0.0009765625 up to 16 in doublings, and a typed value outside that
list is accepted. Beside the Motion Resolution ComboBox a content presenter holds the fixed
fields, built at runtime and labelled Linear Resolution (mm) and Angle Resolution (deg) — Angle
Resolution being this client's wording for the same <code>RotaryResolution_deg</code> the web labels Rotary
Resolution. The three checkboxes sit loose in the same grid, and the Enable Physics one carries no
licence gate.</li>
</ul>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../../index.html">HiNC App Anatomy</a> for git repository links.</p>
<h3 id="web-application">Web Application</h3>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/mission/PreSettingCommandPanel.vue</code> — this editor in both of its
modes: the command node's machining settings, and the <code>meshed-geometry</code> section's file field with
its Browse button.</li>
<li><code>wwwroot-src/src/components/controlTree/missionItemTypes.ts</code> — maps the <code>presetting</code> kind to this
panel, declares its one Meshed Geometry section child, and turns <code>EnableReadMeshedGeom</code> into that
node's tree checkbox on read and on write.</li>
<li><code>wwwroot-src/src/components/controlTree/useControlTreeHost.ts</code> — which nodes show a checkbox: a
command node ticks its own enable flag, a section node with an enable flag ticks that, and every
other node hides the box. A disabled command dims its whole subtree.</li>
<li><code>wwwroot-src/src/components/controlTree/MissionSectionPanel.vue</code> — renders one section of a
command's editor headerless, the breadcrumb already naming it.</li>
<li><code>wwwroot-src/src/components/widgets/FileExplorerDialog.vue</code> — the server-side browser Browse
opens, with the project-directory root and the filter list this panel passes it.</li>
<li><code>wwwroot-src/src/components/mission/GenericCommandPanel.vue</code> — the editor the four
bespoke-panel-less Setup commands get, built from their declared scalar fields.</li>
<li><code>wwwroot-src/src/api/mission.ts</code><code>loadPreSetting</code> and the nine <code>presetting/*</code> PUTs behind it.</li>
<li><code>wwwroot-src/src/i18n/en/mission.ts</code> — the panel's wording, including the Rotary Resolution label
and the meshed-geometry hint and filter names.</li>
<li><code>wwwroot-src/src/stores/appState.ts</code> — the physics-licence flag the Enable Physics checkbox reads.</li>
<li><code>Missions/MissionCommandCatalog.cs</code> — the addable set the Add Command dialog is built from: the
reflected <code>[CommandCatalog]</code> entries, plus <code>presetting</code> as a read-only kind key that creation
never looks at.</li>
<li><code>Missions/MissionController.cs</code> — the <code>commands/{path}/presetting/*</code> endpoints:
enable-read-meshed-geom, meshed-geom-file, machining-resolution-mm,
machining-motion-resolution-type, fixed-linear-resolution-mm, fixed-rotary-resolution-deg,
enable-collision-detection, enable-pause-on-failure and enable-physics. The type switch is where
the current linear and rotary values are carried into a new fixed resolution.</li>
</ul>
<h3 id="hiapi-engine">HiAPI Engine</h3>
<ul>
<li><code>HiNc/SessionCommands/PreSettingCommand.cs</code> — the model, its defaults, the order <code>Run</code> applies
them onto the session shell, and the expansion into the split Setup commands.</li>
<li><code>HiNc/SessionCommands/CommandCatalogAttribute.cs</code> — the attribute the catalog reflects, and the
five categories. A command without it stays loadable from project files but is not offered for
creation.</li>
<li><code>HiNc/SessionCommands/CommandFieldAttribute.cs</code> — the scalar-field declaration the generic editor
renders, with its label, unit and physics-licence gate.</li>
<li><code>HiNc/SessionCommands/ListCommand.cs</code> — the in-order run that skips disabled entries, and the XML
read path that expands a stored bundle in place.</li>
<li><code>HiNc/MachiningProcs/MachiningProject.cs</code> — the same expansion for a bundle stored as the
project's bare root command.</li>
<li><code>HiNc/SessionCommands/MachiningResolutionCommand.cs</code>,
<code>HiNc/SessionCommands/MachiningMotionResolutionCommand.cs</code>,
<code>HiNc/SessionCommands/CollisionDetectionCommand.cs</code>,
<code>HiNc/SessionCommands/PauseOnFailureCommand.cs</code> and <code>HiNc/SessionCommands/PhysicsCommand.cs</code> — the
five Setup commands the web catalog offers, their defaults and their list titles. The detection,
pause and physics settings each carry the &ldquo;from this command on&rdquo; wording.</li>
<li><code>HiNc/MachiningProcs/LocalProjectService.cs</code> — the session's machining resolution and the seeding
from the workpiece's initial resolution on project load or assignment.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="PostExecutionCommand-panel.html">PostExecutionCommand Panel</a> — the other half of the positional pair: these settings apply forward, those outputs cover everything up to there</li>
<li><a href="../../../manual/run/the-other-commands.html">The Other Commands</a> — the five Setup commands this bundle expands into, as a task</li>
<li><a href="index.html">Mission</a> — the rest of the command panels</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -2,16 +2,16 @@
<html>
<head>
<meta charset="utf-8">
<title>Stick Tool Panel | HiAPI-C# 2025 </title>
<title>Mission | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Stick Tool Panel | HiAPI-C# 2025 ">
<meta name="title" content="Mission | HiAPI-C# 2025 ">
<link rel="icon" href="../../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../../public/docfx.min.css">
<link rel="stylesheet" href="../../../public/main.css">
<meta name="docfx:navrel" content="../../../toc.html">
<meta name="docfx:tocrel" content="../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../../">
@@ -84,81 +84,36 @@
<nav id="breadcrumb"></nav>
</div>
<article data-uid="">
<h1 id="stick-tool-panel">Stick Tool Panel</h1>
<article data-uid="Anatomy-Mission">
<h1 id="mission">Mission</h1>
<p>The term stick is for not only milling, but other remover like electric discharge machining tool.</p>
<p>The key model is MillingTool.
Other model: <a class="xref" href="../../../sample/Sample.Common.UserService.html">UserService</a>.</p>
<h2 id="layout">Layout</h2>
<p>Mission is a branch of the Execution page's Control Tree, reached as
<code>/execution?tree=execution/mission</code>. It holds the command list a run executes: the project's own
command is always a list, and nested lists read as sub-trees, so the branch is the mission and the
mission is a tree of commands.</p>
<p>Each command type has its own editor panel. The pages below document one type each; the root panel
documents the list itself — adding, ordering and removing what the run will play.</p>
<p>Ordered by where a command falls in a run: the branch root and its container first, then what is
set up before the run, what the run plays, and what happens after it.</p>
<h2 id="pages">Pages</h2>
<ul>
<li>Head Line
<ul>
<li><a href="../../widget/object-management-menu-button.html">Object Management Menu Button</a>
<ul>
<li>file extension is MillingTool</li>
<li>the pointed Editor Panel is Stick Tool Management Panel</li>
<li><a href="mission-root-panel.html">Mission Root Panel</a> — The branch root: the command list, and how a mission is assembled</li>
<li><a href="ListCommand-panel.html">List Command Panel</a> — The container command; a nested one becomes a sub-tree of the mission</li>
<li><a href="PreSettingCommand-panel.html">PreSetting Command Panel</a> — What is applied to the machine state before the run reaches a program</li>
<li><a href="NcOptOption-panel.html">NC Optimization Option Panel</a> — The optimizer's options as a command in the list, so a run can change them mid-mission</li>
<li><a href="NcFileCommand-panel.html">NcFile Command Panel</a> — Playing a program file from disk</li>
<li><a href="NcCodeCommand-panel.html">NcCode Command Panel</a> — Playing NC text held in the project rather than in a file</li>
<li><a href="script-command-panel.html">Script Command Panel</a> — Driving the session from C# script rather than from NC</li>
<li><a href="PostExecutionCommand-panel.html">PostExecution Command Panel</a> — What runs once the program above it has finished</li>
</ul>
</li>
<li>Title Label</li>
</ul>
</li>
<li>Stick Tool Management Panel
<p>The pages above do not cover every command kind a mission can hold. Three more ship an editor of
their own — Machining Motion Resolution, Record Meshed Geom and Export Meshed Geom — and the rest
fall back to a generic field editor built from the scalars the command declares, so a kind becomes
addable and editable before anyone writes a panel for it. The set on offer is whatever the server's
command catalog returns, which is why this folder cannot be a closed list.</p>
<h2 id="see-also">See Also</h2>
<ul>
<li>Cutter Tab
<ul>
<li><a href="../cutter/index.html">Cutter Panel</a></li>
</ul>
</li>
<li>Holder Tab
<ul>
<li><a href="../holder/index.html">Holder Panel</a></li>
</ul>
</li>
<li>Clamping Tab
<ul>
<li>Exposed-Cutter-Height TextField</li>
<li>Preserved-Distance-Between-Flute-and-Spindle-Nose TextField</li>
</ul>
</li>
<li>Intelligent Holder Tab
Visible if <a class="xref" href="../../../sample/Sample.Common.UserService.html#Sample_Common_UserService_EnablePhysics">EnablePhysics</a> is true.</li>
<li>Info Tab
<ul>
<li>Abstract Note TextField (readonly)</li>
<li>Note TextField (editable)</li>
</ul>
</li>
</ul>
</li>
</ul>
<div class="NOTE">
<h5>Note</h5>
<p>The Exposed-Cutter-Height and Preserved-Distance-Between-Flute-and-Spindle-Nose are directly related. Each value changed if each other value is changed.</p>
</div>
<h2 id="step-by-step-build-guide">Step by Step Build Guide</h2>
<ol>
<li>Build the Stick Tool Panel Layout framework. Since the framework helps to check of the child componenet.</li>
<li>Build accessory part of the framework.
<ol>
<li>Object Management Menu Button</li>
<li>Info Tab</li>
<li>Clamping Tab</li>
</ol>
</li>
<li>Build <a href="../holder/index.html">Holder Panel</a> and the related holder type panel.</li>
<li>Build <a href="../cutter/index.html">Cutter Panel</a> and the related cutter type panel.</li>
</ol>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a href="../../index.html">this page</a> for git repository.</p>
<h3 id="wpf-application-source-code-path">WPF Application Source Code Path</h3>
<ul>
<li>Mech/ToolHouse/StickToolPanel</li>
</ul>
<h3 id="web-page-application-source-code-path">Web Page Application Source Code Path</h3>
<ul>
<li>wwwroot/mech/stick-tool-panel.js</li>
<li>Controller/Mech/MechController.cs</li>
<li><a class="xref" href="../index.html">Execution Page</a> — the run cockpit this branch belongs to</li>
</ul>
</article>
@@ -0,0 +1,357 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Mission Root Panel | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Mission Root Panel | HiAPI-C# 2025 ">
<link rel="icon" href="../../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../../public/docfx.min.css">
<link rel="stylesheet" href="../../../public/main.css">
<meta name="docfx:navrel" content="../../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../../index.html">
<img id="logo" class="svg" src="../../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-MissionRootPanel">
<h1 id="mission-root-panel">Mission Root Panel</h1>
<p>The Mission branch root of the <a class="xref" href="../index.html">Execution page</a>'s Control Tree: route
<code>/execution</code>, Control-Tree path <code>execution/mission</code>, panel <code>MissionRootPanel.vue</code>. <code>/mission</code>
resolves to the same place — it redirects onto <code>/execution?tree=execution/mission</code>.</p>
<p>The panel edits one list of commands: the mission's own command, which is always a
<a class="xref" href="../../../api/Hi.SessionCommands.ListCommand.html">ListCommand</a>. The branch root is therefore a list editor — Add Command, the
entries in run order, and the operations that rewrite the list. Editing a command is not this
panel's job: clicking a row selects that command's tree node, and the command's own editor renders
on that node's panel.</p>
<p>Command nodes are addressed by index-derived tree ids and by dotted API paths (<code>&quot;0&quot;</code>, <code>&quot;1&quot;</code>, nested
<code>&quot;0.2&quot;</code>). A command node id extends its parent's, so a root entry is <code>execution/mission/{index}</code> and
an entry inside a nested list is <code>execution/mission/{index}/{index}</code>; a command's section children
extend it once more, as <code>execution/mission/{index}/{id}</code> keyed on the section. Both forms shift on
every structural change — deleting entry 2 slides the next command into path <code>&quot;2&quot;</code> — so each branch
build stamps its nodes afresh and the editor panels remount onto the new paths.</p>
<h2 id="key-models">Key Models</h2>
<ul>
<li><strong>Primary</strong>: <a class="xref" href="../../../api/Hi.MachiningProcs.MachiningProject.html#Hi_MachiningProcs_MachiningProject_PlayerCommand">PlayerCommand</a> — the mission's command,
always a <code>ListCommand</code>.</li>
<li><strong>Supporting</strong>:
<ul>
<li><a class="xref" href="../../../api/Hi.MachiningProcs.MachiningProject.html">MachiningProject</a></li>
<li><a class="xref" href="../../../api/Hi.SessionCommands.ListCommand.html">ListCommand</a> — the root command and every nested one.
<a class="xref" href="../../../api/Hi.SessionCommands.ListCommand.html#Hi_SessionCommands_ListCommand_CommandEntryList">CommandEntryList</a> is the row list, and a run walks it
top-down.</li>
<li><a class="xref" href="../../../api/Hi.SessionCommands.EnablingWrapper.html">EnablingWrapper</a> — one entry: the command plus
<a class="xref" href="../../../api/Hi.SessionCommands.EnablingWrapper.html#Hi_SessionCommands_EnablingWrapper_IsEnabled">IsEnabled</a>.</li>
<li><a class="xref" href="../../../api/Hi.SessionCommands.CommandCatalogAttribute.html">CommandCatalogAttribute</a> and <a class="xref" href="../../../api/Hi.SessionCommands.CommandCategory.html">CommandCategory</a>
— what Add Command offers, and the group it is offered under.</li>
</ul>
</li>
</ul>
<h2 id="layout">Layout</h2>
<ul>
<li>Mission Root Panel
<ul>
<li>Head Line
<ul>
<li>Add Command Button
<ul>
<li>Opens the Add Command dialog. Disabled while no project is open.</li>
</ul>
</li>
<li>Commands Caption</li>
<li>Command Count Badge
<ul>
<li>Outlined, and grey rather than primary while the list is empty.</li>
</ul>
</li>
</ul>
</li>
<li>Separator</li>
<li>Empty State
<ul>
<li>&ldquo;No project loaded&rdquo; while nothing is open; otherwise &ldquo;No commands yet&rdquo;, naming Add Command.</li>
</ul>
</li>
<li>(Each) Command Row
<ul>
<li>Drag Handle Icon
<ul>
<li>The whole row is draggable; the handle is the affordance for it.</li>
</ul>
</li>
<li>Command Label
<ul>
<li>The command's title as the engine composes it: the kind's localized display name, with a
title the user typed appended as <code>Name [title]</code>.</li>
</ul>
</li>
<li>Move Up Button / Move Down Button
<ul>
<li>Disabled at the ends of the list.</li>
</ul>
</li>
<li>Duplicate Button</li>
<li>Delete Button</li>
<li>A disabled command's row is dimmed, and its buttons stay live.</li>
<li>Clicking the row anywhere but on a button selects that command's tree node.</li>
</ul>
</li>
<li>Drop-Out Zone
<ul>
<li>Present in a nested list editor only, and shown only while a row is being dragged. It sits
below the rows, so the rows do not shift under the pointer at drag start.</li>
</ul>
</li>
<li>Run-Order Hint
<ul>
<li>Commands run top-down, and the checkbox on each command's tree item enables or disables it.</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>Enable/disable is not a control on this panel. It is the tree item's checkbox on the command node; a
disabled command is skipped when the mission plays and stays fully editable.</p>
<h2 id="add-command">Add Command</h2>
<p>The button opens a search-first picker. The search box matches a command's display label, its kind
key, or one of its declared aliases — each alias searchable both by its English key and by its word
in the request language. Arrow keys walk the results and Enter takes the highlighted one. Results
are grouped by category, in the catalog's own display order.</p>
<p>The picked command is appended to the list this editor is scoped to, and the branch rebuilds in
place: the selection stays on the list rather than jumping into the new command's panel.</p>
<p>The addable set is served by the backend rather than mirrored in a frontend menu. Every engine
command carrying <code>[CommandCatalog]</code> is reflected into the catalog, so attributing a new engine
command is all it takes for it to appear in the dialog. Thirteen kinds ship:</p>
<ul>
<li><strong>Setup</strong> — Machining Resolution, Machining Motion Resolution, Collision Detection, Pause on
Failure, Physics</li>
<li><strong>Program</strong> — Program File, NC Code, Script</li>
<li><strong>Optimization</strong> — NC Optimization Config</li>
<li><strong>Output</strong> — Post-Execution, Record Meshed Geometry, Export Meshed Geometry (STL)</li>
<li><strong>Flow</strong> — List</li>
</ul>
<div class="NOTE">
<h5>Note</h5>
<p>Program File plays NC, CL or CSV. The runner is picked from the file extension, and the command
can override that choice.</p>
</div>
<h2 id="ordering-duplicating-and-deleting">Ordering, Duplicating and Deleting</h2>
<p>Move Up and Move Down move an entry within its own list; they never change which list owns it.
Duplicate deep-clones the entry through the same XML round-trip the project file uses — so a nested
list copies with its whole subtree — and the clone lands right after the source. Delete asks for
confirmation in a dialog naming the command.</p>
<p>Dragging a row has three landings:</p>
<ul>
<li>On another row — reorder within this list. A plain row splits at its midline into before and
after, and the whole new order is sent as one ordered path list.</li>
<li>On the middle band of a <code>list</code> row — move the entry inside that list. That row's outer quarters
still reorder around it.</li>
<li>On the drop-out zone of a nested list editor — move the entry out, landing right after the list
command itself in the owning list.</li>
</ul>
<p>The last two are the same reparent call. The server resolves both lists to object references before
it mutates either, so the index shift the removal causes cannot misroute the insert, and it refuses
to move a list into itself or into one of its own descendants.</p>
<h2 id="nesting">Nesting</h2>
<p>A <code>list</code> entry grows the same structure one level down: its children are its own entries, so nested
lists read as sub-trees at any depth, and the very same panel edits them. The differences are the
scope — the root list, versus the node's own dotted path — and the drop-out zone, which appears only
in a nested editor. A nested list's node adds an optional title above the embedded editor, and that
title is appended to the List name in the row and tree label.</p>
<p>Because the mission's own command is always a list, the root and a nested list are one editor at two
scopes. A project file whose stored command is something else loads with that command wrapped into
the root list, so the panel always has a list to edit.</p>
<h2 id="where-a-command-is-edited">Where a Command Is Edited</h2>
<p>A command's editor renders on its own tree node, below the control bar carrying the operations that
rewrite the parent list (Up, Down, Duplicate, Delete):</p>
<ul>
<li>Nine kinds have a bespoke editor: General Config, Machining Motion Resolution,
<a class="xref" href="NcFileCommand-panel.html">Program File</a>, <a class="xref" href="NcCodeCommand-panel.html">NC Code</a>,
<a class="xref" href="script-command-panel.html">Script</a>, <a class="xref" href="NcOptOption-panel.html">NC Optimization Config</a>,
<a class="xref" href="PostExecutionCommand-panel.html">Post-Execution</a>, Record Meshed Geometry and Export Meshed
Geometry.</li>
<li><code>list</code> is edited inline by the control-bar panel: the optional title over the embedded
<a class="xref" href="ListCommand-panel.html">list editor</a>.</li>
<li>The four remaining Setup kinds — Machining Resolution, Collision Detection, Pause on Failure and
Physics — are served by the generic field editor, which renders the command's reflected
<code>[CommandField]</code> scalars with server-localized labels. That is what the app does instead of
shipping one panel per kind: a catalog kind needs frontend code only when it wants a richer
editor.</li>
</ul>
<p><a class="xref" href="PreSettingCommand-panel.html">General Config</a> is the one asymmetry. Its editor ships and
reads a command, but the catalog does not offer it: a project file that stores the bundle loads as
the individual commands it stands for, and the panel serves projects that construct one through the
API.</p>
<p>The multi-card kinds — General Config, NC Optimization Config and Post-Execution — put their extra
cards on section child nodes of the command, one node per card, with the card's own enable flag
surfaced as that section node's tree checkbox. Post-Execution's Shot Files Output and Optimization
Output sections appear only while the physics preference is on.</p>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../../index.html">HiNC App Anatomy</a> for git repository links.</p>
<h3 id="web-application">Web Application</h3>
<ul>
<li><code>wwwroot-src/src/components/controlTree/MissionRootPanel.vue</code> — this panel: Add Command, the entry
rows and their four actions, the three drag landings, and the delete confirmation. It serves the
Mission root and, embedded under a command's control bar, every nested <code>list</code> node.</li>
<li><code>wwwroot-src/src/components/controlTree/AddCommandDialog.vue</code> — the search-first catalog picker.</li>
<li><code>wwwroot-src/src/components/controlTree/MissionCommandSlavePanel.vue</code> — one entry's panel: the
move / duplicate / delete control bar over the kind's editor, or over the title input and embedded
list editor of a <code>list</code> entry.</li>
<li><code>wwwroot-src/src/components/controlTree/MissionSectionPanel.vue</code> — the panel of a command's
section child, rendering that one card.</li>
<li><code>wwwroot-src/src/components/controlTree/missionItemTypes.ts</code> — the Mission wave of the Control
Tree: the item types, the child builders that carry the recursion, the per-kind editor map, the
section definitions and their enable flags, and the kind and category icons.</li>
<li><code>wwwroot-src/src/components/controlTree/useControlTreeHost.ts</code> — builds the Execution root with
the Mission branch node above the Program branch, and drives the command and section checkboxes.</li>
<li><code>wwwroot-src/src/api/mission.ts</code> — typed wrapper over <code>/api/Mission/*</code>: <code>listCommandEntries</code>,
<code>addEntry</code>, <code>removeEntry</code>, <code>moveEntry</code>, <code>duplicateEntry</code>, <code>reparentEntry</code>, <code>reorderEntries</code>,
<code>setListTitle</code>, <code>getCommandCatalog</code>, <code>loadCommandFields</code> / <code>setCommandField</code>, and the per-kind
readers and writers.</li>
<li><code>wwwroot-src/src/components/mission/GenericCommandPanel.vue</code> — the fallback editor for a catalog
kind with no bespoke panel: the command's reflected <code>[CommandField]</code> scalars.</li>
<li>The bespoke editors:
<ul>
<li><code>wwwroot-src/src/components/mission/PreSettingCommandPanel.vue</code></li>
<li><code>wwwroot-src/src/components/mission/MachiningMotionResolutionCommandPanel.vue</code></li>
<li><code>wwwroot-src/src/components/mission/NcFileCommandPanel.vue</code></li>
<li><code>wwwroot-src/src/components/mission/NcCodeCommandPanel.vue</code></li>
<li><code>wwwroot-src/src/components/mission/ScriptCommandPanel.vue</code></li>
<li><code>wwwroot-src/src/components/mission/NcOptOptionCommandPanel.vue</code></li>
<li><code>wwwroot-src/src/components/mission/PostExecutionCommandPanel.vue</code></li>
<li><code>wwwroot-src/src/components/mission/RecordMeshedGeomCommandPanel.vue</code></li>
<li><code>wwwroot-src/src/components/mission/ExportMeshedGeomCommandPanel.vue</code></li>
</ul>
</li>
<li><code>wwwroot-src/src/i18n/en/tree.ts</code> — the <code>tree.mission.*</code> strings this panel renders: Add Command,
the Commands caption, the no-commands-yet state, the drop-out zone, the run-order hint, the kind
display names, the section names and the operation labels.</li>
<li><code>wwwroot-src/src/router/routes.ts</code> — resolves <code>/mission</code> onto <code>/execution?tree=execution/mission</code>.</li>
<li><code>Missions/MissionController.cs</code> — the entry lifecycle (<code>GET list-command/entries</code>,
<code>POST list-command/entries</code> and <code>POST list-command/entries/{path}</code> to add at the root or inside a
nested list, <code>DELETE list-command/entries/{path}</code>, <code>POST list-command/entries/{path}/move</code>,
<code>PUT list-command/reorder</code>, <code>POST list-command/entries/{path}/duplicate</code> and
<code>POST list-command/entries/{path}/reparent</code>), the <code>GET command-catalog</code> the Add Command dialog
reads, and the per-command endpoints including the generic <code>commands/{path}/fields[/{key}]</code> pair.
<code>reparent</code> is what backs both drag-into-a-list and drop-out-to-the-parent, and it is the endpoint
that rejects moving a list into itself or its own descendants.</li>
<li><code>Missions/MissionCommandCatalog.cs</code> — reflects every <code>[CommandCatalog]</code> command into the addable
set served to Add Command, and creates the picked kind.</li>
<li><code>Missions/MissionCommandFields.cs</code> — the reflection layer behind the generic field endpoints: it
describes and updates a command's <code>[CommandField]</code> scalars.</li>
<li><code>Missions/NcOptOptionEndpoints.cs</code> — the NC Optimization Config per-property PUT endpoints.</li>
</ul>
<h3 id="hiapi-engine">HiAPI Engine</h3>
<ul>
<li><code>HiNc/SessionCommands/CommandCatalogAttribute.cs</code><code>CommandCategory</code> and the <code>[CommandCatalog]</code>
attribute (category, order, kind key, aliases), plus the class-name-minus-<code>Command</code> derivation of
the kind key. This is what makes the addable set backend-owned.</li>
<li><code>HiNc/SessionCommands/ListCommand.cs</code> — the container command: the entry list a run walks
top-down, skipping disabled entries.</li>
<li><code>HiNc/MachiningProcs/MachiningProject.cs</code> — declares the mission's command as a list and keeps it
one when a project is read.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="../program.html">Program Branch</a> — the read-only inspection twin of this list: what a
run actually read, one node per NC source file</li>
<li><a class="xref" href="ListCommand-panel.html">List Command Panel</a> — the same editor one level down, and what moving an entry in or out of a nested list costs</li>
<li><a href="../../../manual/run/building-a-mission.html">Building a Mission</a> — the task this panel serves, as a procedure</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,274 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Script Command Panel | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Script Command Panel | HiAPI-C# 2025 ">
<link rel="icon" href="../../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../../public/docfx.min.css">
<link rel="stylesheet" href="../../../public/main.css">
<meta name="docfx:navrel" content="../../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../../index.html">
<img id="logo" class="svg" src="../../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-ScriptCommandPanel">
<h1 id="script-command-panel">Script Command Panel</h1>
<p>The key model is <a class="xref" href="../../../api/Hi.SessionCommands.ScriptCommand.html">ScriptCommand</a>.</p>
<p>The command holds two values: <a class="xref" href="../../../api/Hi.SessionCommands.ScriptCommand.html#Hi_SessionCommands_ScriptCommand_ScriptText">ScriptText</a>, a C# script, and
<a class="xref" href="../../../api/Hi.SessionCommands.ScriptCommand.html#Hi_SessionCommands_ScriptCommand_ScriptTitle">ScriptTitle</a>, the name it runs under. Both are written into
the project file, so the script travels with the project. <code>Run</code> evaluates the text against the
session shell as the globals object, and when the script returns a sequence of actions that sequence
is yielded into the run.</p>
<p>The title is what the mission row and the tree node show: the label is composed as <code>Script [title]</code>,
falling back to a bare <code>Script</code> while the title is empty. A newly added command starts empty on both
clients — no title, no text.</p>
<p>The panel renders on the command's own node in the Mission branch of the Execution page's Control
Tree, below the move / duplicate / delete control bar. A single-purpose kind like this one embeds its
whole editor on that node rather than growing section child nodes. Its tree checkbox only decides
whether the run plays the command; a disabled script is skipped during play and its editor stays
fully usable.</p>
<h2 id="layout">Layout</h2>
<ul>
<li>Head Line
<ul>
<li>Script Title Text Field
<ul>
<li>The model is <a class="xref" href="../../../api/Hi.SessionCommands.ScriptCommand.html#Hi_SessionCommands_ScriptCommand_ScriptTitle">ScriptTitle</a>.</li>
<li>Labelled &ldquo;Title (optional)&rdquo; on the web, &ldquo;Script Title&rdquo; on WPF.</li>
</ul>
</li>
<li>Autosave Indicator (web)
<ul>
<li>Shares the title row. See <a href="#saving">Saving</a>.</li>
</ul>
</li>
</ul>
</li>
<li>Script Editor Area
<ul>
<li>The model is <a class="xref" href="../../../api/Hi.SessionCommands.ScriptCommand.html#Hi_SessionCommands_ScriptCommand_ScriptText">ScriptText</a>.</li>
<li>Fills the rest of the panel.</li>
</ul>
</li>
</ul>
<h2 id="the-editor">The Editor</h2>
<p>The web editor is CodeMirror 6 running the <code>mission-script</code> mode: a stream tokenizer written for
the dialect a mission script actually is — top-level statements, not a compilation unit. It colours
keywords and boolean literals, strings (plain, verbatim <code>@&quot;&quot;</code>, interpolated <code>$&quot;&quot;</code> and <code>$@&quot;&quot;</code>),
character literals, hex and floating-point numbers, line and block comments, and it tags a call site
apart from a plain identifier. Because it is linear rather than a parser, every identifier of the
same kind gets the same colour no matter where it sits in the file.</p>
<p>The C# Lezer grammar the client also ships is not what this editor runs: its <code>compilationUnit</code> rule
rejects top-level statements, so it would recover from an error on line 1 and tag identical names
inconsistently. That grammar serves the separate <code>csharp</code> mode a <code>.cs</code> file opens in elsewhere in
the app.</p>
<p>Nothing in the highlighting is semantic. Everything the editor knows about types and members comes
from the backend, through completion.</p>
<h2 id="completion">Completion</h2>
<p>Typing an identifier — or asking explicitly — sends the whole script text plus the zero-based cursor
offset to <code>POST /api/script/completions</code>, and the response is an <code>items</code> array. Each item carries a
label, a kind already normalised to CodeMirror's own completion type names, a <code>detail</code> (the
signature, shown dim beside the label), a <code>documentation</code> (the XML <code>&lt;summary&gt;</code>, rendered to plain
text by Roslyn and shown in the popup's info pane) and an <code>insertText</code>.</p>
<p>Completion is Roslyn in-process, over the very <code>ScriptOptions</code> — the same references and imports —
that the run compiles the script with, so the session shell's members and the runtime API surface
complete for real rather than by name matching. The service prepends the synthesised
<code>using</code> / <code>using static</code> prefix those imports need and offsets the cursor by its length, so what the
list offers is what the evaluator will see. A cancelled request is answered 499, and a result that
arrives after the editor has moved on is dropped rather than shown.</p>
<p>Picking a method inserts the server-formatted call with each argument as a Tab stop, so the cursor
lands on the first parameter and Tab walks the rest; a method with no parameters inserts as <code>()</code>.
Properties, fields, types and keywords insert as their plain text — a snippet there would be noise.</p>
<h2 id="saving">Saving</h2>
<p>There is no Save button. Every keystroke in either field updates the model and schedules one save
500 ms after the last edit, and the pill beside the title reports the state: Idle, Dirty, Staging…,
Staged, or Error with the message in its tooltip. Staged means the server runtime holds the value —
its tooltip says so, because a staged script is not yet a committed project.</p>
<p>The write is a single PUT carrying the script text, the title and the content hash the last load or
save handed back. Each field is applied only when the body carries it. The response returns the new
hash to chain into the next save.</p>
<p>Two prompts guard the edges, each with three buttons because neither is a yes/no question:</p>
<ul>
<li><strong>Unsaved Changes</strong>, raised when the Control Tree tries to move the selection off a command whose
save is still pending: Save &amp; switch flushes and then allows the switch, Discard drops the pending
write, Cancel keeps the selection where it is.</li>
<li><strong>Script changed elsewhere</strong>, raised when the server rejects the save because the hash no longer
matches — another tab, or a project reload, changed the command underneath. The rejection carries
the server's current text, title and hash, so the panel can offer Discard &amp; reload, Force
overwrite, or Cancel without a second round trip.</li>
</ul>
<p>The panel snapshots its command path at mount, and the autosave closes over that snapshot rather
than over the live selection, so a flush can never land on whichever command happens to be selected
when it fires. After each successful save the tree relabels the node in place instead of rebuilding
the branch, which would remount the editor mid-edit.</p>
<h2 id="the-compile-gate">The Compile Gate</h2>
<p>A script that does not compile does nothing at run time: the command reports the compile failure
into the run's message stream and yields no work, so the run continues without it. Starting a run
therefore compile-checks every enabled script first — with the same script options and globals type
the evaluator uses — and refuses to start when any of them has an error, naming the first offender's
title, path, diagnostic id, message and line. The SPA's Start sends nothing to bypass that check, so
the refusal is what a user sees. The same check is also an endpoint of its own, per command and for
the whole mission.</p>
<h2 id="the-two-clients">The Two Clients</h2>
<p>The desktop client edits the same command in AvalonEdit: a bold Script Title label with its text
box, a Script Text label, and the editor with C# syntax highlighting and a line-number gutter,
bound to the command through an attached behaviour. There is no completion popup and no autosave —
the binding writes straight into the command as the text changes.</p>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../../index.html">HiNC App Anatomy</a> for git repository links.</p>
<h3 id="web-application">Web Application</h3>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/mission/ScriptCommandPanel.vue</code> — this panel: the title row sharing
its line with the autosave indicator, the editor below it, the 500 ms autosave, the two
three-button prompts, the path snapshot taken at mount, and the change notice that relabels the
tree node.</li>
<li><code>wwwroot-src/src/components/widgets/TextEditor.vue</code> — the CodeMirror 6 wrapper: language and
read-only swapped through compartments, CRLF normalised on the way in so an unedited file cannot
dirty the autosave, an opt-in completion source that overrides the language's own, and a widened
monospace popup so full signatures fit before the dim detail column truncates.</li>
<li><code>wwwroot-src/src/components/widgets/missionScriptLanguage.ts</code> — the <code>mission-script</code> mode: the
keyword set, the string and character rules, numbers, comments, call-site tagging, and its own
highlight style.</li>
<li><code>wwwroot-src/src/components/mission/csharpCompletionSource.ts</code> — the completion source: fires on
an identifier prefix or an explicit request, sends the whole document with the cursor offset,
drops stale results, and applies every candidate as a snippet.</li>
<li><code>wwwroot-src/src/api/scriptCompletion.ts</code> — the typed wrapper over the completion endpoint and the
kind union that maps straight onto CodeMirror's completion types.</li>
<li><code>wwwroot-src/src/api/mission.ts</code> — the script command shape (text, title, content hash), its
loader, the save that carries the expected hash, and the conflict error that carries the server's
current copy.</li>
<li><code>wwwroot-src/src/composables/useAutoSave.ts</code> — the debounce, the state machine and the conflict
recovery behind the panel's saving, saved and error states.</li>
<li><code>wwwroot-src/src/components/widgets/AutoSaveIndicator.vue</code> — the status pill beside the title.</li>
<li><code>wwwroot-src/src/components/controlTree/useControlTreeHost.ts</code> — funnels every selection change
through the active panel's switch gate, which is how the Unsaved Changes prompt gets its say.</li>
<li><code>wwwroot-src/src/components/controlTree/missionItemTypes.ts</code> — maps the <code>script</code> kind to this
panel and gives it no section children, so the whole editor lives on the command node.</li>
<li><code>wwwroot-src/src/i18n/en/mission.ts</code> — the panel's wording: both prompts, their buttons, and the
load and save error contexts.</li>
<li><code>Missions/ScriptCompletionController.cs</code><code>POST /api/script/completions</code>: a thin wrapper that
returns the items, 499 on cancellation and 500 with the message on failure.</li>
<li><code>Missions/ScriptCompletionService.cs</code> — the singleton holding one workspace, built from the same
script options the session evaluates with, and the snippet insert text it builds for methods.</li>
<li><code>Missions/ScriptCompileCheckService.cs</code> — compiles a script without running it, and walks the
mission for every enabled one; it backs the two compile-check endpoints and the pre-start gate.</li>
<li><code>Missions/MissionController.cs</code> — the single script PUT with its hash-based concurrency, the
content hash stamped into the command snapshot, and the two compile-check endpoints.</li>
<li><code>Execution/ExecutionController.cs</code> — the start gate that compile-checks the enabled scripts and
refuses to run while one of them has an error.</li>
</ul>
<h3 id="hiapi-engine">HiAPI Engine</h3>
<ul>
<li><code>HiNc/SessionCommands/ScriptCommand.cs</code> — the model: the title and text, the Program-category
catalog registration, the XML round-trip that puts the script into the project file, the
evaluation against the session shell with a compile failure reported into the run's message
stream, the returned actions yielded into the run, and the label rule that brackets the title
after the command name.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="NcCodeCommand-panel.html">NcCodeCommand Panel</a> — the other command that stores its text in the project, and the one that ships no editor chrome</li>
<li><a href="../../../manual/run/the-other-commands.html">The Other Commands</a> — the task this command serves, beside the settings and output kinds</li>
<li><a href="index.html">Mission</a> — the rest of the command panels</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,46 @@
<div id="sidetoggle">
<div>
<div class="sidefilter">
<form class="toc-filter">
<span class="glyphicon glyphicon-filter filter-icon"></span>
<span class="glyphicon glyphicon-remove clear-icon" id="toc_filter_clear"></span>
<input type="text" id="toc_filter_input" placeholder="Filter by title" onkeypress="if(event.keyCode==13) {return false;}">
</form>
</div>
<div class="sidetoc">
<div class="toc" id="toc">
<ul class="nav level1">
<li>
<a href="index.html" name="" title="Overview">Overview</a>
</li>
<li>
<a href="mission-root-panel.html" name="" title="Mission Root Panel">Mission Root Panel</a>
</li>
<li>
<a href="ListCommand-panel.html" name="" title="List Command Panel">List Command Panel</a>
</li>
<li>
<a href="PreSettingCommand-panel.html" name="" title="PreSetting Command Panel">PreSetting Command Panel</a>
</li>
<li>
<a href="NcOptOption-panel.html" name="" title="NcOptOption Panel">NcOptOption Panel</a>
</li>
<li>
<a href="NcFileCommand-panel.html" name="" title="NcFile Command Panel">NcFile Command Panel</a>
</li>
<li>
<a href="NcCodeCommand-panel.html" name="" title="NcCode Command Panel">NcCode Command Panel</a>
</li>
<li>
<a href="script-command-panel.html" name="" title="Script Command Panel">Script Command Panel</a>
</li>
<li>
<a href="PostExecutionCommand-panel.html" name="" title="PostExecution Command Panel">PostExecution Command Panel</a>
</li>
</ul>
</div>
</div>
</div>
</div>
@@ -0,0 +1,2 @@
{"items":[{"name":"Overview","href":"index.html","topicHref":"index.html"},{"name":"Mission Root Panel","href":"mission-root-panel.html","topicHref":"mission-root-panel.html"},{"name":"List Command Panel","href":"ListCommand-panel.html","topicHref":"ListCommand-panel.html"},{"name":"PreSetting Command Panel","href":"PreSettingCommand-panel.html","topicHref":"PreSettingCommand-panel.html"},{"name":"NcOptOption Panel","href":"NcOptOption-panel.html","topicHref":"NcOptOption-panel.html"},{"name":"NcFile Command Panel","href":"NcFileCommand-panel.html","topicHref":"NcFileCommand-panel.html"},{"name":"NcCode Command Panel","href":"NcCodeCommand-panel.html","topicHref":"NcCodeCommand-panel.html"},{"name":"Script Command Panel","href":"script-command-panel.html","topicHref":"script-command-panel.html"},{"name":"PostExecution Command Panel","href":"PostExecutionCommand-panel.html","topicHref":"PostExecutionCommand-panel.html"}]}
@@ -0,0 +1,396 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Program Branch | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Program Branch | HiAPI-C# 2025 ">
<link rel="icon" href="../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../index.html">
<img id="logo" class="svg" src="../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-ProgramBranch">
<h1 id="program-branch">Program Branch</h1>
<p>Program is the lower of the two Control-Tree branches the Execution page hosts: route <code>/execution</code>,
Control-Tree path <code>execution/program</code>, one node per NC source file the session read. It is a
read-only view of a run — the file's text, the passes that went over it, the per-line marks the run
left behind, and the writeback files the run wrote — assembled from session data rather than from
anything typed into it. Its node ids are positional, so an id addresses a node without surviving the
branch growing around it.</p>
<h2 id="program-against-mission">Program Against Mission</h2>
<p>Mission is what a run executes; Program is what the run read. That one difference sets the rest of
the branch:</p>
<ul>
<li>Mission's nodes edit the project. Program's item types declare a panel and a child builder and
nothing else — no enable checkbox (the tree hides the tick on every node that is not a Mission
command or an enable-bearing Mission section), no control bar, no add, delete, move or duplicate.</li>
<li>Mission's shape is authored. Program's shape is a consequence: a subprogram file appears on it
only because a run followed a call into it.</li>
<li>Mission belongs to the project. Program's contents belong to the session — the index behind it is
fed piece by piece as the run executes, and it is dropped when the session ends or the project
changes.</li>
</ul>
<p>Before anything runs the branch is not empty. The server seeds it by walking the mission root-first
through its nested lists — skipping a disabled entry along with everything beneath it — and taking
the NC file of every <strong>Program File</strong> command and the title of every <strong>NC Code</strong> command. It seeds
one placeholder per <em>distinct string</em>, not one per command: both loops append only where no root
already carries that string under the same case-insensitive, slash-normalised comparison the sibling
merge uses, and they share one namespace, so a Program File path and an NC Code title that normalise
alike collapse into one node. A Program File command whose path is still unset is skipped and seeds
nothing. A placeholder carries no invocations, so its panel shows the file's text with no marks and
an empty, disabled pass selector.</p>
<p>The transport bar is the one control the branch does carry, and it is not the branch's own: the
primary editor pane pins the <a href="execution-tool-bar.html">Execution Tool Bar</a> above the panel of the
Execution root and of every node beneath it, so a run can be started, stepped and reset while a
program file is on screen.</p>
<h2 id="the-nodes-the-branch-mints">The Nodes the Branch Mints</h2>
<p>Three item types, each keyed by position rather than by identity.</p>
<p><strong>The branch root</strong> (<code>execution/program</code>) carries the summary panel and the child builder that
fetches everything.</p>
<p><strong>A file node</strong> is one source file as reached through one call edge. The server groups executed
sentences by the file index stamped on them, resolves each invocation's caller from the call-stack
record on the executed piece, and nests the callee's node under the caller's; the same subprogram
called from two different files therefore appears once under each. Sibling nodes merge on a
case-insensitive, slash-normalised path comparison, so repeated passes over one file under one
caller collapse into a single node holding several invocations. Placeholders are appended after the
nodes a run produced, and one whose path a run node already holds is skipped rather than doubled —
that skip is also what keeps an inline command to one node, because the run stamps the command's
title as the path of every piece it plays, so the placeholder and the run node carry the same
string. A later pass flags every root whose path matches an enabled NC Code command's title as
inline; that flag drives three presentational choices — the label prefix, the panel's inline caption
and which missing-text wording it shows — and one behavioural one: the root's child builder matches
a node against the writeback conversions only when the node is not inline, so an inline root is
never stamped with a conversion, never gains the forward links to a converted file, and cannot be
the target a conversion's <em>converted from</em> header jumps back to. The tree label is the path's base
name, prefixed <code>(inline)</code> for an inline node and suffixed with a multiplication sign and a count
when the node holds more than one invocation.</p>
<p><strong>A conversion node</strong> is one NC writeback conversion held by the session, labelled with an arrow and
the written file's base name. Conversion nodes are appended after all file nodes.</p>
<p>A file node's id is its parent's id plus its index among that parent's children
(<code>execution/program/0</code>, <code>execution/program/0/1</code>); a conversion node's is the branch root's id plus
<code>dst-</code> and the conversion's position in the session's list. Both are positional, and the tree those
indices count into grows as a run discovers files, so a <code>?tree=</code> link into this branch names a
position and not a file.</p>
<p>The whole branch costs two requests. The root's child builder takes the file tree in one response
and the conversion list in another, stashes each file's raw subtree on the node it mints, and the
file nodes' builder only maps what is already there — no node fetches for itself. A failed
file-tree request yields an empty branch rather than an error; a failed conversion request leaves
the file tree standing without cross-links.</p>
<h2 id="invocations">Invocations</h2>
<p>An invocation is one pass over one file, keyed on the file index the run stamps onto every executed
sentence. A fresh index is allocated for each top-level play of a file and for every subprogram
call, call repetition and loop or jump re-segmentation, so a file entered or looped over more than
once contributes one invocation per pass, and the file panel's pass selector is exactly a pick among
them. Each entry reads as a hash-prefixed ordinal and a trigger, and the trigger is derived from the
piece that opened the pass:</p>
<ul>
<li>the call term with its P word — <code>M98 P8</code>, <code>M198 P8</code> — when the piece carries a Fanuc
subprogram-call record. Only that record is read, so a pass opened by a Siemens or Heidenhain
call, or by a Fanuc macro call, falls through to the two cases below;</li>
<li><code>re-entry</code> when this session has already seen a pass over the same path;</li>
<li><code>top</code> otherwise.</li>
</ul>
<p>The selector opens on the last invocation in the node's list — the pass that started most recently —
and re-defaults whenever a rebuild leaves the current selection absent from the list. It is disabled
while the node has none, and it takes typed text as a filter over the entries. Everything below it
belongs to the selected pass alone: switching passes drops the mark and link caches and refetches
both for every page of text already loaded, not only for the pages on screen. The footer counts the
file's lines and, for the selected pass, the number of distinct source lines that executed in it.</p>
<h2 id="what-a-run-marks">What a Run Marks</h2>
<p>The line viewer is virtualized. Text arrives in 1000-line pages covering the visible window plus two
pages either side; the server caps one request at 2000 lines and keeps a small most-recently-used
cache of whole files keyed on path and last-write time, so paging through a long program does not
re-read it once per page. A path with no file on disk is answered as non-existent, the miss is
cached so scrolling does not re-fire it, and the panel captions the file as missing — or, for an
inline node, as inline text that is no longer available, because an NC Code command's text is served
from the mission command itself and goes when the command is removed or retitled. A disk file wins
over a command title that shadows it.</p>
<p>Marks are fetched the same way and per selected pass. A line the pass executed carries the sentence
index it ran and the range of machining steps that sentence produced; a line absent from the answer
did not execute in that pass and is greyed — but only once the node holds at least one invocation,
so a placeholder's text renders plain rather than uniformly grey. A line whose sentence produced
steps carries a step badge, single-valued or a range.</p>
<p>Marks grow as a run advances, so a page cached early would keep later lines looking unexecuted. The
panel drops its mark cache and re-fetches the visible window on every throttled CL-strip update
broadcast.</p>
<p>Clicking a line points the shared sentence cursor at that source position, which is what fills the
Step Info column's <strong>Sentence Syntax</strong> panel; when the line's sentence produced steps, the click
also selects that sentence's first step, moving the charts and the 3D strip with it. A line with no
steps — a <code>G54</code>, a comment, a line that never ran — moves the cursor alone. Hovering a line sends
the same anchor as the entered step on a 50 ms leading-edge throttle, and leaving the viewer clears
it.</p>
<p>The traffic runs the other way too. A step picked on a chart or the 3D strip resolves to a source
anchor and lands on the cursor; a file panel whose path matches switches to the pass that step
belongs to and scrolls the line into view. The <strong>Follow</strong> toggle beside the pass selector is the
live variant: while it is on, the panel scrolls to whatever line the execution-status hub's cursor
reports for this file. It is off by default, it belongs to the mounted panel rather than to stored
preferences, and it does not change the selected pass.</p>
<h2 id="writeback-conversions">Writeback Conversions</h2>
<p>The session retains the destination piece streams and source-to-destination maps of its latest
CL-to-NC writeback run — <a class="xref" href="../../api/Hi.MachiningProcs.MachiningSession.html#Hi_MachiningProcs_MachiningSession_NcConversions">NcConversions</a><small>(API)</small>,
refilled by <a class="xref" href="../../api/Hi.MachiningProcs.MachiningSession.html#Hi_MachiningProcs_MachiningSession_ConvertClToNcFiles_">ConvertClToNcFiles</a><small>(API)</small>. This
branch only displays them; the writeback itself is switched on by the mission's <strong>Post-Execution</strong>
command, in its <strong>CL → NC Writeback</strong> section. Because the conversions live on the session, they go
when it does.</p>
<p>A conversion node shows the written file's lines, each line whose piece has a registered source
carrying a back-arrow badge naming that source line. The panel's header names this conversion's
source file; the link on that name lands on the file's node when a root file node matched the
conversion's source path, and otherwise falls back to the first entry of the branch's file-index
map, so a conversion whose source was reached only as a nested or inline node can name one file and
open another. Clicking a badge selects the source node, switches it to the invocation the piece came
from, and scrolls to the line; clicking anywhere else on a row only highlights it.
The viewer has no missing-file caption of its own, so a written file no longer on disk renders as an
empty viewer.</p>
<p>The reverse direction is carried by the file panel. A root file node whose path matches a
conversion's source path gets forward-arrow badges naming the destination line, with a count when
one source line produced several destination lines, and clicking one selects the conversion node and
scrolls to the first of them. That matching is done on root nodes only, and never on an inline node,
so a subprogram file reached through a call carries no forward badges of its own even though a
backward jump can land on it and switch its pass.</p>
<p>The jump itself rides one shared slot, because only one editor panel is mounted at a time: the
requesting panel parks the target line and emits a selection, and the twin takes the parked jump
when it mounts — or through a watcher, when it was already the selected panel. A target consumed
before the first page of text resolves is parked again and re-applied once the line array is sized.</p>
<h2 id="when-the-branch-is-rebuilt">When the Branch Is Rebuilt</h2>
<ul>
<li><strong>Page initialization.</strong> Building the Execution root builds the branch, and only when a project is
loaded; without one it stays empty.</li>
<li><strong>Any change in the broadcast execution status.</strong> The status the hub reports is one of
<code>NoProject</code>, <code>Ready</code>, <code>Running</code>, <code>Paused</code> and <code>Finished</code>, and the host re-reads the branch
whenever that value changes. It deliberately does not take the structural-change path used
elsewhere in the tree, because that path force-expands the node it rebuilds and a run would then
keep re-opening a branch the reader had closed. The panels pick up <strong>part</strong> of the change from
the replaced nodes: the path caption, the pass selector, the inline wording and the executed-line
count are all derived from the node, but the file text is not — it is fetched once per page on
mount and on scroll, with no watcher on the path, and the panel is not remounted because its key
is built from the node id, which is positional and therefore unchanged. A rebuild that puts a
different file at the same index leaves the previous file's text on screen under the new node's
caption and marks.</li>
<li><strong>The refresh button on the root panel.</strong> This one does take the structural-change path: it
re-reads the file tree from the current session and expands the branch.</li>
</ul>
<p>A reset is the emptying event: it ends the session and clears the index, so the next read of the
branch returns the mission's placeholders and nothing else.</p>
<h2 id="layout">Layout</h2>
<ul>
<li>Program Node
<ul>
<li>Intro Caption</li>
<li>Status Row
<ul>
<li>Run-State Badge — reads <code>run data</code> when any of the branch root's direct file children holds at
least one invocation and <code>not run yet</code> otherwise; like the count beside it, it looks one level
deep and never at nested subprogram nodes. It is computed from the nodes, not from the
file-tree response's own run-data flag.</li>
<li>File Count Caption — the branch root's direct file children only; nested subprogram nodes are
not counted.</li>
<li>Converted File Count Caption — shown only when the session holds conversions.</li>
<li>Refresh Button</li>
</ul>
</li>
<li>Not-Run Hint — shown while the badge reads <code>not run yet</code>.</li>
<li>Conversions Hint — shown only when the session holds conversions.</li>
</ul>
</li>
<li>Program File Node
<ul>
<li>Path Caption — the path as the run stamped it; an inline node appends an inline-NC-code tag.</li>
<li>Pass Select</li>
<li>Follow Toggle</li>
<li>Not-Run Hint — shown while the node has no invocations.</li>
<li>Missing-Source Caption — the disk-file wording or the inline-text wording.</li>
<li>Line Viewer
<ul>
<li>Line Number</li>
<li>Line Text</li>
<li>Step Badge</li>
<li>Converted-Line Badge</li>
</ul>
</li>
<li>Count Footer — line count, plus the selected pass's executed-line count.</li>
</ul>
</li>
<li>Converted File Node
<ul>
<li>Path Caption</li>
<li>Converted-From Line — the source file's name, linked to its node when a root file node matched.</li>
<li>Line Viewer
<ul>
<li>Line Number</li>
<li>Line Text</li>
<li>Source-Line Badge</li>
</ul>
</li>
<li>Count Footer — line count and converted-piece count.</li>
</ul>
</li>
</ul>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../index.html">HiNC App Anatomy</a> for git repository links.</p>
<h3 id="web-application">Web Application</h3>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/controlTree/programItemTypes.ts</code> — the branch's three item types, the
node ids, the labels, the conversion stamping, and the two-request child builder.</li>
<li><code>wwwroot-src/src/components/controlTree/ProgramRootPanel.vue</code> — the branch root: intro, run-state
badge, file and conversion counts, hints and the refresh button.</li>
<li><code>wwwroot-src/src/components/controlTree/ProgramFilePanel.vue</code> — one source file: the pass
selector, the Follow toggle, the paged line viewer, the execution marks, the step and
converted-line badges, and the cursor and hover wiring.</li>
<li><code>wwwroot-src/src/components/controlTree/ProgramConversionFilePanel.vue</code> — one written file: the
paged line viewer and the source-line links back into the file panel.</li>
<li><code>wwwroot-src/src/components/controlTree/useControlTreeHost.ts</code> — declares the branch root under
the Execution root, re-reads it on every execution-status change without expanding it, and hides
the tick on every node that is not a Mission command or an enable-bearing Mission section.</li>
<li><code>wwwroot-src/src/components/controlTree/itemTypes.ts</code> — the node shape carrying the program
bookkeeping, the merged item-type registry, and the eager subtree builder.</li>
<li><code>wwwroot-src/src/components/controlTree/ControlTreePanel.vue</code> — the tree pane that renders the
branch's nodes and binds the tree's strict tick strategy.</li>
<li><code>wwwroot-src/src/components/controlTree/PrimarySlavePanel.vue</code> — mounts the selected node's panel,
forwards its selection request to the host, and pins the transport bar above every Execution-scope
node.</li>
<li><code>wwwroot-src/src/api/ncProgram.ts</code> — the typed client for the file tree, the paged lines, the line
marks, the syntax piece, the sentence-to-step mapping and both directions of conversion links.</li>
<li><code>wwwroot-src/src/api/clStrip.ts</code> — the step-select and step-enter posts a line click and a line
hover make.</li>
<li><code>wwwroot-src/src/composables/useConversionJump.ts</code> — the single parked-jump slot the two viewers
hand a target through.</li>
<li><code>wwwroot-src/src/composables/useSentenceCursor.ts</code> — the shared source position, written by a line
click and by the resolved step selection.</li>
<li><code>wwwroot-src/src/composables/useClStripHub.ts</code> — the throttled run-advance counter that
invalidates the visible marks.</li>
<li><code>wwwroot-src/src/composables/useExecutionStatusHub.ts</code> — the status the branch rebuild watches and
the running cursor the Follow toggle scrolls to.</li>
<li><code>wwwroot-src/src/components/execution/SyntaxPiecePanel.vue</code> — the Step Info column's Sentence
Syntax panel, the other reader of the shared cursor.</li>
<li><code>wwwroot-src/src/i18n/en/tree.ts</code> — the branch's labels, hints, badges and pluralised counts.</li>
<li><code>Execution/NcProgramController.cs</code> — the read-only endpoints: the file tree, the paged file lines
with their inline-command fallback and whole-file cache, the line marks, the syntax piece, the
sentence and step lookups, the conversion list and both directions of conversion links.</li>
<li><code>Execution/NcProgramRegistryService.cs</code> — the session-scoped index: the event feed, the invocation
records with their trigger and caller resolution, the file-tree snapshot with its mission
placeholders, the per-line marks, and the clear on reset or project change.</li>
</ul>
<h3 id="hiapi-engine">HiAPI Engine</h3>
<ul>
<li><code>HiNc/MachiningProcs/LocalProjectService.cs</code> — the app-lifetime bridge that forwards each executed
piece and each built machining step, and the reset that ends the session.</li>
<li><code>HiMech/MachiningProcs/MachiningSession.cs</code> — the retained conversions and the CL-to-NC writeback
run that refills them.</li>
<li><code>HiMech/NcParsers/NcWriteback/NcConversion.cs</code> — one conversion: the destination piece stream, its
written path and the source-to-destination map the cross-links walk.</li>
<li><code>HiMech/NcParsers/Syntaxs/SyntaxPiece.cs</code> — the executed unit the index is keyed on, and the
source line stamped on its sentence.</li>
<li><code>HiNc/SessionCommands/NcFileCommand.cs</code> — the Program File command whose path seeds a placeholder.</li>
<li><code>HiNc/SessionCommands/NcCodeCommand.cs</code> — the NC Code command whose title is the pseudo-path of an
inline node and whose text is that node's only source.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="mission/mission-root-panel.html">Mission Root Panel</a> — the editable twin: the command list this branch reports on, and
the only place the NC file list is changed</li>
<li><a class="xref" href="index.html">Execution Page</a> — the page that hosts both branches, and the transport, canvas and charts
the branch's selections drive</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -11,7 +11,7 @@
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
@@ -84,17 +84,23 @@
<nav id="breadcrumb"></nav>
</div>
<article data-uid="">
<article data-uid="Anatomy-SelectedStepInfoPanel">
<h1 id="selected-step-info-panel">Selected-Step Info Panel</h1>
<p>The panel locates on the <a href="index.html">Player Panel</a>.</p>
<p>The model is <a class="xref" href="../../api/Hi.MachiningSteps.MachiningStep.html">MachiningStep</a> and <a class="xref" href="../../sample/Sample.Common.UserService.html">UserService</a>.</p>
<p>The panel that shows the selected machining step's properties. It sits in the <a href="index.html">Execution Page</a>'s Step Info column, where the app labels it <strong>Step Properties</strong>.</p>
<p>The model is <a class="xref" href="../../api/Hi.MachiningSteps.MachiningStep.html">MachiningStep</a> and <code>UserService</code>.</p>
<p>The <a class="xref" href="../../api/Hi.MachiningSteps.MachiningStep.html">MachiningStep</a> is assigned by <a class="xref" href="../../api/Hi.MachiningProcs.LocalProjectService.html#Hi_MachiningProcs_LocalProjectService_ClStrip">ClStrip</a>.<a class="xref" href="../../api/Hi.CutterLocations.ClStrips.ClStrip.html#Hi_CutterLocations_ClStrips_ClStrip_PosSelected">PosSelected</a>.</p>
<p>Show step infomation from <a class="xref" href="../../sample/Sample.Common.UserService.html#Sample_Common_UserService_DisplayedStepPresentAccessList">DisplayedStepPresentAccessList</a>.</p>
<p>The resx of <a class="xref" href="../../api/Hi.MachiningSteps.MachiningStep.html">MachiningStep</a> contains the translation of <a class="xref" href="../../api/Hi.MachiningSteps.PresentAttribute.html">PresentAttribute</a>.<a class="xref" href="../../api/Hi.MachiningSteps.PresentAttribute.html#Hi_MachiningSteps_PresentAttribute_Name">Name</a>, apply the translation to the GUI. If the translation not existed, use the original value.</p>
<p>See Also <a href="../preference/step-present-preference-page.html">Step Present Preference Page</a>.</p>
<p>Show step information from <code>UserService.DisplayedStepPresentAccessList</code>.</p>
<p>The resx of <a class="xref" href="../../api/Hi.MachiningSteps.MachiningStep.html">MachiningStep</a> contains the translation of <a class="xref" href="../../api/Hi.MachiningSteps.PresentAttribute.html">PresentAttribute</a>.<a class="xref" href="../../api/Hi.MachiningSteps.PresentAttribute.html#Hi_MachiningSteps_PresentAttribute_Name">Name</a>; apply the translation to the GUI, and fall back to the original value when no translation exists.</p>
<p>Which properties are listed is chosen on the <a class="xref" href="step-present-dialog.html">Step Present Preference Page</a>, opened from the <strong>Step Present</strong> button on this panel's title bar.</p>
<h2 id="layout">Layout</h2>
<p>A plain key / value list, not a <code>q-list</code>: the value cell has to shrink and wrap, because a step carries arbitrarily long values (a file path, a whole NC line) and a non-shrinking cell would widen the entire column.</p>
<p>Each row is labelled <code>shortName</code>, falling back to <code>name</code> and then to the raw key, with the full <code>name</code> as the row's tool-tip; the unit follows the value, and an absent value renders as <code>-</code>. With nothing selected the panel shows <em>No step selected. Click a step in the canvas to inspect its info.</em></p>
<p>The <code>StepIndex</code> row is filtered out even when the configured list contains it — the column's group bar already carries the step index as a badge.</p>
<h2 id="behavior">Behavior</h2>
<p>Selection bursts (a <code>SetSelectedPos</code> per step while a mission runs) are coalesced into one trailing fetch on an 80 ms timer, and responses are sequence-guarded so a slower earlier fetch cannot overwrite a newer one. A language change re-pulls, because the field names arrive already localized.</p>
<h2 id="sample-code">Sample Code</h2>
<p>Refer the code to show step infomation.</p>
<p>Refer to this code to show step information.</p>
<pre><code class="lang-csharp" name="SampleCode-ShowStepPresent">internal static void ShowStepPresent(
UserService userEnv, MachiningStep machiningStep)
{
@@ -105,21 +111,23 @@
Console.WriteLine($&quot;{present.ShortName}: {valueText} {present.TailUnitString} ({present.Name} [{entry.Key}])&quot;);
}
}
</code></pre><h2 id="signalr-implementation-webapi-only">SignalR Implementation (Webapi Only)</h2>
<p><code>SelectedStepInfoHub</code> provides real-time step updates with method <code>GetSelectedStepInfo()</code> and event <code>SelectedStepInfoUpdated</code>. <code>SelectedStepInfoService</code> monitors <a class="xref" href="../../api/Hi.CutterLocations.ClStrips.ClStrip.html#Hi_CutterLocations_ClStrips_ClStrip_PosSelected">PosSelected</a> and <a class="xref" href="../../api/Hi.CutterLocations.ClStrips.ClStrip.html#Hi_CutterLocations_ClStrips_ClStrip_MachiningStepSelected">MachiningStepSelected</a> events and broadcasts updates. The JavaScript component connects to <code>/selectedStepInfoHub</code> to receive step change notifications and update the UI accordingly.</p>
</code></pre><h2 id="selection-push-web-service-only">Selection Push (Web Service Only)</h2>
<p><code>SelectedStepInfoService</code> answers the payload on demand: the REST endpoint <code>GET /api/Execution/selected-step-info</code> calls it. It has <strong>no SignalR hub of its own</strong> — the &ldquo;selection changed&rdquo; push rides <code>/clStripHub</code>, whose <code>StepSelected</code> broadcast follows the same <a class="xref" href="../../api/Hi.CutterLocations.ClStrips.ClStrip.html#Hi_CutterLocations_ClStrips_ClStrip_PosSelected">PosSelected</a> event. The panel and the cycle-line charts watch that broadcast and re-pull this payload.</p>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a href="../index.html">this page</a> for git repository.</p>
<h3 id="wpf-application-source-code-path">WPF Application Source Code Path</h3>
<p>See <a class="xref" href="../index.html">HiNC App Anatomy</a> for git repository links.</p>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li>Play/SelectedStepInfoPanel</li>
<li><code>wwwroot-src/src/components/execution/SelectedStepInfoPanel.vue</code> — the panel.</li>
<li><code>wwwroot-src/src/components/execution/StepInfoGroupBar.vue</code> — the group bar above it, shared by the step-based panels of the column.</li>
<li><code>Execution/ExecutionController.cs</code><code>GET /api/Execution/selected-step-info</code>.</li>
<li><code>Execution/SelectedStepInfoService.cs</code> — the payload.</li>
<li><code>Execution/ClStripBroadcastService.cs</code> — the <code>StepSelected</code> broadcast that tells the panel to re-pull.</li>
</ul>
<h3 id="web-page-application-source-code-path">Web Page Application Source Code Path</h3>
<h2 id="see-also">See Also</h2>
<ul>
<li>wwwroot/player/selected-step-info-panel.js (Vue component)</li>
<li>wwwroot/player/selected-step-info-panel.css (Styles)</li>
<li>Players/PlayerController.cs (REST API - GetSelectedStepInfo endpoint)</li>
<li>Players/SelectedStepInfoService.cs (Business logic)</li>
<li>Players/SelectedStepInfoHub.cs (SignalR Hub for real-time updates)</li>
<li><a href="index.html">Execution Page</a> — the page whose Step Info column hosts this panel</li>
<li><a class="xref" href="step-present-dialog.html">Step Present Preference Page</a> — chooses which properties are listed</li>
<li><a href="../../manual/run/inspecting-a-step.html">Inspecting a Step</a> — the task this panel serves, and the rest of the column around it</li>
</ul>
</article>
@@ -0,0 +1,207 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Step Present Dialog | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Step Present Dialog | HiAPI-C# 2025 ">
<link rel="icon" href="../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../index.html">
<img id="logo" class="svg" src="../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-StepPresentDialog">
<h1 id="step-present-dialog">Step Present Dialog</h1>
<h2 id="shipped-surface">Shipped Surface</h2>
<p>The dialog has no route and no Control-Tree node. It is a modal on the Execution page (<code>/execution</code>), opened by the <code>tune</code> icon button on the Step Properties panel's title bar in the Step Info column — so it is reachable only while that column and that panel are shown. The button sits next to the list it configures.</p>
<p>In the WPF application the same editor is a window opened from the <a class="xref" href="../shell/preference-menu.html">Preference Menu Dropdown</a>.</p>
<p>The dialog's own view models are the two fetched values, <code>categories</code> and <code>displayedKeys</code>. The server models they mirror are <code>UserService.CandidateStepPresentKeyList</code> plus <code>UserService.StepPresentAccessDictionary</code> (the candidates) and <code>UserConfig.DisplayedStepPresentKeyList</code> (the ordered displayed list). The dialog's only prop is its open/closed flag.</p>
<h2 id="layout">Layout</h2>
<ul>
<li>Header
<ul>
<li>Title, then the counts — <em>n displayed</em> / <em>n available</em></li>
<li>The save-state line (<em>Saving…</em> / <em>Saved</em> / error), which stands in for a Save button</li>
<li><code>Reset</code> and close</li>
</ul>
</li>
<li>Splitter
<ul>
<li>Candidate Keys Panel (left)
<ul>
<li>One accordion per category, in the order the server sends them
<ul>
<li>A tri-state checkbox on the category header</li>
<li>One checkbox per key, with the key's label and unit</li>
</ul>
</li>
<li><code>Add Selected</code></li>
</ul>
</li>
<li>Displayed Keys Panel (right)
<ul>
<li>One row per displayed key, in <code>UserConfig.DisplayedStepPresentKeyList</code> order, drag-reorderable
<ul>
<li>Per-row up / down / remove</li>
</ul>
</li>
<li><code>Clear</code></li>
</ul>
</li>
</ul>
</li>
</ul>
<p>Rows are labelled from the per-key payload the server sends — <code>key</code>, <code>name</code>, <code>shortName</code>, <code>unit</code>. The visible label is <code>shortName</code>, falling back to <code>key</code>, with a unit appended in parentheses when it is not <code>None</code>. The tooltip carries the full <code>name</code> above the raw <code>key</code>.</p>
<h2 id="behavior">Behavior</h2>
<p>Adding is a two-step gesture on the left: tick candidates — individually, or a whole category through the header's tri-state checkbox — then press <code>Add Selected</code>. A key that is already displayed has its checkbox disabled and shows a check badge instead. Removing lives only on the right, per row or through <code>Clear</code>.</p>
<p>Every add, remove and reorder persists immediately: the dialog POSTs the whole ordered list, the server drops unknown keys and duplicates and answers with the effective list, and the dialog re-syncs to that answer. <code>Reset</code> is a DELETE that empties the list; both <code>Reset</code> and <code>Clear</code> go through a confirmation. A failure flips the header state to its error label and notifies; a failed list write additionally re-reads from the server, so the editor never drifts from persisted state.</p>
<p>The dialog fetches fresh on every open, and re-pulls when the UI language changes, because the category and key labels arrive already localized.</p>
<p>The list this dialog configures omits <code>StepIndex</code> even when it is displayed: the Step Info column's group bar already shows the step index as a badge, so the panel filters that one row out rather than repeating it.</p>
<h3 id="categories">Categories</h3>
<p>Categories are a GUI-level grouping of keys. Each ships as a stable code plus a display label: the code is the transport identity the client compares and keys its UI state on, the label is display text only. The seven, in canonical order:</p>
<ol>
<li><code>fileCmdFlagTimeSystem</code>&ldquo;File / Command / Flag / Time / System&rdquo; (<code>StepIndex</code>, <code>FileNo</code>, <code>LineNo</code>, <code>FilePath</code>, <code>LineText</code>, <code>FlagsText</code>, <code>EndTimecode</code>, <code>StepDuration</code>).</li>
<li><code>toolFeedrateSpindle</code>&ldquo;Tool / Feedrate / Spindle Speed&rdquo; (<code>ToolId</code>, <code>SpindleSpeed_rpm</code>, <code>Feedrate_mmdmin</code>, <code>CuttingSpeed_mmds</code>, …).</li>
<li><code>coordinateMove</code>&ldquo;Coordinate / Move&rdquo; (<code>Cl</code>, <code>MoveOnProgramCoordinate</code>, <code>MovingLength_mm</code>, and every <code>MC.</code> key).</li>
<li><code>cuttingGeometryChip</code>&ldquo;Cutting Geometry / Chip / Bias / Roughness&rdquo; (<code>CuttingDepth_mm</code>, <code>ChipThickness_um</code>, <code>Mrr_mm3ds</code>, …).</li>
<li><code>mechanicsPowerEnergy</code>&ldquo;Mechanics / Power / Energy&rdquo; (<code>MaxAbsForce_N</code>, <code>SpindleOutputPower_W</code>, <code>AvgAbsTorque_Nm</code>, …).</li>
<li><code>temperatureWear</code>&ldquo;Temperature / Wear&rdquo; (<code>ChipTemperature_C</code>, <code>AccumulatedFlankWearWidth_um</code>, …).</li>
<li><code>custom</code>&ldquo;Custom&rdquo;, the catch-all for every key the mapping does not place, including the keys registered at run time through <code>UserService.AdditionalStepPresentAccess</code>.</li>
</ol>
<p>All seven are always returned; nothing gates any of them, and the candidate list is read from <code>UserService.CandidateStepPresentKeyList</code> rather than from <a class="xref" href="../../api/Hi.MachiningSteps.MachiningStep.html">MachiningStep</a> directly, so runtime-registered keys are included.</p>
<p>Each client carries its own copy of the key → category mapping: <code>ResolveStepPresentCategory</code> on the webservice, <code>GetKeyCategory</code> in the WPF window. The two are kept in step by hand, with the seven category codes as the shared contract — adding a property means editing both.</p>
<h3 id="labels-and-localization">Labels and Localization</h3>
<p>The web app resolves <code>name</code> and <code>shortName</code> server-side for the requested language from the shipped step-present catalog, laid over the live <a class="xref" href="../../api/Hi.MachiningSteps.PresentAttribute.html">PresentAttribute</a>.<a class="xref" href="../../api/Hi.MachiningSteps.PresentAttribute.html#Hi_MachiningSteps_PresentAttribute_Name">Name</a> and <a class="xref" href="../../api/Hi.MachiningSteps.PresentAttribute.html#Hi_MachiningSteps_PresentAttribute_ShortName">ShortName</a> values — those are the English truth and the fallback for a missing catalog file or key. <a class="xref" href="../../api/Hi.MachiningSteps.PresentAttribute.html#Hi_MachiningSteps_PresentAttribute_Unit">Unit</a> is sent as-is and does not localize. The dialog pins the language with a <code>?lang=</code> argument on the request and re-pulls when the UI locale changes.</p>
<p>The WPF window resolves the same labels locally, through a resource manager over <a class="xref" href="../../api/Hi.MachiningSteps.MachiningStep.html">MachiningStep</a>; a key with no translation keeps its original value.</p>
<p>Refer to the code to apply <a class="xref" href="../../api/Hi.MachiningSteps.PresentAttribute.html">PresentAttribute</a>:</p>
<pre><code class="lang-csharp" name="SampleCode-ShowStepPresent">internal static void ShowStepPresent(
UserService userEnv, MachiningStep machiningStep)
{
foreach (var entry in userEnv.DisplayedStepPresentAccessList)
{
var present = entry.Value.Present;
var valueText = string.Format(&quot;{0:&quot; + present.DataFormatString + &quot;}&quot;, entry.Value.GetValueFunc.Invoke(machiningStep));
Console.WriteLine($&quot;{present.ShortName}: {valueText} {present.TailUnitString} ({present.Name} [{entry.Key}])&quot;);
}
}
</code></pre><h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="selected-step-info-panel.html">Selected-Step Info Panel</a> — the panel whose property list this page configures</li>
<li><a class="xref" href="../shell/preference-menu.html">Preference Menu Dropdown</a> — the dropdown that hosts this entry in the WPF client</li>
<li><a href="../../manual/run/inspecting-a-step.html">Inspecting a Step</a> — the task this dialog serves, as a procedure</li>
</ul>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../index.html">HiNC App Anatomy</a> for git repository links.</p>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/preference/StepPresentPreferenceDialog.vue</code> — the dialog itself: a Candidate Keys / Displayed Keys splitter, category accordions keyed on the stable category code, the per-category tri-state checkbox, <code>Add Selected</code>, drag-reorder plus up / down / remove and <code>Clear</code>, the header counts, <code>Reset</code> and close, and the auto-save state. Refetches on every open and on a locale change.</li>
<li><code>wwwroot-src/src/components/execution/SelectedStepInfoPanel.vue</code> — what opens it: the <code>tune</code> icon button teleported into the panel's title bar (the hosting expansion header, or the panel's own bar when standalone). Also the list the dialog configures, with the <code>StepIndex</code> row filtered out.</li>
<li><code>wwwroot-src/src/components/execution/StepInfoGroupBar.vue</code> — the Step Info column's group bar, which carries the step-index badge instead.</li>
<li><code>wwwroot-src/src/pages/ExecutionPage.vue</code> — the only surface the dialog is reachable from.</li>
<li><code>wwwroot-src/src/api/preference.ts</code><code>getStepPresentKeys</code> (language-pinned) / <code>setStepPresentKeys</code> / <code>resetStepPresentKeys</code>, and the <code>StepPresentKeyInfo</code> / <code>StepPresentCategory</code> / <code>StepPresentKeysSnapshot</code> types.</li>
<li><code>Environments/PreferenceController.cs</code><code>GET</code> / <code>POST</code> / <code>DELETE /api/preference/step-present-keys</code>, the seven-entry category table, the <code>ResolveStepPresentCategory</code> mapping, and persistence of <code>DisplayedStepPresentKeyList</code> through <code>SaveUserConfig()</code>.</li>
<li><code>Environments/PresentCatalogService.cs</code> — server-side localization of <code>name</code> and <code>shortName</code> from the shipped step-present catalog, with the live <a class="xref" href="../../api/Hi.MachiningSteps.PresentAttribute.html">PresentAttribute</a> data as the English base and fallback.</li>
<li><code>Environments/UserService.cs</code><code>StepPresentAccessDictionary</code> and <code>CandidateStepPresentKeyList</code>, the candidate-key model.</li>
<li><code>Environments/UserConfig.cs</code><code>DisplayedStepPresentKeyList</code>, the ordered displayed-key model.</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,219 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Strip Charts | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Strip Charts | HiAPI-C# 2025 ">
<link rel="icon" href="../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../index.html">
<img id="logo" class="svg" src="../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-StripCharts">
<h1 id="strip-charts">Strip Charts</h1>
<p>The strip charts visualise a <strong>windowed, downsampled view of the full mission timeline</strong> for physics / quality aspects that are computed per step across the whole NC program. They are rendered as min/max banded series over the <code>ClStrip.GetDispBegin() .. AbsDispEnd</code> window. Operators use them to spot which segments of the mission are loading the spindle, degrading surface quality, or hitting thermal limits.</p>
<p>They occupy the Strip Charts column of the <a href="index.html">Execution Page</a>. The anatomy covers three, all sharing <code>BaseStripChart.vue</code>:</p>
<table>
<thead>
<tr>
<th>Chart</th>
<th>Source Aspect</th>
<th>Items</th>
</tr>
</thead>
<tbody>
<tr>
<td>Availability Chart</td>
<td><code>Availability</code></td>
<td>Yielding-stress / max-spindle-torque / max-spindle-power / spindle-working-temperature / thermal-yield ratios (5 series).</td>
</tr>
<tr>
<td>Surface Roughness Chart</td>
<td><code>SurfaceRoughness</code></td>
<td>Re-cut depth + program-side cusp + Δ tip deflection X / Y / Z in µm (5 series).</td>
</tr>
<tr>
<td>Color Index Time Chart</td>
<td><code>Individual</code></td>
<td>One series of a user-picked <code>StepPropertyAccessDictionary</code> key (≈90 keys).</td>
</tr>
</tbody>
</table>
<p>Key Model: <a class="xref" href="../../api/Hi.MachiningProcs.MachiningProject.html">MachiningProject</a> via <code>LocalProjectService.ClStrip</code> + <code>LocalProjectService.StepPropertyAccessDictionary</code>.</p>
<h2 id="layout">Layout</h2>
<p>One group bar drives all three charts, and each chart is a collapsible panel below it.</p>
<ul>
<li>Group Bar
<ul>
<li>Range chip — <code>[{dispBegin}..{absDispEnd}] / {count}</code>, with <code>Showing steps {begin}..{end} of {count} total</code> as its tool-tip.</li>
<li>Cursor x readout — the hovered x value, shared by all three charts and reserving a fixed width so hovering never reflows the bar. It is the one place the hovered value is printed; the charts themselves carry no per-chart cursor row.</li>
<li>X-axis mode — <code>IndexByTime</code> (default) / <code>IndexByStep</code>, applied to every strip chart at once.</li>
<li>Fit View — fits the 3D canvas to the tool path / home position. Disabled without a rendering connection id, and the only control here that acts on something other than the charts.</li>
<li>Stick disp-end to live end — keeps the window pinned to the running end of the mission. The state <em>is</em> <code>dispEnd === -1</code>, so a pan or a wheel turns it off by writing a concrete end, and Reset re-arms it.</li>
<li>Reset display range — returns the window to the whole mission.</li>
<li>Reload all strip charts.</li>
</ul>
</li>
<li>Each chart panel
<ul>
<li>Header — title label; the aspect picker on the Color Index chart, a dropdown listing every <code>StepPropertyAccessDictionary</code> key with a <code>GetQuantityFunc</code>, sorted by <code>PresentAttribute.Name</code> and filtered by a type-to-search field; the Y-axis range editor (Fit / Lock / Symmetric with a ± bound); and, on the Color Index chart only, a <code>Colors</code> dropdown editing the colour guide's floor / ceiling / tone function. <strong>That editor drives the 3D workpiece colouring and the colour-scale bar, not the chart's own line.</strong></li>
<li>Body — <code>UplotChart.vue</code> rendering one min/max band pair per item (two stroked series of the same colour plus a filled band at 15% opacity), with uPlot's own legend panel in a splitter pane beside the plot. The legend carries the swatches, labels and live values; its width is shared by all three charts, so dragging one divider moves them together.</li>
<li>Empty-state overlay — in order: &ldquo;No project loaded&rdquo;; &ldquo;Pick a property to inspect&rdquo; (Color Index with no key chosen); &ldquo;No mission data&rdquo; when the fetch returned none; &ldquo;No physics data&rdquo; when every item's min / max arrays are all NaN, which is what Availability looks like without a physics licence.</li>
</ul>
</li>
</ul>
<h2 id="behavior">Behavior</h2>
<ul>
<li><strong>Windowed fetch.</strong> Only the <code>ClStrip.GetDispBegin() .. AbsDispEnd</code> window is fetched, at a bucket count matching the chart's pixel width. The server-side <code>ClStrip.GetMinMaxList(threshold, itemCount)</code> handles down-sampling.</li>
<li><strong>Pointer interactions</strong> — every one of them writes the shared <code>ClStrip</code>, so a gesture on one chart moves all three:
<ul>
<li><code>wheel</code> over the plot area → <code>POST /api/execution/cl-strip/wheel</code> with <code>scale = Math.pow(1.05, deltaY * 1/166)</code> + <code>xPositionPercentage</code>.</li>
<li>Right / middle-button drag → <code>POST /api/execution/cl-strip/pan</code> with accumulated <code>xOffsetPercentage</code>. Frame-batched via <code>requestAnimationFrame</code>. Pen drags also pan without a button modifier.</li>
<li><strong>Left-button drag → a rubber-band x-range zoom</strong>: a translucent band tracks the drag over the plot area and the release POSTs the swept fractions to <code>/api/execution/cl-strip/zoom-range</code>. A drag under 6 px, or under half a percent of the plot width, is discarded so the trailing click selects a step instead — which is what makes click-to-select survive an unsteady hand. The gesture is mouse-only.</li>
<li>Left click → <code>POST /api/execution/cl-strip/select-step</code> with the nearest bucket's original step index (via <code>chart.valToPos</code> reverse lookup). A click that ended a pan or a zoom drag is ignored.</li>
<li>Touch → one finger pans, a second finger converts the pan into an x-axis pinch-zoom; the trailing synthetic click is swallowed so panning past a step never selects it.</li>
<li>Mouse move (debounced 80 ms) → <code>POST /api/execution/cl-strip/enter-step</code> for hover, which is also what fills the group bar's cursor readout.</li>
</ul>
</li>
<li><strong>Live refresh.</strong> <code>useClStripHub</code> subscribes to <code>DispRangeChanged</code> (re-fetch when anyone else zooms / pans) and <code>Updated</code> (coalesced re-broadcast while a mission is running). A rising edge on the execution status hub's <code>hasProject</code> also triggers a refetch.</li>
<li><strong>Client-side debounce.</strong> <code>BaseStripChart.load()</code> is debounced 50 ms on the client so a burst of <code>Updated</code> events during a fast-running mission coalesces into one fetch.</li>
</ul>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../index.html">HiNC App Anatomy</a> for git repository links.</p>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/execution/charts/StripChartGroupBar.vue</code> — the group bar over the three charts.</li>
<li><code>wwwroot-src/src/components/execution/charts/BaseStripChart.vue</code> — shared strip-chart skeleton.</li>
<li><code>wwwroot-src/src/components/execution/charts/UplotChart.vue</code> — uplot wrapper.</li>
<li><code>wwwroot-src/src/components/execution/charts/PluralStripAvailabilityChart.vue</code> — availability chart.</li>
<li><code>wwwroot-src/src/components/execution/charts/PluralStripRoughnessChart.vue</code> — surface-roughness chart.</li>
<li><code>wwwroot-src/src/components/execution/charts/StripIndividualChart.vue</code> — aspect-picker individual chart.</li>
<li><code>wwwroot-src/src/api/clStrip.ts</code> — typed REST wrappers for the range calls.</li>
<li><code>wwwroot-src/src/composables/useClStripHub.ts</code> — singleton hub wrapper (the same ref-counting pattern as the other execution hubs).</li>
<li><code>Execution/ExecutionChartsController.cs</code> — strip-chart data:
<ul>
<li><code>GET /api/execution/strip-chart?aspect=Availability|SurfaceRoughness|Individual&amp;xValueCategory=IndexByTime|IndexByStep&amp;widthHint=…&amp;dispBegin=…&amp;dispEnd=…&amp;inspectingKey=…</code><code>{ xs, indexes, items:[{ key, label, unit, min, max }], isXTicksRangeMode, dispBegin, absDispEnd, count }</code>. The Color Index chart's picked key rides the <code>inspectingKey</code> query parameter of that same call.</li>
<li><code>GET /api/execution/strip-chart-item-config</code> and <code>GET /api/execution/color-guide</code> — the per-item display config and the colour legend.</li>
</ul>
</li>
<li><code>Execution/ClStripController.cs</code> — range manipulation at <code>/api/execution/cl-strip/*</code> (the server-side counterparts of the pointer handlers above):
<ul>
<li><code>GET range</code> — snapshot <code>{ dispBegin, absDispEnd, count, selectedIndex, enteredIndex }</code>.</li>
<li><code>POST disp-range</code> body <code>{ dispBegin, dispEnd }</code>, and <code>POST zoom-range</code>.</li>
<li><code>POST wheel</code> body <code>{ scale, xPositionPercentage, xValueCategory }</code>.</li>
<li><code>POST pan</code> body <code>{ xOffsetPercentage, xValueCategory }</code>. Legacy <code>feelingRatio=2</code>.</li>
<li><code>POST select-step</code> body <code>{ stepIndex }</code><code>ClStrip.SetSelectedPos(...)</code>.</li>
<li><code>POST enter-step</code> body <code>{ stepIndex }</code> (nullable) → <code>ClStrip.SetEnteredPos(...)</code>.</li>
</ul>
</li>
<li><code>Execution/ClStripHub.cs</code> + <code>Execution/ClStripBroadcastService.cs</code> — SignalR at <code>/clStripHub</code>. Broadcasts <code>DispRangeChanged(snapshot)</code>, <code>StepSelected({ stepIndex })</code>, <code>StepEntered({ stepIndex })</code>, <code>Updated(snapshot)</code>. A single-flight <code>Interlocked</code> mutex coalesces re-broadcasts; the client debounce handles the rest of the rate-limiting.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a href="index.html">Execution Page</a> — the page whose Strip Charts column hosts these charts</li>
<li><a href="cycle-line-charts.html">Cycle-Line Charts</a> — per-selected-step charts that share the same <code>uplot</code> engine</li>
<li><a href="execution-extended-renderingcanvas-tool-bar.html">Execution Extended RenderingCanvas Tool Bar</a> — the tool-path display this column's Fit View acts on</li>
<li><a href="../../manual/run/watching-the-run.html">Watching the Run</a> — the task this column serves, with the pointer contract read as a procedure</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,49 @@
<div id="sidetoggle">
<div>
<div class="sidefilter">
<form class="toc-filter">
<span class="glyphicon glyphicon-filter filter-icon"></span>
<span class="glyphicon glyphicon-remove clear-icon" id="toc_filter_clear"></span>
<input type="text" id="toc_filter_input" placeholder="Filter by title" onkeypress="if(event.keyCode==13) {return false;}">
</form>
</div>
<div class="sidetoc">
<div class="toc" id="toc">
<ul class="nav level1">
<li>
<a href="index.html" name="" title="Overview">Overview</a>
</li>
<li>
<a href="execution-tool-bar.html" name="" title="Execution Tool Bar">Execution Tool Bar</a>
</li>
<li>
<a href="execution-extended-renderingcanvas-tool-bar.html" name="" title="Execution Extended RenderingCanvas Tool Bar">Execution Extended RenderingCanvas Tool Bar</a>
</li>
<li>
<a href="selected-step-info-panel.html" name="" title="Selected-Step Info Panel">Selected-Step Info Panel</a>
</li>
<li>
<a href="strip-charts.html" name="" title="Strip Charts">Strip Charts</a>
</li>
<li>
<a href="cycle-line-charts.html" name="" title="Cycle-Line Charts">Cycle-Line Charts</a>
</li>
<li>
<a href="graphic-cache-menu.html" name="" title="Graphic-Cache Menu">Graphic-Cache Menu</a>
</li>
<li>
<a href="step-present-dialog.html" name="" title="Step Present Dialog">Step Present Dialog</a>
</li>
<li>
<a href="mission/index.html" name="mission/toc.html" title="Mission">Mission</a>
</li>
<li>
<a href="program.html" name="" title="Program Branch">Program Branch</a>
</li>
</ul>
</div>
</div>
</div>
</div>
@@ -0,0 +1,2 @@
{"items":[{"name":"Overview","href":"index.html","topicHref":"index.html"},{"name":"Execution Tool Bar","href":"execution-tool-bar.html","topicHref":"execution-tool-bar.html"},{"name":"Execution Extended RenderingCanvas Tool Bar","href":"execution-extended-renderingcanvas-tool-bar.html","topicHref":"execution-extended-renderingcanvas-tool-bar.html"},{"name":"Selected-Step Info Panel","href":"selected-step-info-panel.html","topicHref":"selected-step-info-panel.html"},{"name":"Strip Charts","href":"strip-charts.html","topicHref":"strip-charts.html"},{"name":"Cycle-Line Charts","href":"cycle-line-charts.html","topicHref":"cycle-line-charts.html"},{"name":"Graphic-Cache Menu","href":"graphic-cache-menu.html","topicHref":"graphic-cache-menu.html"},{"name":"Step Present Dialog","href":"step-present-dialog.html","topicHref":"step-present-dialog.html"},{"name":"Mission","href":"mission/index.html","tocHref":"mission/toc.html","topicHref":"mission/index.html","homepage":"mission/index.html"},{"name":"Program Branch","href":"program.html","topicHref":"program.html"}]}
@@ -0,0 +1,276 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Background / Coolant | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Background / Coolant | HiAPI-C# 2025 ">
<link rel="icon" href="../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../index.html">
<img id="logo" class="svg" src="../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-BackgroundCoolant">
<h1 id="background--coolant">Background / Coolant</h1>
<p>Background and Coolant are two leaves of the General Setup page's Control Tree
(<code>/general-setup?tree=equipment/background</code>, <code>/general-setup?tree=equipment/coolant</code>), sitting
directly under the General Setup group between Spindle Capability and Fixture. They are two tree
items rather than a page, so the older <code>/equipment/background-coolant</code> path redirects onto the
Background leaf. Both are served by one panel component, which branches on the selected node's role
path.</p>
<p>They edit the thermal condition on the project's authored equipment face,
<a class="xref" href="../../api/Hi.Machining.MachiningEquipmentUtils.SetupEquipment.html">SetupEquipment</a>, reached as
<a class="xref" href="../../api/Hi.MachiningProcs.LocalProjectService.html#Hi_MachiningProcs_LocalProjectService_SetupEquipment">SetupEquipment</a>:</p>
<ul>
<li><a class="xref" href="../../api/Hi.Machining.MachiningEquipmentUtils.SetupEquipment.html#Hi_Machining_MachiningEquipmentUtils_SetupEquipment_BackgroundTemperature_C">BackgroundTemperature_C</a> (Background
leaf)</li>
<li>the whole <a class="xref" href="../../api/Hi.Physics.CoolantHeatCondition.html">CoolantHeatCondition</a> (Coolant leaf) — file-first, see below</li>
</ul>
<p>Key Model: <a class="xref" href="../../api/Hi.Machining.MachiningEquipmentUtils.SetupEquipment.html">SetupEquipment</a> (+ its
<a class="xref" href="../../api/Hi.Machining.MachiningEquipmentUtils.SetupEquipment.html#Hi_Machining_MachiningEquipmentUtils_SetupEquipment_CoolantHeatCondition">CoolantHeatCondition</a>).</p>
<div class="NOTE">
<h5>Note</h5>
<p>The WPF desktop app has no surface for either value — no page, no panel, and no handler for the
<code>.CoolantHeatCondition</code> extension. There, both are whatever the project XML carries on the
equipment face, or the class defaults when the XML says nothing.</p>
</div>
<h2 id="layout">Layout</h2>
<ul>
<li>Background leaf — <code>equipment/background</code>, item type <code>ThermalCondition</code>
<ul>
<li>Background Temperature NumberField (°C) —
<a class="xref" href="../../api/Hi.Machining.MachiningEquipmentUtils.SetupEquipment.html#Hi_Machining_MachiningEquipmentUtils_SetupEquipment_BackgroundTemperature_C">BackgroundTemperature_C</a>.</li>
</ul>
</li>
<li>Coolant leaf — <code>equipment/coolant</code>, the same item type and the same panel (file pick on top,
properties below, as on the workpiece material leaf)
<ul>
<li>&ldquo;Not attached&rdquo; badge — visible until the first coolant-related save (see <em>Lazy
CoolantHeatCondition creation</em> below).</li>
<li><strong>File row</strong><code>FilePathInput</code> (<code>.CoolantHeatCondition</code>, resource subfolder
<code>CoolantHeatCondition</code>): a <strong>Select</strong> dropdown button whose menu holds <strong>Browse&hellip;</strong>,
<strong>Browse Resource&hellip;</strong> and — only while a file is set — <strong>Clear</strong>, beside a read-only path
input; it loads a saved condition by named root. A <strong>Save As&hellip;</strong> button follows it, prompting
for a project-relative file to export the current condition to. Tracked in
<a class="xref" href="../../api/Hi.Machining.MachiningEquipmentUtils.SetupEquipment.html#Hi_Machining_MachiningEquipmentUtils_SetupEquipment_CoolantHeatConditionFile">CoolantHeatConditionFile</a>. The three
standard cooling types ship as ready-made files in <code>HiNc-Resource</code>
(<code>Resource/CoolantHeatCondition/StandardForcedAir.default.CoolantHeatCondition</code>,
<code>StandardOilBasedCoolant.default.CoolantHeatCondition</code> and
<code>StandardWaterSolubleCoolant.default.CoolantHeatCondition</code>), so <strong>Browse Resource&hellip;</strong> starts
populated like the workpiece-material picker — picking a shipped file <em>is</em> the cooling-type
selection.</li>
<li>Name / Note — read-only mirror of the loaded condition.</li>
<li>Property fields (always visible, under Name / Note):
<ul>
<li>Coolant Temperature (°C) — <code>CoolantTemperature_C</code>.</li>
<li>Flood Convection Coefficient (W/(m²·K)) — <code>CoolantConvectionCoefficient_Wdm2K</code>, <code>min: 0</code>.</li>
<li>Mist / Flood Ratio — <code>MistFloodConvectionRatio</code>, <code>min: 0</code>.</li>
<li>Off (Air) Convection Coefficient (W/(m²·K)) — <code>OffConvectionCoefficient_Wdm2K</code>, <code>min: 0</code>.</li>
</ul>
</li>
</ul>
</li>
<li>Both leaves, with no project open: every control is disabled and a &ldquo;no project — open one first&rdquo;
caption is shown. The panel fetches the snapshot itself on mount and gates on its <code>hasEquipment</code>
flag, so the tree host holds no state for either leaf.</li>
</ul>
<h2 id="behavior">Behavior</h2>
<ul>
<li><strong>File-based conditions (WorkpieceMaterial pattern).</strong> Picking a <code>.CoolantHeatCondition</code> file
installs it as the whole condition (<code>XFactory.GenByFile</code>); <strong>Save As&hellip;</strong> exports the current
values and renames the condition after the file (<code>IPreferredFileName</code>). While
<a class="xref" href="../../api/Hi.Machining.MachiningEquipmentUtils.SetupEquipment.html#Hi_Machining_MachiningEquipmentUtils_SetupEquipment_CoolantHeatConditionFile">CoolantHeatConditionFile</a> is set, the
project save externalizes the condition as
<code>&lt;CoolantHeatCondition&gt;&lt;XmlSource&gt;relPath&lt;/XmlSource&gt;&lt;/CoolantHeatCondition&gt;</code> and regenerates the
side-file (a resource pick is copied into the project on save); with no file the bare inline shape
is kept byte-compatible. Coefficient edits tune <em>this project's</em> copy. The static presets on
<a class="xref" href="../../api/Hi.Physics.CoolantHeatCondition.html">CoolantHeatCondition</a> (<code>StandardPresets</code> / <code>ApplyPreset</code> /
<code>MatchStandardPreset</code>) are the API-side source the shipped files are authored from.</li>
<li><strong>The file row's Clear entry does nothing here.</strong> <code>FilePathInput</code> reports a clear by emitting
<code>update:modelValue(null)</code>, and this panel binds the widget's value one-way and listens only for
<code>picked</code> — so the entry is rendered, is clickable while a file is set, and drops nothing. Clearing
the tracked file is therefore not reachable from this leaf at all; the reference changes only by
loading another file or by a Save As.</li>
<li><strong>The <code>.default</code> marker never survives a user save.</strong> The shipped resource files carry a
<code>.default</code> ownership marker in their names. The Save As&hellip; prompt strips it from its prefill, and
the controller strips it again from the name it stamps on the condition, so a user save cannot
mint a file that masquerades as a shipped default.</li>
<li><strong>The &ldquo;not attached&rdquo; badge is unreachable, and the lazy creation behind it never fires.</strong>
<a class="xref" href="../../api/Hi.Machining.MachiningEquipmentUtils.SetupEquipment.html">SetupEquipment</a> initializes
<a class="xref" href="../../api/Hi.Machining.MachiningEquipmentUtils.SetupEquipment.html#Hi_Machining_MachiningEquipmentUtils_SetupEquipment_CoolantHeatCondition">CoolantHeatCondition</a> at its declaration
and nothing assigns null to it — the project-XML read replaces the instance only when the element
is present — so the controller's <code>hasCoolant</code>, which is a null test on that property, is true
whenever a project is open. The badge is therefore rendered only in the no-equipment case the
panel already covers with its own caption, and the null branch each scalar PUT carries is
defensive rather than reached. What a fresh project actually carries is the class defaults, which
are the water-soluble preset's values.</li>
<li><strong>Celsius is the wire format.</strong> <a class="xref" href="../../api/Hi.Physics.CoolantHeatCondition.html">CoolantHeatCondition</a> stores Kelvin internally
but exposes <code>_C</code> accessors that handle the conversion, and the DTO carries Celsius.</li>
<li><strong>Finite values only.</strong> Every field handler drops a null or non-finite entry before any request is
made, so <code>Infinity</code> never reaches these endpoints even though the numeric widget can parse it.
Background and coolant temperatures accept negatives (e.g. -40 °C for cryogenic coolant); the
three convection fields are clamped to <code>min: 0</code>.</li>
<li><strong>The run sees it at once.</strong> The background-temperature write, all four condition scalars and the
file load each end in the project service's ForwardSetupEnvironmentToExecution, which stamps the
background temperature and the condition reference onto the runtime equipment face — including
the lazily created condition, which otherwise would exist only on the authored side.</li>
</ul>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../index.html">HiNC App Anatomy</a> for git repository links.</p>
<h3 id="web-application">Web Application</h3>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/controlTree/ThermalConditionPanel.vue</code> — the one panel both leaves
share, branching on the node's last role-path segment: Background renders the single temperature
field, Coolant the not-attached badge, the file row, the read-only Name / Note and the four
property fields.</li>
<li><code>wwwroot-src/src/components/controlTree/useControlTreeHost.ts</code> — declares the two
<code>ThermalCondition</code> leaves (<code>equipment/background</code>, <code>equipment/coolant</code>) under the General Setup
group, with no host-level init state.</li>
<li><code>wwwroot-src/src/components/controlTree/itemTypes.ts</code> — registers the <code>ThermalCondition</code> item type
against that panel: no child tree, no geometry-cache chain.</li>
<li><code>wwwroot-src/src/components/widgets/FilePathInput.vue</code> — the file row widget: Browse&hellip; /
Browse Resource&hellip; (opening at the <code>CoolantHeatCondition</code> resource subfolder) / Clear.</li>
<li><code>wwwroot-src/src/components/widgets/NumericInput.vue</code> — the numeric field used by every value on
both leaves, and the source of the <code>min: 0</code> clamp on the three convection fields.</li>
<li><code>wwwroot-src/src/api/backgroundCoolant.ts</code> — typed client for <code>/api/mech/background-coolant/*</code>:
the flat snapshot, the load / save-as pair, and the five scalar PUTs.</li>
<li><code>wwwroot-src/src/router/routes.ts</code> — carries the <code>equipment/background-coolant</code> redirect, which
lands on <code>general-setup?tree=equipment/background</code>.</li>
<li><code>Mech/BackgroundCoolantController.cs</code> — REST surface at <code>/api/mech/background-coolant</code> over the
authored equipment face:
<ul>
<li><code>GET /</code> — flat snapshot <code>{ hasEquipment, backgroundTemperature_C, hasCoolant, coolantTemperature_C, coolantConvectionCoefficient_Wdm2K, mistFloodConvectionRatio, offConvectionCoefficient_Wdm2K, coolantName, coolantNote, coolantHeatConditionFile }</code>.</li>
<li><code>POST /load</code>, <code>POST /save-as</code><code>{ rootName, relFile }</code>: install a <code>.CoolantHeatCondition</code> file
/ export the current condition. Both resolve the path under the named root and refuse anything
that escapes it, and both record the result in
<a class="xref" href="../../api/Hi.Machining.MachiningEquipmentUtils.SetupEquipment.html#Hi_Machining_MachiningEquipmentUtils_SetupEquipment_CoolantHeatConditionFile">CoolantHeatConditionFile</a> as a
project-relative path when it lands under the project. Save-as appends the extension when it is
missing and renames the condition after the file (<code>IPreferredFileName</code>) with the <code>.default</code>
marker stripped.</li>
<li><code>PUT /background-temperature-c</code> — set
<a class="xref" href="../../api/Hi.Machining.MachiningEquipmentUtils.SetupEquipment.html#Hi_Machining_MachiningEquipmentUtils_SetupEquipment_BackgroundTemperature_C">BackgroundTemperature_C</a>.</li>
<li><code>PUT /coolant-temperature-c</code>, <code>PUT /coolant-convection-coefficient-wdm2k</code>,
<code>PUT /mist-flood-convection-ratio</code>, <code>PUT /off-convection-coefficient-wdm2k</code> — the four condition
scalars, all through one helper that lazy-creates the condition when absent, writes it, and
forwards to the runtime face.</li>
</ul>
</li>
</ul>
<h3 id="hiapi-engine">HiAPI Engine</h3>
<ul>
<li><code>HiMech/Physics/CoolantHeatCondition.cs</code> — the model: Kelvin storage with <code>_C</code> accessors, the
flood / mist-ratio / off convection coefficients, Name and Note with <code>PreferredFileName</code>, and the
<code>StandardPresets</code> / <code>ApplyPreset</code> / <code>MatchStandardPreset</code> statics behind the shipped resource
files.</li>
<li><code>HiMech/Machining/MachiningEquipmentUtils/SetupEquipment.cs</code> — the authored equipment face that
owns the background temperature, the condition and its file reference, and externalizes the
condition to the side-file when that reference is set.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="spindle-capability.html">Spindle Capability</a> — sibling equipment editor, editing the same authored equipment face.</li>
<li><a href="../../manual/setup/coolant.html">Coolant (manual)</a> — the end-user task: picking a cooling type in the application.</li>
<li><a class="xref" href="../../technique/milling-physics/coolant-model.html">Coolant Model</a> — what the values on this panel mean, which one a running program applies, and how the condition is stored.</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,579 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Brand Matrix | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Brand Matrix | HiAPI-C# 2025 ">
<link rel="icon" href="../../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../../public/docfx.min.css">
<link rel="stylesheet" href="../../../public/main.css">
<meta name="docfx:navrel" content="../../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../../index.html">
<img id="logo" class="svg" src="../../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-ControllerBrandMatrix">
<h1 id="brand-matrix">Brand Matrix</h1>
<p>The Controller branch is the one branch of the equipment tree whose node set is not fixed: it is
grown from a snapshot of the active NC runner, and a node whose backing dependency the runner does
not resolve is never created at all. This page is the index of every node that branch can grow, and
it owns no node of its own — the ids it lists sit under <code>?tree=equipment/controller</code> on
<code>/general-setup</code>, on two plane stems, <code>equipment/controller/machine</code> and
<code>equipment/controller/program-data</code>.</p>
<div class="IMPORTANT">
<h5>Important</h5>
<p>Anatomy makes no claim that any id named here is stable across versions. The migration hop that
keeps an older <code>?tree=</code> value resolving is described in
<a class="xref" href="../../platform/tree-ids-and-routes.html">Tree Ids and Routes</a>.</p>
</div>
<h2 id="what-decides-the-shape">What Decides the Shape</h2>
<p>One request returns a flat snapshot of the active runner: the brand marker, the chain-driven axis
list, and a set of presence flags. Each flag is a type probe over the runner's
proxy-resolved dependency list — <a class="xref" href="../../../api/Hi.NcParsers.SoftNcRunner.html#Hi_NcParsers_SoftNcRunner_GetEffectiveNcDependencyList_">GetEffectiveNcDependencyList</a><small>(API)</small>,
so what is probed is the concrete table a proxy stands in for, never the proxy. Every gate the tree
reads tests a single type; two of the fields no component reads test a pair instead. The branch
builder reads that snapshot once per build and pushes a node for each flag that reads true. A node
that fails its gate is absent, not hidden and not disabled; the branch is regrown from a fresh
snapshot whenever a panel swaps the runner. How a branch is built, rebuilt and selected is the Control-Tree
engine's own subject — see <a class="xref" href="../../platform/control-tree.html">Control Tree</a>.</p>
<p>Five brands ship as presets: Fanuc, Siemens, Heidenhain, Syntec and Mazak. Each is a property on the
engine's runner type returning a fresh instance whose dependency list is written out literally, so
what a brand grows is decided by that list and by nothing else.</p>
<p>The snapshot also carries one non-boolean field that changes a panel rather than the tree.
<code>workCoordinateKind</code> names which storage model backs the work coordinates, by a type switch over the
first <a class="xref" href="../../../api/Hi.NcParsers.Dependencys.IIsoCoordinateConfig.html">IIsoCoordinateConfig</a> in the effective list: <code>fanuc</code> on Fanuc
and Mazak, <code>syntec</code> on Syntec, <code>siemens</code> on Siemens (the frame table precedes the machine-data
table, which is not an ISO coordinate provider), <code>heidenhain</code> on Heidenhain, and <code>none</code> when nothing
resolves. Its <code>generic</code> arm answers for the brand-neutral
<a class="xref" href="../../../api/Hi.NcParsers.Dependencys.Generic.IsoCoordinateTable.html">IsoCoordinateTable</a>, which no brand preset carries — it is
reachable only from a runner that already holds one, so the arm is defensive rather than dead.</p>
<h2 id="the-fixed-core">The Fixed Core</h2>
<p>Both plane stems are the builder's return value rather than gated entries, so they appear together
whenever a runner resolves, and neither is ever empty: each carries leaves that no flag guards.
Both are <code>Group</code> stems, so each renders an intro line and a clickable child list rather than a field
editor.</p>
<p>The machine plane's ungated leaves are Controller Brand, Machine Limits (Stroke), Rapid Feedrates,
Home / G28 Reference, Tool-Change Position and Controller Parameters. The program-data plane's are
Work Coordinates (G54…) and Tool Offsets.</p>
<p>Nothing in that core is gated, so no dependency decides whether those leaves exist — the builder
pushes them whatever resolves. What the preset lists decide is whether they have anything to show,
and on every brand they do.
<a class="xref" href="../../../api/Hi.NcParsers.Dependencys.ControllerParameterTableBase.html">ControllerParameterTableBase</a> declares the machine-config interfaces
itself — home reference, axis set, rapid feedrates, stroke limits, spindle control, M-code
declarations and the tool-change trigger — and every brand's parameter table derives from it, which
covers the limits, rapid, home and parameters leaves at once. The other two read dependencies each
preset carries in its own right: <a class="xref" href="../../../api/Hi.NcParsers.Dependencys.CncBrandDependency.html">CncBrandDependency</a> behind
Controller Brand, and <a class="xref" href="../../../api/Hi.NcParsers.Dependencys.Generic.ToolingMcConfig.html">ToolingMcConfig</a> behind Tool-Change
Position. On the program-data plane, every preset proxies the generic
<a class="xref" href="../../../api/Hi.NcParsers.Dependencys.Generic.ToolOffsetTable.html">ToolOffsetTable</a>, and each brand supplies an ISO coordinate
provider of its own.</p>
<p>The axis names the per-axis leaves list are the parameter table's own axis entries, which the
machining chain adds to and re-types when a machine tool is attached — it sets a type per chain axis
and removes nothing, so an attached chain yields the union of the two sets rather than a
replacement. Each brand's default table already seeds a linear X, Y and Z, so those rows are
populated before any chain is wired.</p>
<h2 id="with-no-runner">With No Runner</h2>
<p>The builder returns an empty child list, so both stems and every leaf disappear and only the branch
root survives. Its editor is the runner root panel, which then shows an hourglass over &ldquo;No NC
controller runner on this project.&rdquo; and a hint pointing at the ⋮ menu's Load command; the brand
badge is dropped and the file caption reads &ldquo;No NC runner&rdquo;.</p>
<p>The same empty list is returned when the snapshot request throws, so a failed fetch and a genuine
absence produce an identical tree shape, and the root panel renders the same hourglass block for
both. What separates them is transient and comes from elsewhere: the shared snapshot's own failed
read resets it to the empty value and raises an error toast. That read is a separate request from
the builder's, so the two can disagree — a branch collapsed by a thrown build fetch can sit under a
root panel still showing the brand badge of a snapshot that loaded.</p>
<p>Every leaf panel still opens with its own guard on the same flag, rendering the shared empty body
&ldquo;No NC runner — load a project first.&rdquo; Since a leaf node is built only when the snapshot already
reported a runner, that guard is a live defence against the shared snapshot emptying under a
mounted panel — closing the project does exactly that — rather than a state reached by navigating.</p>
<h2 id="the-matrix">The Matrix</h2>
<p>Keyed on the node id, because one tree label is itself conditional: the generic tool-offsets leaf is
relabelled when the Siemens <code>$TC_DP</code> table resolves, so an index keyed on labels would be
conditional on the very thing it indexes. Rows are in the order the builder pushes them.</p>
<p>Machine-plane rows name the page that documents the node. The program-data leaves have no page of
their own, so those rows point at the Layout section below, which names every one of them in tree
order.</p>
<table>
<thead>
<tr>
<th>Node id</th>
<th>Tree label</th>
<th>Gate</th>
<th>Dependency probed</th>
<th>Brands</th>
<th>Documented by</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>equipment/controller/machine/brand</code></td>
<td>Controller Brand</td>
<td>always</td>
<td></td>
<td>all five</td>
<td><a class="xref" href="machine/brand-switch.html">Controller Brand</a></td>
</tr>
<tr>
<td><code>equipment/controller/machine/limits</code></td>
<td>Machine Limits (Stroke)</td>
<td>always</td>
<td></td>
<td>all five</td>
<td><a class="xref" href="machine/per-axis-tables.html">Per-Axis Tables</a></td>
</tr>
<tr>
<td><code>equipment/controller/machine/rapid</code></td>
<td>Rapid Feedrates</td>
<td>always</td>
<td></td>
<td>all five</td>
<td><a class="xref" href="machine/per-axis-tables.html">Per-Axis Tables</a></td>
</tr>
<tr>
<td><code>equipment/controller/machine/home</code></td>
<td>Home / G28 Reference</td>
<td>always</td>
<td></td>
<td>all five</td>
<td><a class="xref" href="machine/per-axis-tables.html">Per-Axis Tables</a></td>
</tr>
<tr>
<td><code>equipment/controller/machine/tool-change</code></td>
<td>Tool-Change Position</td>
<td>always</td>
<td></td>
<td>all five</td>
<td><a class="xref" href="machine/per-axis-tables.html">Per-Axis Tables</a></td>
</tr>
<tr>
<td><code>equipment/controller/machine/parameters</code></td>
<td>Controller Parameters</td>
<td>always</td>
<td></td>
<td>all five</td>
<td><a class="xref" href="machine/interface-parameters.html">Interface Parameters</a></td>
</tr>
<tr>
<td><code>equipment/controller/machine/m-codes</code></td>
<td>M-Code Declarations</td>
<td><code>hasNativeTable</code></td>
<td><a class="xref" href="../../../api/Hi.NcParsers.Dependencys.ControllerParameterTableBase.html">ControllerParameterTableBase</a></td>
<td>all five</td>
<td><a class="xref" href="machine/m-code-declarations.html">M-Code Declarations</a></td>
</tr>
<tr>
<td><code>equipment/controller/machine/canned-cycle</code></td>
<td>Canned Cycle (Peck)</td>
<td><code>hasCannedCycle</code></td>
<td><a class="xref" href="../../../api/Hi.NcParsers.Dependencys.ICannedCycleConfig.html">ICannedCycleConfig</a></td>
<td>all five</td>
<td><a class="xref" href="machine/interface-parameters.html">Interface Parameters</a></td>
</tr>
<tr>
<td><code>equipment/controller/machine/block-skip</code></td>
<td>Block Skip / Delete</td>
<td><code>hasBlockSkip</code></td>
<td><a class="xref" href="../../../api/Hi.NcParsers.Dependencys.IBlockSkipConfig.html">IBlockSkipConfig</a></td>
<td>Fanuc, Siemens, Syntec, Mazak</td>
<td><a class="xref" href="machine/program-reading.html">Program Reading</a></td>
</tr>
<tr>
<td><code>equipment/controller/machine/subprograms</code></td>
<td>Subprogram Folders</td>
<td><code>hasSubprogramFolders</code></td>
<td><a class="xref" href="../../../api/Hi.NcParsers.Dependencys.Generic.SubProgramFolderConfig.html">SubProgramFolderConfig</a></td>
<td>all five</td>
<td><a class="xref" href="machine/program-reading.html">Program Reading</a></td>
</tr>
<tr>
<td><code>equipment/controller/machine/indexing-positions</code></td>
<td>Indexing Position Tables</td>
<td><code>hasIndexingTables</code></td>
<td><a class="xref" href="../../../api/Hi.NcParsers.Dependencys.Siemens.SiemensMachineDataTable.html">SiemensMachineDataTable</a></td>
<td>Siemens</td>
<td><a class="xref" href="machine/indexing-positions.html">Indexing Position Tables</a></td>
</tr>
<tr>
<td><code>equipment/controller/machine/native</code></td>
<td>Parameters (Native)</td>
<td><code>hasNativeTable</code></td>
<td><a class="xref" href="../../../api/Hi.NcParsers.Dependencys.ControllerParameterTableBase.html">ControllerParameterTableBase</a></td>
<td>all five</td>
<td><a class="xref" href="machine/native-parameters.html">Native Parameters</a></td>
</tr>
<tr>
<td><code>equipment/controller/program-data/work-coordinates</code></td>
<td>Work Coordinates (G54…)</td>
<td>always</td>
<td></td>
<td>all five</td>
<td>Layout below</td>
</tr>
<tr>
<td><code>equipment/controller/program-data/tool-offsets</code></td>
<td>Tool Offsets, read as Tool Offsets (ISO G43 H) under <code>hasSiemensToolOffsets</code></td>
<td>always; label switched by <code>hasSiemensToolOffsets</code></td>
<td></td>
<td>all five</td>
<td>Layout below</td>
</tr>
<tr>
<td><code>equipment/controller/program-data/siemens-tool-offsets</code></td>
<td>Tool Offsets ($TC_DP)</td>
<td><code>hasSiemensToolOffsets</code></td>
<td><a class="xref" href="../../../api/Hi.NcParsers.Dependencys.Siemens.SiemensToolOffsetTable.html">SiemensToolOffsetTable</a></td>
<td>Siemens</td>
<td>Layout below</td>
</tr>
<tr>
<td><code>equipment/controller/program-data/tool-names</code></td>
<td>Tool Names</td>
<td><code>hasSiemensToolOffsets</code></td>
<td><a class="xref" href="../../../api/Hi.NcParsers.Dependencys.Siemens.SiemensToolOffsetTable.html">SiemensToolOffsetTable</a></td>
<td>Siemens</td>
<td>Layout below</td>
</tr>
<tr>
<td><code>equipment/controller/program-data/datum-presets</code></td>
<td>Datum Presets (Q339)</td>
<td><code>hasDatums</code></td>
<td><a class="xref" href="../../../api/Hi.NcParsers.Dependencys.HeidenhainDatumTable.html">HeidenhainDatumTable</a></td>
<td>Heidenhain</td>
<td>Layout below</td>
</tr>
<tr>
<td><code>equipment/controller/program-data/datum-shifts</code></td>
<td>Datum Shifts (D)</td>
<td><code>hasDatums</code></td>
<td><a class="xref" href="../../../api/Hi.NcParsers.Dependencys.HeidenhainDatumTable.html">HeidenhainDatumTable</a></td>
<td>Heidenhain</td>
<td>Layout below</td>
</tr>
<tr>
<td><code>equipment/controller/program-data/frames</code></td>
<td>Frames (Siemens)</td>
<td><code>hasFrames</code></td>
<td><a class="xref" href="../../../api/Hi.NcParsers.Dependencys.Siemens.SiemensFrameTable.html">SiemensFrameTable</a></td>
<td>Siemens</td>
<td>Layout below</td>
</tr>
<tr>
<td><code>equipment/controller/program-data/retained-variables</code></td>
<td>Retained Common Variables</td>
<td><code>hasRetainedVariables</code></td>
<td><a class="xref" href="../../../api/Hi.NcParsers.Dependencys.Fanuc.RetainedCommonVariableTable.html">RetainedCommonVariableTable</a></td>
<td>Fanuc, Syntec, Mazak</td>
<td>Layout below</td>
</tr>
<tr>
<td><code>equipment/controller/program-data/r-parameters</code></td>
<td>R Parameters</td>
<td><code>hasRParameters</code></td>
<td><a class="xref" href="../../../api/Hi.NcParsers.Dependencys.Siemens.SiemensRParameterTable.html">SiemensRParameterTable</a></td>
<td>Siemens</td>
<td>Layout below</td>
</tr>
</tbody>
</table>
<p>Three gates never read false on a shipped preset. <code>hasNativeTable</code> is true wherever any brand
parameter table resolves, which every preset provides. <code>hasSubprogramFolders</code> is true because every
one of the five lists carries <a class="xref" href="../../../api/Hi.NcParsers.Dependencys.Generic.SubProgramFolderConfig.html">SubProgramFolderConfig</a>.
<code>hasCannedCycle</code> is true on Fanuc and Mazak through
<a class="xref" href="../../../api/Hi.NcParsers.Dependencys.Fanuc.FanucParameterTable.html">FanucParameterTable</a>, on Syntec through
<a class="xref" href="../../../api/Hi.NcParsers.Dependencys.Syntec.SyntecParameterTable.html">SyntecParameterTable</a>, and on Siemens and Heidenhain through
the <a class="xref" href="../../../api/Hi.NcParsers.Dependencys.Generic.FallbackConfig.html">FallbackConfig</a> those two presets carry. So M-Code
Declarations, Parameters (Native), Canned Cycle (Peck) and Subprogram Folders are gated in code and
unconditional in practice — they can only go missing on a runner composed by hand or loaded from a
file.</p>
<p>Two answers are decided by list <em>order</em>, not only by membership, because the snapshot takes the
first match rather than any match: the parameter table behind <code>hasIndexingTables</code> and the ISO
coordinate provider behind <code>workCoordinateKind</code> are both first-of-type lookups over the effective
list. Each has exactly one candidate in every shipped preset, so order settles nothing there; it
becomes load-bearing only on a runner carrying two.</p>
<h2 id="the-flag-that-does-double-duty">The Flag That Does Double Duty</h2>
<p><code>hasSiemensToolOffsets</code> is the only flag that both adds nodes and rewrites one. It adds Tool Offsets
($TC_DP) and Tool Names, and at the same time switches the ungated tool-offsets leaf from the label
key <code>softNc.node.toolOffsets</code> to <code>softNc.node.toolOffsetsIsoH</code>, so the row reads &ldquo;Tool Offsets (ISO
G43 H)&rdquo; instead of &ldquo;Tool Offsets&rdquo;. The node id is untouched by the relabel. The reason is that the
two ledgers coexist on Siemens — the <code>(T,D)</code>-keyed <code>$TC_DP</code> table beside the single-index ISO G43 H
table — and an unqualified &ldquo;Tool Offsets&rdquo; would blur them into one.</p>
<h2 id="the-one-brand-shaped-hole">The One Brand-Shaped Hole</h2>
<p>Block Skip / Delete is the only node that exists on four brands and not the fifth.
<a class="xref" href="../../../api/Hi.NcParsers.Dependencys.Generic.GenericBlockSkipConfig.html">GenericBlockSkipConfig</a> is the sole implementer of
<a class="xref" href="../../../api/Hi.NcParsers.Dependencys.IBlockSkipConfig.html">IBlockSkipConfig</a> in the engine, and the Heidenhain preset is the one
brand list that does not carry it. Reads of the block-skip endpoint answer absent there, and writes
answer unsuccessful with a message naming the missing dependency.</p>
<h2 id="mazak-resolves-the-fanuc-table">Mazak Resolves the Fanuc Table</h2>
<p>The Mazak preset's dependency list differs from the Fanuc one only in the brand token it carries: it
proxies the same Fanuc parameter table. Both proxies resolve the same concrete per-case table, so
every downstream consequence follows — the same native parameter numbering and prefix, the same
<code>fanuc</code> work-coordinate storage model, and the same rows under a Mazak brand badge. It is also the
one brand pair whose per-case parameter table survives a switch between them, because the sweep that
removes tables the new runner references through no proxy finds this one still referenced.</p>
<h2 id="flags-computed-and-read-by-nothing">Flags Computed and Read By Nothing</h2>
<p>Five snapshot fields are computed by the web service, typed and parsed by the client, and read by no
component. They stay part of what the endpoint returns, so a client other than this one still
receives them.</p>
<ul>
<li><code>hasProject</code> — a null check on the loaded project rather than a dependency probe. It is the only
field the early-return snapshot sets when no runner resolves. The panels gate on the runner flag
instead, and the shared state watches the project store directly.</li>
<li><code>hasCutterComp</code> — would report a cutter-comp startup type on the Fanuc and Syntec tables.</li>
<li><code>hasToolAxisDirection</code> — would report a tool-axis-direction parameter on the Heidenhain table.</li>
<li><code>hasIterationGuards</code> — would report the iteration guards.</li>
<li><code>nativeIdPrefix</code><code>#</code> on Fanuc and Mazak, <code>Pr</code> on Syntec, <code>MD</code> on Siemens, <code>MP</code> on Heidenhain.</li>
</ul>
<p>The screen gets the other four elsewhere. The controller-parameters panel shows its cutter-comp,
tool-axis and iteration-guard controls only where its own read returns a non-null value for each, and
the native-parameters panel renders the prefix from its own read, which recomputes the same switch
server-side.</p>
<p>Two of the snapshot's own doc comments disagree with the code that ships, and the code is what the
tree obeys.</p>
<ul>
<li>The subprogram-folder flag is annotated as a Fanuc-family trait. All five presets carry the
folder config: the Siemens entry is the <code>L</code>-call lookup root and the Heidenhain entry the
<code>CALL PGM</code> lookup root, each with its own comment saying so. The node grows on every brand.</li>
<li>The iteration-guard flag is annotated as the Fanuc GOTO / WHILE-DO guards. The probe also matches
the Siemens jump guard, and matches neither
<a class="xref" href="../../../api/Hi.NcParsers.Dependencys.Heidenhain.HeidenhainGotoIterationDependency.html">HeidenhainGotoIterationDependency</a> — which the
Heidenhain preset does carry — nor the Fanuc WHILE-DO guard. The flag would therefore misreport
Heidenhain even if something read it.</li>
</ul>
<h2 id="what-this-table-cannot-check">What This Table Cannot Check</h2>
<p>The two halves of every brand column live in different repositories and nothing in either build
joins them.</p>
<ul>
<li><strong>The flags are computed in the web service.</strong> The snapshot builder in
<code>Mech/SoftNcRunnerController.cs</code> holds one probe per flag. Changing a probe's type, or
reordering the first-of-type lookups it depends on, moves a whole row of this table.</li>
<li><strong>What each brand carries is written in the engine.</strong> The five brand presets in
<code>HiMech/NcParsers/SoftNcRunner.cs</code> are literal dependency lists. Adding or removing one entry
changes a brand column here, with no compile error and no failing test to mark it.</li>
</ul>
<p>The brand columns above are read from those preset lists. Re-deriving them means diffing those two
files against each other — the probe list on one side, the five preset lists on the other — and
nothing else reports the drift.</p>
<p>Two narrower falsifiers sit under the same join. The interface list on
<a class="xref" href="../../../api/Hi.NcParsers.Dependencys.ControllerParameterTableBase.html">ControllerParameterTableBase</a> is why the machine plane's ungated
leaves have a live table behind them on every brand; if a brand table ever implements
<a class="xref" href="../../../api/Hi.NcParsers.Dependencys.IBlockSkipConfig.html">IBlockSkipConfig</a>, the Heidenhain hole closes silently. And the label
strings in the English locale bundle currently match the hard-coded labels in the branch builder
one for one; the tree renders the label key, so a drift there changes what the tree reads without
changing any id in this table.</p>
<h2 id="layout">Layout</h2>
<ul>
<li>Control Tree — the <strong>General Setup</strong> root
<ul>
<li><strong>Controller</strong> Node Row — the branch root; its editor carries the Object-Management ⋮ button,
the brand badge and the runner-file caption, or the hourglass no-runner block
<ul>
<li><strong>Machine / Controller</strong> Group Stem — intro line plus a clickable list of its children
<ul>
<li><strong>Controller Brand</strong> Node Row</li>
<li><strong>Machine Limits (Stroke)</strong> Node Row</li>
<li><strong>Rapid Feedrates</strong> Node Row</li>
<li><strong>Home / G28 Reference</strong> Node Row</li>
<li><strong>Tool-Change Position</strong> Node Row</li>
<li><strong>Controller Parameters</strong> Node Row</li>
<li><strong>M-Code Declarations</strong> Node Row — gated</li>
<li><strong>Canned Cycle (Peck)</strong> Node Row — gated</li>
<li><strong>Block Skip / Delete</strong> Node Row — gated; absent on Heidenhain</li>
<li><strong>Subprogram Folders</strong> Node Row — gated</li>
<li><strong>Indexing Position Tables</strong> Node Row — gated; Siemens only</li>
<li><strong>Parameters (Native)</strong> Node Row — gated</li>
</ul>
</li>
<li><strong>Program Data</strong> Group Stem — intro line plus a clickable list of its children
<ul>
<li><strong>Work Coordinates (G54…)</strong> Node Row</li>
<li><strong>Tool Offsets</strong> Node Row — reads <strong>Tool Offsets (ISO G43 H)</strong> where the Siemens <code>$TC_DP</code>
table resolves</li>
<li><strong>Tool Offsets ($TC_DP)</strong> Node Row — gated; Siemens only</li>
<li><strong>Tool Names</strong> Node Row — gated; Siemens only</li>
<li><strong>Datum Presets (Q339)</strong> Node Row — gated; Heidenhain only</li>
<li><strong>Datum Shifts (D)</strong> Node Row — gated; Heidenhain only</li>
<li><strong>Frames (Siemens)</strong> Node Row — gated; Siemens only</li>
<li><strong>Retained Common Variables</strong> Node Row — gated; absent on Siemens and Heidenhain</li>
<li><strong>R Parameters</strong> Node Row — gated; Siemens only</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>The rows marked <em>gated</em> are the ones this page's table keys; the rest are the fixed core. Every row
is a plain label with no icon and no checkbox, since the tick column belongs to the mission branch
alone.</p>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../../index.html">HiNC App Anatomy</a> for git repository links.</p>
<h3 id="web-application">Web Application</h3>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/controlTree/softNcItemTypes.ts</code> — the branch builder: it mints every
node id in the table above, holds each leaf's gate, carries the tool-offsets relabel, and returns
an empty child list both when no runner resolves and when the snapshot request throws.</li>
<li><code>wwwroot-src/src/components/controlTree/useControlTreeHost.ts</code> — declares the
<code>equipment/controller</code> root that prefixes every id in the table, and rebuilds the branch when a
panel reports a structural change scoped to it.</li>
<li><code>wwwroot-src/src/api/softNcRunner.ts</code> — the snapshot type, its empty value and its parser; the
five fields with no reader are declared, defaulted and parsed here.</li>
<li><code>wwwroot-src/src/composables/useSoftNcRunner.ts</code> — the shared snapshot the leaf panels gate on,
the project watch that empties it, and the brand switch and Object-Management install that re-read
it.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcRunnerRootPanel.vue</code> — the branch root's editor and
the no-runner block the branch collapses to.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcEmptyState.vue</code> — the shared no-runner body every
leaf panel falls back to.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcWorkCoordinatesPanel.vue</code> — the one consumer of the
snapshot's work-coordinate kind.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcControllerParamsPanel.vue</code> — gates the cutter-comp,
tool-axis and iteration-guard controls on its own read rather than on the snapshot flags.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcNativeParamsPanel.vue</code> — renders the native id
prefix from its own read.</li>
<li><code>wwwroot-src/src/i18n/en/softNc.ts</code> — the node label strings the tree renders, the two group
intros, and the empty-state bodies.</li>
<li><code>Mech/SoftNcRunnerController.cs</code> — builds the snapshot: one probe per flag over the
proxy-resolved dependency list, the early-return form used when no runner resolves, and the brand
switch that swaps the whole runner and sweeps the orphaned per-case tables.</li>
</ul>
<h3 id="hiapi-engine">HiAPI Engine</h3>
<ul>
<li><code>HiMech/NcParsers/SoftNcRunner.cs</code> — the five brand presets as literal dependency lists, and the
proxy resolution every flag probe reads through.</li>
<li><code>HiMech/NcParsers/ControllerPresetWriter.cs</code> — the brand token list and the factory that turns one
token into a preset runner, for the shipped preset files.</li>
<li><code>HiMech/NcParsers/Dependencys/ControllerParameterTableBase.cs</code> — the machine-config interfaces
every brand table inherits, which is why the machine plane's ungated leaves have data on all five
brands.</li>
<li><code>HiMech/NcParsers/Dependencys/CncBrandDependency.cs</code> — the brand marker the snapshot reports, and
the five brand tokens.</li>
<li><code>HiMech/NcParsers/Dependencys/Generic/GenericBlockSkipConfig.cs</code> — the sole implementer of the
block-skip interface, and therefore the whole reason one node is missing on one brand.</li>
<li><code>HiMech/NcParsers/Dependencys/Generic/FallbackConfig.cs</code> — the peck-retraction provider that makes
the canned-cycle gate true on the two brands whose own table has none.</li>
<li><code>HiMech/NcParsers/Dependencys/Generic/SubProgramFolderConfig.cs</code> — the folder config every preset
carries, against the snapshot comment that calls it a Fanuc-family trait.</li>
<li><code>HiMech/NcParsers/Dependencys/Fanuc/FanucParameterTable.cs</code>,
<code>HiMech/NcParsers/Dependencys/Syntec/SyntecParameterTable.cs</code>,
<code>HiMech/NcParsers/Dependencys/Siemens/SiemensMachineDataTable.cs</code>,
<code>HiMech/NcParsers/Dependencys/Heidenhain/HeidenhainParameterTable.cs</code> — the four brand tables and
the extra interfaces that decide the canned-cycle, indexing and work-coordinate answers.</li>
<li><code>HiMech/NcParsers/Dependencys/Fanuc/FanucParameterTableProxy.cs</code> — the per-case resolution the
Fanuc and Mazak presets share, which is why those two survive each other's brand switch.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="index.html">Controller Branch</a> — the branch this table indexes, its two planes and the runner behind them</li>
<li><a class="xref" href="editing-contract.html">Editing Contract</a> — the rules every leaf panel in this table obeys when it reads, commits and rolls back</li>
<li><a class="xref" href="machine/index.html">Machine and Controller Plane</a> — the machine plane, whose leaves make up most of this table</li>
<li><a class="xref" href="program-data/index.html">Program Data Plane</a> — the program-data plane, whose per-case leaves are the rest of this table's rows</li>
<li><a class="xref" href="machine/brand-switch.html">Controller Brand</a> — the control that changes which rows of this table exist, and what it resets on the way</li>
<li><a class="xref" href="machine/per-axis-tables.html">Per-Axis Tables</a> — the four ungated per-axis leaves: stroke limits, rapid rates, home reference and the tool-change position</li>
<li><a class="xref" href="machine/interface-parameters.html">Interface Parameters</a> — the interface parameter form and the canned-cycle leaf, two of the rows above</li>
<li><a class="xref" href="machine/m-code-declarations.html">M-Code Declarations</a> — the M-code declaration leaf, gated by the native-table flag</li>
<li><a class="xref" href="machine/program-reading.html">Program Reading</a> — the block-skip and subprogram leaves, and the one gate that switches a node off for exactly one brand</li>
<li><a class="xref" href="machine/indexing-positions.html">Indexing Position Tables</a> — the one row that appears on a single brand only</li>
<li><a class="xref" href="machine/native-parameters.html">Native Parameters</a> — the native parameter form behind the id prefix this page reports as unread</li>
<li><a class="xref" href="../hidden-controllers.html">Hidden Controller Branches</a> — the other kind of withheld node: the two equipment branches a device preference hides, which a link can reveal where none of these can</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,496 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Editing Contract | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Editing Contract | HiAPI-C# 2025 ">
<link rel="icon" href="../../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../../public/docfx.min.css">
<link rel="stylesheet" href="../../../public/main.css">
<meta name="docfx:navrel" content="../../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../../index.html">
<img id="logo" class="svg" src="../../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-ControllerEditingContract">
<h1 id="editing-contract">Editing Contract</h1>
<p>Every leaf of the Controller branch edits a different table, and almost all of them edit it the same
way. This page is that shared way: what a leaf panel fetches, when an edit commits, what a failed
write does, what a panel shows while its table is absent, and what it never notices. It owns no
Control-Tree node of its own — it holds over every panel of the Controller branch on
<code>/general-setup</code>: the root at <code>equipment/controller</code>, and every leaf beneath
<code>equipment/controller/machine</code> and <code>equipment/controller/program-data</code>. Each leaf page can then
state only what is its own. The last section is the other half of the contract: every panel that
departs from it.</p>
<div class="IMPORTANT">
<h5>Important</h5>
<p>Anatomy makes no claim that these ids are stable across versions. See
<a class="xref" href="../../platform/tree-ids-and-routes.html">Tree Ids and Routes</a> for the migration hop that keeps an older
link resolving.</p>
</div>
<h2 id="the-shape-of-a-leaf-panel">The Shape of a Leaf Panel</h2>
<p>Each leaf panel takes one prop, the tree node, and reads the shared runner snapshot through a
module-singleton composable whose installer it calls synchronously while its script runs rather than
on mount. The installer is idempotent, so whichever panel mounts first pays for it and the rest get
the cache. Only four panels read the node prop at all: two use its id to tell apart the sibling
leaves they serve, and two use it as the scope of the structural change they report.</p>
<p>Beyond that shared snapshot, a panel owns its own table. It imports its reader and its setters
directly from the branch's typed API module and fetches once, in <code>onMounted</code>. There is no store, no
request queue and no batch: one panel, one read, one table. The two panels that fetch nothing — the
branch root and Controller Brand — are exactly the two that have no table of their own and render
off the snapshot alone. How the branch itself is built, rebuilt and selected belongs to the
Control-Tree engine and is described in <a class="xref" href="../../platform/control-tree.html">Control Tree</a>.</p>
<p>Only one panel is mounted at a time. The editor row's remount key is composed from the node id, the
node key and the mission stamp; controller nodes carry an empty key and no mission record, so the
key reduces to the node id alone. Moving the selection therefore remounts and refetches; re-clicking
the row already selected does neither. No controller item type registers a large content view, so
the General Setup content column keeps its own empty hint for every selection in this branch.</p>
<h2 id="when-an-edit-commits">When an Edit Commits</h2>
<p>The commit rule is per control kind, and every panel that uses a given kind uses it the same way.</p>
<p><strong>Numeric fields</strong> are the shared numeric widget, and it commits on blur or on Enter and on nothing
else — never per keystroke. Its full contract, including what a bound violation does to the box, is
<a class="xref" href="../../widget/numeric-input.html">Numeric Input</a>. Three consequences matter here. Enter commits without
moving focus, so leaving the field afterwards commits the same value a second time. The widget
carries no equality guard, so a field that is focused and left unchanged still emits. And the two
halves of the rule are not wired alike: blur is the input component's own event and is raised only
while the field is editable, whereas Enter is a plain listener on the underlying element and still
fires on a read-only one — which is why a read-only cell can still commit.</p>
<p><strong>Checkboxes, toggles and selects</strong> commit immediately, on the click or the pick. That covers the
block-skip layer boxes, the tool-change <em>Stays put</em> box, the M-code tool-change box and its Spindle
and Coolant selects, the tool-word trigger toggle, the cutter-compensation and tool-axis selects,
and the tool-offset <em>Set ideal offset dependent on tool house</em> toggle.</p>
<p><strong>Plain text fields</strong> have no shared widget and are wired twice over. The M-code <em>Not-simulated
note</em> commits on the field's own change event, which carries the string and fires on Enter or on
leaving a changed field. The two subprogram folder inputs bind blur and Enter explicitly, and commit
as a pair.</p>
<p><strong>Row actions</strong> — the datum reset-to-zero button, the work-coordinate P0 and M0 buttons, a delete
button, an add button — commit on the click, and are not optimistic: they write first and touch the
local rows only after the request resolves.</p>
<p>Two panels do not commit per control at all; they stage a draft behind an explicit button, and both
are named below.</p>
<h2 id="the-optimistic-write">The Optimistic Write</h2>
<p>A per-control commit follows one fixed shape. The handler captures the current value, assigns the
new one so the screen updates at once, awaits the setter, and on failure restores the captured value
and raises a toast. Nothing is queued and nothing is retried.</p>
<p>Two details of that shape are easy to misread from the screen:</p>
<ul>
<li><strong>The write is row-scoped even though the handler is per cell.</strong> Editing one cell of a work
coordinate, a datum row, a frame or a tool-offset row sends the whole row or the whole XYZ triad
as it stands after the edit. An M-code edit likewise sends all four declaration fields, which is
why the wire format is a whole-declaration replace rather than a patch.</li>
<li><strong>The rollback restores what the handler captured, which is usually narrower than what it sent.</strong>
A work-coordinate, datum, frame or tool-offset handler captures the one cell it edited and puts
back only that, though the request carried the whole row. Three handlers capture the whole set
they send and restore it intact — the M-code declaration row, the tool-change <em>Stays put</em> pair,
and the two subprogram folders. Either way, a failure the server had partly applied leaves the two
sides disagreeing; the panel's recovery is the toast and a later remount, not a re-read.</li>
</ul>
<p>Adds and removes are outside this shape, and the local rows never move before the request resolves.
Two panels then append the returned row themselves — the generic tool-offset table and the Siemens
<code>$TC_DP</code> table, the two whose add hands back a freshly minted key. The other five re-read their whole
table instead: M-Code Declarations, Tool Names, R Parameters, Retained Common Variables and
Parameters (Native). A delete usually filters the row out in place; Parameters (Native) re-reads
there too. Three deletions are guarded by a confirmation dialog first: a tool-offset row, a Siemens
<code>$TC_DP</code> row, and a native parameter. M-code declarations, tool names, R parameters and retained
common variables delete on the click with no confirmation.</p>
<h2 id="when-a-write-fails">When a Write Fails</h2>
<p>The branch's REST surface answers a missing dependency inside a success envelope rather than with an
error status: the dependency lookup returns HTTP 200 carrying <code>success: false</code> and a message naming
the type it could not find, such as <code>No IStrokeLimitConfig on the active runner</code>, and an exception
raised inside the same lookup is reported the same way. The shared fetch helper turns both a non-2xx
status and a <code>success: false</code> body into a thrown error, so a panel cannot tell the two apart; the
first arrives with the status prefixed to the message, the second as the server's own sentence.</p>
<p>What the user sees is one negative toast, three seconds, composed as the panel's localized context
followed by the raw server message — the locale bundle stores each error key as the bare context and
the code appends the separator and the message. A failed write leaves no inline error state, no
retry affordance and no dirty marker: once the toast expires, nothing on screen records that it
happened. (The <em>unsaved</em> badge and the staged-brand banner on the two draft panels mark a pending
edit, not a failed one.) A console error accompanies the toast.</p>
<p>The server message is not localized. Only a coded error payload can be re-rendered in the app
locale, and the envelope this branch answers with carries no code, so its English sentence is
appended verbatim under any language. The two writes that replace the whole runner answer with real
status codes instead of the envelope: installing one through Object Management, and switching brand.
Of the codes they raise, only <em>not found</em> with no project loaded carries the coded payload — the
conflict raised rather than swapping the parser under a playing NC program, and the refusals of an
unknown key or an unknown brand, are all bare English sentences. One further status code sits on the
datum routes, a defensive 400 for a table segment that is neither <code>preset</code> nor <code>shift</code>; the panel
sends only those two, so nothing reachable from the branch produces it.</p>
<h2 id="the-two-empty-layers">The Two Empty Layers</h2>
<p>A leaf panel opens with two guards, in this order.</p>
<p><strong>Layer one is the shared snapshot.</strong> The shared empty state renders the single line <em>&quot;No NC runner
— load a project first.&quot;</em> whenever the snapshot reports no runner. Every leaf panel carries it; the
branch root is the one panel that does not, because it renders a richer block of its own.</p>
<p>Reaching that line by navigating is not possible: a leaf node is created only when the snapshot
already reported a runner, and a deep link naming an id the built tree lacks is refused. The guard
is live rather than navigational — it answers the snapshot emptying under a panel that is already
mounted, which is what closing the project does.</p>
<p><strong>Layer two is the panel's own read.</strong> Where the runner resolves but its table does not, the panel
renders one line naming the absent table — <em>&quot;No block-skip config on the active runner.&quot;</em>, <em>&quot;No
Siemens frame table on the active runner.&quot;</em>, <em>&quot;No tool-offset table on the active runner.&quot;</em>, and so
on, one string per table. Three panels key that line on one table: Controller Parameters, M-Code
Declarations and Parameters (Native) all gate on the brand's controller parameter table and all
render <em>&quot;No controller parameter table on the active runner.&quot;</em> Controller Parameters is the loosest
fit — its two iteration-guard fields resolve through dependencies of their own, and only its
presence flag is the parameter table.</p>
<p>Controller Brand, which reads no table, has no second layer at all; every other leaf has one. Only
after both guards does the editor body render. One panel keys its second layer on something other
than its read's presence flag, and it is named below.</p>
<h2 id="what-a-mounted-panel-does-not-see">What a Mounted Panel Does Not See</h2>
<p>The shared snapshot is fetched once per load of the application. The installer's flag lives at module
scope, so a later call does nothing and only a browser reload starts over, and the snapshot is
re-read on exactly three events afterwards: the project store's has-a-project flag turning <em>true</em>, an
Object-Management install of a runner, and a brand switch. The flag turning false re-reads nothing —
it clears the snapshot in place. No panel re-reads it either; the composable exposes a reload
function that nothing in the branch calls.</p>
<p>Three consequences follow, and they are the contract's sharpest edges.</p>
<ul>
<li><strong>A project-to-project load does not refresh it.</strong> The has-a-project flag is derived from the
project path being non-empty, and loading another project assigns the new path directly, so the
flag never leaves true and the watch never fires. The page itself is destroyed and rebuilt, because
the layout keys its keep-alive on a project epoch — but the rebuilt panels call the same
now-idempotent installer, which does nothing. Meanwhile the branch builder makes its own,
independent request for the same snapshot, so the tree shape can be built from the new project
while every leaf gates on the old one.</li>
<li><strong>Nothing pushes changes at a mounted panel.</strong> No panel holds a watcher, a poll or a status-hub
subscription over its table. A write made by another browser tab, by an NC run, or by the server's
own sweep after a brand switch is invisible until the panel is remounted by moving the selection
away and back.</li>
<li><strong>A panel's own edits do not update the snapshot or the branch.</strong> Deleting the last row of a table
leaves both the cached snapshot and the tree branch as they were until one of the three refresh
events happens.</li>
</ul>
<p>Because the shared snapshot is a reactive reference, a panel does track it live once it changes —
but with one panel mounted at a time, the change that matters in practice is a project close.</p>
<h2 id="the-table-primitive">The Table Primitive</h2>
<p>Where a leaf renders a table it is Quasar's markup table — dense, flat, bordered — with a
hand-written header and body and a repeat over the rows. No panel in the branch uses the data-table
component, and the four data tables left in the application are the legacy controller tabs this
branch supersedes. The practical difference is that these tables have no built-in sort, no pagination
and no column menu: what the header says is what the column is.</p>
<p>Key columns are rendered as plain text and cannot be edited: the tool and edge numbers of a <code>$TC_DP</code>
row, a tool name, an <code>R</code> number, a <code>#</code> variable number, a datum row index, a native parameter id, and
the axis names of every per-axis table, which follow the machining chain rather than the panel. The
one renameable key is the generic tool-offset row's tool number, and it is guarded against duplicates
on both sides of the wire.</p>
<p>A table that can grow carries an add control, and the branch spells it three ways. Five carry a true
footer below the table — the new row's fields, then a primary button: <strong>Add / Set</strong> on Tool Names,
R Parameters, Retained Common Variables and Parameters (Native), <strong>Declare</strong> on M-Code Declarations.
Two carry a fieldless button in the toolbar above the table instead, because the server mints the
key: <strong>Add</strong> on Tool Offsets and <strong>Add Tool</strong> on the Siemens <code>$TC_DP</code> table. The last is <strong>Add
Position</strong> on Indexing Position Tables, which appends an empty row to the local draft and sends
nothing. Two tables carry a <strong>Show all</strong> toggle that hides an all-zero extended tail, on Work
Coordinates and on Frames.</p>
<h2 id="nothing-guards-a-switch-away">Nothing Guards a Switch Away</h2>
<p>No panel in the branch registers the Control-Tree host's before-switch gate, and none exposes
anything to the host at all. Selecting another node therefore always succeeds immediately. For the
per-control panels that is exactly right — every edit is already on the server. For the two draft
panels it means an unsaved draft and a staged brand are discarded silently when the selection moves,
with no prompt and no warning.</p>
<h2 id="where-the-wave-is-not-uniform">Where the Wave Is Not Uniform</h2>
<p>Everything above is the rule. What follows is every place the branch departs from it, panel by
panel. A contract page without this section invites the next editor to tidy it into a uniformity that
was never true.</p>
<h3 id="machine-limits--rapid-feedrates--home-reference--the-presence-flag-is-discarded">Machine Limits / Rapid Feedrates / Home Reference — the presence flag is discarded</h3>
<p>The axis-table panel serves three leaves and is the one panel whose second empty layer is not its
read's presence flag: it keys on the row count instead. The rows and the flag are independent on the
server, which builds one row per chain axis whether or not the backing config resolves and reports
presence separately. So a runner with axes but no stroke-limit, rapid-feedrate or home config still
renders a full, editable table, and every commit fails with a toast naming the missing dependency.
What fills the cells differs by leaf: the stroke-limit and home values are nullable and come through
blank, while a rapid rate is a plain number the server defaults to <code>0</code>, so that one leaf shows a
table of zeros that never signals the absence at all.</p>
<h3 id="controller-brand--staged-confirmed-and-destructive-by-design">Controller Brand — staged, confirmed, and destructive by design</h3>
<p>Controller Brand is one of two panels with no table read of its own, the only panel with no local
error handler, and one of the two whose write replaces the whole runner — the branch root's
Object-Management install is the other. The select stages a pending brand rather than applying it;
<strong>Revert</strong> drops the staging, an orange banner spells out what the switch destroys, and <strong>Apply
brand</strong> opens a confirmation dialog before the request. Its failures surface through the shared
composable's global notification rather than the panel's own, so the console line names the shared
state and not the panel.</p>
<h3 id="controller-root--its-own-empty-block-its-own-toast">Controller root — its own empty block, its own toast</h3>
<p>The branch root panel does not use the shared empty state. It renders an hourglass over its own
no-runner title and hint. Its error path also differs: it takes an already-composed string from the
Object-Management button, omits the console line, and uses a longer toast than every other panel.</p>
<h3 id="indexing-position-tables--draft-validate-save-whole">Indexing Position Tables — draft, validate, save whole</h3>
<p>The one draft table. Its numeric cells bind straight into a local draft row with no handler, an
<em>unsaved</em> badge appears while the draft differs from what was saved, and nothing reaches the server
until the per-table <strong>Save Table</strong> button, which stays disabled while the draft fails validation. The
validation mirrors the endpoint's own rules — a per-table maximum, strictly ascending values, and a
<code>0 ≤ position &lt; 360</code> range when a modular rotary axis reads the table — and reports the first
violation under the table. A failed save deliberately does <strong>not</strong> roll the draft back: the draft
stays dirty so the edit is not lost, which is the opposite of every other panel.</p>
<p>Its axis-assignment table above the drafts is display-only. The assignment is not editable on this
panel and the endpoint exposes no writer for it; the value is edited as a per-axis integer parameter
in Parameters (Native), which is what the panel's own hint says.</p>
<h3 id="subprogram-folders--two-fields-one-write">Subprogram Folders — two fields, one write</h3>
<p>The two folder inputs are plain text fields committed together on blur or Enter of either one, and
the guard against a redundant write is a saved snapshot of both values rather than a per-field
captured value. Editing one folder writes both.</p>
<h3 id="m-code-declarations--a-third-text-commit-wiring-and-an-add-that-submits-on-enter">M-Code Declarations — a third text-commit wiring, and an add that submits on Enter</h3>
<p>The note cell is the only text cell in the branch that commits through its field's native change
event rather than an explicit blur binding or the numeric widget. The timing matches; the wiring is a
third variant. This is also the only add-row footer whose input submits on Enter.</p>
<h3 id="r-parameters-and-retained-common-variables--the-two-panels-that-commit-a-null">R Parameters and Retained Common Variables — the two panels that commit a null</h3>
<p>Every other numeric handler returns early on a null and treats a cleared cell as no edit. These two
send the null, because an empty value is a real state in both tables — the vacant entry, which a
program reading it reports as an error rather than silently taking as zero.</p>
<h3 id="tool-offsets--a-read-only-column-that-still-writes-and-a-two-call-rollback">Tool Offsets — a read-only column that still writes, and a two-call rollback</h3>
<p>With the tool-house dependence on, the two ideal columns are made read-only rather than hidden. A
read-only field is still focusable and still takes keystrokes, and the widget's Enter binding is a
plain listener on the underlying element rather than the component event blur travels on — so
pressing Enter in one of those cells re-sends the whole row unchanged, while focusing it and leaving
does not, because a non-editable field raises no blur. The tool number column, by contrast, is
swapped for plain text and is genuinely inert. The tool-change position field takes a third route —
it is disabled rather than read-only while its axis stays put, and a disabled field takes no
keystrokes either.</p>
<p>Turning the dependence on also makes two server calls inside one try: the toggle write, then the
refresh from the tool house. If the second fails after the first succeeded — the refresh answers
unsuccessfully when the project carries no tool house — the catch reverts the local toggle while the
server has already committed it.</p>
<h3 id="work-coordinates--the-one-panel-that-writes-outside-the-branch">Work Coordinates — the one panel that writes outside the branch</h3>
<p>Clicking a row picks which coordinate the General Setup canvas marks, and that write goes to the
equipment-display surface rather than to this branch's own. It is also the branch's one call through
the plain-JSON helper instead of the envelope helper, so only a transport failure throws there. The
row click is bound on the whole row with no click-stopping on the cells or the P0 and M0 buttons, so
acting anywhere in a row marks it; a same-id short circuit is what keeps cell editing on the marked
row from writing again on every pass through.</p>
<h3 id="two-truth-sources-for-this-is-siemens">Two truth sources for &ldquo;this is Siemens&rdquo;</h3>
<p>The tree relabels the generic Tool Offsets leaf from the snapshot's <code>$TC_DP</code> flag, while the tool
offsets panel shows its ISO-G43-H caveat, and the controller parameters panel picks its macro-guard
labels, from the brand <em>string</em> instead. The brand marker is a free-form string that can be edited
independently of the dependency list, so the label and the caveat can disagree.</p>
<h3 id="the-error-handler-does-not-agree-with-itself">The error handler does not agree with itself</h3>
<p>Every panel that has a local handler composes the same toast through one signature carrying two
incompatible conventions. Nine pass a localization key and translate inside the handler; eight pass
an already-translated string. Both first parameters are typed as a plain string, so nothing separates
the two conventions at compile time. Nothing user-visible changes; the console line does — the
key-style panels log the raw key path, the string-style panels log the rendered sentence.</p>
<h3 id="the-add-row-footers-disagree-on-two-details">The add-row footers disagree on two details</h3>
<p>Whether Enter submits: only M-Code Declarations binds it. Whether the fields clear after a successful
add: M-Code Declarations clears its code, Tool Names clears both fields, and R Parameters, Retained
Common Variables and Parameters (Native) leave what was typed in place.</p>
<h3 id="a-source-comment-the-code-contradicts">A source comment the code contradicts</h3>
<p>The shared composable's header names the Execution page as the owner of the Controller root. The root
is built by the equipment-scoped host, which only the General Setup page creates, so the code is what
ships. The shared empty state's comment reads like a second case and is not one: the number in it
counts the byte-identical copies its extraction removed, not the call sites it has today, which are
one more.</p>
<h2 id="layout">Layout</h2>
<ul>
<li><strong>General Setup</strong> Control Tree — the left dock of <code>/general-setup</code>
<ul>
<li>Editor Row — the panel of whichever controller leaf is selected, one at a time
<ul>
<li>No-Runner Body — <em>&quot;No NC runner — load a project first.&quot;</em>; shown first, from the shared
snapshot</li>
<li>Absent-Table Body — one line naming the table this leaf edits, from the panel's own read</li>
<li>Editor Body
<ul>
<li>Description Caption — the leaf's own one-line explanation, above the table</li>
<li>Toolbar — where the add button carries no fields: <strong>Add</strong> on Tool Offsets, <strong>Add Tool</strong> on
the Siemens <code>$TC_DP</code> table, beside that panel's own toggles</li>
<li>Table — dense, flat, bordered, no sort and no pagination
<ul>
<li>Header Row — the leaf's own columns, over the shared labels <strong>Axis</strong>, <strong>Id</strong>, <strong>Value</strong>,
<strong>Unit</strong> and <strong>Actions</strong></li>
<li>Data Row — a bold plain-text key cell, then one editable cell per column, then the row's
action buttons</li>
</ul>
</li>
<li>Add Row Footer — the new row's fields, then <strong>Add / Set</strong> or <strong>Declare</strong></li>
<li><strong>Show all</strong> Toggle — Work Coordinates and Frames only, revealing the all-zero extended tail</li>
<li>Draft Bar — <strong>Add Position</strong>, <strong>Revert</strong> and <strong>Save Table</strong> with an <em>unsaved</em> badge on
Indexing Position Tables; <strong>Revert</strong> with <strong>Apply brand</strong> on Controller Brand</li>
</ul>
</li>
</ul>
</li>
<li>Toast — negative, three seconds, the panel's context followed by the server's own message</li>
</ul>
</li>
</ul>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../../index.html">HiNC App Anatomy</a> for git repository links.</p>
<h3 id="web-application">Web Application</h3>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/composables/useSoftNcRunner.ts</code> — the shared snapshot every panel gates on: the
module-scope installed flag, the project-presence watch, the three events that re-read it, the
reload no panel calls, and the global notification the brand panel's failures take.</li>
<li><code>wwwroot-src/src/api/softNcRunner.ts</code> — the branch's typed REST wrappers: the snapshot parser, one
reader and one setter family per table, and the whole-row payloads the per-cell handlers send.</li>
<li><code>wwwroot-src/src/api/http.ts</code> — the two failure shapes a panel cannot tell apart, and the coded
error rendering that a message without a code falls through.</li>
<li><code>wwwroot-src/src/components/widgets/NumericInput.vue</code> — the numeric field: commit on blur or
Enter, no per-keystroke emit, no equality guard, and the Enter listener that keeps firing where a
read-only field's blur is suppressed.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcEmptyState.vue</code> — the shared no-runner line, and the
comment that counts the copies its extraction removed rather than its call sites.</li>
<li><code>wwwroot-src/src/components/controlTree/softNcItemTypes.ts</code> — the branch builder: the node ids the
two multi-leaf panels discriminate on, and the empty node key that reduces the remount key to the
id.</li>
<li><code>wwwroot-src/src/components/controlTree/PrimarySlavePanel.vue</code> — the editor row: the remount key,
the one-panel-at-a-time mounting, and the events it wires to the host.</li>
<li><code>wwwroot-src/src/components/controlTree/useControlTreeHost.ts</code> — the before-switch gate no panel in
this branch registers, and the rebuild that a reported structural change triggers.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcAxisTablePanel.vue</code> — the three per-axis leaves, and
the second empty layer keyed on the row count instead of the presence flag.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcBrandPanel.vue</code> — the staged select, the revert, the
confirmation and the structural change it reports.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcRunnerRootPanel.vue</code> — the panel with its own empty
block and its own toast shape.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcIndexingPositionsPanel.vue</code> — the draft table: the
unsaved badge, the client-side mirror of the endpoint's rules, the save that keeps a failed draft,
and the display-only assignment column.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcSubprogramPanel.vue</code> — the two text fields committed
as a pair against a saved snapshot.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcMCodesPanel.vue</code> — the change-event text cell, the
whole-declaration write, and the only add input that submits on Enter.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcToolOffsetsPanel.vue</code> — the read-only ideal columns
that still commit, the renameable key with its duplicate guard, and the two-call dependence toggle.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcWorkCoordinatesPanel.vue</code> — the row click that writes
to the display surface, and the row-scoped triad write.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcRParametersPanel.vue</code> and
<code>wwwroot-src/src/components/controlTree/SoftNcRetainedVariablesPanel.vue</code> — the two panels that
commit a null to vacate an entry.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcSiemensToolOffsetsPanel.vue</code> and
<code>wwwroot-src/src/components/controlTree/SoftNcNativeParamsPanel.vue</code> — the other two confirmed
deletions, and the two add flows that part company: one appends the minted row, the other re-reads
the whole table.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcToolNamesPanel.vue</code>,
<code>wwwroot-src/src/components/controlTree/SoftNcToolChangePanel.vue</code>,
<code>wwwroot-src/src/components/controlTree/SoftNcDatumTablePanel.vue</code> and
<code>wwwroot-src/src/components/controlTree/SoftNcControllerParamsPanel.vue</code> — the conforming shape:
one read on mount, per-control commit, capture-assign-await-restore, one toast. The tool-change
<em>Stays put</em> box is the one handler among them that captures and restores a pair of fields.</li>
<li><code>wwwroot-src/src/api/equipmentSetup.ts</code> — the canvas marker id the work-coordinate row click
writes, outside this branch's own surface and through the plain-JSON helper.</li>
<li><code>wwwroot-src/src/stores/project.ts</code> — the has-a-project flag the shared snapshot watches, and the
direct path assignment that keeps it true across a project-to-project load.</li>
<li><code>wwwroot-src/src/layouts/MainLayout.vue</code> — the project epoch that rebuilds the page without
re-running the idempotent installer.</li>
<li><code>wwwroot-src/src/i18n/en/softNc.ts</code> — every empty line, column header, hint and error context this
branch renders, and the convention that an error key stores the bare context.</li>
<li><code>wwwroot-src/src/i18n/en/common.ts</code> — the shared column and action labels the tables reuse.</li>
<li><code>Mech/SoftNcRunnerController.cs</code> — the REST surface: the dependency lookup that answers a missing
table inside a success envelope, the reads that report presence independently of their rows, the
rapid rate defaulted to zero where its config is absent, the two runner-replacing writes that
answer real status codes instead, and the datum routes' defensive rejection of an unknown table
segment.</li>
<li><code>Common/ApiError.cs</code> — the coded payloads, and why only the no-project answer can be re-rendered in
the app locale.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="index.html">Controller Branch</a> — the branch these rules hold across, its two planes and the runner behind them</li>
<li><a class="xref" href="machine/index.html">Machine and Controller Plane</a> — the plane whose seven leaf pages this contract lets stay short</li>
<li><a class="xref" href="program-data/index.html">Program Data Plane</a> — the other plane these rules hold across, whose six leaf pages inherit them the same way</li>
<li><a class="xref" href="brand-matrix.html">Brand Matrix</a> — which leaf exists on which brand, and the snapshot flags the first empty layer reads</li>
<li><a class="xref" href="../../widget/numeric-input.html">Numeric Input</a> — the numeric field's own contract: what parses, what the bounds do, and why Enter commits twice</li>
<li><a class="xref" href="machine/brand-switch.html">Controller Brand</a> — the staged, confirmed, whole-runner write that departs furthest from these rules</li>
<li><a class="xref" href="machine/per-axis-tables.html">Per-Axis Tables</a> — the three leaves whose empty layer keys on the row count, and the tool-change position beside them</li>
<li><a class="xref" href="machine/interface-parameters.html">Interface Parameters</a> — the interface parameter form and the canned-cycle field, both plain per-control commits</li>
<li><a class="xref" href="machine/m-code-declarations.html">M-Code Declarations</a> — the whole-declaration write and the change-event text cell</li>
<li><a class="xref" href="machine/program-reading.html">Program Reading</a> — the block-skip boxes and the paired folder inputs</li>
<li><a class="xref" href="machine/indexing-positions.html">Indexing Position Tables</a> — the draft-then-save table, the one panel that keeps a failed edit</li>
<li><a class="xref" href="machine/native-parameters.html">Native Parameters</a> — the confirmed deletions and the add-or-set footer that keeps what was typed</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,409 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Controller Branch | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Controller Branch | HiAPI-C# 2025 ">
<link rel="icon" href="../../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../../public/docfx.min.css">
<link rel="stylesheet" href="../../../public/main.css">
<meta name="docfx:navrel" content="../../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../../index.html">
<img id="logo" class="svg" src="../../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-Controller">
<h1 id="controller-branch">Controller Branch</h1>
<p>The Controller branch is the SoftNcRunner-native settings face: the controller the project actually
parses NC with, edited one tree node at a time. It lives on the General Setup page at
<code>/general-setup</code> under the Control-Tree id <code>equipment/controller</code>, and grows two plane stems beneath
it — <code>equipment/controller/machine</code> and <code>equipment/controller/program-data</code>. The superseded
HardNcEnv surface still ships as a route of its own at <code>/controller</code>, and edits a different model.</p>
<div class="IMPORTANT">
<h5>Important</h5>
<p>Anatomy makes no claim that these ids are stable across versions. See
<a class="xref" href="../../platform/tree-ids-and-routes.html">Tree Ids and Routes</a> for the migration hop that keeps an older link
resolving.</p>
</div>
<h2 id="key-models">Key Models</h2>
<ul>
<li><a class="xref" href="../../../api/Hi.NcParsers.NcRunnerSuit.html">NcRunnerSuit</a> — the switchable suit the branch edits: one runner plus the
per-workpiece dependency list the runner's proxy placeholders resolve against. A suit constructs
on the Fanuc preset, and a project file that carried no runner element keeps that.</li>
<li><a class="xref" href="../../../api/Hi.NcParsers.SoftNcRunner.html">SoftNcRunner</a> — the NC pipeline itself, and the object the branch root's
Object-Management menu loads, pastes and saves.</li>
<li><a class="xref" href="../../../api/Hi.NcParsers.SoftNcRunner.html#Hi_NcParsers_SoftNcRunner_PipelineNcDependencyList">PipelineNcDependencyList</a> — what a runner file carries.</li>
<li><a class="xref" href="../../../api/Hi.NcParsers.NcRunnerSuit.html#Hi_NcParsers_NcRunnerSuit_PerCaseNcDependencyList">PerCaseNcDependencyList</a> — what stays on the project.</li>
<li><a class="xref" href="../../../api/Hi.NcParsers.Dependencys.CncBrandDependency.html">CncBrandDependency</a> — the brand marker the root panel's badge
reads. Its <a class="xref" href="../../../api/Hi.NcParsers.Dependencys.CncBrandDependency.html#Hi_NcParsers_Dependencys_CncBrandDependency_Brand">Brand</a> is a plain string with five
declared tokens, so the marker can be edited independently of the dependency list it sits in.</li>
<li><a class="xref" href="../../../api/Hi.MachiningProcs.LocalProjectService.html#Hi_MachiningProcs_LocalProjectService_SoftNcRunner">SoftNcRunner</a> — the facade setter every install and
every brand switch assigns through. It re-binds the proxies to the suit, stamps the machining
chain's axis codes onto the axis config, and resets the per-session runner state.</li>
</ul>
<p>Every value the branch reads is resolved through
<a class="xref" href="../../../api/Hi.NcParsers.SoftNcRunner.html#Hi_NcParsers_SoftNcRunner_GetEffectiveNcDependencyList_">GetEffectiveNcDependencyList</a><small>(API)</small> rather than the
raw pipeline list: the proxies deliberately implement no machine-config interface, so a consumer
that read the raw list would see a placeholder instead of the table. The tree itself speaks domain
vocabulary only — brand, machine limits, work coordinates. The dependency pipeline, the proxy
indirection and the brand preset lists have no node.</p>
<h2 id="the-two-planes">The Two Planes</h2>
<p>The branch root's children are two <code>Group</code> stems, and neither is ever empty: each carries at least
one leaf that every runner grows. A stem's editor panel is the shared group panel — an intro line
over a clickable list of its children.</p>
<p><strong>Machine / Controller</strong> (<code>equipment/controller/machine</code>) introduces itself as <em>&quot;Machine and
controller presets that travel with the runner file — brand, travel limits, rapid rates, home / G28
reference, the tool-change position and the controller parameters (in both a domain-grouped
interface form and the native parameter form).&quot;</em></p>
<p><strong>Program Data</strong> (<code>equipment/controller/program-data</code>) introduces itself as <em>&quot;Data that travels with
the workpiece / project — work-coordinate offsets (G54…), tool offsets and the brand-specific
per-case tables. Kept on the project even when the runner file is swapped.&quot;</em></p>
<h3 id="what-a-runner-file-actually-carries">What a runner file actually carries</h3>
<p>The runner's pipeline list holds two kinds of entry, and the difference decides what an installed
runner file changes.</p>
<p>Plain instances are the runner's own and are replaced outright by an install: the brand marker, the
tool-change position, the block-skip, subprogram-folder and iteration-guard configs a given preset
carries, and — on Siemens and Heidenhain — the generic fallback config those two presets add
because their parameter tables declare no number for the peck clearance. That last entry is why
<strong>Canned Cycle (Peck)</strong> is runner-owned on those two brands, while on the other three the same
clearance is a row of the brand parameter table and stays with the project.</p>
<p>The brand parameter table is reached through a proxy instead. The proxy carries a machine-config
<strong>seed</strong>, and that seed — not the live table — is what the runner file serializes.
<a class="xref" href="../../../api/Hi.NcParsers.NcRunnerSuit.html#Hi_NcParsers_NcRunnerSuit_WireNcDependencyProxies_">WireNcDependencyProxies</a><small>(API)</small> deep-clones the seed
into the project's per-case list only when the project holds no table of that type yet; a project
that already carries one keeps it. So the per-axis limits, rapid rates, home references, M-code
declarations and native parameters a panel edits are stored on the <strong>project</strong>, and installing a
same-brand runner file leaves them where they are while it does replace the plain instances above.
The Fanuc-family and Syntec parameter tables are per-case because they mix machine configuration
with per-case work-coordinate offsets, which the two planes could not otherwise split. The Siemens
machine-data and Heidenhain tables hold no work coordinates and are per-case for the other half of
the reason: a project's own table wins over the seed, so installing a same-brand runner file
re-binds to the machine data already edited instead of resetting it to the preset's.</p>
<h3 id="what-survives-a-swap">What survives a swap</h3>
<p>The per-case list belongs to the project and survives an install or a brand switch, minus one step:
after the new runner is assigned, per-case tables its proxies resolve nothing for are removed, so a
project switched from Fanuc to Siemens does not keep a retained-common-variable table nothing reads.
The generic tool-offset table survives every switch, because every brand preset proxies it.
Switching back does not restore what the sweep removed. Only the brand parameter table returns from
a seed, deep-cloned out of the new preset's proxy; the per-case tables beside it carry no seed at
all, so their proxies create bare instances — a project switched back to Fanuc gets an empty
retained-common-variable table, not the values it held before.</p>
<h2 id="the-root-panel">The Root Panel</h2>
<p>The <strong>Controller</strong> node's editor is the branch root panel. Its header row carries three things:</p>
<ul>
<li>The <strong>Object Management</strong> menu (<code></code>), whose entries are Load, Save As, Copy, Paste and XML Mode.
Load and Save As browse the server file system through the shared file-explorer dialog, filtered to
<code>*.Controller</code>, <code>*.SoftNcRunner</code> and <code>*.xml</code>; Save As proposes the name <code>NcRunner.Controller</code>.
Paste checks the pasted object against the expected type <code>Hi.NcParsers.SoftNcRunner, HiMech</code>.
Load, Paste and an XML apply all install the swapped object onto the project and then regrow the
branch. The button is disabled while no runner key is indexed, which is the state with no project
open; with a project but no runner a blank placeholder is indexed so the menu stays reachable as a
Load target.</li>
<li>A <strong>brand badge</strong>, shown only while a runner resolves. It reads the brand marker's string, or
<strong>Unknown brand</strong> when the marker is empty.</li>
<li>A <strong>runner-file caption</strong>. It reads the side-file path recorded on the suit, or <strong>Embedded in
project</strong> when the runner is inlined in the project file, or <strong>No NC runner</strong> when none resolves.
The web service reads that recorded path and never writes it, so a Save As from this menu writes a
file without changing what the caption names — and after an Object-Management install or a brand
switch the caption still names the file the project was loaded with rather than the runner now in
place.</li>
</ul>
<p>Below the separator the panel shows one of two bodies. With a runner it shows the presets hint —
<em>&quot;Machine / controller presets (brand, limits, rapid, home, tool change, parameters) travel with the
runner and can be saved / loaded as a <code>.SoftNcRunner</code> asset via the ⋮ menu. Program data (work
coordinates, tool offsets…) stays with this project's workpiece. Edit each group via this item's
child tree-nodes.&quot;</em> Both hint blocks name <code>.SoftNcRunner</code>, while the file picker offers <code>.Controller</code>
first.</p>
<p>An orange line follows when the snapshot reports no axes: <em>&quot;No machine axes yet — per-axis rows
(limits / rapid / home) are driven by the Machine Tool chain. Attach a machine tool to populate
them.&quot;</em> The axis set is read from the controller parameter table's axis-type rows, which the facade
setter stamps from the <a class="xref" href="../machine-tool.html">Machine Tool</a> chain — adding the chain's axes and
keeping the table's persisted ones. Every brand preset's default table already declares X, Y and Z,
so on a preset-built runner the warning does not appear; it is a live guard for a runner whose table
declares no axis, or which resolves no parameter table at all.</p>
<h2 id="when-no-runner-resolves">When No Runner Resolves</h2>
<p>With no project open the host skips the branch builder entirely, so the Controller node stands alone
with no children, and the root panel shows its own no-runner block: an hourglass, <em>&quot;No NC controller
runner on this project.&quot;</em>, and the hint <em>&quot;Load a project, or use the ⋮ menu → Load to attach a
<code>.SoftNcRunner</code> file.&quot;</em> This block is the panel's own, not the shared empty state the leaf panels use.</p>
<p>With a project open the builder runs and returns no children when the snapshot reports no runner —
and returns exactly the same empty result when the snapshot request throws. Nothing in the branch
shape distinguishes a failed fetch from an absent runner, and neither does the root panel: a failed
load resets the shared snapshot to the empty one, so the panel renders the same no-runner block. The
only signal is the error toast that failed load raises. The builder's own fetch failure raises
nothing, so a request that fails for the builder alone leaves a childless branch under a panel still
showing the brand badge.</p>
<p>In practice a loaded project resolves a runner, since the suit's runner property constructs on the
Fanuc preset and a project file with no runner element keeps that default. The empty branch is
therefore what a reader meets before a project is open rather than a state a loaded project sits in.</p>
<h2 id="how-the-branch-regrows">How the Branch Regrows</h2>
<p>Two panels in the whole branch report a structural change, and both scope it to the branch root:</p>
<ul>
<li>The <strong>root panel</strong>, after an Object-Management Load, Paste or XML apply. It installs the indexed
object onto the project, refreshes the shared snapshot and the runner key, and then emits — whether
or not the install itself succeeded, so the branch always regrows against a fresh snapshot.</li>
<li><strong>Controller Brand</strong>, after a brand switch, and only when the switch reported success.</li>
</ul>
<p>The host resolves the named scope, re-runs the branch builder over it, and adds the node to the
expansion list so the fresh children are visible. Because a brand switch replaces the whole runner,
this is what makes the brand-driven leaves appear and disappear.</p>
<p>A brand switch that fails <em>after</em> the server has already assigned the preset leaves the two halves
out of step: the shared snapshot is reloaded either way, so the badge and the brand select read the
new brand, while the branch was not regrown and still lists the old brand's leaves.</p>
<p>The tree shape and the panels' own gate come from two independent requests for the same snapshot.
The builder calls the endpoint directly; the panels read a module-singleton cache filled once per
SPA session and refreshed on a project-presence change, an Object-Management install and a brand
switch. No panel refreshes it, so an edit that empties a table changes neither the cache nor the
branch until one of those happens.</p>
<p>Both structural writes are refused while an NC program is playing: installing a runner and switching
brand each answer a conflict rather than swapping the parser under a running session.</p>
<h2 id="the-superseded-route">The Superseded Route</h2>
<p>The legacy controller page still ships at <code>/controller/:tab?</code> and is reached from the app menu bar's
<strong>Page → Legacy-Controller</strong> entry. It edits <a class="xref" href="../../../api/Hi.Numerical.HardNcEnv.html">HardNcEnv</a> through its own REST
surface, which is a different model from the runner this branch edits — the two are not two views of
one object, and an edit on one is invisible to the other. Its anatomy is
<a class="xref" href="../../legacy-controller.html">Legacy Controller Page</a>.</p>
<h2 id="layout">Layout</h2>
<ul>
<li><strong>General Setup</strong> Control Tree — the left dock of <code>/general-setup</code>
<ul>
<li><strong>Controller</strong> Node Row — <code>equipment/controller</code>, built after <strong>Workpiece</strong> and ahead of the two
optional runner-suit leaves
<ul>
<li><strong>Machine / Controller</strong> Group Row — <code>equipment/controller/machine</code>
<ul>
<li><strong>Controller Brand</strong>, <strong>Machine Limits (Stroke)</strong>, <strong>Rapid Feedrates</strong>,
<strong>Home / G28 Reference</strong>, <strong>Tool-Change Position</strong>, <strong>Controller Parameters</strong> — grown for
every runner</li>
<li><strong>M-Code Declarations</strong>, <strong>Canned Cycle (Peck)</strong>, <strong>Block Skip / Delete</strong>,
<strong>Subprogram Folders</strong>, <strong>Indexing Position Tables</strong>, <strong>Parameters (Native)</strong> — each grown
only where the snapshot reports its backing entry. Across the shipped presets only Block
Skip / Delete and Indexing Position Tables ever go missing; the other four are gated in code
and unconditional in practice</li>
</ul>
</li>
<li><strong>Program Data</strong> Group Row — <code>equipment/controller/program-data</code>
<ul>
<li><strong>Work Coordinates (G54…)</strong> and <strong>Tool Offsets</strong> — grown for every runner. The second reads
<strong>Tool Offsets (ISO G43 H)</strong> while the Siemens <code>$TC_DP</code> table resolves, so the two offset
ledgers do not read as one; its node id is unchanged by that relabel</li>
<li><strong>Tool Offsets ($TC_DP)</strong>, <strong>Tool Names</strong>, <strong>Datum Presets (Q339)</strong>, <strong>Datum Shifts (D)</strong>,
<strong>Frames (Siemens)</strong>, <strong>Retained Common Variables</strong>, <strong>R Parameters</strong> — each grown only where
the snapshot reports its backing table</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>Editor Row — the panel of whichever node is selected
<ul>
<li><strong>Controller</strong> Root Panel
<ul>
<li>Object Management Menu Button (<code></code>) — Load, Save As, Copy, Paste, XML Mode</li>
<li>Brand Badge — the brand marker, or <strong>Unknown brand</strong></li>
<li>Runner File Caption — the recorded side file, <strong>Embedded in project</strong>, or <strong>No NC runner</strong></li>
<li>No-Runner Block — hourglass icon, <strong>No NC controller runner on this project.</strong>, and the
attach-a-file hint; shown instead of the two items below</li>
<li>Presets Hint</li>
<li>No-Axes Warning — shown only while the snapshot carries no axis</li>
</ul>
</li>
<li><strong>Machine / Controller</strong> and <strong>Program Data</strong> Group Panels — the stem's intro line over a
clickable list of its children, each row selecting that node</li>
</ul>
</li>
</ul>
<p>Which brand grows which leaf is <a class="xref" href="brand-matrix.html">Controller Brand Matrix</a>; how a
leaf panel commits an edit is <a class="xref" href="editing-contract.html">Controller Editing Contract</a>.</p>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../../index.html">HiNC App Anatomy</a> for git repository links.</p>
<h3 id="web-application">Web Application</h3>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/controlTree/SoftNcRunnerRootPanel.vue</code> — the branch root's editor: the
Object-Management button and its install-then-regrow handler, the brand badge, the runner-file
caption, the no-runner block, the presets hint and the no-axes warning.</li>
<li><code>wwwroot-src/src/components/controlTree/softNcItemTypes.ts</code> — the wave's item-type registry and the
branch builder: the two plane stems, the leaves every runner grows, the snapshot flag each further
leaf is grown behind, and the Siemens relabel of the Tool Offsets leaf.</li>
<li><code>wwwroot-src/src/composables/useSoftNcRunner.ts</code> — the module-singleton snapshot and runner key,
the idempotent install of the project-presence watch, and the install and brand-switch calls the
two emitting panels use.</li>
<li><code>wwwroot-src/src/api/softNcRunner.ts</code> — the snapshot shape the tree and the panels parse, and the
typed wrappers over the controller's REST surface.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcEmptyState.vue</code> — the shared &ldquo;no runner&rdquo; line the
leaf panels render; the root panel renders its own block instead.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcBrandPanel.vue</code> — the other panel that regrows the
branch: the staged brand select, its confirmation, and the scope it emits.</li>
<li><code>wwwroot-src/src/components/controlTree/useControlTreeHost.ts</code> — builds the <code>equipment/controller</code>
root inside the equipment group, and turns a reported structure change into a branch rebuild.</li>
<li><code>wwwroot-src/src/components/controlTree/GroupInfoPanel.vue</code> — the editor both plane stems use.</li>
<li><code>wwwroot-src/src/components/controlTree/itemTypes.ts</code> — the base item-type registry the wave's own
registry is spread into, and the mapping of the <code>Group</code> item type to that shared editor.</li>
<li><code>wwwroot-src/src/components/widgets/ObjectManagementMenuButton.vue</code> — the <code></code> menu: the server file
browser and its extension filter, the expected type it sends with a paste, and the events the root
panel listens to.</li>
<li><code>wwwroot-src/src/pages/GeneralSetupPage.vue</code> — the route that creates the <code>equipment</code>-scoped tree
host this branch is built in.</li>
<li><code>wwwroot-src/src/router/routes.ts</code> — the <code>/general-setup</code> route and the still-shipping
<code>/controller/:tab?</code> route beside it.</li>
<li><code>wwwroot-src/src/components/AppMenuBar.vue</code> — the Page menu entry that reaches the legacy route.</li>
<li><code>wwwroot-src/src/i18n/en/softNc.ts</code> — every label, group intro, hint and empty string this branch
renders.</li>
<li><code>wwwroot-src/src/i18n/en/tree.ts</code> — the <strong>Controller</strong> root label.</li>
<li><code>Mech/SoftNcRunnerController.cs</code> — the branch's REST surface: the snapshot the tree shape is built
from, the index-and-install pair behind Object Management, the brand switch with its carry and
sweep, and the per-group readers and writers the leaves use.</li>
<li><code>Widget/ObjectManagementController.cs</code> — the server half of the <code></code> menu: the paste that rejects
an object the expected-type string does not admit.</li>
</ul>
<h3 id="hiapi-engine">HiAPI Engine</h3>
<ul>
<li><code>HiMech/NcParsers/NcRunnerSuit.cs</code> — the suit: the runner, its optional side-file path, the
per-case list, the nested serialization of both, and the proxy wiring that materialises a per-case
table into a project holding none of that type yet.</li>
<li><code>HiMech/NcParsers/SoftNcRunner.cs</code> — the pipeline and the five brand presets whose entries decide
which leaves a brand grows, plus the chain configuration that stamps axis codes onto the axis
config.</li>
<li><code>HiMech/NcParsers/Dependencys/CncBrandDependency.cs</code> — the brand marker behind the badge and the
five declared tokens.</li>
<li><code>HiMech/NcParsers/Dependencys/ControllerParameterTableBase.cs</code> — the base whose interface list is
why the per-axis leaves exist on every brand, and the axis-type rows the axis set is read from.</li>
<li><code>HiMech/NcParsers/Dependencys/Fanuc/FanucParameterTableProxy.cs</code> — the get-or-create proxy pattern
the parameter tables use: a serialized seed on the runner, the live table on the project.</li>
<li><code>HiNc/MachiningProcs/LocalProjectService.cs</code> — the facade setter: the hoist of pre-proxy baked
tables, the proxy re-binding, the chain-driven axis stamp and the session reset.</li>
<li><code>HiNc/MachiningProcs/MachiningProject.cs</code> — where the suit hangs off the project and the load path
that leaves a project with the default preset when its file names no runner.</li>
</ul>
<h2 id="pages">Pages</h2>
<p>Ordered as the branch is read: the two references that hold across every node in it, then the two
plane folders whose leaves have editors of their own.</p>
<ul>
<li><a class="xref" href="brand-matrix.html">Controller Brand Matrix</a> — Which leaf exists on which brand,
the snapshot flag behind each one, and what a brand switch keeps, resets or destroys</li>
<li><a class="xref" href="editing-contract.html">Controller Editing Contract</a> — The rules every leaf panel
in the branch follows — when an edit commits, what a failed write does, how a table is rendered —
and the panels that depart from them</li>
<li><a class="xref" href="machine/index.html">Machine / Controller Plane</a> — The plane that carries the machine
and controller settings: the brand switch, the per-axis tables, the parameter forms, the M-code
declarations, program reading and the indexing tables</li>
<li><a class="xref" href="program-data/index.html">Program Data Plane</a> — The plane that carries the per-case
tables: the work coordinates, the two tool-offset ledgers, the Heidenhain datum rows, the Siemens
frames and the two variable tables</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="brand-matrix.html">Brand Matrix</a> — which leaf exists on which brand, and the snapshot flag
behind each one</li>
<li><a class="xref" href="editing-contract.html">Editing Contract</a> — the commit, rollback and error rules every leaf panel in
this branch follows</li>
<li><a class="xref" href="machine/index.html">Machine and Controller Plane</a> — the plane whose leaves carry the machine and controller
settings, one page per editor</li>
<li><a class="xref" href="program-data/index.html">Program Data Plane</a> — the plane whose leaves carry the per-case tables, and the
ownership rules that keep them on the project</li>
<li><a class="xref" href="../index.html">General Setup Page</a> — the page that hosts this branch, and the equipment items beside it</li>
<li><a class="xref" href="../../platform/control-tree.html">Control Tree</a> — the engine that builds, rebuilds and selects this branch</li>
<li><a class="xref" href="../../legacy-controller.html">Legacy Controller Page</a> — the superseded HardNcEnv route this branch replaces, still shipped
at <code>/controller</code></li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,600 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Controller Brand | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Controller Brand | HiAPI-C# 2025 ">
<link rel="icon" href="../../../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../../../public/docfx.min.css">
<link rel="stylesheet" href="../../../../public/main.css">
<meta name="docfx:navrel" content="../../../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../../../index.html">
<img id="logo" class="svg" src="../../../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-ControllerBrandSwitch">
<h1 id="controller-brand">Controller Brand</h1>
<p>Controller Brand is the first leaf of the Controller branch's machine plane, and the one leaf whose
write replaces the entire NC runner instead of one field of one table — the branch root's
Object-Management Load and Paste replace it by the other route, described below. It sits on
the General Setup page at <code>/general-setup</code> under the Control-Tree id
<code>equipment/controller/machine/brand</code>, and it is the only leaf whose write changes which of the
branch's other nodes exist. The tree row reads <strong>Controller Brand</strong>; the select inside the panel is
labelled <strong>Controller brand</strong>.</p>
<div class="IMPORTANT">
<h5>Important</h5>
<p>Anatomy makes no claim that the id named here is stable across versions. The migration hop that
keeps an older <code>?tree=</code> value resolving is described in
<a class="xref" href="../../../platform/tree-ids-and-routes.html">Tree Ids and Routes</a>.</p>
</div>
<h2 id="what-the-brand-actually-selects">What the Brand Actually Selects</h2>
<p>The brand marker itself is a plain string carried by
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.CncBrandDependency.html">CncBrandDependency</a>, and nothing on the switch path reads it: the preset
that answers a pick is chosen by the token the request carries, and what the installed runner does
afterwards follows from that preset's own dependency list. What the panel writes is a <em>preset</em>: a
whole <a class="xref" href="../../../../api/Hi.NcParsers.SoftNcRunner.html">SoftNcRunner</a> built fresh from the engine's brand property, carrying its own
syntax list, its own segmenter, its own initializers and its own dependency list. Four of the five
segment one block per line and Heidenhain brings a segmenter of its own; Fanuc, Syntec and Mazak
share an ISO initializer while Siemens and Heidenhain each bring theirs. So a brand switch changes
the dialect a program is read in, not only the tables the branch shows.</p>
<p>Away from this panel the marker is not inert. NC optimization reads it back out of the session's
effective dependency list for the writeback patch grammar, which takes five things from the marker:
its variable prefix, its comment spans, its keyword set, the place an inserted <code>F</code> word lands — on
Heidenhain after the rightmost <code>DR+</code>/<code>DR-</code> and <code>RL</code>/<code>RR</code>/<code>R0</code>, so the element order a TNC enforces
holds — and the shape of the source note the optimizer embeds, a <code>;</code> comment on Heidenhain and a
parenthesized one on the rest. The radius-compensation syntax all five presets
carry reads it too, raising its negative-radius validation warning only where the marker is
Heidenhain. <strong>The piece classifier does not read it</strong>: none of its re-interpolation guards is
keyed on the brand, and the guard that singles a klartext arc out is keyed on where the arc's
centre came from. So an arc centred on a modal <code>CC</code> line splits like any other, and the one
refused (<code>NcOpt--SplitionStartPointCenterUnsupported</code>) is the arc whose <code>CC</code> chain left an
in-plane coordinate for its own start point to supply. An arc whose centre falls entirely on its
start point never reaches that guard: with no radius it is not an arc at all, so it warns
<code>Arc-CircleCenter--OnStartPoint</code> and is degraded to a chord as the program is read.</p>
<p>Each of the five properties returns a <strong>new instance per read</strong>, so no two projects can share one
preset and the returned runner is safe to mutate before it is installed. The five brand tokens are
declared once as constants on the brand dependency and are then enumerated again, independently, in
the engine's preset writer, in the REST action's own switch and in the client's option constant —
the first two by reference to the constants, the client by spelling the names out. Nothing joins
those lists, and they are not the only places in the engine where the five are spelled out again, so
the option a reader picks and the preset that answers it agree by convention rather than by
construction.</p>
<p>The option list this panel offers is the client-side constant: <strong>Fanuc</strong>, <strong>Siemens</strong>,
<strong>Heidenhain</strong>, <strong>Syntec</strong>, <strong>Mazak</strong>, in that order. Brand names never translate.</p>
<h2 id="what-names-the-current-brand-and-what-names-the-target">What Names the Current Brand, and What Names the Target</h2>
<p>Every string on the panel and in its dialog is either the brand in force or the brand about to
replace it, and the same interpolation token means the opposite thing in two adjacent places. The
select is seeded from the snapshot's brand and re-seeded by a watch whenever the snapshot's brand
changes, so before any pick it reads the brand in force; from the moment a different option is
picked, <strong>nothing left on the panel names the brand in force</strong> — only the confirmation dialog names
both at once.</p>
<table>
<thead>
<tr>
<th>Where it is read</th>
<th>What it names</th>
</tr>
</thead>
<tbody>
<tr>
<td>The <strong>Controller brand</strong> select, before a pick</td>
<td>the brand in force, from the shared snapshot</td>
</tr>
<tr>
<td>The <strong>Controller brand</strong> select, after a pick</td>
<td>the target — the staged brand, not yet applied</td>
</tr>
<tr>
<td>The orange banner's <code>{brand}</code></td>
<td>the target</td>
</tr>
<tr>
<td>The dialog's <code>{brand}</code></td>
<td>the brand in force, or the literal word <em>current</em> when the snapshot carries no brand marker</td>
</tr>
<tr>
<td>The dialog's <code>{next}</code></td>
<td>the target</td>
</tr>
<tr>
<td>The success toast's <code>{brand}</code></td>
<td>the target</td>
</tr>
</tbody>
</table>
<p>The select is not a placeholder surface. It has no placeholder text: with a brand marker present it
shows that marker, and with none it shows only its own label. Because the select maps its value
through the option list and falls back to the raw value when no option matches, a runner whose marker
is a string outside the five — a controller file authored elsewhere, or a marker edited by hand —
displays that string verbatim in the closed select even though no option in the list can reproduce
it. Such a marker is not only cosmetic: the marker's other readers compare it against the five
tokens, so the writeback grammar falls back to its Fanuc-family form in every choice it makes — the
Fanuc variable prefix and comment spans, no keyword set, the conventional <code>F</code> position and a
parenthesized source note — and the negative-radius validation warning stays off.</p>
<h2 id="staging-and-the-two-buttons">Staging, and the Two Buttons</h2>
<p>The panel is one of the branch's two draft panels: the pick stages, it does not write.</p>
<ul>
<li><strong>Apply brand</strong> is enabled only while the staged value is non-empty <em>and</em> differs from the brand
the snapshot reports. It carries a spinner and both buttons disable while a request is in flight.</li>
<li><strong>Revert</strong> drops the staging by re-reading the snapshot's brand, and is enabled under the same
condition.</li>
</ul>
<p>Two consequences follow from that enablement rule. First, the REST action's same-brand re-flash — a
POST naming the brand already in force, which the action documents as per-case lossless — <strong>cannot be
issued from this node</strong>, because staging the current brand is not a change. Second, on a runner whose
marker is blank or outside the five options, every option counts as a change, so all five become
applicable.</p>
<p>Nothing guards a switch away from the node. The panel registers no before-switch gate, so selecting
another tree row discards the staging silently; returning to the node re-seeds the select from the
snapshot. The carry checkbox is not part of that staging and is not persisted anywhere: it is on when
the panel mounts and keeps whatever state it was left in for as long as the panel stays mounted.</p>
<h2 id="the-confirmation-flow">The Confirmation Flow</h2>
<p><strong>Apply brand</strong> opens a confirm dialog before any request is sent. Its title is <em>Switch controller
brand</em>, and it declares no button labels of its own, so both buttons come from Quasar's language
pack — which follows the app locale, and reads <strong>OK</strong> and <strong>Cancel</strong> in English. <strong>Cancel</strong> closes
the dialog and leaves the staging untouched. The body is one of two whole sentences chosen by the
carry checkbox — the two are separate strings rather than a concatenation, because the clause order
differs between languages:</p>
<ul>
<li>Unticked — <em>&quot;Replace the {brand} runner with the {next} preset? Machine settings reset to the
preset's defaults; the old brand's program-data tables are removed.&quot;</em></li>
<li>Ticked — <em>&quot;Replace the {brand} runner with the {next} preset? Machine settings reset to the
preset's defaults; the old brand's program-data tables are removed after their work-coordinate XYZ
is carried over.&quot;</em></li>
</ul>
<p>While a different brand is staged, an orange banner stands above the buttons with the same warning in
the target's voice: <em>&quot;Switching brand replaces the whole runner with the {brand} preset. Machine
settings reset to that preset's defaults and the old brand's program-data tables are removed —
switching back does not restore them.&quot;</em></p>
<p>Both sentences generalise, in opposite directions, and the exact reading is
<a href="#what-survives-and-what-is-swept">What Survives</a> below. The sweep is narrower than <em>the old brand's
program-data tables are removed</em>: the generic tool-offset table is program data and is never removed,
the retained common-variable table survives a switch among the three brands that proxy it, and
between Fanuc and Mazak the brand parameter table survives too. The reset is broader than a
cross-brand switch: the runner-owned machine entries return to the preset's defaults on <em>every</em>
apply, including the ones where nothing at all is swept.</p>
<h3 id="what-the-request-can-be-refused-with">What the request can be refused with</h3>
<p>The action refuses before it touches anything in three cases, and each reaches the same toast:</p>
<table>
<thead>
<tr>
<th>Refusal</th>
<th>Answer</th>
</tr>
</thead>
<tbody>
<tr>
<td>No project loaded</td>
<td><em>not found</em>, with the coded no-project payload — the one refusal here that a non-English locale re-renders</td>
</tr>
<tr>
<td>An NC program is playing</td>
<td><em>conflict</em>, rather than swapping the parser under a running session</td>
</tr>
<tr>
<td>A brand token outside the five</td>
<td><em>bad request</em>, naming the token it was given</td>
</tr>
</tbody>
</table>
<p>The third is unreachable from this node: the select offers only the five, and staging is only
possible for a value that differs from the marker in force, so the request always carries a token the
action's own switch recognises. It stays live for any other client of the endpoint.</p>
<p>Anything thrown after the work has begun is reported inside a success envelope instead: HTTP 200
carrying an unsuccessful flag and the exception's own message. The shared fetch helper raises both
shapes as the same kind of error, so the panel cannot tell a refusal from a failure part-way through.</p>
<h3 id="what-the-operator-sees-afterwards">What the operator sees afterwards</h3>
<p>Success raises an informational toast, <em>&quot;Controller brand switched to {brand}&quot;</em>, naming the target.
Failure raises a negative toast composed as <em>&ldquo;Switch controller brand&rdquo;</em> followed by the server's own
sentence — untranslated, because the envelope carries no code to re-render. Controller Brand is the
one panel in the branch with no local error handler at all: its failures surface through the shared
runner state's global notification, so the console line names that shared state rather than this
panel.</p>
<h2 id="what-the-switch-does">What the Switch Does</h2>
<p>The action runs four steps in a fixed order, and the order is what decides the outcome.</p>
<ol>
<li><strong>Capture.</strong> The outgoing work-coordinate provider — the first
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.IIsoCoordinateConfig.html">IIsoCoordinateConfig</a> in the runner's proxy-resolved list — is
asked for every coordinate id it currently holds a value for, and those id/offset pairs are held
aside. With the carry checkbox unticked the captured list is empty.</li>
<li><strong>Swap.</strong> The preset is assigned through
<a class="xref" href="../../../../api/Hi.MachiningProcs.LocalProjectService.html#Hi_MachiningProcs_LocalProjectService_SoftNcRunner">SoftNcRunner</a>, the single rewiring entry point. That
setter first hoists any brand parameter table baked directly into the outgoing runner's pipeline
list into the project's per-case list, so the incoming proxy can claim it; assigns the runner;
re-wires every proxy, the kinematics solver and the session script dictionaries; resets the
per-session runner state so the next run re-parses from scratch and clears the NC diagnostics; and
finally drops any hoisted table the incoming runner claimed through no proxy. Proxy re-wiring is
also where the new brand's own per-case tables are materialised: each get-or-create proxy installs
a deep clone of its seed only where the project holds no table of that type.</li>
<li><strong>Carry.</strong> The held offsets are written into the <em>new</em> provider, and only for ids that provider
also exposes. The step is skipped entirely when the new provider is the same object as the old
one, which is exactly the Fanuc-to-Mazak case.</li>
<li><strong>Sweep.</strong> Every per-case table the new runner resolves through no proxy is removed from the
project's per-case list, so the previous brand's tables do not linger unread.</li>
</ol>
<h2 id="what-is-carried">What Is Carried</h2>
<p>The carry moves <strong>only the work-coordinate XYZ offsets</strong>. No other table's values cross, and nothing
else on either plane is copied.</p>
<p>The accepted set is the intersection of what the outgoing provider currently holds with what the
incoming provider exposes, and the four providers do not expose the same ids. What each one allocates
by default:</p>
<table>
<thead>
<tr>
<th>Provider</th>
<th>Brands</th>
<th>Coordinate ids it exposes</th>
</tr>
</thead>
<tbody>
<tr>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Fanuc.FanucParameterTable.html">FanucParameterTable</a></td>
<td>Fanuc, Mazak</td>
<td>G54G59 and G54.1 P1P48, all seeded</td>
</tr>
<tr>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Syntec.SyntecParameterTable.html">SyntecParameterTable</a></td>
<td>Syntec</td>
<td>the same set, through the same address map — spelled <code>Pr</code> rather than <code>#</code></td>
</tr>
<tr>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Siemens.SiemensFrameTable.html">SiemensFrameTable</a></td>
<td>Siemens</td>
<td>G54G57, plus the extended series G505G599</td>
</tr>
<tr>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.HeidenhainDatumTable.html">HeidenhainDatumTable</a></td>
<td>Heidenhain</td>
<td>G54G59, aliased onto preset rows 16</td>
</tr>
</tbody>
</table>
<p>So the carry is lossy in ways the checkbox's caption does not say. A switch out of Fanuc, Syntec or
Mazak into Siemens keeps G54 through G57 and drops G58, G59 and all forty-eight G54.1 P offsets.
The reverse switch keeps G54 through G57 and drops the whole G505G599 extended series. Into
Heidenhain from one of those three, G54 through G59 cross and the G54.1 P offsets do not; from
Siemens, only G54 through G57 have anywhere to land. Between Fanuc, Syntec and Mazak the two id sets
match, so nothing is lost.</p>
<p>Whatever is dropped is dropped silently: an id the target does not expose is skipped without a
message, and the table that held it is removed by the sweep in the same request.</p>
<h2 id="what-survives-and-what-is-swept">What Survives and What Is Swept</h2>
<p>The sweep's keep set is the new runner's proxy-resolved list, so a per-case table survives exactly
when the target brand's preset proxies its type.</p>
<table>
<thead>
<tr>
<th>Per-case table</th>
<th>Proxied by</th>
<th>Behaviour on a switch</th>
</tr>
</thead>
<tbody>
<tr>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Generic.ToolOffsetTable.html">ToolOffsetTable</a></td>
<td>all five</td>
<td>survives every switch</td>
</tr>
<tr>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Fanuc.RetainedCommonVariableTable.html">RetainedCommonVariableTable</a></td>
<td>Fanuc, Syntec, Mazak</td>
<td>survives among those three; swept into Siemens or Heidenhain</td>
</tr>
<tr>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Fanuc.FanucParameterTable.html">FanucParameterTable</a></td>
<td>Fanuc, Mazak</td>
<td>survives between those two; swept otherwise</td>
</tr>
<tr>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Syntec.SyntecParameterTable.html">SyntecParameterTable</a></td>
<td>Syntec</td>
<td>swept on leaving Syntec</td>
</tr>
<tr>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Siemens.SiemensMachineDataTable.html">SiemensMachineDataTable</a></td>
<td>Siemens</td>
<td>swept on leaving Siemens</td>
</tr>
<tr>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Siemens.SiemensFrameTable.html">SiemensFrameTable</a></td>
<td>Siemens</td>
<td>swept on leaving Siemens</td>
</tr>
<tr>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Siemens.SiemensRParameterTable.html">SiemensRParameterTable</a></td>
<td>Siemens</td>
<td>swept on leaving Siemens</td>
</tr>
<tr>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Siemens.SiemensToolOffsetTable.html">SiemensToolOffsetTable</a></td>
<td>Siemens</td>
<td>swept on leaving Siemens, tool-name map included</td>
</tr>
<tr>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.HeidenhainDatumTable.html">HeidenhainDatumTable</a></td>
<td>Heidenhain</td>
<td>swept on leaving Heidenhain</td>
</tr>
<tr>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Heidenhain.HeidenhainParameterTable.html">HeidenhainParameterTable</a></td>
<td>Heidenhain</td>
<td>swept on leaving Heidenhain</td>
</tr>
<tr>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Heidenhain.HeidenhainQParameterTable.html">HeidenhainQParameterTable</a></td>
<td>Heidenhain</td>
<td>swept on leaving Heidenhain, and exposed by no node or endpoint in the branch</td>
</tr>
</tbody>
</table>
<p>Switching back does not restore a swept table. The returning proxy finds nothing of its type on the
project and clones a fresh table from its seed, so the rows come back at the preset's defaults and
the previous values are gone. The tool-name map is worth naming separately because it is not a table
of its own: it lives on the Siemens tool-offset table and leaves with it.</p>
<p>Beside the per-case tables, the preset's <strong>plain</strong> entries are replaced outright on every apply,
whatever the target brand. Those are the runner-owned machine settings, and they reset to that
preset's defaults even in the Fanuc-to-Mazak case where nothing at all is swept:</p>
<ul>
<li><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Generic.ToolingMcConfig.html">ToolingMcConfig</a> — the tool-change position, back to its
three-axis default, where X and Y are left unset and Z returns to zero.</li>
<li><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Generic.GenericBlockSkipConfig.html">GenericBlockSkipConfig</a> — the block-skip layers, back to
layer 1 alone. The Heidenhain preset carries no block-skip config at all, so the node itself is
gone after a switch to that brand.</li>
<li><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Generic.SubProgramFolderConfig.html">SubProgramFolderConfig</a> — the subprogram lookup folders,
back to an internal folder of <code>NC</code> and no external folder.</li>
<li>The macro iteration guards, back to the target preset's own guard set.</li>
</ul>
<p>The Object-Management Load and Paste on the branch root install a runner through the same setter and
the same sweep, so the same table survives or is lost by the same rule — but that path has no carry
step and no confirmation of its own.</p>
<h2 id="what-the-tree-does-afterwards">What the Tree Does Afterwards</h2>
<p>The panel derives its rebuild scope from its own id by stripping the fixed <code>/machine/brand</code> suffix,
so the scope is the branch root rather than this leaf, and it reports the structural change only on a
successful switch. The Control-Tree host then rebuilds that branch and adds the root to the expansion
list so the fresh children are visible. The rebuild makes its <strong>own</strong> request for the runner snapshot,
independent of the shared one the panels gate on.</p>
<p>The shared snapshot is re-read either way, in the call's finally block, together with the runner's
index key. Three things follow:</p>
<ul>
<li><strong>The selection does not move.</strong> Controller Brand exists on every brand, and the editor row's
remount key is composed from the node id and the node key, both unchanged — so the panel is not
remounted. The select is re-seeded by its watch rather than by a fresh mount, which is why the
staged value clears after a success and stays put after a failure that left the brand alone.</li>
<li><strong>A failure that reached the server after the swap leaves the tree describing the previous shape.</strong>
The regrow is skipped on failure, but the snapshot re-read is not; the select and the root's brand
badge then show whatever brand the server now reports, over a branch built before the attempt. The
staging clears with the brand, so <strong>Apply brand</strong> is disabled and the branch cannot be regrown from
this node. Moving the selection does not regrow it either — only a structural change reported on
that branch, a whole-tree rebuild, or the page rebuild a project change forces will bring the two
back into agreement.</li>
<li><strong>A failed snapshot re-read empties the branch's panels.</strong> That read resets the shared snapshot to
its empty value and raises its own toast, <em>&ldquo;Load controller settings&rdquo;</em> followed by the server's
message; every leaf panel then renders the shared no-runner line even where the tree still lists
the leaves.</li>
</ul>
<h2 id="layout">Layout</h2>
<ul>
<li><strong>General Setup</strong> Control Tree — the left dock of <code>/general-setup</code>
<ul>
<li><strong>Controller</strong> Node Row
<ul>
<li><strong>Machine / Controller</strong> Group Row
<ul>
<li><strong>Controller Brand</strong> Node Row — <code>equipment/controller/machine/brand</code></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>Editor Row — <strong>Controller Brand</strong> Panel
<ul>
<li>Shared Empty Line — <em>&quot;No NC runner — load a project first.&quot;</em>, in place of everything below it
while the shared snapshot reports no runner</li>
<li><strong>Controller brand</strong> Select — dense, outlined; five options in list order, <strong>Fanuc</strong>,
<strong>Siemens</strong>, <strong>Heidenhain</strong>, <strong>Syntec</strong>, <strong>Mazak</strong>; no clear button and no search field</li>
<li><strong>Carry work-coordinate XYZ (G54…) into the new brand's table</strong> CheckBox — dense, ticked when the
panel mounts</li>
<li>Warning Banner — orange, dense, rounded; present only while a different brand is staged</li>
<li>Button Row
<ul>
<li><strong>Apply brand</strong> Button — primary, disabled unless a different brand is staged, spinner while
the request is in flight</li>
<li><strong>Revert</strong> Button — flat, same enablement</li>
</ul>
</li>
</ul>
</li>
<li>Confirm Dialog — title <strong>Switch controller brand</strong>, one of the two message variants, <strong>Cancel</strong> and
<strong>OK</strong></li>
</ul>
<p>No controller item type registers a large content view, so the General Setup content column shows its
own empty hint while this node is selected.</p>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../../../index.html">HiNC App Anatomy</a> for git repository links.</p>
<h3 id="web-application">Web Application</h3>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/controlTree/SoftNcBrandPanel.vue</code> — this panel: the staged brand and
its watch, the carry checkbox and its default, the dirty rule behind both buttons, the confirm
dialog and its two message variants, and the rebuild scope derived by stripping the leaf's own id
suffix.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcEmptyState.vue</code> — the shared no-runner line this
panel renders in place of its body.</li>
<li><code>wwwroot-src/src/composables/useSoftNcRunner.ts</code> — the shared snapshot the select is seeded from,
the brand-switch call with its success and failure toasts, and the snapshot and key re-read that
runs whichever way the call ends.</li>
<li><code>wwwroot-src/src/api/softNcRunner.ts</code> — the five-brand option constant, the snapshot shape, and the
typed brand-switch request carrying the target and the carry flag.</li>
<li><code>wwwroot-src/src/api/http.ts</code> — why a refusal and a part-way failure arrive as the same kind of
error, and why only a coded payload is re-rendered in the app locale.</li>
<li><code>wwwroot-src/src/components/controlTree/softNcItemTypes.ts</code> — this node's id, label and label key,
and the branch builder that regrows the whole child set from a fresh snapshot after the switch.</li>
<li><code>wwwroot-src/src/components/controlTree/useControlTreeHost.ts</code> — the branch root the scope resolves
to, and the rebuild that expands it and re-resolves the selection.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcRunnerRootPanel.vue</code> — the brand badge and runner-file
caption that change with the snapshot, and the Object-Management install that replaces the runner
by the other route.</li>
<li><code>wwwroot-src/src/i18n/en/softNc.ts</code> — the tree label, the select label, the checkbox caption, the
banner sentence, the button captions and the two toast contexts.</li>
<li><code>wwwroot-src/src/i18n/en/dialog.ts</code> — the dialog title, the two message variants and the <em>current</em>
fallback used when the snapshot carries no brand marker.</li>
<li><code>Mech/SoftNcRunnerController.cs</code> — the brand-switch action: its refusals, the four ordered steps,
the sweep of per-case tables the new runner references through no proxy, and the runner-install
action that shares that sweep.</li>
<li><code>Common/ApiError.cs</code> — the coded no-project payload, the one refusal here a non-English locale can
re-render.</li>
</ul>
<h3 id="hiapi-engine">HiAPI Engine</h3>
<ul>
<li><code>HiMech/NcParsers/SoftNcRunner.cs</code> — the five brand presets as literal dependency lists, each
property returning a fresh instance, together with the segmenter, initializers and syntax list each
brand brings; and the proxy resolution every read on this branch goes through.</li>
<li><code>HiMech/NcParsers/NcRunnerSuit.cs</code> — the suit that holds the runner beside the project's own
per-case dependency list, and the proxy wiring the swap triggers.</li>
<li><code>HiMech/NcParsers/ControllerPresetWriter.cs</code> — the brand token list and preset factory that mirror
the REST action's switch, and the controller-file extension a saved runner takes.</li>
<li><code>HiMech/NcParsers/Dependencys/CncBrandDependency.cs</code> — the brand marker itself: five tokens and
one free-form string property, with no behaviour of its own; its readers live elsewhere.</li>
<li><code>HiMech/NcOpt/SoftNcOptProc.cs</code> — where NC optimization reads the marker back out of the session's
effective dependency list, for the writeback grammar.</li>
<li><code>HiMech/NcOpt/NcOptPieceClassifier.cs</code> — the re-interpolation guards, none of them keyed on the
brand; the arc guard keys on the centre's provenance instead.</li>
<li><code>HiMech/NcParsers/Keywords/Generic/ArcCenterSource.cs</code> — the two centre-provenance stamps: the one
the arc guard refuses, and the one that tells the writeback a split arc's fragments share the <code>CC</code>
line unchanged. An arc whose own block states its centre carries neither.</li>
<li><code>HiMech/NcParsers/NcWriteback/NcPatchWriter.cs</code> — the writeback grammar the marker selects: the
variable prefix, the comment spans, the Heidenhain keyword set, the pre-feed word patterns that
place an inserted <code>F</code>, and the trailing-comment shape the embedded source note takes.</li>
<li><code>HiMech/NcParsers/PostLogicSyntaxs/RadiusCompensationSyntax.cs</code> — the negative-radius validation
warning raised only where the marker reads Heidenhain, in a syntax all five presets carry.</li>
<li><code>HiMech/NcParsers/Dependencys/IIsoCoordinateConfig.cs</code> — the work-coordinate provider contract the
carry reads and writes through, including the id enumeration that decides what crosses.</li>
<li><code>HiMech/NcParsers/Dependencys/IsoCoordinateAddressMap.cs</code> — the G54G59 and G54.1 P1P48 id set the
Fanuc-family and Syntec tables expose, and the seeding that makes every one of them present.</li>
<li><code>HiMech/NcParsers/Dependencys/Siemens/SiemensFrameTable.cs</code> — the frame table's own allocation,
G54G57 plus the extended G505G599 series, which is what a carry into Siemens accepts.</li>
<li><code>HiMech/NcParsers/Dependencys/Heidenhain/HeidenhainDatumTable.cs</code> — the datum table's preset rows
and the six of them aliased onto G54G59.</li>
<li><code>HiMech/NcParsers/Dependencys/Fanuc/FanucParameterTableProxy.cs</code> — the get-or-create proxy shape
the brand tables share: a serialized seed on the runner, a deep clone installed only where the
project holds no table of that type, and a take thereafter.</li>
<li><code>HiMech/NcParsers/Dependencys/Generic/ToolOffsetTableProxy.cs</code> — the proxy every preset carries,
and therefore the reason the generic tool-offset table survives every switch.</li>
<li><code>HiMech/NcParsers/Dependencys/Generic/ToolingMcConfig.cs</code> — the tool-change position's preset
default, one of the runner-owned values every apply resets.</li>
<li><code>HiMech/NcParsers/Dependencys/Generic/GenericBlockSkipConfig.cs</code> — the block-skip layers' preset
default, and the config the Heidenhain preset does not carry.</li>
<li><code>HiMech/NcParsers/Dependencys/Generic/SubProgramFolderConfig.cs</code> — the subprogram folders' preset
defaults.</li>
<li><code>HiNc/MachiningProcs/LocalProjectService.cs</code> — the facade setter the swap assigns through: the
hoist of pre-proxy baked tables, the proxy and kinematics re-wiring, the per-session runner state
reset, and the drop of hoisted tables no proxy claimed.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="index.html">Machine and Controller Plane</a> — the plane this leaf opens, and the storage column that says
which of its other leaves this control resets</li>
<li><a class="xref" href="../brand-matrix.html">Brand Matrix</a> — which nodes each brand grows, so what the branch looks like
after the switch is a lookup rather than a surprise</li>
<li><a class="xref" href="../editing-contract.html">Editing Contract</a> — the commit and error rules the rest of the branch
follows, and which this panel departs from on purpose</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,489 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Machine and Controller Plane | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Machine and Controller Plane | HiAPI-C# 2025 ">
<link rel="icon" href="../../../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../../../public/docfx.min.css">
<link rel="stylesheet" href="../../../../public/main.css">
<meta name="docfx:navrel" content="../../../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../../../index.html">
<img id="logo" class="svg" src="../../../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-ControllerMachine">
<h1 id="machine-and-controller-plane">Machine and Controller Plane</h1>
<p>Machine / Controller is the first of the Controller branch's two plane stems: what describes the
machine and the control that drives it, edited one leaf at a time. It lives on the General Setup
page at <code>/general-setup</code> under the Control-Tree id <code>equipment/controller/machine</code>, and each of its
leaves takes that id plus one segment — <code>brand</code>, <code>limits</code>, <code>rapid</code>, <code>home</code>, <code>tool-change</code>,
<code>parameters</code>, <code>m-codes</code>, <code>canned-cycle</code>, <code>block-skip</code>, <code>subprograms</code>, <code>indexing-positions</code>,
<code>native</code>. The stem is not gated: it is part of the branch builder's return value rather than a
conditional entry, so it appears wherever a runner resolves and always carries the leaves no flag
guards.</p>
<div class="IMPORTANT">
<h5>Important</h5>
<p>Anatomy makes no claim that any id named here is stable across versions. The migration hop that
keeps an older <code>?tree=</code> value resolving is described in
<a class="xref" href="../../../platform/tree-ids-and-routes.html">Tree Ids and Routes</a>.</p>
</div>
<p>Selecting the stem shows the shared group editor: its own introduction over a clickable list of its
children. The introduction reads <em>&quot;Machine and controller presets that travel with the runner file —
brand, travel limits, rapid rates, home / G28 reference, the tool-change position and the controller
parameters (in both a domain-grouped interface form and the native parameter form).&quot;</em></p>
<h2 id="what-travels-with-the-runner-file-means">What &ldquo;Travels With the Runner File&rdquo; Means</h2>
<p>The phrase names an <strong>ownership</strong> boundary, not a file on disk, and the boundary does not fall where
the introduction's list suggests. Half of what this plane edits is stored on the project.</p>
<h3 id="the-suits-two-members">The suit's two members</h3>
<p><a class="xref" href="../../../../api/Hi.NcParsers.NcRunnerSuit.html">NcRunnerSuit</a> carries the runner, <a class="xref" href="../../../../api/Hi.NcParsers.SoftNcRunner.html">SoftNcRunner</a>, and the
project's own <a class="xref" href="../../../../api/Hi.NcParsers.NcRunnerSuit.html#Hi_NcParsers_NcRunnerSuit_PerCaseNcDependencyList">PerCaseNcDependencyList</a>, and serializes the two side
by side. The runner half honours <a class="xref" href="../../../../api/Hi.NcParsers.NcRunnerSuit.html#Hi_NcParsers_NcRunnerSuit_SoftNcRunnerFile">SoftNcRunnerFile</a>: with a
project-relative path recorded there the runner XML is written to that side file and referenced from
the project; with none recorded it is inlined in the project file. The per-case half is always
inlined — the suit writes it as a nested element with no file reference of its own.</p>
<p>The web service reads that recorded path and never writes one. On a project that has never been
given a side file, both halves therefore sit inside the same project file, in two slots whose
contents behave differently the moment a runner is swapped. A controller file is one serialized
runner. Its canonical extension is <code>.Controller</code>
(<a class="xref" href="../../../../api/Hi.NcParsers.ControllerPresetWriter.html#Hi_NcParsers_ControllerPresetWriter_FileExtension">FileExtension</a>), and the branch root's Load browser
accepts the earlier <code>.SoftNcRunner</code> spelling beside it.</p>
<h3 id="which-half-a-value-lands-in">Which half a value lands in</h3>
<p>A runner's <a class="xref" href="../../../../api/Hi.NcParsers.SoftNcRunner.html#Hi_NcParsers_SoftNcRunner_PipelineNcDependencyList">PipelineNcDependencyList</a> holds two shapes of entry.</p>
<p>A <strong>plain dependency</strong> is the runner's own. Its edited values serialize with the runner, and
installing another runner replaces the instance outright.</p>
<p>A <strong>proxy</strong><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.INcDependencyProxy.html">INcDependencyProxy</a> — carries no live data. Each brand
parameter-table proxy holds a fixed machine-config seed and serializes only that seed;
<a class="xref" href="../../../../api/Hi.NcParsers.NcRunnerSuit.html#Hi_NcParsers_NcRunnerSuit_WireNcDependencyProxies_">WireNcDependencyProxies</a><small>(API)</small> deep-clones the seed
into the project's per-case list only when the project holds no table of that type, and an existing
table wins. Reads and writes then resolve through
<a class="xref" href="../../../../api/Hi.NcParsers.SoftNcRunner.html#Hi_NcParsers_SoftNcRunner_GetEffectiveNcDependencyList_">GetEffectiveNcDependencyList</a><small>(API)</small>, which
substitutes the project's table for the placeholder. All four brand tables' proxies are written to
the same shape, and the seed is set once at construction and never rewritten by an edit.</p>
<p>Two consequences follow, and they are what the introduction's phrasing hides:</p>
<ul>
<li>The per-axis rows, the M-code declarations and the native parameter rows are stored on the
<strong>project</strong>, because they are all rows of the brand parameter table. Installing a same-brand
runner file leaves them where they are, apart from the rotary reference and rapid rows that any
re-bind of the suit re-stamps.</li>
<li>Saving a runner file captures the proxy's seed, not the table the panels have been editing. A
<code>.Controller</code> written after the axis tables were tuned carries the preset seed those tables were
cloned from.</li>
</ul>
<p>Why the table is per-case at all splits by brand. The Fanuc-family and Syntec tables mix machine
configuration with the per-case work-coordinate offsets the program-data plane edits, which the two
planes could not otherwise separate. The Siemens machine-data and Heidenhain tables carry no work
coordinates — those two brands keep their offsets in a separate per-case table — and are per-case
for the other half of the same reason: the project's own table wins over the seed, so machine data
and machine parameters already edited survive a same-brand runner install instead of resetting to
the preset's.</p>
<h2 id="the-planes-leaves">The Plane's Leaves</h2>
<p>Ordered as the branch builder pushes them: the ungated leaves first, then each gated leaf behind the
snapshot flag that grows it. Which brands satisfy each flag is
<a class="xref" href="../brand-matrix.html">Brand Matrix</a>; the column that page does not carry is the last
one here.</p>
<table>
<thead>
<tr>
<th>Node segment</th>
<th>Label the tree shows</th>
<th>Gate</th>
<th>What backs it</th>
<th>Stored on</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>brand</code></td>
<td>Controller Brand</td>
<td>ungated</td>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.CncBrandDependency.html">CncBrandDependency</a></td>
<td>runner</td>
</tr>
<tr>
<td><code>limits</code></td>
<td>Machine Limits (Stroke)</td>
<td>ungated</td>
<td>the brand parameter table's stroke-limit rows</td>
<td>project</td>
</tr>
<tr>
<td><code>rapid</code></td>
<td>Rapid Feedrates</td>
<td>ungated</td>
<td>the brand parameter table's rapid-rate rows</td>
<td>project</td>
</tr>
<tr>
<td><code>home</code></td>
<td>Home / G28 Reference</td>
<td>ungated</td>
<td>the brand parameter table's reference-position rows</td>
<td>project</td>
</tr>
<tr>
<td><code>tool-change</code></td>
<td>Tool-Change Position</td>
<td>ungated</td>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Generic.ToolingMcConfig.html">ToolingMcConfig</a></td>
<td>runner</td>
</tr>
<tr>
<td><code>parameters</code></td>
<td>Controller Parameters</td>
<td>ungated</td>
<td>the brand parameter table, plus the runner's iteration guards</td>
<td>split</td>
</tr>
<tr>
<td><code>m-codes</code></td>
<td>M-Code Declarations</td>
<td><code>hasNativeTable</code></td>
<td>the brand parameter table's declaration map and tool-change trigger</td>
<td>project</td>
</tr>
<tr>
<td><code>canned-cycle</code></td>
<td>Canned Cycle (Peck)</td>
<td><code>hasCannedCycle</code></td>
<td>the brand parameter table, or <a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Generic.FallbackConfig.html">FallbackConfig</a></td>
<td>brand-dependent</td>
</tr>
<tr>
<td><code>block-skip</code></td>
<td>Block Skip / Delete</td>
<td><code>hasBlockSkip</code></td>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Generic.GenericBlockSkipConfig.html">GenericBlockSkipConfig</a></td>
<td>runner</td>
</tr>
<tr>
<td><code>subprograms</code></td>
<td>Subprogram Folders</td>
<td><code>hasSubprogramFolders</code></td>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Generic.SubProgramFolderConfig.html">SubProgramFolderConfig</a></td>
<td>runner</td>
</tr>
<tr>
<td><code>indexing-positions</code></td>
<td>Indexing Position Tables</td>
<td><code>hasIndexingTables</code></td>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Siemens.SiemensMachineDataTable.html">SiemensMachineDataTable</a></td>
<td>project</td>
</tr>
<tr>
<td><code>native</code></td>
<td>Parameters (Native)</td>
<td><code>hasNativeTable</code></td>
<td>the brand parameter table, as its raw dictionaries</td>
<td>project</td>
</tr>
</tbody>
</table>
<p>Every label above is a translated role string rather than a type name, so the tree renders the
translation and a locale change rewrites the rows without touching an id.</p>
<p>One flag grows two of these leaves: <code>hasNativeTable</code> gates both M-Code Declarations and Parameters
(Native), because the declarations live on the same table the native form exposes.</p>
<h3 id="two-leaves-that-straddle-the-boundary">Two leaves that straddle the boundary</h3>
<p><strong>Controller Parameters</strong> is the one leaf whose fields do not share a storage half. Max spindle
speed, the cutter-compensation startup type and the tool-axis direction are written onto the brand
parameter table, so they are project-owned; the macro loop guards under <em>Macro loop guards
(advanced)</em> are written onto the runner's own iteration-guard dependencies, so they are
runner-owned. The panel gates its whole body on the parameter table's presence, which is the
project-owned half.</p>
<p><strong>Canned Cycle (Peck)</strong> is the one leaf whose storage half depends on the brand. Where the brand
table supplies the clearance it is a row of that table and project-owned; where the preset supplies
it through the generic fallback config it is a plain runner entry and runner-owned. The read reports
which of the two answered, and the panel names the source in the caption under the field.</p>
<h2 id="what-an-install-or-a-brand-switch-does">What an Install or a Brand Switch Does</h2>
<p>Object-Management Load, Paste and XML apply all install a runner through
<a class="xref" href="../../../../api/Hi.MachiningProcs.LocalProjectService.html#Hi_MachiningProcs_LocalProjectService_SoftNcRunner">SoftNcRunner</a>, and a brand switch assigns the brand's
preset through the same setter. Both are followed by the same sweep, and for this plane the outcome
splits exactly along the storage column.</p>
<p>The <strong>runner-owned</strong> leaves take the incoming runner's values, so a brand switch returns the
tool-change position, the block-skip layers, the subprogram folders and the macro guards to the new
preset's defaults. Installing a controller file of the brand already in force replaces those same
four with the file's values while the project keeps its own parameter table — the same-brand
re-flash the REST surface's own remark calls per-case lossless, naming those four as what resets.
That re-flash is not reachable from the brand select, whose apply is enabled only while the staged
brand differs from the active one (<a class="xref" href="brand-switch.html">Brand Switch</a>); Load, Paste
and XML apply are the surfaces that issue it.</p>
<p>The <strong>project-owned</strong> leaves keep the project's table: the incoming proxy re-binds to it rather than
cloning its seed over it. Before the swap, any brand parameter table baked directly into the
outgoing runner's pipeline list — the storage a project file saved before that table went per-case
still uses — is moved into the per-case list so the incoming proxy can claim it, and a moved table
no proxy claims is dropped again afterwards. The chain walk that follows the re-bind is the only
part of the swap that writes into the surviving table: it stamps the chain's axes onto it, and with
them every rotary axis's reference position and rapid rate.</p>
<p>A switch to a <strong>different</strong> brand ends with the per-case tables the new runner resolves through no
proxy removed, so the previous brand's parameter table does not linger unread. Switching back does
not restore it; a fresh table is cloned from the new preset's seed instead.</p>
<p>Both writes are refused while an NC program is playing, and each answers with a conflict rather than
swapping the parser under a running session.</p>
<h2 id="one-table-two-forms">One Table, Two Forms</h2>
<p>Machine Limits, Rapid Feedrates, Home / G28 Reference, M-Code Declarations, most of Controller
Parameters and all of Parameters (Native) read and write one object: the brand parameter table.
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.ControllerParameterTableBase.html">ControllerParameterTableBase</a> declares the role accessors — home
reference, axis set, rapid feedrates, stroke limits, spindle control, M-code declarations, the
tool-change trigger — and each brand subclass maps those roles onto its own parameter numbers. The
domain-grouped leaves call the accessors; the native leaf writes the raw dictionary cell under the
number. Editing one form changes what the other shows.</p>
<p>Where a brand stores a role in its own unit the two forms differ by that unit, and the peck
clearance is the case that ships: the Syntec table stores it in microns and its accessor converts,
while the Fanuc-family table stores millimetres directly. The interface leaf therefore shows the
same number on every brand and the native leaf shows the brand's raw stored value.</p>
<p>What a freshly switched brand shows is decided by that brand's default table. Every one of them
declares a linear X, Y and Z with a reference position of zero and a rapid rate per axis, so Rapid
Feedrates and Home / G28 Reference open populated. None of them declares a stroke limit, so Machine
Limits opens with empty cells on every brand; and only the Siemens default table pre-declares any
M-codes, so M-Code Declarations opens empty on the other four brands, Mazak included, because it
carries the Fanuc table. An axis with no rapid-rate row of its own reads a fixed default rather than
a blank cell.</p>
<p>The axis rows those leaves list are the parameter table's own axis entries. Whenever the suit or the
machine chain is re-bound, a chain walk stamps the chain's axis codes onto that table: it adds axes
and removes none, and a linear axis keeps what the table already holds, gaining a reference position
only where it had none. A rotary axis is the exception — every walk rewrites its reference position
to 0 deg and its rapid rate to 36000 deg/min, so a tuned rotary value does not survive the next
stamp. The chain itself is edited on <a class="xref" href="../../machine-tool.html">Machine Tool</a>.</p>
<h2 id="layout">Layout</h2>
<ul>
<li><strong>General Setup</strong> Control Tree — the left dock of <code>/general-setup</code>
<ul>
<li><strong>Controller</strong> Node Row
<ul>
<li><strong>Machine / Controller</strong> Group Row — <code>equipment/controller/machine</code>
<ul>
<li><strong>Controller Brand</strong> Node Row</li>
<li><strong>Machine Limits (Stroke)</strong> Node Row</li>
<li><strong>Rapid Feedrates</strong> Node Row</li>
<li><strong>Home / G28 Reference</strong> Node Row</li>
<li><strong>Tool-Change Position</strong> Node Row</li>
<li><strong>Controller Parameters</strong> Node Row</li>
<li><strong>M-Code Declarations</strong> Node Row — grown while the snapshot reports a brand parameter table</li>
<li><strong>Canned Cycle (Peck)</strong> Node Row — grown while it reports a peck-clearance provider</li>
<li><strong>Block Skip / Delete</strong> Node Row — grown while it reports a block-skip config</li>
<li><strong>Subprogram Folders</strong> Node Row — grown while it reports a subprogram-folder config</li>
<li><strong>Indexing Position Tables</strong> Node Row — grown while the brand table is the Siemens
machine-data table</li>
<li><strong>Parameters (Native)</strong> Node Row — grown on the same flag as <strong>M-Code Declarations</strong></li>
</ul>
</li>
<li><strong>Program Data</strong> Group Row — the plane beside this one</li>
</ul>
</li>
</ul>
</li>
<li>Editor Row — the panel of whichever node is selected
<ul>
<li><strong>Machine / Controller</strong> Group Panel
<ul>
<li>Intro Caption — the stem's introduction line</li>
<li>Child List — one bordered, separated row per leaf above, each showing the leaf's label in the
theme's primary colour with a right chevron; a click selects that leaf</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>The stem's own editor holds no field, so nothing on this plane is edited from the group row itself.</p>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../../../index.html">HiNC App Anatomy</a> for git repository links.</p>
<h3 id="web-application">Web Application</h3>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/controlTree/softNcItemTypes.ts</code> — the branch builder: the machine
stem's id, label key and intro key, the leaves it pushes unconditionally, and the snapshot flag
each further leaf is pushed behind, in the order the table above lists them.</li>
<li><code>wwwroot-src/src/components/controlTree/GroupInfoPanel.vue</code> — the stem's editor: the intro caption
and the clickable child list that selects a leaf.</li>
<li><code>wwwroot-src/src/components/controlTree/useControlTreeHost.ts</code> — builds the <code>equipment/controller</code>
root this stem hangs from, inside the equipment group.</li>
<li><code>wwwroot-src/src/api/softNcRunner.ts</code> — the snapshot shape the builder reads its flags from, and
the typed wrappers over each leaf's reader and writers.</li>
<li><code>wwwroot-src/src/composables/useSoftNcRunner.ts</code> — the shared snapshot the leaf panels gate on, and
the install and brand-switch calls that regrow the branch.</li>
<li><code>wwwroot-src/src/i18n/en/softNc.ts</code> — the stem's introduction line and every leaf label in the
table above.</li>
<li><code>Mech/SoftNcRunnerController.cs</code> — the plane's REST surface: the presence flags, the per-group
readers and writers, the runner install, and the brand switch with its carry and its sweep of
per-case tables the new runner references through no proxy.</li>
</ul>
<h3 id="hiapi-engine">HiAPI Engine</h3>
<ul>
<li><code>HiMech/NcParsers/NcRunnerSuit.cs</code> — the suit: the runner, its optional side-file path, the
per-case list, the flat serialization that inlines the per-case half and file-references the
runner half, and the proxy wiring.</li>
<li><code>HiMech/NcParsers/SoftNcRunner.cs</code> — the pipeline list this plane's dependencies sit in, the five
brand presets that decide which of them a brand carries, the proxy resolution every read goes
through, and the chain configuration that stamps axis codes onto the table.</li>
<li><code>HiMech/NcParsers/ControllerPresetWriter.cs</code> — the canonical controller-file extension and the
writer that renders one brand preset as a standalone runner file.</li>
<li><code>HiMech/NcParsers/Dependencys/INcDependencyProxy.cs</code> — the maker-and-taker contract: what a proxy
may serialize, and why its resolved data is never written into the runner.</li>
<li><code>HiMech/NcParsers/Dependencys/Fanuc/FanucParameterTableProxy.cs</code> — the get-or-create proxy behind
the Fanuc-family table the Fanuc and Mazak presets share, and the shape the other three brand
tables each repeat in a proxy class of their own: a serialized seed on the runner, a deep clone
installed into the project only when the project has no table of that type, and a take thereafter.</li>
<li><code>HiMech/NcParsers/Dependencys/ControllerParameterTableBase.cs</code> — the role accessors the
domain-grouped leaves call, the per-brand parameter numbers they map onto, and the raw
dictionaries the native leaf edits.</li>
<li><code>HiMech/NcParsers/Dependencys/Fanuc/FanucParameterTable.cs</code>,
<code>HiMech/NcParsers/Dependencys/Syntec/SyntecParameterTable.cs</code>,
<code>HiMech/NcParsers/Dependencys/Siemens/SiemensMachineDataTable.cs</code>,
<code>HiMech/NcParsers/Dependencys/Heidenhain/HeidenhainParameterTable.cs</code> — the four brand tables and
their default seeds: the axis rows, the rapid rates, the peck clearance and its unit, and the only
pre-declared M-codes on the plane.</li>
<li><code>HiMech/NcParsers/Dependencys/Generic/ToolingMcConfig.cs</code>,
<code>HiMech/NcParsers/Dependencys/Generic/GenericBlockSkipConfig.cs</code>,
<code>HiMech/NcParsers/Dependencys/Generic/SubProgramFolderConfig.cs</code>,
<code>HiMech/NcParsers/Dependencys/Generic/FallbackConfig.cs</code> — the plain runner-owned dependencies
behind the tool-change, block-skip, subprogram and fallback-clearance leaves, and the values each
one serializes with the runner.</li>
<li><code>HiMech/NcParsers/Dependencys/Fanuc/FanucGotoIterationDependency.cs</code> — a macro guard, the
runner-owned half of the Controller Parameters leaf.</li>
<li><code>HiMech/NcParsers/Dependencys/CncBrandDependency.cs</code> — the brand marker the first leaf edits.</li>
<li><code>HiNc/MachiningProcs/LocalProjectService.cs</code> — the facade setter every install and brand switch
assigns through: the hoist of pre-proxy baked tables, the drop of the ones no proxy claims, the
proxy re-binding and the chain-driven axis stamp.</li>
<li><code>HiGeom/Common/XmlUtils/XmlUtil.cs</code> — the file-reference helper that decides whether the runner is
written to its side file or inlined.</li>
</ul>
<h2 id="pages">Pages</h2>
<p>Ordered by the first node each page owns, as the plane lists them.</p>
<ul>
<li><a class="xref" href="brand-switch.html">Controller Brand</a> — The brand select and its staged apply,
and what a brand change carries, resets and destroys</li>
<li><a class="xref" href="per-axis-tables.html">Per-Axis Tables</a> — The four leaves whose rows follow the
machine chain: stroke limits, rapid rates, the G28 reference and the tool-change position</li>
<li><a class="xref" href="interface-parameters.html">Interface Parameters</a> — The domain-grouped parameter form and
the peck-clearance leaf beside it, including the fields whose storage half differs</li>
<li><a class="xref" href="m-code-declarations.html">M-Code Declarations</a> — What a declared machine M-code consumes, the
tool-change trigger mode, and the codes an undeclared M word still warns about</li>
<li><a class="xref" href="program-reading.html">Program Reading</a> — The two leaves that decide how a program
is read rather than how the machine moves: block-skip layers and the subprogram lookup folders</li>
<li><a class="xref" href="indexing-positions.html">Indexing Positions</a> — The coded-position tables and the
per-axis assignment that decides which axis reads which</li>
<li><a class="xref" href="native-parameters.html">Native Parameters</a> — The raw parameter form: brand
numbering, raw stored units, and the same table every other leaf on this plane edits</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="../index.html">Controller Branch</a> — the branch this plane is half of, its runner root and the program-data
plane beside it</li>
<li><a class="xref" href="../program-data/index.html">Program Data Plane</a> — that plane itself, whose leaves are project-owned throughout
where this one's ownership splits between the runner file and the project</li>
<li><a class="xref" href="../brand-matrix.html">Brand Matrix</a> — which brand satisfies each gate named above, and what each
flag actually probes</li>
<li><a class="xref" href="../editing-contract.html">Editing Contract</a> — the commit, rollback and rendering rules every leaf on
this plane inherits, and the panels that depart from them</li>
<li><a class="xref" href="brand-switch.html">Controller Brand</a> — the control that replaces the runner, and therefore
everything in the runner-owned column here</li>
<li><a class="xref" href="per-axis-tables.html">Per-Axis Tables</a> — the per-axis leaves whose rows come from the machine chain
and whose values are stored on the project</li>
<li><a class="xref" href="interface-parameters.html">Interface Parameters</a> — the interface parameter form and the peck-clearance leaf, the
two entries that straddle the storage boundary</li>
<li><a class="xref" href="m-code-declarations.html">M-Code Declarations</a> — the declaration map on the same table the per-axis leaves edit</li>
<li><a class="xref" href="program-reading.html">Program Reading</a> — the block-skip and subprogram leaves, the two settings
that change how a program is read rather than how the machine moves</li>
<li><a class="xref" href="indexing-positions.html">Indexing Position Tables</a> — the leaf that grows only where the brand table is the
Siemens machine-data table</li>
<li><a class="xref" href="native-parameters.html">Native Parameters</a> — the raw form of the table behind most of this plane</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,448 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Indexing Position Tables | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Indexing Position Tables | HiAPI-C# 2025 ">
<link rel="icon" href="../../../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../../../public/docfx.min.css">
<link rel="stylesheet" href="../../../../public/main.css">
<meta name="docfx:navrel" content="../../../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../../../index.html">
<img id="logo" class="svg" src="../../../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-ControllerIndexingPositions">
<h1 id="indexing-position-tables">Indexing Position Tables</h1>
<p>Indexing Position Tables is the machine plane's single-brand leaf: the two global position lists a
Sinumerik control resolves coded-position words against, edited as drafts and saved whole. It lives
on the General Setup page at <code>/general-setup</code> under the Control-Tree id
<code>equipment/controller/machine/indexing-positions</code>, and of the five controller brands only Siemens
grows it. The tables it edits are machine data of the Siemens machine-data table; the assignment
that decides which axis reads which of them is shown here and edited on the sibling leaf,
<a class="xref" href="native-parameters.html">Parameters (Native)</a>.</p>
<div class="IMPORTANT">
<h5>Important</h5>
<p>Anatomy makes no claim that any id named here is stable across versions. The migration hop that
keeps an older <code>?tree=</code> value resolving is described in
<a class="xref" href="../../../platform/tree-ids-and-routes.html">Tree Ids and Routes</a>.</p>
</div>
<h2 id="the-one-single-brand-node">The One Single-Brand Node</h2>
<p>The branch builder pushes this leaf behind the <code>hasIndexingTables</code> snapshot flag, and that flag is a
<strong>pure type probe</strong>: it reports whether the runner's brand parameter table <em>is</em> the
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Siemens.SiemensMachineDataTable.html">SiemensMachineDataTable</a>. Nothing about the tables' contents
is examined, so the node grows on every Siemens runner whether or not a single position has been
entered. Only the Siemens preset carries that table, which is why this leaf is the machine plane's
one single-brand row in the <a class="xref" href="../brand-matrix.html">Brand Matrix</a>; the program-data
plane beside it has several.</p>
<p>The flag and the panel's own read do not ask quite the same question. The flag inspects the <strong>first</strong>
brand parameter table in the resolved dependency list; the panel's reader looks for a Siemens
machine-data table <strong>anywhere</strong> in it. The two agree on every shipped preset, where the Siemens
machine-data table is the only parameter table present.</p>
<p>That table is also the sole implementer of
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.IIndexingPositionConfig.html">IIndexingPositionConfig</a> in the engine, so nothing else in a runner
can supply an indexing table and no other brand can grow this node by carrying a different type.</p>
<h2 id="what-the-two-tables-are">What the Two Tables Are</h2>
<p>The leaf edits <a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Siemens.SiemensMachineDataTable.html#Hi_NcParsers_Dependencys_Siemens_SiemensMachineDataTable_IndexAxPosTab1">IndexAxPosTab1</a> and
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Siemens.SiemensMachineDataTable.html#Hi_NcParsers_Dependencys_Siemens_SiemensMachineDataTable_IndexAxPosTab2">IndexAxPosTab2</a> — the general machine
data MD10910 and MD10930. They describe axes that take up discrete stations rather than arbitrary
positions: Hirth-coupled tables, indexing rotaries, turret-style workholders.</p>
<ul>
<li><strong>They are global, not per-axis.</strong> Each is one ordered list shared by every axis assigned to it, so
an edit to Table 1 changes every axis whose assignment reads 1.</li>
<li><strong>Row order is the position number, and numbering is 1-based.</strong> Row 1 is indexing position 1. The
used-length machine data beside each table — MD10900 and MD10920 — is not stored: the list count
<em>is</em> the used length, which is why the panel has no length field.</li>
<li><strong>Values are in the axis' own native units</strong> — degrees on a rotary axis, millimetres on a linear
one — and the panel prints no unit suffix on the cells, because the unit follows whichever axes
read the table.</li>
<li><strong>Values must be strictly ascending</strong>, and where a modular rotary axis reads the table they must
also satisfy <code>0 ≤ position &lt; 360</code>.</li>
<li><strong>At most 60 entries per table</strong>, the MD10910 / MD10930 array size. Finer station counts are what
the equidistant definition below exists for.</li>
</ul>
<h3 id="what-consumes-them">What consumes them</h3>
<p>The tables are read at the write stage of the parsing pipeline, through
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.IIndexingPositionConfig.html#Hi_NcParsers_Dependencys_IIndexingPositionConfig_TryGetIndexingPosition_">TryGetIndexingPosition</a><small>(API)</small>
and <a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.IIndexingPositionConfig.html#Hi_NcParsers_Dependencys_IIndexingPositionConfig_TryFindIndexingAnchor_">TryFindIndexingAnchor</a><small>(API)</small>,
for the Siemens coded-position coordinate functions <code>CAC()</code>, <code>CIC()</code>, <code>CDC()</code>, <code>CACP()</code> and <code>CACN()</code>
— the family whose argument is a position <strong>number</strong> rather than a coordinate. The Siemens
per-word coordinate syntax unwraps those verbs only on axes that
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.IIndexingPositionConfig.html#Hi_NcParsers_Dependencys_IIndexingPositionConfig_IsIndexingAxis_">IsIndexingAxis</a><small>(API)</small> reports
as usable, and the rotary-only members of the family additionally require a rotary axis; the rotary
words are then resolved by the ABC write pass and the linear words by the incremental-resolve pass,
both through the shared coded-position helper.</p>
<p><strong>An empty table is a real state with a visible consequence.</strong> An axis assigned to a table that holds
no entries is not a usable indexing axis, so its coded-position words are left unrecognised and
surface as unresolved text rather than being resolved against nothing. The engine lookups themselves
deliberately do not validate the Siemens constraints — a table that breaks them resolves nonsense
positions with no diagnostic — which is why the write endpoint, and this panel with it, is where the
rules are enforced.</p>
<h2 id="machine-data-not-program-data">Machine Data, Not Program Data</h2>
<p>The tables belong to the machine plane rather than the program-data plane: they configure the
machine's stations, not a part's setup, and they carry no per-workpiece meaning the way a work
coordinate or a datum shift does. That is the classification the branch builder gives the node, and
it is the reason the node sits beside Machine Limits and Rapid Feedrates rather than beside Work
Coordinates.</p>
<p><strong>Their storage half is a separate question, and the answer is the project.</strong> The Siemens preset
carries a <a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Siemens.SiemensMachineDataTableProxy.html">SiemensMachineDataTableProxy</a>, not a table: the
proxy holds a fixed machine-config seed, deep-clones it into the project's per-case list only when
the project has no Siemens machine-data table yet, and thereafter resolves the project's own table.
Three consequences follow, and they are the ones the plane's other project-owned leaves live with —
Machine Limits, Rapid Feedrates, Home / G28 Reference, M-Code Declarations and Parameters (Native),
all rows of the same per-case table. The plane's runner-owned leaves get the opposite of all three,
and which half a leaf falls in is listed on
<a class="xref" href="index.html">Machine and Controller Plane</a>:</p>
<ul>
<li>Edited positions are stored on the <strong>project</strong>, serialized with the per-case table as one XML
element per entry carrying its table number and its value.</li>
<li>Installing another Siemens runner, or re-applying the Siemens brand, leaves them alone: the
incoming proxy re-binds to the project's table instead of cloning its seed over it.</li>
<li>Saving a controller file captures the <strong>seed</strong>, not the edited table. A <code>.Controller</code> written after
the tables were filled in carries the preset seed they were cloned from.</li>
</ul>
<p>Switching to any other brand removes the Siemens machine-data table from the project, because no
proxy of the incoming runner resolves it, and the positions go with it. Switching back clones a fresh
table from the seed rather than restoring the removed one — see
<a class="xref" href="brand-switch.html">Brand Switch</a>.</p>
<h2 id="the-assignment-is-shown-here-and-edited-elsewhere">The Assignment Is Shown Here and Edited Elsewhere</h2>
<p>Above the two drafts the panel renders one display-only table: <strong>Axis</strong> against <strong>Indexing assignment
(MD30500)</strong>, the per-axis machine datum that decides what an axis does with the two global lists.
Four values are named:</p>
<table>
<thead>
<tr>
<th>Assignment</th>
<th>Label the user reads</th>
<th>Meaning</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td><em>Not an indexing axis</em></td>
<td>the axis ignores the coded-position family</td>
</tr>
<tr>
<td>1</td>
<td><em>Table 1 (MD10910)</em></td>
<td>the axis reads the first draft below</td>
</tr>
<tr>
<td>2</td>
<td><em>Table 2 (MD10930)</em></td>
<td>the axis reads the second draft below</td>
</tr>
<tr>
<td>3</td>
<td><em>Equidistant (MD30501MD30503)</em></td>
<td>positions come from a spacing rule, not from either table</td>
</tr>
</tbody>
</table>
<p>Any other stored value renders as the bare number.</p>
<p><strong>The rows are the axes that carry an MD30500 entry, not the plane's usual axis set.</strong> Every other
per-axis leaf reads the table's axis-type rows — the set a machine-tool install stamps the chain's
axis codes into — while this one reads the keys of the assignment row itself, sorted by axis name. An
axis of the machine with no assignment entry does not appear here at all, and an assignment entry
written against a name the machine does not have does.</p>
<p>An axis the table reports as modular rotary carries the note <em>(wraps 0360°)</em> beside its name, and
its presence is what turns on the one-revolution rule for whichever table it reads. That report is
the machine-axis contract's default —
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.IMachineAxisConfig.html#Hi_NcParsers_Dependencys_IMachineAxisConfig_IsModularRotary_">IsModularRotary</a><small>(API)</small> is not
overridden by any parameter table — so on this branch every rotary and every spindle-mode axis counts
as modular.</p>
<p><strong>Nothing on this leaf writes an assignment.</strong> The panel renders the value as plain text, and the
leaf's REST surface exposes a reader for the two tables with their per-axis assignments and a writer
for one position table; no endpoint of its own writes an assignment. MD30500 is written where every
other raw machine datum is written: on
<a class="xref" href="native-parameters.html">Parameters (Native)</a>, as a row of the <strong>Axis parameters
(integer)</strong> section, added through the <em>Section</em> / <em>Parameter id</em> / <em>Axis</em> / <em>Value (raw)</em> footer with
the section set to <strong>Axis (integer)</strong>. With no assignment row anywhere on the table this panel says
so and points there: <em>&quot;No axis declares an indexing assignment (MD30500) yet — add it as a per-axis
integer parameter in Parameters (Native).&quot;</em></p>
<p>The equidistant definition is edited entirely on that same sibling leaf and has <strong>no</strong> surface here.
Its three machine data — the numerator MD30501, the denominator MD30502 and the offset MD30503 —
are ordinary parameter rows, and an axis on assignment 3 consults neither of the two tables this
panel edits. On a modular rotary axis the numerator is ignored and the revolution is divided into
MD30502 positions; elsewhere the spacing is the numerator over the denominator and the position count
is unbounded.</p>
<h2 id="draft-validate-save-whole">Draft, Validate, Save Whole</h2>
<p>This is the branch's one draft-then-save table, and it departs from the shared commit rule that
<a class="xref" href="../editing-contract.html">Editing Contract</a> sets for its siblings. Every other table
in the branch commits per cell and rolls a failed write back; this one commits nothing until a button
is pressed, and keeps a failed edit on screen.</p>
<ul>
<li><strong>Cells bind straight into the draft.</strong> Each numeric cell is bound with a two-way model and carries
no commit handler, so a cell's commit — which the widget runs on blur and on Enter, never on a
keystroke — lands in the draft row and goes no further. The badge, the validation line and the two
buttons therefore follow those commits rather than the typing. The field itself still refuses text
it cannot parse, with an untranslated message under the box; it carries no minimum and no maximum
here, so every rule below is the draft validator's — see
<a class="xref" href="../../../widget/numeric-input.html">Numeric Input</a>.</li>
<li><strong>Add and delete are draft operations too.</strong> <em>Add Position</em> appends an empty row and is disabled at
60; the row's delete button removes it from the draft with no confirmation and no request.</li>
<li><strong>An orange <em>unsaved</em> badge</strong> appears beside a table's title while the draft differs from what was
last saved, compared row count first and then value by value.</li>
<li><strong>Save Table sends the whole list</strong> for that one table and is disabled while the draft is clean or
while validation fails. <em>Revert</em> restores the draft from the last saved values.</li>
<li><strong>The validator mirrors the endpoint's own rules</strong> and reports the first violation as a line under
the table: the per-table maximum, a <em>fill in position … (or delete its row)</em> line naming an empty
or non-finite cell, a strictly-ascending check naming both offending positions with their values,
and the <code>0 ≤ position &lt; 360</code> range when a modular rotary axis reads that table.</li>
<li><strong>A failed save does not roll the draft back.</strong> The draft stays dirty and the badge stays up, so
the work is not lost — the opposite of every other panel in the branch.</li>
<li><strong>Saving an empty table clears it.</strong> The endpoint accepts an empty list, and the axes assigned to
that table then stop being indexing axes.</li>
</ul>
<p>The endpoint enforces the same four rules independently, so the panel's validation is live feedback
rather than the gate. A save that passes is applied to the live table and the panel does not re-read:
it marks the draft saved from what it sent.</p>
<h2 id="what-a-freshly-switched-siemens-runner-shows">What a Freshly Switched Siemens Runner Shows</h2>
<p><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Siemens.SiemensMachineDataTable.html#Hi_NcParsers_Dependencys_Siemens_SiemensMachineDataTable_Default3Axis">Default3Axis</a> — the seed the Siemens
proxy clones — declares a linear X, Y and Z, a reference position and a velocity for each, a maximum
spindle speed and six note-only OEM M-codes. It declares <strong>no</strong> MD30500 assignment and <strong>no</strong> entry
in either position table. So a project that has just been switched to Siemens grows this node,
opens it successfully, and finds it completely empty: the assignment caption in place of the axis
table, and two titled tables with headers, no rows and an <em>Add Position</em> button.</p>
<p>This leaf is also the only place the two lists appear. Parameters (Native) shows every row of the
machine-data table's three parameter dictionaries, and the two position tables are not in any of
them — they are separate list properties, so this panel is the whole editable surface for MD10910
and MD10930.</p>
<p>The panel opens through the branch's two-layer empty gate: the shared <em>&quot;No NC runner — load a
project first.&quot;</em> from the runner snapshot, then <em>&quot;No Siemens machine-data table on the active
runner.&quot;</em> from its own read. The second line is also what a failed read leaves on screen, since the
presence flag starts false and the failure path only raises a toast.</p>
<h2 id="layout">Layout</h2>
<ul>
<li><strong>General Setup</strong> Control Tree — the left dock of <code>/general-setup</code>
<ul>
<li><strong>Controller</strong> Node Row
<ul>
<li><strong>Machine / Controller</strong> Group Row
<ul>
<li><strong>Indexing Position Tables</strong> Node Row — <code>equipment/controller/machine/indexing-positions</code>,
between <strong>Subprogram Folders</strong> and <strong>Parameters (Native)</strong></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>Editor Row — the panel of the selected node
<ul>
<li><strong>Indexing Position Tables</strong> Panel — one column, gutter-spaced
<ul>
<li>Description Caption — <em>&quot;Global indexing position tables for coded-position programming
(<code>CAC/CIC/CDC/CACP/CACN</code>). Row order is position-number order — row 1 is indexing position 1.
Values are in the axis' native units (degrees for rotary, mm for linear) and must be strictly
ascending. Which axis reads which table is MD30500, edited in Parameters (Native); an axis
whose table is empty is not an indexing axis (its coded-position words stay unresolved).&quot;</em></li>
<li>Assignment Table — dense, flat, bordered; display-only
<ul>
<li>Header Row — <strong>Axis</strong>, <strong>Indexing assignment (MD30500)</strong></li>
<li>Data Row — the axis name in bold, with <em>(wraps 0360°)</em> beside it on a modular rotary axis,
then the assignment's label</li>
<li>No-Assignment Caption — replaces the table when no axis carries an assignment</li>
</ul>
</li>
<li>Position Table Block — one per table, twice
<ul>
<li>Title Row — <strong>Table 1 (MD10910)</strong> or <strong>Table 2 (MD10930)</strong>, then either <em>used by</em> and the
names of the axes assigned to it or <em>not used by any axis</em>, then an orange <strong>unsaved</strong> badge
while the draft is dirty</li>
<li>Table — dense, flat, bordered
<ul>
<li>Header Row — <strong>Position #</strong>, <strong>Value</strong>, and an unlabelled action column</li>
<li>Data Row — the 1-based position number in bold, the numeric cell, and a delete button that
removes the row from the draft</li>
</ul>
</li>
<li>Validation Line — the first rule the draft breaks, in the negative colour</li>
<li>Button Row — <strong>Add Position</strong> (disabled at 60 rows), then <strong>Revert</strong> and <strong>Save Table</strong>, both
disabled while the draft is clean and <strong>Save Table</strong> also while validation fails</li>
</ul>
</li>
<li>No-Table Body — replaces everything above: <em>&quot;No Siemens machine-data table on the active
runner.&quot;</em></li>
<li>Shared Empty State — replaces the whole body while the snapshot reports no runner: <em>&quot;No NC
runner — load a project first.&quot;</em></li>
</ul>
</li>
<li>Toast — negative, three seconds, the panel's context followed by the server's own message</li>
</ul>
</li>
</ul>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../../../index.html">HiNC App Anatomy</a> for git repository links.</p>
<h3 id="web-application">Web Application</h3>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/controlTree/SoftNcIndexingPositionsPanel.vue</code> — this panel: the draft
model over the two tables, the dirty comparison behind the badge, the validator that mirrors the
endpoint's rules, the display-only assignment table with its four labels and its wrap note, and the
save that marks the draft clean without re-reading.</li>
<li><code>wwwroot-src/src/components/controlTree/softNcItemTypes.ts</code> — the node's id, label and label key,
and the snapshot flag it is pushed behind, between the subprogram and native leaves.</li>
<li><code>wwwroot-src/src/api/softNcRunner.ts</code> — the reader that parses the two tables and the axis rows,
the whole-table replace and its documented refusals, and the snapshot flag the builder gates on.</li>
<li><code>wwwroot-src/src/composables/useSoftNcRunner.ts</code> — the shared runner snapshot the first empty layer
reads.</li>
<li><code>wwwroot-src/src/components/widgets/NumericInput.vue</code> — the cell: the two-way binding this panel
uses in place of a commit handler, the blur-and-Enter moment that binding emits on, the
empty-to-null parse the validator then reports, and the untranslated parse message.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcEmptyState.vue</code> — the shared no-runner line.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcNativeParamsPanel.vue</code> — the sibling panel that owns
the assignment: the integer per-axis section and the footer that adds a row by id and axis.</li>
<li><code>wwwroot-src/src/i18n/en/softNc.ts</code> — the node label, the description caption, the four assignment
labels, the wrap note, the table titles, the used-by captions, the four validation messages and the
absent-table line quoted above.</li>
<li><code>wwwroot-src/src/i18n/en/common.ts</code> — the shared <strong>Axis</strong> and <strong>Value</strong> column headers.</li>
<li><code>Mech/SoftNcRunnerController.cs</code> — the REST surface: the read that pairs the two tables with the
per-axis assignments and reports presence from the Siemens table alone, the whole-table replace
with its capacity, finite, ascending and one-revolution checks, the integer per-axis parameter
write the assignment is edited through, and the brand switch whose sweep removes the table on
leaving Siemens.</li>
</ul>
<h3 id="hiapi-engine">HiAPI Engine</h3>
<ul>
<li><code>HiMech/NcParsers/Dependencys/IIndexingPositionConfig.cs</code> — the contract: 1-based position
numbering, the native-unit and frame rules, the position count, the cyclic test, the number-to-
coordinate lookup and the anchor search, and the rule that an axis whose table is empty is not a
usable indexing axis.</li>
<li><code>HiMech/NcParsers/Dependencys/Siemens/SiemensMachineDataTable.cs</code> — the two position lists, their
XML round-trip and the statement that the lookups do not validate the Siemens constraints, the
MD30500 assignment dictionary, the equidistant spacing resolution over MD30501MD30503, the five
contract implementations, and the default seed that declares none of them.</li>
<li><code>HiMech/NcParsers/Dependencys/Siemens/SiemensMachineDataTableProxy.cs</code> — the get-or-create proxy:
the seed serialized on the runner, the deep clone installed only into a project that has no table,
and the deliberate refusal to implement the machine-config interfaces itself.</li>
<li><code>HiMech/NcParsers/Dependencys/IMachineAxisConfig.cs</code> — the axis contract, and the modular-rotary
default that makes every rotary and spindle axis subject to the one-revolution rule.</li>
<li><code>HiMech/NcParsers/SoftNcRunner.cs</code> — the Siemens preset that carries the machine-data proxy, and
the proxy resolution every read on this leaf goes through.</li>
<li><code>HiMech/NcParsers/Syntaxs/SiemensSyntaxUtil.cs</code> — the syntax list that wires the coded-position
producer and its two write-stage consumers into the Siemens pipeline.</li>
<li><code>HiMech/NcParsers/EvaluationSyntaxs/Siemens/SiemensAcIcSyntax.cs</code> — the producer: it unwraps the
five coded verbs only on usable indexing axes and stamps the per-word override, leaving the word
untouched elsewhere.</li>
<li><code>HiMech/NcParsers/LogicSyntaxs/CodedPositionUtil.cs</code> — the shared resolution: position number to
coordinate, the hold-on-failure semantics, and the diagnostics for an invalid number or a table
that has gone missing between stages.</li>
<li><code>HiMech/NcParsers/LogicSyntaxs/McAbcSyntax.cs</code>,
<code>HiMech/NcParsers/LogicSyntaxs/IncrementalResolveSyntax.cs</code> — the two write-stage consumers, rotary
words and linear words.</li>
<li><code>HiMech/NcParsers/LogicSyntaxs/McAbcCyclicPathSyntax.cs</code> — the tail pass that turns the resolved
directional approach into a path, and the one other syntax pass that reads the modular-rotary flag
directly.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="index.html">Machine and Controller Plane</a> — the plane this leaf sits on, and which half of the project each
of its neighbours is stored in</li>
<li><a class="xref" href="../brand-matrix.html">Brand Matrix</a> — the gate behind this node, why it is the machine plane's
only single-brand row, and what every other flag probes</li>
<li><a class="xref" href="../editing-contract.html">Editing Contract</a> — the per-cell commit rule the rest of the branch follows
and this panel deliberately breaks</li>
<li><a class="xref" href="native-parameters.html">Native Parameters</a> — where the MD30500 assignment and the equidistant
machine data this leaf only displays are actually edited</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,614 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Interface Parameters | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Interface Parameters | HiAPI-C# 2025 ">
<link rel="icon" href="../../../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../../../public/docfx.min.css">
<link rel="stylesheet" href="../../../../public/main.css">
<meta name="docfx:navrel" content="../../../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../../../index.html">
<img id="logo" class="svg" src="../../../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-ControllerParameters">
<h1 id="interface-parameters">Interface Parameters</h1>
<p>Two leaves of the Controller branch's machine plane put the controller's non-axis parameters into
domain vocabulary instead of parameter numbers: <strong>Controller Parameters</strong> and <strong>Canned Cycle
(Peck)</strong>. They live on the General Setup page at <code>/general-setup</code> under the Control-Tree ids
<code>equipment/controller/machine/parameters</code> and <code>equipment/controller/machine/canned-cycle</code>. Each value
they edit that is a row of the brand parameter table is also reachable as a raw numbered row on the
sibling leaf, <a class="xref" href="native-parameters.html">Parameters (Native)</a>; the values stored
elsewhere are named below, and they reach no native form at all.</p>
<div class="IMPORTANT">
<h5>Important</h5>
<p>Anatomy makes no claim that any id named here is stable across versions. The migration hop that
keeps an older <code>?tree=</code> value resolving is described in
<a class="xref" href="../../../platform/tree-ids-and-routes.html">Tree Ids and Routes</a>.</p>
</div>
<p>Controller Parameters is ungated: the branch builder pushes it with the brand and the four per-axis
leaves, so it exists wherever a runner resolves. Canned Cycle (Peck) is pushed behind the
<code>hasCannedCycle</code> flag, which is true on all five shipped brand presets — see
<a class="xref" href="../brand-matrix.html">Brand Matrix</a>.</p>
<h2 id="the-five-fields-and-which-brand-shows-which">The Five Fields, and Which Brand Shows Which</h2>
<p>Controller Parameters renders at most five controls, in one column, in this fixed order. There is no
tab, no group box and no scroll region; a field the brand does not have is not disabled but absent.</p>
<table>
<thead>
<tr>
<th>Field the user reads</th>
<th>Control</th>
<th>Backed by</th>
<th>Fanuc, Mazak</th>
<th>Syntec</th>
<th>Siemens</th>
<th>Heidenhain</th>
</tr>
</thead>
<tbody>
<tr>
<td>Max spindle speed</td>
<td>numeric, <code>rpm</code>, minimum 0</td>
<td>the brand parameter table</td>
<td>#3741</td>
<td>Pr3741</td>
<td>MD35100</td>
<td>MP100</td>
</tr>
<tr>
<td>Cutter compensation startup / cancel type</td>
<td>select — Type A / Type B / Type C</td>
<td>the brand parameter table</td>
<td>#5003</td>
<td>Pr5003</td>
<td></td>
<td></td>
</tr>
<tr>
<td>Tool-axis direction</td>
<td>select — Z / Y / X</td>
<td>the brand parameter table</td>
<td></td>
<td></td>
<td></td>
<td>MP101</td>
</tr>
<tr>
<td>GOTO — max jumps per N target †</td>
<td>numeric, minimum 1</td>
<td>an iteration-guard dependency</td>
<td>Fanuc guard</td>
<td>Fanuc guard</td>
<td>Siemens guard</td>
<td></td>
</tr>
<tr>
<td>WHILE/DO — max iterations per loop †</td>
<td>numeric, minimum 1</td>
<td>an iteration-guard dependency</td>
<td>Fanuc guard</td>
<td>Fanuc guard</td>
<td>Siemens guard</td>
<td></td>
</tr>
</tbody>
</table>
<p>† The Fanuc-family spelling. Siemens spells both guard labels differently — the two pairs are below.</p>
<p>The last two sit under a separator and the caption <em>Macro loop guards (advanced)</em>, and that whole
group is rendered only when at least one of the two guard values resolves. On Heidenhain neither
does, so the separator, the caption and both fields are absent together; that brand shows two fields
in total.</p>
<p><strong>The two guard labels are chosen from the brand marker, not from the dependency that answered.</strong> The
Siemens spellings — <em>GOTOF/GOTOB — max jumps per label</em> and <em>WHILE / FOR / REPEAT / LOOP — max
iterations per loop</em> — are used when the brand marker string reads <code>Siemens</code>, and the Fanuc spellings
otherwise. Syntec and Mazak therefore read the Fanuc spellings, which is correct: both presets carry
the Fanuc guard types. The label and the value are independent lookups, so a runner whose brand
marker was edited apart from its dependency list can show one brand's wording over the other brand's
guard.</p>
<p>The three select options <em>Type A</em> / <em>Type B</em> / <em>Type C</em> are translated strings; the three tool-axis
options <code>Z</code>, <code>Y</code> and <code>X</code> are literal letters written into the component and are the same in every
locale.</p>
<h2 id="visibility-is-null-driven-not-flag-driven">Visibility Is Null-Driven, Not Flag-Driven</h2>
<p>Which fields appear is decided <strong>per field, by a null in the read</strong>, and not by the shared runner
snapshot the tree shape is built from. The reader answers one object with <code>present</code> plus five
nullable numbers, computed by type-switching on the resolved dependencies:</p>
<ul>
<li><strong>Max spindle speed</strong> is non-null for each of the four brand parameter-table types, so it is
present on all five brands. A <code>ControllerParameterTableBase</code> subclass outside those four answers
null, and the field disappears while <code>present</code> stays true.</li>
<li><strong>Cutter compensation type</strong> is non-null only for the Fanuc and the Syntec table.</li>
<li><strong>Tool-axis direction</strong> is non-null only for the Heidenhain table.</li>
<li>Each <strong>guard</strong> is the Fanuc dependency's value if one resolves, otherwise the Siemens dependency's,
otherwise null.</li>
</ul>
<p>The panel's own conditions are <code>!= null</code> on those five keys. No snapshot flag decides a field on this
leaf; the only one read at all is the shared no-runner guard. The snapshot's brand marker is read
twice more, but only to choose between the two guard label pairs — never to show or hide anything.</p>
<p><strong>Three flags in the snapshot describe exactly these fields and are read by nothing.</strong>
<code>hasCutterComp</code>, <code>hasToolAxisDirection</code> and <code>hasIterationGuards</code> are computed server-side, typed,
defaulted and parsed in the client's API layer, and no component consults any of them. They remain a
live part of the REST snapshot for any other client. Two notes about the third:</p>
<ul>
<li><code>hasIterationGuards</code> probes the Fanuc and the Siemens jump-guard types only. The Heidenhain preset
carries a jump guard of its own type, so the flag reports false for a brand that has one. That
matches what the panel shows on the shipped presets — the reader probes the same two types, so the
Heidenhain guard has no field either — while under-describing the runner.</li>
<li>Its doc comment names the Fanuc GOTO / WHILE-DO guards alone while the code also matches the
Siemens jump guard. The code is what ships.</li>
</ul>
<p>The panel's whole body — the guards included — is hidden behind <code>present</code>, which reports the
<strong>parameter table</strong> and nothing else: <em>&quot;No controller parameter table on the active runner.&quot;</em> The
guards live on separate dependencies with their own write endpoints, so a runner carrying guards but
no parameter table would hide two reachable fields. All five brand presets carry a parameter-table
proxy, so the combination does not arise on a shipped preset.</p>
<h2 id="units-edited-versus-stored">Units: Edited Versus Stored</h2>
<p>Every value but one is stored in the unit it is edited in.</p>
<table>
<thead>
<tr>
<th>Value</th>
<th>Edited as</th>
<th>Stored as</th>
</tr>
</thead>
<tbody>
<tr>
<td>Max spindle speed</td>
<td>rpm</td>
<td>rpm, in the table's system-parameter row</td>
</tr>
<tr>
<td>Cutter compensation type</td>
<td>one of three named options</td>
<td>the option's index, 0 / 1 / 2, as a number in the same row set</td>
</tr>
<tr>
<td>Tool-axis direction</td>
<td>one of <code>Z</code> / <code>Y</code> / <code>X</code></td>
<td>0 / 1 / 2 in the same row set</td>
</tr>
<tr>
<td>Macro guards</td>
<td>a plain count</td>
<td>the same count, on the guard dependency</td>
</tr>
<tr>
<td>Peck retraction clearance</td>
<td>mm</td>
<td>mm on Fanuc and Mazak, <strong>microns on Syntec</strong>, mm on the generic fallback</td>
</tr>
</tbody>
</table>
<p><strong>Syntec is the one brand whose stored unit differs from the edited one, and the peck clearance is
the one value it happens to.</strong> The Syntec table's accessor divides its stored row by 1000 on read and
the write multiplies by 1000, so this leaf shows <code>5</code> on every brand while the native leaf shows
<code>5000</code> under Pr4002 on Syntec and <code>5</code> under #4002 on Fanuc and Mazak. An operator copying a number
off a real controller reads the native form; an operator entering a clearance in millimetres reads
this one. The two are the same setting.</p>
<p>The two select fields are the second unit hazard, in a milder form: the native leaf shows their
stored number, <code>0</code>, <code>1</code> or <code>2</code>, with the brand table's own description of that number under the id,
and never the words <em>Type A</em> or <code>Z</code>.</p>
<p>Not every value reaches the native form at all. The two macro guards are properties of their own
runner-owned dependencies rather than rows of any table, so they appear there on no brand; the peck
clearance does the same on Siemens and Heidenhain, where the generic fallback supplies it.</p>
<h2 id="peck-retraction-clearance">Peck Retraction Clearance</h2>
<p>Canned Cycle (Peck) is a single numeric field labelled <em>Peck retraction clearance (G83)</em>, in
millimetres, at zero or above, with a caption naming where the value is stored. Its model is
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.ICannedCycleConfig.html#Hi_NcParsers_Dependencys_ICannedCycleConfig_PeckRetractionDistance_mm">PeckRetractionDistance_mm</a>, and the read reports
which implementation answered. Three can, and between them they cover every shipped preset:</p>
<table>
<thead>
<tr>
<th>Caption the user reads</th>
<th>Answered by</th>
<th>Brands</th>
</tr>
</thead>
<tbody>
<tr>
<td><em>Stored in the Fanuc parameter table (#4002, mm).</em></td>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Fanuc.FanucParameterTable.html">FanucParameterTable</a></td>
<td>Fanuc, Mazak</td>
</tr>
<tr>
<td><em>Stored in the Syntec parameter table (Pr4002, microns) — edited here in mm.</em></td>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Syntec.SyntecParameterTable.html">SyntecParameterTable</a></td>
<td>Syntec</td>
</tr>
<tr>
<td><em>This brand takes the clearance per call (CYCLE83 / CYCL DEF); this value is the fallback used when a call omits it.</em></td>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Generic.FallbackConfig.html">FallbackConfig</a></td>
<td>Siemens, Heidenhain</td>
</tr>
</tbody>
</table>
<p>That third caption describes an override the mapping does not implement. The Siemens and Heidenhain
front-end syntaxes translate their own cycle vocabulary into the shared ISO cycle section, whose key
set is fixed at X, Y, Z, R, Q, F, P and K — and none of those eight is a retraction clearance. The
CYCLE83 mapping fills the R plane, the hole bottom Z and the first peck depth Q; the klartext cycle
mapping fills the same three plus a bottom dwell and a feed, and folds the klartext set-up clearance
into the R plane rather than keeping a slot of its own. The shared G83 expansion then reads the
clearance from this dependency unconditionally. So on those two brands the value here is not a
fallback for an omitted argument: it is the clearance every pecking cycle uses.</p>
<p>An <code>ICannedCycleConfig</code> implementation outside those three answers a fourth source token, which no
caption is written for: the field renders at the value that implementation supplies and the line
under it is blank. It is the peck counterpart of the null a parameter table outside the four returns
on the other leaf.</p>
<p>Which storage answered also decides where the value lives. On Fanuc, Mazak and Syntec it is a row of
the project's parameter table and appears in the native form; on Siemens and Heidenhain it is a field
of a plain runner-owned dependency, so it appears in <strong>no</strong> native form and a brand apply returns it
to the preset default — see <a class="xref" href="index.html">Machine and Controller Plane</a>.</p>
<p>The value is consumed by two cycle expansions and they use it differently. In G83 it is the clearance
the tool rapids down to above the previous stroke bottom before feeding deeper; in G73 it is the
whole chip-break retract distance between strokes. The field's label names G83 only. G73 expands on
Fanuc, Mazak, Syntec and Siemens; the Heidenhain syntax list registers the G83 expansion without it.</p>
<h2 id="what-the-values-reach">What the Values Reach</h2>
<p>Four of the six values are read by the parsing pipeline; the other two are stored declarations.</p>
<ul>
<li><strong>Peck retraction clearance</strong> drives the two cycle expansions above.</li>
<li><strong>Tool-axis direction</strong> is read where a Heidenhain <code>PLANE SPATIAL … COORD ROT</code> block is resolved:
it decides which spatial angle may be non-zero for the rotation to count as purely about the tool
axis, which is the only case where COORD ROT suppresses rotary positioning and rotates the
coordinate system instead.</li>
<li><strong>The macro guards</strong> are soft caps with runtime counters. Above the cap the consuming block emits
an iteration-limit warning and stops firing the jump or the loop back-edge, so a runaway macro ends
as a diagnostic instead of an unbounded run. Each is keyed per source file and per target — per N
target, per label or per loop — and the counters reset on the session-init edge while the cap does
not.</li>
<li><strong>Max spindle speed</strong> and <strong>cutter compensation type</strong> are stored, edited and serialized, and no
syntax or semantic in the shipped pipeline reads either. Cutter radius compensation itself <em>is</em>
resolved, by the G41 / G42 / G40 pass over the motion sections; what that pass does not consult is
the A / B / C startup-and-cancel vector style this field records.</li>
</ul>
<h2 id="defaults-a-freshly-seeded-table-opens-on">Defaults a Freshly Seeded Table Opens On</h2>
<p>Each brand's default table decides what the fields show once that brand's table is cloned fresh into
a project; the guards are plain preset entries and are re-created at their type defaults on every
apply.</p>
<table>
<thead>
<tr>
<th>Value</th>
<th>Fanuc, Mazak</th>
<th>Syntec</th>
<th>Siemens</th>
<th>Heidenhain</th>
</tr>
</thead>
<tbody>
<tr>
<td>Max spindle speed</td>
<td>60000</td>
<td>24000</td>
<td>60000</td>
<td>60000</td>
</tr>
<tr>
<td>Cutter compensation type</td>
<td>Type A</td>
<td>Type A</td>
<td></td>
<td></td>
</tr>
<tr>
<td>Tool-axis direction</td>
<td></td>
<td></td>
<td></td>
<td>Z</td>
</tr>
<tr>
<td>GOTO jump cap</td>
<td>1000</td>
<td>1000</td>
<td>1000</td>
<td></td>
</tr>
<tr>
<td>Loop cap</td>
<td>10000</td>
<td>10000</td>
<td>10000</td>
<td></td>
</tr>
<tr>
<td>Peck retraction clearance</td>
<td>5 mm</td>
<td>5 mm (5000 stored)</td>
<td>5 mm (fallback)</td>
<td>5 mm (fallback)</td>
</tr>
</tbody>
</table>
<p>The table-backed values — the first three rows, and the peck clearance on the three brands whose
parameter table stores it — are reached only by an apply that leaves the project holding no table of
the incoming proxy's type: the outgoing brand's table is swept and the incoming proxy clones its seed
in place of it. Fanuc and Mazak share one table type, so the hop between those two re-binds the
project's existing table instead, and every value in it survives with its edits. The two guards and
the fallback clearance are runner-owned and return to the preset's on every apply, whichever brands
it runs between.</p>
<p>The two caps differ by an order of magnitude on purpose: a jump is not the legitimate bulk-iteration
primitive, so its cap stays tight, while loops are what generated drill grids and calibration sweeps
are built from.</p>
<h2 id="reading-a-field-can-create-its-row">Reading a Field Can Create Its Row</h2>
<p>Both readers reach the stored values through accessors that <strong>write a default into the table when the
row is absent</strong>. Opening either leaf on a table whose max-spindle, cutter-comp, tool-axis or peck
row was deleted from <a class="xref" href="native-parameters.html">Parameters (Native)</a> therefore
re-creates that row at the type's default, in the live project-owned table, with no edit and no
request from the user. On Heidenhain — which has neither a cutter-comp row nor a peck row — the
max-spindle and tool-axis rows are the whole set. The native leaf's delete removes the row; the next
read of this leaf puts it back.</p>
<p>The guard dependencies have no such behaviour — their values are ordinary properties with field
initializers.</p>
<h2 id="editing-a-field">Editing a Field</h2>
<p>Both leaves follow the branch's shared commit, rollback and toast rules, which are
<a class="xref" href="../editing-contract.html">Editing Contract</a>. Four details are specific to these two
leaves:</p>
<ul>
<li><strong>The numeric fields commit on blur or on Enter</strong>, never per keystroke; their full contract is
<a class="xref" href="../../../widget/numeric-input.html">Numeric Input</a>. The two selects commit on the pick.</li>
<li><strong>Each write is one field.</strong> Every control has its own endpoint and sends only its own value; no
edit here resends a neighbouring field.</li>
<li><strong>A value the handler refuses is left on screen.</strong> Clearing a field parses to null and the handler
returns before the request; a guard field additionally refuses anything that is not a whole number,
and every numeric handler here refuses a non-finite one — the field accepts the words <code>Infinity</code>
and <code>NaN</code> as text and turns them into numbers, and the handlers are what stop them. In each case
the box keeps what was typed — blank, where the field was cleared — while the stored value stands,
because nothing re-writes the box until the panel is remounted by selecting another node and coming
back.</li>
<li><strong>A bound violation never reaches a handler at all.</strong> The numeric field itself refuses a value
below its minimum and shows an untranslated <em>Must be ≥ …</em> under the box. Each handler carries a
lower-bound test of its own — 1 on a guard, 0 on the peck clearance — but the field's own minimum
is the same number and stops every finite value first, so those tests decide nothing.</li>
</ul>
<h2 id="layout">Layout</h2>
<ul>
<li><strong>General Setup</strong> Control Tree — the left dock of <code>/general-setup</code>
<ul>
<li><strong>Controller</strong> Node Row
<ul>
<li><strong>Machine / Controller</strong> Group Row
<ul>
<li><strong>Controller Parameters</strong> Node Row — <code>equipment/controller/machine/parameters</code></li>
<li><strong>Canned Cycle (Peck)</strong> Node Row — <code>equipment/controller/machine/canned-cycle</code></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>Editor Row — the panel of whichever node is selected
<ul>
<li><strong>Controller Parameters</strong> Panel — one column, gutter-spaced
<ul>
<li><strong>Max spindle speed</strong> Numeric Field — dense, outlined, suffixed <code>rpm</code>, minimum 0</li>
<li><strong>Cutter compensation startup / cancel type</strong> Select — dense, outlined; options <strong>Type A</strong>,
<strong>Type B</strong>, <strong>Type C</strong></li>
<li><strong>Tool-axis direction</strong> Select — dense, outlined; options <strong>Z</strong>, <strong>Y</strong>, <strong>X</strong></li>
<li>Guard Group — rendered only when at least one guard value resolves
<ul>
<li>Separator</li>
<li><strong>Macro loop guards (advanced)</strong> Caption</li>
<li>Jump-Guard Numeric Field — minimum 1; labelled <strong>GOTO — max jumps per N target</strong> or
<strong>GOTOF/GOTOB — max jumps per label</strong></li>
<li>Loop-Guard Numeric Field — minimum 1; labelled <strong>WHILE/DO — max iterations per loop</strong> or
<strong>WHILE / FOR / REPEAT / LOOP — max iterations per loop</strong></li>
</ul>
</li>
<li>No-Table Block — replaces every field above:
<em>&quot;No controller parameter table on the active runner.&quot;</em></li>
</ul>
</li>
<li><strong>Canned Cycle (Peck)</strong> Panel — one column, gutter-spaced
<ul>
<li><strong>Peck retraction clearance (G83)</strong> Numeric Field — dense, outlined, suffixed <code>mm</code>, minimum 0</li>
<li>Source Caption — one of the three sentences in the table above, or empty where a fourth
implementation answered</li>
<li>No-Config Block — replaces both:
<em>&quot;No canned-cycle config on the active runner.&quot;</em></li>
</ul>
</li>
<li>Shared Empty State — replaces either panel's whole body while the snapshot reports no runner:
<em>&quot;No NC runner — load a project first.&quot;</em></li>
</ul>
</li>
</ul>
<p>Neither panel has a table, an add-row control, a delete control or a Save button.</p>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../../../index.html">HiNC App Anatomy</a> for git repository links.</p>
<h3 id="web-application">Web Application</h3>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/controlTree/SoftNcControllerParamsPanel.vue</code> — the five-field panel:
the per-field <code>!= null</code> conditions, the guard group's combined condition, the brand-marker choice
between the two guard label pairs, the translated compensation-type options beside the literal
tool-axis letters, and the three commit handlers with their null, non-finite and integer refusals
beside the lower-bound tests the field's own minimum shadows.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcCannedCyclePanel.vue</code> — the peck panel: the single
field, the presence layer, and the caption chosen from the read's source token.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcEmptyState.vue</code> — the shared no-runner line both
panels open with.</li>
<li><code>wwwroot-src/src/components/widgets/NumericInput.vue</code> — the numeric field: the unit rendered as a
suffix, commit on blur or Enter, the empty-text-to-null parse both panels reject, and the
untranslated bound message that stops a value before it is emitted.</li>
<li><code>wwwroot-src/src/components/controlTree/softNcItemTypes.ts</code> — the two node ids and their labels:
Controller Parameters among the unconditional machine leaves, Canned Cycle (Peck) behind the
peck-provider flag.</li>
<li><code>wwwroot-src/src/api/softNcRunner.ts</code> — the two readers and their setters, the five nullable
parameter keys, the peck source token, and the three snapshot flags that describe these fields and
are declared, defaulted and parsed without a consumer.</li>
<li><code>wwwroot-src/src/composables/useSoftNcRunner.ts</code> — the shared runner snapshot the first empty layer
reads.</li>
<li><code>wwwroot-src/src/i18n/en/softNc.ts</code> — the two node labels, the five field labels, the two guard
label pairs, the compensation-type options, the macro-guard caption, the three peck source
sentences and the two empty-state lines quoted above.</li>
<li><code>Mech/SoftNcRunnerController.cs</code> — the REST surface: the parameter read's type switches and its
table-only presence flag, one write per field, the guard writes that fall back from the Fanuc
dependency to the Siemens one, the peck read with its source token, and the peck write that
converts to microns for the Syntec table.</li>
</ul>
<h3 id="hiapi-engine">HiAPI Engine</h3>
<ul>
<li><code>HiMech/NcParsers/Dependencys/ControllerParameterTableBase.cs</code> — the base every brand table
extends, and the raw system-parameter dictionary the first three fields are rows of.</li>
<li><code>HiMech/NcParsers/Dependencys/Fanuc/FanucParameterTable.cs</code> — the Fanuc and Mazak table: the
max-spindle, peck-clearance and cutter-comp parameter numbers, the compensation-type enum with its
three vector styles, the millimetre peck accessor, and the default seed.</li>
<li><code>HiMech/NcParsers/Dependencys/Syntec/SyntecParameterTable.cs</code> — the Syntec table: the same three
parameter numbers, the micron peck row and the accessor that converts it, and the default seed.</li>
<li><code>HiMech/NcParsers/Dependencys/Siemens/SiemensMachineDataTable.cs</code> — the Siemens table: the
max-spindle machine datum, and the absence of any compensation-type or peck member.</li>
<li><code>HiMech/NcParsers/Dependencys/Heidenhain/HeidenhainParameterTable.cs</code> — the Heidenhain table: the
max-spindle and tool-axis-direction machine parameters and their default seed.</li>
<li><code>HiMech/NcParsers/Dependencys/Fanuc/FanucParameterTableProxy.cs</code> — the proxy shape all four brand
tables share: the seed clone that fires only where the project holds no table of that type, and
the pure take afterwards.</li>
<li><code>HiMech/NcParsers/Dependencys/ICannedCycleConfig.cs</code> — the peck-clearance contract and its three
implementations.</li>
<li><code>HiMech/NcParsers/Dependencys/Generic/FallbackConfig.cs</code> — the runner-owned clearance the Siemens
and Heidenhain presets carry, and its millimetre default.</li>
<li><code>HiMech/NcParsers/Dependencys/Fanuc/FanucGotoIterationDependency.cs</code>,
<code>HiMech/NcParsers/Dependencys/Fanuc/FanucWhileDoIterationDependency.cs</code>,
<code>HiMech/NcParsers/Dependencys/Siemens/SiemensGotoIterationDependency.cs</code>,
<code>HiMech/NcParsers/Dependencys/Siemens/SiemensLoopIterationDependency.cs</code> — the four guards the two
advanced fields write: their caps, their per-file counters and the session reset that clears the
counters alone.</li>
<li><code>HiMech/NcParsers/Dependencys/Heidenhain/HeidenhainGotoIterationDependency.cs</code> — the fifth guard,
the one the reader does not probe and no field edits.</li>
<li><code>HiMech/NcParsers/SoftNcRunner.cs</code> — the five brand presets: which parameter-table proxy, which
guards and whether a fallback clearance each one carries.</li>
<li><code>HiMech/NcParsers/LogicSyntaxs/CannedCycleSyntaxUtil.cs</code> — the shared ISO cycle section: the fixed
eight-key parameter set none of whose keys is a retraction clearance, and the modal merge that
consumes them.</li>
<li><code>HiMech/NcParsers/LogicSyntaxs/PeckDrillingCycleSyntax.cs</code> — the G83 expansion, which reads the
clearance from the dependency rather than from the block.</li>
<li><code>HiMech/NcParsers/LogicSyntaxs/HighSpeedPeckCycleSyntax.cs</code> — the G73 expansion that spends the
same value as the chip-break retract distance.</li>
<li><code>HiMech/NcParsers/Syntaxs/FanucSyntaxUtil.cs</code>,
<code>HiMech/NcParsers/Syntaxs/MazakSyntaxUtil.cs</code>,
<code>HiMech/NcParsers/Syntaxs/SyntecSyntaxUtil.cs</code>,
<code>HiMech/NcParsers/Syntaxs/SiemensSyntaxUtil.cs</code>,
<code>HiMech/NcParsers/Syntaxs/HeidenhainSyntaxUtil.cs</code> — the five brand syntax lists, and which of
them register both peck expansions against the one that registers the G83 expansion alone.</li>
<li><code>HiMech/NcParsers/LogicSyntaxs/Siemens/SiemensModalCycleSyntax.cs</code> — the CYCLE83 mapping onto the
shared cycle section, and the arguments it drops.</li>
<li><code>HiMech/NcParsers/LogicSyntaxs/Heidenhain/HeidenhainCannedCycleSyntax.cs</code> — the klartext cycle
mapping onto the same section.</li>
<li><code>HiMech/NcParsers/LogicSyntaxs/Heidenhain/HeidenhainPlaneTiltSyntax.cs</code> — the one consumer of the
tool-axis direction: the pure-tool-axis test that decides whether COORD ROT suppresses rotary
positioning.</li>
<li><code>HiMech/NcParsers/PostLogicSyntaxs/RadiusCompensationSyntax.cs</code> — the cutter radius compensation
that is resolved, and that does not consult the stored startup-and-cancel type.</li>
<li><code>HiGeom/Common/Collections/DictionaryUtil.cs</code> — the get-or-create the table accessors read through,
and therefore why a read re-creates a deleted parameter row.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="index.html">Machine and Controller Plane</a> — the plane these two leaves sit on, and which half of the project
each of their values is stored in</li>
<li><a class="xref" href="../brand-matrix.html">Brand Matrix</a> — why one of the two leaves is gated and the other is not, and
which brand resolves which parameter table</li>
<li><a class="xref" href="../editing-contract.html">Editing Contract</a> — the fetch, commit, rollback and empty-layer rules these
panels share with the rest of the branch</li>
<li><a class="xref" href="native-parameters.html">Native Parameters</a> — the same rows by number, in their raw stored units,
including the peck clearance this page shows converted</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,452 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>M-Code Declarations | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="M-Code Declarations | HiAPI-C# 2025 ">
<link rel="icon" href="../../../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../../../public/docfx.min.css">
<link rel="stylesheet" href="../../../../public/main.css">
<meta name="docfx:navrel" content="../../../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../../../index.html">
<img id="logo" class="svg" src="../../../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-ControllerMCodes">
<h1 id="m-code-declarations">M-Code Declarations</h1>
<p>M-Code Declarations is the leaf that gives a machine's own M-codes a meaning the parser can act on —
what each machine-specific (OEM or PLC) code does — and it carries beside them the tool-change
trigger mode that decides whether a bare <code>T</code> word changes the tool by itself. It lives on the
General Setup page at <code>/general-setup</code> under the Control-Tree id
<code>equipment/controller/machine/m-codes</code>, grown while the runner snapshot reports a brand controller
parameter table. Both the declaration map and the trigger flag are stored on that one table, so this
leaf appears and disappears together with
<a class="xref" href="native-parameters.html">Parameters (Native)</a>, and the rows declared on it travel
with the project rather than with the runner file, which carries only the preset seed a fresh
project's table is cloned from.</p>
<div class="IMPORTANT">
<h5>Important</h5>
<p>Anatomy makes no claim that any id named here is stable across versions. The migration hop that
keeps an older <code>?tree=</code> value resolving is described in
<a class="xref" href="../../../platform/tree-ids-and-routes.html">Tree Ids and Routes</a>.</p>
</div>
<h2 id="the-one-object-behind-both-halves">The One Object Behind Both Halves</h2>
<p><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.ControllerParameterTableBase.html">ControllerParameterTableBase</a> implements
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.IMCodeDeclarationConfig.html">IMCodeDeclarationConfig</a> and
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.IToolChangeTriggerConfig.html">IToolChangeTriggerConfig</a> over its own storage, so the table every
brand carries supplies the whole leaf: the declaration map keyed on the code as parsed, and the
single trigger boolean. A third face, <a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.ISpindleControlConfig.html">ISpindleControlConfig</a>, reads
the same map for spindle direction alone rather than keeping a second one, which is why one code is
never half-recognized by two separate dictionaries.</p>
<p>The tree gate and the panel's own second empty layer probe the same type. The branch builder grows
the node while the snapshot's <code>hasNativeTable</code> flag is set, and the read answers a presence flag
computed from the same resolved parameter table; every shipped brand preset carries one, so in
practice the leaf is unconditional and the <em>&quot;No controller parameter table on the active runner.&quot;</em>
line is reachable only on a runner assembled by hand or loaded from XML. Which brand satisfies which
gate is <a class="xref" href="../brand-matrix.html">Brand Matrix</a>.</p>
<p>Because the map is a member of the brand parameter table, and that table is the project's per-case
copy rather than the runner's, declarations survive installing another runner file of the same brand
— see <a class="xref" href="index.html">Machine and Controller Plane</a> for which half of a project each
value on this plane is written into.</p>
<h2 id="what-a-declaration-carries">What a Declaration Carries</h2>
<p>One row is one <a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.MCodeEffects.html">MCodeEffects</a>, and a real OEM code is frequently
composite, so the row records every effect the code performs rather than a single meaning.</p>
<table>
<thead>
<tr>
<th>Column</th>
<th>Effect</th>
<th>What the code expands to</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Tool change</strong></td>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.MCodeEffects.html#Hi_NcParsers_Dependencys_MCodeEffects_IsToolChange">IsToolChange</a></td>
<td><code>M06</code></td>
</tr>
<tr>
<td><strong>Spindle</strong></td>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.MCodeEffects.html#Hi_NcParsers_Dependencys_MCodeEffects_SpindleDirection">SpindleDirection</a></td>
<td><code>M03</code> for CW, <code>M04</code> for CCW, <code>M05</code> for STOP</td>
</tr>
<tr>
<td><strong>Coolant</strong></td>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.MCodeEffects.html#Hi_NcParsers_Dependencys_MCodeEffects_CoolantMode">CoolantMode</a></td>
<td><code>M07</code> for Mist, <code>M08</code> for Flood, <code>M09</code> for Off</td>
</tr>
<tr>
<td><strong>Not-simulated note</strong></td>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.MCodeEffects.html#Hi_NcParsers_Dependencys_MCodeEffects_UnmodeledNote">UnmodeledNote</a></td>
<td>nothing — it raises one informational diagnostic per occurrence</td>
</tr>
</tbody>
</table>
<p>The two pickers each open on a dash, which is the null option: a declaration that touches neither
the spindle nor the coolant carries neither. The note is free text and is itself an effect — a code
whose real behaviour is a chip conveyor or a door interlock is declared with the note alone, and
every occurrence then reports <code>DeclaredMCode--UnmodeledEffects</code> at message severity instead of the
unknown-code warning. A declaration with no effect and no note is the fourth legal state and means
<em>consume this code silently</em>, which the panel's hint says in as many words: <em>&quot;A declaration with
nothing set consumes its code silently.&quot;</em></p>
<p>That fourth state is what a freshly declared row is. <strong>Declare</strong> writes a row whose tool-change box
is clear, whose two pickers sit on the dash and whose note is empty, so the act of naming a code
already silences it; the fields are then filled in against a live row.</p>
<h2 id="how-a-declared-code-is-read">How a Declared Code Is Read</h2>
<p><a class="xref" href="../../../../api/Hi.NcParsers.LogicSyntaxs.MCodeExpansionSyntax.html">MCodeExpansionSyntax</a> rewrites the declared flag into the canonical
ISO flags the ordinary consumers already understand, and it runs ahead of them:
<a class="xref" href="../../../../api/Hi.NcParsers.LogicSyntaxs.SpindleSpeedSyntax.html">SpindleSpeedSyntax</a>,
<a class="xref" href="../../../../api/Hi.NcParsers.LogicSyntaxs.CoolantSyntax.html">CoolantSyntax</a> and
<a class="xref" href="../../../../api/Hi.NcParsers.LogicSyntaxs.ToolChangeSyntax.html">ToolChangeSyntax</a> each then see the flag they know. Expanding once,
early, is what lets a single composite code feed several consumers without any of them fighting over
which removes the original flag.</p>
<p>Three rules of the rewrite are visible in a program:</p>
<ul>
<li><strong>The expansion sits at the declared flag's own position</strong>, and a repeat of the same declared code
later in the block is dropped. Textual order therefore keeps deciding last-wins conflicts exactly
as it did before any declaration existed — a composite code followed by a raw <code>M05</code> still ends
with the spindle stopped.</li>
<li><strong>A canonical flag that also appears raw and undeclared in the same block is not emitted twice.</strong>
The raw occurrence keeps its position, so the expansion cannot reorder what the block already said.</li>
<li><strong>Undeclared codes are untouched.</strong> One that no other syntax consumes then reaches
<a class="xref" href="../../../../api/Hi.NcParsers.InspectionSyntaxs.UnconsumedCheckSyntax.html">UnconsumedCheckSyntax</a>, which reports it as
<code>Parsing--Unconsumed</code> — the warning this leaf exists to answer.</li>
</ul>
<h3 id="the-spindle-only-row-takes-a-different-path">The spindle-only row takes a different path</h3>
<p>A declaration whose sole content is a spindle direction is deliberately <strong>not</strong> expanded.
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.MCodeEffects.html#Hi_NcParsers_Dependencys_MCodeEffects_IsSpindleDirectionOnly">IsSpindleDirectionOnly</a> marks it, the expansion skips
it, and <a class="xref" href="../../../../api/Hi.NcParsers.LogicSyntaxs.SpindleSpeedSyntax.html">SpindleSpeedSyntax</a> resolves it in place through
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.ISpindleControlConfig.html#Hi_NcParsers_Dependencys_ISpindleControlConfig_TryResolveDirection_">TryResolveDirection</a><small>(API)</small>.
What separates the two paths is mechanism, not reach. The resolver is consulted <strong>before</strong> the
built-in ISO mapping, so a spindle-only row overrides a canonical code where the code stands, with
no rewrite at all — declaring <code>M03</code> as CCW makes <code>M03</code> turn the spindle counter-clockwise. Add any
second effect to the same row and it leaves that path for the expansion, which arrives at the same
place by rewriting: the declared flag is replaced by the ISO flags its effects name, so declaring
<code>M08</code> as Mist makes the block emit <code>M07</code> instead. On either path a declaration over a canonical code wins against that
code's built-in meaning.</p>
<p>The split also reaches the project file. A spindle-only row serializes as the legacy
<code>&lt;SpindleMCode&gt;</code> element so an older reader still resolves it; every other row serializes as
<code>&lt;MCode&gt;</code> with one attribute per effect that is set. A row with nothing set writes an <code>&lt;MCode&gt;</code>
element carrying only its code, so the silent-consume state round-trips.</p>
<h3 id="what-the-writer-refuses">What the writer refuses</h3>
<p>The declare endpoint validates before it stores. An empty code is answered unsuccessfully; so is a
spindle direction that is not <code>CW</code> / <code>CCW</code> / <code>STOP</code> and a coolant mode that is not <code>Mist</code> / <code>Flood</code> /
<code>Off</code>, and those two answers quote the rejected value back alongside the set that was expected. A
typo therefore cannot degrade into a declaration that consumes a code silently. The panel's two
pickers cannot produce such a value; the guard is there for the other writers of the same table.</p>
<p>A coolant mode that names no known mode can still arrive from a project file, because the XML reader
keeps the attribute raw rather than normalizing it and write-then-read stays an identity. The
expansion is where that surfaces: it reports <code>DeclaredMCode--UnknownCoolantMode</code>, skips the coolant
half, and emits the rest of the declaration.</p>
<h2 id="the-tool-change-trigger-mode">The Tool-Change Trigger Mode</h2>
<p>The toggle above the table reads <em>&quot;T word performs the tool change itself (turret / lathe)&quot;</em> and
writes <a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.IToolChangeTriggerConfig.html#Hi_NcParsers_Dependencys_IToolChangeTriggerConfig_ToolWordTriggersChange">ToolWordTriggersChange</a>. It defaults
off, which is machining-centre behaviour: a <code>T</code> word only pre-selects, the magazine rotates without
moving a feed axis, and the trigger M-code performs the change.</p>
<p>Turned on, <a class="xref" href="../../../../api/Hi.NcParsers.LogicSyntaxs.ToolChangeSyntax.html">ToolChangeSyntax</a> treats a block carrying a <code>T</code> word and
no tool-change flag as the change itself and records <code>T</code> as the triggering term. The test is per
block: a block carrying both a <code>T</code> word and a tool-change flag is unaffected — the flag wins and
<code>M06</code> is recorded — so no single block is counted twice. Where the two words sit in different blocks
— a <code>T</code> pre-select first, an <code>M06</code> later — the mode counts the <code>T</code> block as a change on its own, and
the <code>M06</code> block then records a second one, its tool number arriving modally. The toggle therefore
belongs on a machine whose <code>T</code> word really performs the change; on a machining centre it turns every
pre-select into a change of its own.</p>
<p><strong>On the Siemens machine-data table the flag is not a field of its own.</strong> That table binds the
property to its tool-change-mode row,
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Siemens.SiemensMachineDataTable.html#Hi_NcParsers_Dependencys_Siemens_SiemensMachineDataTable_MdToolChangeMode">MdToolChangeMode</a>: present, the row
decides — a stored zero means the <code>T</code> word changes the tool — and the toggle writes back into it as
0 or 1 rather than into the brand-neutral field; absent, the table behaves like every other brand.
The write is skipped when the effective value already matches, so copying a table never rewrites a
raw machine-data value of 2 down to 1. The same row is visible and editable by number on
<a class="xref" href="native-parameters.html">Parameters (Native)</a>, and the two forms are one storage.</p>
<h2 id="the-rows">The Rows</h2>
<p>The read returns the declarations sorted by code as <strong>text</strong>, case-insensitively, not by numeric
value. <code>M106</code> therefore sorts ahead of <code>M12</code>, and <code>M6</code> lands after <code>M331</code>.</p>
<p>Keys are matched case-insensitively too, so <code>m13</code> and <code>M13</code> are one declaration and cannot both
exist; re-declaring a code in a different spelling replaces its effects and leaves the spelling
already stored on display. The <strong>Code</strong> cell is plain bold text with no editor — a code is changed
by declaring the new one and deleting the old.</p>
<p>Only one brand's default parameter table pre-declares anything. The Siemens machine-data default
seeds six auxiliary codes — <code>M12</code>, <code>M13</code>, <code>M22</code>, <code>M23</code>, <code>M330</code> and <code>M331</code> — each carrying no effect
and the same note, <em>&ldquo;machine-specific auxiliary function (OEM/PLC); the exact behavior depends on
the machine&rdquo;</em>. Those recur often enough in real Siemens programs to be worth silencing with an
explanation rather than a warning, and a machine table that knows their actual effects overrides the
seeded note. On the other four brands the table opens empty.</p>
<h2 id="editing-adding-and-removing">Editing, Adding and Removing</h2>
<p>Every cell commits on its own, and the write is the whole declaration: the handler merges the edited
field into the row and sends all four values, so an edit rewrites the row from what the panel is
currently showing. The checkbox and the two pickers commit on the click or the pick; the note cell
commits through its field's native change event, which is this branch's third text-commit wiring and
is recorded as such in <a class="xref" href="../editing-contract.html">Editing Contract</a> — that page also
carries the optimistic-write, rollback and toast rules these cells share with the rest of the branch.</p>
<p>The footer holds a single field labelled <strong>M-code</strong>, hinted <code>M106</code>, and a <strong>Declare</strong> button. Enter
in that field submits, which no other add-row footer in the branch does. A blank code raises the
toast <em>&quot;M-code must not be empty.&quot;</em> locally and never reaches the server. A successful declare clears
the field and re-reads the whole table, so the new row appears in sort position rather than at the
end.</p>
<p>Removal is the trash button at the end of each row. It carries no confirmation dialog, and the row
leaves the list only after the server has answered — the delete is not optimistic. The removed code
returns to what it was before the declaration: <code>Parsing--Unconsumed</code> wherever a program uses it,
unless a shared or brand syntax already knew the code, in which case it goes back to that built-in
meaning.</p>
<h2 id="what-this-table-does-not-show">What This Table Does Not Show</h2>
<p>The stored map is not the whole of what a machine honours, and three families of M-code behaviour
live outside it.</p>
<p><strong>The canonical ISO codes.</strong> <code>M06</code>, <code>M03</code> / <code>M04</code> / <code>M05</code> and <code>M07</code> / <code>M08</code> / <code>M09</code> are consumed by
the shared syntaxes with no declaration at all; this table exists for the codes those consumers do
not already know. Declaring a canonical code is still legal and still honoured — a note-only
declaration on <code>M08</code>, for instance, consumes the raw <code>M08</code> for its own declaration while a composite
code's flood half continues to emit <code>M08</code>.</p>
<p><strong>A brand's own M-function dialect.</strong> The Heidenhain preset's
<a class="xref" href="../../../../api/Hi.NcParsers.LogicSyntaxs.Heidenhain.HeidenhainMFunctionSyntax.html">HeidenhainMFunctionSyntax</a> owns <code>M126</code> / <code>M127</code>
(shortest-path rotary traverse) and <code>M140</code> (tool-axis retract) as brand meanings, not as
declarations, so none of them is a row here. That preset's syntax list places the expansion <strong>ahead</strong>
of the brand syntax precisely so a machine that redefines one of those codes can say so: a
declaration for <code>M126</code> is expanded and consumed before the brand meaning is reached, and the
declaration wins.</p>
<p><strong>The Siemens tool-change M function.</strong> The Siemens machine-data table's
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Siemens.SiemensMachineDataTable.html#Hi_NcParsers_Dependencys_Siemens_SiemensMachineDataTable_MdToolChangeMCode">MdToolChangeMCode</a> row names the
M function that performs a tool change, and the table overlays a tool-change declaration for it onto
the view the parsers resolve — merged into a clone of any explicit declaration on the same code, so a
note or coolant half is kept, and never written into the stored map. <strong>The panel reads the stored
map, so that overlaid row is not on screen.</strong> A Siemens machine whose machine data names a
non-standard trigger honours it in every program while this table lists nothing for it; the value
lives on <a class="xref" href="native-parameters.html">Parameters (Native)</a> as the machine-data row, in
the raw form an operator reads off the real controller. The overlay's own rules matter when the two
views are compared: the raw number is zero-padded to the parsed flag form, so 6 becomes <code>M06</code> and
106 becomes <code>M106</code>; a value that is not a positive whole number overlays nothing; and a code already
declared as a tool change here is left exactly as stored.</p>
<p>The trigger toggle above the table does <strong>not</strong> share that blind spot. It is read through the
property the Siemens table overrides, so it shows the machine data's answer, while the rows beside it
show only what was declared. The asymmetry is the single most useful thing to know about this leaf:
the mode is the effective value, the table is the stored one.</p>
<p>One further consequence of the overlay is invisible in both views. Spindle-only resolution also runs
against the overlaid view, so a code that the machine data names as the tool-change trigger and that
is <em>also</em> declared here with a spindle direction alone stops being spindle-only: it leaves the
in-place resolver and is expanded instead, emitting both the tool change and the direction.</p>
<h2 id="layout">Layout</h2>
<ul>
<li><strong>General Setup</strong> Control Tree — the left dock of <code>/general-setup</code>
<ul>
<li><strong>Controller</strong> Node Row
<ul>
<li><strong>Machine / Controller</strong> Group Row
<ul>
<li><strong>M-Code Declarations</strong> Node Row — <code>equipment/controller/machine/m-codes</code></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>Editor Row — <strong>M-Code Declarations</strong> Panel
<ul>
<li>No-Runner Line — <em>&quot;No NC runner — load a project first.&quot;</em>, the branch's shared empty state, shown
while the snapshot reports no runner</li>
<li>No-Table Line — <em>&quot;No controller parameter table on the active runner.&quot;</em>, shown when the read
itself reports no table</li>
<li><strong>T word performs the tool change itself (turret / lathe)</strong> Toggle</li>
<li>Hint Caption — <em>&quot;Declares machine-specific M-codes: a tool-change trigger other than <code>M6</code>,
composite OEM codes (e.g. <code>M13</code> = spindle CW + flood coolant), or known-but-unsimulated codes
(note only, one info message per occurrence). A declaration with nothing set consumes its code
silently. Undeclared codes keep the unconsumed warning.&quot;</em></li>
<li>Declaration Table — dense, flat and bordered, one row per declared code
<ul>
<li>Header Row — <strong>Code</strong>, <strong>Tool change</strong>, <strong>Spindle</strong>, <strong>Coolant</strong>, <strong>Not-simulated note</strong>, and
an unlabelled action column</li>
<li><strong>Code</strong> Cell — bold plain text, not editable</li>
<li><strong>Tool change</strong> CheckBox</li>
<li><strong>Spindle</strong> Select — a dash, <strong>CW (M03)</strong>, <strong>CCW (M04)</strong>, <strong>STOP (M05)</strong></li>
<li><strong>Coolant</strong> Select — a dash, <strong>Mist (M07)</strong>, <strong>Flood (M08)</strong>, <strong>Off (M09)</strong></li>
<li><strong>Not-simulated note</strong> Field — placeholder <em>e.g. chip conveyor forward</em></li>
<li>Delete Button — a trash icon, no confirmation</li>
</ul>
</li>
<li>Add Row Footer
<ul>
<li><strong>M-code</strong> Field — placeholder <code>M106</code>; Enter submits</li>
<li><strong>Declare</strong> Button</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>The panel mounts against one node and takes nothing from it: unlike the per-axis leaves it serves a
single tree id, so it reads no role off the node's path.</p>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../../../index.html">HiNC App Anatomy</a> for git repository links.</p>
<h3 id="web-application">Web Application</h3>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/controlTree/SoftNcMCodesPanel.vue</code> — this panel: the two empty layers,
the trigger toggle, the two option lists with their dash entry, the whole-declaration write behind
every cell, the change-event note cell, the Enter-submitting add footer and the
confirmation-free delete.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcEmptyState.vue</code> — the shared no-runner line the panel
opens with.</li>
<li><code>wwwroot-src/src/components/controlTree/softNcItemTypes.ts</code> — the node id, its label and label key,
the snapshot flag it is grown behind, and the item type that binds it to the panel.</li>
<li><code>wwwroot-src/src/api/softNcRunner.ts</code> — the declaration row and snapshot shapes, the reader that
coerces an empty string to a null field, the whole-declaration writer, the delete, and the trigger
setter.</li>
<li><code>wwwroot-src/src/composables/useSoftNcRunner.ts</code> — the shared runner snapshot the first empty layer
reads.</li>
<li><code>wwwroot-src/src/i18n/en/softNc.ts</code> — the node label, the toggle label, the hint, the column
headers, the two option label sets, the note placeholder, the empty-code toast and the two empty
strings quoted above.</li>
<li><code>Mech/SoftNcRunnerController.cs</code> — the REST surface: the read with its presence flag and its
case-insensitive text ordering, the declare endpoint's direction and coolant validation, the
delete, the trigger write, and the shared helper that answers a missing dependency with an
unsuccessful body rather than an error status.</li>
</ul>
<h3 id="hiapi-engine">HiAPI Engine</h3>
<ul>
<li><code>HiMech/NcParsers/Dependencys/IMCodeDeclarationConfig.cs</code> — the declaration contract: the map, the
lookup that returns false for an undeclared code, and the declare and remove mutators whose stored
effects are copied rather than aliased.</li>
<li><code>HiMech/NcParsers/Dependencys/MCodeEffects.cs</code> — the four effects, the empty and spindle-only
predicates that partition the two consumption paths, and the coolant-mode normalizer.</li>
<li><code>HiMech/NcParsers/Dependencys/IToolChangeTriggerConfig.cs</code> — the trigger contract and the
magazine-versus-turret distinction it encodes.</li>
<li><code>HiMech/NcParsers/Dependencys/ISpindleControlConfig.cs</code> — the narrower spindle face over the same
storage, and the resolver the spindle-only rows take.</li>
<li><code>HiMech/NcParsers/Dependencys/ControllerParameterTableBase.cs</code> — the storage every brand inherits:
the case-insensitive map, the overridable effective view, the virtual trigger property, and the
XML round trip that writes a spindle-only row as the legacy element.</li>
<li><code>HiMech/NcParsers/Dependencys/Siemens/SiemensMachineDataTable.cs</code> — the two machine-data rows that
change this leaf's meaning: the tool-change mode bound to the trigger property, the tool-change
M function overlaid onto the effective view with its zero-padding rule, and the six auxiliary
codes the default table pre-declares.</li>
<li><code>HiMech/NcParsers/LogicSyntaxs/MCodeExpansionSyntax.cs</code> — the rewrite: in-place expansion, the
duplicate and raw-twin guards, the spindle-only skip, and the two diagnostics a declaration can
raise.</li>
<li><code>HiMech/NcParsers/LogicSyntaxs/ToolChangeSyntax.cs</code> — the consumer of the expanded tool change and
of the trigger mode, and the term it records for each.</li>
<li><code>HiMech/NcParsers/LogicSyntaxs/SpindleSpeedSyntax.cs</code> — the consumer that tries the machine map
before the ISO defaults, which is what lets a spindle-only row remap a canonical code.</li>
<li><code>HiMech/NcParsers/LogicSyntaxs/CoolantSyntax.cs</code> — the consumer of the three expanded coolant
flags.</li>
<li><code>HiMech/NcParsers/LogicSyntaxs/Heidenhain/HeidenhainMFunctionSyntax.cs</code> — the brand meanings a
declaration can pre-empt on that preset.</li>
<li><code>HiMech/NcParsers/InspectionSyntaxs/UnconsumedCheckSyntax.cs</code> — the warning an undeclared code
keeps, and the one a declaration removes.</li>
<li><code>HiMech/NcParsers/Syntaxs/FanucSyntaxUtil.cs</code>,
<code>HiMech/NcParsers/Syntaxs/SiemensSyntaxUtil.cs</code>,
<code>HiMech/NcParsers/Syntaxs/SyntecSyntaxUtil.cs</code>,
<code>HiMech/NcParsers/Syntaxs/MazakSyntaxUtil.cs</code>,
<code>HiMech/NcParsers/Syntaxs/HeidenhainSyntaxUtil.cs</code> — where the expansion sits in each brand's
syntax list, and why the Heidenhain list anchors it earlier than the rest.</li>
<li><code>HiMech/NcParsers/Keywords/Generic/IsoKeywords.cs</code> — the canonical flag spellings the expansion
emits, <code>M06</code> among them.</li>
<li><code>HiMech/NcParsers/Keywords/ICoolantDef.cs</code> — the three coolant constants the picker's values map
to.</li>
<li><code>HiGeom/Numerical/SpindleDirection.cs</code> — the direction enum behind the spindle column.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="index.html">Machine and Controller Plane</a> — the plane this leaf sits on, and which half of a project the
declaration map is written into</li>
<li><a class="xref" href="../brand-matrix.html">Brand Matrix</a> — the flag that grows this node, and why it is never false on
a shipped brand preset</li>
<li><a class="xref" href="../editing-contract.html">Editing Contract</a> — the fetch, commit, rollback and toast rules this panel
shares with the branch, and the two places it is recorded as departing from them</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,561 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Native Parameters | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Native Parameters | HiAPI-C# 2025 ">
<link rel="icon" href="../../../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../../../public/docfx.min.css">
<link rel="stylesheet" href="../../../../public/main.css">
<meta name="docfx:navrel" content="../../../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../../../index.html">
<img id="logo" class="svg" src="../../../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-ControllerNativeParameters">
<h1 id="native-parameters">Native Parameters</h1>
<p>Parameters (Native) is the machine plane's escape hatch: the brand parameter table rendered as the
flat numbered form an operator reads off the real control, with no domain vocabulary and no unit
conversion in between. It lives on the General Setup page at <code>/general-setup</code> under the Control-Tree
id <code>equipment/controller/machine/native</code>, and it is one of the two leaves the <code>hasNativeTable</code>
snapshot flag grows — the other, M-Code Declarations, edits a different part of the same object.
Every value the domain-grouped leaves beside it edit <em>that is a row of the brand parameter table</em> is
a row here; the ones stored elsewhere — the tool-change pose, the block-skip layers, the subprogram
folders and the indexing position lists among them — reach no native form at all. Several rows here
reach no domain-grouped face.</p>
<div class="IMPORTANT">
<h5>Important</h5>
<p>Anatomy makes no claim that any id named here is stable across versions. The migration hop that
keeps an older <code>?tree=</code> value resolving is described in
<a class="xref" href="../../../platform/tree-ids-and-routes.html">Tree Ids and Routes</a>.</p>
</div>
<p>The leaf is pushed behind <code>hasNativeTable</code>, which reports whether any
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.ControllerParameterTableBase.html">ControllerParameterTableBase</a> resolves in the runner's effective
dependency list. All five shipped presets carry one, so the flag is a gate in code and unconditional
in practice — see <a class="xref" href="../brand-matrix.html">Brand Matrix</a>.</p>
<h2 id="how-a-parameter-is-addressed">How a Parameter Is Addressed</h2>
<p>The table stores its numbers in three dictionaries, and the panel is those three dictionaries with a
heading over each:</p>
<table>
<thead>
<tr>
<th>Section heading the user reads</th>
<th>Backing storage</th>
<th>Cell</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>System parameters</strong></td>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.ControllerParameterTableBase.html#Hi_NcParsers_Dependencys_ControllerParameterTableBase_SystemParams">SystemParams</a></td>
<td>one value per id</td>
</tr>
<tr>
<td><strong>Axis parameters</strong></td>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.ControllerParameterTableBase.html#Hi_NcParsers_Dependencys_ControllerParameterTableBase_AxisParams">AxisParams</a></td>
<td>one floating-point value per id <strong>per axis</strong></td>
</tr>
<tr>
<td><strong>Axis parameters (integer)</strong></td>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.ControllerParameterTableBase.html#Hi_NcParsers_Dependencys_ControllerParameterTableBase_IntAxisParams">IntAxisParams</a></td>
<td>one integer value per id <strong>per axis</strong></td>
</tr>
</tbody>
</table>
<p>An id is a bare number in storage. What the panel prints in front of it is the brand's prefix, and
that prefix is the whole of the addressing convention:</p>
<table>
<thead>
<tr>
<th>Brand marker</th>
<th>Prefix shown</th>
<th>Table that answers</th>
<th>Example row</th>
</tr>
</thead>
<tbody>
<tr>
<td>Fanuc</td>
<td><code>#</code></td>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Fanuc.FanucParameterTable.html">FanucParameterTable</a></td>
<td><code>#1420</code> rapid traverse rate</td>
</tr>
<tr>
<td>Mazak</td>
<td><code>#</code></td>
<td>the <strong>Fanuc</strong> table</td>
<td><code>#1420</code> rapid traverse rate</td>
</tr>
<tr>
<td>Syntec</td>
<td><code>Pr</code></td>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Syntec.SyntecParameterTable.html">SyntecParameterTable</a></td>
<td><code>Pr1420</code> rapid traverse rate</td>
</tr>
<tr>
<td>Siemens</td>
<td><code>MD</code></td>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Siemens.SiemensMachineDataTable.html">SiemensMachineDataTable</a></td>
<td><code>MD32000</code> max axis velocity</td>
</tr>
<tr>
<td>Heidenhain</td>
<td><code>MP</code></td>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Heidenhain.HeidenhainParameterTable.html">HeidenhainParameterTable</a></td>
<td><code>MP1010</code> rapid traverse rate</td>
</tr>
</tbody>
</table>
<p><strong>Mazak borrows the Fanuc table outright.</strong> The Mazak preset carries the Fanuc parameter-table proxy
rather than one of its own, so the prefix resolves to <code>#</code> and every number on this leaf is a Fanuc
parameter number under a <code>Mazak</code> brand badge. The prefix is not taken from the brand marker string
but from the type of the parameter table that resolved, so the two cannot drift apart the way a
brand-marker label can — see <a class="xref" href="../brand-matrix.html">Brand Matrix</a>.</p>
<p>A <code>ControllerParameterTableBase</code> subclass outside those four resolves an empty prefix, and the ids
then render bare. No shipped preset produces that.</p>
<p>The prefix the panel renders comes from the leaf's <strong>own</strong> read of the parameter form. The shared
runner snapshot carries a prefix field of its own, and nothing renders it.</p>
<h3 id="the-caption-under-the-id">The caption under the id</h3>
<p>Each id cell carries the number in bold and, beneath it, a short usage caption — <em>Rapid traverse rate
(mm/min or deg/min)</em>, <em>Axis type (0 linear / 1 rotary / 2 spindle)</em>, <em>G54 X offset</em>, <em>Tool change M
function code</em>. The same text repeats as the cell's hover title.</p>
<p>The caption is the brand table's own description of a well-known number, produced by
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.ControllerParameterTableBase.html#Hi_NcParsers_Dependencys_ControllerParameterTableBase_DescribeSystemParam_">DescribeSystemParam</a><small>(API)</small>
and its two per-axis counterparts, and it travels down the wire with the row. It is therefore
<strong>engine text, not an interface string</strong>: it is written in English in the brand table and belongs to
no locale bundle, so it reads the same under every language the application offers. An id the brand
table does not model has no caption at all, which is how a modelled row and a free extra are told
apart on screen.</p>
<h2 id="raw-values-raw-units">Raw Values, Raw Units</h2>
<p>The caption at the top of the panel states the rule: <em>&quot;Raw stored values in native units — no unit
conversion is applied here. The interface-form nodes edit the same backing table.&quot;</em></p>
<p>No cell carries a unit suffix, and none can: the unit belongs to the number, not to the column. What
a cell means is whatever the brand stores at that address — millimetres or degrees on a stroke limit,
mm/min or deg/min on a rapid rate, rpm on a spindle limit, an enumeration ordinal on an axis type, a
count on a controlled-axis parameter.</p>
<p><strong>The peck clearance is the case where the two forms visibly disagree, and it ships.</strong> Canned Cycle
(Peck) always speaks millimetres; the Fanuc-family table stores millimetres at <code>#4002</code>, and the
Syntec table stores <strong>microns</strong> at <code>Pr4002</code>. A machine at a 5 mm clearance therefore reads <code>5</code> on
<a class="xref" href="interface-parameters.html">Interface Parameters</a> on every brand, and reads <code>5</code> here on Fanuc
and Mazak but <code>5000</code> here on Syntec. The write endpoint converts on the way in; this leaf does not,
which is the point of it — an operator copying from a real Syntec control reads the micron form.</p>
<p>Two further asymmetries follow from where a value is stored rather than from its unit:</p>
<ul>
<li><strong>On Siemens and Heidenhain the peck clearance has no row here at all.</strong> Those two presets supply
it through a generic fallback config, a plain runner entry rather than a parameter table, so the
interface leaf shows a field whose backing has no place in this form.</li>
<li><strong>On the Fanuc-family and Syntec tables the work-coordinate offsets are rows of this table.</strong> The
G54G59 triads start at <code>#5221</code> and the G54.1 P1P48 triads at <code>#7001</code>, both on a stride of 20, all
in the System parameters section. That mixing is why the machine plane's parameter table is stored
on the project rather than on the runner — see
<a class="xref" href="index.html">Machine and Controller Plane</a>. On the Fanuc-family table those two
address ranges are also what an NC program's <code>#</code>-variable read resolves against, so those rows are
live in both directions.</li>
</ul>
<h2 id="what-only-this-leaf-reaches">What Only This Leaf Reaches</h2>
<p>Most rows have a domain-grouped twin. These do not.</p>
<p><strong>The axis roster itself.</strong> Every per-axis leaf of the branch — Machine Limits (Stroke), Rapid
Feedrates, Home / G28 Reference, Tool-Change Position — lists the axes named by the <strong>axis-type row</strong>
of this table (<code>#1006</code>, <code>Pr1006</code>, <code>MD30300</code>, <code>MP400</code>), because
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.ControllerParameterTableBase.html#Hi_NcParsers_Dependencys_ControllerParameterTableBase_AxisNames">AxisNames</a> is that row's key set. None of
those leaves can add an axis, rename one, remove one, or change one from linear to rotary; their
columns are handed to them. Inside the Controller branch the axis-type row is editable here and
nowhere else, through the footer with the section set to <strong>Axis (integer)</strong>, the brand's axis-type
number as the id, an axis name, and a value of <code>0</code> linear, <code>1</code> rotary or <code>2</code> spindle. The one other
writer is outside the branch entirely:
<a class="xref" href="../../../../api/Hi.NcParsers.SoftNcRunner.html#Hi_NcParsers_SoftNcRunner_ConfigureByMachiningChain_">ConfigureByMachiningChain</a><small>(API)</small> stamps the machining
chain's axis codes into the same row when a machine tool is attached to the project; an axis the
chain does not name stays in the row. That call does not stop at the axis-type row: a chain axis it
marks rotary also has its reference position set to <code>0</code> and its rapid rate to <code>36000</code> deg/min,
whatever those two Axis parameters rows held.</p>
<p>The inverse holds, with one exception. Deleting the axis-type row leaves the table with no named
axes, so Machine Limits (Stroke), Rapid Feedrates and Home / G28 Reference have no rows left to
draw. Tool-Change Position keeps its own: its reader falls back to the tool-change configuration's
own axis keys when the axis set is empty, and every shipped preset seeds that configuration with X,
Y and Z. Rows recovered that way carry no axis type and are reported linear.</p>
<p><strong>The Siemens indexing machine data, all four numbers.</strong> <code>MD30500</code>, the per-axis assignment that
decides which axis consumes which position table, is <em>shown</em> on
<a class="xref" href="indexing-positions.html">Indexing Position Tables</a> as display-only text and is
written here as an <strong>Axis parameters (integer)</strong> row; the REST surface carries no assignment writer
at all. The equidistant definition — the numerator <code>MD30501</code> and the offset <code>MD30503</code> as axis
parameters, the denominator <code>MD30502</code> as an integer axis parameter — has no endpoint that reads or
writes it and no panel that offers a cell for it: on the web surface it is three ordinary rows on
this leaf. The engine is its reader: an axis whose <code>MD30500</code> is 3 resolves its station spacing from
the trio, and Indexing Position Tables prints that axis' assignment as <em>Equidistant
(MD30501MD30503)</em>, naming the three numbers it offers no cell for. The two position lists
themselves are the mirror image, stored as list properties outside all three dictionaries and so
absent from this form entirely.</p>
<p><strong>Numbers with no interface field.</strong> The controlled-axis count (<code>#1020</code>, <code>Pr1020</code>) is read by nothing
in the web service. The Siemens fixed-point position <code>MD30600</code>, the G75 target, likewise. So is the
Siemens tool-change M function <code>MD22560</code>, whose presence overlays a tool-change effect onto the named
code in the declaration view the parsers resolve against. M-Code Declarations renders the stored map
rather than that view, so the overlaid row is on no screen at all: the effect is honoured in every
program the machine runs, and the one number that shows anywhere is this row. The Siemens tool-change
mode <code>MD22550</code> is a half case: where the row already exists, the <em>T word performs the tool change
itself</em> toggle on M-Code Declarations writes it, and where it does not, that toggle writes a
brand-neutral flag instead; creating the row is possible only here.</p>
<p><strong>Any number the model does not name.</strong> The three writers are get-or-create, so an id outside the
brand's well-known set is accepted, stored, serialized into the project file and read back. It
carries no usage caption, and the engine consumes only the numbers its brand table models — a free
extra is a record, not a setting.</p>
<h2 id="a-fractional-number-has-three-fates">A Fractional Number Has Three Fates</h2>
<p>The <strong>Axis parameters (integer)</strong> section is the one place on the branch where a value can be typed,
accepted by the field, and silently discarded.</p>
<p>The cell is the shared numeric widget, which commits on blur or Enter and carries no integer rule.
Typing <code>2.5</code> parses cleanly: the widget clears its own error state, emits <code>2.5</code> to the panel, and
rewrites its text to the parsed form. The panel's integer handler is where the value stops — it
returns without assigning and without raising anything when the number is not whole. Because the
bound value never changed, nothing pushes the stored integer back into the box. <strong>The cell goes on
reading <code>2.5</code> over a table that still holds the old integer</strong>, with no error message, no toast and no
console line. The row corrects itself only when the panel refetches: after an add, after a removal,
or on a fresh mount when the selection leaves this leaf and comes back.</p>
<p>The footer does not behave the same way. <strong>Add / Set</strong> with <strong>Axis (integer)</strong> selected <em>truncates</em> a
fractional value toward zero and writes the truncation, so the same <code>2.5</code> becomes a stored <code>2</code>; and
the same button refuses a fractional <strong>Parameter id</strong> outright, with the message <em>&quot;Enter a
non-negative integer id and a value.&quot;</em> That refusal belongs to the button, not to the box — the
<strong>Parameter id</strong> field is the shared numeric widget with a minimum of 0 and no integer rule, so it
accepts and keeps <code>2.5</code> the way any other cell does, and the one message answers an empty <strong>Value
(raw)</strong> as well. Three controls, one number, three outcomes.</p>
<p><strong>Clearing any cell ends the same way for a different reason.</strong> An emptied field commits <code>null</code>, and
all three handlers return early on <code>null</code>, so the box is blank and the stored value is untouched.
There is no way to unset one cell of a table; the value stands until it is overwritten or its whole
row is removed. The general rule for a box that disagrees with its model is
<a class="xref" href="../../../widget/numeric-input.html">Numeric Input</a>; what is particular here is that the widget did nothing
wrong — the panel accepted the commit and dropped it.</p>
<h2 id="adding-overwriting-and-removing-a-row">Adding, Overwriting and Removing a Row</h2>
<p>The footer is one control set for both adding and overwriting, and the button says so: <strong>Add / Set</strong>.
The write is get-or-create at every level — an unknown id creates its row, an unknown axis name
creates its column — so setting an existing cell and creating a new one are the same request.</p>
<ul>
<li>The <strong>Axis</strong> field appears only while the section is not <em>System</em>, is free text, and is trimmed and
checked for emptiness only. It is not validated against the machine's axes: any name is accepted
and becomes a new column.</li>
<li>A successful add re-reads the whole form. The footer's own fields are <strong>not</strong> cleared, and Enter
does not submit — see <a class="xref" href="../editing-contract.html">Editing Contract</a> for how the
branch's add footers differ from one another.</li>
<li>The two client-side refusals are the id, which must be a non-negative whole number, and, for the
two per-axis sections, a non-empty axis name. Each raises a negative toast and sends nothing.</li>
</ul>
<p><strong>Delete is per row, not per cell.</strong> The trailing button on a row of either per-axis table removes
the id from that dictionary entirely — every axis column of it at once, not the cell it sits beside.
A confirmation dialog comes first, titled <em>Remove parameter</em> and reading <em>Remove <code>&lt;prefixed id&gt;</code> from
the <code>&lt;section&gt;</code> section?</em>, where the section word is the storage kind — <em>system</em>, <em>axis</em>, <em>int-axis</em>
rather than the heading printed above the table. The removal is not optimistic: the request is
awaited and the whole form is re-read.</p>
<p><strong>A deleted well-known row can come back on its own.</strong> The brand tables' modelled accessors are
get-or-create with a default, so a read through a sibling leaf re-materializes the row it wants. The
clearest case is the maximum spindle speed: delete <code>#3741</code> here, open Interface Parameters, and that
read stores the brand's default back — 60000 on Fanuc, Mazak, Siemens and Heidenhain, 24000 on
Syntec — so the row reappears at that value. The cutter-compensation type, the tool-axis direction
and the peck clearance behave the same way. A free extra id has no accessor and stays deleted.</p>
<h2 id="what-a-freshly-switched-brand-shows">What a Freshly Switched Brand Shows</h2>
<p>The seed each brand's proxy clones decides what the three tables open with. All five declare a linear
X, Y and Z in the axis-type row, a reference position of zero per axis and a rapid rate per axis, so
on every brand the Axis parameters section opens with two rows and the Axis parameters (integer)
section with one. The System section is where they part company.</p>
<table>
<thead>
<tr>
<th>Brand</th>
<th>System parameters</th>
<th>Axis parameters</th>
<th>Axis parameters (integer)</th>
</tr>
</thead>
<tbody>
<tr>
<td>Fanuc, Mazak</td>
<td>166 rows — controlled axes, max spindle speed, peck retraction, cutter-comp type, and 162 seeded work-offset addresses at zero</td>
<td>reference position, rapid rate</td>
<td>axis type</td>
</tr>
<tr>
<td>Syntec</td>
<td>166 rows — the same four under <code>Pr</code> numbering, the peck retraction in microns, and the same 162 offsets</td>
<td>reference position, rapid rate</td>
<td>axis type</td>
</tr>
<tr>
<td>Siemens</td>
<td>1 row — max spindle speed</td>
<td>reference position, max axis velocity</td>
<td>axis type</td>
</tr>
<tr>
<td>Heidenhain</td>
<td>2 rows — max spindle speed, tool-axis direction</td>
<td>reference position, rapid rate</td>
<td>axis type</td>
</tr>
</tbody>
</table>
<p>The 162 offset rows are
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.IsoCoordinateAddressMap.html#Hi_NcParsers_Dependencys_IsoCoordinateAddressMap_SeedAllDefaults_">SeedAllDefaults</a><small>(API)</small> writing
every G54G59 and G54.1 P1P48 triad as zero, so that a managed address always has a value the way a
real control with a fresh battery reads zero rather than nothing. They dominate the System section on
the three brands that carry them, and their captions — <em>G54 X offset</em>, <em>G54.1P12 Z offset</em> — are what
separates them from machine configuration in the same list.</p>
<p>No brand seeds a stroke limit, so the positive and negative stroke-limit rows are absent from the
Axis parameters section until Machine Limits (Stroke) or this leaf creates them.</p>
<h2 id="the-two-empty-layers-and-which-one-is-reachable">The Two Empty Layers, and Which One Is Reachable</h2>
<p>The panel opens through the branch's shared two-layer gate. The first layer is the shared <em>&quot;No NC
runner — load a project first.&quot;</em> from the runner snapshot; the second is <em>&quot;No controller parameter
table on the active runner.&quot;</em> from the leaf's own read — the same line M-Code Declarations and
Interface Parameters show, and honest in all three, because every one of those reads reports presence
from the same parameter-table lookup.</p>
<p>Neither line is normally reached by navigating, because the node itself exists only while the
snapshot reported a parameter table. The second line is also what a failed read leaves on screen: the
presence flag starts false and the failure path only raises a toast. A failed write answers inside a
success envelope — HTTP 200 carrying <code>No ControllerParameterTableBase on the active runner</code> — and
surfaces as one negative toast, three seconds, the panel's localized context followed by that raw
English sentence. The full rule is <a class="xref" href="../editing-contract.html">Editing Contract</a>.</p>
<h2 id="layout">Layout</h2>
<ul>
<li><strong>General Setup</strong> Control Tree — the left dock of <code>/general-setup</code>
<ul>
<li><strong>Controller</strong> Node Row
<ul>
<li><strong>Machine / Controller</strong> Group Row
<ul>
<li><strong>Parameters (Native)</strong> Node Row — <code>equipment/controller/machine/native</code>, the last leaf of the
plane, after <strong>Indexing Position Tables</strong></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>Editor Row — the panel of the selected node
<ul>
<li><strong>Parameters (Native)</strong> Panel — one column, gutter-spaced
<ul>
<li>Hint Caption — <em>&quot;Raw stored values in native units — no unit conversion is applied here. The
interface-form nodes edit the same backing table.&quot;</em></li>
<li><strong>System parameters</strong> Section Heading</li>
<li>System Table — dense, flat, bordered
<ul>
<li>Header Row — <strong>Id</strong>, <strong>Value</strong>, and an unlabelled action column</li>
<li>Data Row — the prefixed id in bold over its usage caption (absent on a free extra), a numeric
cell, and a delete button</li>
</ul>
</li>
<li><strong>Axis parameters</strong> Section Heading</li>
<li>Axis Table — dense, flat, bordered
<ul>
<li>Header Row — <strong>Id</strong>, then one column per axis name, then an unlabelled action column</li>
<li>Data Row — the prefixed id in bold over its usage caption, one numeric cell per axis column
(blank where the row has no entry for that axis), and a delete button that removes the whole
id</li>
</ul>
</li>
<li><strong>Axis parameters (integer)</strong> Section Heading</li>
<li>Integer Axis Table — the same shape as the Axis Table, over the integer dictionary</li>
<li>Separator</li>
<li>Add Row — one line, bottom-aligned
<ul>
<li><strong>Section</strong> Select — <em>System</em> / <em>Axis (double)</em> / <em>Axis (integer)</em></li>
<li><strong>Parameter id</strong> Numeric Field — minimum 0</li>
<li><strong>Axis</strong> Text Field — present only while the section is not <em>System</em></li>
<li><strong>Value (raw)</strong> Numeric Field</li>
<li><strong>Add / Set</strong> Button — primary</li>
</ul>
</li>
<li>No-Table Body — replaces everything above: <em>&quot;No controller parameter table on the active
runner.&quot;</em></li>
<li>Shared Empty State — replaces the whole body while the snapshot reports no runner: <em>&quot;No NC
runner — load a project first.&quot;</em></li>
</ul>
</li>
<li>Remove Dialog — <em>Remove parameter</em> over <em>Remove <code>&lt;prefixed id&gt;</code> from the <code>&lt;section&gt;</code> section?</em>,
with a cancel</li>
<li>Toast — negative, three seconds, the panel's context followed by the server's own message</li>
</ul>
</li>
</ul>
<p>The axis columns of the two per-axis tables are computed independently of each other, and each is the
union of the shared snapshot's axis names with every axis key present in that table's own rows. An
axis created here therefore gets its column from the rows even before the shared snapshot is next
refreshed, and an axis the snapshot knows about with no entry in a row shows an empty cell rather
than being omitted.</p>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../../../index.html">HiNC App Anatomy</a> for git repository links.</p>
<h3 id="web-application">Web Application</h3>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/controlTree/SoftNcNativeParamsPanel.vue</code> — this panel: the three tables
over the three dictionaries, the prefixed id column with its usage caption, the axis-column union,
the three commit handlers with the integer test that drops a fractional value, the
confirm-then-remove flow, and the add footer with its truncation and its two refusals.</li>
<li><code>wwwroot-src/src/components/controlTree/softNcItemTypes.ts</code> — the node's id, label and label key,
and the snapshot flag it is pushed behind, last on the machine plane.</li>
<li><code>wwwroot-src/src/api/softNcRunner.ts</code> — the reader that parses the prefix and the three row
families, the three per-cell writers and the three per-id removals, and the snapshot field carrying
a prefix this panel does not use.</li>
<li><code>wwwroot-src/src/composables/useSoftNcRunner.ts</code> — the shared snapshot the first empty layer and the
axis-column union read.</li>
<li><code>wwwroot-src/src/components/widgets/NumericInput.vue</code> — the cell: commit on blur or Enter, the
parsed value written back into the box, and the absence of any integer rule.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcEmptyState.vue</code> — the shared no-runner line.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcIndexingPositionsPanel.vue</code> — the sibling that
displays the MD30500 assignment this leaf writes, and points here for it.</li>
<li><code>wwwroot-src/src/api/http.ts</code> — the shared fetch helper that turns both a non-2xx status and a
<code>success: false</code> body into one thrown error.</li>
<li><code>wwwroot-src/src/i18n/en/softNc.ts</code> — the node label, the raw-units hint, the three section
headings, the three add-footer section options, the two footer refusals and the five error
contexts — one read and four writes, the two per-axis savers sharing a context.</li>
<li><code>wwwroot-src/src/i18n/en/common.ts</code> — the shared <strong>Id</strong>, <strong>Value</strong>, <strong>Axis</strong> and <strong>Add / Set</strong>
strings.</li>
<li><code>wwwroot-src/src/i18n/en/dialog.ts</code> — the remove dialog's title, its message, and the three storage
kind words it interpolates.</li>
<li><code>Mech/SoftNcRunnerController.cs</code> — the REST surface: the read that orders each dictionary by id and
attaches the brand's usage description to every row, the prefix computed from the resolved table's
type, the three get-or-create writers and the three whole-row removals, the shared dependency
wrapper that answers a missing table inside a success envelope, and the neighbouring readers this
form's rows also feed — the three per-axis reads that take the axis-type row's key set bare, the
tool-change read that falls back to its own configuration's keys when that set is empty, and the
M-code read bound to the stored declaration map rather than the overlaid view.</li>
</ul>
<h3 id="hiapi-engine">HiAPI Engine</h3>
<ul>
<li><code>HiMech/NcParsers/Dependencys/ControllerParameterTableBase.cs</code> — the three dictionaries this leaf
is, the get-or-create bucket accessors, the role interfaces that let the domain-grouped leaves read
the same rows, the axis-name set taken from the axis-type row, the three usage-description methods,
the rotary-axis helper that writes type, reference position and rapid rate in one call, and the XML
round-trip that persists every row including free extras.</li>
<li><code>HiMech/NcParsers/Dependencys/Fanuc/FanucParameterTable.cs</code> — the <code>#</code> vocabulary: the well-known
numbers, the get-or-create defaults behind the modelled accessors, the macro-variable lookup over
the work-offset address ranges, and the seed a fresh Fanuc or Mazak runner opens on.</li>
<li><code>HiMech/NcParsers/Dependencys/Syntec/SyntecParameterTable.cs</code> — the <code>Pr</code> vocabulary, and the peck
clearance stored in microns with the conversion kept in its accessor.</li>
<li><code>HiMech/NcParsers/Dependencys/Siemens/SiemensMachineDataTable.cs</code> — the <code>MD</code> vocabulary: the
indexing assignment, the three equidistant numbers this leaf alone edits and the spacing
resolution that reads them, the tool-change mode, the tool-change M function with the read-time
declaration overlay that never enters the stored map, the fixed-point position, and the seed that
declares one system row.</li>
<li><code>HiMech/NcParsers/Dependencys/Heidenhain/HeidenhainParameterTable.cs</code> — the <code>MP</code> vocabulary and its
two-row seed.</li>
<li><code>HiMech/NcParsers/Dependencys/IsoCoordinateAddressMap.cs</code> — the work-offset address scheme shared by
the Fanuc and Syntec tables, the per-address description that captions those rows, and the seeding
of every triad to zero.</li>
<li><code>HiMech/NcParsers/Dependencys/IMachineAxisConfig.cs</code> — the axis contract, and the axis-type
enumeration a cell of the integer section stores.</li>
<li><code>HiMech/NcParsers/Dependencys/Generic/ToolingMcConfig.cs</code> — the tool-change pose, stored outside
the three dictionaries, whose own X / Y / Z keys are what Tool-Change Position falls back to when
the axis-type row is gone.</li>
<li><code>HiMech/NcParsers/SoftNcRunner.cs</code> — the five brand presets and which parameter-table proxy each
carries, the Fanuc proxy inside the Mazak preset among them, the tool-change configuration every
preset seeds, and the chain configuration that is the only other writer of the axis-type row and,
on a rotary chain axis, of that axis' reference position and rapid rate.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="index.html">Machine and Controller Plane</a> — the plane this leaf sits on, and why the table it exposes is
stored on the project rather than on the runner file</li>
<li><a class="xref" href="../brand-matrix.html">Brand Matrix</a> — the flag that grows this node, why it never reports false on
a shipped preset, and why Mazak resolves the Fanuc table</li>
<li><a class="xref" href="../editing-contract.html">Editing Contract</a> — the commit, rollback, confirm and toast rules this panel
shares with the branch, and the add-footer details it differs on</li>
<li><a class="xref" href="interface-parameters.html">Interface Parameters</a> — the same values in domain vocabulary, including the peck
clearance this leaf shows in the brand's own unit</li>
<li><a class="xref" href="indexing-positions.html">Indexing Position Tables</a> — the leaf that displays the MD30500 assignment and the
equidistant definition that are written here</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,580 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Per-Axis Tables | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Per-Axis Tables | HiAPI-C# 2025 ">
<link rel="icon" href="../../../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../../../public/docfx.min.css">
<link rel="stylesheet" href="../../../../public/main.css">
<meta name="docfx:navrel" content="../../../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../../../index.html">
<img id="logo" class="svg" src="../../../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-ControllerAxisTables">
<h1 id="per-axis-tables">Per-Axis Tables</h1>
<p>Four leaves of the Controller branch's machine plane are one table each, keyed on the machine's
axes: <strong>Machine Limits (Stroke)</strong>, <strong>Rapid Feedrates</strong>, <strong>Home / G28 Reference</strong> and <strong>Tool-Change
Position</strong>. They live on the General Setup page at <code>/general-setup</code> under the Control-Tree ids
<code>equipment/controller/machine/limits</code>, <code>equipment/controller/machine/rapid</code>,
<code>equipment/controller/machine/home</code> and <code>equipment/controller/machine/tool-change</code>; none of them is
gated by a snapshot flag, so every brand grows all four. Two components serve them — one panel
behind the first three, discriminating on the id's last segment, and a second behind Tool-Change
Position — and the differences between those two components are what this page is mostly about.</p>
<div class="IMPORTANT">
<h5>Important</h5>
<p>Anatomy makes no claim that any id named here is stable across versions. The migration hop that
keeps an older <code>?tree=</code> value resolving is described in
<a class="xref" href="../../../platform/tree-ids-and-routes.html">Tree Ids and Routes</a>.</p>
</div>
<h2 id="where-the-rows-come-from">Where the Rows Come From</h2>
<p>Every row on all four leaves is one axis of the runner's machine-axis configuration, read as
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.IMachineAxisConfig.html#Hi_NcParsers_Dependencys_IMachineAxisConfig_AxisNames">AxisNames</a> through the proxy-resolved dependency
list. On all five brand presets that configuration is the brand parameter table itself:
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.ControllerParameterTableBase.html">ControllerParameterTableBase</a> declares
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.IMachineAxisConfig.html">IMachineAxisConfig</a> alongside
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.IStrokeLimitConfig.html">IStrokeLimitConfig</a>,
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.IRapidFeedrateConfig.html">IRapidFeedrateConfig</a> and
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.IHomeMcConfig.html">IHomeMcConfig</a>, so one object supplies both the row set and three of
the four value sets. Which brand carries which table is
<a class="xref" href="../brand-matrix.html">Brand Matrix</a>.</p>
<p>The table's axis set is a single parameter row: the per-axis integer bucket under the brand's
axis-type number, whose keys are the axis names and whose values are the
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.AxisType.html">AxisType</a> — linear, rotary or spindle. Adding an axis therefore means
adding a key to that one row, and exactly one surface on this branch reaches it.</p>
<ul>
<li>Neither panel here has an add-row or delete-row control. Both render a fixed header over one
<code>&lt;tr&gt;</code> per axis and nothing else.</li>
<li>The four per-axis routes behind them write one axis' value and nothing more: the caller names an
axis, and the endpoint writes into the stroke-limit, rapid-rate, home or tool-change store under
that name. None of them touches the axis-type row, so none of them changes which rows the next
read returns.</li>
<li><a class="xref" href="native-parameters.html">Parameters (Native)</a> edits the axis-type row itself, and
is the one place on the branch that widens the axis set: its <strong>Add / Set</strong> footer takes a
free-text axis name, so the section <em>Axis (integer)</em>, the brand's axis-type number, a name and a
value of <code>0</code> linear, <code>1</code> rotary or <code>2</code> spindle adds an axis, which then appears on all four
leaves. Its grid cannot — the per-axis columns are the union of the axis names already present, so
a cell can be written only for an axis that exists — and its delete button removes a whole
parameter row, every axis cell of one number at once, rather than one axis.</li>
</ul>
<p>Axes arrive from the machining chain. Whenever the active runner suit or the chain changes, the
project service re-binds the suit and calls
<a class="xref" href="../../../../api/Hi.NcParsers.SoftNcRunner.html#Hi_NcParsers_SoftNcRunner_ConfigureByMachiningChain_">ConfigureByMachiningChain</a><small>(API)</small>, which walks the
chain's axis codes and their transformers: a rotating transformer writes the axis as rotary — and,
on a brand parameter table, writes its reference position as 0 deg and its rapid rate as 36000
deg/min — any other transformer writes the axis as linear, and every chain axis whose reference
position is still unset is seeded to 0. That is the mechanism behind the description above the
Home / G28 Reference table, <em>&quot;Axes without a value are seeded to 0 when a machine tool attaches.&quot;</em></p>
<p><strong>The stamp adds rows and never removes one.</strong> The generic axis configuration carried by the CSV and
CL runner suits is cleared before the walk; a brand parameter table is not, and keeps every axis it
already held. An axis dropped from the chain therefore keeps its rows on all four leaves, and no
surface removes it individually. The three-axis default table every brand preset seeds is why a
project with no machine tool attached still shows X, Y and Z.</p>
<p>Two <strong>values</strong> are the exception, and only on a rotary chain axis of a brand table: that branch of
the walk is unconditional, so it rewrites the axis' reference position to 0 deg and its rapid rate
to 36000 deg/min whether or not one was stored. The walk re-runs on every suit re-bind, so an edited
Home Position or Rapid Rate on a rotary axis is back at those two numbers once the chain or the
active suit changes. A linear axis' values, and every value on an axis the chain does not carry, are
left untouched.</p>
<p>The caption below the first three tables records the row half of that: <em>&quot;Axis rows follow the Machine
Tool chain; they cannot be added here.&quot;</em> Where the chain itself is edited is
<a class="xref" href="../../machine-tool.html">Machine Tool</a>.</p>
<h2 id="the-value-columns">The Value Columns</h2>
<p>Each leaf renders the axis column, its own value column or columns, and a unit column.</p>
<table>
<thead>
<tr>
<th>Leaf</th>
<th>Value columns</th>
<th>Empty cell means</th>
</tr>
</thead>
<tbody>
<tr>
<td>Machine Limits (Stroke)</td>
<td><strong>+ Limit</strong>, <strong> Limit</strong></td>
<td>no limit set for that axis and side</td>
</tr>
<tr>
<td>Rapid Feedrates</td>
<td><strong>Rapid Rate</strong></td>
<td>never empty — see below</td>
</tr>
<tr>
<td>Home / G28 Reference</td>
<td><strong>Home Position</strong></td>
<td>no reference position stored</td>
</tr>
<tr>
<td>Tool-Change Position</td>
<td><strong>Stays put</strong>, <strong>Position</strong></td>
<td>see <a href="#stays-put-and-the-sentinel-behind-it">Stays Put</a></td>
</tr>
</tbody>
</table>
<p>The stroke-limit and home readers return an optional value and hand null straight through, so an
unset cell is blank. The rapid reader does not: it returns the stored rate, or — when the axis has
no rate of its own — the base class's fixed default, 20000 mm/min for a linear axis and 36000
deg/min for a rotary one. So a Rapid Rate cell is always a number, and a number in it is not proof
that the axis has a row.</p>
<p>A table cloned fresh from a brand's preset seed decides what the three leaves then open on, and all
four brand defaults agree in shape: a linear X, Y and Z, a reference position of 0 on each, and a
rapid rate per axis — and no stroke limit at all. Machine Limits therefore opens entirely blank on
every brand, while Rapid Feedrates and Home / G28 Reference open populated. Whether a brand switch
clones a fresh table or keeps the project's own is
<a class="xref" href="index.html">Machine and Controller Plane</a>.</p>
<h2 id="the-unit-column">The Unit Column</h2>
<p>The unit column's header is the translated label <em>Unit</em>; its cell text is not translated and does
not come from the server. Each panel computes it in the browser from the row's rotary flag, using
literal strings:</p>
<table>
<thead>
<tr>
<th>Leaf</th>
<th>Linear axis</th>
<th>Rotary axis</th>
</tr>
</thead>
<tbody>
<tr>
<td>Machine Limits (Stroke)</td>
<td><code>mm</code></td>
<td><code>deg</code></td>
</tr>
<tr>
<td>Rapid Feedrates</td>
<td><code>mm/min</code></td>
<td><code>deg/min</code></td>
</tr>
<tr>
<td>Home / G28 Reference</td>
<td><code>mm</code></td>
<td><code>deg</code></td>
</tr>
<tr>
<td>Tool-Change Position</td>
<td><code>mm</code></td>
<td><code>deg</code></td>
</tr>
</tbody>
</table>
<p>The flag itself is server-side, and it is the axis type rather than a separate setting: an axis
stored as rotary <strong>or as spindle</strong> reports rotary, so a spindle-mode axis is labelled in degrees.</p>
<p><strong>The unit is a label, not a conversion.</strong> The base class writes a linear and a rotary rapid rate
into the same per-axis cell and reads them back through the same lookup, differing only in the
default returned when the cell is absent; the stroke-limit and home accessors are equally unit-blind.
Nothing on these three leaves converts, so their numbers are the raw stored numbers, and the same
values appear in <a class="xref" href="native-parameters.html">Parameters (Native)</a> under the brand's own
parameter number:</p>
<table>
<thead>
<tr>
<th>Role</th>
<th>Fanuc, Mazak</th>
<th>Syntec</th>
<th>Siemens</th>
<th>Heidenhain</th>
</tr>
</thead>
<tbody>
<tr>
<td>Axis type — the row set</td>
<td>1006</td>
<td>1006</td>
<td>30300</td>
<td>400</td>
</tr>
<tr>
<td>Reference position (home)</td>
<td>1240</td>
<td>1240</td>
<td>34010</td>
<td>410</td>
</tr>
<tr>
<td>Positive stroke limit</td>
<td>1300</td>
<td>1300</td>
<td>36100</td>
<td>420</td>
</tr>
<tr>
<td>Negative stroke limit</td>
<td>1320</td>
<td>1320</td>
<td>36110</td>
<td>430</td>
</tr>
<tr>
<td>Rapid rate</td>
<td>1420</td>
<td>1420</td>
<td>32000</td>
<td>1010</td>
</tr>
</tbody>
</table>
<p>Mazak shares the Fanuc column because the Mazak preset carries the Fanuc parameter-table proxy;
Siemens spends its rapid-rate role on the max-axis-velocity machine datum.</p>
<p>Tool-Change Position is the exception on both counts: its values live on
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Generic.ToolingMcConfig.html">ToolingMcConfig</a>, a plain runner-owned dependency rather than
a parameter table, so they appear in no native form at all and a brand apply returns them to the
preset defaults — see <a class="xref" href="index.html">Machine and Controller Plane</a>.</p>
<h2 id="stays-put-and-the-sentinel-behind-it">Stays Put, and the Sentinel Behind It</h2>
<p><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.IToolingMcConfig.html">IToolingMcConfig</a> stores one number per axis, and the contract gives
one number a second meaning: <strong>NaN means the axis stays where it is</strong> during a tool change. The read
splits that back into two fields — a stored NaN becomes the <em>Stays put</em> flag with no position,
anything else becomes the position — and the panel renders the flag as a checkbox and the position
as a numeric field disabled while the box is ticked.</p>
<p>Writing goes the same way round. Ticking the box sends the stay flag and the reader writes NaN.
Unticking it sends a position, and because the panel clears its own cell when the box is ticked, the
position it sends on the way back is 0 — so unticking parks the axis at machine zero rather than
restoring what was there before.</p>
<p>A <strong>third</strong> state exists and is not the sentinel: an axis with no entry in the map at all. Its box is
unticked and its Position cell blank, because the reader reports no position and no stay. At run
time the two are indistinguishable — the tool-change motion overlays only those axes that carry a
number and are not NaN onto the current pose, and leaves every other axis alone. So the machine
stays put either way; the tick mark distinguishes only how that was recorded.</p>
<p>The overlay has a vocabulary of its own, narrower than the table. X, Y and Z always take part; every
other axis takes part only while the machine declares it rotary. A linear axis outside those three
is stored, shown and editable on this leaf, and never moved.</p>
<p>The preset default is exactly this mixture: X and Y carry the sentinel, Z carries 0, and no rotary
axis carries an entry, which is why a rotary axis usually opens unticked and blank.</p>
<p>Above the table sits <strong>Tool-change mechanism time</strong>, a single field in seconds bound to
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.IToolingMcConfig.html#Hi_NcParsers_Dependencys_IToolingMcConfig_ToolingTime">ToolingTime</a>. It becomes the duration of the tool
change step the runner emits, and it is the changer mechanism alone — the axis travel to and from
the position is timed separately from the positions in the table. It is 0 on every brand preset, and
it is written into the runner file only when non-zero.</p>
<h2 id="one-reader-two-readings-of-its-presence-flag">One Reader, Two Readings of Its Presence Flag</h2>
<p>All four reads answer the same shape: a <code>present</code> flag plus one row per axis. <code>present</code> reports
whether the backing configuration resolved — stroke limit, rapid feedrate, home or tool change — and
it is computed <strong>independently of the rows</strong>. Each reader builds the row list from the axis set
whether or not that configuration resolved, and fills the value cells from an optional reference, so
a missing configuration yields blank limit and home cells, a 0 rapid rate, and rows all the same.</p>
<p>The two components read the answer differently, and this is the sharpest divergence between them.</p>
<ul>
<li>The <strong>axis-table panel discards <code>present</code> entirely.</strong> Its typed reader parses the flag; the panel
assigns only the rows. Its second empty layer keys on the row count instead: with no axes it shows
<em>&quot;No machine axes yet — these rows are driven by the Machine Tool chain (MechBuilder). Attach a
machine tool to edit per-axis values.&quot;</em>, and with axes it renders the table whatever the flag said.</li>
<li>The <strong>tool-change panel reads <code>present</code></strong> and, when it is false, shows <em>&quot;No tool-change config on
the active runner.&quot;</em></li>
</ul>
<p>What that costs: a runner that has axes but no stroke-limit, rapid-feedrate or home configuration
renders a full, editable table of blank or zero cells, and every commit fails with a toast naming the
missing dependency instead of the panel saying the table is absent. On the five shipped brand presets
the state cannot arise, because the one object that supplies the axis set supplies all three
configurations too. The reachable case is the mirror one, and it reaches the two panels differently:
deleting the axis-type parameter row from Parameters (Native) empties the axis set, and the three
axis-table leaves fall to the no-axes line while <strong>Tool-Change Position keeps its rows</strong>, because its
reader falls back to the tool-change configuration's own axis keys when the axis set is empty. Rows
recovered that way are reported as linear whatever they are.</p>
<p>The tool-change flag itself is never false on a shipped preset: all five carry the tool-change
configuration as a plain entry, so its empty line is reachable only on a hand-built or file-loaded
runner.</p>
<p>Above both layers sits the shared no-runner guard every leaf of the branch carries; it, and the
commit, rollback and toast rules the four leaves share with the rest of the branch, are
<a class="xref" href="../editing-contract.html">Editing Contract</a>.</p>
<h2 id="editing-a-cell">Editing a Cell</h2>
<p>Numeric cells are the shared numeric field, so a value commits on blur or on Enter and never per
keystroke — its full contract is <a class="xref" href="../../../widget/numeric-input.html">Numeric Input</a>. The <em>Stays put</em>
checkbox commits on the click. Three details govern how these four leaves commit:</p>
<ul>
<li><strong>The writes are per cell, not per row.</strong> Editing <strong>+ Limit</strong> sends that side alone, and the
endpoint leaves a side it was not sent unchanged. The branch's row-shaped editors do not agree on
this: a tool-offset or work-coordinate edit resends the whole row, while
<a class="xref" href="native-parameters.html">Parameters (Native)</a> writes one axis cell at a time as
these four do.</li>
<li><strong>Clearing a cell writes nothing.</strong> An emptied field parses to null and the handler returns before
the request, so the box is left blank on screen while the stored number stands, and the number
returns when the panel is remounted by selecting another node and coming back. There is
consequently no way to unset a stroke limit or a reference position from these panels; the only
surface that removes one is the native leaf's whole-row delete.</li>
<li><strong>No cell is bounded.</strong> None of the four tables passes a minimum or a maximum to its fields, so a
negative limit or a negative rapid rate is accepted and stored. The mechanism time is the one
bounded field on the four leaves, at zero or above.</li>
</ul>
<h2 id="what-the-values-do-when-a-program-plays">What the Values Do When a Program Plays</h2>
<p>The stroke limits are checked at each played step against the chain's current machine pose while the
session's stroke-limit check is on; a position past a configured limit is reported as a
stroke-limit validation error anchored to that step, and pauses the player when pause-on-failure is
set. The check walks a <strong>fixed axis vocabulary</strong> — X, Y and Z from the machine point, A, B and C
from its orientation — so a limit stored against any other axis name is kept and shown here but never
tested. Rapid-traverse timing has the same shape and the same six names: a G00 move is timed
axis-by-axis at each axis' rate and takes the slowest, falling back to the same 20000 mm/min and
36000 deg/min defaults when no configuration answers.</p>
<p>The reference positions reach further, but only at the start. The initializer that sets the machine
pose at the first block writes <strong>every declared axis</strong>, reading each axis' stored reference and
falling back to 0 for one that has none. The G28 reference return reads the same store through a
narrower window: X, Y and Z for its linear stage, and A, B or C for its rotary stage — and a rotary
letter only while the machine declares that axis rotary and a reference position is stored for it,
an unconfigured one being reported as a validation error instead. So an axis outside the six
canonical names still carries its stored reference into the pose at the first block, while its
stroke limit, its rapid rate and its G28 return are all inert.</p>
<h2 id="layout">Layout</h2>
<ul>
<li><strong>General Setup</strong> Control Tree — the left dock of <code>/general-setup</code>
<ul>
<li><strong>Controller</strong> Node Row
<ul>
<li><strong>Machine / Controller</strong> Group Row
<ul>
<li><strong>Machine Limits (Stroke)</strong> Node Row — <code>equipment/controller/machine/limits</code></li>
<li><strong>Rapid Feedrates</strong> Node Row — <code>equipment/controller/machine/rapid</code></li>
<li><strong>Home / G28 Reference</strong> Node Row — <code>equipment/controller/machine/home</code></li>
<li><strong>Tool-Change Position</strong> Node Row — <code>equipment/controller/machine/tool-change</code></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>Editor Row — the panel of whichever node is selected
<ul>
<li><strong>Machine Limits (Stroke)</strong> / <strong>Rapid Feedrates</strong> / <strong>Home / G28 Reference</strong> Panel — one
component, its role taken from the selected id's last segment
<ul>
<li>Description Caption — <em>&quot;Machine travel limits per axis (empty = no limit set). Motions beyond a
limit raise a stroke-limit validation error.&quot;</em>, <em>&quot;G00 rapid traverse rate per axis.&quot;</em> or
<em>&quot;G28 first reference (home) machine coordinate per axis. Axes without a value are seeded to 0
when a machine tool attaches.&quot;</em></li>
<li>Axis Table — dense, flat, bordered
<ul>
<li>Header Row — <strong>Axis</strong>, then <strong>+ Limit</strong> and <strong> Limit</strong>, or <strong>Rapid Rate</strong>, or
<strong>Home Position</strong>, then <strong>Unit</strong></li>
<li>Axis Row, one per axis
<ul>
<li>Axis Name Label — bold, not editable</li>
<li>Value Numeric Field, one per value column</li>
<li>Unit Label — <code>mm</code> / <code>deg</code>, or <code>mm/min</code> / <code>deg/min</code> on Rapid Feedrates</li>
</ul>
</li>
</ul>
</li>
<li>Footer Caption — <em>&quot;Axis rows follow the Machine Tool chain; they cannot be added here.&quot;</em></li>
<li>No-Axes Block — replaces the description, the table and the footer caption when the axis set is
empty: a precision-manufacturing icon beside <em>&quot;No machine axes yet — these rows are driven by
the Machine Tool chain (MechBuilder). Attach a machine tool to edit per-axis values.&quot;</em></li>
</ul>
</li>
<li><strong>Tool-Change Position</strong> Panel
<ul>
<li><strong>Tool-change mechanism time</strong> Numeric Field — suffixed <code>s</code>, minimum 0</li>
<li>Axis Table — dense, flat, bordered
<ul>
<li>Header Row — <strong>Axis</strong>, <strong>Stays put</strong>, <strong>Position</strong>, <strong>Unit</strong></li>
<li>Axis Row, one per axis
<ul>
<li>Axis Name Label — bold, not editable</li>
<li><strong>Stays put</strong> CheckBox</li>
<li>Position Numeric Field — disabled while <strong>Stays put</strong> is ticked</li>
<li>Unit Label — <code>mm</code> / <code>deg</code></li>
</ul>
</li>
</ul>
</li>
<li>Footer Caption — <em>&ldquo;Stays put&rdquo; leaves the axis where it is during a tool change.</em></li>
<li>No-Config Block — replaces the field, the table and the caption:
<em>&quot;No tool-change config on the active runner.&quot;</em></li>
</ul>
</li>
<li>Shared Empty State — replaces either panel's whole body while the snapshot reports no runner:
<em>&quot;No NC runner — load a project first.&quot;</em></li>
</ul>
</li>
</ul>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../../../index.html">HiNC App Anatomy</a> for git repository links.</p>
<h3 id="web-application">Web Application</h3>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/controlTree/SoftNcAxisTablePanel.vue</code> — the panel behind the first
three leaves: the role taken from the node id's last segment, the value columns and description
per role, the unit strings computed from the row's rotary flag, the row-count empty layer that
discards the read's presence flag, and the per-cell commit that returns on a cleared field.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcToolChangePanel.vue</code> — the tool-change panel: the
mechanism-time field, the <em>Stays put</em> checkbox that clears the position and sends 0 on the way
back, the disabled position cell, and the presence-flag empty layer.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcEmptyState.vue</code> — the shared no-runner line both
panels open with.</li>
<li><code>wwwroot-src/src/components/widgets/NumericInput.vue</code> — the numeric cell: commit on blur or Enter,
and the empty-text-to-null parse the panels reject.</li>
<li><code>wwwroot-src/src/components/controlTree/softNcItemTypes.ts</code> — the four node ids, their labels and
label keys, and the two item types that bind them to the two panels.</li>
<li><code>wwwroot-src/src/api/softNcRunner.ts</code> — the four readers and their setters: the row shapes, the
presence flag both panels receive, the per-side stroke-limit payload, and the stay-or-position
tool-change payload.</li>
<li><code>wwwroot-src/src/composables/useSoftNcRunner.ts</code> — the shared runner snapshot the first empty layer
reads.</li>
<li><code>wwwroot-src/src/i18n/en/softNc.ts</code> — the four node labels, the three descriptions, the column
headers, the two footer captions and the two empty-state lines quoted above.</li>
<li><code>wwwroot-src/src/i18n/en/common.ts</code> — the shared <strong>Axis</strong> and <strong>Unit</strong> column headers.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcNativeParamsPanel.vue</code> — the neighbouring leaf that
edits the same parameter rows by number: the per-cell axis writes, the grid columns unioned from
the existing axis names, the delete that drops a whole row, and the <strong>Add / Set</strong> footer whose
free-text axis field is what widens the axis-type row.</li>
<li><code>Mech/SoftNcRunnerController.cs</code> — the REST surface: the chain-driven axis helper every reader
builds its rows from, the four readers with their independently computed presence flags, the four
per-axis writers that name an axis but never declare one, the native per-axis-integer route that
does, the tool-change fallback to the tool-change configuration's own axis keys, the NaN sentinel
write, and the rotary lookup the rapid write re-derives server-side.</li>
</ul>
<h3 id="hiapi-engine">HiAPI Engine</h3>
<ul>
<li><code>HiMech/NcParsers/Dependencys/ControllerParameterTableBase.cs</code> — the one object behind three of the
four leaves: the axis set as the keys of the axis-type row, the reference-position, rapid-rate and
stroke-limit accessors over the per-axis buckets, the fixed rapid defaults, and the rotary
configuration helper the chain walk calls, which writes the axis type, the reference position and
the rapid rate together with no guard on the two values.</li>
<li><code>HiMech/NcParsers/Dependencys/IMachineAxisConfig.cs</code> — the axis contract and the axis-type enum
whose rotary and spindle members both make a row rotary.</li>
<li><code>HiMech/NcParsers/Dependencys/IStrokeLimitConfig.cs</code> — the limit accessors, and the check itself
with its fixed X/Y/Z and A/B/C vocabulary and its validation report.</li>
<li><code>HiMech/NcParsers/Dependencys/IRapidFeedrateConfig.cs</code> — the linear and rotary rate accessors.</li>
<li><code>HiMech/NcParsers/Dependencys/IHomeMcConfig.cs</code> — the reference-position accessors.</li>
<li><code>HiMech/NcParsers/Dependencys/IToolingMcConfig.cs</code> — the tool-change contract: the NaN sentinel and
the mechanism time.</li>
<li><code>HiMech/NcParsers/Dependencys/Generic/ToolingMcConfig.cs</code> — the sole implementation: the per-axis
map, the preset default of X and Y staying and Z at 0, and the serialization that omits a zero
mechanism time.</li>
<li><code>HiMech/NcParsers/Dependencys/Fanuc/FanucParameterTable.cs</code>,
<code>HiMech/NcParsers/Dependencys/Syntec/SyntecParameterTable.cs</code>,
<code>HiMech/NcParsers/Dependencys/Siemens/SiemensMachineDataTable.cs</code>,
<code>HiMech/NcParsers/Dependencys/Heidenhain/HeidenhainParameterTable.cs</code> — the parameter numbers in
the table above and the three-axis default each brand opens on.</li>
<li><code>HiMech/NcParsers/Dependencys/Fanuc/FanucParameterTableProxy.cs</code>,
<code>HiMech/NcParsers/Dependencys/Syntec/SyntecParameterTableProxy.cs</code>,
<code>HiMech/NcParsers/Dependencys/Siemens/SiemensMachineDataTableProxy.cs</code>,
<code>HiMech/NcParsers/Dependencys/Heidenhain/HeidenhainParameterTableProxy.cs</code> — the placeholders the
brand presets actually carry: each clones its three-axis seed into the project's own dependency
list when that list holds none, and resolves to the project's table when it does.</li>
<li><code>HiMech/NcParsers/SoftNcRunner.cs</code> — the chain walk that stamps axis codes, hands a rotary axis to
the unconditional rotary helper on a brand table, and fills a missing reference position; and the
five brand presets that decide which parameter table and which tool-change configuration a brand
carries.</li>
<li><code>HiNc/MachiningProcs/LocalProjectService.cs</code> — where the chain walk is triggered from, why the
generic axis configuration is cleared first and a brand table is not, and the per-step stroke-limit
check that reads the limits through the proxy-resolved list.</li>
<li><code>HiMech/MachiningProcs/MachiningSession.cs</code> — the play loop that runs that check once per step and
pauses on failure.</li>
<li><code>HiMech/NcParsers/Semantics/LinearMotionUtil.cs</code> — rapid-traverse timing: per-axis rates, the
slowest axis, and the same six axis names the stroke check uses.</li>
<li><code>HiMech/NcParsers/Initializers/HomeMcInitializer.cs</code> — the reference positions written for every
declared axis at the first block.</li>
<li><code>HiMech/NcParsers/LogicSyntaxs/ReferenceReturnSyntax.cs</code> — the G28 return: the three linear names
it resolves a final position for, the three rotary letters it tests against the declared axes, and
the validation error a rotary letter the machine has not declared raises.</li>
<li><code>HiMech/NcParsers/LogicSyntaxs/ToolChangeMotionSyntax.cs</code> — the overlay: X, Y and Z plus the
declared rotary axes, of which only the ones carrying a non-NaN position move.</li>
<li><code>HiMech/NcParsers/Semantics/ToolChangeSemantic.cs</code> — the tool-change step that carries the
mechanism time as its duration.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="index.html">Machine and Controller Plane</a> — the plane these four leaves sit on, and which half of the
project each of their values is stored in</li>
<li><a class="xref" href="../brand-matrix.html">Brand Matrix</a> — why all four are ungated, and which table each brand
resolves them through</li>
<li><a class="xref" href="../editing-contract.html">Editing Contract</a> — the fetch, commit, rollback and empty-layer rules these
panels share with the rest of the branch, and where they are recorded as departing from them</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,533 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Program Reading | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Program Reading | HiAPI-C# 2025 ">
<link rel="icon" href="../../../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../../../public/docfx.min.css">
<link rel="stylesheet" href="../../../../public/main.css">
<meta name="docfx:navrel" content="../../../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../../../index.html">
<img id="logo" class="svg" src="../../../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-ControllerProgramReading">
<h1 id="program-reading">Program Reading</h1>
<p>Program Reading is the pair of Machine / Controller leaves that change how the runner <em>reads</em> a
program rather than how the machine moves: which block-skip layers are active, and where a
subprogram call goes looking for its file. Both live on the General Setup page at <code>/general-setup</code>,
under the Control-Tree ids <code>equipment/controller/machine/block-skip</code> and
<code>equipment/controller/machine/subprograms</code>. The pair is also unevenly branded — every shipped brand
preset carries the subprogram-folder config, and one of the five carries no block-skip config at
all.</p>
<div class="IMPORTANT">
<h5>Important</h5>
<p>Anatomy makes no claim that any id named here is stable across versions. The migration hop that
keeps an older <code>?tree=</code> value resolving is described in
<a class="xref" href="../../../platform/tree-ids-and-routes.html">Tree Ids and Routes</a>.</p>
</div>
<h2 id="the-asymmetry">The Asymmetry</h2>
<p>Both nodes are grown behind a plain presence probe over the runner's proxy-resolved dependency
list: <code>hasBlockSkip</code> is set when the list holds any
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.IBlockSkipConfig.html">IBlockSkipConfig</a>, <code>hasSubprogramFolders</code> when it holds a
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Generic.SubProgramFolderConfig.html">SubProgramFolderConfig</a>. Neither probe inspects a value.</p>
<table>
<thead>
<tr>
<th>Brand preset</th>
<th>Block-skip config</th>
<th>Subprogram-folder config</th>
</tr>
</thead>
<tbody>
<tr>
<td>Fanuc</td>
<td>yes</td>
<td>yes</td>
</tr>
<tr>
<td>Siemens</td>
<td>yes</td>
<td>yes</td>
</tr>
<tr>
<td>Syntec</td>
<td>yes</td>
<td>yes</td>
</tr>
<tr>
<td>Mazak</td>
<td>yes</td>
<td>yes</td>
</tr>
<tr>
<td>Heidenhain</td>
<td><strong>no</strong></td>
<td>yes</td>
</tr>
</tbody>
</table>
<p><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Generic.GenericBlockSkipConfig.html">GenericBlockSkipConfig</a> is the <strong>only</strong> implementer of the
block-skip interface anywhere in the engine, and the Heidenhain preset's pipeline list is the one of
the five that does not carry it. That single omission is what makes Block Skip / Delete the one node
in the whole Controller branch that exists on four brands and not the fifth; every other gate on
this plane is satisfied either by all five presets or by a Siemens-only table. Which flag each row
answers to is <a class="xref" href="../brand-matrix.html">Brand Matrix</a>.</p>
<p>The config is not the only thing missing there. The block-skip switches gate a parsing syntax,
<a class="xref" href="../../../../api/Hi.NcParsers.ParsingSyntaxs.BlockSkipSyntax.html">BlockSkipSyntax</a>, and that syntax appears in the Fanuc, Siemens,
Syntec and Mazak syntax lists and in no other — the Heidenhain list carries no block-delete parser,
so on that preset the nine switches would have nothing to gate.</p>
<p>The subprogram-folder config runs the other way: all five presets carry one, and five call
statements across three brand families read the same two values. The node's own vocabulary is
Fanuc's, because the storage is, but the setting is not Fanuc-only.</p>
<h2 id="block-skip--delete">Block Skip / Delete</h2>
<p>The panel is a caption over nine checkboxes and nothing else. The caption reads <em>&quot;Enabled layers
skip their <code>/n</code>-prefixed blocks (<code>/</code> alone = layer 1).&quot;</em>, the first box is labelled <em>&quot;Layer 1
(bare /)&quot;</em> and the rest <em>&ldquo;Layer 2&rdquo;</em> through <em>&ldquo;Layer 9&rdquo;</em>.</p>
<h3 id="what-a-layer-does-to-a-block">What a layer does to a block</h3>
<p><a class="xref" href="../../../../api/Hi.NcParsers.ParsingSyntaxs.BlockSkipSyntax.html">BlockSkipSyntax</a> matches a leading <code>/</code> optionally followed by a
single digit <code>1</code><code>9</code>; a bare slash is layer 1. It then always writes a
<a class="xref" href="../../../../api/Hi.NcParsers.Keywords.BlockSkip.html">BlockSkip</a> section recording the symbol and the layer, and always
consumes the prefix out of the block's unparsed text — so the <code>/</code> itself never survives as residue
for the downstream unrecognized-text check, whatever the switches say.</p>
<p>What the switch decides is the block's <strong>body</strong>:</p>
<ul>
<li><strong>Layer on</strong> — the remaining text is moved into
<a class="xref" href="../../../../api/Hi.NcParsers.Keywords.BlockSkip.html#Hi_NcParsers_Keywords_BlockSkip_Body">Body</a> and the unparsed text is cleared. Every downstream
parsing syntax then sees nothing, so the block emits no NC act, and the run records
<code>BlockSkip--Skipped</code> at message severity for that block.</li>
<li><strong>Layer off, or no config at all</strong> — the body stays null and the rest of the block parses exactly
as an unprefixed one would. A runner with no block-skip config therefore simulates the whole
program, which is the safe reading of an unknown machine setting.</li>
</ul>
<p>Ordering matters for one thing and is fixed in each brand's list: the comment and NC-embedded
script syntaxes run <strong>before</strong> the block-skip syntax, so a comment on a skipped block — and any
script embedded in it — still takes effect while the motion beside it does not.</p>
<h3 id="one-value-seven-spellings-of-the-same-range">One value, seven spellings of the same range</h3>
<p>Layers 1 through 9 are the whole range, and no shared constant carries it. The bound is written out
separately in the parsing syntax's regular expression, in the config's XML reader and again in its
CSV setter, in its per-layer mutator guard, in the read endpoint that enumerates the layers, in the
write endpoint that loops over them, and in the panel's checkbox repeat. A tenth layer is therefore
seven edits, not one, and a partial change would be silent — a switch the panel offered but the
config refused to store would simply never take.</p>
<h3 id="what-a-fully-cleared-set-does-not-survive">What a fully cleared set does not survive</h3>
<p>A freshly applied preset has layer 1 on and the other eight off. The stored form is a
comma-separated list of the enabled layers, and an empty set serializes as an empty element. The
reader treats a blank list and a missing element identically — both mean <em>layer 1 only</em> — so
clearing every box and then reloading the project brings layer 1 back on. Every other combination
round-trips exactly.</p>
<h3 id="the-write">The write</h3>
<p>Each box commits on the click, and the request is the <strong>whole</strong> enabled set rather than the one
layer that changed: the panel sorts its set and sends it, and the endpoint walks 1 through 9 setting
each layer to whether the payload names it. A failed write restores the previous set and raises a
toast; the shared rules behind that shape are
<a class="xref" href="../editing-contract.html">Editing Contract</a>.</p>
<p>On the brand that carries no config, the read answers <em>not present</em> with an empty layer list and any
write is answered unsuccessfully with <em>&ldquo;No IBlockSkipConfig on the active runner&rdquo;</em> — but the node is
not built there, so neither answer is reachable from the tree.</p>
<h2 id="subprogram-folders">Subprogram Folders</h2>
<p>Two plain text fields, labelled <em>&quot;Internal folder (M98)&quot;</em> and <em>&quot;External folder (M198)&quot;</em>, hinted
<em>&ldquo;Relative to the host NC file's folder; empty = that folder itself&rdquo;</em> and <em>&ldquo;Fanuc external-storage
calls; empty = fall back to the internal folder&rdquo;</em>. They write
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Generic.SubProgramFolderConfig.html#Hi_NcParsers_Dependencys_Generic_SubProgramFolderConfig_InternalFolder">InternalFolder</a> and
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Generic.SubProgramFolderConfig.html#Hi_NcParsers_Dependencys_Generic_SubProgramFolderConfig_ExternalFolder">ExternalFolder</a>; an empty or
whitespace-only box is stored as null rather than as an empty string.</p>
<h3 id="which-call-statements-read-these-two-values">Which call statements read these two values</h3>
<table>
<thead>
<tr>
<th>Preset</th>
<th>Call statement</th>
<th>Field it reads</th>
<th>File-name chain, first match wins</th>
</tr>
</thead>
<tbody>
<tr>
<td>Fanuc, Mazak, Syntec</td>
<td><code>M98 P_ L_</code></td>
<td>Internal</td>
<td><code>O{P:D4}.NC</code>, <code>O{P}.NC</code>, <code>O{P:D4}</code>, <code>O{P}</code>, <code>{P:D4}.NC</code>, <code>{P}.NC</code></td>
</tr>
<tr>
<td>Fanuc, Mazak, Syntec</td>
<td><code>M198 P_</code></td>
<td>External, falling back to Internal when it is null</td>
<td>the same chain</td>
</tr>
<tr>
<td>Fanuc</td>
<td><code>G65</code> / <code>G66</code> macro call</td>
<td>Internal</td>
<td>the same chain</td>
</tr>
<tr>
<td>Siemens</td>
<td>name call (<code>L9810</code>, a named cycle)</td>
<td>Internal</td>
<td><code>{name}.SPF</code>, <code>{name}.MPF</code>, <code>{name}</code></td>
</tr>
<tr>
<td>Heidenhain</td>
<td><code>CALL PGM name</code></td>
<td>Internal</td>
<td><code>{name}.h</code>, <code>{name}.H</code>, <code>{name}</code></td>
</tr>
</tbody>
</table>
<p>Two consequences follow from the table, and both are invisible from the field labels.</p>
<p><strong>The External folder is inert on two brands.</strong> Only the Fanuc-family inliner,
<a class="xref" href="../../../../api/Hi.NcParsers.EvaluationSyntaxs.SubProgramCallSyntax.html">SubProgramCallSyntax</a>, ever reads it — the Siemens and
Heidenhain call syntaxes look up their callee through the Internal folder alone. The field is
offered on every brand because the storage is brand-neutral, and it is honoured on the three that
have an <code>M198</code>.</p>
<p><strong>The Internal folder is read by more than the M98 label says.</strong> On the Fanuc preset it also anchors
both Custom Macro B lookups — <a class="xref" href="../../../../api/Hi.NcParsers.EvaluationSyntaxs.Fanuc.FanucMacroCallSyntax.html">FanucMacroCallSyntax</a> for
the <code>G65</code> one-shot call and <a class="xref" href="../../../../api/Hi.NcParsers.EvaluationSyntaxs.Fanuc.FanucModalMacroSyntax.html">FanucModalMacroSyntax</a> for
the <code>G66</code> modal — and on Siemens and Heidenhain it is the <em>only</em> subprogram root there is. Emptying
it to fix an M98 path therefore moves the macro and named-call lookups with it.</p>
<p>Each brand also brings its own file-name chain, and the chains are per-syntax properties rather than
one shared list — the Fanuc chain is
<a class="xref" href="../../../../api/Hi.NcParsers.EvaluationSyntaxs.MacroFileResolver.html#Hi_NcParsers_EvaluationSyntaxs_MacroFileResolver_FilenamePatterns">FilenamePatterns</a>, a static array every
Fanuc-family caller shares, while
<a class="xref" href="../../../../api/Hi.NcParsers.EvaluationSyntaxs.Siemens.SiemensSubProgramCallSyntax.html#Hi_NcParsers_EvaluationSyntaxs_Siemens_SiemensSubProgramCallSyntax_FilePatterns">FilePatterns</a> and
<a class="xref" href="../../../../api/Hi.NcParsers.EvaluationSyntaxs.Heidenhain.HeidenhainSubProgramCallSyntax.html#Hi_NcParsers_EvaluationSyntaxs_Heidenhain_HeidenhainSubProgramCallSyntax_FilePatterns">FilePatterns</a> are
instance properties of their own call syntaxes and are re-writable from the runner file. Case
matching is delegated to the host file system throughout, which is why the Heidenhain chain carries
both <code>.h</code> and <code>.H</code>.</p>
<h3 id="where-a-relative-folder-is-anchored">Where a relative folder is anchored</h3>
<p>The resolver takes the folder as written. An absolute path is used unchanged. A relative path — and
an empty one, which means <em>the root itself</em> — is combined with the base directory supplied by
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.SystemWired.ProjectFolderDependency.html">ProjectFolderDependency</a>, and the machining session wires
that dependency to the <strong>project's</strong> own root before each play.</p>
<p>So the anchor is the project folder, not the folder the host NC file happens to sit in. A program
played from a subfolder of the project resolves <code>NC</code> against the project root, not against its own
neighbour of that name. The field's hint says otherwise, and so does the API summary on the two
config properties the panel writes; the resolver is what runs.</p>
<p>Two further conditions end the lookup before any pattern is tried: a relative folder with no base
directory resolves to nothing, and so does a folder that does not exist as a directory.</p>
<h3 id="a-missing-file-is-an-error-on-one-family-and-a-warning-on-the-other">A missing file is an error on one family and a warning on the other</h3>
<p>The same empty result is reported at two different severities, and the split follows the call
statement rather than the setting.</p>
<ul>
<li><strong>Fanuc, Mazak, Syntec</strong> — an unresolved <code>M98</code> / <code>M198</code> raises <code>SubProgramCall--FileNotFound</code> at
error severity, quoting the folder that was searched, and the call is consumed.</li>
<li><strong>Siemens, Heidenhain</strong> — an unresolved call raises <code>SiemensCall--Skipped</code> or
<code>HeidenhainCall--Skipped</code> at warning severity and is consumed with no motion effect. That is
deliberate: the common unresolved callee on those controls is an OEM or measuring cycle whose
definition file ships with the machine and never travels with the program, so a hard error would
fire on ordinary, correct programs.</li>
</ul>
<p>A resolved call is inlined: the callee is segmented through the runner's own segmenter and its
blocks are prepended into the program ahead of the host block, each stamped with a record naming the
call it came from. <code>M98</code> with an <code>L</code> count above one inlines the same file that many times in
series, each repetition its own segmentation pass.</p>
<h3 id="the-nesting-ceiling-and-where-there-is-none">The nesting ceiling, and where there is none</h3>
<p>A self- or mutually-recursive callee re-captures its own call statement inside every inlined body,
so a call path needs a rail of its own — no loop watchdog covers it. <strong>That rail is declared twice,
independently, and is missing on the third path.</strong></p>
<table>
<thead>
<tr>
<th>Preset</th>
<th>Ceiling</th>
<th>Where it is declared</th>
<th>Exceeded</th>
</tr>
</thead>
<tbody>
<tr>
<td>Siemens</td>
<td>32 frames</td>
<td><a class="xref" href="../../../../api/Hi.NcParsers.EvaluationSyntaxs.Siemens.SiemensSubProgramCallSyntax.html#Hi_NcParsers_EvaluationSyntaxs_Siemens_SiemensSubProgramCallSyntax_DefaultMaxCallDepth">DefaultMaxCallDepth</a></td>
<td><code>SiemensCall--DepthLimitExceeded</code>, consumed as a safe skip</td>
</tr>
<tr>
<td>Heidenhain</td>
<td>32 frames</td>
<td><a class="xref" href="../../../../api/Hi.NcParsers.EvaluationSyntaxs.Heidenhain.HeidenhainSubProgramCallSyntax.html#Hi_NcParsers_EvaluationSyntaxs_Heidenhain_HeidenhainSubProgramCallSyntax_DefaultMaxCallDepth">DefaultMaxCallDepth</a></td>
<td><code>HeidenhainCall--DepthLimitExceeded</code>, consumed as a safe skip</td>
</tr>
<tr>
<td>Fanuc, Mazak, Syntec</td>
<td>none</td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
<p>The two constants are separate <code>const int</code> declarations on separate classes that happen to hold the
same number, each surfaced as its own writable <code>MaxCallDepth</code> property and each serialized into the
runner file as its own element. Raising one raises nothing else. Both compare against the depth of
the call stack already stamped on the host block, so the count is frames entered, not files listed.</p>
<p>On the Fanuc-family inliner and on both Custom Macro B calls there is no depth comparison at all.
None of them reads the host block's frame count before inlining, so none carries the recursion rail
the Siemens and Heidenhain syntaxes declare. The depth itself is recorded either way — every inlined
block is stamped with a pushed call-stack frame that the matching return statement pops — so what is
missing is the comparison, not the count.</p>
<p>The Heidenhain call syntax carries a second, unrelated ceiling for its section-repeat form:
<a class="xref" href="../../../../api/Hi.NcParsers.EvaluationSyntaxs.Heidenhain.HeidenhainSubProgramCallSyntax.html#Hi_NcParsers_EvaluationSyntaxs_Heidenhain_HeidenhainSubProgramCallSyntax_MaxRepetitions">MaxRepetitions</a> caps
a <code>REP</code> literal at 65534, since the repeat re-scans the file once per repetition.</p>
<h3 id="what-an-empty-internal-folder-does-not-survive">What an empty Internal folder does not survive</h3>
<p>The config's default internal folder is <code>NC</code> — the layout where the main program sits beside an
<code>NC/</code> directory of subprograms — and that default is applied by the property itself, before the XML
reader runs. The writer omits an element for a null folder, and the reader keeps whatever the
property already holds when the element is absent. Clearing the <strong>Internal folder</strong> box therefore
stores a null, writes no element, and reads back as <code>NC</code> the next time the project is opened.</p>
<p>The <strong>External folder</strong> box has no such default and round-trips cleanly: cleared, it stores null,
writes no element, and reads back null — which is the state the field's own hint describes, falling
back to the internal folder.</p>
<h2 id="editing-and-storage">Editing and Storage</h2>
<p>Both panels open with the branch's two empty layers — the shared <em>&quot;No NC runner — load a project
first.&quot;</em> line first, then the panel's own read reporting <em>&quot;No block-skip config on the active
runner.&quot;</em> or <em>&quot;No subprogram-folder config on the active runner.&quot;</em> The first layer is a live guard
against the snapshot emptying under a panel that is already mounted, which is what closing the
project does. The second is unreachable by navigating: the node's gate and the panel's read probe
the same dependency on the same resolved list, so the leaf exists only where the read is about to
succeed.</p>
<p>Their commit shapes differ, and both are recorded in
<a class="xref" href="../editing-contract.html">Editing Contract</a>: the block-skip boxes commit on the click
and roll back on failure, while the two folder fields commit <strong>as a pair</strong> on blur or Enter of
either one, compared against a saved snapshot of both rather than a per-field captured value — so
editing one folder writes both, and a failure restores both.</p>
<p>Both configs are plain runner-owned entries rather than proxies, so their values serialize with the
runner and follow it. Installing a controller file replaces them outright, and a brand apply assigns
a whole fresh preset, so <strong>an applied brand change returns both to that preset's defaults</strong> — layer 1
alone, and the <code>NC</code> internal folder with no external one. Selecting the brand already in force is not
a route to that reset: the apply stays disabled until the selection differs from the runner's own
brand. Which half of a project each value on this plane lands in is
<a class="xref" href="index.html">Machine and Controller Plane</a>.</p>
<h2 id="layout">Layout</h2>
<ul>
<li><strong>General Setup</strong> Control Tree — the left dock of <code>/general-setup</code>
<ul>
<li><strong>Controller</strong> Node Row
<ul>
<li><strong>Machine / Controller</strong> Group Row
<ul>
<li><strong>Block Skip / Delete</strong> Node Row — <code>equipment/controller/machine/block-skip</code>, grown while
the snapshot reports a block-skip config</li>
<li><strong>Subprogram Folders</strong> Node Row — <code>equipment/controller/machine/subprograms</code>, grown while it
reports a subprogram-folder config</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>Editor Row — <strong>Block Skip / Delete</strong> Panel
<ul>
<li>No-Runner Line — <em>&quot;No NC runner — load a project first.&quot;</em>, the branch's shared empty state</li>
<li>No-Config Line — <em>&quot;No block-skip config on the active runner.&quot;</em></li>
<li>Description Caption — <em>&quot;Enabled layers skip their <code>/n</code>-prefixed blocks (<code>/</code> alone = layer 1).&quot;</em></li>
<li>Layer CheckBox Column — nine dense boxes in one column
<ul>
<li><strong>Layer 1 (bare /)</strong> CheckBox</li>
<li><strong>Layer 2</strong><strong>Layer 9</strong> CheckBoxes</li>
</ul>
</li>
</ul>
</li>
<li>Editor Row — <strong>Subprogram Folders</strong> Panel
<ul>
<li>No-Runner Line — the same shared empty state</li>
<li>No-Config Line — <em>&quot;No subprogram-folder config on the active runner.&quot;</em></li>
<li><strong>Internal folder (M98)</strong> Field — outlined and dense, hinted <em>&ldquo;Relative to the host NC file's
folder; empty = that folder itself&rdquo;</em> — a hint the resolver contradicts, the anchor being the
project root</li>
<li><strong>External folder (M198)</strong> Field — outlined and dense, hinted <em>&ldquo;Fanuc external-storage calls;
empty = fall back to the internal folder&rdquo;</em></li>
</ul>
</li>
</ul>
<p>Every label above is a translated role string rather than a type name, so a locale change rewrites
what is read without touching an id. Both panels mount against one node and take nothing from it:
each serves a single tree id, so neither reads a role off the node's path.</p>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../../../index.html">HiNC App Anatomy</a> for git repository links.</p>
<h3 id="web-application">Web Application</h3>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/controlTree/SoftNcBlockSkipPanel.vue</code> — the block-skip panel: the two
empty layers, the caption with its two code slots, the nine boxes and their layer-one label, and
the whole-set write with its rollback.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcSubprogramPanel.vue</code> — the folder panel: the two
outlined fields, the blur and Enter bindings on both, the saved-snapshot guard that skips a
redundant write, and the paired rollback.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcEmptyState.vue</code> — the shared no-runner line both
panels open with.</li>
<li><code>wwwroot-src/src/components/controlTree/softNcItemTypes.ts</code> — the two node ids, their labels and
label keys, the snapshot flag each is pushed behind, and the item types that bind them to their
panels.</li>
<li><code>wwwroot-src/src/api/softNcRunner.ts</code> — the two readers and their coercions, the layer-array
writer, the paired folder writer, and the snapshot fields the gates read.</li>
<li><code>wwwroot-src/src/api/http.ts</code> — the envelope reader that turns an unsuccessful body into the error
a panel's toast quotes.</li>
<li><code>wwwroot-src/src/composables/useSoftNcRunner.ts</code> — the shared snapshot both first empty layers
read, and its once-per-session install.</li>
<li><code>wwwroot-src/src/i18n/en/softNc.ts</code> — the two node labels, the block-skip caption and layer
labels, the four folder strings, the two empty-state lines and the error contexts.</li>
<li><code>Mech/SoftNcRunnerController.cs</code> — the REST surface: the two presence probes on the snapshot, the
block-skip read that enumerates the nine layers and the write that sets each one, the folder read
that substitutes an empty string for a null and the write that substitutes a null for a blank, the
brand switch whose remark names both of these among the runner-owned values it resets, and the
shared helper that answers a missing dependency with an unsuccessful body rather than an error
status.</li>
</ul>
<h3 id="hiapi-engine">HiAPI Engine</h3>
<ul>
<li><code>HiMech/NcParsers/Dependencys/IBlockSkipConfig.cs</code> — the block-skip contract: the per-layer query
and mutator, and the stated safe default when the dependency is absent.</li>
<li><code>HiMech/NcParsers/Dependencys/Generic/GenericBlockSkipConfig.cs</code> — the only implementer: the
layer-one default, the comma-separated stored form, the range filter repeated in its reader and
its setter, and the blank-equals-absent rule behind the cleared-set round trip.</li>
<li><code>HiMech/NcParsers/ParsingSyntaxs/BlockSkipSyntax.cs</code> — the prefix parser: the layer regular
expression, the always-recorded section, the body move that silences a block, and the message the
skip reports.</li>
<li><code>HiMech/NcParsers/Keywords/BlockSkip.cs</code> — the recorded section: symbol, layer, and the body that
is null exactly when the skip did not take effect.</li>
<li><code>HiMech/NcParsers/Dependencys/Generic/SubProgramFolderConfig.cs</code> — the two folders, the <code>NC</code>
default applied by the property itself, the null fallbacks, and the writer that omits an element
for a null.</li>
<li><code>HiMech/NcParsers/EvaluationSyntaxs/SubProgramCallSyntax.cs</code> — the Fanuc-family inliner: the
internal-versus-external choice, the repetition loop, the file-not-found error, and the absence of
any call-depth comparison.</li>
<li><code>HiMech/NcParsers/EvaluationSyntaxs/MacroFileResolver.cs</code> — the shared resolver: the Fanuc file
name chain, the absolute-versus-relative anchoring, and the directory-exists precondition.</li>
<li><code>HiMech/NcParsers/EvaluationSyntaxs/Fanuc/FanucMacroCallSyntax.cs</code>,
<code>HiMech/NcParsers/EvaluationSyntaxs/Fanuc/FanucModalMacroSyntax.cs</code> — the other two Fanuc readers
of the internal folder: the <code>G65</code> one-shot macro call, and the <code>G66</code> modal that resolves the same
file again on every motion block it fires on.</li>
<li><code>HiMech/NcParsers/EvaluationSyntaxs/Siemens/SiemensSubProgramCallSyntax.cs</code> — the name-call
inliner: its own file-pattern chain, its own depth constant and property, and the safe-skip that
answers an unresolved OEM cycle.</li>
<li><code>HiMech/NcParsers/EvaluationSyntaxs/Heidenhain/HeidenhainSubProgramCallSyntax.cs</code> — the klartext
call inliner: the <code>CALL PGM</code> lookup through the same internal folder, the second depth constant,
and the separate repeat ceiling.</li>
<li><code>HiMech/NcParsers/Keywords/CallStack.cs</code>,
<code>HiMech/NcParsers/EvaluationSyntaxs/SubProgramReturnSyntax.cs</code> — the frame each inliner stamps on
an inlined block, and the <code>M99</code> return that pops it.</li>
<li><code>HiMech/NcParsers/NcDiagnosticProgress.cs</code> — the category and severity behind each diagnostic id
named above.</li>
<li><code>HiMech/NcParsers/Dependencys/SystemWired/ProjectFolderDependency.cs</code> — the base directory a
relative folder is anchored against, and the host-wired provider behind it.</li>
<li><code>HiMech/MachiningProcs/MachiningSession.cs</code> — where that provider is wired to the project root
before a play.</li>
<li><code>HiMech/NcParsers/SoftNcRunner.cs</code> — the five brand presets, and which of them carries each of
these two dependencies.</li>
<li><code>HiMech/NcParsers/Syntaxs/FanucSyntaxUtil.cs</code>,
<code>HiMech/NcParsers/Syntaxs/SiemensSyntaxUtil.cs</code>,
<code>HiMech/NcParsers/Syntaxs/SyntecSyntaxUtil.cs</code>,
<code>HiMech/NcParsers/Syntaxs/MazakSyntaxUtil.cs</code>,
<code>HiMech/NcParsers/Syntaxs/HeidenhainSyntaxUtil.cs</code> — the four lists that carry the block-skip
parser and the one that does not, and which call syntax each preset runs.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="index.html">Machine and Controller Plane</a> — the plane these two leaves sit on, and the storage split that
decides which of its values a brand apply resets and which it leaves alone</li>
<li><a class="xref" href="../brand-matrix.html">Brand Matrix</a> — the two flags that grow these nodes, what each one probes,
and the brand sets they produce</li>
<li><a class="xref" href="../editing-contract.html">Editing Contract</a> — the commit, rollback and empty-state rules these panels
share with the branch, and the paired-field write recorded as one of its exceptions</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,43 @@
<div id="sidetoggle">
<div>
<div class="sidefilter">
<form class="toc-filter">
<span class="glyphicon glyphicon-filter filter-icon"></span>
<span class="glyphicon glyphicon-remove clear-icon" id="toc_filter_clear"></span>
<input type="text" id="toc_filter_input" placeholder="Filter by title" onkeypress="if(event.keyCode==13) {return false;}">
</form>
</div>
<div class="sidetoc">
<div class="toc" id="toc">
<ul class="nav level1">
<li>
<a href="index.html" name="" title="Overview">Overview</a>
</li>
<li>
<a href="brand-switch.html" name="" title="Controller Brand">Controller Brand</a>
</li>
<li>
<a href="per-axis-tables.html" name="" title="Per-Axis Tables">Per-Axis Tables</a>
</li>
<li>
<a href="interface-parameters.html" name="" title="Interface Parameters">Interface Parameters</a>
</li>
<li>
<a href="m-code-declarations.html" name="" title="M-Code Declarations">M-Code Declarations</a>
</li>
<li>
<a href="program-reading.html" name="" title="Program Reading">Program Reading</a>
</li>
<li>
<a href="indexing-positions.html" name="" title="Indexing Positions">Indexing Positions</a>
</li>
<li>
<a href="native-parameters.html" name="" title="Native Parameters">Native Parameters</a>
</li>
</ul>
</div>
</div>
</div>
</div>
@@ -0,0 +1,2 @@
{"items":[{"name":"Overview","href":"index.html","topicHref":"index.html"},{"name":"Controller Brand","href":"brand-switch.html","topicHref":"brand-switch.html"},{"name":"Per-Axis Tables","href":"per-axis-tables.html","topicHref":"per-axis-tables.html"},{"name":"Interface Parameters","href":"interface-parameters.html","topicHref":"interface-parameters.html"},{"name":"M-Code Declarations","href":"m-code-declarations.html","topicHref":"m-code-declarations.html"},{"name":"Program Reading","href":"program-reading.html","topicHref":"program-reading.html"},{"name":"Indexing Positions","href":"indexing-positions.html","topicHref":"indexing-positions.html"},{"name":"Native Parameters","href":"native-parameters.html","topicHref":"native-parameters.html"}]}
@@ -0,0 +1,464 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Datum Tables | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Datum Tables | HiAPI-C# 2025 ">
<link rel="icon" href="../../../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../../../public/docfx.min.css">
<link rel="stylesheet" href="../../../../public/main.css">
<meta name="docfx:navrel" content="../../../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../../../index.html">
<img id="logo" class="svg" src="../../../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-ControllerDatumTables">
<h1 id="datum-tables">Datum Tables</h1>
<p>Datum Tables is the pair of Heidenhain per-case ledgers the Controller branch grows together: the
preset rows a <code>CYCL DEF 247</code> cycle selects, and the datum shift rows a <code>CYCL DEF 7</code> cycle adds on top
of the selected preset. Both live on the General Setup page at <code>/general-setup</code> under the
Control-Tree ids <code>equipment/controller/program-data/datum-presets</code> and
<code>equipment/controller/program-data/datum-shifts</code> — reached as
<code>?tree=equipment/controller/program-data/datum-presets</code> and
<code>?tree=equipment/controller/program-data/datum-shifts</code> — and the tree labels them
<strong>Datum Presets (Q339)</strong> and <strong>Datum Shifts (D)</strong>. One snapshot flag grows the pair, one component
serves both nodes, and one of the two tables is also the object the Work Coordinates leaf edits.</p>
<div class="IMPORTANT">
<h5>Important</h5>
<p>Anatomy makes no claim that any id named here is stable across versions. The migration hop that
keeps an older <code>?tree=</code> value resolving is described in
<a class="xref" href="../../../platform/tree-ids-and-routes.html">Tree Ids and Routes</a>.</p>
</div>
<h2 id="two-nodes-one-component">Two Nodes, One Component</h2>
<p>The branch builder pushes both nodes inside a single test on the snapshot's datum flag, so the two
rows appear and disappear as a pair and never one without the other. That flag probes
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.HeidenhainDatumTable.html">HeidenhainDatumTable</a> over the runner's proxy-resolved dependency
list, and only the Heidenhain preset carries it — which brand grows which leaf is
<a class="xref" href="../brand-matrix.html">Brand Matrix</a>.</p>
<p>Both nodes carry the same item type, so the editor row mounts the same component for either
selection. The component takes its role from the last segment of the node id it is given: an id
ending in <code>datum-shifts</code> makes it the shift editor, and <strong>anything else</strong> makes it the preset editor.
The test is a suffix match with a preset default rather than an equality check over two known ids, so
the preset role is what an unrecognised id falls to.</p>
<p>Four things follow from that role, and nothing else in the panel differs between the two nodes.</p>
<table>
<thead>
<tr>
<th>Decided by the role</th>
<th>Datum Presets (Q339)</th>
<th>Datum Shifts (D)</th>
</tr>
</thead>
<tbody>
<tr>
<td>Which array of the one read is rendered</td>
<td>the preset rows</td>
<td>the shift rows</td>
</tr>
<tr>
<td>The index column's header</td>
<td><code>Q339</code></td>
<td><code>D</code></td>
</tr>
<tr>
<td>The description caption above the table</td>
<td><em>&quot;Datum presets (CYCL DEF 247, Q339 = row). Rows 16 double as G54G59.&quot;</em></td>
<td><em>&quot;Datum shifts (CYCL DEF 7).&quot;</em></td>
</tr>
<tr>
<td>The route segment its writes address</td>
<td><code>preset</code></td>
<td><code>shift</code></td>
</tr>
</tbody>
</table>
<p><strong>One request serves both nodes.</strong> The read returns a presence flag together with <em>both</em> arrays, and
the panel keeps one and discards the other. Selecting the sibling node changes the node id, which
changes the editor row's remount key, so the component remounts and re-fetches the same payload for
the other half. Reading the pair therefore costs two identical requests, and neither node can show a
value the other's request had already returned.</p>
<p>The <code>preset</code> and <code>shift</code> segments are the only two the panel ever sends; the endpoint's rejection of
a third is one of the departures recorded in
<a class="xref" href="../editing-contract.html">Editing Contract</a>.</p>
<h2 id="what-each-table-holds">What Each Table Holds</h2>
<p>Both tables are the same shape — an integer row number against an X / Y / Z translation — and both
are seeded with rows 1 through 20 at zero when the table is constructed. What separates them is which
klartext cycle reads which, and how the value is applied.</p>
<ul>
<li><strong>Datum Presets (Q339)</strong> is the preset store. <code>CYCL DEF 247 DATUM SETTING</code> with <code>Q339=N</code> selects
row <em>N</em>, and its translation becomes the block's active coordinate offset. The same parser claims
the DIN/ISO spelling <code>G247 Q339=+N</code> and stamps the identical cycle record, so both dialects reach
one store.</li>
<li><strong>Datum Shifts (D)</strong> is the shift store. <code>CYCL DEF 7</code> with a <code>#N</code> row index reads row <em>N</em>, and the
translation composes <strong>on top of</strong> the active preset rather than replacing it — the two land in
separate entries of the block's transform chain. <code>CYCL DEF 7</code> written with direct X / Y / Z values
instead of a <code>#</code> index reads no table row at all.</li>
</ul>
<p>The two stores sit on one dependency and are two dictionaries of it, which is why one flag grows two
nodes and one request answers for both.</p>
<p><strong>Neither store is a parameter table.</strong> <a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.HeidenhainDatumTable.html">HeidenhainDatumTable</a> does not
derive from the controller parameter base every brand's machine settings hang off, and the Heidenhain
preset carries it as an entry of its own beside the machine-parameter entry. So no datum row has a
native parameter number, and none of these values appears in
<a class="xref" href="../machine/native-parameters.html">Native Parameters</a> — unlike the Fanuc and Syntec work
coordinates, which are parameter addresses and do.</p>
<h3 id="where-the-table-is-stored">Where the table is stored</h3>
<p>The Heidenhain preset does not carry a datum table. It carries
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.HeidenhainDatumTableProxy.html">HeidenhainDatumTableProxy</a>, a get-or-create placeholder that installs
a bare new table into the project's per-case list when that list holds none of the type, and resolves
to the project's own table when it does. The proxy holds no seed and serializes as an empty element,
so a controller file saved from the branch root carries none of these values, and installing a
Heidenhain runner file over a Heidenhain project leaves the rows already edited exactly where they
are. The plane's storage model in full is <a class="xref" href="index.html">Program Data Plane</a>.</p>
<h2 id="the-columns-and-the-axis-set">The Columns and the Axis Set</h2>
<p>Each leaf renders one dense markup table: the row-number column, three value columns, and an
unlabelled action column.</p>
<ul>
<li>The row-number cell is bold plain text and cannot be edited. Its header is the role's literal —
<code>Q339</code> or <code>D</code>.</li>
<li>The three value headers read <code>X (mm)</code>, <code>Y (mm)</code> and <code>Z (mm)</code>. Those three and the index literal
beside them are hard-coded rather than translated, so they read the same under any locale, while
the description caption, both empty bodies and the action button's tooltip are translated.</li>
<li>The action column has no header text and holds one button per row.</li>
</ul>
<p><strong>The axis set is fixed at three and does not follow the machine.</strong> A datum row is a three-component
translation on both sides of the wire, so the columns are X, Y and Z whatever the machining chain
declares — a fourth or rotary axis has no column here and no stored datum component. That is the
opposite of the branch's <a class="xref" href="../machine/per-axis-tables.html">Per-Axis Tables</a>, whose rows are the
chain's own axis names. The <code>mm</code> in the header is a label: nothing on the path from the stored
translation to the cell converts anything.</p>
<p>Rows are whatever the read returned, in row-number order. A table built by the proxy opens with
twenty rows of zeros in each node; a table deserialized from a project file carries the rows that
file holds.</p>
<h2 id="editing-a-row">Editing a Row</h2>
<p>Every value cell is the shared numeric field, so it commits on blur or on Enter and never per
keystroke; its parsing, its bounds behaviour and the second commit that follows Enter are
<a class="xref" href="../../../widget/numeric-input.html">Numeric Input</a>. No cell here passes a minimum or a maximum, so a negative
offset is accepted and stored.</p>
<p><strong>The write is the whole row.</strong> A commit assigns the new number into the local row and then sends
that row's three components as they stand to the row's number; a failure restores the one component
the handler captured and raises the branch's standard toast, though the request carried all three.
That shape, the toast it ends in and what a failed write leaves behind are
<a class="xref" href="../editing-contract.html">Editing Contract</a>.</p>
<p>A cleared cell parses to null, and <code>Infinity</code> or <code>NaN</code> parse to a non-finite number; the handler
returns before the request in every one of those cases. Nothing is written and nothing is restored,
so the cell is left blank — or showing the literal <code>Infinity</code> — while the stored number stands
unchanged, until the panel is remounted by selecting another node and coming back.</p>
<p>The action button is labelled with the single character <code>0</code> and carries the tooltip <em>&ldquo;Reset to
zero&rdquo;</em>. It writes zeros into all three components of that row through a route of its own rather than
through the row setter, and it is not a delete: the row stays, at zero. It is also the one write on
these two leaves that is not optimistic — the request resolves first, and only then are the three
local cells set to zero. No confirmation guards it.</p>
<p>Neither leaf has an add control, a delete control, a draft or a save button, and the component
declares no events at all, so no edit made here rebuilds the branch or marks anything dirty.</p>
<h2 id="the-preset-table-is-also-the-work-coordinate-table">The Preset Table Is Also the Work-Coordinate Table</h2>
<p>This is the pairing with <a class="xref" href="work-coordinates.html">Work Coordinates</a>, and it holds for
exactly one of the two tables.</p>
<p><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.HeidenhainDatumTable.html">HeidenhainDatumTable</a> implements
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.IIsoCoordinateConfig.html">IIsoCoordinateConfig</a>, and the work-coordinate endpoint reads and
writes through the <em>first</em> implementer of that interface in the runner's proxy-resolved list. On the
Heidenhain preset the datum proxy is the second entry of the dependency list, immediately after the
brand marker, and no other entry that preset carries implements the interface — the Heidenhain
machine-parameter table does not. So on Heidenhain the object behind Work Coordinates is this datum
table, and the snapshot's coordinate-kind field reports the Heidenhain arm for that same reason.</p>
<p>The alias is narrow, and one-directional in coverage.</p>
<ul>
<li><strong>Datum Presets (Q339) aliases.</strong> The ISO face maps <code>G54</code> through <code>G59</code> onto preset rows 1 through
6, in order, on both the read and the write, and enumerates no id outside that series — one for
each of those six rows the table holds, which is all six unless a stored project table omits one of
them. Editing the <code>Q339</code> row 3 cells changes what Work Coordinates shows for <code>G56</code>, and an edit
made on <code>G56</code> changes row 3 — one instance, one cell, two faces. The row-zeroing button reaches the
same cells.</li>
<li><strong>Datum Shifts (D) does not alias.</strong> Nothing on the ISO face touches the shift store: the id
enumeration reads the preset dictionary only, and both the offset getter and the offset setter
resolve to preset rows. The shift table has exactly one editing surface, the leaf on this page.</li>
<li><strong>Preset rows 7 through 20 have no work-coordinate face either.</strong> The ISO face reserves six rows
for the <code>G54</code> series and enumerates no id past <code>G59</code>, so fourteen preset rows and all twenty shift
rows are reachable from this page and from nowhere else on the branch.</li>
</ul>
<p>The two faces are never on screen together — the editor row mounts one panel at a time — and each
panel reads once on mount, so moving the selection between the two nodes is itself the refresh. What
a mounted panel therefore does not see is
<a class="xref" href="../editing-contract.html">Editing Contract</a>.</p>
<h3 id="what-a-brand-switch-does-to-the-pair">What a brand switch does to the pair</h3>
<p>The brand switch's carry option reads the outgoing runner's coordinate provider and writes the values
into the incoming one, for the ids the incoming provider enumerates. On this table that means at most
six values, landing in preset rows 1 through 6; a switch away from Heidenhain reads those same six
rows out. <strong>No shift row is ever carried, and neither are preset rows 7 through 20.</strong></p>
<p>A switch away from Heidenhain then sweeps the datum table itself, because no other brand preset
proxies it — both stores go, and switching back materializes a fresh table of zeros rather than
restoring them. The staged select, its confirmation and the rest of that operation are
<a class="xref" href="../machine/brand-switch.html">Brand Switch</a>.</p>
<h2 id="what-the-rows-feed">What the Rows Feed</h2>
<p>At run time both stores are read and neither is written. No syntax in the parse pipeline writes
either dictionary — every writer is one of the four surfaces named below — so playing a program
cannot change a datum row.</p>
<p><strong>The preset path.</strong> A <code>CYCL DEF 247</code> block resolves its <code>Q339</code> number — a literal, or the number a
Q-expression evaluated to — looks the row up, and writes a synthetic coordinate id naming that row
alongside the translation. The shared ISO coordinate syntax then keeps the offset alive on following
blocks by re-resolving that synthetic id against the same table, which is how a mid-program edit to a
row takes effect on the blocks after the declaration. A <code>CYCL DEF 247</code> whose <code>Q339</code> is not a number
resolves nothing, leaves the active datum unchanged, and reports the validation warning <em>&quot;CYCL DEF
247 without a literal Q339 preset number; the active datum is kept unchanged.&quot;</em></p>
<p><strong>A successful preset selection clears the active shift.</strong> That is the one path in the cycle handler
that does not carry the previous block's shift forward.</p>
<p><strong>The shift path.</strong> A <code>CYCL DEF 7</code> block carrying a <code>#</code> index reads that shift row and composes it as
a second, separate transform entry, so preset and shift add rather than replace. On blocks with no
cycle the shift is carried modally, and a numbered shift re-resolves from the table on every block,
exactly as the preset does. A <code>CYCL DEF 7</code> written with direct values carries them forward verbatim
instead and needs no datum table at all; cancelling is a zero shift. A <code>CYCL DEF 7</code> carrying neither
form reports <em>&quot;CYCL DEF 7 carries neither a #-table index nor direct X/Y/Z values; the active datum
shift is kept unchanged.&quot;</em></p>
<p><strong>A <code>G54</code> with axis words is not a table read.</strong> On the Heidenhain syntax list the datum-shift parser
claims a <code>G54</code> that is followed by at least one axis value and routes it into the direct-shift flow,
which reads no row; a bare <code>G54</code> is left alone and reaches the ISO path, where it resolves preset row</p>
<ol>
<li>So <code>G54</code> means a table lookup or a literal shift depending on what follows it in the block.</li>
</ol>
<p>A row number that resolves to nothing yields a zero translation rather than an error, on both paths.
And nothing is active at the first block: the Heidenhain preset's initializer seeds no coordinate
section at all, unlike the Fanuc family's <code>G54</code> and the Siemens cancel frame, so a program that
issues no datum cycle and no <code>G54</code> machines with no offset applied.</p>
<h2 id="where-non-zero-rows-come-from">Where Non-Zero Rows Come From</h2>
<p>Four surfaces put a value into these tables, and only the first is on this page.</p>
<ul>
<li><strong>The two leaves themselves</strong>, per cell or per row.</li>
<li><strong>Work Coordinates</strong>, for preset rows 1 through 6 only, through the alias above.</li>
<li><strong>A brand switch onto Heidenhain with its carry option on</strong>, through that same ISO face and into
the table the incoming preset resolves: at most six offsets, into preset rows 1 through 6 —
<a class="xref" href="../machine/brand-switch.html">Brand Switch</a>.</li>
<li><strong>A project-load migration.</strong> Opening a project file that carries the superseded controller
environment element and no per-case list populates the materialized per-case tables from that
element, and the datum half runs only when the legacy brand element reads Heidenhain. It copies
preset entries and shift entries by row number, overwriting a row it names and leaving every other
row as seeded. The legacy work-coordinate table is replayed first, through the same ISO face that
aliases onto preset rows 1 through 6, so where the two overlap the explicit datum copy is what
stands.</li>
</ul>
<p>The superseded <code>/controller</code> route also carries datum preset and datum shift surfaces, over the
separate environment model rather than over the runner — an edit made there does not reach the table
on this page. That route's own anatomy is <a class="xref" href="../../../legacy-controller.html">Legacy Controller Page</a>.</p>
<h2 id="layout">Layout</h2>
<ul>
<li><strong>General Setup</strong> Control Tree — the left dock of <code>/general-setup</code>
<ul>
<li><strong>Controller</strong> Node Row
<ul>
<li><strong>Program Data</strong> Group Row — <code>equipment/controller/program-data</code>
<ul>
<li><strong>Datum Presets (Q339)</strong> Node Row — <code>equipment/controller/program-data/datum-presets</code></li>
<li><strong>Datum Shifts (D)</strong> Node Row — <code>equipment/controller/program-data/datum-shifts</code></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>Editor Row — the panel of whichever node is selected, one at a time
<ul>
<li><strong>Datum Presets (Q339)</strong> / <strong>Datum Shifts (D)</strong> Panel — one component, its role taken from the
selected id's last segment
<ul>
<li>No-Runner Body — <em>&quot;No NC runner — load a project first.&quot;</em>; the shared empty state, shown
instead of everything below while the snapshot reports no runner</li>
<li>Absent-Table Body — <em>&quot;No Heidenhain datum table on the active runner.&quot;</em>; shown instead of
everything below while the panel's own read reports no table</li>
<li>Description Caption — grey, one line: <em>&quot;Datum presets (CYCL DEF 247, Q339 = row). Rows 16
double as G54G59.&quot;</em> or <em>&quot;Datum shifts (CYCL DEF 7).&quot;</em></li>
<li>Datum Table — dense, flat, bordered; no sort, no pagination, no column menu
<ul>
<li>Header Row — <code>Q339</code> or <code>D</code>, then <code>X (mm)</code>, <code>Y (mm)</code> and <code>Z (mm)</code>, then an unlabelled action
column</li>
<li>Datum Row, one per row the read returned, in row-number order
<ul>
<li>Row Number Label — bold plain text, never editable</li>
<li>Value Numeric Field, one per axis column — no minimum, no maximum, no unit suffix</li>
<li><strong>0</strong> Button — flat, dense, centred, tooltip <em>&ldquo;Reset to zero&rdquo;</em></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>Toast — negative, three seconds, the panel's context followed by the server's own message</li>
</ul>
</li>
</ul>
<p>The panel carries no heading, no toolbar, no add row, no delete button, no toggle, no save button and
no dialog.</p>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../../../index.html">HiNC App Anatomy</a> for git repository links.</p>
<h3 id="web-application">Web Application</h3>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/controlTree/SoftNcDatumTablePanel.vue</code> — the one component behind both
leaves: the role taken from the node id's suffix with its preset default, the four things that
role decides, the literal column headers, the whole-row cell commit with its non-finite guard, and
the non-optimistic row reset.</li>
<li><code>wwwroot-src/src/components/controlTree/softNcItemTypes.ts</code> — the branch builder: the two node ids
and label keys pushed together inside one flag test, and the single item type both carry.</li>
<li><code>wwwroot-src/src/components/controlTree/PrimarySlavePanel.vue</code> — the editor row: the remount key
that makes a move between the two nodes a re-fetch, and the events this panel declares none of.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcEmptyState.vue</code> — the shared no-runner line the panel
opens with.</li>
<li><code>wwwroot-src/src/components/widgets/NumericInput.vue</code> — the value cell: commit on blur or Enter,
the empty-text-to-null parse, and the infinity and NaN literals the panel's handler then rejects.</li>
<li><code>wwwroot-src/src/api/softNcRunner.ts</code> — the read that returns both tables and its row shape, the
two role values, the per-row setter and the reset call, and the snapshot's datum flag.</li>
<li><code>wwwroot-src/src/composables/useSoftNcRunner.ts</code> — the shared snapshot the first empty layer reads.</li>
<li><code>wwwroot-src/src/api/http.ts</code> — the envelope helper that turns a success-flagged failure body into
a thrown error.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcWorkCoordinatesPanel.vue</code> — the second face on the
preset rows: the same six cells addressed by coordinate id, and the caption that names the mapping.</li>
<li><code>wwwroot-src/src/i18n/en/softNc.ts</code> — the two node labels, the two descriptions, the absent-table
body and the three error contexts.</li>
<li><code>wwwroot-src/src/i18n/en/common.ts</code> — the reset button's tooltip.</li>
<li><code>Mech/SoftNcRunnerController.cs</code> — the REST surface: the datum flag's probe in the snapshot
builder, the read that returns both dictionaries ordered by row number, the per-row writer and the
zeroing route behind one shared table-segment guard, the work-coordinate routes that reach the same
object through the ISO interface, and the brand switch's capture-swap-carry-sweep.</li>
</ul>
<h3 id="hiapi-engine">HiAPI Engine</h3>
<ul>
<li><code>HiMech/NcParsers/Dependencys/Heidenhain/HeidenhainDatumTable.cs</code> — the two dictionaries, their
twenty seeded rows, the accessors both cycles read through, and the ISO face: the <code>G54</code><code>G59</code> map
onto preset rows 16, the synthetic preset ids it also resolves, and the id enumeration that stops
at six.</li>
<li><code>HiMech/NcParsers/Dependencys/Heidenhain/HeidenhainDatumTableProxy.cs</code> — the seedless get-or-create
placeholder the preset carries, and the bare table it installs into a project holding none.</li>
<li><code>HiMech/NcParsers/Dependencys/IIsoCoordinateConfig.cs</code> — the offset-provider contract whose first
implementer the work-coordinate face resolves.</li>
<li><code>HiMech/NcParsers/Dependencys/Heidenhain/HeidenhainParameterTable.cs</code> — the other Heidenhain
per-case table: it derives from the controller parameter base, which declares no coordinate
contract, which is why the datum table is the preset's only implementer of one.</li>
<li><code>HiMech/NcParsers/SoftNcRunner.cs</code> — the Heidenhain preset's dependency list and the position of the
datum proxy in it, the proxy resolution every read goes through, and the legacy per-case populate
that fills both dictionaries by row number after replaying the legacy coordinate table.</li>
<li><code>HiMech/NcParsers/ParsingSyntaxs/Heidenhain/CyclDefSyntaxs/HeidenhainDatumSettingSyntax.cs</code> — the
<code>CYCL DEF 247</code> parser and the DIN/ISO <code>G247</code> spelling it claims for the same cycle record.</li>
<li><code>HiMech/NcParsers/ParsingSyntaxs/Heidenhain/CyclDefSyntaxs/HeidenhainDatumShiftSyntax.cs</code> — the
<code>CYCL DEF 7</code> parser: the <code>#</code> row index, the direct-value form, and the axis-word test that decides
whether a <code>G54</code> becomes a direct shift or stays on the ISO path.</li>
<li><code>HiMech/NcParsers/LogicSyntaxs/Heidenhain/HeidenhainCoordinateOffsetSyntax.cs</code> — the resolver: the
preset lookup and its synthetic id, the additive shift in its own transform entry, the modal carry
that re-resolves a numbered shift from the table, the shift reset a successful preset selection
performs, and the two validation warnings.</li>
<li><code>HiMech/NcParsers/LogicSyntaxs/IsoCoordinateOffsetSyntax.cs</code> — the <code>G54</code>-series path and the modal
lookback that re-queries the table for the carried id.</li>
<li><code>HiMech/NcParsers/LogicSyntaxs/CoordinateOffsetUtil.cs</code> — the offset resolution and the translation
composed onto the block's transform chain.</li>
<li><code>HiMech/NcParsers/Syntaxs/HeidenhainSyntaxUtil.cs</code> — the Heidenhain syntax list carrying both the
ISO coordinate syntax and the datum-cycle syntax.</li>
<li><code>HiMech/NcParsers/Initializers/StaticInitializer.cs</code> — the Heidenhain initializer that seeds no
coordinate section, against the Fanuc and Siemens ones that do.</li>
<li><code>HiGeom/Geom/Vec3d.cs</code> — the three-component translation a datum row stores, and the zero it
defaults to.</li>
<li><code>HiNc/MachiningProcs/MachiningProject.cs</code> — the load path that wires the proxies and then runs the
legacy per-case populate on a project carrying no per-case list.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="index.html">Program Data Plane</a> — the plane these two leaves sit on, where their table is
stored, and the per-case tables beside them a brand switch keeps or sweeps</li>
<li><a class="xref" href="work-coordinates.html">Work Coordinates</a> — the second face on the preset rows: the same six cells
addressed as <code>G54</code><code>G59</code>, on the one brand where two nodes edit one object</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,508 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Frames | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Frames | HiAPI-C# 2025 ">
<link rel="icon" href="../../../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../../../public/docfx.min.css">
<link rel="stylesheet" href="../../../../public/main.css">
<meta name="docfx:navrel" content="../../../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../../../index.html">
<img id="logo" class="svg" src="../../../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-ControllerFrames">
<h1 id="frames">Frames</h1>
<p>Frames is the Controller branch's Sinumerik settable-frame ledger: one row per <code>$P_UIFR</code> entry,
each carrying the X / Y / Z translation HiNC stores for that frame. It lives on the General Setup
page at <code>/general-setup</code> under the Control-Tree id <code>equipment/controller/program-data/frames</code>,
reached as <code>?tree=equipment/controller/program-data/frames</code>, and it is grown only while the active
runner resolves a Siemens frame table — among the shipped brand presets, Siemens alone. That same
table is the offset provider behind the ungated <strong>Work Coordinates (G54…)</strong> leaf on this brand, so
the two nodes edit one object.</p>
<div class="IMPORTANT">
<h5>Important</h5>
<p>Anatomy makes no claim that any id named here is stable across versions. The migration hop that
keeps an older <code>?tree=</code> value resolving is described in
<a class="xref" href="../../../platform/tree-ids-and-routes.html">Tree Ids and Routes</a>.</p>
</div>
<h2 id="what-a-frame-is-here">What a Frame Is Here</h2>
<p>On a Sinumerik, <code>$P_UIFR</code> is an array of frames and each entry carries a translation, a rotation, a
scale and a mirror. HiNC consumes the translation and nothing else, so
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Siemens.SiemensFrameTable.html">SiemensFrameTable</a> stores one <a class="xref" href="../../../../api/Hi.Geom.Vec3d.html">Vec3d</a> per frame
id in <a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Siemens.SiemensFrameTable.html#Hi_NcParsers_Dependencys_Siemens_SiemensFrameTable_Frames">Frames</a>, with a second,
axis-letter-keyed dictionary beside it for translation components on axes other than X, Y and Z.</p>
<p>The table is a dependency in its own right rather than a section of the machine data, because
<code>$P_UIFR</code> is not machine data on the control either:
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Siemens.SiemensMachineDataTable.html">SiemensMachineDataTable</a> holds the <code>MD</code>-numbered parameters
— axis type, reference position, stroke limits — and carries no frame. That split is also why the
frame table is the brand's ISO offset provider while the machine-data table is not.</p>
<p>The array index is not the row key. Rows are keyed by the G-code that selects the frame, and the
bridge that serves <code>$P_UIFR</code> accesses maps one onto the other:</p>
<table>
<thead>
<tr>
<th><code>$P_UIFR</code> index</th>
<th>Frame id</th>
<th>Row on this leaf</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>0</code></td>
<td><code>G500</code></td>
<td>none — the cancel frame is never stored</td>
</tr>
<tr>
<td><code>1</code><code>4</code></td>
<td><code>G54</code><code>G57</code></td>
<td>the four base rows</td>
</tr>
<tr>
<td><code>5</code><code>99</code></td>
<td><code>G505</code><code>G599</code></td>
<td>the extended tail</td>
</tr>
</tbody>
</table>
<p>An index outside <code>0</code><code>99</code> maps to no id at all, and the bridge drops the access rather than
inventing a frame for it.</p>
<h3 id="settable-and-computed">Settable, and computed</h3>
<p>Four parts of the Sinumerik frame chain show up in this branch's behaviour, and only one of them is
a row here.</p>
<ul>
<li><strong>The settable frames</strong> are the table. Ninety-nine ids are allocated by the constructor —
<code>G54</code><code>G57</code> plus every id in
<a class="xref" href="../../../../api/Hi.NcParsers.Keywords.Siemens.SiemensKeywords.html#Hi_NcParsers_Keywords_Siemens_SiemensKeywords_ExtendedCoordinateSeries">ExtendedCoordinateSeries</a>, which is
<code>G505</code> through <code>G599</code> — each seeded to zero, matching a control where every <code>$P_UIFR[n]</code> is
allocated with an initial value of zero. Every one is editable on this leaf.</li>
<li><strong><code>G500</code></strong> is computed, not stored. The accessor answers a zero offset for it and drops a write,
and the constructor deliberately keeps it out of the dictionary, so it is a row on neither this
leaf nor Work Coordinates. The description above the table says as much: <em>&quot;Settable frames
($P_UIFR, translation only); G500 cancels and stores nothing.&quot;</em></li>
<li><strong>The programmable frame</strong><code>TRANS</code> / <code>ATRANS</code> / <code>ROT</code> / <code>AROT</code> and their solid-angle forms,
together with the <code>CYCLE800</code> tilt that shares its slot — is computed per block from the program
and has no table, no node and no stored value anywhere in this branch.</li>
<li><strong>A frame's rotation and fine-offset components</strong> reach neither this table nor the simulation. Only
the <code>TR</code> component of a <code>$P_UIFR</code> access is bridged into the frame entry; a write to any other
component is recorded on the block and reported as recognised-but-not-simulated by the Siemens
system-variable catch-all.</li>
</ul>
<h2 id="how-the-chain-composes">How the Chain Composes</h2>
<p>A frame word in a program selects one id. The Siemens coordinate syntax resolves that id's offset
through <a class="xref" href="../../../../api/Hi.NcParsers.SoftNcRunner.html#Hi_NcParsers_SoftNcRunner_GetEffectiveNcDependencyList_">GetEffectiveNcDependencyList</a><small>(API)</small>, taking
the first provider that answers, and writes the result twice: as the block's coordinate-offset
section, and as one named entry in the block's program-to-machine transform chain. Entries in that
chain are multiplied in the order they were written, so slot order is the whole of the composition
rule.</p>
<p>Two placements in the Siemens syntax list decide what a settable frame ends up doing.</p>
<ul>
<li><strong>The programmable-frame entry is written first, deliberately.</strong> The tilt and programmable-frame
syntaxes run ahead of the coordinate-offset syntaxes so their shared entry lands ahead of the
settable frame's. That is what keeps a <code>ROT</code> turning the program coordinates <em>inside</em> the frame
rather than turning the frame's own offset with them. Tool-height compensation is written between
the two, and the kinematic pivot transform is written last so every frame-space operation is
accumulated before the final inverse kinematics.</li>
<li><strong>Both coordinate syntaxes are registered, and they share one entry.</strong> The Siemens preset carries
the ISO coordinate syntax and the Siemens one side by side; each writes the chain entry under the
same name, and a repeated name is replaced in place rather than appended. So the <code>G54</code><code>G59</code>
vocabulary the ISO syntax recognises and the <code>G500</code> and <code>G505</code><code>G599</code> vocabulary the Siemens one
adds resolve into a single slot. The Siemens syntax leaves the coordinate flag in place instead of
consuming it, and falls back to the block's existing coordinate section and then to the previous
block's, which is what carries a frame selection modally.</li>
</ul>
<p>Three codes bypass the composed transform for one block rather than changing it: <code>G53</code>, <code>G153</code> and
<code>SUPA</code> all reduce to the same one-shot machine-coordinate move, and the values on this leaf take no
part in such a block.</p>
<p>The word vocabulary is also wider than the table. The ISO syntax's own series adds <code>G58</code>, <code>G59</code> and
<code>G59.1</code><code>G59.9</code>, none of which the frame table allocates; such a word resolves to no offset from any
provider and composes a zero translation.</p>
<h2 id="the-rows-and-the-columns">The Rows and the Columns</h2>
<p>The panel is a four-column table and nothing else — no toolbar, no add footer, no action column, no
dialog. The <strong>Frame</strong> header is translated; <code>X (mm)</code>, <code>Y (mm)</code> and <code>Z (mm)</code> are literals in the
template and stay English under any locale. The id cell is bold plain text and cannot be edited.</p>
<p><strong>The row set is fixed.</strong> The branch exposes exactly two frame routes, a whole-table read and a
per-id write; no third route adds, deletes or resets a row. The write itself is an unguarded
assignment by key rather than an update of an existing row, so an id the table has never held would
be seated as a new one — but no panel has a way to send such an id. A row exists because the table
allocated it, and the ninety-nine allocated ids are the same ninety-nine whatever a project has done
to their values.</p>
<p><strong>Nothing in the path sorts.</strong> The reader projects the dictionary as it enumerates it and applies no
ordering, unlike the datum, retained-variable, R-parameter and tool-offset readers beside it, and the
panel adds none of its own. What that yields depends on how the table was built. A table the proxy
has just created lists <code>G54</code><code>G57</code> first and then the extended tail, in construction order. A table
read back from a project file lists the ids in the order the file names them — and the file is
written sorted by id as text, not as a number. Plain string order puts <code>G505</code> ahead of <code>G54</code>, because
the third character decides that pair, and <code>G54</code> ahead of <code>G540</code>, because the shorter of two strings
that share a prefix comes first. So a saved and reopened table does not push the four base frames to
either end: it interleaves them into the extended tail, as <code>G505</code><code>G539</code>, <code>G54</code>, <code>G540</code><code>G549</code>,
<code>G55</code>, <code>G550</code><code>G559</code>, <code>G56</code>, <code>G560</code><code>G569</code>, <code>G57</code>, then <code>G570</code><code>G599</code>. Work Coordinates enumerates
the same dictionary, so both leaves reorder together.</p>
<h3 id="show-all">Show all</h3>
<p>A <strong>Show all</strong> toggle sits at the right of the header strip. With it off, a row is listed only when
its id matches <code>G54</code> through <code>G57</code>, or when at least one of its three values is non-zero; with it on,
every allocated id is listed. The test is the panel's own regular expression, evaluated in the
browser over the rows the read returned — nothing about it reaches the server, and the toggle is a
plain local flag that returns to off whenever the panel is remounted.</p>
<p>Two details of that rule are worth reading against the sibling leaf. The toggle here is
unconditional, so it renders even on a table with no extended rows at all. And the always-visible
test here is narrower than the work-coordinate one, which admits <code>G54</code> through <code>G59</code> and any of them
followed by a dot and one further digit. On the seeded table the two tests keep the same four rows
visible, because the frame table allocates none of the ids where they differ.</p>
<h2 id="two-nodes-one-table">Two Nodes, One Table</h2>
<div class="WARNING">
<h5>Warning</h5>
<p>On Siemens this leaf and <strong>Work Coordinates (G54…)</strong> are two faces of one instance, not two tables
kept in step. The work-coordinate leaf resolves the first
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.IIsoCoordinateConfig.html">IIsoCoordinateConfig</a> on the active runner; on this brand that is
the frame table, because it is the only dependency the Siemens preset carries that implements that
interface — the machine-data table included. A <code>G54</code> edit made on either node is the same
assignment into the same cell.</p>
</div>
<p>This is the reciprocal half of the warning <a class="xref" href="work-coordinates.html">Work Coordinates</a>
carries. What the two leaves share, exactly:</p>
<ul>
<li><strong>The row set.</strong> The work-coordinate reader enumerates the provider's own ids, which for this
provider is the frame dictionary's key set. The two leaves therefore list the same ids in the same
order, and gain or lose none independently.</li>
<li><strong>The X, Y and Z cells.</strong> Both leaves write the whole triad for one id, and both land in the same
dictionary entry. Neither mutates the vector already sitting there: each write installs a fresh
<a class="xref" href="../../../../api/Hi.Geom.Vec3d.html">Vec3d</a> over it, so a reference taken before the write still reads the old values. A
value typed on one leaf is what the other shows at its next mount.</li>
<li><strong>The absence of <code>G500</code>.</strong> Neither leaf can show it, for the same reason: it is not in the
dictionary the ids are enumerated from.</li>
</ul>
<p>What is not shared, and what a reader must not infer from the shared face:</p>
<ul>
<li><strong>The per-axis translation components.</strong> A settable frame's translation on an axis letter other
than X, Y or Z lives in the table's second dictionary, written and read only by the <code>$P_UIFR</code>
bridge. Neither leaf returns it, and neither leaf disturbs it: both write a fresh
<a class="xref" href="../../../../api/Hi.Geom.Vec3d.html">Vec3d</a> into the frame entry and leave the axis dictionary untouched, so a C-axis
frame component set by a program survives every edit made from either panel.</li>
<li><strong>The row actions.</strong> The <strong>P0</strong> and <strong>M0</strong> buttons and the canvas marker belong to Work
Coordinates. This leaf has no action column at all — but P0 and M0 write through the same provider,
so pressing one there changes what this leaf shows.</li>
<li><strong>The presence gate.</strong> This leaf gates on the concrete frame table; the other gates on the ISO
interface. On Siemens the two answers are one object and the leaves appear together; the empty
lines they would show differ all the same — <em>&quot;No Siemens frame table on the active runner.&quot;</em> here,
<em>&quot;No work-coordinate table on the active runner.&quot;</em> there.</li>
<li><strong>The write path.</strong> The work-coordinate route writes through the provider's coordinate accessor,
which drops a <code>G500</code> write; the frame route assigns into the dictionary by key and would store one.
Neither is reachable from a panel, because both panels only ever send an id their own read
returned.</li>
<li><strong>The caption.</strong> Work Coordinates picks its caption from the snapshot's coordinate-kind field and
on this brand shows <em>&quot;Stored as Siemens settable frames ($P_UIFR; G500 cancels and is always
zero).&quot;</em> — which is that leaf naming this one. This leaf's caption is fixed.</li>
</ul>
<p>Nothing reconciles the two views while both are in scope, and nothing needs to: the editor row mounts
one panel at a time and each fetches once on mount, so moving the selection between the two nodes is
itself the refresh. What a panel that stays mounted therefore never sees is described in
<a class="xref" href="../editing-contract.html">Editing Contract</a>.</p>
<h2 id="editing-a-frame">Editing a Frame</h2>
<p>Every value cell is the shared numeric field, so it commits on blur or on Enter and never per
keystroke; its parsing, its bounds behaviour and the second commit that follows Enter are
<a class="xref" href="../../../widget/numeric-input.html">Numeric Input</a>. No cell here passes a minimum or a maximum, and no cell
carries a unit suffix — the millimetre is stated in the column header instead.</p>
<p><strong>The write is the whole row.</strong> A commit assigns the new number into the local row and sends that
row's <code>x</code>, <code>y</code> and <code>z</code> as they now stand under the row's id. The optimistic shape, the single-cell
rollback and the toast a failure raises are the branch's own and are described in the
<a class="xref" href="../editing-contract.html">Editing Contract</a>; what is this panel's is the guard in
front of them. The numeric field can emit an empty value as null and the literals <code>Infinity</code>,
<code>-Infinity</code> and <code>NaN</code> as non-finite numbers, and the handler returns before the request on all four.
So a cleared cell stores nothing and clears nothing: the box shows blank while the stored number
stands, and the number returns when the panel is remounted by selecting another node and coming back.</p>
<p>No surface here renames a frame: the id is the write's route key and no route accepts a replacement
for it. The panel reports no structural change either, so no edit on this leaf rebuilds the branch.</p>
<h2 id="what-survives-and-what-a-brand-change-destroys">What Survives, and What a Brand Change Destroys</h2>
<p>The table is per-case: <a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Siemens.SiemensFrameTableProxy.html">SiemensFrameTableProxy</a> stands in the
runner's pipeline list and the real table lives on the project's
<a class="xref" href="../../../../api/Hi.NcParsers.NcRunnerSuit.html#Hi_NcParsers_NcRunnerSuit_PerCaseNcDependencyList">PerCaseNcDependencyList</a>, serialized inside the project file. That
proxy carries <strong>no seed</strong> — it installs a freshly constructed table when the project holds none and
takes the project's own table thereafter — so the runner file records only the placeholder, and a
<code>.Controller</code> written from this project carries no frame values at all. The ownership rules the
proxy pattern follows across the branch are <a class="xref" href="index.html">Program Data</a>.</p>
<p>Two consequences follow for the values on this leaf.</p>
<ul>
<li><strong>A same-brand runner install keeps them.</strong> The incoming preset's proxy re-binds to the table the
project already holds rather than replacing it, and the sweep that follows keeps every per-case
table the new runner resolves through a proxy.</li>
<li><strong>A switch to any other brand destroys them.</strong> No other preset proxies a frame table, so the sweep
removes it, and switching back installs a fresh all-zero table rather than the one that was there.
The brand switch's optional carry salvages a corner of it: the offsets are read from the outgoing
provider before the swap and written into the incoming one afterwards, but only for ids the
incoming provider already enumerates. No other shipped provider allocates a <code>G5xx</code> id — the
Fanuc-family and Syntec tables enumerate <code>G54</code><code>G59</code> and <code>G54.1P1</code><code>G54.1P48</code>, the Heidenhain datum
table <code>G54</code><code>G59</code> — so the carry keeps <code>G54</code> through <code>G57</code> and the whole extended tail is lost.
What a switch keeps, resets and destroys elsewhere is <a class="xref" href="../machine/brand-switch.html">Brand Switch</a>;
which leaf exists on which brand is <a class="xref" href="../brand-matrix.html">Brand Matrix</a>.</li>
</ul>
<h2 id="what-a-run-writes-back">What a Run Writes Back</h2>
<p>A played program does not only read this table; it writes into it, through a bridge with two
halves.</p>
<ul>
<li><strong><code>$P_UIFR[n,axis,TR]=…</code> writes into it.</strong> A literal numeric assignment is routed into the table:
an X, Y or Z component replaces that component of the frame entry, and any other axis letter lands
in the per-axis dictionary. A non-literal right-hand side is left for the expression evaluator
earlier in the same block, and a write to index <code>0</code> is consumed and ignored, mirroring the cancel
frame's own rule. With no frame table on the runner the bridge is a no-op and the assignment stays
visible as unconsumed residue.</li>
<li><strong><code>$P_UIFR</code> reads come back out of it.</strong> The same mapping serves the read side, answering zero for
an allocated frame's unset axis component and for every component of <code>G500</code>, and null — a
fall-through to the next lookup in the chain — for an id the table does not hold.</li>
</ul>
<p>The table is deliberately <strong>not</strong> session-resettable. Settable frames are setting data on a real
control: a <code>$P_UIFR</code> write survives reset and power-off, so a replayed session must see what the
previous run left. The runner clears every session-resettable dependency on the fresh-session edge
and this table is not among them, which means a value a run wrote is what this panel shows
afterwards, and what the project file keeps.</p>
<p>Which frame a program starts on is not stored in the table either. The Siemens preset's static
initializer seeds the first block's coordinate id as <code>G500</code>, so an untagged Siemens program begins
with no frame active and a zero offset until a frame word appears — unlike the Fanuc-family and
Syntec presets, which begin on <code>G54</code>.</p>
<h2 id="layout">Layout</h2>
<ul>
<li><strong>General Setup</strong> Control Tree — the left dock of <code>/general-setup</code>
<ul>
<li><strong>Controller</strong> Node Row
<ul>
<li><strong>Program Data</strong> Group Row — <code>equipment/controller/program-data</code>
<ul>
<li><strong>Frames (Siemens)</strong> Node Row — <code>equipment/controller/program-data/frames</code>; grown while the
snapshot reports a Siemens frame table</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>Editor Row — the panel of whichever node is selected
<ul>
<li><strong>Frames (Siemens)</strong> Panel
<ul>
<li>No-Runner Body — <em>&quot;No NC runner — load a project first.&quot;</em>; the shared empty state, shown
instead of everything below while the snapshot reports no runner</li>
<li>Absent-Table Body — <em>&quot;No Siemens frame table on the active runner.&quot;</em>; shown instead of
everything below while the panel's own read reports no table</li>
<li>Header Strip
<ul>
<li>Description Caption — <em>&quot;Settable frames ($P_UIFR, translation only); G500 cancels and stores
nothing.&quot;</em>, left-aligned and grey</li>
<li><strong>Show all</strong> Toggle — right-aligned, dense; always rendered</li>
</ul>
</li>
<li>Frame Table — dense, flat, bordered; no sort, no pagination, no column menu
<ul>
<li>Header Row — <strong>Frame</strong>, <strong>X (mm)</strong>, <strong>Y (mm)</strong>, <strong>Z (mm)</strong>. Only <strong>Frame</strong> is translated</li>
<li>Frame Row, one per visible id — not clickable, and carrying no tooltip
<ul>
<li>Frame Id Label — bold plain text, never editable</li>
<li>Value Numeric Field, one per axis column — no minimum, no maximum, no unit suffix</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>Toast — negative, three seconds, the panel's context followed by the server's own message</li>
</ul>
</li>
</ul>
<p>The panel carries no heading, no add or delete control, no reset button, no save button and no
unsaved marker.</p>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../../../index.html">HiNC App Anatomy</a> for git repository links.</p>
<h3 id="web-application">Web Application</h3>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/controlTree/SoftNcFramesPanel.vue</code> — the panel: the two empty layers,
the fixed description and the unconditional <strong>Show all</strong> toggle, the four-column table with its two
literal header spellings, the always-visible regular expression, and the whole-triad cell commit
with its null and non-finite guard.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcWorkCoordinatesPanel.vue</code> — the other node on this
table: the same ids and values through the ISO reader, plus the wider always-visible test, the
conditional toggle, the P0 and M0 actions and the canvas marker this leaf has none of.</li>
<li><code>wwwroot-src/src/components/controlTree/softNcItemTypes.ts</code> — the branch builder: this leaf's node
id and label key, the snapshot flag it is pushed behind, and its position among the program-data
children.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcEmptyState.vue</code> — the shared no-runner line the panel
opens with.</li>
<li><code>wwwroot-src/src/components/widgets/NumericInput.vue</code> — the value cell: commit on blur or Enter,
the empty-text-to-null parse, and the infinity and NaN literals the panel's handler then rejects.</li>
<li><code>wwwroot-src/src/api/softNcRunner.ts</code> — the frames reader and its whole-triad setter, the shared
offset-row shape and parser both leaves reuse, and the presence flag this leaf is gated on.</li>
<li><code>wwwroot-src/src/api/http.ts</code> — the envelope helper that turns a <code>success: false</code> body into a
thrown error indistinguishable from a transport failure.</li>
<li><code>wwwroot-src/src/composables/useSoftNcRunner.ts</code> — the shared snapshot the first empty layer reads.</li>
<li><code>wwwroot-src/src/i18n/en/softNc.ts</code> — the node label, the description, the <strong>Frame</strong> column header,
the absent-table line and the two error contexts.</li>
<li><code>wwwroot-src/src/i18n/en/common.ts</code> — the shared <strong>Show all</strong> label.</li>
<li><code>Mech/SoftNcRunnerController.cs</code> — the REST surface: the presence probe behind the node's gate, the
unsorted frames reader with its zero fill, the per-id writer that assigns into the dictionary
directly rather than through the coordinate accessor, the work-coordinate routes that share the
same object on this brand, and the brand switch's capture-swap-carry-sweep with its accepted-id
filter.</li>
</ul>
<h3 id="hiapi-engine">HiAPI Engine</h3>
<ul>
<li><code>HiMech/NcParsers/Dependencys/Siemens/SiemensFrameTable.cs</code> — the table: the ninety-nine ids its
constructor seeds, the cancel frame it keeps out of them, the coordinate accessors the
work-coordinate leaf uses, the per-axis accessors the <code>$P_UIFR</code> bridge uses, the axis dictionary
neither panel exposes, the deliberate absence of session-reset, and the id-sorted serialization
that decides the row order after a reload.</li>
<li><code>HiMech/NcParsers/Dependencys/Siemens/SiemensFrameTableProxy.cs</code> — the seedless get-or-create
placeholder: what the runner file records, and the fresh table it installs into a project holding
none.</li>
<li><code>HiMech/NcParsers/Dependencys/IIsoCoordinateConfig.cs</code> — the offset-provider contract this table
implements, which is what makes it the work-coordinate leaf's provider on this brand.</li>
<li><code>HiMech/NcParsers/Dependencys/Siemens/SiemensMachineDataTable.cs</code> — the <code>MD</code> table beside it,
carrying no frames and implementing no offset-provider contract.</li>
<li><code>HiMech/NcParsers/Keywords/Siemens/SiemensKeywords.cs</code> — the cancel frame and the extended series
the constructor seeds from.</li>
<li><code>HiMech/NcParsers/Keywords/Generic/IsoKeywords.cs</code> — the <code>G54</code> series the ISO syntax recognises,
wider than this table allocates.</li>
<li><code>HiMech/NcParsers/SoftNcRunner.cs</code> — the Siemens preset that carries the frame-table proxy as its
first per-case entry, behind only the brand marker; the proxy resolution every read goes through;
and the session-reset sweep this table is deliberately outside of.</li>
<li><code>HiMech/NcParsers/ISessionResettable.cs</code> — the marker the table does not carry, and the difference
between session state and setting data it draws.</li>
<li><code>HiMech/NcParsers/NcRunnerSuit.cs</code> — the per-case list the table hangs off and the proxy wiring
that installs it.</li>
<li><code>HiMech/NcParsers/LogicSyntaxs/CoordinateOffsetUtil.cs</code> — the resolver that walks every provider
and takes the first non-null answer, the coordinate section it writes, and the translation it
composes.</li>
<li><code>HiMech/NcParsers/LogicSyntaxs/Siemens/SiemensCoordinateOffsetSyntax.cs</code> — the frame-word path: the
vocabulary it detects, the flag it leaves in place, the modal lookback, and the zero fallback when
nothing answers.</li>
<li><code>HiMech/NcParsers/LogicSyntaxs/IsoCoordinateOffsetSyntax.cs</code> — the sibling registered beside it on
this preset, writing the same chain entry from the <code>G54</code><code>G59.9</code> vocabulary.</li>
<li><code>HiMech/NcParsers/LogicSyntaxs/Siemens/SiemensProgrammableFrameSyntax.cs</code> — the computed
programmable frame, and the entry it shares with the tilt cycle ahead of the settable frame's.</li>
<li><code>HiMech/NcParsers/Syntaxs/SiemensSyntaxUtil.cs</code> — the slot order that decides the composition:
tilt and programmable frame, tool height, the two coordinate syntaxes, then the pivot transform.</li>
<li><code>HiMech/NcParsers/Syntaxs/TransformationUtil.cs</code> — the transform chain: in-order multiplication,
replacement of a repeated entry in place, and the pivot entry that must stay last.</li>
<li><code>HiMech/NcParsers/LogicSyntaxs/MachineCoordSelectSyntax.cs</code> — the one-shot codes that bypass the
composed transform, widened on this preset to <code>G153</code> and <code>SUPA</code>.</li>
<li><code>HiMech/NcParsers/EvaluationSyntaxs/Siemens/SiemensUifrWritingSyntax.cs</code> — the write half of the
<code>$P_UIFR</code> bridge: the literal-only rule, the ignored cancel-frame index, and the no-op when no
frame table resolves.</li>
<li><code>HiMech/NcParsers/EvaluationSyntaxs/Siemens/SiemensUifrVariableLookup.cs</code> — the read half.</li>
<li><code>HiMech/NcParsers/EvaluationSyntaxs/Siemens/SiemensVariableKey.cs</code> — the index-to-id map behind
both halves, and the <code>TR</code>-only key pattern.</li>
<li><code>HiMech/NcParsers/EvaluationSyntaxs/Siemens/SiemensSystemVariableSyntax.cs</code> — where a non-<code>TR</code>
frame component goes instead: recorded on the block, reported unsupported, never reaching the
frame table.</li>
<li><code>HiMech/NcParsers/Initializers/StaticInitializer.cs</code> — the Siemens preset's first-block coordinate
id, and the two brands that differ from it.</li>
<li><code>HiMech/NcParsers/Dependencys/IsoCoordinateAddressMap.cs</code> — the Fanuc-family id set the brand
switch's carry intersects this table against.</li>
<li><code>HiMech/NcParsers/Dependencys/Heidenhain/HeidenhainDatumTable.cs</code> — the third id set the same carry
can meet.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="index.html">Program Data Plane</a> — the plane this leaf sits on, and the ownership rules that
decide what a runner install keeps and what a brand switch sweeps</li>
<li><a class="xref" href="work-coordinates.html">Work Coordinates</a> — the other node on this table, where the same ids are
edited as G54 offsets, with the row actions and the canvas marker this leaf has none of</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,537 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Program Data Plane | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Program Data Plane | HiAPI-C# 2025 ">
<link rel="icon" href="../../../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../../../public/docfx.min.css">
<link rel="stylesheet" href="../../../../public/main.css">
<meta name="docfx:navrel" content="../../../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../../../index.html">
<img id="logo" class="svg" src="../../../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-ControllerProgramData">
<h1 id="program-data-plane">Program Data Plane</h1>
<p>Program Data is the second of the Controller branch's two plane stems: the coordinate, offset and
variable tables that belong to the workpiece rather than to the control, edited one leaf at a time.
It lives on the General Setup page at <code>/general-setup</code> under the Control-Tree id
<code>equipment/controller/program-data</code>, and each of its leaves takes that id plus one segment —
<code>work-coordinates</code>, <code>tool-offsets</code>, <code>siemens-tool-offsets</code>, <code>tool-names</code>, <code>datum-presets</code>,
<code>datum-shifts</code>, <code>frames</code>, <code>retained-variables</code>, <code>r-parameters</code>. The stem is not gated: it is part of
the branch builder's return value rather than a conditional entry, so it appears wherever a runner
resolves and always carries the two leaves no flag guards.</p>
<div class="IMPORTANT">
<h5>Important</h5>
<p>Anatomy makes no claim that any id named here is stable across versions. The migration hop that
keeps an older <code>?tree=</code> value resolving is described in
<a class="xref" href="../../../platform/tree-ids-and-routes.html">Tree Ids and Routes</a>.</p>
</div>
<p>Selecting the stem shows the shared group editor: its own introduction over a clickable list of its
children. The introduction reads <em>&quot;Data that travels with the workpiece / project — work-coordinate
offsets (G54…), tool offsets and the brand-specific per-case tables. Kept on the project even when
the runner file is swapped.&quot;</em></p>
<p>Every leaf below follows the branch's shared editing rules — one read on mount, per-control commit,
capture-assign-await-restore on failure, a dense markup table with no sort and no pagination, and the
two empty layers. Those rules, and the places this plane departs from them, are
<a class="xref" href="../editing-contract.html">Editing Contract</a>.</p>
<h2 id="where-the-data-actually-lives">Where the Data Actually Lives</h2>
<p>The stem's introduction names a storage location, and here — unlike the machine stem's own
introduction — it holds for every leaf. What it does not say is that the location is not a file.</p>
<h3 id="one-list-inlined-in-the-project">One list, inlined in the project</h3>
<p><a class="xref" href="../../../../api/Hi.NcParsers.NcRunnerSuit.html">NcRunnerSuit</a> carries the runner beside the project's own
<a class="xref" href="../../../../api/Hi.NcParsers.NcRunnerSuit.html#Hi_NcParsers_NcRunnerSuit_PerCaseNcDependencyList">PerCaseNcDependencyList</a>, and serializes the two side by side. The
runner half can be written to a side file; the per-case half never is — the suit writes it as a
nested element with no file reference of its own, so it lands inside whatever file the owning project
writes. Every table this plane edits is an entry of that one list.</p>
<p>No panel and no endpoint reads or writes a table by reaching into that list by name. The one place
that addresses the list directly is the sweep that follows a runner swap, and it removes whole
entries rather than editing any value in one. A brand preset's pipeline list carries an
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.INcDependencyProxy.html">INcDependencyProxy</a> in place of each of these tables, and every read
and write resolves through
<a class="xref" href="../../../../api/Hi.NcParsers.SoftNcRunner.html#Hi_NcParsers_SoftNcRunner_GetEffectiveNcDependencyList_">GetEffectiveNcDependencyList</a><small>(API)</small>, which substitutes
the project's table for the placeholder. That is also what the snapshot flags probe, so the tree
grows a leaf for the concrete table and never for the proxy —
<a class="xref" href="../brand-matrix.html">Brand Matrix</a>.</p>
<h3 id="the-proxies-on-this-plane-carry-nothing">The proxies on this plane carry nothing</h3>
<p><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Generic.ToolOffsetTableProxy.html">ToolOffsetTableProxy</a> and its siblings — the Siemens <code>$TC_DP</code>,
frame and R-parameter proxies, the Heidenhain datum proxy and the Fanuc-family retained common
variable proxy — are get-or-create placeholders with no seed. Each serializes an empty element, and
on wiring installs a bare new instance into the per-case list when the project holds none of that
type; an existing table is left untouched.</p>
<p>Two consequences follow. A controller file written from the branch root's Object-Management menu
carries none of the values edited on this plane: the seedless proxies write an empty element, and the
one proxy on this plane that does carry a seed — the brand parameter-table proxy standing behind the
work coordinates on the Fanuc family and Syntec — serializes the fixed seed it was constructed with
rather than the table the panels have been editing. And a table this plane loses is not recoverable
from a preset: a fresh instance is what the proxy makes.</p>
<h3 id="which-store-a-work-coordinate-lands-in">Which store a work coordinate lands in</h3>
<p>Work Coordinates is the one leaf whose backing object changes with the brand. The panel reads and
writes the first <a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.IIsoCoordinateConfig.html">IIsoCoordinateConfig</a> in the effective list, and the
caption above its table names which one answered.</p>
<table>
<thead>
<tr>
<th>Brand</th>
<th>Backing object</th>
<th>Ids the table lists</th>
<th>Caption above the table</th>
</tr>
</thead>
<tbody>
<tr>
<td>Fanuc, Mazak</td>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Fanuc.FanucParameterTable.html">FanucParameterTable</a></td>
<td>G54G59 and G54.1P1G54.1P48</td>
<td><em>&quot;Stored in the Fanuc parameter table (#5221+ / #7001+).&quot;</em></td>
</tr>
<tr>
<td>Syntec</td>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Syntec.SyntecParameterTable.html">SyntecParameterTable</a></td>
<td>G54G59 and G54.1P1G54.1P48</td>
<td><em>&quot;Stored in the Syntec parameter table (Pr5221+ / Pr7001+).&quot;</em></td>
</tr>
<tr>
<td>Siemens</td>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Siemens.SiemensFrameTable.html">SiemensFrameTable</a></td>
<td>G54G57 and G505G599</td>
<td><em>&quot;Stored as Siemens settable frames ($P_UIFR; G500 cancels and is always zero).&quot;</em></td>
</tr>
<tr>
<td>Heidenhain</td>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.HeidenhainDatumTable.html">HeidenhainDatumTable</a></td>
<td>G54G59</td>
<td><em>&quot;G54G59 map onto Heidenhain datum preset rows 16.&quot;</em></td>
</tr>
</tbody>
</table>
<p>An id appears only where the store actually holds it. A Fanuc-family or Syntec table yields an id
whose parameter address is present, and every one of those default tables seeds all of them at zero;
the Siemens frame table seeds G54G57 and the extended series, and has no G58 or G59 row at all; the
Heidenhain table yields one id per preset row 16 that exists. G500 is never a row — the frame table
refuses to store it and does not seed it.</p>
<p>That caption is the panel's only statement of where a value went, and it is selected from the
snapshot's work-coordinate kind rather than from the brand marker.</p>
<h3 id="the-one-setting-that-is-not-in-the-list">The one setting that is not in the list</h3>
<p>The Tool Offsets leaf's <em>Set ideal offset dependent on tool house</em> toggle is not a dependency. It
writes <a class="xref" href="../../../../api/Hi.MachiningProcs.MachiningActRunnerConfig.html#Hi_MachiningProcs_MachiningActRunnerConfig_IsIdealOffsetDependentOnToolHouse">IsIdealOffsetDependentOnToolHouse</a>, an
element the project serializes beside the runner suit. It therefore stays with the project like
everything else on this plane, but by a different route — and, being outside the per-case list, it is
untouched by the sweep that follows a runner swap.</p>
<h2 id="how-this-differs-from-the-machine-plane">How This Differs From the Machine Plane</h2>
<p>The machine plane's ownership is split. Several of its leaves are plain instances the runner owns
outright — the tool-change position, the block-skip layers, the subprogram folders, and on Siemens
and Heidenhain the peck clearance — and an install replaces them with the incoming runner's; the
rest are rows of the brand parameter table and are stored on the project. One leaf holds both
halves: Controller Parameters, where the macro loop guards are runner-owned fields sitting beside
table rows. Which side of that line a machine-plane field falls on is
<a class="xref" href="../machine/index.html">Machine and Controller Plane</a>.</p>
<p>This plane has no such split. Nothing on it is a plain runner-owned instance: every leaf resolves a
per-case table through a proxy, so every value it edits is stored on the project. That is what makes
the two introductions' phrasing load-bearing rather than decorative — and it is why installing a
controller file of the brand already in force changes nothing here at all: each of that brand's
proxies takes the table the project already holds rather than installing a fresh one over it. The
same install does return the machine plane's runner-owned settings to the incoming file's values.</p>
<p>The one crossing runs the other way. On Fanuc, Mazak and Syntec the work coordinates are rows of the
same brand parameter table whose other rows the machine plane's per-axis leaves and native parameter
form edit. A single object straddles both stems there, which is exactly why that table is per-case at
all: the two planes could not otherwise be separated.</p>
<h2 id="the-planes-leaves">The Plane's Leaves</h2>
<p>Ordered as the branch builder pushes them: the two ungated leaves first, then each gated leaf or
gated pair behind the snapshot flag that grows it. Which brands satisfy each flag is
<a class="xref" href="../brand-matrix.html">Brand Matrix</a>; the column that page does not carry is the last
one here.</p>
<table>
<thead>
<tr>
<th>Node segment</th>
<th>Label the tree shows</th>
<th>Gate</th>
<th>Backing table</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>work-coordinates</code></td>
<td>Work Coordinates (G54…)</td>
<td>ungated</td>
<td>the brand's ISO coordinate provider, per the table above</td>
</tr>
<tr>
<td><code>tool-offsets</code></td>
<td>Tool Offsets, read as Tool Offsets (ISO G43 H) where the Siemens table resolves</td>
<td>ungated; label switched by <code>hasSiemensToolOffsets</code></td>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Generic.ToolOffsetTable.html">ToolOffsetTable</a></td>
</tr>
<tr>
<td><code>siemens-tool-offsets</code></td>
<td>Tool Offsets ($TC_DP)</td>
<td><code>hasSiemensToolOffsets</code></td>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Siemens.SiemensToolOffsetTable.html">SiemensToolOffsetTable</a>, its cutting-edge map</td>
</tr>
<tr>
<td><code>tool-names</code></td>
<td>Tool Names</td>
<td><code>hasSiemensToolOffsets</code></td>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Siemens.SiemensToolOffsetTable.html">SiemensToolOffsetTable</a>, its name map</td>
</tr>
<tr>
<td><code>datum-presets</code></td>
<td>Datum Presets (Q339)</td>
<td><code>hasDatums</code></td>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.HeidenhainDatumTable.html">HeidenhainDatumTable</a>, its preset rows</td>
</tr>
<tr>
<td><code>datum-shifts</code></td>
<td>Datum Shifts (D)</td>
<td><code>hasDatums</code></td>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.HeidenhainDatumTable.html">HeidenhainDatumTable</a>, its shift rows</td>
</tr>
<tr>
<td><code>frames</code></td>
<td>Frames (Siemens)</td>
<td><code>hasFrames</code></td>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Siemens.SiemensFrameTable.html">SiemensFrameTable</a></td>
</tr>
<tr>
<td><code>retained-variables</code></td>
<td>Retained Common Variables</td>
<td><code>hasRetainedVariables</code></td>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Fanuc.RetainedCommonVariableTable.html">RetainedCommonVariableTable</a></td>
</tr>
<tr>
<td><code>r-parameters</code></td>
<td>R Parameters</td>
<td><code>hasRParameters</code></td>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Siemens.SiemensRParameterTable.html">SiemensRParameterTable</a></td>
</tr>
</tbody>
</table>
<p>Every label above is a translated role string rather than a type name, so the tree renders the
translation and a locale change rewrites the rows without touching an id.</p>
<p><strong>Two leaves stand on every brand: Work Coordinates (G54…) and Tool Offsets.</strong> They are the builder's
unconditional entries, and every brand preset satisfies them — each carries an ISO coordinate
provider of its own, and all five proxy the generic tool-offset table. The remaining leaves are
pushed inside a flag test, and each of those flags is satisfied by one brand family only.</p>
<h3 id="fewer-tables-than-leaves">Fewer tables than leaves</h3>
<p>Two flags grow two leaves each, because two leaves edit two halves of one object. Tool Offsets
($TC_DP) and Tool Names are the cutting-edge map and the name map of a single Siemens tool-offset
table, which is why there is no separate tool-name flag and why the two nodes appear and disappear
together. Datum Presets (Q339) and Datum Shifts (D) are the preset rows and the shift rows of a
single Heidenhain datum table, served by one component that reads its role from the node id's last
segment.</p>
<p>The work coordinates then fold two more leaves together, on the two brands where the ISO provider is
a table this plane already lists. On Siemens, Work Coordinates and Frames (Siemens) are two views of
one frame dictionary — the same ids, the same X/Y/Z translations, edited through two panels. On
Heidenhain, the G54G59 rows of Work Coordinates <em>are</em> preset rows 16 of Datum Presets (Q339), so
editing one changes what the other shows. Datum Shifts has no such alias, and the Siemens frame
table's per-axis translation components beyond X/Y/Z are reachable from neither panel.</p>
<h3 id="the-two-tool-offset-ledgers-disagree-on-the-sign-of-wear">The two tool-offset ledgers disagree on the sign of wear</h3>
<p>Where both are present the difference matters more than the labels suggest. On the generic table an
effective value is the ideal minus the wear; on the Siemens <code>$TC_DP</code> table it is the geometry plus
the wear. That is why the Siemens panel spells the addition out in a footnote below its table, and
says in its own description that a shortened tool is entered as negative wear, while the generic
panel states no sign rule at all. The relabel of the ungated leaf to <strong>Tool Offsets (ISO G43 H)</strong>
exists so that the two are not read as one ledger.</p>
<h2 id="what-a-brand-change-does-here">What a Brand Change Does Here</h2>
<p>A brand switch replaces the whole runner with the target brand's preset and then removes every
per-case table the new runner references through no proxy. This plane is where that sweep lands: the
machine plane's project-owned parameter table is one entry, and every other entry the sweep can reach
is a table edited here. The staged select, its confirmation and the rest of the control are
<a class="xref" href="../machine/brand-switch.html">Controller Brand</a>; what follows is the outcome for this plane
alone.</p>
<ul>
<li><strong>Tool Offsets survives every switch.</strong> All five presets proxy the generic tool-offset table, so
the sweep's keep-set always contains it. Its rows, its tool numbering and the tool-house dependence
toggle beside it come through a brand change unchanged; only the leaf's label can change.</li>
<li><strong>Every other table on the plane is brand-bound and is removed</strong> the moment the target brand
proxies none of it — the Siemens <code>$TC_DP</code> table with its tool-name map, the Siemens frame table,
the Siemens R-parameter table, the Heidenhain datum table, and the Fanuc-family or Syntec parameter
table that holds the work coordinates. Switching back does not restore what was removed; the proxy
makes a fresh instance instead, empty for the tool-offset, tool-name, R-parameter and
retained-variable tables, and zero-filled for the frame and datum tables that seed a row set in
their constructor.</li>
<li><strong>The Fanuc and Mazak presets proxy the same parameter table</strong>, so a switch between those two
brands sweeps nothing here: the work coordinates and every other row of that table stay where they
are.</li>
<li><strong>Retained Common Variables spans a family rather than a brand.</strong> Fanuc, Syntec and Mazak all proxy
it, so it survives any switch among those three and is removed on a switch to Siemens or
Heidenhain.</li>
<li><strong>Work coordinates are the one set of values a switch can carry across.</strong> The brand panel's <em>Carry
work-coordinate XYZ (G54…) into the new brand's table</em> checkbox is on by default; the switch reads
every id the outgoing providers exposed and, after the swap, writes each back only to a provider
on the incoming runner that lists it — never to a provider that would merely accept it. The
Fanuc-family and Syntec presets expose the same set — G54G59 and all forty-eight G54.1 P offsets
on the brand table, every address seeded, plus the extended G59.1G59.9 on the brand-neutral
table behind it — so a switch between Syntec and either of Fanuc and Mazak carries every one of
them. A switch into Siemens carries only G54G57, the frame table listing no G58, no G59, no
G59.x and no G54.1 P id at all; a switch into Heidenhain carries G54G59 and drops the
forty-eight G54.1 P offsets and the nine G59.x. No other table's values are carried, and the carry
is skipped for a table the swap re-bound rather than replaced — which is the Fanuc-to-Mazak case.</li>
</ul>
<p>The banner shown before the apply says as much in one line: <em>&quot;Switching brand replaces the whole
runner with the {brand} preset. Machine settings reset to that preset's defaults and the old brand's
program-data tables are removed — switching back does not restore them.&quot;</em></p>
<p>An Object-Management Load, Paste or XML apply runs the same install and the same sweep, so a
controller file of another brand takes this plane through the identical outcome without the carry
option. Both writes are refused while an NC program is playing.</p>
<h2 id="layout">Layout</h2>
<ul>
<li><strong>General Setup</strong> Control Tree — the left dock of <code>/general-setup</code>
<ul>
<li><strong>Controller</strong> Node Row
<ul>
<li><strong>Machine / Controller</strong> Group Row — the plane beside this one</li>
<li><strong>Program Data</strong> Group Row — <code>equipment/controller/program-data</code>
<ul>
<li><strong>Work Coordinates (G54…)</strong> Node Row</li>
<li><strong>Tool Offsets</strong> Node Row — reads <strong>Tool Offsets (ISO G43 H)</strong> where the Siemens <code>$TC_DP</code>
table resolves</li>
<li><strong>Tool Offsets ($TC_DP)</strong> Node Row — grown while the snapshot reports a Siemens tool-offset
table</li>
<li><strong>Tool Names</strong> Node Row — grown on the same flag</li>
<li><strong>Datum Presets (Q339)</strong> Node Row — grown while it reports a Heidenhain datum table</li>
<li><strong>Datum Shifts (D)</strong> Node Row — grown on the same flag</li>
<li><strong>Frames (Siemens)</strong> Node Row — grown while it reports a Siemens frame table</li>
<li><strong>Retained Common Variables</strong> Node Row — grown while it reports a retained common variable
table</li>
<li><strong>R Parameters</strong> Node Row — grown while it reports a Siemens R-parameter table</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>Editor Row — the panel of whichever node is selected
<ul>
<li><strong>Program Data</strong> Group Panel
<ul>
<li>Intro Caption — the stem's introduction line</li>
<li>Child List — one bordered, separated row per leaf above, each showing the leaf's label in the
theme's primary colour with a right chevron; a click selects that leaf</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>The stem's own editor holds no field, so nothing on this plane is edited from the group row itself.</p>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../../../index.html">HiNC App Anatomy</a> for git repository links.</p>
<h3 id="web-application">Web Application</h3>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/controlTree/softNcItemTypes.ts</code> — the branch builder: the program-data
stem's id, label key and intro key, the two leaves it pushes unconditionally, the snapshot flag
each further leaf is pushed behind in the order the table above lists them, and the tool-offsets
relabel.</li>
<li><code>wwwroot-src/src/components/controlTree/GroupInfoPanel.vue</code> — the stem's editor: the intro caption
and the clickable child list that selects a leaf.</li>
<li><code>wwwroot-src/src/components/controlTree/useControlTreeHost.ts</code> — builds the <code>equipment/controller</code>
root this stem hangs from, inside the equipment group.</li>
<li><code>wwwroot-src/src/api/softNcRunner.ts</code> — the snapshot shape the builder reads its flags from, and
the typed wrappers over each leaf's reader and writers.</li>
<li><code>wwwroot-src/src/composables/useSoftNcRunner.ts</code> — the shared snapshot the leaf panels gate on, the
brand switch with its carry option, and the Object-Management install that regrows the branch.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcWorkCoordinatesPanel.vue</code> — the one panel that reads
the snapshot's work-coordinate kind, the caption it selects from it, and the extended-row filter.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcToolOffsetsPanel.vue</code> — the generic ledger: the
tool-house dependence toggle, the refresh it enables, and the Siemens caveat gated on the brand
string rather than on the table flag.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcSiemensToolOffsetsPanel.vue</code> and
<code>wwwroot-src/src/components/controlTree/SoftNcToolNamesPanel.vue</code> — the two leaves over one Siemens
table: the cutting-edge rows and the name map.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcDatumTablePanel.vue</code> — the one component behind both
Heidenhain leaves, choosing its role from the node id's last segment.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcFramesPanel.vue</code> — the settable-frame table and its
own always-visible set, narrower than the work-coordinate panel's.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcRetainedVariablesPanel.vue</code> and
<code>wwwroot-src/src/components/controlTree/SoftNcRParametersPanel.vue</code> — the two sparse variable
tables, and the vacant entry a cleared cell writes.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcEmptyState.vue</code> — the shared no-runner line every
leaf on this plane falls back to.</li>
<li><code>wwwroot-src/src/api/equipmentSetup.ts</code> — the canvas marker id a work-coordinate row click writes,
outside this branch's own REST surface.</li>
<li><code>wwwroot-src/src/i18n/en/softNc.ts</code> — the stem's introduction line, every leaf label in the table
above, the work-coordinate storage captions and the brand-switch warning.</li>
<li><code>wwwroot-src/src/i18n/en/common.ts</code> — the shared column and action labels these tables reuse.</li>
<li><code>Mech/SoftNcRunnerController.cs</code> — the plane's REST surface: the presence flags, the per-table
readers and writers, the brand switch with its work-coordinate carry, and the sweep of per-case
tables the new runner references through no proxy.</li>
<li><code>Mech/EquipmentSetupDisplayController.cs</code> — the marked-coordinate id a work-coordinate row click
stores, on the user configuration rather than on the project.</li>
</ul>
<h3 id="hiapi-engine">HiAPI Engine</h3>
<ul>
<li><code>HiMech/NcParsers/NcRunnerSuit.cs</code> — the suit: the per-case list this plane's tables live in, the
serialization that inlines it with no file reference, and the proxy wiring that materialises a
table into a project holding none of that type.</li>
<li><code>HiMech/NcParsers/SoftNcRunner.cs</code> — the five brand presets as literal dependency lists, showing
which proxies each brand carries, and the proxy resolution every read on this plane goes through.</li>
<li><code>HiMech/NcParsers/Dependencys/INcDependencyProxy.cs</code> — the maker-and-taker contract, and why a
proxy's resolved data is never written into the runner.</li>
<li><code>HiMech/NcParsers/Dependencys/Generic/ToolOffsetTableProxy.cs</code> — the seedless get-or-create shape
the Siemens, Heidenhain and Fanuc-family per-case proxies on this plane all repeat.</li>
<li><code>HiMech/NcParsers/Dependencys/Fanuc/FanucParameterTableProxy.cs</code> — the contrasting seeded proxy,
and its own account of why the Fanuc-family table mixes machine configuration with the per-case
work-coordinate offsets.</li>
<li><code>HiMech/NcParsers/Dependencys/IIsoCoordinateConfig.cs</code> and
<code>HiMech/NcParsers/Dependencys/IsoCoordinateAddressMap.cs</code> — the interface the work-coordinate panel
edits through, and the address arithmetic that decides which ids a Fanuc-family or Syntec table
lists.</li>
<li><code>HiMech/NcParsers/Dependencys/Generic/ToolOffsetTable.cs</code> — the generic ledger every preset
proxies, and the subtraction that makes an effective value ideal minus wear.</li>
<li><code>HiMech/NcParsers/Dependencys/Siemens/SiemensToolOffsetTable.cs</code> — the cutting-edge map and the
name map behind two leaves, and the addition that makes an effective value geometry plus wear.</li>
<li><code>HiMech/NcParsers/Dependencys/Siemens/SiemensFrameTable.cs</code> — the settable frames: the ids the
constructor seeds, the ISO coordinate implementation the work-coordinate leaf shares, and the
per-axis translation components neither panel exposes.</li>
<li><code>HiMech/NcParsers/Dependencys/Siemens/SiemensRParameterTable.cs</code> and
<code>HiMech/NcParsers/Dependencys/Fanuc/RetainedCommonVariableTable.cs</code> — the two persistent variable
tables, their accepted id ranges, and the vacant entry a valueless element preserves across a save.</li>
<li><code>HiMech/NcParsers/Dependencys/Heidenhain/HeidenhainDatumTable.cs</code> — the preset and shift rows behind
two leaves, and the mapping that aliases G54G59 onto preset rows 16.</li>
<li><code>HiMech/MachiningProcs/MachiningActRunnerConfig.cs</code> — the tool-house dependence flag, the one
setting on this plane stored outside the per-case list.</li>
<li><code>HiNc/MachiningProcs/LocalProjectService.cs</code> — the facade setter every install and brand switch
assigns through, and the proxy re-binding that materialises the incoming brand's per-case tables.</li>
<li><code>HiNc/MachiningProcs/MachiningProject.cs</code> — where the suit and the runner configuration hang off
the project, and the load path for a project file carrying the per-case list.</li>
</ul>
<h2 id="pages">Pages</h2>
<p>Ordered by the first node each page owns, as the plane lists them.</p>
<ul>
<li><a class="xref" href="work-coordinates.html">Work Coordinates</a> — The one uniform face over four brand
storage models, its P0 and M0 row actions, and the row click that marks a coordinate on the canvas</li>
<li><a class="xref" href="tool-offsets.html">Tool Offsets</a> — The generic ISO ledger: ideal against wear, the
renameable tool number, and what tool-house dependence takes over</li>
<li><a class="xref" href="siemens-tool-offsets.html">Siemens Tool Offsets</a> — The <code>$TC_DP</code> cutting-edge table
and the tool-name map beside it, two leaves over one object</li>
<li><a class="xref" href="datum-tables.html">Datum Tables</a> — The Heidenhain preset and shift rows, one
component serving both, and the preset rows the work coordinates alias</li>
<li><a class="xref" href="frames.html">Frames</a> — The Siemens settable frames, the ids they seed, and what
the work-coordinate leaf shows of the same table</li>
<li><a class="xref" href="persistent-variables.html">Persistent Variables</a> — The retained common variables
and the R parameters: sparse tables, accepted ranges, and the difference between vacant and deleted</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="../index.html">Controller Branch</a> — the branch this plane is half of, its runner root and the machine plane
beside it</li>
<li><a class="xref" href="../machine/index.html">Machine and Controller Plane</a> — the other plane, whose ownership splits between the runner and
the project where this one does not</li>
<li><a class="xref" href="../brand-matrix.html">Brand Matrix</a> — which brand satisfies each gate named above, and what each
flag actually probes</li>
<li><a class="xref" href="../editing-contract.html">Editing Contract</a> — the commit, rollback and rendering rules every leaf on
this plane inherits, and the panels that depart from them</li>
<li><a class="xref" href="work-coordinates.html">Work Coordinates</a> — the ungated leaf whose backing object changes with the
brand, and the only values a brand switch can carry</li>
<li><a class="xref" href="tool-offsets.html">Tool Offsets</a> — the other ungated leaf, and the one table on this plane that
survives every brand switch</li>
<li><a class="xref" href="siemens-tool-offsets.html">Siemens Tool Offsets</a> — the second tool-offset ledger, whose presence
relabels the first</li>
<li><a class="xref" href="datum-tables.html">Datum Tables</a> — the two Heidenhain leaves, and the preset rows the work
coordinates share</li>
<li><a class="xref" href="frames.html">Frames</a> — the Siemens frame table, read by two leaves of this plane at once</li>
<li><a class="xref" href="persistent-variables.html">Persistent Variables</a> — the two variable tables a program's arithmetic
reads, kept on the project rather than reset with the session</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,565 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Persistent Variables | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Persistent Variables | HiAPI-C# 2025 ">
<link rel="icon" href="../../../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../../../public/docfx.min.css">
<link rel="stylesheet" href="../../../../public/main.css">
<meta name="docfx:navrel" content="../../../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../../../index.html">
<img id="logo" class="svg" src="../../../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-ControllerPersistentVariables">
<h1 id="persistent-variables">Persistent Variables</h1>
<p>Persistent Variables is the pair of program-variable ledgers the Controller branch grows for one
brand or the other, and no shipped preset carries both: the Fanuc-style retained common variables
and the Sinumerik R parameters. Both live on the General Setup page at <code>/general-setup</code> under the
Control-Tree ids <code>equipment/controller/program-data/retained-variables</code> and
<code>equipment/controller/program-data/r-parameters</code> — reached as
<code>?tree=equipment/controller/program-data/retained-variables</code> and
<code>?tree=equipment/controller/program-data/r-parameters</code> — and the tree labels them
<strong>Retained Common Variables</strong> and <strong>R Parameters</strong>. They share a page because no preset shows both:
each node is grown by a flag that one set of brand presets satisfies and the other set does not.</p>
<div class="IMPORTANT">
<h5>Important</h5>
<p>Anatomy makes no claim that any id named here is stable across versions. The migration hop that
keeps an older <code>?tree=</code> value resolving is described in
<a class="xref" href="../../../platform/tree-ids-and-routes.html">Tree Ids and Routes</a>.</p>
</div>
<h2 id="two-nodes-that-exclude-each-other">Two Nodes That Exclude Each Other</h2>
<p>The branch builder pushes each of the two nodes inside a test of its own, one snapshot flag each, and
each flag is a single type probe over the runner's proxy-resolved dependency list —
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Fanuc.RetainedCommonVariableTable.html">RetainedCommonVariableTable</a> behind one and
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Siemens.SiemensRParameterTable.html">SiemensRParameterTable</a> behind the other. Which brand
satisfies which gate is <a class="xref" href="../brand-matrix.html">Brand Matrix</a>; the short answer is that
the Fanuc, Syntec and Mazak presets carry a retained-common-variable proxy, the Siemens preset carries
an R-parameter proxy, and the Heidenhain preset carries neither.</p>
<p><strong>The exclusion is a property of the five preset lists, not a rule in the builder.</strong> Nothing in the
tree code forbids the two rows from standing together; no shipped preset simply carries both entries,
and the brand select can produce nothing but a preset. A runner composed by hand or loaded from a
controller file whose pipeline list named both tables would grow both rows, each with its own working
panel — and the Object-Management menu on the branch root, described in
<a class="xref" href="../index.html">Controller Branch</a>, installs such a file without inspecting what its
pipeline names, so that route is open from inside the branch.</p>
<p>The two panels are separate components rather than one component discriminating on the node id, so
neither reads the node it was given. What they share is a shell: the same three-state body, the same
dense table, the same footer, and the same commit, rollback and toast rules the whole branch follows
<a class="xref" href="../editing-contract.html">Editing Contract</a>.</p>
<h2 id="what-each-ledger-is">What Each Ledger Is</h2>
<h3 id="retained-common-variables">Retained Common Variables</h3>
<p><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Fanuc.RetainedCommonVariableTable.html">RetainedCommonVariableTable</a> models the Fanuc-style ISO
controller's common variables in the retained range <code>#500</code><code>#999</code> — the range a real control keeps in
non-volatile memory across a power cycle, which is why the model stores it with the project rather
than with the run. It is the range, not the whole macro-variable surface: the table answers a <code>#nnn</code>
lookup only for 500 through 999 and returns nothing for any other key, so an out-of-range reference
falls through to the next lookup in the evaluator's chain.</p>
<p>The neighbouring ranges are deliberately elsewhere, and none of them has a node on this branch.
<code>#1</code><code>#33</code> are call-frame locals bounded by a macro call rather than by a power cycle. <code>#100</code><code>#499</code>
are the non-retained commons, carried block to block in the per-block dataflow and cleared on the
block that ends the program. <code>#1000</code> and above are read-only or computed from runtime state rather
than stored here: some resolve against other dependencies — the brand parameter table behind the
work-coordinate addresses, the tool offset table behind <code>#2001</code><code>#2200</code> — and some come from the
block's own record, which is where the position variables are read and where a system-control write
is recorded rather than emulated.</p>
<p>The type name and the tree label both say <em>common variable</em> rather than <em>macro variable</em>, because the
model reserves &ldquo;macro&rdquo; for Custom Macro B's call frame. The hint above the table is the one string in
the leaf that says <strong>Retained macro variables</strong> instead.</p>
<h3 id="r-parameters">R Parameters</h3>
<p><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Siemens.SiemensRParameterTable.html">SiemensRParameterTable</a> models the Sinumerik arithmetic
variable surface <code>R0</code><code>R999</code> — the <code>R63=100.5</code> and <code>C=R61</code> of a Siemens program. On the control these
sit in retentive memory and survive both program end and a power cycle, and the model keeps them the
same way: stored with the project, not reset with the session.</p>
<p>The table answers an <code>Rn</code> key in the canonical uppercase form the Siemens expression parser produces,
and a lowercase <code>rn</code> from a raw capture resolves as well. Every other key returns nothing.</p>
<h2 id="addressing-and-ranges">Addressing and Ranges</h2>
<table>
<thead>
<tr>
<th></th>
<th>Retained Common Variables</th>
<th>R Parameters</th>
</tr>
</thead>
<tbody>
<tr>
<td>Node id</td>
<td><code>equipment/controller/program-data/retained-variables</code></td>
<td><code>equipment/controller/program-data/r-parameters</code></td>
</tr>
<tr>
<td>Snapshot flag</td>
<td><code>hasRetainedVariables</code></td>
<td><code>hasRParameters</code></td>
</tr>
<tr>
<td>Backing table</td>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Fanuc.RetainedCommonVariableTable.html">RetainedCommonVariableTable</a></td>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Siemens.SiemensRParameterTable.html">SiemensRParameterTable</a></td>
</tr>
<tr>
<td>Brands</td>
<td>Fanuc, Syntec, Mazak</td>
<td>Siemens</td>
</tr>
<tr>
<td>Key column header</td>
<td><strong>Variable</strong></td>
<td><strong>Parameter</strong></td>
</tr>
<tr>
<td>Key cell reads</td>
<td><code>#</code> then the number</td>
<td><code>R</code> then the number</td>
</tr>
<tr>
<td>Accepted ids</td>
<td>500999</td>
<td>0999</td>
</tr>
<tr>
<td>Bounds on the add field</td>
<td>minimum 500, maximum 999</td>
<td>minimum 0, maximum 999</td>
</tr>
<tr>
<td>Absent-table body</td>
<td><em>&quot;No retained common variable table on the active runner.&quot;</em></td>
<td><em>&quot;No R-parameter table on the active runner.&quot;</em></td>
</tr>
</tbody>
</table>
<p>Each range is a pair of constants declared on the table type, and four separate places enforce it.
The panel refuses an add whose id is not a whole number inside the range and says so in a toast of its
own. The numeric field carrying that id rejects a <em>finite</em> value outside its bounds before the panel
ever sees it — the infinity and NaN literals the field also parses skip the bounds test entirely and
are stopped by the panel's whole-number check instead. The write endpoint compares the id against the
same two constants and answers unsuccessfully with the range spelled out in the message. And behind
all three, the table's own writer ignores an out-of-range id — the guard that holds for a caller
reaching the table directly rather than through the endpoint, which is how a run writes.</p>
<p>The endpoint's guard is not reachable from a project this app wrote. The rows a panel writes to came
from the table, the add path is already filtered, and the serializer drops an out-of-range key on the
way out, so no file the app saves carries one. Loading applies no such filter: a project file edited
by hand can carry an id outside the range, the read lists it and the panel shows it, and editing that
row's value is the one way a reader meets the endpoint's range message.</p>
<p><strong>No range is narrowed by machine data.</strong> The R range in particular is the fixed pair 0999 on the
table type; the endpoint reads no value from the Siemens machine-data table before accepting an id,
so the parameter count a real control would allocate does not constrain what this leaf accepts.</p>
<p><strong>Neither table is dense.</strong> A row exists for an id that has been written, and for no other, so a
thirty-row table and a four-row table are equally normal and the numbers in the key column need not be
contiguous. A project that has never written one shows an empty table body under the header, because
the proxy that materialises the table installs a bare instance carrying nothing —
<a class="xref" href="index.html">Program Data Plane</a> covers the seedless proxies this plane is
built from. The read returns the rows in ascending id order and the panel never re-sorts: an edit
leaves a row where it is, a delete removes it in place, and only the re-read that follows an add
re-orders anything.</p>
<h2 id="what-persists-and-what-clears">What Persists, and What Clears</h2>
<p>Both tables are per-case entries of the project's own dependency list, reached through a get-or-create
placeholder in the runner's pipeline list. The runner file records only an empty placeholder element,
so the values are written into the project file rather than into a controller asset.</p>
<p>Four things in the runtime could plausibly discard what these tables hold. Three of them leave both
tables alone; the fourth removes a table outright rather than clearing it.</p>
<ul>
<li><strong>A new session does not clear them.</strong> The session-init edge that rebuilds the syntax pipeline
sweeps every dependency and syntax declaring session-scoped state — iteration counters, index
allocators — and calls each one's reset. Neither table declares any, so both are skipped.</li>
<li><strong>A power reset does not clear them.</strong> The power-reset command sweeps the proxy-resolved dependency
list for dependencies declaring a volatile subset and then resets the session state, dropping the
per-block dataflow. None of the dependencies a brand preset carries declares a volatile subset, so
what a power reset actually discards is the dataflow — which is where the non-retained commons live
— while every value in these two tables stands.</li>
<li><strong>Program end does not clear them.</strong> The syntax that models a control's <code>M02</code> / <code>M30</code> reset empties
the block's volatile dictionary and cancels an active modal macro. It writes into the block's own
record and reaches no dependency at all, so neither table is in its path.</li>
<li><strong>A brand switch removes the whole table rather than clearing it.</strong> The sweep that follows a runner
swap keeps exactly the per-case tables the new runner resolves through a proxy, so a switch to a
brand that proxies neither drops the table outright, and switching back materialises a fresh empty
one rather than the values that were there. Retained Common Variables is the wider of the two: all
three of Fanuc, Syntec and Mazak proxy it, so it survives any switch among those three and is
removed on a switch to Siemens or Heidenhain. R Parameters survives only a Siemens-to-Siemens
re-flash. What a switch keeps, resets and destroys across the whole plane is
<a class="xref" href="index.html">Program Data Plane</a>.</li>
</ul>
<h3 id="a-run-writes-into-the-same-table">A run writes into the same table</h3>
<p>These are not a snapshot of what a program did — they are the store the program uses. Each brand's
syntax list carries a reading syntax that consumes a literal assignment out of the parsed block and
writes it straight into the table: <code>#500 = 1.234</code> on the three Fanuc-style brands, <code>R63 = 100.5</code> on
Siemens. No mirror is kept in the per-block dataflow; the table is the single source of truth. A
non-literal right-hand side is resolved to a literal earlier in the same block by the expression
normaliser and then lands by the same route, so <code>#600 = #500 + 1</code> reaches the table as well.</p>
<p>Reads run the other way through the same object: each table is a variable lookup on the effective
dependency list, so an expression naming an id in range reads whatever the leaf shows.</p>
<p>Two consequences follow for a reader of this leaf. A played program changes what the table holds, and
because no panel in the branch is pushed at, the change appears only when the panel is remounted by
selecting another node and coming back. And because the table is written into the project file, those
run-time writes are what the next load of that project starts from — once the project is saved.
Nothing in the branch and nothing on the run path saves on its own: a panel edit and a run-time write
alike stand in memory until an explicit save, and closing the project without one discards them.</p>
<h3 id="vacant-is-a-value-and-it-fails-loud">Vacant is a value, and it fails loud</h3>
<p>A stored null is a <strong>vacant</strong> entry, and so is a missing key — the table reads the two identically.
Vacant is not zero. An expression that evaluates a vacant id does not fall back to a number: the
evaluator returns a failure carrying the code <code>Variable--Vacant</code> and a message naming the key, which
is what the R-parameter hint means by reporting an error instead of silently using 0.</p>
<p>Vacant and deleted are different operations even though they read the same at run time. Clearing a
cell keeps the key with a null value; the delete button drops the key. The distinction survives a save,
because a vacant entry is written as an element carrying an id and no value while a deleted one is
written as nothing at all — so a vacant row comes back as a row, and a deleted one does not come back.</p>
<h2 id="the-editor-body">The Editor Body</h2>
<p>Above the table sits a one-line hint, and it is the only prose either panel shows. Both render the
literal text <code>&lt;vacant&gt;</code> inside it as inline code.</p>
<ul>
<li>Retained Common Variables: <em>&quot;Retained macro variables #500#999 (power-off safe). Empty =
<code>&lt;vacant&gt;</code>. #100#499 are volatile and live in the run's dataflow, not here.&quot;</em></li>
<li>R Parameters: <em>&quot;Sinumerik R parameters R0R999 (retentive). Empty = <code>&lt;vacant&gt;</code> — a program reading
a vacant R parameter reports an error instead of silently using 0.&quot;</em></li>
</ul>
<p>The table below it has three columns. The first is the key — headed <strong>Variable</strong> or <strong>Parameter</strong>
rendered bold as plain text and not editable, which is the branch's rule for a key column. The second
is headed <strong>Value</strong> and holds the shared numeric field. The third carries the row's delete button
under a blank header, so the branch's shared <strong>Actions</strong> heading does not appear on either leaf.</p>
<h3 id="editing-a-value">Editing a value</h3>
<p>A value cell is the shared numeric widget, so it commits on blur or on Enter and never per keystroke;
its parsing, its bound behaviour and the double commit an Enter can cause are
<a class="xref" href="../../../widget/numeric-input.html">Numeric Input</a>. No value cell is bounded — neither panel passes a minimum
or a maximum to it — so any finite number is accepted and stored, negatives included.</p>
<p><strong>These two panels are the branch's only pair that commits a null.</strong> Every other numeric handler in
the branch treats a cleared cell as no edit and returns before the request; here the null is sent,
because vacating an entry is a real state of both tables and there is no other control that reaches
it. Clearing the cell is therefore how a row is made vacant while keeping its key.</p>
<p>The handler is otherwise the branch's ordinary optimistic write — assign, send, and on failure put the
captured value back and raise the toast — and it carries no finiteness test. The widget parses
<code>Infinity</code> and <code>-Infinity</code> as values, and neither survives JSON encoding: the request body reaches the
server carrying a null, and the entry is set vacant. The cell keeps showing <code>Infinity</code> until the panel
is remounted, so that is the one place in the table body where the screen and the store disagree
after a write the server accepted. A typed <code>NaN</code> takes the same route to vacant, and the cell renders
it as empty, which is what a vacant entry looks like anyway.</p>
<h3 id="adding-and-removing-a-row">Adding and removing a row</h3>
<p>The footer under the table is the branch's fielded add form: a numeric field for the id — labelled
<strong>Variable # (500999)</strong> or <strong>Parameter # (0999)</strong> — a second numeric field labelled <strong>Value</strong>, and a
primary <strong>Add / Set</strong> button. Both fields are the same numeric widget as the value cells, so what they
hold reaches the panel on blur or Enter rather than per keystroke, and neither input submits on Enter.</p>
<p>The button label's second word is the accurate one: the write is an upsert. Entering an id the table
already lists overwrites that row's value rather than adding a second row. Leaving <strong>Value</strong> empty
creates the row vacant. A successful add is followed by a full re-read of the table, which is what
puts the new row in ascending order; the two fields keep what was typed in them.</p>
<p>Two refusals sit in front of that write, and they read differently. An id outside the field's own
bounds is rejected inside the widget, which shows its message under the box, emits nothing and leaves
the rejected text on screen — so the panel still holds the id the field last committed to it. On a
panel that has committed none, that is nothing at all, and pressing <strong>Add / Set</strong> reports the panel's
own message: <em>&quot;Variable number must be an integer in 500999.&quot;</em> or <em>&quot;Parameter number must be an
integer in 0999.&quot;</em> The panel's message is also what a fractional id produces, since the panel
requires a whole number and the field does not.</p>
<p>A field that has already committed a valid id keeps it, and nothing clears either field after an add.
So an out-of-range id typed over one the field already accepted is the second place on these leaves
where the screen and the store part company: the box shows the rejected number, the panel still holds
the earlier id, and <strong>Add / Set</strong> upserts that earlier id with whatever the value field holds — no
message of any kind, and the re-read that follows leaves the rejected number in the box.</p>
<p>Removal is one icon button per row, with no label, no tooltip and no confirmation dialog: the click
sends the delete, and the row is filtered out of the panel's list once the request resolves. The branch
guards some of its deletions with a confirmation dialog; neither of these two leaves is among them.
The endpoint removes the key and reports success whether or not the key was there.</p>
<h2 id="what-the-surface-covers">What the Surface Covers</h2>
<p>A third table of the same shape exists in the engine and has no node.
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Heidenhain.HeidenhainQParameterTable.html">HeidenhainQParameterTable</a> holds the Heidenhain persistent
Q parameters — the free range <code>Q0</code><code>Q99</code> and the permanent <code>QR0</code><code>QR499</code>, in two stores of its own —
and the Heidenhain preset carries a proxy for it beside the two documented above. It is materialised
into the project's per-case list exactly as they are, is serialized into the project file the same way,
is swept by a brand switch away from Heidenhain the same way, and is read and written during a run by
the Heidenhain reading syntax that routes <code>Qn</code> and <code>QRn</code> assignments by id range.</p>
<p>What it has no part in is this branch. The snapshot the tree is built from declares no flag that
probes it, the branch's REST surface declares no route that reaches it, and the branch builder mints
no node for it. The Controller branch's editable persistent-variable surface is the two tables named
above; a Heidenhain project's Q parameters are set and read by the program that runs, and are carried
by the project file between runs.</p>
<h2 id="layout">Layout</h2>
<ul>
<li><strong>General Setup</strong> Control Tree — the left dock of <code>/general-setup</code>
<ul>
<li><strong>Controller</strong> Node Row
<ul>
<li><strong>Program Data</strong> Group Row — <code>equipment/controller/program-data</code>
<ul>
<li><strong>Retained Common Variables</strong> Node Row —
<code>equipment/controller/program-data/retained-variables</code>; grown while the snapshot reports a
retained common variable table</li>
<li><strong>R Parameters</strong> Node Row — <code>equipment/controller/program-data/r-parameters</code>; grown while the
snapshot reports a Siemens R-parameter table</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>Editor Row — the panel of whichever node is selected
<ul>
<li><strong>Retained Common Variables</strong> Panel
<ul>
<li>No-Runner Body — the shared <em>&quot;No NC runner — load a project first.&quot;</em>; shown first</li>
<li>Absent-Table Body — <em>&quot;No retained common variable table on the active runner.&quot;</em></li>
<li>Editor Body
<ul>
<li>Hint Caption — <em>&quot;Retained macro variables #500#999 (power-off safe). Empty = <code>&lt;vacant&gt;</code>.
#100#499 are volatile and live in the run's dataflow, not here.&quot;</em></li>
<li>Variable Table — dense, flat, bordered
<ul>
<li>Header Row — <strong>Variable</strong>, <strong>Value</strong>, and a blank third heading</li>
<li>Variable Row, one per stored id
<ul>
<li>Variable Number Label — bold plain text, <code>#</code> then the number, not editable</li>
<li>Value Numeric Field — unbounded; cleared commits vacant</li>
<li>Delete Button — a bin icon, flat and dense, no label and no confirmation</li>
</ul>
</li>
</ul>
</li>
<li>Add Row Footer
<ul>
<li><strong>Variable # (500999)</strong> Numeric Field — minimum 500, maximum 999</li>
<li><strong>Value</strong> Numeric Field — unbounded; left empty adds the row vacant</li>
<li><strong>Add / Set</strong> Button — primary, unelevated</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li><strong>R Parameters</strong> Panel — the same shell with the Siemens strings
<ul>
<li>No-Runner Body — the shared <em>&quot;No NC runner — load a project first.&quot;</em></li>
<li>Absent-Table Body — <em>&quot;No R-parameter table on the active runner.&quot;</em></li>
<li>Editor Body
<ul>
<li>Hint Caption — <em>&quot;Sinumerik R parameters R0R999 (retentive). Empty = <code>&lt;vacant&gt;</code> — a program
reading a vacant R parameter reports an error instead of silently using 0.&quot;</em></li>
<li>Parameter Table — dense, flat, bordered
<ul>
<li>Header Row — <strong>Parameter</strong>, <strong>Value</strong>, and a blank third heading</li>
<li>Parameter Row, one per stored id
<ul>
<li>Parameter Number Label — bold plain text, <code>R</code> then the number, not editable</li>
<li>Value Numeric Field — unbounded; cleared commits vacant</li>
<li>Delete Button — a bin icon, flat and dense, no label and no confirmation</li>
</ul>
</li>
</ul>
</li>
<li>Add Row Footer
<ul>
<li><strong>Parameter # (0999)</strong> Numeric Field — minimum 0, maximum 999</li>
<li><strong>Value</strong> Numeric Field — unbounded; left empty adds the row vacant</li>
<li><strong>Add / Set</strong> Button — primary, unelevated</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>Neither panel opens a dialog, and neither carries a select, a toggle or a <strong>Show all</strong> control.</p>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../../../index.html">HiNC App Anatomy</a> for git repository links.</p>
<h3 id="web-application">Web Application</h3>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/controlTree/SoftNcRetainedVariablesPanel.vue</code> — the <code>#500</code><code>#999</code>
ledger: the hint with its vacant slot, the three-column table, the value handler that sends a null
without a finiteness test, the unconfirmed delete, and the add form with its whole-number and range
check.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcRParametersPanel.vue</code> — the <code>R0</code><code>R999</code> ledger: the
same shell with the Siemens strings, the <code>R</code>-prefixed key cell, and the add field bounded from 0.</li>
<li><code>wwwroot-src/src/components/controlTree/softNcItemTypes.ts</code> — the branch builder: the two node ids,
their labels and label keys, the two flags each is pushed behind, and the item types binding them to
the two panels.</li>
<li><code>wwwroot-src/src/api/softNcRunner.ts</code> — the two readers, their identical row shape with a nullable
value, the two setters and the two removers, and the two snapshot flags the builder tests.</li>
<li><code>wwwroot-src/src/api/http.ts</code> — the helper that turns a success-flagged failure body into a thrown
error, which is how an out-of-range write would reach a panel.</li>
<li><code>wwwroot-src/src/components/widgets/NumericInput.vue</code> — the value and add fields: commit on blur or
Enter, the empty-text-to-null parse both panels forward, the infinity and NaN literals it also
accepts, and the bound rejection that shows a message and emits nothing.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcEmptyState.vue</code> — the shared no-runner line both
panels open with.</li>
<li><code>wwwroot-src/src/composables/useSoftNcRunner.ts</code> — the shared snapshot the first empty layer reads,
and the events that re-read it.</li>
<li><code>wwwroot-src/src/i18n/en/softNc.ts</code> — the two node labels, the two hints, the two absent-table
bodies, the two add-field labels, the two invalid-id messages and the error contexts each panel
prefixes its toast with.</li>
<li><code>wwwroot-src/src/i18n/en/common.ts</code> — the shared <strong>Value</strong> column label and the <strong>Add / Set</strong> button
label.</li>
<li><code>Mech/SoftNcRunnerController.cs</code> — the REST surface: the two flag probes in the snapshot builder,
the two reads that order rows by ascending id and report presence from the table alone, the two
upsert writers with their range guards, the two removers, and the sweep of per-case tables the new
runner references through no proxy.</li>
</ul>
<h3 id="hiapi-engine">HiAPI Engine</h3>
<ul>
<li><code>HiMech/NcParsers/Dependencys/Fanuc/RetainedCommonVariableTable.cs</code> — the <code>#500</code><code>#999</code> store: the
range constants, the nullable dictionary that makes vacant a value, the writer that ignores an
out-of-range id, the variable lookup that answers only in range, and the serialization that keeps a
vacant entry as a valueless element.</li>
<li><code>HiMech/NcParsers/Dependencys/Fanuc/RetainedCommonVariableTableProxy.cs</code> — the seedless
get-or-create placeholder the three Fanuc-style presets carry, the bare table it installs into a
project holding none, and the legacy element name an older project file still deserializes through.</li>
<li><code>HiMech/NcParsers/Dependencys/Siemens/SiemensRParameterTable.cs</code> — the <code>R0</code><code>R999</code> store: the range
constants and the reason the upper one is 999, and the lookup that accepts an uppercase or lowercase
<code>R</code> key.</li>
<li><code>HiMech/NcParsers/Dependencys/Siemens/SiemensRParameterTableProxy.cs</code> — the matching seedless
placeholder carried by the Siemens preset alone.</li>
<li><code>HiMech/NcParsers/Dependencys/Heidenhain/HeidenhainQParameterTable.cs</code> — the third table of the same
shape: the free and permanent Q stores, the ranges each accepts, and the system and volatile ranges
it deliberately declines.</li>
<li><code>HiMech/NcParsers/Dependencys/Heidenhain/HeidenhainQParameterTableProxy.cs</code> — its placeholder in the
Heidenhain preset, seedless like the other two.</li>
<li><code>HiMech/NcParsers/SoftNcRunner.cs</code> — the five brand presets as literal dependency lists, which is
where the mutual exclusion of the two nodes is actually decided, and the proxy resolution every flag
probe and every read goes through.</li>
<li><code>HiMech/NcParsers/NcRunnerSuit.cs</code> — the per-case list these tables live in, and its inline
serialization beside the runner.</li>
<li><code>HiMech/NcParsers/Dependencys/INcDependencyProxy.cs</code> — the maker-and-taker contract, and why a
proxy's resolved table is never written into the runner file.</li>
<li><code>HiMech/NcParsers/ISessionResettable.cs</code> — the session-scoped contract neither table implements, and
its own statement of the boundary between session state and persistent state.</li>
<li><code>HiMech/NcParsers/Dependencys/IPowerResettable.cs</code> — the volatile-subset contract a power reset
sweeps, which no dependency of any brand preset declares.</li>
<li><code>HiMech/NcParsers/LogicSyntaxs/ProgramEndCleanSyntax.cs</code> — the program-end clear that empties the
block's volatile dictionary and names the retained range as untouched.</li>
<li><code>HiMech/NcParsers/EvaluationSyntaxs/VolatileVariableReadingSyntax.cs</code> — the <code>#100</code><code>#499</code> range that
has no node here: the per-block dictionary it carries forward, and the session that bounds it.</li>
<li><code>HiMech/NcParsers/Dependencys/Fanuc/FanucPositionVariableLookup.cs</code> and
<code>HiMech/NcParsers/EvaluationSyntaxs/Fanuc/FanucSystemControlVariableSyntax.cs</code> — the two <code>#1000</code>-and-up
groups no dependency answers: the position variables read from the previous block's record, and the
system-control writes recorded on the block rather than emulated.</li>
<li><code>HiMech/NcParsers/EvaluationSyntaxs/RetainedCommonVariableReadingSyntax.cs</code> — the run-time writer for
the retained range: the literal assignment it consumes and writes straight into the table, with no
mirror kept.</li>
<li><code>HiMech/NcParsers/EvaluationSyntaxs/Siemens/SiemensRParameterReadingSyntax.cs</code> — the same shape for
<code>Rn</code> assignments.</li>
<li><code>HiMech/NcParsers/EvaluationSyntaxs/Heidenhain/HeidenhainQParameterReadingSyntax.cs</code> — the same shape
for <code>Qn</code> and <code>QRn</code>, routing by id range into the free store, the permanent store, a read-only warning
or the volatile dataflow.</li>
<li><code>HiMech/NcParsers/EvaluationSyntaxs/VariableEvaluatorSyntax.cs</code> — the normaliser that resolves a
non-literal right-hand side to a literal before the reading syntaxes run, and the lookup chain these
tables join.</li>
<li><code>HiMech/NcParsers/EvaluationSyntaxs/Evaluation/IVariableLookup.cs</code> and
<code>HiMech/NcParsers/EvaluationSyntaxs/Evaluation/NcExpressionEvaluator.cs</code> — the lookup contract both
tables implement, and the vacant failure an expression raises instead of reading zero.</li>
<li><code>HiMech/NcParsers/Syntaxs/FanucSyntaxUtil.cs</code>,
<code>HiMech/NcParsers/Syntaxs/SyntecSyntaxUtil.cs</code>,
<code>HiMech/NcParsers/Syntaxs/MazakSyntaxUtil.cs</code>,
<code>HiMech/NcParsers/Syntaxs/SiemensSyntaxUtil.cs</code> and
<code>HiMech/NcParsers/Syntaxs/HeidenhainSyntaxUtil.cs</code> — the five syntax lists, each carrying the reading
syntax for its brand's persistent variable range.</li>
<li><code>HiNc/MachiningProcs/LocalProjectService.cs</code> — the facade setter every install and brand switch
assigns through, and the power reset that sweeps volatile subsets and then drops the session
dataflow.</li>
<li><code>HiNc/MachiningProcs/SessionShell.cs</code> — the scriptable power-reset entry point over that service
call.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="index.html">Program Data Plane</a> — the plane both leaves sit on: where their tables are stored,
the seedless proxies that make them, and what a brand switch keeps or sweeps beside them</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,596 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Siemens Tool Offsets | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Siemens Tool Offsets | HiAPI-C# 2025 ">
<link rel="icon" href="../../../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../../../public/docfx.min.css">
<link rel="stylesheet" href="../../../../public/main.css">
<meta name="docfx:navrel" content="../../../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../../../index.html">
<img id="logo" class="svg" src="../../../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-ControllerSiemensToolOffsets">
<h1 id="siemens-tool-offsets">Siemens Tool Offsets</h1>
<p>Siemens Tool Offsets is the Sinumerik <code>$TC_DP</code> ledger — one offset row per cutting edge of a tool,
addressed by the tool number and the edge number together — plus the tool-name map that resolves a
string tool call to a tool number. It occupies two leaves of the Controller branch on the General
Setup page at <code>/general-setup</code>, under the Control-Tree ids
<code>equipment/controller/program-data/siemens-tool-offsets</code> and
<code>equipment/controller/program-data/tool-names</code>, and those two ids are the whole of what this page
owns. Both are grown by one snapshot flag over one backing object, which is why one page carries
them.</p>
<div class="IMPORTANT">
<h5>Important</h5>
<p>Anatomy makes no claim that any id named here is stable across versions. The migration hop that
keeps an older <code>?tree=</code> value resolving is described in
<a class="xref" href="../../../platform/tree-ids-and-routes.html">Tree Ids and Routes</a>.</p>
</div>
<p>Both panels follow the branch's shared rules — one read on mount, per-control commit, the
capture-assign-await-restore write, the two empty layers, and a dense markup table with no sort and
no pagination. Those rules, and the places these two panels depart from them, are
<a class="xref" href="../editing-contract.html">Editing Contract</a>. What is these leaves' own is everything
below.</p>
<h2 id="two-nodes-over-one-table">Two Nodes Over One Table</h2>
<p>The tree labels differ, the panels differ, and the object behind them does not.</p>
<table>
<thead>
<tr>
<th>Node id</th>
<th>Tree label</th>
<th>Which half of the object</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>equipment/controller/program-data/siemens-tool-offsets</code></td>
<td><strong>Tool Offsets ($TC_DP)</strong></td>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Siemens.SiemensToolOffsetTable.html#Hi_NcParsers_Dependencys_Siemens_SiemensToolOffsetTable_Edges">Edges</a> — the offset rows</td>
</tr>
<tr>
<td><code>equipment/controller/program-data/tool-names</code></td>
<td><strong>Tool Names</strong></td>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Siemens.SiemensToolOffsetTable.html#Hi_NcParsers_Dependencys_Siemens_SiemensToolOffsetTable_ToolNames">ToolNames</a> — the name map</td>
</tr>
</tbody>
</table>
<p>The branch builder pushes both inside a single test of the snapshot's Siemens tool-offset flag, so
they appear and disappear together and there is no separate tool-name flag anywhere in the snapshot.
The flag itself is a type probe for <a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Siemens.SiemensToolOffsetTable.html">SiemensToolOffsetTable</a>
over the proxy-resolved dependency list, and the Siemens preset is the only shipped brand list that
carries the proxy for it — <a class="xref" href="../brand-matrix.html">Brand Matrix</a>.</p>
<p>The same flag does one more thing: it relabels the ungated generic offset leaf one row above from
<strong>Tool Offsets</strong> to <strong>Tool Offsets (ISO G43 H)</strong>, without touching that leaf's id. The two ledgers
are present together on a Siemens project and an unqualified label would blur them into one — see
<a class="xref" href="tool-offsets.html">Tool Offsets</a>.</p>
<p>Each panel reads only its own half. The <code>$TC_DP</code> panel reads and writes rows; the Tool Names panel
reads and writes name mappings; neither reports the other's edits, and neither re-reads when the
other writes. The presence flag both receive is the same one — whether the table object resolved —
so the Tool Names panel's absent-table line, <em>&quot;No Siemens tool-name table on the active runner.&quot;</em>,
names a table that is not a type of its own.</p>
<h2 id="the-key-has-two-parts">The Key Has Two Parts</h2>
<p><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Siemens.ISiemensToolOffsetConfig.html">ISiemensToolOffsetConfig</a> addresses an offset by a <strong>tool
number</strong> and a <strong>cutting edge number</strong> together, where
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.IToolOffsetConfig.html">IToolOffsetConfig</a> — the contract behind the generic ledger — takes a
single integer. That is the structural difference between the two ledgers, and it is why one tool can
carry several rows: a tool with more than one usable edge stores an offset per edge, and the program
selects among them with the <code>D</code> word after the tool is mounted.</p>
<p>The pair is the dictionary key, and both halves are rendered as bold plain text with a hard-coded
letter prefix — <code>T</code> on the tool cell and <code>D</code> on the edge cell. Neither is editable and no rename
route exists on either half of the object, so a key cannot be corrected in place. Neither can a
chosen one be asked for: the <code>$TC_DP</code> add accepts no target pair — it mints the next tool, or the
next edge of the tool it is given — so arriving at a particular pair means growing the table up to it
and deleting what was stepped through. Only the tool-name half takes a typed key. That same
server-side minting is why nothing on this leaf can create a duplicate.</p>
<p>An unconfigured pair is not an error. The interface contract returns 0 for a pair the table has no
row for, and the try-form is the only reliable miss signal, because 0 is itself a legal stored
offset — a distinction that decides the fallback below.</p>
<h2 id="the-columns">The Columns</h2>
<p>Ten labelled columns: the two key columns, then eight value columns in millimetres over the four
geometry components and their four wear counterparts.</p>
<table>
<thead>
<tr>
<th>Column header</th>
<th><code>$TC_DP</code> index</th>
<th>Stored as</th>
<th>Read by the pipeline</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Tool T</strong></td>
<td></td>
<td>the key's tool number</td>
<td>the tool the offsets belong to</td>
</tr>
<tr>
<td><strong>Edge D</strong></td>
<td></td>
<td>the key's edge number</td>
<td>the <code>D</code> word</td>
</tr>
<tr>
<td><strong>Length 1 (Z)</strong></td>
<td>3</td>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Siemens.SiemensToolEdgeOffset.html#Hi_NcParsers_Dependencys_Siemens_SiemensToolEdgeOffset_Length1_mm">Length1_mm</a></td>
<td>the height path</td>
</tr>
<tr>
<td><strong>Length 2 (X)</strong></td>
<td>4</td>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Siemens.SiemensToolEdgeOffset.html#Hi_NcParsers_Dependencys_Siemens_SiemensToolEdgeOffset_Length2_mm">Length2_mm</a></td>
<td>nothing</td>
</tr>
<tr>
<td><strong>Length 3 (Y)</strong></td>
<td>5</td>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Siemens.SiemensToolEdgeOffset.html#Hi_NcParsers_Dependencys_Siemens_SiemensToolEdgeOffset_Length3_mm">Length3_mm</a></td>
<td>nothing</td>
</tr>
<tr>
<td><strong>Radius</strong></td>
<td>6</td>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Siemens.SiemensToolEdgeOffset.html#Hi_NcParsers_Dependencys_Siemens_SiemensToolEdgeOffset_Radius_mm">Radius_mm</a></td>
<td>nothing</td>
</tr>
<tr>
<td><strong>Length 1 Wear</strong></td>
<td>12</td>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Siemens.SiemensToolEdgeOffset.html#Hi_NcParsers_Dependencys_Siemens_SiemensToolEdgeOffset_WearLength1_mm">WearLength1_mm</a></td>
<td>the height path</td>
</tr>
<tr>
<td><strong>Length 2 Wear</strong></td>
<td>13</td>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Siemens.SiemensToolEdgeOffset.html#Hi_NcParsers_Dependencys_Siemens_SiemensToolEdgeOffset_WearLength2_mm">WearLength2_mm</a></td>
<td>nothing</td>
</tr>
<tr>
<td><strong>Length 3 Wear</strong></td>
<td>14</td>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Siemens.SiemensToolEdgeOffset.html#Hi_NcParsers_Dependencys_Siemens_SiemensToolEdgeOffset_WearLength3_mm">WearLength3_mm</a></td>
<td>nothing</td>
</tr>
<tr>
<td><strong>Radius Wear</strong></td>
<td>15</td>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Siemens.SiemensToolEdgeOffset.html#Hi_NcParsers_Dependencys_Siemens_SiemensToolEdgeOffset_WearRadius_mm">WearRadius_mm</a></td>
<td>nothing</td>
</tr>
</tbody>
</table>
<p>The table declares accessors for all three length components and for the radius, and the shipped
pipeline calls exactly one of them: the try-form that returns length 1 plus its wear. The radius
accessor and the direction-indexed length accessor are called by nothing in it. Radius compensation
reads the <strong>generic</strong> ledger's radius by a single offset number, so a radius entered here changes
nothing that plays. Length 2 and Length 3 are stored, serialized and handed back unchanged, and no
consumer reads them.</p>
<p>The axis letters in the three length headers are part of the header string and not a binding. Length
1 is the component the height path consumes whatever axis the machine calls it; the other two are
indexed positionally by an accessor nothing calls.</p>
<p>There is no unit column and no conversion — every field is a plain millimetre double, written and
read back as typed. None of the eight numeric cells passes a bound, so a negative value is accepted
and stored, which the sign convention below makes load-bearing rather than incidental.</p>
<h3 id="the-fields-the-row-carries-but-does-not-interpret">The fields the row carries but does not interpret</h3>
<p>A row also holds <a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Siemens.SiemensToolEdgeOffset.html#Hi_NcParsers_Dependencys_Siemens_SiemensToolEdgeOffset_VerbatimDpFields">VerbatimDpFields</a>, a
bag keyed by <code>$TC_DP</code> index for every index outside the eight above. Nothing interprets those values
and no cell edits them; they exist so that a full controller dump survives a load and a save
unchanged. The panel shows only their count, as a small grey <strong>+N</strong> beside the row's action buttons
with a tooltip explaining that the fields are stored and saved with the project and not used by the
simulation. The row write sends the whole row object including that count, and the endpoint assigns
only the eight doubles, so the bag survives every edit made here.</p>
<p>The two halves stay separate on both sides of serialization. Writing an index into the row routes a
consumed index onto its typed property and every other index into the bag, and the serializer filters
the consumed indices back out of the bag before writing, so an index cannot end up stored twice and
shadow its own typed value on the next load. The element the table writes carries one <code>Edge</code> entry
per key with the eight components as named attributes and the bag as <code>DP</code>-indexed ones, and one
<code>ToolName</code> entry per mapping; the reader accepts both spellings of a component, so a <code>DP</code>-indexed
geometry value lands on the typed property rather than in the bag.</p>
<h2 id="the-sign-convention">The Sign Convention</h2>
<p><strong>On this table an effective value is the geometry plus the wear.</strong> The height the pipeline reads is
length 1 added to length-1 wear, and the radius and per-direction accessors add their wear the same
way. A positive wear therefore lengthens the compensation, and a tool that has worn shorter is
recorded as a <strong>negative</strong> wear value. The panel states the addition twice: the description above the
table ends <em>&quot;Wear adds onto geometry (enter a shortened tool as negative wear).&quot;</em>, and the footnote
below it reads <em>&quot;All values in mm. Effective length 1 = Length 1 (Z) + Length 1 Wear; same for the
other columns.&quot;</em></p>
<div class="WARNING">
<h5>Warning</h5>
<p><strong>The generic ledger one node above uses the opposite convention.</strong> On
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Generic.ToolOffsetTable.html">ToolOffsetTable</a> an effective value is the ideal <strong>minus</strong>
the wear — <a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Generic.ToolOffsetRow.html#Hi_NcParsers_Dependencys_Generic_ToolOffsetRow_FullHeight_mm">FullHeight_mm</a> is the ideal height
less the axial wear and <a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Generic.ToolOffsetRow.html#Hi_NcParsers_Dependencys_Generic_ToolOffsetRow_FullRadius_mm">FullRadius_mm</a> the
ideal radius less the radial wear — and that panel states no sign rule at all. On a Siemens project
both leaves are present at once, so the same wear number typed into the two ledgers moves the tool
in opposite directions. See <a class="xref" href="tool-offsets.html">Tool Offsets</a>, which carries this
warning from the other side.</p>
</div>
<p>The two conventions are not a defect to reconcile: each matches the control whose vocabulary its
table borrows. What makes the pair sharp is that they meet — the fallback below reads a height out of
the subtracting table and uses it where an adding table's height was expected.</p>
<h2 id="how-a-d-word-resolves">How a D Word Resolves</h2>
<p>The Siemens height path consumes a standalone <code>D</code> word from the block and writes the same downstream
state as the ISO <code>G43</code> path: a tool-height compensation section marked with the term <code>D</code>, and a
translation of the effective height composed into the program-to-machine transform chain. Both
brands' paths compose that entry under one source key and replace it in place, so the two are
mutually exclusive on a file that mixes the dialects.</p>
<p>Resolution runs in this order.</p>
<ol>
<li><strong><code>D0</code> cancels.</strong> The section is written with an offset of 0 and an identity transform, with no
table lookup and no diagnostic.</li>
<li><strong>The active tool number comes from the block's tool-change section</strong>, or from the previous
block's when this one carries none. A numeric tool id is taken as it stands.</li>
<li><strong>A string tool id is resolved through the tool-name map</strong> on this same table. An unmapped name
raises an unsupported warning and leaves the tool number at 0.</li>
<li><strong>The <code>(T, D)</code> pair is looked up here.</strong> A configured row answers with length 1 plus its wear.</li>
<li><strong>A pair with no row falls back to the generic ledger</strong>, described next.</li>
</ol>
<p>A block that carries no <code>D</code> word re-resolves the previous block's edge modally, and only while that
previous section is still owned by the <code>D</code> term; a section already claimed by an ISO term on the same
block, or carried modally from one, is left to the ISO path. The modal re-resolve deliberately raises
no diagnostic — the warnings below are attached to blocks that spell a <code>D</code> word out, so a single
missing row reports once instead of on every block that inherits it.</p>
<h3 id="what-a-missing-row-falls-back-to">What a missing row falls back to</h3>
<p>For a <code>(T, D)</code> pair this table has no row for — and equally when no Siemens offset table resolves at
all — the height is taken from the <strong>generic</strong> tool-offset table's height for the tool number,
through the single-integer contract the ISO <code>G43 H</code> path already reads. Every Siemens runner carries
that table, so the fallback always has a source. It exists so that an unfilled per-case <code>$TC_DP</code>
table follows the tool-house-fed ledger instead of resolving 0 and machining the whole program one
tool length low.</p>
<p>Both arms report. A fallback that finds a usable height raises a configuration warning naming the
missing pair and the millimetre value it used instead. A fallback that finds <code>NaN</code> — the sentinel the
generic ledger's tool-house refresh writes for a tool whose tip length does not resolve — raises a
configuration warning of its own and degrades to 0 rather than composing a non-finite translation.
Both carry the code <code>SiemensToolOffset--TcdpRowMissing</code>.</p>
<p>The fallback crosses the sign boundary. What it reads out of the generic table is that table's own
effective height, ideal minus wear; what it substitutes is a <code>$TC_DP</code> height, geometry plus wear. A
project that keeps wear in both places therefore compensates in one direction while the row exists
here and in the other once it does not.</p>
<p>An unresolved tool name reaches the same fallback with the tool number left at 0, and the generic
table's own panel cannot create a row 0 — its tool-number cell takes a minimum of 1 and refuses
anything below it, and its add route mints from 1 upward — so the height resolves to 0.</p>
<h2 id="what-the-tool-name-map-is-for">What the Tool-Name Map Is For</h2>
<p>Siemens programs may name a tool as a string rather than a number, as in the <code>T=&quot;D16R3Z6&quot;</code> form the
panel's own hint quotes. The act chain the simulation builds is keyed by integer tool id, so the
string has to become a number before anything can use it, and this map is the only surface in the
engine that performs that translation. Lookup is case-insensitive: the map is constructed with an
ordinal case-insensitive comparer.</p>
<p>Two consumers read it, and they fail differently.</p>
<ul>
<li><strong>The tool change itself.</strong> The tool-change semantic resolves a string tool id through this map
before it emits a tooling step. An unmapped name raises a <code>ToolChange--NameUnresolved</code> warning and
emits <strong>no step at all</strong>, so the tool change is not simulated rather than simulated with the wrong
tool.</li>
<li><strong>The <code>D</code> word.</strong> The height path resolves the same name for its own <code>(T, D)</code> lookup and, on a
miss, raises <code>SiemensToolOffset--ToolUnresolved</code> and carries on with tool number 0.</li>
</ul>
<p>Both consumers reach the map through the concrete Siemens table rather than through an interface, so
a runner that resolves no such table resolves no tool name either, whatever its brand marker says.</p>
<p>The map's own panel is a two-column table over a name and a tool number. The name is the key and is
rendered as bold plain text; the tool number is a numeric cell with a minimum of 1. Row order is the
server's — the read sorts the keys case-insensitively. The panel never sorts, and its add path
re-reads the whole table rather than appending a row locally.</p>
<p>Its footer is an <strong>Add / Set</strong> pair of fields, and the verb is both: the write is an upsert keyed on
the name. Entering a name that already exists overwrites that mapping's number instead of adding a
second row. Because the underlying dictionary keeps the key it already holds when a value is
assigned, a name entered in different casing updates the existing row and the re-read redisplays the
casing that was stored first.</p>
<h2 id="row-life-cycle">Row Life Cycle</h2>
<p>The two leaves spell adding and deleting differently, and the difference is which side mints the key.</p>
<p><strong>The <code>$TC_DP</code> table's adds are fieldless toolbar and row buttons, because the server mints both
halves of the key.</strong> <strong>Add Tool</strong> in the toolbar sends no tool number and the endpoint takes the
highest tool present plus one — or 1 on an empty table — with edge 1. The per-row button, tooltipped
<em>&quot;Add a cutting edge for T{tool}&quot;</em>, sends that row's tool number and the endpoint takes that tool's
highest edge plus one. Neither half of the key fills a gap: a deleted middle tool or edge is stepped
over rather than reused. Either way the panel appends a locally zeroed row for the returned pair and
re-sorts by tool then edge; those zeros mirror the fresh row the server actually inserted rather than
standing in as a placeholder.</p>
<p><strong>The Tool Names table's add is the footer pair described above</strong>, since its key is typed rather than
minted. A blank name is refused before any request with <em>&quot;Tool name must not be empty.&quot;</em>, and a
number that is not a positive integer with <em>&quot;Tool # must be a positive integer.&quot;</em>; a successful write
clears both fields and re-reads the table. The endpoint refuses a blank name a second time.</p>
<p>Deletion is guarded on one leaf and not the other. A <code>$TC_DP</code> row opens a confirmation dialog first —
<strong>Remove tool offset row</strong> over <em>&quot;Remove the offset row for (T{tool}, D{edge})?&quot;</em> — and the row is
filtered out locally once the request resolves; deleting a pair the table no longer holds answers
unsuccessfully with a message naming it. A tool name deletes on the click with no dialog and no undo,
and a name the map does not hold answers unsuccessfully with a message naming it.</p>
<p>Cell edits differ from both. Every numeric cell on the <code>$TC_DP</code> table sends the <strong>whole row</strong> — all
eight components — and rolls back only the one cell it captured on failure. The Tool Names number
cell sends the mapping it belongs to. Neither panel commits a cleared cell: an emptied field parses
to null and the handler returns before the request, so the box is left blank on screen while the
stored number stands and returns on the next remount. The tool-number cell additionally drops a
non-integer silently, while a value below 1 is refused inside the numeric widget itself, which marks
the box and emits nothing.</p>
<h2 id="what-materializes-the-table">What Materializes the Table</h2>
<p>Three of the writes re-wire the runner suit's dependency proxies before resolving the table: the
<code>$TC_DP</code> row write, the <code>$TC_DP</code> add, and the tool-name write. The proxy is a get-or-create
placeholder, so that wiring installs a bare table into the project's per-case list when it holds none,
and the write then lands on a real table instead of answering that one is missing. The reads and the
two deletes do not re-wire.</p>
<p>Neither panel reports a structural change and neither refreshes the shared snapshot, so a table
created by one of those writes does not re-evaluate the flag or regrow the branch. In practice the
guard never fires from these leaves: the suit wires its proxies when it is deserialized and on every
runner assignment, and the nodes are built only after the flag already read true.</p>
<h2 id="when-the-tables-are-absent">When the Tables Are Absent</h2>
<p>Both leaves carry the branch's two empty layers. The shared one renders <em>&quot;No NC runner — load a
project first.&quot;</em> while the snapshot reports no runner. Each panel's own read then gates on whether the
Siemens table resolved, and renders <em>&quot;No Siemens tool-offset table on the active runner.&quot;</em> or <em>&quot;No
Siemens tool-name table on the active runner.&quot;</em> when it did not.</p>
<p>That second layer is not reachable by navigating, because the flag that grows either node is the same
probe the reads answer with. It is a live guard for a table that goes away under a mounted panel. A
failed write answers inside a success envelope, and the sentence it carries depends on the route. The
four that resolve the table through the shared dependency helper — the row upsert, the row delete and
the two tool-name routes — answer with the type name, <code>No SiemensToolOffsetTable on the active runner</code>; the <code>$TC_DP</code> add builds its own envelope instead and answers word for word with the
<code>$TC_DP</code> panel's own absent-table line. Either way the panel shows its localized context followed by
the sentence the server sent.</p>
<p>The table is stored on the project rather than in the runner file. It reaches the pipeline as a
seedless proxy in the Siemens preset's dependency list and resolves to the concrete table on the
project's per-case list, so a controller file saved from the branch root carries none of these rows.
A brand switch away from Siemens removes it along with the two nodes, and a switch back creates an
empty one rather than restoring what was there —
<a class="xref" href="index.html">Program Data Plane</a>.</p>
<h2 id="layout">Layout</h2>
<ul>
<li><strong>General Setup</strong> Control Tree — the left dock of <code>/general-setup</code>
<ul>
<li><strong>Controller</strong> Node Row
<ul>
<li><strong>Program Data</strong> Group Row
<ul>
<li><strong>Tool Offsets ($TC_DP)</strong> Node Row — <code>equipment/controller/program-data/siemens-tool-offsets</code></li>
<li><strong>Tool Names</strong> Node Row — <code>equipment/controller/program-data/tool-names</code></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>Editor Row — the panel of whichever node is selected
<ul>
<li><strong>Tool Offsets ($TC_DP)</strong> Panel
<ul>
<li>No-Runner Body — <em>&quot;No NC runner — load a project first.&quot;</em>; shown instead of everything below</li>
<li>Absent-Table Body — <em>&quot;No Siemens tool-offset table on the active runner.&quot;</em>; shown instead of
the body</li>
<li>Description Caption — <em>&quot;Sinumerik $TC_DP tool offsets, one row per (tool T, cutting edge D). A
<code>D1</code> word in the program reads this table; when a row is missing, playback falls back to the
generic Tool Offsets table and reports a warning. Wear adds onto geometry (enter a shortened
tool as negative wear).&quot;</em>, with <code>D1</code> set in code style</li>
<li>Toolbar — right-aligned
<ul>
<li><strong>Add Tool</strong> Button — primary, add icon</li>
</ul>
</li>
<li>Offset Table — dense, flat, bordered
<ul>
<li>Header Row — <strong>Tool T</strong>, <strong>Edge D</strong>, <strong>Length 1 (Z)</strong>, <strong>Length 2 (X)</strong>, <strong>Length 3 (Y)</strong>,
<strong>Radius</strong>, <strong>Length 1 Wear</strong>, <strong>Length 2 Wear</strong>, <strong>Length 3 Wear</strong>, <strong>Radius Wear</strong>, then an
unlabelled action column</li>
<li>Offset Row, one per <code>(tool, edge)</code> pair, ascending by tool then edge
<ul>
<li>Tool Cell — bold plain text, prefixed <code>T</code></li>
<li>Edge Cell — bold plain text, prefixed <code>D</code></li>
<li>Eight Numeric Fields — the four geometry components, then their four wear components; no
bounds and no unit suffix</li>
<li>Add-Cutting-Edge Button — flat, <code>playlist_add</code> icon, tooltipped <em>&quot;Add a cutting edge for
T{tool}&quot;</em></li>
<li>Delete Button — flat, delete icon</li>
<li>Verbatim-Field Badge — a grey <strong>+N</strong>; shown only where the row carries unconsumed <code>$TC_DP</code>
fields, tooltipped that they are stored and saved with the project and not used by the
simulation</li>
</ul>
</li>
</ul>
</li>
<li>Footnote — <em>&quot;All values in mm. Effective length 1 = Length 1 (Z) + Length 1 Wear; same for the
other columns.&quot;</em></li>
</ul>
</li>
<li>Remove-Row Confirmation Dialog — <strong>Remove tool offset row</strong>, <em>&quot;Remove the offset row for (T{tool},
D{edge})?&quot;</em>, with a cancel button</li>
<li><strong>Tool Names</strong> Panel
<ul>
<li>No-Runner Body — <em>&quot;No NC runner — load a project first.&quot;</em></li>
<li>Absent-Table Body — <em>&quot;No Siemens tool-name table on the active runner.&quot;</em></li>
<li>Hint Caption — <em>&quot;Maps the tool names written in the NC program (<code>T=&quot;D16R3Z6&quot;</code>) to tool numbers.
Names are case-insensitive. An unmapped name cannot mount a tool and its <code>D</code> word resolves no
offset.&quot;</em>, with the call form and the <code>D</code> word set in code style</li>
<li>Name Table — dense, flat, bordered
<ul>
<li>Header Row — <strong>Tool Name</strong>, <strong>Tool #</strong>, then an unlabelled action column</li>
<li>Name Row, one per mapping, ordered case-insensitively by name
<ul>
<li>Name Cell — bold plain text</li>
<li>Tool Number Numeric Field — minimum 1</li>
<li>Delete Button — flat, delete icon, no confirmation</li>
</ul>
</li>
</ul>
</li>
<li>Add Row Footer
<ul>
<li><strong>Tool name</strong> Text Field</li>
<li><strong>Tool #</strong> Numeric Field — minimum 1</li>
<li><strong>Add / Set</strong> Button — primary; the only submit path, since neither field submits on Enter</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../../../index.html">HiNC App Anatomy</a> for git repository links.</p>
<h3 id="web-application">Web Application</h3>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/controlTree/SoftNcSiemensToolOffsetsPanel.vue</code> — the <code>$TC_DP</code> panel:
the eight unbounded numeric cells, the whole-row write with its finite-value guard, the two add
buttons and the local append-and-sort, the confirmed delete, and the verbatim-field badge.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcToolNamesPanel.vue</code> — the tool-name panel: the
plain-text key column, the bounded tool-number cell, the unconfirmed delete, and the <strong>Add / Set</strong>
footer with its two client-side refusals and its full re-read.</li>
<li><code>wwwroot-src/src/components/controlTree/softNcItemTypes.ts</code> — the branch builder: the single flag
test that pushes both node ids, their labels and label keys, and the relabel it applies to the
generic offset leaf at the same time.</li>
<li><code>wwwroot-src/src/api/softNcRunner.ts</code> — the typed wrappers: the row and mapping shapes with their
presence flags, the whole-row upsert, the add that returns a minted pair, the two deletes, and the
name-keyed upsert with its URL encoding.</li>
<li><code>wwwroot-src/src/composables/useSoftNcRunner.ts</code> — the shared snapshot behind the first empty layer
and behind the tree's flag test.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcEmptyState.vue</code> — the shared no-runner line both
panels open with.</li>
<li><code>wwwroot-src/src/components/widgets/NumericInput.vue</code> — the numeric cell: commit on blur or Enter,
the minimum rejected inside the widget, and the empty-text-to-null parse both panels drop.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcToolOffsetsPanel.vue</code> — the neighbouring generic
ledger, its subtracting sign rule, and the Siemens caveat it shows from the brand string.</li>
<li><code>wwwroot-src/src/i18n/en/softNc.ts</code> — the two node labels, the description and the hint, the ten
column headers, the footnote, the two absent-table lines, the add-strip refusals and the error
contexts.</li>
<li><code>wwwroot-src/src/i18n/en/common.ts</code> — the shared <strong>Tool #</strong> header and the <strong>Add / Set</strong> label.</li>
<li><code>wwwroot-src/src/i18n/en/dialog.ts</code> — the <code>$TC_DP</code> remove-row dialog's title and message, a
different key pair from the generic ledger's.</li>
<li><code>Mech/SoftNcRunnerController.cs</code> — the REST surface: the snapshot probe behind both nodes, the
ordered reads whose presence flag is the table object itself, the row upsert that assigns the eight
components and leaves the verbatim bag alone, the add that mints a tool or an edge, the two deletes
with their refusals, the name upsert with its blank-name refusal, and the proxy re-wire the three
writes run first.</li>
</ul>
<h3 id="hiapi-engine">HiAPI Engine</h3>
<ul>
<li><code>HiMech/NcParsers/Dependencys/Siemens/SiemensToolOffsetTable.cs</code> — the object both leaves edit: the
pair-keyed row dictionary, the case-insensitive name map and its lookup, the additive effective
values, the row type with its typed components and verbatim bag, and the serializer that keeps a
consumed index out of the bag.</li>
<li><code>HiMech/NcParsers/Dependencys/Siemens/ISiemensToolOffsetConfig.cs</code> — the two-key offset contract:
the try-form that is the only miss signal, and the direction-indexed and radius accessors nothing
calls.</li>
<li><code>HiMech/NcParsers/Dependencys/Siemens/SiemensToolOffsetTableProxy.cs</code> — the seedless get-or-create
placeholder the Siemens preset carries, and the bare table it installs into a project holding none.</li>
<li><code>HiMech/NcParsers/SoftNcRunner.cs</code> — the five brand presets, of which only the Siemens list carries
that proxy, and the proxy resolution every read of the table goes through.</li>
<li><code>HiMech/NcParsers/NcRunnerSuit.cs</code> — the per-case list this table is stored in, and the proxy wiring
the three writes re-run.</li>
<li><code>HiMech/NcParsers/LogicSyntaxs/Siemens/SiemensToolOffsetSyntax.cs</code> — the <code>D</code> path: the cancel arm,
the modal ownership rule, the tool-number resolution through the name map, the fallback onto the
generic ledger with its two warnings, and the suppression of both on a modal re-resolve.</li>
<li><code>HiMech/NcParsers/LogicSyntaxs/ToolHeightOffsetSyntax.cs</code> — the ISO sibling, and the shared
composition of the height as one replaceable entry of the transform chain.</li>
<li><code>HiMech/NcParsers/Semantics/ToolChangeSemantic.cs</code> — the other consumer of the name map: the string
tool id it resolves, and the tooling step it declines to emit when the name is unmapped.</li>
<li><code>HiMech/NcParsers/PostLogicSyntaxs/RadiusCompensationSyntax.cs</code> — the radius side, which reads the
generic single-integer table rather than this one.</li>
<li><code>HiMech/NcParsers/Dependencys/Generic/ToolOffsetTable.cs</code> — the fallback source: the subtracting
effective values, and the zero it answers for an offset number it does not hold.</li>
<li><code>HiMech/NcParsers/Dependencys/Generic/ToolOffsetTableProxy.cs</code> — the placeholder every brand preset
carries, which is why the fallback always resolves a table on Siemens.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="index.html">Program Data Plane</a> — the plane both leaves sit on, where this table is stored, and
what a brand switch does to it</li>
<li><a class="xref" href="tool-offsets.html">Tool Offsets</a> — the generic ledger one node above, whose label this table's
presence changes and whose wear convention is the opposite of the one stated here</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,40 @@
<div id="sidetoggle">
<div>
<div class="sidefilter">
<form class="toc-filter">
<span class="glyphicon glyphicon-filter filter-icon"></span>
<span class="glyphicon glyphicon-remove clear-icon" id="toc_filter_clear"></span>
<input type="text" id="toc_filter_input" placeholder="Filter by title" onkeypress="if(event.keyCode==13) {return false;}">
</form>
</div>
<div class="sidetoc">
<div class="toc" id="toc">
<ul class="nav level1">
<li>
<a href="index.html" name="" title="Overview">Overview</a>
</li>
<li>
<a href="work-coordinates.html" name="" title="Work Coordinates">Work Coordinates</a>
</li>
<li>
<a href="tool-offsets.html" name="" title="Tool Offsets">Tool Offsets</a>
</li>
<li>
<a href="siemens-tool-offsets.html" name="" title="Siemens Tool Offsets">Siemens Tool Offsets</a>
</li>
<li>
<a href="datum-tables.html" name="" title="Datum Tables">Datum Tables</a>
</li>
<li>
<a href="frames.html" name="" title="Frames">Frames</a>
</li>
<li>
<a href="persistent-variables.html" name="" title="Persistent Variables">Persistent Variables</a>
</li>
</ul>
</div>
</div>
</div>
</div>
@@ -0,0 +1,2 @@
{"items":[{"name":"Overview","href":"index.html","topicHref":"index.html"},{"name":"Work Coordinates","href":"work-coordinates.html","topicHref":"work-coordinates.html"},{"name":"Tool Offsets","href":"tool-offsets.html","topicHref":"tool-offsets.html"},{"name":"Siemens Tool Offsets","href":"siemens-tool-offsets.html","topicHref":"siemens-tool-offsets.html"},{"name":"Datum Tables","href":"datum-tables.html","topicHref":"datum-tables.html"},{"name":"Frames","href":"frames.html","topicHref":"frames.html"},{"name":"Persistent Variables","href":"persistent-variables.html","topicHref":"persistent-variables.html"}]}
@@ -0,0 +1,498 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Tool Offsets | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Tool Offsets | HiAPI-C# 2025 ">
<link rel="icon" href="../../../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../../../public/docfx.min.css">
<link rel="stylesheet" href="../../../../public/main.css">
<meta name="docfx:navrel" content="../../../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../../../index.html">
<img id="logo" class="svg" src="../../../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-ControllerToolOffsets">
<h1 id="tool-offsets">Tool Offsets</h1>
<p>Tool Offsets is the Controller branch's brand-neutral offset ledger — one row per integer offset
number, each carrying a height and a radius split into an ideal (geometry) component and a wear
component. It lives on the General Setup page at <code>/general-setup</code> under the Control-Tree id
<code>equipment/controller/program-data/tool-offsets</code>, and that one id is the whole of what this page
owns. The leaf is ungated: every brand preset proxies the table behind it, so the node is grown
wherever a runner resolves — <a class="xref" href="../brand-matrix.html">Brand Matrix</a>.</p>
<div class="IMPORTANT">
<h5>Important</h5>
<p>Anatomy makes no claim that any id named here is stable across versions. The migration hop that
keeps an older <code>?tree=</code> value resolving is described in
<a class="xref" href="../../../platform/tree-ids-and-routes.html">Tree Ids and Routes</a>.</p>
</div>
<p>The panel follows the branch's shared rules — one read on mount, per-control commit, the
capture-assign-await-restore write, the two empty layers, and a dense markup table with no sort and
no pagination. Those rules, and the places this panel departs from them, are
<a class="xref" href="../editing-contract.html">Editing Contract</a>. What is this leaf's own is everything
below.</p>
<h2 id="one-node-two-labels">One Node, Two Labels</h2>
<p>The node id never changes, and the tree label does. The branch builder pushes this leaf
unconditionally, then chooses its label from the snapshot's Siemens tool-offset flag:</p>
<table>
<thead>
<tr>
<th>Condition</th>
<th>Label the tree row reads</th>
</tr>
</thead>
<tbody>
<tr>
<td>No <a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Siemens.SiemensToolOffsetTable.html">SiemensToolOffsetTable</a> resolves</td>
<td><strong>Tool Offsets</strong></td>
</tr>
<tr>
<td>One does</td>
<td><strong>Tool Offsets (ISO G43 H)</strong></td>
</tr>
</tbody>
</table>
<p>Among the shipped presets only Siemens carries that table, so only Siemens sees the second label —
and it sees it beside a second offset leaf, <strong>Tool Offsets ($TC_DP)</strong>, grown from the same flag. The
qualifier exists so that the two ledgers do not read as one row of the tree; the id under it is
<code>equipment/controller/program-data/tool-offsets</code> either way, so an existing deep link is unaffected
by which label is showing. Which flag grows which leaf, and what that flag probes, is
<a class="xref" href="../brand-matrix.html">Brand Matrix</a>.</p>
<p>A second, independent Siemens signal appears <strong>inside</strong> the panel. While the runner's brand marker
reads <code>Siemens</code>, an orange caption sits above the toolbar:</p>
<blockquote>
<p><em>&quot;On Siemens this table serves the ISO <code>G43 H</code> path only — it is not the Sinumerik tool-offset
ledger. <code>D</code> tool calls read the Tool Offsets ($TC_DP) table; when a (T, D) row is missing there,
playback falls back to this table's height for the tool number and reports a warning.&quot;</em></p>
</blockquote>
<p>The label is chosen from the table flag and the caption from the brand string. Those are computed
from different things and can disagree — one of the departures catalogued in
<a class="xref" href="../editing-contract.html">Editing Contract</a>.</p>
<h2 id="what-a-row-holds">What a Row Holds</h2>
<p>The table is the Fanuc Memory C layout: the H number and the D number address the same row, so one
row carries both the length and the radius of one offset.</p>
<table>
<thead>
<tr>
<th>Column</th>
<th>Stores</th>
<th>Read by</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Tool #</strong></td>
<td>the offset number, the row's key</td>
<td>the H word, the D word, or the Heidenhain tool number</td>
</tr>
<tr>
<td><strong>Ideal Height (mm)</strong></td>
<td>the tool's length before wear</td>
<td>the height side</td>
</tr>
<tr>
<td><strong>Axial Wear (mm)</strong></td>
<td>accumulated length wear</td>
<td>the height side</td>
</tr>
<tr>
<td><strong>Ideal Radius (mm)</strong></td>
<td>the cutter's radius before wear</td>
<td>the radius side</td>
</tr>
<tr>
<td><strong>Radial Wear (mm)</strong></td>
<td>accumulated radius wear</td>
<td>the radius side</td>
</tr>
</tbody>
</table>
<p>There is no unit column and no unit conversion:
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Generic.ToolOffsetRow.html">ToolOffsetRow</a> stores four plain millimetre doubles, and the
panel writes and reads them unchanged. No cell is bounded except the key, which takes a minimum of</p>
<ol>
<li>The panel renders no description caption above its table: the Siemens caveat is the only line it
shows there, and the only other prose it carries is the closing caption the tool-house dependence
turns on below the table.</li>
</ol>
<p>The key column is the branch's one renameable key, and the rename is guarded on both sides of the
wire: the panel refuses a value already present in its own rows with the toast <em>&quot;Tool id {id}
already exists.&quot;</em> and re-reads the table, and the endpoint refuses the same case with a message
naming the existing id. Below 1 never reaches either — the numeric widget rejects the bound itself,
marks the box and emits nothing. A non-integer that the widget does emit is dropped by the handler
with no request and no message at all.</p>
<h2 id="the-sign-convention">The Sign Convention</h2>
<p><strong>On this table an effective value is the ideal minus the wear.</strong>
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Generic.ToolOffsetRow.html#Hi_NcParsers_Dependencys_Generic_ToolOffsetRow_FullHeight_mm">FullHeight_mm</a> is the ideal height less the
axial wear and <a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Generic.ToolOffsetRow.html#Hi_NcParsers_Dependencys_Generic_ToolOffsetRow_FullRadius_mm">FullRadius_mm</a> the ideal radius
less the radial wear, and those two are exactly what
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.IToolOffsetConfig.html">IToolOffsetConfig</a> hands every consumer. A positive wear therefore
shortens the compensation and a negative wear lengthens it. Nothing in the panel states this: the
four column headers name the components and no footnote names the operation, which is why the rule
belongs here. An offset number the table holds no row for is not an error either: both getters
answer 0 for it, and neither the ISO height path nor the radius side reports the miss — a mistyped H
is a zero-length compensation that plays to the end of the program without a warning.</p>
<div class="WARNING">
<h5>Warning</h5>
<p><strong>The neighbouring Siemens ledger uses the opposite convention.</strong> On
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Siemens.SiemensToolOffsetTable.html">SiemensToolOffsetTable</a> an effective value is the geometry
<strong>plus</strong> the wear, and that panel prints the addition in a footnote under its own table. On a
Siemens project both leaves are present at once, so one wear number entered in the two ledgers
moves the tool in opposite directions. See
<a class="xref" href="siemens-tool-offsets.html">Siemens Tool Offsets</a>.</p>
</div>
<h3 id="how-the-height-reaches-the-machine">How the height reaches the machine</h3>
<p>The height side is consumed as a translation, not as a property of the tool. On a G43 block the ISO
tool-height syntax reads the effective height for the H number and composes it into the
program-to-machine transform chain as a translation of that many millimetres along the current tool
orientation; G44 negates the same number and G49 writes zero. The G43.4 RTCP path reads the same
number for the same H word and re-aims it through the machine kinematics. On Heidenhain there is no
cancel word: a TOOL CALL block reads the effective height for the <strong>tool number</strong> and adds the
block's <code>DL</code> delta to it, and the result composes into the same chain entry.</p>
<p>Nothing on that path reads the tool the project actually carries. The modelled tool assembly's own
spindle-buckle-to-tool-tip length is what decides where its tip sits once the machine has moved by
the compensation, so the stored ideal height and the modelled tool's length are two independent
numbers that have to agree for the programmed contour to land on the workpiece. Making them agree is
the entire purpose of the tool-house dependence below, which writes
<a class="xref" href="../../../../api/Hi.Machining.IMachiningTool.html#Hi_Machining_IMachiningTool_SpindleBuckleToToolTipLength">SpindleBuckleToToolTipLength</a> straight into the ideal column.</p>
<h3 id="how-the-radius-reaches-the-path">How the radius reaches the path</h3>
<p>The radius side is read by the G41/G42 radius-compensation syntax against the D number, and it is
signed rather than absolute. A negative effective radius — a radial wear larger than the ideal
radius — is accepted, and it offsets the path to the opposite of the programmed side. On the first
block of a compensation move the parser raises a validation warning for that case on Heidenhain
only, mirroring the look-ahead check a TNC control performs; the other brands take it silently.</p>
<h3 id="the-macro-variable-window">The macro-variable window</h3>
<p>On the Fanuc, Mazak and Syntec syntax lists a variable lookup maps <code>#2001</code> through <code>#2200</code> onto the
effective <strong>height</strong> of offsets 1 through 200, so a macro program can read the same subtraction the
G43 path applies. It is a read: the lookup exposes no writer, and neither the Siemens nor the
Heidenhain syntax list registers it.</p>
<h3 id="where-a-blank-cell-comes-from">Where a blank cell comes from</h3>
<p>A cleared numeric cell is not a stored value — the panel drops a null or a non-finite edit before
sending anything, so clearing a cell leaves the number on the server and the box repopulates on the
next remount. A blank cell that arrives <strong>from</strong> the server is a different thing: it is <code>NaN</code>, which
the numeric widget renders as empty text. The tool-house refresh is what writes one, for a tool
whose tip length does not resolve. Only the Siemens <code>$TC_DP</code> fallback tests for it — a NaN height
there is reported as a configuration warning and degrades to zero — while the ISO G43 path composes
whatever the table returned.</p>
<h2 id="tool-house-dependence">Tool-House Dependence</h2>
<p>Above the table sits the toggle <strong>Set ideal offset dependent on tool house</strong>. It is the one setting
this leaf edits that is not a dependency at all: it writes
<a class="xref" href="../../../../api/Hi.MachiningProcs.MachiningActRunnerConfig.html#Hi_MachiningProcs_MachiningActRunnerConfig_IsIdealOffsetDependentOnToolHouse">IsIdealOffsetDependentOnToolHouse</a>, a project-level
element serialized beside the runner suit, so it is untouched by a runner install and by the sweep
that follows a brand switch. Its stored default is on; the reader answers <em>off</em> when no project is
loaded, which is a fallback rather than the model's own default.</p>
<p>With the dependence on, the panel hands three things over:</p>
<ul>
<li>The two <strong>ideal</strong> columns become read-only, and a closing caption says so: <em>&quot;Ideal columns mirror
the Tool House; only the wear columns are editable.&quot;</em> Read-only is a screen guarantee only — the
write is row-scoped, so the ideal values ride along on every wear edit, and the endpoint writes
all four components unconditionally. That the two ideal cells still commit on Enter is one of the
panel's catalogued departures from <a class="xref" href="../editing-contract.html">Editing Contract</a>.</li>
<li>The <strong>Tool #</strong> cell is replaced by bold plain text, so the key cannot be renamed.</li>
<li>Row CRUD disappears: the <strong>Add</strong> button is not rendered and each row's delete button becomes a
grey em dash. A <strong>Refresh from Tool House</strong> button appears next to the toggle in their place.</li>
</ul>
<h3 id="the-table-does-not-otherwise-track-the-tool-library">The table does not otherwise track the tool library</h3>
<p>Offset number and tool id are independent integers, and with the dependence off nothing reconciles
them. A row may name a number the project's tool library has never held, and the library may hold
tools with no row; neither state is reported anywhere on this panel, and both are stored and played
as written. The library itself is edited on <a class="xref" href="../../../tool-house/index.html">Tool House Page</a>.</p>
<p>The refresh is what imposes a one-to-one mapping, and it does so in both directions.</p>
<h3 id="the-refresh-is-destructive-and-it-is-not-only-a-button">The refresh is destructive, and it is not only a button</h3>
<p>One endpoint backs both the button and the toggle, and it runs three steps. For every tool in the
project's library it writes the ideal height from that tool's spindle-buckle-to-tool-tip length —
<code>NaN</code> where the length does not resolve — and, where the cutter is a milling cutter, the ideal
radius from the largest radius on its cutter profile; a row is created for a library tool that had
none. <strong>It then deletes every row whose number is not in the library.</strong> The wear columns are never
touched.</p>
<p>The engine carries the same three steps of its own, as
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Generic.ToolOffsetTable.html#Hi_NcParsers_Dependencys_Generic_ToolOffsetTable_UpdateIdealByToolHouse_">UpdateIdealByToolHouse</a><small>(API)</small>,
and that copy is what playback calls. The two agree step for step and part company only on a missing
library: the engine method returns without doing anything, while the endpoint answers unsuccessfully
with <em>&ldquo;No tool house available&rdquo;</em>.</p>
<p>Three consequences deserve stating plainly.</p>
<ul>
<li><strong>There is no confirmation.</strong> The dialog that guards a single delete button does not guard this,
and there is no undo.</li>
<li><strong>Turning the toggle on runs it immediately.</strong> Enabling the dependence is two server calls in one
attempt: the flag write, then the refresh. A project with no tool library fails the second with
<em>&ldquo;No tool house available&rdquo;</em> after the first has already committed, and the panel's local toggle
springs back while the server-side flag stays on — the two-call rollback recorded in
<a class="xref" href="../editing-contract.html">Editing Contract</a>. Turning the toggle off writes the flag
and stops there — no refresh, and no re-read.</li>
<li><strong>Playback runs it too.</strong> While the flag is on, starting an NC program refreshes the table from
the library before the first block, so a hand-typed ideal value is replaced whether or not the
panel was ever opened, and an empty library at that moment empties the table. No panel is told:
what is on screen is whatever the last read returned until the selection moves away and back.</li>
</ul>
<h2 id="row-life-cycle">Row Life Cycle</h2>
<p>The add and the delete are the two flows the branch spells one way here and another way elsewhere.</p>
<p><strong>Add</strong> is a fieldless toolbar button, because the server mints the key: the endpoint takes the
highest existing number plus one — or 1 on an empty table — inserts an all-zero row and returns the
number, and the panel appends that row locally rather than re-reading. All four zeros are the
server's actual new-row values, not a screen placeholder.</p>
<p><strong>Delete</strong> opens a confirmation dialog first, titled <strong>Remove tool offset</strong> over <em>&quot;Remove the offset
row for tool #{id}?&quot;</em>, and filters the row out locally once the request resolves. Deleting a number
the table no longer holds answers unsuccessfully with a message naming it.</p>
<p><strong>Rename</strong> is optimistic and re-sorts the rows ascending once the write returns; a failure restores
the previous number. Every other cell is a per-cell handler that sends the whole row.</p>
<h2 id="when-the-table-is-absent">When the Table Is Absent</h2>
<p>The leaf carries both of the branch's empty layers. The shared one renders <em>&quot;No NC runner — load a
project first.&quot;</em> while the snapshot reports no runner. The panel's own read then gates on whether a
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Generic.ToolOffsetTable.html">ToolOffsetTable</a> resolved, and renders <em>&quot;No tool-offset table
on the active runner.&quot;</em> when it did not — which hides the dependence toggle along with the table,
since the whole body sits behind that guard. A failed write answers inside a success envelope. The
row write, the delete, the rename and the refresh all go through the branch's shared dependency
helper and carry <code>No ToolOffsetTable on the active runner</code>, which the panel shows as its own context
followed by that sentence. <strong>Add</strong> does not use that helper: it tests the table itself and answers
<code>No tool-offset table on the active runner</code> — the panel's own absent-table line, less its full stop.
The dependence write reaches neither string, because what it writes is a project-level element
rather than the table; it is the one route on this leaf that answers a coded payload instead, and it
reports no project loaded.</p>
<p>That second layer is hard to reach. <a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Generic.ToolOffsetTableProxy.html">ToolOffsetTableProxy</a> is
a seedless get-or-create placeholder: on wiring it installs a bare table into the project's per-case
list whenever the project holds none, and the suit wires its proxies when it is deserialized and
again on every runner assignment.</p>
<h2 id="layout">Layout</h2>
<ul>
<li><strong>General Setup</strong> Control Tree — the left dock of <code>/general-setup</code>
<ul>
<li><strong>Controller</strong> Node Row
<ul>
<li><strong>Program Data</strong> Group Row
<ul>
<li><strong>Tool Offsets</strong> Node Row — <code>equipment/controller/program-data/tool-offsets</code>; reads
<strong>Tool Offsets (ISO G43 H)</strong> while a Siemens <code>$TC_DP</code> table resolves</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>Editor Row — the panel of whichever node is selected
<ul>
<li><strong>Tool Offsets</strong> Panel
<ul>
<li>No-Runner Body — <em>&quot;No NC runner — load a project first.&quot;</em>; shown instead of everything below</li>
<li>Absent-Table Body — <em>&quot;No tool-offset table on the active runner.&quot;</em>; shown instead of the body</li>
<li>Siemens Caveat Line — orange caption, shown while the brand marker reads <code>Siemens</code>, with
<code>G43 H</code> and <code>D</code> set in code style</li>
<li>Toolbar
<ul>
<li><strong>Set ideal offset dependent on tool house</strong> Toggle</li>
<li><strong>Refresh from Tool House</strong> Button — flat, refresh icon; only while the dependence is on</li>
<li><strong>Add</strong> Button — primary, add icon, right-aligned; only while the dependence is off</li>
</ul>
</li>
<li>Offset Table — dense, flat, bordered
<ul>
<li>Header Row — <strong>Tool #</strong>, <strong>Ideal Height (mm)</strong>, <strong>Axial Wear (mm)</strong>, <strong>Ideal Radius (mm)</strong>,
<strong>Radial Wear (mm)</strong>, then an unlabelled action column</li>
<li>Offset Row, one per offset number, ascending
<ul>
<li>Tool Number Cell — a numeric field with a minimum of 1, or bold plain text while the
dependence is on</li>
<li>Ideal Height, Axial Wear, Ideal Radius and Radial Wear Numeric Fields — the two ideal ones
read-only while the dependence is on</li>
<li>Delete Button — or a grey em dash while the dependence is on</li>
</ul>
</li>
</ul>
</li>
<li>Dependent Footnote — <em>&quot;Ideal columns mirror the Tool House; only the wear columns are
editable.&quot;</em>; only while the dependence is on</li>
</ul>
</li>
<li>Remove-Row Confirmation Dialog — <strong>Remove tool offset</strong>, <em>&quot;Remove the offset row for tool
#{id}?&quot;</em>, with a cancel button</li>
</ul>
</li>
</ul>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../../../index.html">HiNC App Anatomy</a> for git repository links.</p>
<h3 id="web-application">Web Application</h3>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/controlTree/SoftNcToolOffsetsPanel.vue</code> — the panel: the dependence
toggle and its two-call enable, the refresh button, the read-only ideal columns, the renameable
key with its duplicate guard, the confirmed delete, the locally appended add, and the Siemens
caption gated on the brand string.</li>
<li><code>wwwroot-src/src/components/controlTree/softNcItemTypes.ts</code> — the branch builder: this leaf's id,
its two labels and label keys, and the snapshot flag that switches between them.</li>
<li><code>wwwroot-src/src/api/softNcRunner.ts</code> — the typed wrappers: the row shape with its presence and
dependence flags, the whole-row write, the add that returns a minted number, the rename, the
delete, and the refresh call.</li>
<li><code>wwwroot-src/src/components/widgets/NumericInput.vue</code> — the numeric cell: commit on blur or Enter,
the minimum rejected inside the widget, the empty-text-to-null parse the panel drops, and the
<code>NaN</code> that renders as an empty box.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcEmptyState.vue</code> — the shared no-runner line the
panel opens with.</li>
<li><code>wwwroot-src/src/composables/useSoftNcRunner.ts</code> — the shared snapshot behind that first guard and
behind the tree's choice of label.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcSiemensToolOffsetsPanel.vue</code> — the neighbouring
ledger on Siemens, and the footnote in which it states the opposite sign rule.</li>
<li><code>wwwroot-src/src/i18n/en/softNc.ts</code> — the two node labels, the Siemens caveat, the four column
headers, the dependent footnote, the duplicate-key toast, the absent-table line and the panel's
error contexts.</li>
<li><code>wwwroot-src/src/i18n/en/common.ts</code> — the shared <strong>Tool #</strong>, <strong>Add</strong>, toggle and refresh labels.</li>
<li><code>wwwroot-src/src/i18n/en/dialog.ts</code> — the remove-row dialog's title and message.</li>
<li><code>Mech/SoftNcRunnerController.cs</code> — the REST surface: the reader with its presence and dependence
flags, the whole-row write, the add that mints the next number, the delete and the rename with
their refusals, the project-level dependence write, and the refresh with its drop of rows the
library no longer holds.</li>
<li><code>Common/ApiError.cs</code> — the coded no-project payload the dependence write answers with, the one
answer on this leaf that can be re-rendered in the application locale.</li>
</ul>
<h3 id="hiapi-engine">HiAPI Engine</h3>
<ul>
<li><code>HiMech/NcParsers/Dependencys/Generic/ToolOffsetTable.cs</code> — the ledger itself: the four stored
components, the subtraction that makes an effective value ideal minus wear, the integer-keyed row
dictionary, and the tool-house update with its row creation and its row removal.</li>
<li><code>HiMech/NcParsers/Dependencys/Generic/ToolOffsetTableProxy.cs</code> — the seedless get-or-create
placeholder every brand preset carries in place of the table, and the bare instance it installs
into a project holding none.</li>
<li><code>HiMech/NcParsers/Dependencys/IToolOffsetConfig.cs</code> — the single-integer offset contract the
consumers read through, and the pointer to the Siemens tool-and-edge contract beside it.</li>
<li><code>HiMech/NcParsers/Dependencys/Siemens/SiemensToolOffsetTable.cs</code> — the neighbouring ledger, and
the addition that makes its effective values geometry plus wear.</li>
<li><code>HiMech/NcParsers/SoftNcRunner.cs</code> — the five brand presets, each of which carries the tool-offset
proxy, and the proxy resolution every read of the table goes through.</li>
<li><code>HiMech/NcParsers/LogicSyntaxs/ToolHeightOffsetSyntax.cs</code> — the ISO height path: the H word, the
G43 / G44 / G49 arms, and the translation along the tool orientation both brand siblings compose
through.</li>
<li><code>HiMech/NcParsers/LogicSyntaxs/G43p4RtcpSyntax.cs</code> — the RTCP path that reads the same height for
the same H word and re-aims it through the kinematics.</li>
<li><code>HiMech/NcParsers/LogicSyntaxs/Heidenhain/HeidenhainToolOffsetSyntax.cs</code> — the TOOL CALL path: the
tool number as the offset id, the <code>DL</code> delta added to the table height, and the absence of a
cancel word.</li>
<li><code>HiMech/NcParsers/LogicSyntaxs/Siemens/SiemensToolOffsetSyntax.cs</code> — the <code>D</code> path's fallback onto
this table when the <code>$TC_DP</code> map has no row, its warning, and the <code>NaN</code> case it degrades to zero.</li>
<li><code>HiMech/NcParsers/PostLogicSyntaxs/RadiusCompensationSyntax.cs</code> — the radius side: the D word, the
signed offset that flips the compensated side when it goes negative, and the Heidenhain-only
warning for that case.</li>
<li><code>HiMech/NcParsers/Dependencys/Fanuc/FanucToolOffsetVariableLookup.cs</code> — the read-only macro window
onto the effective heights, and the address range it answers for.</li>
<li><code>HiMech/NcParsers/Syntaxs/FanucSyntaxUtil.cs</code>,
<code>HiMech/NcParsers/Syntaxs/MazakSyntaxUtil.cs</code> and
<code>HiMech/NcParsers/Syntaxs/SyntecSyntaxUtil.cs</code> — the three syntax lists that register that window.</li>
<li><code>HiMech/MachiningProcs/MachiningActRunnerConfig.cs</code> — the tool-house dependence flag, its stored
default and the element the project serializes it as.</li>
<li><code>HiMech/MachiningProcs/MachiningSession.cs</code> — the play loop's refresh of the table from the
library before the first block, run whenever the flag is on.</li>
<li><code>HiMech/Machining/MachiningToolHouse.cs</code> — the tool-id-keyed library the refresh walks.</li>
<li><code>HiMech/Machining/IMachiningTool.cs</code> — the tool contract the library's entries answer to, and
where the spindle-buckle-to-tool-tip length the refresh reads is declared.</li>
<li><code>HiMech/Milling/MillingTools/MillingTool.cs</code> — the assembly walk behind that length, and the <code>NaN</code>
it answers when the walk does not resolve.</li>
<li><code>HiNc/MachiningProcs/LocalProjectService.cs</code> — the facade that owns the library and the runner
suit, and the entry point the play-time refresh reaches this table through.</li>
<li><code>HiNc/MachiningProcs/MachiningProject.cs</code> — where the library, the runner configuration and the
suit hang off the project, and the load path that leaves the library unset when the project file
names none.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="index.html">Program Data Plane</a> — the plane this leaf sits on, where its table is stored, and
what a brand switch does to the tables beside it</li>
<li><a class="xref" href="siemens-tool-offsets.html">Siemens Tool Offsets</a> — the <code>$TC_DP</code> ledger that appears next to this one on
Siemens, and whose wear convention is the opposite of the one stated above</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,627 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Work Coordinates | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Work Coordinates | HiAPI-C# 2025 ">
<link rel="icon" href="../../../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../../../public/docfx.min.css">
<link rel="stylesheet" href="../../../../public/main.css">
<meta name="docfx:navrel" content="../../../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../../../index.html">
<img id="logo" class="svg" src="../../../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-ControllerWorkCoordinates">
<h1 id="work-coordinates">Work Coordinates</h1>
<p>Work Coordinates is the Controller branch's G54 ledger: one table of coordinate ids against an X / Y
/ Z machine offset, edited through whichever offset provider the active runner resolves. It lives on
the General Setup page at <code>/general-setup</code> under the Control-Tree id
<code>equipment/controller/program-data/work-coordinates</code>, reached as
<code>?tree=equipment/controller/program-data/work-coordinates</code>, and no snapshot flag gates it — every
runner grows it. The face is the same on all five brands; what sits behind it is a different object
on each, and on two of them that object is also the subject of a node of its own.</p>
<div class="IMPORTANT">
<h5>Important</h5>
<p>Anatomy makes no claim that any id named here is stable across versions. The migration hop that
keeps an older <code>?tree=</code> value resolving is described in
<a class="xref" href="../../../platform/tree-ids-and-routes.html">Tree Ids and Routes</a>.</p>
</div>
<h2 id="the-discriminator">The Discriminator</h2>
<p>The panel does not probe the runner. It reads one field of the shared runner snapshot,
<code>workCoordinateKind</code>, and uses it for exactly one thing: the grey caption above the table. The field
is a type switch over the first <a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.IIsoCoordinateConfig.html">IIsoCoordinateConfig</a> in the runner's
proxy-resolved dependency list, and it has six arms.</p>
<table>
<thead>
<tr>
<th>Arm</th>
<th>Provider matched</th>
<th>Brands that reach it</th>
<th>Caption the panel shows</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>fanuc</code></td>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Fanuc.FanucParameterTable.html">FanucParameterTable</a></td>
<td>Fanuc, Mazak</td>
<td><em>&quot;Stored in the Fanuc parameter table (#5221+ / #7001+).&quot;</em></td>
</tr>
<tr>
<td><code>syntec</code></td>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Syntec.SyntecParameterTable.html">SyntecParameterTable</a></td>
<td>Syntec</td>
<td><em>&quot;Stored in the Syntec parameter table (Pr5221+ / Pr7001+).&quot;</em></td>
</tr>
<tr>
<td><code>siemens</code></td>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Siemens.SiemensFrameTable.html">SiemensFrameTable</a></td>
<td>Siemens</td>
<td><em>&quot;Stored as Siemens settable frames ($P_UIFR; G500 cancels and is always zero).&quot;</em></td>
</tr>
<tr>
<td><code>heidenhain</code></td>
<td><a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.HeidenhainDatumTable.html">HeidenhainDatumTable</a></td>
<td>Heidenhain</td>
<td><em>&quot;G54G59 map onto Heidenhain datum preset rows 16.&quot;</em></td>
</tr>
<tr>
<td><code>generic</code></td>
<td>any other implementer, i.e. <a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.Generic.IsoCoordinateTable.html">IsoCoordinateTable</a></td>
<td>none</td>
<td><em>&quot;Brand-neutral work-coordinate table.&quot;</em></td>
</tr>
<tr>
<td><code>none</code></td>
<td>nothing resolved</td>
<td>none</td>
<td><em>&quot;Brand-neutral work-coordinate table.&quot;</em></td>
</tr>
</tbody>
</table>
<p>Two arms answer for no shipped preset, and they answer alike because the caption's switch falls
through to one default for both.</p>
<ul>
<li><strong><code>generic</code></strong> needs the brand-neutral table to be the FIRST provider the runner resolves. The
Fanuc, Mazak and Syntec presets do carry that table — seeded with the nine <code>G59.x</code> ids — but
always behind their brand table, so the arm is reachable only from a runner composed by hand or
deserialized from a file that put the brand-neutral table first. Which brand carries which
dependency is <a class="xref" href="../brand-matrix.html">Brand Matrix</a>.</li>
<li><strong><code>none</code></strong> has two producers, and a different guard stops each of them. The snapshot's early
return leaves the field's default in place when no runner resolves at all — the same string the
client's empty snapshot carries — and the shared no-runner guard then replaces the whole body. The
type switch's own null arm answers <code>none</code> for a runner that does resolve but carries no offset
provider; that state reports a runner, so what replaces the body is the absent-table guard
instead, because the lookup that answered <code>none</code> is the one the panel's own read reports absent.
Neither path reaches the caption.</li>
</ul>
<p>The brand marker plays no part here. <code>workCoordinateKind</code> is derived from the resolved object's
type, while the badge on the branch root reads a free-form string, so a marker edited to say
something else changes the badge and leaves this caption exactly where it was.</p>
<h2 id="the-row-inventory">The Row Inventory</h2>
<p>Every row is one id from the provider's own id enumeration, and each provider enumerates a different
set. That is the whole reason the row counts differ: the endpoint asks the resolved provider what ids
it has and fills each row from its offset accessor, substituting a zero triad for a null answer.</p>
<table>
<thead>
<tr>
<th>Brand</th>
<th>Provider</th>
<th>Ids enumerated</th>
<th>Always visible</th>
<th>Hidden while all-zero</th>
</tr>
</thead>
<tbody>
<tr>
<td>Fanuc, Mazak</td>
<td>Fanuc parameter table, then the extended table</td>
<td>G54G59, G54.1P1G54.1P48; G59.1G59.9</td>
<td>the six G5x rows and the nine <code>G59.x</code> rows</td>
<td>the 48 <code>G54.1P</code> rows</td>
</tr>
<tr>
<td>Syntec</td>
<td>Syntec parameter table, then the extended table</td>
<td>G54G59, G54.1P1G54.1P48; G59.1G59.9</td>
<td>the six G5x rows and the nine <code>G59.x</code> rows</td>
<td>the 48 <code>G54.1P</code> rows</td>
</tr>
<tr>
<td>Siemens</td>
<td>Siemens frame table</td>
<td>G54G57, G505G599</td>
<td>the four G5x rows</td>
<td>the 95 <code>G5xx</code> rows</td>
</tr>
<tr>
<td>Heidenhain</td>
<td>Heidenhain datum table</td>
<td>G54G59</td>
<td>all six</td>
<td>none</td>
</tr>
<tr>
<td></td>
<td>brand-neutral table (standalone)</td>
<td>G54G59, G59.1G59.9</td>
<td>all fifteen</td>
<td>none</td>
</tr>
</tbody>
</table>
<p>The <em>extended table</em> on the three ISO presets is the brand-neutral table seeded with the nine
<code>G59.x</code> ids only; a standalone brand-neutral table (a runner built without a brand table) seeds all
fifteen.</p>
<p>Three consequences of that column are worth reading twice.</p>
<ul>
<li><strong>Siemens seeds no G58 and no G59 row.</strong> The frame table's constructor seeds G54, G55, G56 and G57
plus the extended G505G599 series, and nothing else, and no panel on the branch can add an id to
the set. A <code>G58</code> in a Siemens program resolves through the same ISO path as on any other brand,
finds no frame, and falls back to a zero offset.</li>
<li><strong>Fanuc and Syntec enumerate an id only while at least one of its three parameter addresses is
present.</strong> <a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.IsoCoordinateAddressMap.html">IsoCoordinateAddressMap</a> maps G54G59 onto <code>#5221</code> with a
stride of 20 and G54.1 P1P48 onto <code>#7001</code> with the same stride, three consecutive addresses per
entry, and both brand tables seed all 162 of them with zero at construction. So all 54 rows exist
from the first read; the enumeration rule bites only a table whose addresses have been removed.
<a class="xref" href="../machine/native-parameters.html">Native Parameters</a> is the surface that removes one — its
row delete drops a system-parameter address outright, and dropping all three of an entry's
addresses removes that row from this leaf. The same form is where those addresses are visible under
their own numbers, each annotated with the component it holds.</li>
<li><strong>Heidenhain shows six ids for a twenty-row table.</strong> The datum table seeds preset rows 120 and
shift rows 120, and its ISO face aliases only preset rows 16 onto G54G59. Rows 720, and every
shift row, have no id here at all.</li>
</ul>
<p><code>G500</code> is a row on no brand. The frame table treats it as the cancel frame: reads answer a zero
offset, a write through the coordinate accessor is dropped, and it is deliberately not stored in the
frame dictionary, so it never reaches the id enumeration — which is why the Siemens caption names a
code the table below it never shows.</p>
<h2 id="two-nodes-one-table">Two Nodes, One Table</h2>
<div class="WARNING">
<h5>Warning</h5>
<p>On Siemens and on Heidenhain this leaf is a second face on an object that already has a node of
its own. The Siemens frame table is edited here <em>and</em> under <strong>Frames (Siemens)</strong>; the Heidenhain
datum preset table is edited here <em>and</em> under <strong>Datum Presets (Q339)</strong>. These are not two copies
kept in step — they are one instance, and a write through either node lands in the same cell.</p>
</div>
<p>The aliasing is exact, and its two halves are addressed differently.</p>
<ul>
<li><strong>Siemens.</strong> This leaf reads and writes through the ISO interface, so the payload is keyed by the
G-code id. <a class="xref" href="frames.html">Frames</a> reads and writes the frame dictionary directly,
keyed by the same string. Both panels render the same id set with the same values, and a <code>G54</code> edit
made on either is the same assignment. The two routes part on one id only: this leaf's write goes
through the coordinate accessor and drops a <code>G500</code>, where the frame route would assign it into the
dictionary — an id neither panel ever sends. Nor are the three value columns the whole of what the
frame table holds: they are the X / Y / Z translation, while the translation components for other
axis letters live in a second dictionary that only the <code>$P_UIFR</code> bridge writes and neither panel
exposes. What does differ between the two panels is furniture — this leaf carries the <strong>Actions</strong>
column with P0 and M0, and Frames has no counterpart for it.</li>
<li><strong>Heidenhain.</strong> This leaf's <code>G54</code><code>G59</code> rows are preset rows 16.
<a class="xref" href="datum-tables.html">Datum Tables</a> shows the same six cells under the <strong>Q339</strong>
column, alongside rows 720 and the whole datum-shift table, which this leaf cannot reach. So the
aliasing is one-directional in coverage: everything on this leaf is on that one, and most of that
one is not here.</li>
</ul>
<p>Nothing in the branch reconciles the two views while both are in scope, and nothing needs to: the
editor row mounts one panel at a time and each panel fetches once on mount, so moving the selection
from one node to the other is itself the refresh. What that costs, and what a mounted panel therefore
never sees, is <a class="xref" href="../editing-contract.html">Editing Contract</a>.</p>
<h2 id="which-rows-show">Which Rows Show</h2>
<p>A row is always visible when its id reads <code>G5</code>, one digit in 49, and optionally a dot and one more
digit. Every other row is hidden unless one of its three values is non-zero, or the <strong>Show all</strong>
toggle is on. That rule is the panel's own regular expression, evaluated in the browser over the rows
the read returned; nothing about it reaches the server.</p>
<p>The toggle itself is conditional. It renders only while at least one row fails the always-visible
test, so it is present on Fanuc, Mazak, Syntec and Siemens and absent on Heidenhain — and absent on
the brand-neutral table too, whose fifteen ids all pass. The Frames leaf carries the same toggle
unconditionally and applies a narrower always-visible test of its own, <code>G54</code> through <code>G57</code>; on the
Siemens table the two tests keep the same four rows visible, so only the toggle's presence differs.</p>
<h2 id="editing-a-row">Editing a Row</h2>
<p>Every value cell is the shared numeric field, so it commits on blur or on Enter and never per
keystroke — its parsing, its bounds behaviour and the double commit that follows Enter are
<a class="xref" href="../../../widget/numeric-input.html">Numeric Input</a>. No cell here passes a minimum or a maximum, and the id
column is plain bold text that cannot be edited.</p>
<p><strong>The write is the whole row.</strong> A commit assigns the new number into the local row, then sends that
row's <code>x</code>, <code>y</code> and <code>z</code> as they now stand to the row's id. A failure restores the one component the
handler captured and raises the branch's standard toast; the request that failed carried all three. A
cleared cell parses to null, and a cell holding <code>NaN</code> or an infinity parses to a non-finite number;
the handler returns before the request in both cases, so neither empties a stored offset. The cell
keeps showing what the field made of the text — blank for a cleared cell and for <code>NaN</code>, the literal
<code>Infinity</code> or <code>-Infinity</code> for an infinity — until the panel is remounted.</p>
<p>Where that row lands differs by provider, and this is the point at which the uniform face ends.</p>
<ul>
<li>Fanuc, Mazak and Syntec write three consecutive parameter addresses, all three unconditionally.</li>
<li>Siemens replaces the frame entry for that id.</li>
<li>Heidenhain replaces the preset row the id aliases onto.</li>
</ul>
<p>The Fanuc-family and Heidenhain writes silently drop an id they do not map: the Fanuc-family write
resolves the address first and does nothing without one, and the Heidenhain write matches the id
against its two recognised forms. The Siemens frame table and the brand-neutral table do the
opposite — an id they have never held is stored as a new row, the frame table refusing only <code>G500</code>.
Neither behaviour is reachable from this leaf, because the panel only ever sends an id the same
provider enumerated.</p>
<h3 id="p0-and-m0">P0 and M0</h3>
<p>Two flat buttons sit in the <strong>Actions</strong> column of every row and write the whole triad in one call.</p>
<ul>
<li><strong>M0</strong> sets the row to machine zero.</li>
<li><strong>P0</strong> sets it to the machine coordinate at which the workpiece's program-zero anchor sits with
every dynamic axis of the equipment assembly — not only the machining chain's — stepped to zero,
falling back to the machining chain's table buckle when the workpiece declares no program-zero
anchor.</li>
</ul>
<p>Both re-read the whole table on success rather than patching the local row, so a value the provider
stored differently from what was sent is picked up at once. Neither is confirmed. P0 carries a
failure of its own that no other write in the branch produces: with no chain resolving a tool buckle
the position is null, and the endpoint answers unsuccessfully with <em>&ldquo;Could not get the machine
position at program zero&rdquo;</em> before it ever reaches the offset provider.</p>
<h2 id="the-canvas-marker">The Canvas Marker</h2>
<p>Clicking a row also chooses which coordinate the General Setup canvas draws its triad on. That is the
one write in this branch addressed to another surface, and it behaves differently from the rest in
four ways worth naming.</p>
<ul>
<li><strong>Where it is stored.</strong> The id goes to the equipment-display surface, which assigns it on the
shared user configuration and schedules a debounced save of that file. It is device state, not
project state: it is not written into the project, it outlives the project that set it, and a
failure of the file write itself is logged rather than reported.</li>
<li><strong>What reads it.</strong> The canvas marker resolves the <em>first</em>
<a class="xref" href="../../../../api/Hi.NcParsers.Dependencys.IIsoCoordinateConfig.html">IIsoCoordinateConfig</a> on the active runner — the same object this
panel edits — so an offset edited here moves the marker on the next frame. A marked id the provider
does not answer draws nothing at all, which is the state after a brand switch leaves behind an id
the new provider has never heard of. The marker is also suppressed while its Scene flag is off, and
while no machining chain resolves an anchor.</li>
<li><strong>How it fails.</strong> The handler follows the same optimistic shape as a cell commit — assign, await,
restore and toast on failure — but it is the branch's one call through the plain-JSON helper rather
than the envelope helper, so only a non-2xx status throws there. On mount the panel seeds its
highlight from the same surface and swallows any failure, so an unhighlighted table is not evidence
that nothing is marked.</li>
<li><strong>What triggers it.</strong> The click handler is bound on the whole row and nothing inside the row stops
propagation, so editing a cell or pressing P0 or M0 marks that row as well as doing its own work. A
click on the row already marked returns immediately, which is what keeps repeated cell edits on the
marked row from re-writing the user configuration on every pass.</li>
</ul>
<p>The highlight is a tinted row background, and the row carries the native tooltip <em>&ldquo;Click to mark this
coordinate on the General Setup canvas&rdquo;</em>.</p>
<h2 id="what-a-brand-switch-carries">What a Brand Switch Carries</h2>
<p>The brand switch is the one operation in the branch that treats work coordinates specially. Its panel
carries a checkbox, <strong>Carry work-coordinate XYZ (G54…) into the new brand's table</strong>, ticked by
default, and the flag reaches the endpoint with the brand.</p>
<p>With it set, the offsets are read from the outgoing provider before the preset is swapped in, and
written into the incoming one afterwards — but only for the ids the incoming provider already
enumerates, and only when the two providers are different instances. Everything else is dropped. So
the carry is lossy in a shape that follows directly from the inventory above: Fanuc to Siemens keeps
G54 through G57 and loses G58, G59 and all 48 extended entries; Siemens to Fanuc keeps the same four
and loses the whole G505 series; a switch to Heidenhain keeps at most six.</p>
<p>The instance guard matters on the pair that shares a table. Mazak and Fanuc proxy the same parameter
table, so a switch between them resolves the same instance on both sides and the carry is skipped as
redundant — the values are already there. What a switch keeps, resets and destroys elsewhere is
<a class="xref" href="../machine/brand-switch.html">Brand Switch</a>.</p>
<h2 id="what-the-rows-feed">What the Rows Feed</h2>
<p>A work-coordinate word in a program is resolved into a translation composed onto the block's
program-to-machine transform. Two details of that path do not match what this panel shows.</p>
<p><strong>The run-time lookup walks every provider; so does the panel.</strong> The resolver iterates all offset
providers in the effective list and takes the first non-null answer, which is what lets a brand
table cover its hardware-mapped ids while a second provider covers ids the brand table does not map
— the shape the Fanuc, Mazak and Syntec presets ship in, with the extended <code>G59.1</code><code>G59.9</code> on a
brand-neutral table behind the brand table. The panel lists every provider's ids in the same order
(the first provider that holds an id wins) and writes an edit to the provider that enumerates the
id; the snapshot's kind field still names the first provider only, which is why the caption reads
&ldquo;Fanuc parameter table&rdquo; above rows the second table holds.</p>
<p><strong>The 48 extended rows are reached by <code>G54.1 Pn</code> — and by <code>G54 Pn</code>.</strong> The ISO coordinate syntax
reads G54G59 and G59.1G59.9 from the block's parsed flags, and the additional work coordinate
systems from the <code>G54.1 P</code> capture the parsing bundle writes as a sub-object; both spellings Fanuc's
manual gives that chapter (&ldquo;G54.1 or G54&rdquo;) land in that capture, with or without a space before the
P word, so <code>G54 P48</code> and <code>G54P48</code> select row 48 exactly as <code>G54.1 P48</code> does. A <code>G54</code> with no P word
in its scope stays the plain G54 flag. The row's id is the un-padded <code>G54.1P48</code>, the same key the
Fanuc-family table maps to <code>#7001</code>+, so on Fanuc, Mazak and Syntec the extended rows this panel edits
are the rows the program selects. A row that is selected but was never entered — the brand tables
seed every extended row with zero, as a fresh-battery controller reads them — reports
<code>Coord-WorkOffset--AdditionalZero</code> on the selecting block; the program then runs on the machine
origin, which is almost never what a pallet or fixture offset meant. A zero G54G59 stays silent: it
is a common authoring convention. The Fanuc-family table is also the macro variable lookup, so on
Fanuc and Mazak those rows read back from a macro program as <code>#7001</code><code>#7999</code>, alongside G54G59's
own <code>#5221</code><code>#5328</code>; the Syntec table implements no variable lookup, so on Syntec the rows are
reached by the coordinate words only.</p>
<p><strong>The extended G59.1G59.9 ids live on a second table behind the brand table.</strong> The Fanuc-family
tables map G54G59 and the 48 additional rows and nothing else, so the Fanuc, Mazak and Syntec
presets mount a brand-neutral coordinate table holding only <code>G59.1</code><code>G59.9</code> right behind their
brand table. The two id sets are disjoint, the run-time lookup finds each id on the one provider
that holds it, and this leaf shows the nine rows among the others and writes each edit to the
provider that carries the id. Those rows are seeded with zero and, like <code>G54</code><code>G59</code> and unlike the
48 additional rows, a zero one stays silent: leaving a row of the standard series at zero is a
common authoring convention. A project that kept a <code>G59.x</code> value in its
legacy coordinate table carries it into this second table when the legacy table migrates (each row
lands on the provider that carries its id). A controller saved before the second table existed
gains it on load; one that lost it resolves no offset for a <code>G59.x</code> at all and reports
<code>Coord-WorkOffset--NoTableEntry</code> instead.</p>
<p>Which row an untagged program starts on is the brand preset's static initializer, and the three
answers differ.</p>
<table>
<thead>
<tr>
<th>Preset</th>
<th>Initial coordinate id</th>
<th>Effect</th>
</tr>
</thead>
<tbody>
<tr>
<td>Fanuc, Mazak, Syntec</td>
<td><code>G54</code></td>
<td>the G54 row is active from the first block</td>
</tr>
<tr>
<td>Siemens</td>
<td><code>G500</code></td>
<td>no frame is active; the offset is zero until a frame word appears</td>
</tr>
<tr>
<td>Heidenhain</td>
<td>none</td>
<td>no coordinate section is seeded; the datum cycles set one</td>
</tr>
</tbody>
</table>
<p>On Heidenhain the program's own route into the same table is the datum cycle rather than a G word: a
preset cycle reads the preset row its number names and writes a synthetic coordinate id that the
table resolves back, which is how rows 720 are consumed at run time despite having no row on this
leaf. A word or a cycle that resolves no offset composes a zero translation rather than failing.</p>
<h2 id="layout">Layout</h2>
<ul>
<li><strong>General Setup</strong> Control Tree — the left dock of <code>/general-setup</code>
<ul>
<li><strong>Controller</strong> Node Row
<ul>
<li><strong>Program Data</strong> Group Row — <code>equipment/controller/program-data</code>
<ul>
<li><strong>Work Coordinates (G54…)</strong> Node Row — <code>equipment/controller/program-data/work-coordinates</code></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>Editor Row — the panel of whichever node is selected
<ul>
<li><strong>Work Coordinates (G54…)</strong> Panel
<ul>
<li>No-Runner Body — <em>&quot;No NC runner — load a project first.&quot;</em>; the shared empty state, shown
instead of everything below while the snapshot reports no runner</li>
<li>Absent-Table Body — <em>&quot;No work-coordinate table on the active runner.&quot;</em>; shown instead of
everything below while the panel's own read reports no provider</li>
<li>Header Strip
<ul>
<li>Storage Caption — the one-line note for the snapshot's coordinate kind, left-aligned and grey</li>
<li><strong>Show all</strong> Toggle — right-aligned, dense; rendered only while a row exists that the
always-visible test rejects</li>
</ul>
</li>
<li>Coordinate Table — dense, flat, bordered; no sort, no pagination, no column menu
<ul>
<li>Header Row — <strong>Id</strong>, <strong>X (mm)</strong>, <strong>Y (mm)</strong>, <strong>Z (mm)</strong>, <strong>Actions</strong>. The three axis headers
are literals in the template; <strong>Id</strong> and <strong>Actions</strong> are the shared translated labels</li>
<li>Coordinate Row, one per visible id — the whole row is clickable and carries the tooltip
<em>&ldquo;Click to mark this coordinate on the General Setup canvas&rdquo;</em>; the marked row is tinted
<ul>
<li>Id Label — bold plain text, never editable</li>
<li>Value Numeric Field, one per axis column — no minimum, no maximum, no unit suffix</li>
<li><strong>P0</strong> Button — flat, dense, tooltip <em>&ldquo;Set to the machine position at program zero&rdquo;</em></li>
<li><strong>M0</strong> Button — flat, dense, tooltip <em>&ldquo;Set to machine zero&rdquo;</em></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>Toast — negative, three seconds, the panel's context followed by the server's own message</li>
</ul>
</li>
</ul>
<p>The panel carries no heading, no save button, no unsaved marker and no dialog, and it reports no
structural change, so no edit made on this leaf rebuilds the branch.</p>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../../../index.html">HiNC App Anatomy</a> for git repository links.</p>
<h3 id="web-application">Web Application</h3>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/controlTree/SoftNcWorkCoordinatesPanel.vue</code> — the panel: the storage
caption's five-way switch, the always-visible test and the conditional <strong>Show all</strong> toggle, the
whole-triad cell commit with its non-finite guard, the P0 / M0 actions and their re-read, and the
row click that writes the canvas marker.</li>
<li><code>wwwroot-src/src/components/controlTree/softNcItemTypes.ts</code> — the branch builder: this leaf's node
id and label key, and its position as the first ungated member of the program-data stem.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcEmptyState.vue</code> — the shared no-runner line the panel
opens with.</li>
<li><code>wwwroot-src/src/components/widgets/NumericInput.vue</code> — the value cell: commit on blur or Enter,
the empty-text-to-null parse, and the infinity and NaN literals the panel's handler then rejects.</li>
<li><code>wwwroot-src/src/api/softNcRunner.ts</code> — the snapshot's coordinate-kind field and its parser, the
work-coordinate reader and its row shape, the whole-triad setter, the two zeroing actions, and the
brand switch's carry flag.</li>
<li><code>wwwroot-src/src/api/equipmentSetup.ts</code> — the canvas marker id: the getter the panel seeds its
highlight from and the setter the row click writes, on the equipment-display surface rather than
this branch's own.</li>
<li><code>wwwroot-src/src/api/http.ts</code> — the two helpers this panel mixes: the envelope helper every table
call uses, and the plain-JSON helper the marker call uses.</li>
<li><code>wwwroot-src/src/composables/useSoftNcRunner.ts</code> — the shared snapshot the coordinate kind and the
no-runner guard are read from.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcFramesPanel.vue</code> — the other node on the Siemens
frame table: the same rows through the frame-keyed reader, with an unconditional toggle and a
narrower always-visible test.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcDatumTablePanel.vue</code> — the other node on the
Heidenhain datum table: the preset rows this leaf aliases, plus the rows and the shift table it
does not reach.</li>
<li><code>wwwroot-src/src/components/controlTree/SoftNcBrandPanel.vue</code> — the carry checkbox and its default.</li>
<li><code>wwwroot-src/src/i18n/en/softNc.ts</code> — the node label, the two empty bodies, the five storage
captions, the row and button tooltips, and the five error contexts.</li>
<li><code>wwwroot-src/src/i18n/en/common.ts</code> — the shared <strong>Id</strong> and <strong>Actions</strong> column headers and the
<strong>Show all</strong> label.</li>
<li><code>Mech/SoftNcRunnerController.cs</code> — the REST surface: the coordinate-kind type switch in the
snapshot builder, the reader that enumerates the provider's ids and zero-fills a null offset, the
whole-triad writer, the two zeroing actions with the program-zero lookup's own failure message, and
the brand switch's capture-swap-carry-sweep with its accepted-id filter and its instance guard.</li>
<li><code>Mech/EquipmentSetupDisplayController.cs</code> — the marker id's reader and writer, the user-config slot
it lands in, and the debounced save that follows.</li>
<li><code>Disp/EquipmentSetupDisplayee.cs</code> — the marker's binding to the first offset provider on the active
runner, and the guards that drop it with no chain or with its Scene flag off.</li>
<li><code>Disp/EquipmentSetupDisplayeeConfig.cs</code> — the stored marker id, its <code>G54</code> default and its
serialization.</li>
<li><code>Environments/UserService.cs</code> — the loose save behind the marker write, and the failure it logs
rather than returns.</li>
</ul>
<h3 id="hiapi-engine">HiAPI Engine</h3>
<ul>
<li><code>HiMech/NcParsers/Dependencys/IIsoCoordinateConfig.cs</code> — the offset-provider contract: the id-keyed
get and set, and the id enumeration every row of this leaf comes from.</li>
<li><code>HiMech/NcParsers/Dependencys/IsoCoordinateAddressMap.cs</code> — the Fanuc-family address scheme shared
by the Fanuc and Syntec tables: the two base addresses and the stride, the read that treats an
entry with no address as absent, the write that lays down all three, the enumeration this leaf's
rows follow, the per-address description the native form annotates with, and the seeding of every
entry with zero.</li>
<li><code>HiMech/NcParsers/Dependencys/Fanuc/FanucParameterTable.cs</code> — the Fanuc and Mazak provider: its
offset accessors over the address map, its default table, and the variable lookup that also exposes
the same addresses to a macro program.</li>
<li><code>HiMech/NcParsers/Dependencys/Syntec/SyntecParameterTable.cs</code> — the Syntec provider: the same
address scheme without the variable lookup.</li>
<li><code>HiMech/NcParsers/Dependencys/Siemens/SiemensFrameTable.cs</code> — the Siemens provider: the seeded
G54G57 and G505G599 ids, the cancel-frame handling that keeps one code out of the row set, and
the per-axis translation dictionary neither panel exposes.</li>
<li><code>HiMech/NcParsers/Dependencys/Heidenhain/HeidenhainDatumTable.cs</code> — the Heidenhain provider: the
twenty preset and twenty shift rows, the G54G59 alias onto preset rows 16, and the synthetic ids
the datum cycles resolve through.</li>
<li><code>HiMech/NcParsers/Dependencys/Generic/IsoCoordinateTable.cs</code> — the brand-neutral table behind the
<code>generic</code> arm, and the fifteen ids it seeds.</li>
<li><code>HiMech/NcParsers/Dependencys/Generic/IsoCoordinateTableProxy.cs</code> — its get-or-create proxy, the
other shape a runner can carry it in.</li>
<li><code>HiMech/NcParsers/Dependencys/Siemens/SiemensFrameTableProxy.cs</code>,
<code>HiMech/NcParsers/Dependencys/Heidenhain/HeidenhainDatumTableProxy.cs</code> — the seedless proxies those
two brands carry: each installs a fresh table into a project holding none, which is why those two
brands open on their constructors' defaults.</li>
<li><code>HiMech/NcParsers/Dependencys/Fanuc/FanucParameterTableProxy.cs</code> — the seeded proxy the Fanuc and
Mazak presets share, and the reason those two resolve one instance.</li>
<li><code>HiMech/NcParsers/SoftNcRunner.cs</code> — the five brand presets and the offset provider each carries,
the proxy resolution every read goes through, and the legacy import that replays an older
coordinate table into whichever provider resolves.</li>
<li><code>HiMech/NcParsers/LogicSyntaxs/CoordinateOffsetUtil.cs</code> — the run-time resolver that walks every
provider and takes the first non-null answer, and the translation it composes.</li>
<li><code>HiMech/NcParsers/LogicSyntaxs/IsoCoordinateOffsetSyntax.cs</code> — the G-word path: the flag vocabulary
it consumes, the modal lookback that keeps a coordinate alive across blocks, and the zero fallback
when nothing answers.</li>
<li><code>HiMech/NcParsers/LogicSyntaxs/Heidenhain/HeidenhainCoordinateOffsetSyntax.cs</code> — the datum-cycle
path into the same table, and the synthetic ids it writes.</li>
<li><code>HiMech/NcParsers/Keywords/Generic/IsoKeywords.cs</code> — the recognised G-word series.</li>
<li><code>HiMech/NcParsers/Keywords/Siemens/SiemensKeywords.cs</code> — the extended frame series and the cancel
frame.</li>
<li><code>HiMech/NcParsers/Initializers/StaticInitializer.cs</code> — the three brand defaults for the coordinate
active at the first block.</li>
<li><code>HiMech/Machining/MachiningEquipmentUtils/MachiningEquipmentUtil.cs</code> — the program-zero machine
position P0 writes: the reflection that zeroes every dynamic branch of the equipment assembly, and
the anchor fallback when the workpiece declares none.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="index.html">Program Data Plane</a> — the plane this leaf opens, and the per-case tables beside it
that a brand switch keeps or sweeps</li>
<li><a class="xref" href="frames.html">Frames</a> — the other node on the Siemens frame table, where the same rows are
edited as settable frames rather than as G54 offsets</li>
<li><a class="xref" href="datum-tables.html">Datum Tables</a> — the other node on the Heidenhain datum table, carrying the
preset rows this leaf aliases plus the fourteen rows and the shift table it cannot reach</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,34 @@
<div id="sidetoggle">
<div>
<div class="sidefilter">
<form class="toc-filter">
<span class="glyphicon glyphicon-filter filter-icon"></span>
<span class="glyphicon glyphicon-remove clear-icon" id="toc_filter_clear"></span>
<input type="text" id="toc_filter_input" placeholder="Filter by title" onkeypress="if(event.keyCode==13) {return false;}">
</form>
</div>
<div class="sidetoc">
<div class="toc" id="toc">
<ul class="nav level1">
<li>
<a href="index.html" name="" title="Overview">Overview</a>
</li>
<li>
<a href="brand-matrix.html" name="" title="Brand Matrix">Brand Matrix</a>
</li>
<li>
<a href="editing-contract.html" name="" title="Editing Contract">Editing Contract</a>
</li>
<li>
<a href="machine/index.html" name="machine/toc.html" title="Machine / Controller Plane">Machine / Controller Plane</a>
</li>
<li>
<a href="program-data/index.html" name="program-data/toc.html" title="Program Data Plane">Program Data Plane</a>
</li>
</ul>
</div>
</div>
</div>
</div>
@@ -0,0 +1,2 @@
{"items":[{"name":"Overview","href":"index.html","topicHref":"index.html"},{"name":"Brand Matrix","href":"brand-matrix.html","topicHref":"brand-matrix.html"},{"name":"Editing Contract","href":"editing-contract.html","topicHref":"editing-contract.html"},{"name":"Machine / Controller Plane","href":"machine/index.html","tocHref":"machine/toc.html","topicHref":"machine/index.html","homepage":"machine/index.html"},{"name":"Program Data Plane","href":"program-data/index.html","tocHref":"program-data/toc.html","topicHref":"program-data/index.html","homepage":"program-data/index.html"}]}
@@ -0,0 +1,323 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Fixture | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Fixture | HiAPI-C# 2025 ">
<link rel="icon" href="../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../index.html">
<img id="logo" class="svg" src="../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-Fixture">
<h1 id="fixture">Fixture</h1>
<p>The fixture is what holds the workpiece on the machine table: one geometry, and the two anchor
transformers that place that geometry against the workpiece and against the table. It has two
surfaces:</p>
<ul>
<li>the <code>equipment/fixture</code> branch of the General Setup page's Control Tree
(<code>/general-setup?tree=equipment/fixture</code>), an equipment child of the General Setup group between
Coolant and Workpiece. <code>/fixture</code> and anything below it redirects there;</li>
<li>in the WPF client, a sub-window opened from the <a class="xref" href="../shell/main-panel.html">Main Panel</a>.</li>
</ul>
<p>Both edit the one fixture the project owns.</p>
<ul>
<li>Key Model:
<ul>
<li><a class="xref" href="../../api/Hi.NcMech.Fixtures.Fixture.html">Fixture</a>
On the web it is addressed by an IndexService key handed to the
<a class="xref" href="../widget/object-management-menu-button.html">Object Management Menu Button</a> and installed back onto
the project through the controller's UpdateFixture. On the WPF client the same button is wired
with a TargetObjectGetter / TargetObjectSetter pair (see that page for the design pattern).</li>
</ul>
</li>
<li>Assistant Model:
<ul>
<li><a class="xref" href="../../api/Hi.MachiningProcs.LocalProjectService.html">LocalProjectService</a> — owns the fixture as
<a class="xref" href="../../api/Hi.MachiningProcs.LocalProjectService.html#Hi_MachiningProcs_LocalProjectService_Fixture">Fixture</a>.</li>
</ul>
</li>
</ul>
<p>The displayee differs by client. The web branch has no fixture-only canvas: it shares the General
Setup canvas, whose displayee is the equipment-setup displayee (<code>Disp/EquipmentSetupDisplayee.cs</code>) —
the merged fixture + workpiece scene with the anchor, buckle and controller-coordinate overlays. Its
option set is the equipment-setup config (<code>Disp/EquipmentSetupDisplayeeConfig.cs</code>), carried on
<code>UserService.UserConfig</code> as its <code>EquipmentSetupDisplayeeConfig</code> property. The WPF sub-window has a
canvas of its own, whose displayee is <a class="xref" href="../../api/Hi.NcMech.Fixtures.FixtureEditorDisplayee.html">FixtureEditorDisplayee</a> configured by
<a class="xref" href="../../api/Hi.NcMech.Fixtures.FixtureEditorDisplayeeConfig.html">FixtureEditorDisplayeeConfig</a>, taken from <code>UserService.UserConfig</code> as its
<code>FixtureSetupDisplayeeConfig</code> property.</p>
<h2 id="layout">Layout</h2>
<h3 id="control-tree-branch">Control Tree Branch</h3>
<p>The root panel is rendered inline by the dock's primary editor pane, so it has no title label of its
own — the wrapping expansion header carries the selection breadcrumb. Every item below is a tree
node whose editor is swapped into that same PRIMARY row.</p>
<ul>
<li>Fixture Root Panel — <code>equipment/fixture</code>, item type <code>FixtureRoot</code>
<ul>
<li><a class="xref" href="../widget/object-management-menu-button.html">Object Management Menu Button</a> — file extension
<code>.Fixture</code>, load type <code>Hi.NcMech.Fixtures.Fixture, HiMech</code>, rel file <code>Fixture.xml</code>, based at the
project directory. Load / Save As / Copy / Paste / XML.</li>
<li>Geometry Type Badge — the attached geometry's kind name, <code>none</code> when the slot is empty.</li>
<li>Intro caption, and an empty-state caption while no fixture key is minted.</li>
</ul>
</li>
<li><code>equipment/fixture/geometry</code>&ldquo;Geometry&rdquo;, a Geometry slot backing
<a class="xref" href="../../api/Hi.NcMech.Fixtures.Fixture.html#Hi_NcMech_Fixtures_Fixture_Geom">Geom</a>. The slot's own panel is the kind picker
(<a class="xref" href="../geom/geom-manage-control.html">Geometry Management Control</a>, selector only); the picked kind's
editor is the slot's child item. Kinds offered: <code>Box3d</code>, <code>Cylindroid</code>, <code>StlFile</code>,
<code>TransformationGeom</code>, <code>GeomCombination</code>, plus None. The geometry must satisfy <code>IStlSource</code>, so the
runtime-only voxel <code>CubeTreeFile</code> is not among them.</li>
<li><code>equipment/fixture/anchor</code>&ldquo;Anchor&rdquo;, a Group stem whose info text states how the fixture is
placed. Two Transformer slots hang from it, each a kind picker
(<a class="xref" href="../geom/transformer-panel.html">Transformer Manage Panel</a>) over the picked kind's child editor:
<ul>
<li><code>equipment/fixture/geom-to-workpiece</code>&ldquo;Geom To Workpiece&rdquo;,
<a class="xref" href="../../api/Hi.NcMech.Fixtures.Fixture.html#Hi_NcMech_Fixtures_Fixture_GeomToWorkpieceTransformer">GeomToWorkpieceTransformer</a>, which places the workpiece buckle
— where the workpiece attaches — relative to the fixture geometry (see
<a class="xref" href="workpiece.html">Workpiece</a>).</li>
<li><code>equipment/fixture/geom-to-table</code>&ldquo;Geom To Table&rdquo;,
<a class="xref" href="../../api/Hi.NcMech.Fixtures.Fixture.html#Hi_NcMech_Fixtures_Fixture_GeomToTableTransformer">GeomToTableTransformer</a>, which pins the fixture geometry onto
the machine table buckle.</li>
<li>Kinds offered on both: <code>StaticTranslation</code>, <code>StaticRotation</code>, <code>StaticFreeform</code>,
<code>DynamicTranslation</code>, <code>DynamicRotation</code>, <code>GeneralTransform</code>, <code>NoTransform</code>.</li>
</ul>
</li>
<li>Shared slave view: the General Setup equipment canvas in the page's MAIN column, shared with
Machine Tool, Workpiece and Controller (see <a class="xref" href="index.html">General Setup Page</a>). Its
Display Options dropdown carries this branch's share of the scene — the <strong>Fixture</strong>
Solid / Edge / Hide radio group, and the Fixture Geom Anchor, Workpiece Buckle and Table Buckle
flags in the Anchors group.</li>
</ul>
<h3 id="wpf-page">WPF Page</h3>
<ul>
<li>Head Line
<ul>
<li><a class="xref" href="../widget/object-management-menu-button.html">Object Management Menu Button</a> — file extension
<code>.Fixture</code>, load type <a class="xref" href="../../api/Hi.NcMech.Fixtures.Fixture.html">Fixture</a>, rel file <code>Fixture.xml</code>. The pointed
Editor Panel is the Management Tabs Panel, a ContentPresenter the button swaps between the tab
stack and XML mode.</li>
<li>Title Label</li>
</ul>
</li>
<li>Management Tabs Panel — the same content the tree renders as nodes, stacked as tabs:
<ul>
<li>Geometry Tab — <a class="xref" href="../geom/geom-manage-control.html">Geometry Management Control</a> over
<a class="xref" href="../../api/Hi.NcMech.Fixtures.Fixture.html#Hi_NcMech_Fixtures_Fixture_Geom">Geom</a>.</li>
<li>Anchor Tab — a nested tab stack, each tab a
<a class="xref" href="../geom/transformer-panel.html">Transformer Manage Panel</a>:
<ul>
<li>Geom To Workpiece Tab</li>
<li>Geom To Table Tab</li>
</ul>
</li>
</ul>
</li>
<li>Viewer Panel
<ul>
<li>Viewer ToolBar
<ul>
<li><a class="xref" href="../widget/renderingcanvas-tool-bar.html">RenderingCanvas Tool Bar</a></li>
<li>Display Options menu — the Show Geom Anchor, Show Workpiece Buckle and Show Table Buckle
checkboxes and the Solid / Edge / Hide rendering-mode radio group, all writing
<a class="xref" href="../../api/Hi.NcMech.Fixtures.FixtureEditorDisplayeeConfig.html">FixtureEditorDisplayeeConfig</a>.</li>
</ul>
</li>
<li>RenderingCanvas — its <a class="xref" href="../../api/Hi.Disp.DispEngine.html">DispEngine</a>.<a class="xref" href="../../api/Hi.Disp.DispEngine.html#Hi_Disp_DispEngine_Displayee">Displayee</a> is
<a class="xref" href="../../api/Hi.NcMech.Fixtures.FixtureEditorDisplayee.html">FixtureEditorDisplayee</a>.</li>
</ul>
</li>
</ul>
<p>Both clients put a draggable divider between the editor column and the canvas: a GridSplitter in the
WPF page, nested draggable splitters on the web page (dock / content column / canvas), plus the
dock's own height divider between the tree row and the editor row.</p>
<h2 id="behavior">Behavior</h2>
<ul>
<li><a class="xref" href="../../api/Hi.NcMech.Fixtures.Fixture.html#Hi_NcMech_Fixtures_Fixture_ClearGeomCache">ClearGeomCache()</a> runs after any change at or below the branch. On
the web each node's afterChange chain ends in the controller's ClearGeometryCache, which calls it;
in WPF the page calls it directly from the geometry control's setter and update callbacks.</li>
<li>The two anchor slots re-commit their swap-in <em>before</em> clearing the cache: an inner-value edit
re-posts UpdateGeomToWorkpieceTransformer / UpdateGeomToTableTransformer, so the object the
switchboard produced is always the one installed on the owning field when the redraw happens.</li>
<li>Load / Paste / XML-Apply only swap the IndexService entry. The tree root then installs the result
onto the project through UpdateFixture — the equipment setter re-attaches the buckles itself —
re-runs Initialize to re-mint the branch's keys, and clears the geometry cache. The button's XML
dialog re-emits its object-loaded event on Apply, so that install and refresh chain runs exactly
once.</li>
<li>Kind pickers are parent-aware. Picking a geometry kind calls CreateGeometry, which installs the
new object on <a class="xref" href="../../api/Hi.NcMech.Fixtures.Fixture.html#Hi_NcMech_Fixtures_Fixture_Geom">Geom</a> server-side and maps the literal <code>None</code> to
null; picking a transformer kind creates the object and then rebinds the owning field.</li>
<li>View snapping differs by client. The WPF page snaps its canvas to the isometric view when the
displayee is bound, and again whenever a new geometry object is set, on the assumption that a
shape swap changes the scene more than an edit within one shape does. The shared web canvas snaps
once, when its displayee is bound — the display controller's initialize does it server-side.
Afterwards a geometry set only clears the cache, and the operator re-frames from the
<a class="xref" href="../widget/renderingcanvas-tool-bar.html">RenderingCanvas Tool Bar</a>'s view picker.</li>
<li>Both canvases draw the live model, so an edit shows on the next frame with no further calls.</li>
</ul>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../index.html">HiNC App Anatomy</a> for git repository links.</p>
<h3 id="web-application">Web Application</h3>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/controlTree/useControlTreeHost.ts</code> — builds the <code>equipment/fixture</code>
root (item type <code>FixtureRoot</code>, keyed on the IndexService fixture key) with its <code>geometry</code> slot and
the <code>anchor</code> Group holding <code>geom-to-workpiece</code> and <code>geom-to-table</code>; owns the clear-cache and
transformer-rebind afterChange chains, the create hooks and the Object Management install
handlers.</li>
<li><code>wwwroot-src/src/components/controlTree/PrimarySlavePanel.vue</code> — renders the root editor inline:
the Object Management button, the geometry-type badge and the intro / empty-state captions.</li>
<li><code>wwwroot-src/src/components/controlTree/itemTypes.ts</code> — the ItemType registry that binds the
branch's Geometry and Transformer slots and every concrete kind to their panels, and grows the
sub-tree from the indexed object's type.</li>
<li><code>wwwroot-src/src/components/controlTree/GeometrySlotPanel.vue</code> — the Geometry node's kind picker.</li>
<li><code>wwwroot-src/src/components/controlTree/TransformerSlotPanel.vue</code> — the Geom To Workpiece and
Geom To Table nodes' kind picker.</li>
<li><code>wwwroot-src/src/components/controlTree/SoleEditorPanel.vue</code> — hosts the concrete kind's own
editor once a geometry or transformer kind is chosen.</li>
<li><code>wwwroot-src/src/components/controlTree/ControlTreeDock.vue</code> — the left dock: the Control Tree row
over the PRIMARY editor row, with a draggable height divider.</li>
<li><code>wwwroot-src/src/pages/GeneralSetupPage.vue</code> — the three-column host page (dock / content column /
canvas) that provides the equipment-scope control-tree host.</li>
<li><code>wwwroot-src/src/components/mech/EquipmentSetupPanel.vue</code> — the shared canvas column: the
RenderingCanvasToolBar, the Display Options dropdown and the RenderingCanvas.</li>
<li><code>wwwroot-src/src/components/widgets/ObjectManagementMenuButton.vue</code> — the reused
object-management dropdown, keyed entirely on its <code>modelKey</code> (see
<a class="xref" href="../widget/object-management-menu-button.html">Object Management Menu Button</a>).</li>
<li><code>wwwroot-src/src/components/geom/GeometryEditor.vue</code> — the generic geometry kind switchboard
reached from the Geometry slot panel, including its None (unset) entry.</li>
<li><code>wwwroot-src/src/components/topo/TransformerSelectPanel.vue</code> — the generic transformer kind
switchboard reached from the Transformer slot panels.</li>
<li><code>wwwroot-src/src/api/fixture.ts</code> — typed client for <code>/api/Fixture/*</code>: Initialize, UpdateFixture,
the three Get…Type reads, UpdateGeometry, the two Update…Transformer rebinds, CreateGeometry and
ClearGeometryCache.</li>
<li><code>wwwroot-src/src/api/equipmentSetup.ts</code> — typed client for the shared canvas: binds the
equipment-setup displayee onto the connection's engine and drives its display options.</li>
<li><code>wwwroot-src/src/router/routes.ts</code> — declares <code>general-setup</code> and the <code>fixture/:rest(.*)*</code>
redirect onto <code>?tree=equipment/fixture</code>.</li>
<li><code>Mech/FixtureController.cs</code> — REST surface at <code>/api/Fixture</code>: Initialize, UpdateFixture,
GetGeometryType, GetGeomToWorkpieceType, GetGeomToTableType, UpdateGeometry,
UpdateGeomToWorkpieceTransformer, UpdateGeomToTableTransformer, CreateGeometry,
IndexCurrentGeometry and ClearGeometryCache.</li>
<li><code>Mech/EquipmentSetupDisplayController.cs</code><code>/api/mech/equipment-setup-display</code>: binds and
configures the displayee per rendering connection and serves its whole option set — the three
rendering-mode endpoints (fixture, raw-geom, ideal-geom), show-machine, show-tool,
show-dimension-bar, show-fixture-geom-anchor, show-workpiece-buckle, show-table-buckle,
show-workpiece-geom-anchor, show-program-zero-anchor, show-meshed-geom,
show-controller-coordinate and controller-coordinate-id.</li>
<li><code>Disp/EquipmentSetupDisplayee.cs</code> — the displayee the General Setup canvas renders: the merged
fixture + workpiece scene with the anchor, buckle and controller-coordinate overlays.</li>
</ul>
<h3 id="hiapi-engine">HiAPI Engine</h3>
<ul>
<li><code>HiMech/NcMech/Fixtures/Fixture.cs</code> — the key model: the geometry, the table and workpiece
buckles, the geom anchor, the two anchor transformers and ClearGeomCache.</li>
<li><code>HiMech/NcMech/Fixtures/FixtureEditorDisplayee.cs</code> — the WPF fixture canvas's displayee.</li>
<li><code>HiMech/NcMech/Fixtures/FixtureEditorDisplayeeConfig.cs</code> — its option set: ShowGeomAnchor,
ShowWorkpieceBuckle, ShowTableBuckle and the rendering mode.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="../util/mech-builder.html">Mechanism Builder Page</a> — reuses this pages parent-aware transformer rebind pattern</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,610 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hidden Controller Branches | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Hidden Controller Branches | HiAPI-C# 2025 ">
<link rel="icon" href="../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../index.html">
<img id="logo" class="svg" src="../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-HiddenControllers">
<h1 id="hidden-controller-branches">Hidden Controller Branches</h1>
<p>Two Control-Tree nodes on the General Setup page are absent from a fresh installation: <strong>CSV
Controller</strong> and <strong>CL Controller</strong>, on <code>/general-setup</code> under the ids <code>equipment/controller-csv</code> and
<code>equipment/controller-cl</code>. Each is the entire editing face of one resident non-brand runner suit —
the CSV control-table pipeline and the NX-CL (CLSF) pipeline — and each is minted only when its
device-local Preference checkbox is on or the current URL targets it. This page is the answer to
&ldquo;why is that node not in the tree&rdquo;, because the equipment tree withholds a node in two different ways
and only one of them can be talked out of it.</p>
<div class="IMPORTANT">
<h5>Important</h5>
<p>Anatomy makes no claim that any id named here is stable across versions. The migration hop that
keeps an older <code>?tree=</code> value resolving is described in
<a class="xref" href="../platform/tree-ids-and-routes.html">Tree Ids and Routes</a>.</p>
</div>
<h2 id="two-kinds-of-invisibility">Two Kinds of Invisibility</h2>
<table>
<thead>
<tr>
<th style="text-align: left;"></th>
<th>Preference-gated</th>
<th>Brand-gated</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left;">Which nodes</td>
<td><code>equipment/controller-csv</code>, <code>equipment/controller-cl</code></td>
<td>every gated leaf under <code>equipment/controller</code></td>
</tr>
<tr>
<td style="text-align: left;">What decides</td>
<td>a checkbox stored in the browser</td>
<td>the active runner's dependency list</td>
</tr>
<tr>
<td style="text-align: left;">Where the decision lives</td>
<td>one device</td>
<td>the project</td>
</tr>
<tr>
<td style="text-align: left;">Reachable by a link anyway</td>
<td>yes, and deliberately</td>
<td>no</td>
</tr>
<tr>
<td style="text-align: left;">What a link to it does</td>
<td>mints the node and selects it</td>
<td>nothing at all</td>
</tr>
</tbody>
</table>
<p>The distinction is not a matter of degree. A preference-gated node exists in the code that builds
the tree and is simply not pushed; the same build reads the URL, so naming the node in a link is
enough to bring it back for that visit. A brand-gated node has no such second door: the branch
builder never creates it, and the selection code refuses an id the built tree does not carry, so
there is nothing for a link to select. Which leaf that branch grows on which brand, and the snapshot
flag behind each one, is <a class="xref" href="controller/brand-matrix.html">Brand Matrix</a>.</p>
<h2 id="preference-gated-the-two-runner-suit-nodes">Preference-Gated: The Two Runner-Suit Nodes</h2>
<p>The equipment tree host mints each of the two nodes when <strong>a project is open</strong> and <strong>either</strong> the
node's checkbox is on <strong>or</strong> the route's tree id begins with that node's id. All three parts matter.</p>
<p><strong>With no project open neither node is built</strong>, whatever the checkboxes say — the same guard that
keeps the Controller branch childless before a project is loaded. The consequence is not a dead end:
loading a project bumps the layout's project epoch, which destroys and rebuilds the page, and the
rebuilt host runs the build again and then adopts whatever <code>?tree=</code> the URL still carries. A link
opened against an empty application therefore lands on its node once a project is opened, without
the link being touched.</p>
<p><strong>The checkboxes are device-local.</strong> Both are held in one browser-storage record of view
preferences, off by default, written back a moment after each flip, and never sent to the server —
so the setting belongs to a browser rather than to a user account or to a project. They are the
<strong>CSV Controller</strong> and <strong>CL Controller</strong> entries of the app menu bar's Preference dropdown, described
in <a class="xref" href="../shell/preference-menu.html">Preference Menu</a>.</p>
<p><strong>Only the route reveals — not the persisted last selection.</strong> The host also remembers the last node
selected on each tree page, and that memory deliberately mints nothing: the landing selection is
honoured only when the id it names is already in the built tree, and falls back to the <code>equipment</code>
group root when it is not. Reveal-by-route has to happen while the tree is being built, because the
code that adopts a <code>?tree=</code> id afterwards refuses an id the tree lacks.</p>
<h3 id="switching-a-box-off-while-standing-on-its-node">Switching a box off while standing on its node</h3>
<p>The host watches both checkboxes and rebuilds the whole equipment tree when either flips. Before the
rebuild it checks whether the current selection is one of the nodes that just lost its box, and when
it is, it does two things in this order:</p>
<ol>
<li>Replaces the URL's <code>tree</code> query with <code>equipment</code>.</li>
<li>Moves the selection to the <code>equipment</code> group root.</li>
</ol>
<p>Only then does the tree rebuild. The order is the point: the URL already mirrors the selection, and
the URL is exactly what reveals an unchecked node, so a selection left standing there would leave its
id in the query, and the next build's prefix test would re-mint the node that was just switched off.
The reader lands on the General Setup group panel, whose clickable child list no longer carries the
row. The selection is moved directly rather than through the host's switch request, which would
matter only for a panel registering the before-switch gate; neither of these two does.</p>
<p>Switching a box off while the selection is elsewhere takes no such detour — the rebuild simply drops
the node from the tree and from the group panel's child list.</p>
<h3 id="what-the-two-nodes-are-not">What the two nodes are not</h3>
<p>Both are childless leaves. Neither declares a child builder, so nothing grows beneath them; neither
registers a content view, so the General Setup content column keeps its own empty hint for both; and
neither panel reports a structural change, so nothing either panel does rebuilds any part of the
tree. There is no Object-Management menu on either: a CSV or CL pipeline is not loaded, pasted or
saved as a file of its own the way a brand controller is.</p>
<h2 id="brand-gated-the-node-that-was-never-minted">Brand-Gated: The Node That Was Never Minted</h2>
<p>Under <code>equipment/controller</code> the node set is grown from a snapshot of the active NC runner, and a
leaf whose backing dependency that runner does not resolve is never created. Nothing reveals it. A
link naming such an id — <code>equipment/controller/program-data/frames</code> on a project running a Fanuc
runner, say — reaches the host, finds no such node in the built tree, and is dropped: the selection
is left as it was, the URL keeps the id it was given, and no redirect and no message follow. On a
first load that means the editor row shows its own hint, <em>&quot;Select an item in the Control Tree to edit
it here.&quot;</em>, under a tree in which the node the link names is simply not present.</p>
<p>The two behaviours meet at the same line of code from opposite sides. The runner-suit nodes are read
out of the URL <strong>while the tree is built</strong>, so by the time that check runs they exist; a brand-gated
leaf is not, so the check is the end of the road for it. That is why a checkbox can be overruled by a
link and a runner cannot.</p>
<h2 id="what-the-preference-captions-mean">What the Preference Captions Mean</h2>
<p>Under each checkbox sits a caption reading either <em>&ldquo;This project plays CSV&rdquo;</em> / <em>&ldquo;This project plays
CL&rdquo;</em> or <em>&ldquo;Not used by this project&rdquo;</em>. It is the informed-choice half of the design: the menu answers
whether the loaded project plays that kind, so the decision to show a node is made with evidence
rather than by trial. The caption is read when the Preference dropdown opens rather than on page
load, and both runner snapshots are fetched together; with no project open both captions are blank,
and a snapshot that fails to load leaves its own caption blank rather than asserting either answer.</p>
<p>The evidence is a scan of the loaded project, per kind:</p>
<ul>
<li>A mission <strong>Program File</strong> command whose effective kind is that kind — its explicit kind, or, for
the default <em>Auto</em>, the kind detected from the file extension: <code>.cl</code>, <code>.cls</code> and <code>.clsf</code> are CL,
<code>.csv</code> is CSV, and every other extension is brand NC code
(<a class="xref" href="../../api/Hi.MachiningProcs.NcKindUtil.html#Hi_MachiningProcs_NcKindUtil_DetectByPath_">DetectByPath</a><small>(API)</small>).</li>
<li>A mission <strong>script</strong> command whose text contains <code>CsvFile(</code> or <code>ClFile(</code>. It is a bare substring
test rather than a list of verbs. The play verbs end in it —
<a class="xref" href="../../api/Hi.MachiningProcs.SessionShell.html#Hi_MachiningProcs_SessionShell_PlayCsvFile_">PlayCsvFile</a><small>(API)</small> and
<a class="xref" href="../../api/Hi.MachiningProcs.SessionShell.html#Hi_MachiningProcs_SessionShell_PlayClFile_">PlayClFile</a><small>(API)</small> — and on the CSV side so do
the two sensor-mapping verbs,
<a class="xref" href="../../api/Hi.MachiningProcs.SessionShell.html#Hi_MachiningProcs_SessionShell_MapSingleByCsvFile_">MapSingleByCsvFile</a><small>(API)</small> and
<a class="xref" href="../../api/Hi.MachiningProcs.SessionShell.html#Hi_MachiningProcs_SessionShell_MapSeriesByCsvFile_">MapSeriesByCsvFile</a><small>(API)</small>, so a script that
only maps recorded telemetry onto steps counts as CSV evidence though it plays no control table.
The CL side carries no such neighbour: every member whose call text ends in <code>ClFile(</code> plays or runs
a CLSF file.</li>
<li>For CL only, a machining chain that is a <a class="xref" href="../../api/HiMachining.Milling.ClMillingDevice.html">ClMillingDevice</a>, since a
pure-CL project plays nothing else.</li>
</ul>
<p>Three properties of that scan are easy to guess wrong. It walks the whole mission, the entries of a
nested list command included, and it walks <strong>through</strong> each command's enable wrapper without reading
it — a Program File command that is switched off still counts as evidence. The script test cuts the
other way too: a play routed through the generic NC entry point with a <code>.csv</code> path is deliberately
not matched, because a bare <code>.csv</code> appears in step and shot output templates far more often than in
play paths. And the answer feeds the caption and nothing else — <strong>usage evidence mints no node.</strong>
The tree is built from the checkbox and the route, and from nothing the scan reports.</p>
<h2 id="how-both-panels-edit">How Both Panels Edit</h2>
<p>Neither node belongs to the Controller branch, so that branch's
<a class="xref" href="controller/editing-contract.html">Editing Contract</a> does not govern them. Both panels
nonetheless follow most of its shape, and the places they do not are worth naming.</p>
<p><strong>Shared with the contract.</strong> Each panel fetches its own snapshot once on mount and owns no store.
Each opens with two guards, in order: <em>&quot;No project loaded.&quot;</em> from the snapshot's own project flag,
then a line naming the absent config — <em>&quot;No CSV column config on the CSV runner.&quot;</em> or <em>&quot;No CLSF
config on the CL runner.&quot;</em> Every control commits on its own, with no save button and no dirty
marker. A failed write raises one negative toast, three seconds, composed as the panel's localized
context followed by the server's own English sentence, with a console line beside it, and leaves no
inline error state behind once it expires. Both REST surfaces answer a missing config inside a
success envelope rather than with an error status, and the shared write helper turns that envelope
into a thrown error; the read helper inspects no envelope, and neither endpoint has a non-200 path,
so a read fails only on transport. A failed read therefore leaves the empty snapshot in place and the
panel renders its <em>&quot;No project loaded.&quot;</em> line — the same body an absent project produces, with the
toast as the only signal that the two differ.</p>
<p><strong>Where they part from it.</strong> The CSV panel is not optimistic: its text fields are bound to a local
draft, so the screen already shows the new text, and the request is awaited before the panel's
server mirror is updated; a failure restores the field from that mirror. The CL panel is optimistic
in the contract's own shape, assigning first and restoring on failure, and its two numeric fields are
the shared numeric widget, committing on blur or on Enter and never per keystroke; the full widget
contract is <a class="xref" href="../widget/numeric-input.html">Numeric Input</a>.</p>
<p><strong>Both panels guard on equality, and the guard is the panel's rather than the widget's.</strong> Every
commit handler on both — the CSV tag fields, the two CL rates and the tool-house toggle — compares
the incoming value against the fetched server mirror first and returns when the two match, so
committing an untouched field, or blurring after an Enter that already succeeded, sends nothing.
That is the departure from the Controller branch, whose numeric leaves carry no such comparison and
lean on the shared widget alone, and the widget emits on every blur and every Enter. The one control
across the two panels with no such comparison is the CL chip field.</p>
<h2 id="csv-controller">CSV Controller</h2>
<p>The node is the face of the project's resident CSV runner suit
(<a class="xref" href="../../api/Hi.MachiningProcs.MachiningProject.html#Hi_MachiningProcs_MachiningProject_CsvRunnerSuit">CsvRunnerSuit</a>), a whole runner suit constructed alongside
the brand one and never absent from a loaded project. Its runner is a
<a class="xref" href="../../api/Hi.NcParsers.SoftNcRunner.html">SoftNcRunner</a> assembled as the CSV pipeline by the factory
<a class="xref" href="../../api/Hi.Numerical.CsvParsers.GeneralCsvRunner.html">GeneralCsvRunner</a>, and it replays a CSV control table. A project file
that stores the column configuration flat, outside any suit, has that element migrated into the
suit's pipeline as it loads, so such a file keeps its tags.</p>
<p>The whole face is one flat configuration, <a class="xref" href="../../api/Hi.Numerical.CsvParsers.CsvRunnerConfig.html">CsvRunnerConfig</a> — no brand
presets, no per-case tables and no native parameter form, which is why the node is a leaf rather than
a branch. The panel's caption says what it edits: <em>&quot;Column tags of the CSV control table (matched
against the header line). Played by a mission Program File of CSV kind or a script's
<code>PlayCsvFile(&quot;&quot;)</code>.&quot;</em> The header line is the file's first row, split on commas; each title then has
quote characters stripped from its two ends and is trimmed of whitespace, in that order, so a title
whose opening quote sits behind a space keeps that quote and never matches its tag.</p>
<table>
<thead>
<tr>
<th>Field label</th>
<th>Default tag</th>
<th>What reads it</th>
</tr>
</thead>
<tbody>
<tr>
<td>Machine coordinate prefix</td>
<td><code>MC.</code></td>
<td>the machine-coordinate columns, composed as the prefix plus <code>X</code>, <code>Y</code>, <code>Z</code>, <code>A</code>, <code>B</code>, <code>C</code></td>
</tr>
<tr>
<td>Cutter location prefix</td>
<td><code>CL.</code></td>
<td>nothing in the shipped pipeline</td>
</tr>
<tr>
<td>Tool id column</td>
<td><code>ToolId</code></td>
<td>the tool-change section</td>
</tr>
<tr>
<td>Spindle speed column (rpm)</td>
<td><code>SpindleSpeed_rpm</code></td>
<td>the spindle section</td>
</tr>
<tr>
<td>Spindle direction column</td>
<td><code>Spd.Dir.</code></td>
<td>the spindle section, parsed as a direction name; a speed with no direction turns clockwise</td>
</tr>
<tr>
<td>Feedrate column (mm/min)</td>
<td><code>Feedrate_mmdmin</code></td>
<td>the feedrate section</td>
</tr>
<tr>
<td>Step duration column (s)</td>
<td><code>StepDuration</code></td>
<td>the recorded-timing section</td>
</tr>
<tr>
<td>Actual time column</td>
<td><code>ActualTime</code></td>
<td>the recorded-timing section</td>
</tr>
<tr>
<td>Coolant column</td>
<td><code>Coolant</code></td>
<td>the coolant section</td>
</tr>
<tr>
<td>Line-begin C# script column</td>
<td><code>LineBeginCsScript</code></td>
<td>the script section, run before the row</td>
</tr>
<tr>
<td>Line-end C# script column</td>
<td><code>LineEndCsScript</code></td>
<td>the script section, run after the row</td>
</tr>
</tbody>
</table>
<p>Four things that table does not show on its own:</p>
<ul>
<li><strong>The cutter-location prefix is stored, serialized and editable, and no syntax in the shipped CSV
pipeline reads it.</strong> Columns under it are not consumed as coordinates; they survive into the
residual telemetry below.</li>
<li><strong>Naming a column consumes it.</strong> Each syntax removes the columns its tags name from the decoded
row, and whatever is left over is carried onto the step as recorded data — sensor channels, file
and line bookkeeping, anything else the file holds. So clearing a tag does more than stop the value
being used: it moves that column into the residual set.</li>
<li><strong>The duration cell is read as a time span rather than as a number of seconds</strong>, despite the
field's own <code>(s)</code>. A cell that does not parse falls back to the difference between this row's
actual time and the previous parseable one, and a resolved duration longer than one minute is
clamped to one minute, on the reasoning that a longer gap is spliced recordings rather than
machining time.</li>
<li><strong>An empty field is a real edit.</strong> The write applies every field the payload carries, and the panel
sends exactly the field that changed, so blanking a box clears that tag on the model rather than
leaving it alone.</li>
</ul>
<p>Each field commits on blur or on Enter — never while typing — and sends only itself. There is no
validation on either side of the wire: no trimming, no bounds, no uniqueness.</p>
<h2 id="cl-controller">CL Controller</h2>
<p>The node is the face of the project's resident NX-CL suit
(<a class="xref" href="../../api/Hi.MachiningProcs.MachiningProject.html#Hi_MachiningProcs_MachiningProject_ClsfRunnerSuit">ClsfRunnerSuit</a>), constructed the same way beside the brand
suit and the CSV suit, its runner a <a class="xref" href="../../api/Hi.NcParsers.SoftNcRunner.html">SoftNcRunner</a> assembled as the CLSF pipeline
by the factory <a class="xref" href="../../api/Hi.Numerical.ClsfParsers.NxClRunner.html">NxClRunner</a>. Its caption reads <em>&quot;Plays NX
cutter-location files (CLSF) — a mission Program File of CL kind or a script's <code>PlayClFile(&quot;&quot;)</code>.&quot;</em></p>
<p>Everything on the panel is one <a class="xref" href="../../api/Hi.Numerical.ClsfParsers.ClsfRunnerConfig.html">ClsfRunnerConfig</a>, and every field on
it answers something a cutter-location file cannot say for itself.</p>
<p><strong>Rapid feedrate (assumed)</strong>, suffixed <code>mm/min</code>, and <strong>Rotary rapid feedrate (assumed)</strong>, suffixed
<code>deg/min</code>, default to 20000 and 36000. A CLSF carries no machine axes, so a <code>RAPID</code> move has nothing
to be timed against — but the two rates are not read on the same path, and the machining chain
decides which. On a pure-CL chain the CLSF pipeline's own motion semantic reads the linear rate
straight off this configuration and times every rapid from it; the rotary rate is never consulted.
On a machine-tool chain, where each CLSF block is re-expressed in machine coordinates and routed to
the motion semantics reused from the NC pipeline, the configuration doubles as the pipeline's
rapid-feedrate provider and answers axis-uniformly — every linear axis gets the first rate, every
rotary axis the second — and that is the only place the rotary rate is read.</p>
<p>Both fields carry a minimum of 1: a smaller value is refused inside the widget itself, which shows
<em>Must be ≥ 1</em> under the box and emits nothing, so no request is made and no toast appears. The
server's own positivity check sits behind that and is not reachable from this panel. A cleared or
non-finite entry is dropped by the panel before the request, which leaves the box showing what was
typed while the stored rate stands; selecting another node and returning restores the box.</p>
<p><strong>Prefer Tool House on LOAD/TOOL</strong> is a toggle, on by default, committing on the click. Its caption
sits below it permanently rather than as a field hint: <em>&quot;On: a LOAD/TOOL id already configured in the
Tool House keeps that tool (CLSF TLDATA geometry ignored). Off: TLDATA overwrites the tool-house
entry on every load.&quot;</em> That is what the code does — a tool change requesting an id the tool house
already holds is left alone while the toggle is on, and the file's own tool geometry rebuilds the
entry when it is off. Either way an id the house does not hold is built from the file's tool data,
and an id with neither is reported as a configuration error.</p>
<p><strong>Excluded record words</strong> is a chip field: a word typed and entered joins the list, and there is no
dropdown, because the option list is deliberately empty. Its preset list is <code>PAINT</code>, <code>TOOLNO</code>,
<code>LOADTL</code>, <code>TOOL PATH</code>, <code>TOOLPATH</code> and <code>END-OF-PATH</code>. A CLSF record that reaches the end of the
pipeline unhandled is otherwise reported as a validation warning naming the record word; a word on
this list is consumed silently instead, matched whole and without regard to case. Adding or removing
one chip writes the whole list, the server trims each word and drops the blank ones before replacing
the stored list, and the panel keeps what was typed — so a word entered with stray whitespace reads
differently on screen from what was stored, until the panel is remounted.</p>
<h2 id="layout">Layout</h2>
<ul>
<li>App Menu Bar
<ul>
<li><strong>Preference ▾</strong> Dropdown — fetches both runner snapshots as it opens, and only with a project
open
<ul>
<li><strong>CSV Controller</strong> CheckBox
<ul>
<li>Usage Caption — <em>&ldquo;This project plays CSV&rdquo;</em>, <em>&ldquo;Not used by this project&rdquo;</em>, or blank</li>
</ul>
</li>
<li><strong>CL Controller</strong> CheckBox
<ul>
<li>Usage Caption — the same three states, for CL</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li><strong>General Setup</strong> Control Tree — the left dock of <code>/general-setup</code>
<ul>
<li><strong>General Setup</strong> Group Row — <code>equipment</code>
<ul>
<li><strong>Controller</strong> Node Row — the brand branch, built ahead of the two rows below</li>
<li><strong>CSV Controller</strong> Node Row — <code>equipment/controller-csv</code>; present only under the conditions
above, and pushed before the CL row when both are</li>
<li><strong>CL Controller</strong> Node Row — <code>equipment/controller-cl</code>; the last row the group can carry. Each
is a plain label with no icon, no checkbox and no children</li>
</ul>
</li>
</ul>
</li>
<li>Editor Row — the panel of whichever node is selected
<ul>
<li><strong>General Setup</strong> Group Panel — where a switched-off node's selection lands: the group's intro
caption over a bordered, separated list of its children, one clickable row each</li>
<li><strong>CSV Controller</strong> Panel
<ul>
<li>No-Project Body — <em>&quot;No project loaded.&quot;</em></li>
<li>No-Config Body — <em>&quot;No CSV column config on the CSV runner.&quot;</em></li>
<li>Editor Body
<ul>
<li>Intro Caption — the column-tags line, with <code>PlayCsvFile(&quot;&quot;)</code> set in code</li>
<li>Tag Text Field, one per row, in this order: <strong>Machine coordinate prefix</strong> (hint <em>e.g. &ldquo;MC.&rdquo;
MC.X / MC.Y / MC.Z</em>), <strong>Cutter location prefix</strong> (hint <em>e.g. &ldquo;CL.&rdquo; → CL.X / CL.Y / CL.Z</em>),
<strong>Tool id column</strong>, <strong>Spindle speed column (rpm)</strong>, <strong>Spindle direction column</strong>,
<strong>Feedrate column (mm/min)</strong>, <strong>Step duration column (s)</strong> (hint <em>Overrides feedrate-derived
timing.</em>), <strong>Actual time column</strong> (hint <em>Wall-clock instant of the row.</em>), <strong>Coolant column</strong>
(hint <em>Flood / Mist / Off or on/off.</em>), <strong>Line-begin C# script column</strong> (hint <em>Runs before the
row.</em>), <strong>Line-end C# script column</strong> (hint <em>Runs after the row.</em>)</li>
</ul>
</li>
</ul>
</li>
<li><strong>CL Controller</strong> Panel
<ul>
<li>No-Project Body — <em>&quot;No project loaded.&quot;</em></li>
<li>No-Config Body — <em>&quot;No CLSF config on the CL runner.&quot;</em></li>
<li>Editor Body
<ul>
<li>Intro Caption — the plays-CLSF line, with <code>PlayClFile(&quot;&quot;)</code> set in code</li>
<li><strong>Rapid feedrate (assumed)</strong> Numeric Field — suffixed <code>mm/min</code>, minimum 1</li>
<li><strong>Rotary rapid feedrate (assumed)</strong> Numeric Field — suffixed <code>deg/min</code>, minimum 1</li>
<li><strong>Prefer Tool House on LOAD/TOOL</strong> Toggle</li>
<li>Policy Caption — the <em>On: … Off: …</em> explanation, always shown</li>
<li><strong>Excluded record words</strong> Chip Field — free-text entry with no dropdown list, hint <em>Record
words consumed silently as intentional skips (case-insensitive). Type + Enter to add.</em></li>
</ul>
</li>
</ul>
</li>
<li>Toast — negative, three seconds, the panel's context followed by the server's own message</li>
</ul>
</li>
</ul>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../index.html">HiNC App Anatomy</a> for git repository links.</p>
<h3 id="web-application">Web Application</h3>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/controlTree/useControlTreeHost.ts</code> — the build rule that mints the two
nodes and where in the equipment group they sit, the checkbox watch that steps the selection off a
node losing its box before the rebuild, and the route-adoption guard that refuses an id the built
tree lacks.</li>
<li><code>wwwroot-src/src/components/controlTree/runnerSuitItemTypes.ts</code> — the two item types and the panels
they bind to; its own docblock names the usage evidence and the persisted selection as reveal
conditions, which the host's build rule does not.</li>
<li><code>wwwroot-src/src/components/controlTree/CsvRunnerPanel.vue</code> — the column-tag form: the two guards,
the field list with its labels and hints, the blur-and-Enter commit with its equality guard against
the fetched mirror, and the single-field payload.</li>
<li><code>wwwroot-src/src/components/controlTree/ClRunnerPanel.vue</code> — the CLSF form: the two rapid rates,
the cleared and non-finite values it drops before the request, the equality guard on each rate and
on the tool-house toggle, and the unguarded whole-list chip write.</li>
<li><code>wwwroot-src/src/composables/useViewPrefs.ts</code> — the device-local browser-storage record behind both
checkboxes: their default, their persistence, and the record they share with the tree's expansion
and last-selection memory.</li>
<li><code>wwwroot-src/src/components/AppMenuBar.vue</code> — the Preference dropdown's two checkboxes, and the
caption refresh that fetches both snapshots as the menu opens.</li>
<li><code>wwwroot-src/src/api/csvRunner.ts</code> and <code>wwwroot-src/src/api/clRunner.ts</code> — the two snapshot shapes,
their pre-fetch empty values, and the typed wrappers over each REST surface.</li>
<li><code>wwwroot-src/src/api/http.ts</code> — the plain-JSON read that inspects no envelope, beside the envelope
write that throws a <code>success: false</code> message.</li>
<li><code>wwwroot-src/src/components/widgets/NumericInput.vue</code> — the field behind the two rapid rates: commit
on blur or Enter, and the minimum that reports inline without emitting.</li>
<li><code>wwwroot-src/src/components/controlTree/PrimarySlavePanel.vue</code> — the editor row: the remount key
that refetches when the selection moves, and the select-an-item hint a refused deep link leaves
behind.</li>
<li><code>wwwroot-src/src/components/controlTree/GroupInfoPanel.vue</code> — the General Setup group's editor,
whose clickable child list gains and loses these two rows with the tree.</li>
<li><code>wwwroot-src/src/components/controlTree/itemTypes.ts</code> — the registry the two item types are spread
into, and the panel lookup the editor row resolves through.</li>
<li><code>wwwroot-src/src/router/treeRoutes.ts</code> — the id migration applied to a <code>?tree=</code> value before the
reveal test reads it, and the page roots a foreign id is redirected by.</li>
<li><code>wwwroot-src/src/pages/GeneralSetupPage.vue</code> — the route that creates the equipment-scoped tree host
and calls its initialization.</li>
<li><code>wwwroot-src/src/layouts/MainLayout.vue</code> — the project epoch that rebuilds the page when a project
loads, which is what lets a link opened against an empty application land afterwards.</li>
<li><code>wwwroot-src/src/i18n/en/tree.ts</code> — the two node labels, both panels' intros, empty lines, field
labels, hints and error contexts.</li>
<li><code>wwwroot-src/src/i18n/en/menu.ts</code> — the two checkbox labels and the two usage-caption strings.</li>
<li><code>wwwroot-src/src/i18n/en/common.ts</code> — the shared &ldquo;No project loaded.&rdquo; line both panels open with.</li>
<li><code>Mech/CsvRunnerController.cs</code> — the CSV suit's REST surface: the snapshot, the write that applies
only the fields the payload carries, and the success envelope a missing config is reported in.</li>
<li><code>Mech/ClRunnerController.cs</code> — the CL suit's REST surface: the same envelope, the positivity checks
on the two rates, and the trim-and-replace of the excluded-word list.</li>
<li><code>Mech/NcSuitUsage.cs</code> — the usage scan behind the two captions: the mission walk that reads through
a command's enable wrapper, the two script substrings, the extension detection for an Auto Program
File, and the CL-only chain test.</li>
</ul>
<h3 id="hiapi-engine">HiAPI Engine</h3>
<ul>
<li><code>HiNc/MachiningProcs/MachiningProject.cs</code> — the two resident suits as property-initialized members,
their load paths with the fall back to a fresh pipeline, and the migration of the older flat CSV
column element into the suit.</li>
<li><code>HiNc/MachiningProcs/LocalProjectService.cs</code> — the re-bind that wires both suits to the host
alongside the brand suit, and the chain walk that clears a generic axis configuration before
stamping the chain's axes onto it.</li>
<li><code>HiNc/MachiningProcs/SessionShell.cs</code> — the script verbs whose call text the caption's evidence
scan matches: the two play verbs, and the two CSV sensor-mapping verbs that end in the same
substring.</li>
<li><code>HiNc/SessionCommands/NcFileCommand.cs</code> — the Program File command whose kind the scan reads, and
its Auto default.</li>
<li><code>HiNc/SessionCommands/EnablingWrapper.cs</code> — the enable wrapper the scan walks through without
reading, which is why a switched-off command still counts as evidence.</li>
<li><code>HiMech/MachiningProcs/NcKind.cs</code> — the kind enum and the closed extension sets behind the Auto
detection.</li>
<li><code>HiMech/Numerical/CsvParsers/GeneralCsvRunner.cs</code> — the factory that assembles the suit's runner as
the CSV pipeline, and the place its column configuration sits in that pipeline.</li>
<li><code>HiMech/Numerical/CsvParsers/CsvRunnerConfig.cs</code> — the eleven tags, their serialization, and the
constants they default to.</li>
<li><code>HiMech/Mapping/MappingUtil.cs</code> — those constants.</li>
<li><code>HiMech/Numerical/CsvParsers/CsvSegmenter.cs</code> — the header row the tags are matched against, and
the quote-then-whitespace trim applied to each title.</li>
<li><code>HiMech/Numerical/CsvParsers/RowToSyntaxs/RowToMachineCoordinateSyntax.cs</code> — the six axis names the
machine-coordinate prefix is composed with, and the consumption of those columns whether or not
they resolved.</li>
<li><code>HiMech/Numerical/CsvParsers/CsvSemantics/CsvTimingUtil.cs</code> — the duration parse, the actual-time
fallback and the one-minute clamp.</li>
<li><code>HiMech/Numerical/CsvParsers/CsvSemantics/CsvActDataSemantic.cs</code> — the residual columns carried onto
the step, which is where a column no tag names ends up.</li>
<li><code>HiMech/Numerical/ClsfParsers/NxClRunner.cs</code> — the factory that assembles the CLSF pipeline, and
the configuration's place in it.</li>
<li><code>HiMech/Numerical/ClsfParsers/ClsfRunnerConfig.cs</code> — the four values, their defaults, and the
axis-uniform rapid-rate provider the machine-coordinate motion semantics read.</li>
<li><code>HiMech/Numerical/ClsfParsers/ClMotionValveSemantic.cs</code> — the router that sends a block carrying
machine coordinates to the semantics reused from the NC pipeline and every other block to the
pure-CL one, which is what decides whether the rotary rate is read at all.</li>
<li><code>HiMech/Numerical/ClsfParsers/ClMotionSemantic.cs</code> — the pure-CL motion semantic, timing a rapid
from the linear rate taken directly off the configuration.</li>
<li><code>HiMech/Numerical/ClsfParsers/ClsfRecordCleanupSyntax.cs</code> — the excluded-word match, and the
validation warning it replaces with a silent consume.</li>
<li><code>HiMech/Numerical/ClsfParsers/ClsfToolBuildSemantic.cs</code> — the tool-house policy: the entry that
wins, the file's tool data that builds a missing one, and the error when neither answers.</li>
<li><code>HiMech/Milling/ClMillingDevice.cs</code> — the chain type that is CL usage evidence on its own.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="index.html">General Setup Page</a> — the page whose tree these two nodes are minted into, and the
equipment items they sit after</li>
<li><a class="xref" href="../shell/preference-menu.html">Preference Menu Dropdown</a> — the dropdown holding the two checkboxes and their usage captions,
and the other per-user settings beside them</li>
<li><a class="xref" href="controller/brand-matrix.html">Brand Matrix</a> — the other kind of missing node: which leaf the brand branch
grows on which brand, and the snapshot flag behind each one</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,150 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>General Setup Page | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="General Setup Page | HiAPI-C# 2025 ">
<link rel="icon" href="../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../index.html">
<img id="logo" class="svg" src="../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-GeneralSetup">
<h1 id="general-setup-page">General Setup Page</h1>
<p>General Setup is the equipment page at <code>/general-setup</code>. It is one of the two Control-Tree pages:
the left dock names every piece of equipment in the project and edits whatever is selected, and the
canvas on the right shows all of it at once. Selection rides the URL as <code>?tree=equipment/…</code>, so any
branch of it is a link. The tree's one root is a pure Group stem at <code>equipment</code>, selectable in its
own right: it renders an orientation panel listing the equipment items beneath it.</p>
<p>The page renders three columns in nested splitters — the Control-Tree dock, the content column, and
the equipment canvas. The dock carries two stacked rows: the tree above, and below it the editor
panel of whichever node is selected. The content column holds a second, wider view of the same
selection, mounted only for the item types that register one — on this page the spindle branch and
nothing else — while every other selection leaves it showing its empty hint. Column widths are
device-local, and the dock aligns with the Execution page's so the two tree pages feel like one
application.</p>
<p>Ordered as the page builds its equipment children.</p>
<h2 id="pages">Pages</h2>
<ul>
<li><a href="machine-tool.html">Machine Tool</a> — The kinematic chain the whole scene hangs from, and the standalone route that loads and previews it</li>
<li><a href="spindle-capability.html">Spindle Capability</a> — The spindle envelope: thermal condition, gear shift, dry run, power and torque</li>
<li><a href="background-coolant.html">Background / Coolant</a> — The two scene branches that are not machine parts: the backdrop and the coolant model</li>
<li><a href="fixture.html">Fixture</a> — What holds the workpiece, its geometry and the two anchors that place it</li>
<li><a href="workpiece.html">Workpiece</a> — Raw and target geometry, the anchors that locate them, the mesh, and the material</li>
<li><a href="controller/index.html">Controller</a> — The SoftNcRunner-native controller branch: its two planes, the brand matrix behind which nodes appear at all, and the contract every leaf edits by</li>
<li><a href="hidden-controllers.html">Hidden Controller Branches</a> — The two runner-suit nodes a fresh installation never builds, the device-local checkboxes that reveal them, and the second kind of invisibility a link cannot undo</li>
</ul>
<p>The superseded <a class="xref" href="../legacy-controller.html">Legacy Controller</a> screen at its own route is a separate
surface, editing a different model from the branch above.</p>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="../execution/index.html">Execution Page</a> — the other Control-Tree page, and the one this was split out of</li>
<li><a class="xref" href="../tool-house/index.html">Tool House Page</a> — the third equipment surface, a tab page rather than a tree page</li>
<li><a class="xref" href="../shell/main-panel.html">Main Panel</a> — the shell whose Page menu reaches this route</li>
<li><a class="xref" href="../platform/control-tree.html">Control Tree</a> — the engine behind this page's tree: how a branch is built, rebuilt and selected</li>
<li><a href="controller/index.html">Controller Branch</a> — the branch this page hosts whose node set changes with the controller brand</li>
<li><a href="hidden-controllers.html">Hidden Controller Branches</a> — the two equipment nodes this page withholds until a preference or a link asks for them</li>
<li><a class="xref" href="../legacy-controller.html">Legacy Controller</a> — the superseded controller screen at its own route, and the three settings only it edits</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,326 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Machine Tool | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Machine Tool | HiAPI-C# 2025 ">
<link rel="icon" href="../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../index.html">
<img id="logo" class="svg" src="../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-MachineTool">
<h1 id="machine-tool">Machine Tool</h1>
<p>The machine tool is the project's kinematic chain — the linkage between the machine table and the
tool spindle. It has three surfaces:</p>
<ul>
<li>the <code>equipment/machine-tool</code> branch of the General Setup page's Control Tree
(<code>/general-setup?tree=equipment/machine-tool</code>), the first equipment child of the General Setup
group and a <strong>leaf</strong>: chain structure belongs to the
<a class="xref" href="../util/mech-builder.html">Mechanism Builder Page</a>, so the root carries no child slots;</li>
<li>the <code>/machine-tool</code> route, a standalone surface pairing a chain-only canvas with a load button
and a GUI / XML view of what is loaded. Its fields are read-only, but the route is not: its Load
installs the picked chain file onto the project. It carries no Page-menu entry and is reached by
URL;</li>
<li>in the WPF client, a sub-window opened from the <a class="xref" href="../shell/main-panel.html">Main Panel</a>.</li>
</ul>
<p>All three edit the one chain the project owns.</p>
<ul>
<li>Key Model:
<ul>
<li><a class="xref" href="../../api/Hi.Mech.IMachiningChain.html">IMachiningChain</a>
On the web the chain is addressed by an IndexService key handed to the
<a class="xref" href="../widget/object-management-menu-button.html">Object Management Menu Button</a> and installed back onto
the project through the controller's Update. On the WPF client the same button is wired with a
TargetObjectGetter / TargetObjectSetter pair (see that page for the design pattern).</li>
</ul>
</li>
<li>Assistant Model:
<ul>
<li><a class="xref" href="../../api/Hi.MachiningProcs.LocalProjectService.html">LocalProjectService</a> — owns the chain as the
<a class="xref" href="../../api/Hi.MachiningProcs.LocalProjectService.html#Hi_MachiningProcs_LocalProjectService_MachiningChain">MachiningChain</a> /
<a class="xref" href="../../api/Hi.MachiningProcs.LocalProjectService.html#Hi_MachiningProcs_LocalProjectService_MachiningChainFile">MachiningChainFile</a> pair.</li>
<li><a class="xref" href="../../api/Hi.MachiningProcs.MachiningProject.html">MachiningProject</a></li>
</ul>
</li>
</ul>
<h2 id="layout">Layout</h2>
<h3 id="control-tree-branch">Control Tree Branch</h3>
<p>The root panel is rendered inline by the dock's primary editor pane, so it has no title label of its
own — the wrapping expansion header carries the selection breadcrumb.</p>
<ul>
<li>Machine Tool Root Panel
<ul>
<li><a class="xref" href="../widget/object-management-menu-button.html">Object Management Menu Button</a>
<ul>
<li>New Items: one entry, <code>New ClMillingDevice</code> (&ldquo;CL-driven blank device&rdquo;). Its parameterless form
is the complete CL-driven device; any other chain type arrives by Load, Paste or XML.</li>
<li>Load / Save As / Copy / Paste / XML, over the project directory and the admin directory.</li>
<li>File extensions are <code>.MachineTool</code> and <code>.mt</code>, so the Load / Save As filter reads
<code>*.MachineTool / *.mt / *.xml</code>; the load type is <a class="xref" href="../../api/Hi.Mech.IMachiningChain.html">IMachiningChain</a>.</li>
</ul>
</li>
<li>Type Badge — the chain's runtime type name, <code>none</code> when no chain is attached.</li>
<li>Read-only caption lines, shown once a chain is attached:
<ul>
<li><code>Name:</code><a class="xref" href="../../api/Hi.Common.INameNote.html#Hi_Common_INameNote_Name">Name</a></li>
<li><code>Note:</code><a class="xref" href="../../api/Hi.Common.INameNote.html#Hi_Common_INameNote_Note">Note</a>, when non-empty</li>
<li><code>File:</code> — the chain's project-relative file, when it has one</li>
</ul>
</li>
<li>Intro caption, and an empty-state caption while no project is open.</li>
</ul>
</li>
<li>Shared slave view: the General Setup equipment canvas, whose Solid group's <strong>Machine</strong> flag draws
this chain (see <a class="xref" href="index.html">General Setup Page</a>). <strong>That flag starts cleared</strong>, so
the shared canvas shows a correctly attached chain only after it is ticked; the <code>/machine-tool</code>
route's own canvas has no such flag and always draws it.</li>
</ul>
<h3 id="machine-tool-route"><code>/machine-tool</code> Route</h3>
<p>Load and show only: no Save As, no ReLoad, no name editing.</p>
<ul>
<li>Header Row
<ul>
<li>Title Label &ldquo;Machine Tool&rdquo;</li>
<li><code>[relFile]</code> caption — the loaded chain's project-relative path, when it has one.</li>
<li>Folder-icon Load Button — opens the server-side file explorer dialog, restricted to the
<code>ProjectDirectory</code> and <code>ResourceDir</code> roots and filtered to <code>Machine Tool (.MachineTool|.mt)</code>.
Disabled until a project is open.</li>
<li>Read-only Name Field, shown once a chain is loaded.</li>
<li>GUI / XML View Toggle, disabled until a chain is loaded.</li>
</ul>
</li>
<li>Body — two columns
<ul>
<li>Left: Identity Card or XML Source
<ul>
<li>GUI mode — a read-only Name input, a read-only auto-grow Note textarea, and a Type chip.</li>
<li>XML mode — the chain serialised to XML, badged <code>read-only</code>, with a refresh button. The text is
fetched lazily on the first switch to XML and re-fetched after a load while XML mode is open.</li>
</ul>
</li>
<li>Right: Viewer Panel
<ul>
<li><a class="xref" href="../widget/renderingcanvas-tool-bar.html">RenderingCanvas Tool Bar</a> and a connection badge.</li>
<li>RenderingCanvas, cache id <code>MachineToolCanvas</code> — this route's own canvas.</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="wpf-page">WPF Page</h3>
<ul>
<li>Head Line
<ul>
<li><a class="xref" href="../widget/object-management-menu-button.html">Object Management Menu Button</a>
<ul>
<li>file extension is <code>mt</code>; the load type is <a class="xref" href="../../api/Hi.Mech.IMachiningChain.html">IMachiningChain</a>.</li>
<li>The pointed Editor Panel is Management Panel — a ContentPresenter the button swaps between the
built GUI panel and an AvalonEdit text editor for XML mode.</li>
</ul>
</li>
<li>Title Label &ldquo;Machine Tool&rdquo;</li>
</ul>
</li>
<li>Management Panel
<ul>
<li>If the key model inherits <a class="xref" href="../../api/Hi.Common.INameNote.html">INameNote</a>:
<ul>
<li>Name Setting Line
<ul>
<li>Name Label</li>
<li>Name TextField — its TextChanged writes straight onto the chain.</li>
</ul>
</li>
<li>Note Setting Line
<ul>
<li>Note Label</li>
<li>Note TextField — multi-line, same write-through.</li>
</ul>
</li>
</ul>
</li>
<li>Type Line — italic <code>Type: {chain type}</code>.</li>
<li>Empty-state label when no chain is loaded.</li>
</ul>
</li>
</ul>
<h2 id="behavior">Behavior</h2>
<ul>
<li>Load / Paste / XML-Apply only swap the IndexService entry. The tree root then installs the result
onto <a class="xref" href="../../api/Hi.MachiningProcs.LocalProjectService.html#Hi_MachiningProcs_LocalProjectService_MachiningChain">MachiningChain</a> — the setter re-attaches the
fixture and workpiece buckles and wires the runtime hooks itself — re-mints the key and rebuilds
the branch. The button's XML dialog re-emits its object-loaded event on Apply, so that install and
refresh chain runs exactly once.</li>
<li>A chain the file describes wrongly is refused, and which of the two steps refuses it depends on
the file. A machine tool file asking for its collision pairs to be generated is walked from the
ground anchor to each end while it is being read, so a chain carrying no anchor named exactly <code>t</code>
or exactly <code>w</code> never reaches the IndexService at all: Object Management's Load answers
<code>Load failed: This kinematic chain has no worktable-end anchor named 'w'.</code> A file that lists its
collision pairs instead is read without that walk and does reach the index; the install that
follows builds the kinematics solver, which requires the same two anchors, so the same sentence
arrives one step later as <code>Install machine tool: …</code> and the project keeps the chain it had. The
shipped machine tool files all ask for generated pairs, so the first form is the one usually met.
The worktable end is checked before the tool end, so a chain missing both names only <code>'w'</code> until
that one is fixed. The <code>/machine-tool</code> route runs both steps inside its single call, so either
refusal reaches it as <code>Load machine tool: …</code>, and — like the no-project refusal behind the
disabled folder button — leaves the loaded chain untouched.</li>
<li>The IndexService key is minted even on a project with no chain attached, so Load and Paste stay
usable from an empty root.</li>
<li>The <code>/machine-tool</code> route's own Load takes the shorter path: one call materialises the picked file
server-side and assigns the chain to <a class="xref" href="../../api/Hi.MachiningProcs.LocalProjectService.html#Hi_MachiningProcs_LocalProjectService_MachiningChain">MachiningChain</a>
in the same request, with no IndexService swap in between. It re-points
<a class="xref" href="../../api/Hi.MachiningProcs.LocalProjectService.html#Hi_MachiningProcs_LocalProjectService_MachiningChainFile">MachiningChainFile</a> too — project-relative when the
picked file sits under the project directory, and the path relative to the chosen root otherwise.
It is refused while no project is open, which is why the folder button is disabled there.</li>
<li><code>New ClMillingDevice</code> builds and installs the blank chain in one server call and clears the
project's chain-file reference, so a later project save embeds the fresh chain inline instead of
overwriting the previous chain's file.</li>
<li>When the replacement came from a server-file Load, the install also re-points
<a class="xref" href="../../api/Hi.MachiningProcs.LocalProjectService.html#Hi_MachiningProcs_LocalProjectService_MachiningChainFile">MachiningChainFile</a>: project-relative when the file
sits under the project directory, resource-root-relative when it sits under the resource root,
absolute otherwise. An absolute path there would make every later project save rewrite the shared
resource file in place instead of copying it into the project. Paste and XML applies leave the
file reference unchanged.</li>
<li>Both canvases draw the live model, so a replacement shows on the next frame with no further calls.
The <code>/machine-tool</code> canvas snaps to the isometric view when it is bound and again after each load;
the shared equipment canvas snaps once, when its displayee is bound.</li>
</ul>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../index.html">HiNC App Anatomy</a> for git repository links.</p>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/controlTree/useControlTreeHost.ts</code> — builds the
<code>equipment/machine-tool</code> node (item type <code>MachineToolRoot</code>, keyed on the IndexService chain key)
and owns the install / create / rebind handlers.</li>
<li><code>wwwroot-src/src/components/controlTree/PrimarySlavePanel.vue</code> — renders that branch inline: the
Object Management button, its <code>New ClMillingDevice</code> entry, the type badge and the read-only
Name / Note / File captions.</li>
<li><code>wwwroot-src/src/components/widgets/ObjectManagementMenuButton.vue</code> — the reused object-management
dropdown, keyed entirely on its <code>modelKey</code> (see
<a class="xref" href="../widget/object-management-menu-button.html">Object Management Menu Button</a>).</li>
<li><code>wwwroot-src/src/components/mech/EquipmentSetupPanel.vue</code> — the shared equipment canvas the tree
root's chain is drawn on.</li>
<li><code>wwwroot-src/src/pages/MachineToolPage.vue</code> — the <code>/machine-tool</code> route.</li>
<li><code>wwwroot-src/src/components/widgets/FileExplorerDialog.vue</code> — the server-side file picker that
route opens, seeded to the resource root's <code>MachineTool</code> folder when nothing is loaded and to the
current file's directory otherwise; it emits <code>&quot;{rootName}:{relativePath}&quot;</code> selection keys.</li>
<li><code>wwwroot-src/src/api/machineTool.ts</code> — typed client for <code>/api/mech/machine-tool/*</code>: snapshot,
initialize, update, create, load, XML, plus the display initialize and reset-view calls. Shared by
the route page and the tree root.</li>
<li><code>wwwroot-src/src/router/routes.ts</code> — declares the <code>machine-tool</code> route.</li>
<li><code>wwwroot-src/src/components/AppMenuBar.vue</code> — the Page menu, which lists General Setup but not
<code>/machine-tool</code>.</li>
<li><code>Mech/MachineToolController.cs</code> — REST surface at <code>/api/mech/machine-tool</code>: the snapshot DTO,
<code>initialize</code> / <code>update</code> / <code>create</code> / <code>load</code> and the XML read. Only <code>ClMillingDevice</code> is offered
for blank creation.</li>
<li><code>Mech/MachineToolDisplayController.cs</code><code>/api/mech/machine-tool/display</code>: binds the project's
chain onto a rendering connection's DispEngine through a delegating displayee, and serves the
isometric reset-view the route page calls after a load.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="../util/mech-builder.html">Mechanism Builder Page</a> — the user-scoped editor for the same anchor topology</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,328 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Spindle Capability | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Spindle Capability | HiAPI-C# 2025 ">
<link rel="icon" href="../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../index.html">
<img id="logo" class="svg" src="../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-SpindleCapabilityPage">
<h1 id="spindle-capability">Spindle Capability</h1>
<p>The Spindle Capability editor is the <code>equipment/spindle</code> branch of the General Setup page's Control
Tree (<code>/general-setup?tree=equipment/spindle</code>), the equipment child after Machine Tool. Two older
paths redirect onto it: <code>/spindle-capability/:tab?</code>, whose tab segment maps straight onto one of the
branch's five children, and <code>/equipment/spindle</code>. The menu bar carries no entry of its own for it —
the Page menu reaches General Setup, and the tree reaches the branch.</p>
<p>It edits <a class="xref" href="../../api/Hi.Milling.SpindleCapability.html">SpindleCapability</a> on the project's authored equipment face,
<a class="xref" href="../../api/Hi.Machining.MachiningEquipmentUtils.SetupEquipment.html">SetupEquipment</a>, reached as
<a class="xref" href="../../api/Hi.MachiningProcs.LocalProjectService.html#Hi_MachiningProcs_LocalProjectService_SetupEquipment">SetupEquipment</a>. It exposes the metadata (name, note),
the thermal scalars (energy efficiency, working-temperature ceiling), the gear-shift spindle speed,
the two dry-run coefficients, and the power / torque contour lists.</p>
<ul>
<li>Key Model: <a class="xref" href="../../api/Hi.Milling.SpindleCapability.html">SpindleCapability</a></li>
<li>Assistant Model:
<ul>
<li><a class="xref" href="../../api/Hi.Machining.MachiningEquipmentUtils.SetupEquipment.html">SetupEquipment</a> — owns the capability as
<a class="xref" href="../../api/Hi.Machining.MachiningEquipmentUtils.SetupEquipment.html#Hi_Machining_MachiningEquipmentUtils_SetupEquipment_SpindleCapability">SpindleCapability</a> and its optional
side-file reference
<a class="xref" href="../../api/Hi.Machining.MachiningEquipmentUtils.SetupEquipment.html#Hi_Machining_MachiningEquipmentUtils_SetupEquipment_SpindleCapabilityFile">SpindleCapabilityFile</a>.</li>
<li><a class="xref" href="../../api/Hi.MachiningProcs.MachiningProject.html">MachiningProject</a> — carries that face across the <code>.hincproj</code> save.</li>
</ul>
</li>
</ul>
<div class="NOTE">
<h5>Note</h5>
<p>The WPF desktop app has no spindle-capability surface: no page, no panel, and no handler for the
<code>.SpindleCapability</code> extension. There, the value is whatever the project XML carries on the
equipment face, or the class defaults when the XML says nothing. The <code>.SpindleCapability</code> object
management menu ships on the web only, on this branch's root panel.</p>
</div>
<h2 id="layout">Layout</h2>
<p>The branch is one root plus five children, six selectable nodes in all. Each node's editor occupies
the dock's PRIMARY row, and all six — the root included — register the <em>same</em> CONTENT-column view,
the two contour charts, so the charts stay mounted while the operator walks the branch.</p>
<h3 id="control-tree-branch">Control Tree Branch</h3>
<ul>
<li>Spindle Capability Root Panel — <code>equipment/spindle</code>, item type <code>SpindleCapabilityRoot</code>
<ul>
<li><a class="xref" href="../widget/object-management-menu-button.html">Object Management Menu Button</a> — Load / Save As /
Copy / Paste / XML over file extension <code>.SpindleCapability</code>, load type
<code>Hi.Milling.SpindleCapability, HiMech</code>, rel file <code>SpindleCapability.xml</code>, based at the project
directory. This is the whole file surface of the editor.</li>
<li>Caption — the capability's name, or a &ldquo;no capability&rdquo; note.</li>
<li>Empty state, while nothing is attached: an hourglass, a &ldquo;not attached to project&rdquo; line and a
hint pointing at the ⋮ menu's Load entry.</li>
<li>Name TextField — <a class="xref" href="../../api/Hi.Milling.SpindleCapability.html#Hi_Milling_SpindleCapability_Name">Name</a>.</li>
<li>Note TextField — <a class="xref" href="../../api/Hi.Milling.SpindleCapability.html#Hi_Milling_SpindleCapability_Note">Note</a>.</li>
</ul>
</li>
<li><code>equipment/spindle/thermal</code>&ldquo;Thermal / Energy&rdquo;, item type <code>SpindleScalars</code>
<ul>
<li>Energy Efficiency NumberField — <a class="xref" href="../../api/Hi.Milling.SpindleCapability.html#Hi_Milling_SpindleCapability_EnergyEfficiency">EnergyEfficiency</a>, clamped to
0 1.</li>
<li>Working Temperature Upper Boundary NumberField (°C) —
<a class="xref" href="../../api/Hi.Milling.SpindleCapability.html#Hi_Milling_SpindleCapability_WorkingTemperatureUpperBoundary_C">WorkingTemperatureUpperBoundary_C</a>.</li>
</ul>
</li>
<li><code>equipment/spindle/gear-shift</code>&ldquo;Gear Shift&rdquo;, item type <code>SpindleScalars</code>
<ul>
<li>Has-Gear-Shift CheckBox — flips
<a class="xref" href="../../api/Hi.Milling.SpindleCapability.html#Hi_Milling_SpindleCapability_GearShiftSpindleSpeed_rpm">GearShiftSpindleSpeed_rpm</a> between <code>null</code> (no mechanism) and
<code>0</code> (mechanism present; the operator dials in the speed).</li>
<li>Gear Shift Spindle Speed NumberField (rpm) — always rendered, disabled until the checkbox is on.</li>
</ul>
</li>
<li><code>equipment/spindle/dry-run</code>&ldquo;Dry-Run Coefficients&rdquo;, item type <code>SpindleScalars</code>
<ul>
<li>Friction Power Coefficient NumberField (mW/rpm) —
<a class="xref" href="../../api/Hi.Milling.SpindleCapability.html#Hi_Milling_SpindleCapability_DryRunFrictionPowerCoefficient_mWdrpm">DryRunFrictionPowerCoefficient_mWdrpm</a>.</li>
<li>Windage Power Coefficient NumberField (pW/rpm³) —
<a class="xref" href="../../api/Hi.Milling.SpindleCapability.html#Hi_Milling_SpindleCapability_DryRunWindagePowerCoefficient_pWdrpm3">DryRunWindagePowerCoefficient_pWdrpm3</a>.</li>
</ul>
</li>
<li><code>equipment/spindle/power</code>&ldquo;Power Contours&rdquo;, item type <code>SpindleContour</code>
<ul>
<li>Count line and an <strong>Add</strong> button.</li>
<li>Contour Selector — a select over the contour list, each entry labelled by its workable duration
(<code>n min</code>, or Continuous for the infinite one).</li>
<li>Edit-duration Button — opens the modal below.</li>
<li>Delete Button — confirms, then removes the selected contour. Disabled at one remaining contour.</li>
<li>Points Table — the selected contour's points, one row per point: a spindle-speed cell (rpm,
<code>min: 0</code>), a value cell, an insert-next button that clones the row, and a double-click remove
that is disabled at one remaining point. A cell edit replaces the whole point list server-side.</li>
<li>Empty state, when the axis has no contour yet.</li>
<li>Edit-duration Modal
<ul>
<li>Continuous (∞) CheckBox — disabled when another contour already holds the continuous key.</li>
<li>Workable Duration NumberField (min), shown while Continuous is off, <code>min: 0</code>.</li>
<li>Save rejects a non-positive duration and is a no-op when the key is unchanged.</li>
</ul>
</li>
</ul>
</li>
<li><code>equipment/spindle/torque</code>&ldquo;Torque Contours&rdquo;, item type <code>SpindleContour</code>, the same shape over
the torque list.</li>
<li>Shared content view, registered by the root and by all five children: the two contour charts in
the General Setup page's CONTENT column.
<ul>
<li>Power chart and Torque chart, each with a chip legend — one chip per contour, showing its
duration and, on hover, its point count. Clicking a chip selects the contour the PRIMARY-row
editor edits.</li>
<li>A vertical marker line at the gear-shift spindle speed, while one is set.</li>
<li>Its own not-attached empty state, mirroring the root panel's.</li>
</ul>
</li>
</ul>
<h2 id="behavior">Behavior</h2>
<ul>
<li><strong>Rpm ↔ cycles/s conversion lives server-side.</strong> <a class="xref" href="../../api/Hi.Milling.SpindleCapability.html">SpindleCapability</a> stores
spindle speed in cycles/s (Hz); the controller converts to rpm in its DTOs and back on write. The
frontend only ever sees rpm.</li>
<li><strong>Nullable gear shift.</strong> The Has-Gear-Shift checkbox writes <code>0</code> when checked and <code>null</code> when
cleared, which is how the model distinguishes &ldquo;mechanism present, speed not yet dialled in&rdquo; from
&ldquo;no gear-shift mechanism&rdquo;.</li>
<li><strong>One state, thin views throughout.</strong> Every panel on the branch — root, the three scalar
sections, the two contour sections and the charts — reads and writes one module-singleton
composable. A point edit therefore redraws the chart in the same tick, and a legend click moves
the editor. The composable installs a single project-change watch that reloads on open and clears
on close.</li>
<li><strong>No cache chain.</strong> These scalars feed the cutting-force and thermal physics, not the setup
canvas, so the branch's afterChange does nothing — unlike the geometry branches, nothing here
clears a geometry cache.</li>
<li><strong>Add Contour.</strong> The prompt is pre-filled with <code>60</code>; a blank entry means continuous (∞). A
duration already present is refused, a second continuous contour included. The new contour is
seeded from the currently selected contour, falling back to the first in the list, and to a
built-in default curve when the axis is empty — so the operator rarely draws from scratch.</li>
<li><strong>Project-scoped, and forwarded to the run.</strong> The capability lives on the authored equipment face
and is persisted with the <code>.hincproj</code> save; setting
<a class="xref" href="../../api/Hi.Machining.MachiningEquipmentUtils.SetupEquipment.html#Hi_Machining_MachiningEquipmentUtils_SetupEquipment_SpindleCapabilityFile">SpindleCapabilityFile</a> externalizes it
to a side-file instead. The runtime equipment face holds the same object by reference, so a scalar
or point edit is visible to a run immediately; a <em>replacement</em> (Load, Paste, XML Apply) re-stamps
that reference through the project service's ForwardSetupEnvironmentToExecution.</li>
<li><strong>Load / Paste / XML-Apply only swap the IndexService entry.</strong> The root panel then installs the
result onto the equipment through the controller's <code>update</code>, and reloads the snapshot and the key.
The controller's <code>initialize</code> indexes a blank placeholder when nothing is attached yet, which is
what keeps the ⋮ menu usable — and Load reachable — on a project with no capability.</li>
</ul>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../index.html">HiNC App Anatomy</a> for git repository links.</p>
<h3 id="web-application">Web Application</h3>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/controlTree/useControlTreeHost.ts</code> — builds the <code>equipment/spindle</code>
root (item type <code>SpindleCapabilityRoot</code>) and its five children: thermal, gear-shift and dry-run as
<code>SpindleScalars</code>, power and torque as <code>SpindleContour</code>.</li>
<li><code>wwwroot-src/src/components/controlTree/itemTypes.ts</code> — registers those three item types and gives
all of them the same CONTENT-column view.</li>
<li><code>wwwroot-src/src/components/controlTree/SpindleCapabilityPanel.vue</code> — the branch root panel: the
Object Management button, the name caption, the not-attached empty state, and the Name / Note
fields.</li>
<li><code>wwwroot-src/src/components/controlTree/SpindleScalarsPanel.vue</code> — one scalar section per role
path: thermal, gear-shift (checkbox plus the rpm field, disabled while the value is null) and
dry-run.</li>
<li><code>wwwroot-src/src/components/controlTree/SpindleContourPanel.vue</code> — the power / torque child: the
count line, Add, the contour selector, the edit-duration modal and the delete button, hosting the
point editor.</li>
<li><code>wwwroot-src/src/components/spindle/SpindleContourEditor.vue</code> — the selected contour's numeric
points table; its own header is hidden when the tree panel drives it.</li>
<li><code>wwwroot-src/src/components/spindle/SpindleContoursPanel.vue</code> — the CONTENT-column view every
spindle item registers: both charts.</li>
<li><code>wwwroot-src/src/components/spindle/SpindleContoursChart.vue</code> — one plotted chart with the
clickable chip legend that selects the contour the editor edits, plus the gear-shift marker line.</li>
<li><code>wwwroot-src/src/components/controlTree/ContentSlavePanel.vue</code> — the General Setup page's CONTENT
column, which mounts whichever content panel the selection's item type registers; it deliberately
leaves that component unkeyed so the charts survive a move between the branch's six nodes.</li>
<li><code>wwwroot-src/src/composables/useSpindleCapability.ts</code> — the module-singleton state and every
mutation on the branch: the scalar handlers, add / remove / rename contour, the point updates and
the object-management install hooks.</li>
<li><code>wwwroot-src/src/components/widgets/ObjectManagementMenuButton.vue</code> — the reused ⋮ menu carrying
Load / Save As / Copy / Paste / XML for the <code>.SpindleCapability</code> file surface.</li>
<li><code>wwwroot-src/src/api/spindleCapability.ts</code> — typed client for <code>/api/mech/spindle-capability/*</code>.</li>
<li><code>wwwroot-src/src/router/routes.ts</code> — the <code>spindle-capability/:tab?</code> redirect (the tab mapped onto
the branch child id) and the <code>equipment/spindle</code> redirect, both landing on <code>general-setup?tree=…</code>.</li>
<li><code>wwwroot-src/src/router/treeRoutes.ts</code> — defines the tab segments that redirect accepts: thermal,
gear-shift, dry-run, power, torque.</li>
<li><code>Mech/SpindleCapabilityController.cs</code> — REST surface at <code>/api/mech/spindle-capability</code> over the
authored equipment face's capability:
<ul>
<li><code>GET /</code> — flat snapshot: metadata plus the contour keys of each axis.</li>
<li><code>GET /power-contours</code>, <code>GET /torque-contours</code> — per-axis contour lists with their points.</li>
<li><code>PUT /name | /note | /energy-efficiency | /working-temperature-upper-boundary-c | /gear-shift-spindle-speed-rpm | /dry-run-friction-power-coefficient-mwdrpm | /dry-run-windage-power-coefficient-pwdrpm3</code> — one scalar each.</li>
<li><code>POST /power-contours | /torque-contours</code> — add one contour.</li>
<li><code>DELETE /power-contours/{key} | /torque-contours/{key}</code> — remove one contour (<code>{key}</code> accepts
numeric strings and <code>&quot;inf&quot;</code> / <code>&quot;infinity&quot;</code>).</li>
<li><code>PUT /power-contours/{key}/points | /torque-contours/{key}/points</code> — replace one contour's
points; this is what backs per-point editing.</li>
<li><code>PUT /power-contours/{key}/key | /torque-contours/{key}/key</code> — rename a contour's
workable-duration key, refusing a key that already exists; this is what backs the edit-duration
modal.</li>
<li><code>GET /xml</code> — serialise to XML text.</li>
<li><code>POST /load</code>, <code>POST /load-file</code>, <code>POST /reload</code> — install from XML text, from a file under a
named root, or from the file already stamped on the equipment.</li>
<li><code>PUT /capability-file</code> — set the side-file reference without reloading.</li>
<li><code>POST /initialize</code>, <code>POST /update</code> — index the capability for the Object Management button, and
install the swapped-in object back onto the equipment.</li>
</ul>
</li>
</ul>
<h3 id="hiapi-engine">HiAPI Engine</h3>
<ul>
<li><code>HiMech/Milling/SpindleCapability.cs</code> — the model: EnergyEfficiency,
WorkingTemperatureUpperBoundary_K with its <code>_C</code> accessor, the nullable GearShiftSpindleSpeed_cycleds
with its <code>_rpm</code> convenience property, the two dry-run coefficients and the power / torque contour
dictionaries keyed by workable duration.</li>
<li><code>HiMech/Machining/MachiningEquipmentUtils/SetupEquipment.cs</code> — the authored equipment face that
owns the capability and its file reference and serializes them, inline or as a side-file
reference.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="../util/mech-builder.html">Mechanism Builder Page</a> — same file-level IO pattern (Load / Reload / Save As) but user-scoped rather than project-scoped.</li>
<li><a class="xref" href="background-coolant.html">Background / Coolant</a> — sibling branch of the same Control Tree, editing the same authored equipment face.</li>
<li><a class="xref" href="../../technique/machine-capability/spindle-capability.html">Spindle Capability</a> — what the model represents physically and how the per-step ratios come out of it.</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,43 @@
<div id="sidetoggle">
<div>
<div class="sidefilter">
<form class="toc-filter">
<span class="glyphicon glyphicon-filter filter-icon"></span>
<span class="glyphicon glyphicon-remove clear-icon" id="toc_filter_clear"></span>
<input type="text" id="toc_filter_input" placeholder="Filter by title" onkeypress="if(event.keyCode==13) {return false;}">
</form>
</div>
<div class="sidetoc">
<div class="toc" id="toc">
<ul class="nav level1">
<li>
<a href="index.html" name="" title="Overview">Overview</a>
</li>
<li>
<a href="machine-tool.html" name="" title="Machine Tool">Machine Tool</a>
</li>
<li>
<a href="spindle-capability.html" name="" title="Spindle Capability">Spindle Capability</a>
</li>
<li>
<a href="background-coolant.html" name="" title="Background / Coolant">Background / Coolant</a>
</li>
<li>
<a href="fixture.html" name="" title="Fixture">Fixture</a>
</li>
<li>
<a href="workpiece.html" name="" title="Workpiece">Workpiece</a>
</li>
<li>
<a href="controller/index.html" name="controller/toc.html" title="Controller">Controller</a>
</li>
<li>
<a href="hidden-controllers.html" name="" title="Hidden Controller Branches">Hidden Controller Branches</a>
</li>
</ul>
</div>
</div>
</div>
</div>
@@ -0,0 +1,2 @@
{"items":[{"name":"Overview","href":"index.html","topicHref":"index.html"},{"name":"Machine Tool","href":"machine-tool.html","topicHref":"machine-tool.html"},{"name":"Spindle Capability","href":"spindle-capability.html","topicHref":"spindle-capability.html"},{"name":"Background / Coolant","href":"background-coolant.html","topicHref":"background-coolant.html"},{"name":"Fixture","href":"fixture.html","topicHref":"fixture.html"},{"name":"Workpiece","href":"workpiece.html","topicHref":"workpiece.html"},{"name":"Controller","href":"controller/index.html","tocHref":"controller/toc.html","topicHref":"controller/index.html","homepage":"controller/index.html"},{"name":"Hidden Controller Branches","href":"hidden-controllers.html","topicHref":"hidden-controllers.html"}]}
@@ -0,0 +1,428 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Workpiece | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Workpiece | HiAPI-C# 2025 ">
<link rel="icon" href="../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../index.html">
<img id="logo" class="svg" src="../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-Workpiece">
<h1 id="workpiece">Workpiece</h1>
<p>The Workpiece editor is the <code>equipment/workpiece</code> branch of the General Setup page's Control Tree (<code>/general-setup?tree=equipment/workpiece</code>); the old <code>/workpiece</code> route redirects there. In the WPF client it is a sub-window opened from the <a class="xref" href="../shell/main-panel.html">Main Panel</a>.</p>
<p>The key model is <a class="xref" href="../../api/Hi.NcMech.Workpieces.Workpiece.html">Workpiece</a>, taken from the Main Panel's
<a class="xref" href="../../api/Hi.MachiningProcs.LocalProjectService.html#Hi_MachiningProcs_LocalProjectService_Workpiece">Workpiece</a>. The cached solids it is drawn from belong to
<a class="xref" href="../../api/Hi.NcMech.Workpieces.WorkpieceService.html">WorkpieceService</a>, which both clients share.</p>
<p>The display config differs by client. The web branch has none of its own: it shares the General Setup
canvas, gated by the equipment-setup config (<code>Disp/EquipmentSetupDisplayeeConfig.cs</code>), which carries
the fixture and workpiece flags together — the fixture and workpiece geometry anchors keep separate
flags, while the fixture↔workpiece buckle pair, being one attached identity at one location, is a
single flag. The WPF page takes its own <a class="xref" href="../../api/Hi.NcMech.Workpieces.WorkpieceEditorDisplayeeConfig.html">WorkpieceEditorDisplayeeConfig</a>
from <code>UserService.UserConfig</code>.</p>
<h2 id="layout">Layout</h2>
<h3 id="control-tree-branch">Control Tree Branch</h3>
<ul>
<li>Workpiece Root — a summary panel, rendered inline by the dock's primary editor pane. It carries no
Object Management button: the workpiece has no standalone <code>.Workpiece</code> file surface here, and its
geometry, anchors, mesh and material are all edited through the child items below.
<ul>
<li>Raw Geometry Type Badge and Target Geometry Type Badge (<code>none</code> when the slot is empty)</li>
<li>Intro caption, and an empty-state caption while the project has no workpiece</li>
</ul>
</li>
<li><code>equipment/workpiece/raw-geometry</code>&ldquo;Raw Geometry&rdquo;, a Geometry slot backing
<a class="xref" href="../../api/Hi.NcMech.Workpieces.Workpiece.html#Hi_NcMech_Workpieces_Workpiece_InitGeom">InitGeom</a>. The slot's own panel is the kind picker
(<a class="xref" href="../geom/geom-manage-control.html">Geometry Management Control</a>, selector only); the picked kind's
editor is the slot's child item. Kinds offered: <code>Box3d</code>, <code>Cylindroid</code>, <code>StlFile</code>,
<code>TransformationGeom</code>, <code>GeomCombination</code>, <code>CubeTreeFile</code>, plus None. <code>ExtendedCylinder</code> is an
<code>IMakeXmlSource</code> and was offered here until its start section — wired by a host, never serialized
— was found to reload a saved project degenerate; see
<a class="xref" href="../geom/extended-cylinder-panel.html">Extended Cylinder Panel</a> for what an older project holding
one still shows. The source
choice folds into that one picker — picking the voxel <code>CubeTreeFile</code>, which the picker shows as
<code>MeshedGeomFile</code>, <em>is</em> the meshed source (see
<a class="xref" href="../geom/meshed-geom-panel.html">Meshed Geometry Panel</a>).</li>
<li><code>equipment/workpiece/target-geometry</code>&ldquo;Target Geometry&rdquo;, the same slot shape backing
<a class="xref" href="../../api/Hi.NcMech.Workpieces.Workpiece.html#Hi_NcMech_Workpieces_Workpiece_IdealGeom">IdealGeom</a>. Kinds offered: <code>Box3d</code>, <code>Cylindroid</code>, <code>StlFile</code>,
<code>TransformationGeom</code>, plus None. The target must satisfy <code>IGetStl</code>, so <code>CubeTreeFile</code> is not among
them.</li>
<li><code>equipment/workpiece/anchor</code>&ldquo;Anchor&rdquo;, a Group stem whose info text states how the workpiece is
placed. Two Transformer slots hang from it, each a kind picker
(<a class="xref" href="../geom/transformer-panel.html">Transformer Manage Panel</a>) over the picked kind's child editor:
<ul>
<li><code>.../anchor/geom-to-fixture</code>&ldquo;Geom To Fixture&rdquo;, which positions the workpiece geometry on the
fixture's workpiece buckle (see <a class="xref" href="fixture.html">Fixture</a>).</li>
<li><code>.../anchor/geom-to-program-zero</code>&ldquo;Geom To Program Zero&rdquo;, which places the program zero, the
NC origin, relative to the workpiece geometry.</li>
</ul>
</li>
<li><code>equipment/workpiece/runtime</code> — labelled <strong>Mesh</strong> in the tree.
<ul>
<li>Initial Resolution Select (<a class="xref" href="../../api/Hi.NcMech.Workpieces.Workpiece.html#Hi_NcMech_Workpieces_Workpiece_InitResolution">InitResolution</a>, in mm), a
fifteen-step powers-of-two ladder:
<ul>
<li>0.0009765625</li>
<li>0.001953125</li>
<li>0.00390625</li>
<li>0.0078125</li>
<li>0.015625</li>
<li>0.03125</li>
<li>0.0625</li>
<li>0.125</li>
<li>0.25</li>
<li>0.5</li>
<li>1</li>
<li>2</li>
<li>4</li>
<li>8</li>
<li>16</li>
</ul>
</li>
<li>Hint caption: a smaller resolution means a finer voxel grid and a slower runtime simulation.</li>
<li>The engine's own ladder bottoms out at <code>0.001953125</code>, so the first entry builds the same mesh
as the second. Values typed in from elsewhere are rounded to the next finer rung rather than
used as given — see <a class="xref" href="../../technique/simulation-performance/mesh-resolution.html">Mesh Resolution</a>.</li>
<li>The panel self-gates on the project having a workpiece.</li>
</ul>
</li>
<li><code>equipment/workpiece/material</code>&ldquo;Material&rdquo;, a Group stem whose info text names the two
pre-prepared resources loaded by reference. Present whenever the project has a workpiece; nothing
on this branch gates it on a physics flag. Its two children share one panel, which reads its role
from the node's own path:
<ul>
<li><code>.../material/workpiece-material</code><code>.WorkpieceMaterial</code> files, seeded to the resource root's
<code>WorkpieceMaterial</code> folder.</li>
<li><code>.../material/cutting-parameter</code><code>.mp</code> files, seeded to the resource root's
<code>CuttingParameter</code> folder.</li>
<li>Each child is one Resource-only File Path Input — the project-directory Browse entry is
suppressed, leaving the Resource-root pick — plus a readonly Name field
(<a class="xref" href="../../api/Hi.Common.INameNote.html#Hi_Common_INameNote_Name">Name</a>) and a readonly Note field
(<a class="xref" href="../../api/Hi.Common.INameNote.html#Hi_Common_INameNote_Note">Note</a>).</li>
</ul>
</li>
</ul>
<h3 id="page-frame">Page Frame</h3>
<ul>
<li>The General Setup page lays the tree dock, the content column and the equipment canvas out in
nested splitters with device-local widths; inside the dock, a height divider between the Control
Tree row and the primary editor row trades their heights. See
<a class="xref" href="index.html">General Setup Page</a>.</li>
<li>Canvas Column — one RenderingCanvas bound to the equipment-setup displayee
(<code>Disp/EquipmentSetupDisplayee.cs</code>): the merged fixture + workpiece setup scene, with optional
machine and tool solids.
<ul>
<li><a class="xref" href="../widget/renderingcanvas-tool-bar.html">RenderingCanvas Tool Bar</a></li>
<li>Display Options Menu — six groups; the workpiece's own entries are in <strong>bold</strong>:
<ul>
<li>Solid — Machine, Tool, <strong>Meshed Geometry</strong></li>
<li>Fixture Rendering Mode — Solid / Edge / Hide</li>
<li><strong>Raw Shape Rendering Mode</strong> — Solid / Edge / Hide</li>
<li><strong>Target Shape Rendering Mode</strong> — Solid / Edge / Hide</li>
<li>Anchors — Fixture Geometry Anchor, <strong>Workpiece Buckle</strong> (the one flag for the
fixture↔workpiece pair), Table Buckle, <strong>Workpiece Geometry Anchor</strong>,
<strong>Program-Zero Anchor</strong>, Controller Coordinate</li>
<li>Display Aids — Dimension Bar</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="wpf-page">WPF Page</h3>
<ul>
<li>Workpiece Page
<ul>
<li>Management Panel
<ul>
<li>Head Line
<ul>
<li><a class="xref" href="../widget/object-management-menu-button.html">Object Management Menu Button</a>
<ul>
<li>file extension is <code>.Workpiece</code>; the load type is <a class="xref" href="../../api/Hi.NcMech.Workpieces.Workpiece.html">Workpiece</a></li>
<li>The pointed Editor Panel is Management Tabs Panel</li>
</ul>
</li>
<li>Title Label</li>
</ul>
</li>
<li>Management Tabs Panel
<ul>
<li>Raw Shape Tab
<ul>
<li>Raw Geometry Source DropDown (Common Geometry and Meshed Geometry are EXCLUSIVE)
<ul>
<li>Common Geometry
Apply <a class="xref" href="../geom/geom-manage-control.html">Geometry Management Control</a></li>
<li>Meshed Geometry
Apply <a class="xref" href="../geom/meshed-geom-panel.html">Meshed Geometry Panel</a></li>
</ul>
</li>
</ul>
</li>
<li>Target Shape Tab
<ul>
<li>Geometry Management Control</li>
</ul>
</li>
<li>Anchor Tab
<ul>
<li>Geom To Fixture Tab
<ul>
<li><a class="xref" href="../geom/transformer-panel.html">Transformer Manage Panel</a></li>
</ul>
</li>
<li>Geom To Program-Zero Tab
<ul>
<li>Transformer Manage Panel</li>
</ul>
</li>
</ul>
</li>
<li>Runtime Tab
<ul>
<li>Initial Resolution Dropdown — the same powers-of-two ladder as the Mesh item above.</li>
</ul>
</li>
<li>Material Tab
Visible if <code>UserService.EnablePhysics</code> is true.
<ul>
<li>Workpiece Material File Selector
<ul>
<li>Browse Button
The initial directory is the project directory.</li>
<li>Browse Resource Button
The directory is the Default Resource directory.</li>
<li>Readonly File Path TextBox</li>
<li>Readonly Name TextBox (<a class="xref" href="../../api/Hi.Common.INameNote.html#Hi_Common_INameNote_Name">Name</a>)
<ul>
<li>ToolTip: <a class="xref" href="../../api/Hi.Common.INameNote.html#Hi_Common_INameNote_Note">Note</a></li>
</ul>
</li>
</ul>
</li>
<li>Cutting Parameter File Selector
<ul>
<li>Browse Button
The initial directory is the project directory.</li>
<li>Browse Resource Button
The directory is the Default Resource directory.</li>
<li>Readonly File Path TextBox</li>
<li>Readonly Name TextBox (<a class="xref" href="../../api/Hi.Common.INameNote.html#Hi_Common_INameNote_Name">Name</a>)
<ul>
<li>ToolTip: <a class="xref" href="../../api/Hi.Common.INameNote.html#Hi_Common_INameNote_Note">Note</a></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>Viewer Panel
<ul>
<li>Viewer ToolBar
<ul>
<li><a class="xref" href="../widget/renderingcanvas-tool-bar.html">RenderingCanvas Tool Bar</a></li>
<li>SetupDisplayee Options ToolBar
<ul>
<li>Options of <a class="xref" href="../../api/Hi.NcMech.Workpieces.WorkpieceEditorDisplayee.html">WorkpieceEditorDisplayee</a></li>
</ul>
</li>
</ul>
</li>
<li>RenderingCanvas
<ul>
<li>The <a class="xref" href="../../api/Hi.Disp.DispEngine.html">DispEngine</a>.<a class="xref" href="../../api/Hi.Disp.DispEngine.html#Hi_Disp_DispEngine_Displayee">Displayee</a> is <a class="xref" href="../../api/Hi.NcMech.Workpieces.WorkpieceEditorDisplayee.html">WorkpieceEditorDisplayee</a> (Apply the model <a class="xref" href="../../api/Hi.NcMech.Workpieces.WorkpieceEditorDisplayeeConfig.html">WorkpieceEditorDisplayeeConfig</a>).</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="default-resource">Default Resource</h2>
<p>The default resources of Workpiece Material and Cutting Parameter exist in <code>Resource</code> folder under application folder (Not project folder). Both clients seed their file pickers at the matching <code>Resource</code> sub folder:</p>
<ul>
<li>&ldquo;Resource/WorkpieceMaterial&rdquo;</li>
<li>&ldquo;Resource/CuttingParameter&rdquo;</li>
</ul>
<h2 id="behavior">Behavior</h2>
<ul>
<li><p>A geometry slot's edit re-commits the swap-in <strong>before</strong> clearing the cache. The canvas draws the
runtime face's solids, and that face follows an authored edit only by rebuild, so a bare cache
clear would re-solidify a stale face. The re-commit is skipped when the slot is cleared to None:
None leaves the old object indexed, and re-committing would resurrect it.</p>
</li>
<li><p>Call <a class="xref" href="../../api/Hi.NcMech.Workpieces.WorkpieceService.html">WorkpieceService</a>.<a class="xref" href="../../api/Hi.NcMech.Workpieces.WorkpieceService.html#Hi_NcMech_Workpieces_WorkpieceService_ClearRawGeomCache">ClearRawGeomCache()</a> on Raw Shape set or changed.</p>
</li>
<li><p>Call <a class="xref" href="../../api/Hi.NcMech.Workpieces.WorkpieceService.html">WorkpieceService</a>.<a class="xref" href="../../api/Hi.NcMech.Workpieces.WorkpieceService.html#Hi_NcMech_Workpieces_WorkpieceService_ClearIdealGeomCache">ClearIdealGeomCache()</a> on Target Shape set or changed.</p>
</li>
<li><p>Both clients reach that one service by different routes: the web branch posts the controller's
cache-clear endpoints, and the WPF page calls
<a class="xref" href="../../api/Hi.NcMech.Workpieces.WorkpieceEditorDisplayee.html">WorkpieceEditorDisplayee</a>, which forwards to the same service.</p>
</li>
<li><p>Anchor edits re-commit their transformer and deliberately do <strong>not</strong> clear the geometry cache, so
a placement change forces no <code>CubeTreeFile</code> re-mesh.</p>
</li>
<li><p>The Mesh item's Initial Resolution feeds the runtime voxel simulation, not the setup canvas, so no
cache chain runs after a change.</p>
</li>
<li><p>The equipment canvas snaps to the isometric view when its displayee is bound to a rendering
connection; a shape edit on the branch re-commits and clears the cache so the next frame
re-solidifies, and leaves the view alone. The WPF page snaps its own canvas to isometric when a
Raw or Target shape is <em>set</em> to a different object, and whenever its meshed-geometry panel updates
its content — the assumption being that setting a shape changes the viewer more than changing its
content does, so only the setter event adjusts the view.</p>
</li>
<li><p>Keep <a href="../conventions/gui-file-path-assignment.html#portability">Portability</a> of the Material properties.</p>
</li>
</ul>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../index.html">HiNC App Anatomy</a> for git repository links.</p>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/controlTree/useControlTreeHost.ts</code> — builds the whole
<code>equipment/workpiece</code> branch: the root, the two geometry slots, the anchor group with its two
transformer slots, the Mesh item and the material group with its two resource items — including
each slot's re-commit-then-clear chain and the two allowed-kind whitelists.</li>
<li><code>wwwroot-src/src/components/controlTree/PrimarySlavePanel.vue</code> — renders the root inline: the Raw
and Target type badges plus the intro.</li>
<li><code>wwwroot-src/src/components/controlTree/itemTypes.ts</code> — the ItemType registry mapping the branch's
children to their panels.</li>
<li><code>wwwroot-src/src/components/controlTree/GeometrySlotPanel.vue</code> — the kind picker behind the raw
and target geometry slots.</li>
<li><code>wwwroot-src/src/components/controlTree/TransformerSlotPanel.vue</code> — the kind picker behind the two
anchor slots.</li>
<li><code>wwwroot-src/src/components/controlTree/WorkpieceRuntimePanel.vue</code> — the Mesh item's editor: the
Initial Resolution ladder and its hint.</li>
<li><code>wwwroot-src/src/components/controlTree/WorkpieceResourcePanel.vue</code> — the one panel both Material
children share, choosing its extension and resource subfolder from the node's role path.</li>
<li><code>wwwroot-src/src/components/widgets/FilePathInput.vue</code> — the file field those children use; its
project Browse entry is suppressed under <code>resourceOnly</code>, leaving the Resource-root pick.</li>
<li><code>wwwroot-src/src/components/mech/EquipmentSetupPanel.vue</code> — the canvas column and its Display
Options menu.</li>
<li><code>wwwroot-src/src/pages/GeneralSetupPage.vue</code> — the <code>/general-setup</code> page hosting the tree dock,
the content column and the canvas in nested splitters.</li>
<li><code>wwwroot-src/src/api/workpiece.ts</code> — typed client for <code>/api/Workpiece/*</code>: initialize, status, the
geometry re-commit and cache-clear calls, the two anchor transformer updates, <code>init-resolution</code>,
the two resource loads and the diff settings.</li>
<li><code>wwwroot-src/src/api/equipmentSetup.ts</code> — typed client for the shared canvas: the three
rendering-mode targets and the ten display flags, several of which are the workpiece's own
overlays.</li>
<li><code>Mech/WorkpieceController.cs</code> — the model REST surface at <code>/api/Workpiece</code>: geometry install and
cache clears, the anchor transformer updates, <code>init-resolution</code>, <code>workpiece-material/load</code>,
<code>cutting-parameter/load</code>, and the geometry-diff settings pair (visual radius and detection radius)
whose menu rides the <a class="xref" href="../execution/execution-extended-renderingcanvas-tool-bar.html">Execution Extended Tool Bar</a> rather
than this branch.</li>
<li><code>Mech/EquipmentSetupDisplayController.cs</code><code>/api/mech/equipment-setup-display</code>: binds the
equipment-setup displayee to a rendering connection, framing it isometrically at bind time, and
serves the rendering-mode and flag endpoints.</li>
<li><code>Disp/EquipmentSetupDisplayee.cs</code> — the merged fixture + workpiece setup scene the canvas draws.</li>
<li><code>Disp/EquipmentSetupDisplayeeConfig.cs</code> — its config: the fixture and workpiece display flags in
one place.</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,184 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Box3d Control | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Box3d Control | HiAPI-C# 2025 ">
<link rel="icon" href="../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../index.html">
<img id="logo" class="svg" src="../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-Box3dControl">
<h1 id="box3d-control">Box3d Control</h1>
<p>The Box3d editor edits an axis-aligned <a class="xref" href="../../api/Hi.Geom.Box3d.html">Box3d</a> through four vector rows — Min, Max,
Dimension and Center — of which an <strong>Edit Mode</strong> selector decides which two are writable. It is
reached wherever a Geometry slot admits the <code>Box3d</code> kind: a Control-Tree kind node under a Geometry
slot, or the geometry switchboard embedded inline.</p>
<h2 id="layout">Layout</h2>
<ul>
<li><strong>Edit Mode</strong> — a selector over three modes. Its internal values are <code>MinMax</code>, <code>MinDimension</code> and
<code>CenterDimension</code>; the web labels them &ldquo;Min / Max&rdquo;, &ldquo;Min + Dimension&rdquo; and &ldquo;Center + Dimension&rdquo;,
the desktop client &ldquo;Min and Max&rdquo;, &ldquo;Min and Dimension&rdquo; and &ldquo;Center and Dimension&rdquo;.</li>
<li><strong>Min</strong>, <strong>Max</strong>, <strong>Dimension</strong>, <strong>Center</strong> — four vector rows, one X / Y / Z input each. The web
captions Dimension &ldquo;Dimension (Max Min)&rdquo; and Center &quot;Center ((Max + Min) / 2)&quot;; the Min and Max
captions come from the shared label set rather than the geometry one.</li>
</ul>
<p>The mode rules each row read-only or editable rather than showing a different set of rows: Min is
read-only in Center + Dimension, Max in anything but Min / Max, Dimension in Min / Max, and Center
in anything but Center + Dimension.</p>
<h2 id="key-model">Key Model</h2>
<p>Only <a class="xref" href="../../api/Hi.Geom.Box3d.html#Hi_Geom_Box3d_Min">Min</a> and <a class="xref" href="../../api/Hi.Geom.Box3d.html#Hi_Geom_Box3d_Max">Max</a> are settable on the type;
<a class="xref" href="../../api/Hi.Geom.Box3d.html#Hi_Geom_Box3d_Dim">Dim</a> and <a class="xref" href="../../api/Hi.Geom.Box3d.html#Hi_Geom_Box3d_Center">Center</a> are derived and get-only. That is why the
other two modes are arithmetic over the same two stored corners rather than a different storage
shape — and on the web that arithmetic happens in the browser, which then posts the resulting Min
and Max.</p>
<h2 id="behavior">Behavior</h2>
<ul>
<li><strong>A row commits on blur or Enter, not per keystroke.</strong> The vector widget emits only from its blur
handlers, suppresses the emit when the parsed value is unchanged, and reverts an unparseable entry
to its previous value.</li>
<li><strong>The web editor has no read-only variant.</strong> The desktop control additionally carries an
<code>IsInfoMode</code> property that a host sets to turn the whole control into a display: every field goes
read-only and the Edit Mode selector is hidden outright. Two hosts use it, the STL file control and
the geometry combination control. The web equivalent is not this editor — the STL file editor draws
its own read-only bounding-box rows.</li>
<li><strong>Four endpoints ship without a caller.</strong> The controller exposes <code>UpdateByMinDimension</code>,
<code>UpdateByCenterDimension</code>, <code>IndexDimension</code> and <code>IndexCenter</code>; the shipped SPA calls none of them,
because the editor resolves every mode to a plain <code>Update</code>.</li>
</ul>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../index.html">HiNC App Anatomy</a> for git repository links.</p>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/geom/Box3dEditor.vue</code> — the editor: the Edit Mode selector, the four
vector rows and their per-mode read-only rules, and the client-side arithmetic behind the two
derived modes. Its only prop is the IndexService key.</li>
<li><code>wwwroot-src/src/components/widgets/Vec3Input.vue</code> — the X / Y / Z row widget, in standard or
single-field text mode; its <code>readonly</code> prop is what the edit mode drives.</li>
<li><code>wwwroot-src/src/components/geom/geometryEditors.ts</code> — maps the <code>Box3d</code> kind to this editor, the
single map both the switchboard and the Control Tree resolve through.</li>
<li><code>wwwroot-src/src/components/geom/GeometryEditor.vue</code> — the kind picker that creates a Box3d and,
outside selector-only mode, embeds this editor beneath itself.</li>
<li><code>wwwroot-src/src/components/controlTree/itemTypes.ts</code> — registers <code>Box3d</code> against the sole-editor
panel and lists it among the kinds a container may hold.</li>
<li><code>wwwroot-src/src/components/controlTree/SoleEditorPanel.vue</code> — the kind node's panel, resolving
<code>Box3d</code> through the kind map and binding the editor to the node's key.</li>
<li><code>wwwroot-src/src/components/controlTree/GeometrySlotPanel.vue</code> — the Geometry slot's picker, from
which a Box3d is chosen.</li>
<li><code>wwwroot-src/src/api/geometry.ts</code><code>getBox3d</code>, <code>indexBox3dMin</code>, <code>indexBox3dMax</code> and the create
entry. There is deliberately no update wrapper here; the editor posts the update itself.</li>
<li><code>wwwroot-src/src/i18n/en/geom.ts</code> — the <code>box.*</code> and <code>bounds.*</code> strings, and
<code>wwwroot-src/src/i18n/en/common.ts</code> — the shared Min and Max row captions.</li>
<li><code>Geom/Box3dController.cs</code> — REST endpoints at <code>/api/Box3d/*</code>: <code>New</code>, <code>NewWithValue</code>, <code>Get</code>,
<code>Update</code>, <code>IndexMin</code>, <code>IndexMax</code>, <code>IndexDimension</code>, <code>IndexCenter</code>, <code>UpdateByMinDimension</code> and
<code>UpdateByCenterDimension</code>.</li>
<li><code>Common/IndexService.cs</code> — the keyed object store every box key resolves against.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a href="geom-manage-control.html">Geometry Management Panel</a> — the switchboard that offers this kind and
hosts this editor</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,191 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Cylindroid Control | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Cylindroid Control | HiAPI-C# 2025 ">
<link rel="icon" href="../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../index.html">
<img id="logo" class="svg" src="../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-CylindroidControl">
<h1 id="cylindroid-control">Cylindroid Control</h1>
<p>The Cylindroid editor edits a <a class="xref" href="../../api/Hi.Geom.Cylindroid.html">Cylindroid</a> — a solid of revolution defined by a list of
(Z, R) pairs — as a table of rows, with a minimum of two. It is reached from any Geometry slot that
admits the <code>Cylindroid</code> kind, and again from the Tool House cylindroid holder, whose Geometry node
embeds the same editor directly.</p>
<h2 id="layout">Layout</h2>
<ul>
<li><strong>Z-R pairs</strong> — a header row carrying the section label and an <strong>Add</strong> button. Add appends a row
ten millimetres above the last one, at the same radius.</li>
<li><strong>The table</strong> — one row per pair, columns <code>#</code>, <code>Z (mm)</code> and <code>R (mm)</code>. Each Z and R cell is an
inline text field parsed as a number; there is no spinner. A per-row remove button sits at the end
of each row, disabled once only two pairs remain.</li>
<li><strong>The caption</strong> — a one-line statement of the type: a solid of revolution defined by (Z, R) pairs,
minimum two.</li>
</ul>
<p>The desktop control is shaped differently for the same job: a title, an <strong>Add Point</strong> and a <strong>Clear
All</strong> button, and a data grid with Z, R and Actions columns. Clear All has no web counterpart — it
confirms, wipes the list and re-seeds a single pair.</p>
<h2 id="behavior">Behavior</h2>
<ul>
<li><strong>A cell commits on blur or Enter.</strong> An empty or non-finite entry is dropped rather than written,
and a negative radius is clamped to zero — on the client and again on the server.</li>
<li><strong>The floor is two pairs, and both ends enforce it.</strong> The web disables the remove button at two
and the controller refuses the call below two. The desktop client blocks only at one, with a
warning dialog.</li>
<li><strong>Creating one seeds three pairs.</strong> The create endpoint starts a new cylindroid at (0, 20),
(30, 20) and (60, 10) rather than empty.</li>
<li><strong>Four endpoints ship without a caller.</strong> The controller exposes <code>GetPairCount</code>, <code>GetPairAt</code>,
<code>UpdatePairAt</code> and <code>SortByZ</code>; the shipped SPA uses none of them, committing the whole list instead.</li>
</ul>
<h2 id="tessellation-resolution-is-not-a-property-of-the-shape">Tessellation Resolution Is Not a Property of the Shape</h2>
<p>There is no longitude count, and no per-instance resolution, on a cylindroid. The STL longitude
number is derived internally at generation time from a resolution the caller supplies; the type
carries only a static default. The desktop client once had a Longitude Number field and its handler
was deleted when the backing property went away.</p>
<p>Where resolution <em>is</em> exposed, it belongs to the holder rather than to the shape: the Tool House
cylindroid holder carries a <strong>Resolution</strong> surface beside its Geometry one — a tree node on the web,
a tab in the desktop client — offering a linear resolution in millimetres and an angle resolution in
degrees, described in the app as the tessellation resolution used for display and collision meshing.</p>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../index.html">HiNC App Anatomy</a> for git repository links.</p>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/geom/CylindroidEditor.vue</code> — the editor: the header row and Add
button, the Z / R table with its per-cell commit, and the per-row remove and its two-pair floor.
Edits by IndexService key and emits <code>changed</code> so the owner can resync.</li>
<li><code>wwwroot-src/src/components/geom/geometryEditors.ts</code> — maps the <code>Cylindroid</code> kind to this editor.</li>
<li><code>wwwroot-src/src/components/geom/GeometryEditor.vue</code> — the kind picker that creates a Cylindroid
and embeds this editor beneath itself.</li>
<li><code>wwwroot-src/src/components/controlTree/itemTypes.ts</code> — registers <code>Cylindroid</code> against the
sole-editor panel and lists it among the kinds a container may hold.</li>
<li><code>wwwroot-src/src/components/controlTree/SoleEditorPanel.vue</code> — the kind node's panel.</li>
<li><code>wwwroot-src/src/components/controlTree/toolhouse/HolderSectionPanel.vue</code> — the second host: it
embeds this editor on the holder's cylindroid key for the Geometry node, and renders the linear
and angle resolution fields for the sibling Resolution node.</li>
<li><code>wwwroot-src/src/components/controlTree/toolHouseItemTypes.ts</code> — grows those two holder children,
and only for a cylindroid holder.</li>
<li><code>wwwroot-src/src/api/geometry.ts</code><code>getCylindroid</code>, <code>updateCylindroidPairs</code>, <code>addCylindroidPair</code>,
<code>removeCylindroidPairAt</code> and the create entry.</li>
<li><code>wwwroot-src/src/api/cylindroidHolder.ts</code> — the holder side: the cylindroid key it exposes, the
geometry update, and the resolution setter.</li>
<li><code>wwwroot-src/src/i18n/en/geom.ts</code> — the <code>cylindroid.*</code> strings, and
<code>wwwroot-src/src/i18n/en/toolhouse.ts</code> — the Resolution node's own labels and hint.</li>
<li><code>Geom/CylindroidController.cs</code> — REST endpoints at <code>/api/Cylindroid/*</code>: <code>New</code>, <code>Get</code>,
<code>GetPairCount</code>, <code>GetPairAt</code>, <code>UpdatePairAt</code>, <code>UpdateAllPairs</code>, <code>AddPair</code>, <code>RemovePairAt</code> and
<code>SortByZ</code>.</li>
<li><code>Mech/CylindroidHolderController.cs</code> — the holder surface at <code>/api/CylindroidHolder/*</code> behind the
Tool House host.</li>
<li><code>Common/IndexService.cs</code> — the keyed object store every cylindroid key resolves against.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a href="geom-manage-control.html">Geometry Management Panel</a> — the switchboard that offers this kind and
hosts this editor</li>
<li><a class="xref" href="../tool-house/holder/cylindroid-holder-panel.html">Cylindroid Holder Panel</a> — the second host, and the surface
that owns the tessellation resolution</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,212 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Extended Cylinder Panel | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Extended Cylinder Panel | HiAPI-C# 2025 ">
<link rel="icon" href="../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../index.html">
<img id="logo" class="svg" src="../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-ExtendedCylinderPanel">
<h1 id="extended-cylinder-panel">Extended Cylinder Panel</h1>
<p>The Extended Cylinder editor edits one number on an <a class="xref" href="../../api/Hi.Geom.ExtendedCylinder.html">ExtendedCylinder</a>: its full
length, measured from <code>Z=0</code> and <strong>including the span below the start section</strong>. Its only home on
either client is the Tool House cutter's Upper Beam, where the start section is the flute top.</p>
<h2 id="layout">Layout</h2>
<ul>
<li>An explanatory block stating what the type is and, in bold, how the length is measured.</li>
<li><strong>Full Length</strong> — a single numeric field in millimetres, with a hint under it.</li>
</ul>
<p>The desktop panel is a title over the same one field.</p>
<h2 id="behavior">Behavior</h2>
<ul>
<li><strong>The hint states a lower bound only when that bound is positive.</strong> The bound is the Z of the
start section, served with the value as <code>minFullLength</code>; above zero the hint reads &ldquo;must exceed
the start-section Z&rdquo;, and at zero it degrades to a plain &ldquo;full length from Z=0&rdquo;. The test is the
number, not the wiring — a geometry with no start-section source and one whose source sits at
<code>Z=0</code> read identically here, because the controller answers <code>0</code> for both. There is no upper bound
anywhere — not in the editor and not in the controller.</li>
<li><strong>A live bound belongs to the cutter alone.</strong> The bare create endpoint installs a constant start
pair at <code>Z=0</code>, and the container-aware create switches install no source at all; both answer a
bound of zero. Only the cutter upper beam wires a real one — setting the beam links it to the
flute top — so only there does a bound appear.</li>
<li><strong>The guard is two layers, and they differ by one.</strong> The numeric field rejects a value <em>below</em> the
minimum — its minimum is inclusive — so a value exactly equal to the bound passes the field and is
caught one layer up by the editor, which raises an error to its host as a banner rather than an
inline field message. The controller then rejects non-finite, non-positive, and at-or-below-bound
values with a 400.</li>
<li><strong>The field commits on blur or Enter.</strong> The desktop control does not: it writes on every
keystroke, and performs no minimum, positivity or finiteness check at all. The range guard is a
web and backend feature.</li>
<li><strong>Creating one through the cutter is not the same as creating one bare.</strong> The Upper Beam creates
through the cutter's own endpoint so the backend seeds a valid full length and can return
geometry-issue warnings — which the Upper Beam panel renders as persistent banners. The bare
create endpoint seeds a length of 100 and a single start pair.</li>
</ul>
<h2 id="reach">Reach</h2>
<p>Both clients restrict this kind to the cutter's upper beam, by different mechanisms. In the desktop
client the switchboard's Extended Cylinder entry ships collapsed and is revealed by a property that
exactly one host sets — the milling cutter panel. On the web <strong>no Geometry slot offers the kind at
all</strong>: it is absent from the container kinds, so it can be neither a transformation geometry's inner
geometry nor a combination child, and it was taken out of the workpiece's raw geometry once a saved
project was found to reload one degenerate. The single surface that offers it is the cutter's Upper
Beam, which mounts the switchboard itself over its own six-kind list rather than being a Geometry
slot.</p>
<div class="NOTE">
<h5>Note</h5>
<p><strong>A project saved earlier can still carry one on a workpiece, and the two halves of the tree
disagree about it.</strong> The kind is serialized; only the start-section hookup is not. Such a project
reloads with the raw geometry in place, and the Geometry slot's child builder tests the type
against the whole kind map rather than against the slot's whitelist, so the <code>ExtendedCylinder</code>
kind node and this editor still appear beneath it. The slot's own picker tests the whitelist,
finds the type outside it and blanks — leaving an empty type dropdown above a child node that
edits an Extended Cylinder. The bound reads zero there, because nothing is wiring a start
section.</p>
</div>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../index.html">HiNC App Anatomy</a> for git repository links.</p>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/geom/ExtendedCylinderEditor.vue</code> — the description block and the Full
Length field, its hint, and the at-or-below-bound check that raises an error to the host.</li>
<li><code>wwwroot-src/src/components/widgets/NumericInput.vue</code> — the shared numeric field: an inclusive
minimum rejected on blur, and the hint slot under it.</li>
<li><code>wwwroot-src/src/components/toolhouse/UpperBeamDiv.vue</code> — the component's real home: the cutter's
Upper Beam, which creates through the container-aware cutter endpoint and renders the returned
geometry-issue warnings.</li>
<li><code>wwwroot-src/src/components/controlTree/toolhouse/CutterSectionPanel.vue</code> — mounts that panel for
the cutter's Upper Beam node, and deliberately skips its own fetch there.</li>
<li><code>wwwroot-src/src/components/controlTree/toolHouseItemTypes.ts</code> — builds the Upper Beam child of
the Cutter node.</li>
<li><code>wwwroot-src/src/components/geom/geometryEditors.ts</code> — maps the <code>ExtendedCylinder</code> kind to this
editor.</li>
<li><code>wwwroot-src/src/components/geom/GeometryEditor.vue</code> — the kind picker.</li>
<li><code>wwwroot-src/src/components/controlTree/itemTypes.ts</code> — registers <code>ExtendedCylinder</code> against the
sole-editor panel, is where its absence from the container kinds is declared, and holds the
Geometry slot's child builder — the half that tests the whole kind map rather than the whitelist.</li>
<li><code>wwwroot-src/src/components/controlTree/useControlTreeHost.ts</code> — the workpiece raw-geometry
whitelist, and the comment recording why the kind is no longer in it.</li>
<li><code>wwwroot-src/src/api/geometry.ts</code><code>getExtendedCylinder</code>,
<code>updateExtendedCylinderFullLength</code> and the create entry.</li>
<li><code>wwwroot-src/src/api/toolHouse.ts</code> — the upper-beam get, create and resync wrappers, and the
warnings contract.</li>
<li><code>wwwroot-src/src/i18n/en/geom.ts</code> — the <code>extCylinder.*</code> strings: the label, the description and
measure rule, both hints, and the below-minimum error.</li>
<li><code>Geom/ExtendedCylinderController.cs</code> — REST endpoints at <code>/api/ExtendedCylinder/*</code>: <code>New</code>, <code>Get</code>,
<code>UpdateFullLength</code> and <code>GetFullLength</code>. <code>Get</code> carries <code>minFullLength</code> alongside the value.</li>
<li><code>Mech/CutterController.cs</code> — the upper-beam routes the Tool House host actually goes through, and
the geometry-issue warnings it surfaces.</li>
<li><code>Mech/WorkpieceController.cs</code> — the raw-geometry create switch, which still carries an
<code>ExtendedCylinder</code> arm that no picker can now reach.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a href="geom-manage-control.html">Geometry Management Panel</a> — the switchboard that offers this kind and
hosts this editor</li>
<li><a class="xref" href="../tool-house/cutter/milling-cutter-panel.html">Milling Cutter Panel</a> — the cutter whose Upper Beam is this
editor's real home, and the one surface that supplies a start section</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,205 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Geometry Combination Control | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Geometry Combination Control | HiAPI-C# 2025 ">
<link rel="icon" href="../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../index.html">
<img id="logo" class="svg" src="../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-GeomCombinationControl">
<h1 id="geometry-combination-control">Geometry Combination Control</h1>
<p>A <a class="xref" href="../../api/Hi.Geom.GeomCombination.html">GeomCombination</a> is several geometries treated as one, and it has <strong>two faces on the
web</strong>: an inline editor that renders every child's own editor at once, and a Control-Tree panel that
manages the list only and puts each child on its own node. The desktop client has the inline shape
alone. There is no selection model on any of them — every child carries its own remove button.</p>
<h2 id="layout">Layout</h2>
<h3 id="the-inline-editor">The inline editor</h3>
<ul>
<li><strong>Add bar</strong> — a child-kind picker beside an <strong>Add</strong> button, and a <strong>Clear all</strong> button.</li>
<li><strong>One card per child</strong>, each with a <code>#n</code> index badge, the child's type name, its own remove
button, and — in the card body — that child's full editor, rendered recursively. All children are
editable at once.</li>
</ul>
<h3 id="the-control-tree-panel">The Control-Tree panel</h3>
<p>Deliberately shallow: a list of read-only rows with Add, a per-row remove and Clear all, and a
standing hint that an <strong>Item</strong> child node is where a child is actually edited. No editor is embedded
here; each child is its own <code>Item</code> node — <code>.../item-{index}</code> — under this one.</p>
<h3 id="the-desktop-control">The desktop control</h3>
<p>A title, then an <strong>Add</strong> and a <strong>Clear All</strong> button over a scrolling list of bordered cards, each
holding that child's own geometry management panel and a remove button — no index badge and no type
caption. Below the list sits a collapsed <strong>Combination Information</strong> expander reporting the aggregate
triangle count and bounding box, the latter as a read-only box control.</p>
<h2 id="adding-a-child">Adding a Child</h2>
<p>The two clients differ here, and the web is the one that gained something:</p>
<ul>
<li><strong>Web</strong> — pick the kind first, then Add; the request carries the kind.</li>
<li><strong>Desktop</strong> — Add takes no kind and always appends a 100 mm cube. The kind is switched afterwards,
on that child's own Geometry Type combo.</li>
</ul>
<p>Five kinds may be children — Box3d, Cylindroid, StlFile, TransformationGeom and a <strong>nested</strong>
GeomCombination, so combinations nest arbitrarily deep. <code>CubeTreeFile</code> and <code>ExtendedCylinder</code> are
accepted by neither client's switch.</p>
<h2 id="behavior">Behavior</h2>
<ul>
<li><strong>A type switch rewires the slot, not just the alias.</strong> Changing a child's kind goes through
<code>SetItemAt</code>, which replaces the entry in <a class="xref" href="../../api/Hi.Geom.GeomCombination.html#Hi_Geom_GeomCombination_StlSources">StlSources</a> itself.</li>
<li><strong>The aggregate STL is cached, and a child's own controller does not invalidate it.</strong> Editing a
child through its own endpoints leaves the combination returning the mesh it had already built, so
both web faces call <code>CleanCache</code> before bubbling the change, and compose that call into the
ancestor chain. This is the behavioural fact the rest of the page depends on.</li>
<li><strong>Removing a child does not renumber the ones after it.</strong> Item keys are minted as
<code>{key}-item-{index}</code> and the remove endpoint leaves later indices pointing at the old aliases,
which is why every structural change rebuilds the branch and re-mints them.</li>
<li><strong>Clear all confirms only on the desktop.</strong> The web posts it directly.</li>
</ul>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../index.html">HiNC App Anatomy</a> for git repository links.</p>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/geom/GeomCombinationEditor.vue</code> — the inline face: the Add bar, the
per-child cards, and the recursive editor in each. Type switches route through <code>SetItemAt</code>, a
cache clean precedes every <code>changed</code> bubble, and item aliases are released on refresh and unmount.</li>
<li><code>wwwroot-src/src/components/controlTree/GeomCombinationTreePanel.vue</code> — the Control-Tree face:
list management only, emitting a structure change so the host re-mints the item aliases.</li>
<li><code>wwwroot-src/src/components/controlTree/itemTypes.ts</code> — registers the kind against that panel with
a child builder that mints one <code>Item</code> node per element, and declares the five container kinds a
child may be.</li>
<li><code>wwwroot-src/src/components/geom/geometryEditors.ts</code> — maps the <code>GeomCombination</code> kind to the
inline editor.</li>
<li><code>wwwroot-src/src/components/geom/GeometryEditor.vue</code> — the recursive kind picker each child card
embeds.</li>
<li><code>wwwroot-src/src/components/controlTree/GeometrySlotPanel.vue</code> and
<code>wwwroot-src/src/components/controlTree/SoleEditorPanel.vue</code> — an <code>Item</code> node's picker and, once a
kind is chosen, that kind's editor.</li>
<li><code>wwwroot-src/src/components/geom/TransformationGeomEditor.vue</code> — the nesting host: a combination
is an allowed inner geometry.</li>
<li><code>wwwroot-src/src/components/controlTree/useControlTreeHost.ts</code>,
<code>wwwroot-src/src/pages/MechBuilderPage.vue</code> and
<code>wwwroot-src/src/components/toolhouse/UpperBeamDiv.vue</code> — the slots that admit the kind: the
fixture geometry, the workpiece raw geometry, a mechanism anchor's geometry and the cutter upper
beam. The workpiece's target geometry deliberately does not.</li>
<li><code>wwwroot-src/src/api/geometry.ts</code><code>getGeomCombination</code>, <code>addGeomCombinationItem</code>,
<code>setGeomCombinationItemAt</code>, <code>removeGeomCombinationItemAt</code>, <code>clearGeomCombination</code>,
<code>cleanGeomCombinationCache</code> and <code>indexGeomCombinationItemAt</code>, with the child-kind and combination
types and the create entry.</li>
<li><code>wwwroot-src/src/api/index-service.ts</code> — the index release the editor uses to drop item aliases.</li>
<li><code>wwwroot-src/src/i18n/en/geom.ts</code> — the <code>combination.*</code> strings, and
<code>wwwroot-src/src/i18n/en/tree.ts</code> — the tree panel's own strings and the <code>Item</code> node label.</li>
<li><code>Geom/GeomCombinationController.cs</code> — REST endpoints at <code>/api/GeomCombination/*</code>: <code>New</code>, <code>Get</code>,
<code>GetCount</code>, <code>AddItem</code>, <code>SetItemAt</code>, <code>RemoveItemAt</code>, <code>Clear</code>, <code>CleanCache</code>, <code>IndexItemAt</code> and
<code>GetItemTypeAt</code>. <code>AddItem</code> and <code>SetItemAt</code> share one five-arm kind switch and both clean the
cache.</li>
<li><code>Geom/TransformationGeomController.cs</code>, <code>Mech/FixtureController.cs</code>,
<code>Mech/WorkpieceController.cs</code>, <code>Mech/MechBuilder/GeneralMechanismController.cs</code> and
<code>Mech/CutterController.cs</code> — the container-aware create switches behind each host's <code>onCreate</code>.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a href="geom-manage-control.html">Geometry Management Panel</a> — the switchboard that offers this kind, and
the panel each desktop child card embeds</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,252 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Geometry Management Panel | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Geometry Management Panel | HiAPI-C# 2025 ">
<link rel="icon" href="../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../index.html">
<img id="logo" class="svg" src="../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-GeomManageControl">
<h1 id="geometry-management-panel">Geometry Management Panel</h1>
<p>The geometry switchboard is one control: a kind picker over the kinds its host allows, and — unless
the host asked for the picker alone — the picked kind's editor beneath it. On the web it binds a
<strong>key into the shared object store</strong>, not an object, and every Control-Tree Geometry slot is this
same control in selector-only mode.</p>
<h2 id="layout">Layout</h2>
<p>Web: a geometry-type dropdown; a separator, drawn only while a kind is active; then that kind's
editor. Picking a kind commits immediately — there is no Apply button. In selector-only mode
everything below the dropdown is dropped, which is the form each Geometry slot node shows.</p>
<p>Desktop: a combo box on the left of a head line with a <strong>Convert</strong> menu on its right, over a bordered
content presenter that hosts the kind's own control.</p>
<h2 id="what-it-binds">What It Binds</h2>
<p>The web panel has no target-geometry property. It takes a <code>modelKey</code> naming the object it edits and
mutates what sits behind that key; a host that also owns the <em>field</em> holding the geometry passes an
<code>onCreate</code> hook, so switching the kind rebinds the host's field rather than just the key. The
desktop panel is the one that exchanges an <a class="xref" href="../../api/Hi.Geom.IStlSource.html">IStlSource</a> directly, through a getter and
setter pair its host supplies.</p>
<p>Each host's field type decides which kinds it <em>may</em> offer; whether the kind survives a save decides
which of those it <em>does</em>. A fixture's geometry and a transformation geometry's inner geometry are
<a class="xref" href="../../api/Hi.Geom.IGetStl.html">IGetStl</a>; a workpiece's initial geometry is an <code>IMakeXmlSource</code>, which is why the
cube-tree file — which is not an <code>IGetStl</code> — reaches that picker and no other.</p>
<p>Type compatibility alone is not sufficient, and <a class="xref" href="../../api/Hi.Geom.ExtendedCylinder.html">ExtendedCylinder</a> is the case that
shows it. It is an <code>IMakeXmlSource</code>, so the workpiece's raw slot could hold one and once did — but
its start section is supplied by a host at run time and is not serialized, so a saved project
reloaded it with a zero-radius start and the shape degenerated. The kind is now offered by the one
host that wires that source, the cutter's upper beam.</p>
<h2 id="reach">Reach</h2>
<p>The picker's own default is <strong>six of the seven kinds</strong>: <a class="xref" href="../../api/Hi.Geom.Box3d.html">Box3d</a>,
<a class="xref" href="../../api/Hi.Geom.Cylindroid.html">Cylindroid</a>, <a class="xref" href="../../api/Hi.Geom.StlFile.html">StlFile</a>, <a class="xref" href="../../api/Hi.Geom.TransformationGeom.html">TransformationGeom</a>,
<a class="xref" href="../../api/Hi.Geom.GeomCombination.html">GeomCombination</a> and <a class="xref" href="../../api/Hi.Cbtr.CubeTreeFile.html">CubeTreeFile</a>. <a class="xref" href="../../api/Hi.Geom.ExtendedCylinder.html">ExtendedCylinder</a> is
in the kind map but out of that default: a host that can wire its start section names it explicitly.
Nothing is hidden by a flag on the web.</p>
<p><strong>The default is a guard rail, not a shipped list.</strong> Every host passes its own whitelist — the five
Control-Tree Geometry slots from the node context, the four inline hosts as a constant — so no
shipped surface renders the default at all, and the whitelist is what really decides reach.</p>
<table>
<thead>
<tr>
<th>Host</th>
<th>Kinds offered</th>
</tr>
</thead>
<tbody>
<tr>
<td>Fixture geometry, mechanism anchor, a transformation geometry's inner geometry, a combination child</td>
<td>the five container kinds — Box3d, Cylindroid, StlFile, TransformationGeom, GeomCombination</td>
</tr>
<tr>
<td>Workpiece raw geometry</td>
<td>six — the five container kinds plus CubeTreeFile</td>
</tr>
<tr>
<td>Workpiece target geometry</td>
<td>four</td>
</tr>
<tr>
<td>Cutter upper beam</td>
<td>six — Cylindroid, ExtendedCylinder, TransformationGeom, StlFile, Box3d, GeomCombination</td>
</tr>
</tbody>
</table>
<p>A null geometry is a legal state of the model, but the web picker offers its <strong>None (unset)</strong> entry
only when the host asks for it: the three General Setup slots and a transformation geometry's inner
geometry do, while the mechanism anchor, the cutter upper beam and combination children do not, so
those pickers cannot clear the slot. Clearing goes through the host's create hook with the literal
kind <code>None</code>, which the container-aware endpoints map to null. The desktop combo always lists a
<strong>None</strong> item.</p>
<h2 id="converting-is-desktop-only">Converting Is Desktop-Only</h2>
<p>Wrapping an existing geometry into a <a class="xref" href="../../api/Hi.Geom.TransformationGeom.html">TransformationGeom</a> — and extracting it back out
— exists only in the desktop client, as items in the <strong>Convert</strong> menu rather than as buttons. The
<em>Convert to Transformation</em> item re-titles itself <em>Extract from Transformation</em> when the current
geometry already is one; the combination item behaves the same way, and its extract is offered only
when the combination holds exactly one child. Three code-behind properties can hide the menu and each
of its two items, but no shipped host sets any of them.</p>
<p><strong>The web has no equivalent, and the difference is not cosmetic.</strong> Picking <code>TransformationGeom</code> in
the picker creates a new, empty one and discards the geometry that was there — it does not wrap it.</p>
<h2 id="in-the-control-tree">In the Control Tree</h2>
<p><code>Geometry</code> is a slot item type. Its child builder probes the slot's key for the type behind it and,
when that type is a known kind, grows exactly <strong>one</strong> child node of that kind bound to the same key
— so the slot node carries the picker and its single child carries the editor. Five kinds resolve to
the shared sole-editor panel; <code>TransformationGeom</code> and <code>GeomCombination</code> instead map to panels of
their own, because they grow further slot children rather than hosting one editor. The two
file-backed kinds compose the referenced file into their node label.</p>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../index.html">HiNC App Anatomy</a> for git repository links.</p>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/geom/GeometryEditor.vue</code> — the switchboard: the kind picker over the
allowed kinds, the opt-in unset entry, the immediate commit through the create hook, and the
active kind's editor.</li>
<li><code>wwwroot-src/src/components/geom/geometryEditors.ts</code> — the single kind → editor map both this
picker and the Control Tree's sole-editor panel resolve through.</li>
<li><code>wwwroot-src/src/components/controlTree/GeometrySlotPanel.vue</code> — the slot node's panel: this
switchboard in selector-only mode, fed its allowed kinds, its unset flag and its create hook from
the node.</li>
<li><code>wwwroot-src/src/components/controlTree/SoleEditorPanel.vue</code> — the kind child's panel.</li>
<li><code>wwwroot-src/src/components/controlTree/itemTypes.ts</code> — registers <code>Geometry</code> as a slot type with
its child builder, registers each kind against its panel, and holds both the container-kind list
and the file-backed node labels.</li>
<li><code>wwwroot-src/src/components/controlTree/useControlTreeHost.ts</code> — the three General Setup Geometry
slots and their per-host kind whitelists.</li>
<li><code>wwwroot-src/src/pages/MechBuilderPage.vue</code> and
<code>wwwroot-src/src/components/toolhouse/UpperBeamDiv.vue</code> — the two inline, non-tree hosts.</li>
<li><code>wwwroot-src/src/api/index-service.ts</code> — the type probe the picker calls to discover which kind is
currently behind the key.</li>
<li><code>wwwroot-src/src/api/geometry.ts</code> — the kind union, the create dispatcher and its per-kind
endpoint map, and every per-kind call.</li>
<li><code>wwwroot-src/src/api/fixture.ts</code>, <code>wwwroot-src/src/api/workpiece.ts</code>,
<code>wwwroot-src/src/api/toolHouse.ts</code> and <code>wwwroot-src/src/api/generalMechanism.ts</code> — the
container-aware create wrappers each host's hook posts through.</li>
<li><code>wwwroot-src/src/i18n/en/common.ts</code> and <code>wwwroot-src/src/i18n/en/geom.ts</code> — the picker's label and
its unset and empty-state strings. Geometry type names are deliberately not translated.</li>
<li>The seven per-kind controllers under <code>Geom/</code><code>Geom/Box3dController.cs</code>,
<code>Geom/CylindroidController.cs</code>, <code>Geom/ExtendedCylinderController.cs</code>, <code>Geom/StlFileController.cs</code>,
<code>Geom/CubeTreeFileController.cs</code>, <code>Geom/TransformationGeomController.cs</code> and
<code>Geom/GeomCombinationController.cs</code> — each at <code>/api/{Kind}/*</code>. There is no single geometry
controller and no geometry hub.</li>
<li><code>Common/IndexController.cs</code> — the type probe at <code>/api/Index/GetType</code>.</li>
<li><code>Common/IndexService.cs</code> — the keyed object store all of the above read and write. There is no
per-panel session object.</li>
<li><code>Mech/FixtureController.cs</code>, <code>Mech/WorkpieceController.cs</code>, <code>Mech/CutterController.cs</code> and
<code>Mech/MechBuilder/GeneralMechanismController.cs</code> — the container-aware create endpoints that set
the host's own field, and the ones that accept <code>None</code> and store null.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="../util/mech-builder.html">Mechanism Builder Page</a> — embeds this control under its anchor
editor</li>
<li><a href="box3d-control.html">Box3d Control</a> — the kind editors this switchboard resolves</li>
<li><a href="cylindroid-control.html">Cylindroid Control</a> — the same, for a solid of revolution</li>
<li><a href="extended-cylinder-panel.html">Extended Cylinder Panel</a> — the same, and the kind exactly one host offers</li>
<li><a href="stlfile-control.html">STL File Control</a> — the same, for an STL reference</li>
<li><a href="meshed-geom-panel.html">Meshed Geometry Panel</a> — the same, for a voxel cube tree</li>
<li><a href="transformation-geom-control.html">Transformation Geometry Control</a> — the container kind this panel
can wrap into on the desktop client</li>
<li><a href="geom-combination-control.html">Geometry Combination Control</a> — the other container kind, whose
every child embeds this panel again</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,228 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Geometry Panels | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Geometry Panels | HiAPI-C# 2025 ">
<link rel="icon" href="../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../index.html">
<img id="logo" class="svg" src="../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-Geom">
<h1 id="geometry-panels">Geometry Panels</h1>
<p>The Geometry Panels are the editors for <a class="xref" href="../../technique/api-foundations/basic-geometry.html">Geometry Objects</a>, together
with the switchboard that picks which <a class="xref" href="../../api/Hi.Mech.Topo.ITransformer.html">ITransformer</a> places them. Two switchboards
run the folder: the <a href="geom-manage-control.html">Geometry Management Panel</a> chooses a geometry kind and
hosts that kind's editor, and the <a href="transformer-panel.html">Transformer Select Panel</a> does the same for
a transformer.</p>
<p>On the shipped SPA a Control Tree is where both switchboards are usually met. A <strong>Geometry slot</strong> or
a <strong>Transformer slot</strong> carries the kind picker, and the picked kind becomes the slot's single child
node, whose panel is that kind's editor. The same editors are also embedded inline, without a tree —
by <a href="transformation-geom-control.html">Transformation Geometry Control</a> for its inner geometry and inner
transformer, by <a href="geom-combination-control.html">Geometry Combination Control</a> for each child, by the
Mechanism Builder page, and by the Tool House cutter's upper beam.</p>
<p>A page belongs in this folder when it documents one geometry kind's editor, or a switchboard that
picks a kind and hosts that editor. The transformer switchboard lives beside the geometry kinds
rather than under a route because it sits in the same Control-Tree slot pattern and is embedded by
the same editors.</p>
<p>Ordered the way the folder builds up: the primitive solids first, then the file-backed geometries,
then the operators that wrap and combine them, and last the two switchboards that host all of them.</p>
<h2 id="geometry-kinds">Geometry Kinds</h2>
<table>
<thead>
<tr>
<th>Page</th>
<th>What it edits</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="box3d-control.html">Box3d Control</a></td>
<td>A 3D box defined by min/max coordinates</td>
</tr>
<tr>
<td><a href="cylindroid-control.html">Cylindroid Control</a></td>
<td>A cylindroid with radius values along the Z axis</td>
</tr>
<tr>
<td><a href="extended-cylinder-panel.html">Extended Cylinder Panel</a></td>
<td>An <a class="xref" href="../../api/Hi.Geom.ExtendedCylinder.html">ExtendedCylinder</a> and its full length</td>
</tr>
<tr>
<td><a href="stlfile-control.html">STL File Control</a></td>
<td>An <a class="xref" href="../../api/Hi.Geom.IStlSource.html">IStlSource</a> loaded from an STL file</td>
</tr>
<tr>
<td><a href="meshed-geom-panel.html">Meshed Geometry Panel</a></td>
<td>A <a class="xref" href="../../api/Hi.Cbtr.CubeTreeFile.html">CubeTreeFile</a>, the pre-computed voxel cube tree; the kind picker shows it as <code>MeshedGeomFile</code></td>
</tr>
<tr>
<td><a href="transformation-geom-control.html">Transformation Geometry Control</a></td>
<td>An inner geometry plus the transformer that places it</td>
</tr>
<tr>
<td><a href="geom-combination-control.html">Geometry Combination Control</a></td>
<td>Several geometries combined into one</td>
</tr>
</tbody>
</table>
<p>Every kind above is an ordinary entry in the same geometry kind map, and the switchboard resolves each
one the same way. What separates them in practice is reach, not class: each host names the kinds its
own model accepts, so <code>CubeTreeFile</code> reaches only the workpiece's raw-geometry picker, and
<code>ExtendedCylinder</code> only the cutter's upper beam.</p>
<h2 id="switchboards">Switchboards</h2>
<table>
<thead>
<tr>
<th>Page</th>
<th>What it hosts</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="geom-manage-control.html">Geometry Management Panel</a></td>
<td>The geometry kind picker and the picked kind's editor</td>
</tr>
<tr>
<td><a href="transformer-panel.html">Transformer Select Panel</a></td>
<td>The transformer kind picker and the picked kind's editor, over all seven transformer kinds</td>
</tr>
</tbody>
</table>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../index.html">HiNC App Anatomy</a> for git repository links.</p>
<p>The editors and the two switchboards:</p>
<ul>
<li><code>wwwroot-src/src/components/geom/</code> — the geometry editors, plus
<code>wwwroot-src/src/components/geom/GeometryEditor.vue</code>, the geometry switchboard itself.</li>
<li><code>wwwroot-src/src/components/topo/</code> — the transformer editors, plus
<code>wwwroot-src/src/components/topo/TransformerSelectPanel.vue</code>.</li>
<li><code>wwwroot-src/src/components/geom/geometryEditors.ts</code> and
<code>wwwroot-src/src/components/topo/transformerEditors.ts</code> — the two kind → editor maps every host
resolves an editor through.</li>
</ul>
<p>The Control-Tree glue that makes them reachable:</p>
<ul>
<li><code>wwwroot-src/src/components/controlTree/GeometrySlotPanel.vue</code> and
<code>wwwroot-src/src/components/controlTree/TransformerSlotPanel.vue</code> — the two slot panels, each its
switchboard in selector-only mode.</li>
<li><code>wwwroot-src/src/components/controlTree/SoleEditorPanel.vue</code> — the kind node's panel, resolving the
node's kind through both maps.</li>
<li><code>wwwroot-src/src/components/controlTree/itemTypes.ts</code> — registers <code>Geometry</code> and <code>Transformer</code> as
slot types; the five leaf geometry kinds and all seven transformer kinds against <code>SoleEditorPanel</code>;
and <code>TransformationGeom</code> and <code>GeomCombination</code> against their own tree panels, which grow further
slot children instead of one editor.</li>
</ul>
<p>The REST controllers, one per kind:</p>
<ul>
<li><code>Geom/Box3dController.cs</code>, <code>Geom/CylindroidController.cs</code>, <code>Geom/ExtendedCylinderController.cs</code>,
<code>Geom/StlFileController.cs</code>, <code>Geom/CubeTreeFileController.cs</code>,
<code>Geom/TransformationGeomController.cs</code>, <code>Geom/GeomCombinationController.cs</code>.</li>
<li><code>Mech/Topo/StaticTranslationController.cs</code>, <code>Mech/Topo/StaticRotationController.cs</code>,
<code>Mech/Topo/StaticFreeformController.cs</code>, <code>Mech/Topo/DynamicTranslationController.cs</code>,
<code>Mech/Topo/DynamicRotationController.cs</code>, <code>Mech/Topo/GeneralTransformController.cs</code>,
<code>Mech/Topo/NoTransformController.cs</code>.</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,192 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Meshed Geometry Panel | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Meshed Geometry Panel | HiAPI-C# 2025 ">
<link rel="icon" href="../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../index.html">
<img id="logo" class="svg" src="../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-MeshedGeomPanel">
<h1 id="meshed-geometry-panel">Meshed Geometry Panel</h1>
<p>The meshed-geometry editor points a geometry at a pre-computed voxel cube tree in a <code>.wct</code> file. Its
model is <a class="xref" href="../../api/Hi.Cbtr.CubeTreeFile.html">CubeTreeFile</a>, a <em>reference</em> to that file: neither the editor nor the
controller loads the voxel data, which stays deferred until something actually needs the mesh.</p>
<div class="IMPORTANT">
<h5>Important</h5>
<p><strong>The kind is <code>CubeTreeFile</code>; the label is <code>MeshedGeomFile</code>.</strong> The rename is display-only and lives
in exactly two registries — the kind picker's option label and the Control-Tree node label — while
the value sent to the backend stays the type name. The domain type carries the matching display
name of its own. Any page describing a mesh source has to keep the two apart.</p>
</div>
<h2 id="layout">Layout</h2>
<ul>
<li>A caption naming what the field takes, <code>.wct</code>.</li>
<li><strong>The file selector</strong> — the shared file-path widget, filtered to <code>.wct</code>, with an empty-state hint
when nothing is chosen.</li>
<li>A standing description: the geometry is a pre-computed voxel cube tree, and loading it is deferred
until it is needed.</li>
</ul>
<p>The desktop panel is a read-only path field with <strong>Browse</strong> and <strong>Reload</strong> buttons. The web has no
Reload: the controller has no such action.</p>
<h2 id="reach">Reach</h2>
<p><strong>One slot offers this kind: the workpiece's Raw Geometry.</strong> The fixture's geometry and the
workpiece's target geometry both exclude it, and it is not among the container kinds, so it can be
neither a transformation geometry's inner geometry nor a combination child. The Control-Tree node
carries the chosen file in its label, as <code>MeshedGeomFile [&lt;path&gt;]</code>.</p>
<p>The desktop client reaches it differently. There, the workpiece page carries a <strong>Geometry Source</strong>
combo — Common Geometry or Meshed Geometry — that decides whether this panel or the geometry
switchboard is visible. The web has no such toggle: picking the <code>MeshedGeomFile</code> kind <em>is</em> choosing
the meshed source. The classification survives on the server, which still reports a raw geometry of
this type as a meshed one.</p>
<h2 id="key-model">Key Model</h2>
<p><a class="xref" href="../../api/Hi.Cbtr.CubeTreeFile.html">CubeTreeFile</a> is the file reference; <a class="xref" href="../../api/Hi.Cbtr.CubeTree.html">CubeTree</a> is the voxel tree itself.
The reader resolves the stored relative path and opens it with no extension test of its own — <code>.wct</code>
is what the app writes and what every user-facing string names, not something the loader enforces.</p>
<p>The consumer that reads the data is the equipment-setup scene: it recognises a workpiece whose
initial geometry is a cube-tree file and builds the meshed geometry for the canvas, behind the
canvas's own Meshed Geometry display toggle.</p>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../index.html">HiNC App Anatomy</a> for git repository links.</p>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/geom/CubeTreeFileEditor.vue</code> — the editor: the caption, the <code>.wct</code>
file selector and the deferred-loading description.</li>
<li><code>wwwroot-src/src/components/widgets/FilePathInput.vue</code> and
<code>wwwroot-src/src/components/widgets/FileExplorerDialog.vue</code> — the picker and the in-app browser it
opens.</li>
<li><code>wwwroot-src/src/components/geom/geometryEditors.ts</code> — maps the <code>CubeTreeFile</code> kind to this
editor.</li>
<li><code>wwwroot-src/src/components/geom/GeometryEditor.vue</code> — the kind picker, and the display-only
rename applied to its option label.</li>
<li><code>wwwroot-src/src/components/controlTree/itemTypes.ts</code> — registers the kind against the sole-editor
panel, holds the other half of the rename, builds the node label that carries the file, and is
where the kind's absence from the container kinds is declared.</li>
<li><code>wwwroot-src/src/components/controlTree/SoleEditorPanel.vue</code> and
<code>wwwroot-src/src/components/controlTree/GeometrySlotPanel.vue</code> — the kind node's panel and the
parent slot's picker.</li>
<li><code>wwwroot-src/src/components/controlTree/useControlTreeHost.ts</code> — the workpiece raw-geometry slot,
the only slot whose allowed kinds admit it, and the node-label refresh when the file changes.</li>
<li><code>wwwroot-src/src/api/geometry.ts</code><code>getCubeTreeFile</code>, <code>setCubeTreeFileSourceFile</code> and the create
entry.</li>
<li><code>wwwroot-src/src/i18n/en/geom.ts</code> — the <code>meshedFile.*</code> strings, and
<code>wwwroot-src/src/i18n/en/tree.ts</code> — the Raw Geometry slot label.</li>
<li><code>Geom/CubeTreeFileController.cs</code> — REST endpoints at <code>/api/CubeTreeFile/*</code>: <code>New</code>, <code>Get</code> and
<code>SetSourceFile</code>, and nothing else. None of them loads the voxel data.</li>
<li><code>Mech/WorkpieceController.cs</code> — the raw-geometry create path, and the classification that still
reports this type as a meshed geometry.</li>
<li><code>Common/IndexService.cs</code> — the keyed object store every cube-tree-file key resolves against.</li>
<li><code>Disp/EquipmentSetupDisplayee.cs</code> and <code>Mech/EquipmentSetupDisplayController.cs</code> — the scene that
builds the meshed geometry for the canvas, and the display toggle that shows it.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a href="geom-manage-control.html">Geometry Management Panel</a> — the switchboard that offers this kind and
hosts this editor</li>
<li><a href="stlfile-control.html">STL File Control</a> — the other file-backed kind, and the one this slot offers
beside it</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,208 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>STL File Control | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="STL File Control | HiAPI-C# 2025 ">
<link rel="icon" href="../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../index.html">
<img id="logo" class="svg" src="../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-StlFileControl">
<h1 id="stl-file-control">STL File Control</h1>
<p>The STL file editor points a geometry at an <code>.stl</code> file rather than describing a shape: the model
holds a file reference, and the editor is a picker over it plus a read-only summary of what was
loaded. On the web the file lives on the <strong>server</strong>, not on the machine running the browser, and
that difference shapes everything else on the page.</p>
<h2 id="layout">Layout</h2>
<ol>
<li><strong>File reference</strong> — a read-only field showing the path currently referenced, with an empty-state
hint when there is none.</li>
<li><strong>The picker</strong> — on the web a <strong>Select</strong> dropdown whose menu holds <em>Browse…</em>, <em>Browse Resource…</em>
and, only while a path is set, <em>Clear</em>. Either Browse entry opens the in-app File Explorer dialog
filtered to <code>.stl</code>. In the desktop client this is a <strong>Browse</strong> button opening the operating
system's file dialog, beside a <strong>Reload</strong> button and a status line; the web has no Reload.</li>
<li><strong>STL info</strong> — behind an info icon on the web, opening a dialog; a collapsed <em>Information</em>
expander in the desktop client. Both show the triangle count and the bounding box as read-only
vector rows, and the web dialog states an empty case when nothing is loaded.</li>
</ol>
<h2 id="behavior">Behavior</h2>
<ul>
<li><strong>The picker browses the server's named roots, not a file system.</strong> Three roots are exposed —
Project, Resource and Admin — and nothing outside them is reachable.</li>
<li><strong>A pick outside the current project is silently re-homed.</strong> The update endpoint stores
<code>Geom/&lt;filename&gt;</code> rather than the picked location, so saving the project copies the geometry into
the project folder. The consequence is visible: after such a pick the read-only field shows the
re-homed path, not the one that was chosen.</li>
<li><strong>The info dialog refetches on each open</strong>, because the bounding box is computed server-side by
walking the triangles rather than kept resident. Only Min and Max come back from the server;
Dimension and Center are derived in the browser from them.</li>
<li><strong>The picker previews.</strong> Double-clicking an <code>.stl</code> row in the File Explorer dialog swaps its slave
panel to a 3D preview of that file before the pick is confirmed — a capability the desktop file
dialog has no equivalent of. The dialog opens with that panel hidden, which is why the double-click
is the gesture that reaches a preview there; a single click does so only once the toolbar's pencil
has revealed the panel. The preview belongs to the File Explorer rather than to this editor — see
<a class="xref" href="../util/stl-preview-pane.html">STL Preview Pane</a>.</li>
<li><strong>One endpoint ships without a caller.</strong> The controller exposes <code>Reload</code>; nothing in the SPA calls
it.</li>
</ul>
<h2 id="reach">Reach</h2>
<p><code>StlFile</code> is the least restricted geometry kind. It is allowed in every Control-Tree geometry slot —
the fixture's geometry and both of the workpiece's — is one of the container kinds, so it can be a
transformation geometry's inner geometry or a combination child, and it is offered by the two inline
hosts as well: a mechanism anchor's geometry and the cutter's upper beam. The Control-Tree node
carries the referenced file in its own label.</p>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../index.html">HiNC App Anatomy</a> for git repository links.</p>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/geom/StlFileEditor.vue</code> — the editor: the caption, the file-path
input, and the info dialog with its triangle-count badge and four read-only vector rows.</li>
<li><code>wwwroot-src/src/components/widgets/FilePathInput.vue</code> — the picker widget: the Select dropdown
with its Browse, Browse Resource and Clear entries beside a read-only field.</li>
<li><code>wwwroot-src/src/components/widgets/FileExplorerDialog.vue</code> and
<code>wwwroot-src/src/components/FileExplorer.vue</code> — the in-app browser the Browse entries open, and
the row gestures that swap in the STL preview.</li>
<li><code>wwwroot-src/src/components/StlPreviewPane.vue</code> and <code>wwwroot-src/src/api/stlPreview.ts</code> — that
preview and the surface behind it.</li>
<li><code>wwwroot-src/src/components/widgets/Vec3Input.vue</code> — the read-only vector rows in the info dialog.</li>
<li><code>wwwroot-src/src/components/geom/geometryEditors.ts</code> — maps the <code>StlFile</code> kind to this editor.</li>
<li><code>wwwroot-src/src/components/geom/GeometryEditor.vue</code> — the kind picker; it leaves this kind's
label verbatim.</li>
<li><code>wwwroot-src/src/components/controlTree/itemTypes.ts</code> — registers <code>StlFile</code> against the
sole-editor panel, lists it among the container kinds, and builds the node label that carries the
referenced file.</li>
<li><code>wwwroot-src/src/components/controlTree/SoleEditorPanel.vue</code> and
<code>wwwroot-src/src/components/controlTree/GeometrySlotPanel.vue</code> — the kind node's panel and the
parent slot's picker.</li>
<li><code>wwwroot-src/src/components/controlTree/useControlTreeHost.ts</code> — the three tree slots that admit
the kind, and the node-label refresh when the source file changes.</li>
<li><code>wwwroot-src/src/components/geom/TransformationGeomEditor.vue</code>,
<code>wwwroot-src/src/components/geom/GeomCombinationEditor.vue</code>,
<code>wwwroot-src/src/components/toolhouse/UpperBeamDiv.vue</code> and
<code>wwwroot-src/src/pages/MechBuilderPage.vue</code> — the inline hosts that also admit it.</li>
<li><code>wwwroot-src/src/api/geometry.ts</code><code>getStlFile</code>, <code>getStlFileInfo</code>, <code>setStlFileSource</code> and the
create entry.</li>
<li><code>wwwroot-src/src/i18n/en/geom.ts</code> — the <code>stl.*</code> strings and the <code>bounds.*</code> captions shared with
the Box3d editor.</li>
<li><code>Geom/StlFileController.cs</code> — REST endpoints at <code>/api/StlFile/*</code>: <code>New</code>, <code>NewWithPath</code>, <code>Get</code>,
<code>GetInfo</code>, <code>UpdateSourceFile</code>, <code>UpdateSource</code>, <code>Clear</code>, <code>GetFileInfo</code> and <code>Reload</code>. <code>UpdateSource</code>
is where the re-homing happens; <code>GetInfo</code> answers <code>loaded: false</code> when nothing is cached.</li>
<li><code>Common/NamedRootResolver.cs</code> — the three named roots the picker can reach, and the only three.</li>
<li><code>Common/IndexService.cs</code> — the keyed object store every STL file key resolves against.</li>
<li><code>Mech/WorkpieceController.cs</code> — the workpiece slots' create paths.</li>
<li><code>Disp/StlPreviewController.cs</code> and <code>Disp/StlPreviewService.cs</code> — the picker's preview, loaded onto
the caller's own rendering connection and superseded as the selection moves.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a href="geom-manage-control.html">Geometry Management Panel</a> — the switchboard that offers this kind and
hosts this editor</li>
<li><a href="meshed-geom-panel.html">Meshed Geometry Panel</a> — the other file-backed kind, and the one the
workpiece's raw geometry offers beside this one</li>
<li><a class="xref" href="../util/stl-preview-pane.html">STL Preview Pane</a> — the preview the picker opens on an <code>.stl</code> row
before the pick is confirmed</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,49 @@
<div id="sidetoggle">
<div>
<div class="sidefilter">
<form class="toc-filter">
<span class="glyphicon glyphicon-filter filter-icon"></span>
<span class="glyphicon glyphicon-remove clear-icon" id="toc_filter_clear"></span>
<input type="text" id="toc_filter_input" placeholder="Filter by title" onkeypress="if(event.keyCode==13) {return false;}">
</form>
</div>
<div class="sidetoc">
<div class="toc" id="toc">
<ul class="nav level1">
<li>
<a href="index.html" name="" title="Overview">Overview</a>
</li>
<li>
<a href="box3d-control.html" name="" title="Box3d Control">Box3d Control</a>
</li>
<li>
<a href="cylindroid-control.html" name="" title="Cylindroid Control">Cylindroid Control</a>
</li>
<li>
<a href="extended-cylinder-panel.html" name="" title="Extended Cylinder Panel">Extended Cylinder Panel</a>
</li>
<li>
<a href="stlfile-control.html" name="" title="STL File Control">STL File Control</a>
</li>
<li>
<a href="geom-combination-control.html" name="" title="Geometry Combination Control">Geometry Combination Control</a>
</li>
<li>
<a href="meshed-geom-panel.html" name="" title="Meshed Geometry Panel">Meshed Geometry Panel</a>
</li>
<li>
<a href="transformation-geom-control.html" name="" title="Transformation Geometry Control">Transformation Geometry Control</a>
</li>
<li>
<a href="transformer-panel.html" name="" title="Transformer Panel">Transformer Panel</a>
</li>
<li>
<a href="geom-manage-control.html" name="" title="Geometry Management Panel">Geometry Management Panel</a>
</li>
</ul>
</div>
</div>
</div>
</div>
@@ -0,0 +1,2 @@
{"items":[{"name":"Overview","href":"index.html","topicHref":"index.html"},{"name":"Box3d Control","href":"box3d-control.html","topicHref":"box3d-control.html"},{"name":"Cylindroid Control","href":"cylindroid-control.html","topicHref":"cylindroid-control.html"},{"name":"Extended Cylinder Panel","href":"extended-cylinder-panel.html","topicHref":"extended-cylinder-panel.html"},{"name":"STL File Control","href":"stlfile-control.html","topicHref":"stlfile-control.html"},{"name":"Geometry Combination Control","href":"geom-combination-control.html","topicHref":"geom-combination-control.html"},{"name":"Meshed Geometry Panel","href":"meshed-geom-panel.html","topicHref":"meshed-geom-panel.html"},{"name":"Transformation Geometry Control","href":"transformation-geom-control.html","topicHref":"transformation-geom-control.html"},{"name":"Transformer Panel","href":"transformer-panel.html","topicHref":"transformer-panel.html"},{"name":"Geometry Management Panel","href":"geom-manage-control.html","topicHref":"geom-manage-control.html"}]}
@@ -0,0 +1,218 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Transformation Geometry Control | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Transformation Geometry Control | HiAPI-C# 2025 ">
<link rel="icon" href="../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../index.html">
<img id="logo" class="svg" src="../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-TransformationGeomControl">
<h1 id="transformation-geometry-control">Transformation Geometry Control</h1>
<p>A <a class="xref" href="../../api/Hi.Geom.TransformationGeom.html">TransformationGeom</a> is an inner geometry plus the transformer that places it, and the
editor is those two things side by side. It has two faces on the web: an embedded two-card editor,
and a Control-Tree panel that shows only the status of each half and puts the editing on child
nodes.</p>
<div class="IMPORTANT">
<h5>Important</h5>
<p><strong>Choosing a kind here replaces, it does not wrap.</strong> Picking an inner geometry calls the
container-aware create endpoint, which constructs a fresh instance and discards whatever was
there. Wrapping an existing geometry into a transformation geometry, and extracting it back out,
exists only in the desktop client's
<a href="geom-manage-control.html">Geometry Management Panel</a> Convert menu.</p>
</div>
<h2 id="layout">Layout</h2>
<h3 id="the-embedded-editor">The embedded editor</h3>
<p>Two bordered cards, each badged with the live type behind it, over a short explanation of what a
transformer applied to a geometry means:</p>
<ul>
<li><strong>Inner Geometry</strong> (<code>.../inner-geom</code>) — the full <a href="geom-manage-control.html">Geometry Management Panel</a>,
its dropdown labelled <em>Inner geometry type</em>, over five kinds: Box3d, Cylindroid, StlFile, a nested
TransformationGeom and GeomCombination — which is what makes recursion and nesting legal — plus a
<strong>None (unset)</strong> entry.</li>
<li><strong>Inner Transformer</strong> — the
<a class="xref" href="transformer-panel.html">Transformer Select Panel</a>, mounted with no kind restriction at all, so
its own default of <strong>all seven</strong> transformer kinds applies.</li>
</ul>
<h3 id="the-control-tree-panel">The Control-Tree panel</h3>
<p>Status badges for the two halves and the same explanation, and nothing editable: the tree grows an
<strong>Inner Geometry</strong> slot and an <strong>Inner Transformer</strong> slot as children, and each of those grows its
own kind child. The registry deliberately overrides the kind → editor map here, so the two-card
editor never appears inside a tree.</p>
<h3 id="the-desktop-control">The desktop control</h3>
<p>A geometry-type combo over the inner geometry's own control, and a transformer group box. Its lists
are narrower on both sides: three inner geometry kinds — Box3d, Cylindroid and StlFile, with no
unset entry — and a transformer list restricted to No Transform, Static Translation, Static Freeform
and General Transform. <strong>Static Rotation is unreachable there for a second reason</strong>: the desktop
transformer picker never builds it into its base list, so it is absent whatever the restriction says.</p>
<h2 id="behavior">Behavior</h2>
<ul>
<li><strong>The identity transformer is the floor on the web.</strong> The inner-transformer picker offers no null
entry, and the server installs a <code>NoTransform</code> whenever the field is null, so the slot is never
unset. The desktop panel instead carries a <strong>Not Set</strong> entry, shown by default and never removed by
its type filter, which leaves the slot holding a null transformer.</li>
<li><strong>Creation is container-aware on both halves.</strong> Both go through this type's own create endpoints
rather than the generic per-kind ones, so the owning field is rebound and not merely the store
entry.</li>
<li><strong>Neither client previews.</strong> There is no viewport on this control. It reports edits upward — one
event when a half is mutated, another when a half is replaced — and the hosting page's shared 3D
canvas re-renders from there.</li>
<li><strong>A null inner geometry has a deterministic key anyway.</strong> When the inner geometry is unset the
server returns no key, so the editor falls back to a derived alias; without it the picker could
never create the first geometry.</li>
</ul>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../index.html">HiNC App Anatomy</a> for git repository links.</p>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/geom/TransformationGeomEditor.vue</code> — the embedded editor: the two
cards and their type badges, the two container-aware create hooks, and the fallback alias for a
null inner geometry.</li>
<li><code>wwwroot-src/src/components/controlTree/TransformationGeomTreePanel.vue</code> — the Control-Tree face:
status only.</li>
<li><code>wwwroot-src/src/components/controlTree/itemTypes.ts</code> — registers the kind against that panel with
a child builder that grows the Inner Geometry and Inner Transformer slots, and is where the
override of the kind → editor map lives.</li>
<li><code>wwwroot-src/src/components/controlTree/GeometrySlotPanel.vue</code>,
<code>wwwroot-src/src/components/controlTree/TransformerSlotPanel.vue</code> and
<code>wwwroot-src/src/components/controlTree/SoleEditorPanel.vue</code> — the two child slots' pickers and
each kind grandchild's editor.</li>
<li><code>wwwroot-src/src/components/geom/geometryEditors.ts</code> and
<code>wwwroot-src/src/components/geom/GeometryEditor.vue</code> — the kind map that reaches this editor, and
the switchboard it re-enters for its own inner geometry.</li>
<li><code>wwwroot-src/src/components/topo/TransformerSelectPanel.vue</code> and
<code>wwwroot-src/src/components/topo/transformerEditors.ts</code> — the inner-transformer picker and the
seven kinds it offers, with the labels it renders them under.</li>
<li><code>wwwroot-src/src/components/geom/GeomCombinationEditor.vue</code>,
<code>wwwroot-src/src/pages/MechBuilderPage.vue</code> and
<code>wwwroot-src/src/components/toolhouse/UpperBeamDiv.vue</code> — the three embedders of the non-tree
form.</li>
<li><code>wwwroot-src/src/api/geometry.ts</code> — the transformation-geometry DTO, the two index calls and the
two create calls.</li>
<li><code>wwwroot-src/src/api/transformer.ts</code> — the transformer kind union and the per-kind endpoints the
inner transformer's editors bind through.</li>
<li><code>wwwroot-src/src/i18n/en/geom.ts</code> and <code>wwwroot-src/src/i18n/en/tree.ts</code> — the card labels, the
explanation, and the two child-node labels.</li>
<li><code>Geom/TransformationGeomController.cs</code> — REST endpoints at <code>/api/TransformationGeom/*</code>: <code>New</code>,
<code>Get</code>, <code>IndexGeom</code>, <code>IndexTransformer</code>, <code>CreateGeom</code>, <code>CreateTransformer</code>, <code>UpdateTransformer</code>,
<code>GetGeomType</code> and <code>GetTransformerType</code>. <code>IndexGeom</code> answers empty for a null geometry, and
<code>IndexTransformer</code> is where the identity default is installed.</li>
<li><code>Geom/Box3dController.cs</code>, <code>Geom/CylindroidController.cs</code>, <code>Geom/StlFileController.cs</code> and
<code>Geom/GeomCombinationController.cs</code> — the four other inner-geometry kinds' own surfaces.</li>
<li>The seven per-kind transformer controllers under <code>Mech/Topo/</code> — reached by the inner transformer's
editors for their own reads and writes, not for creation, which this type's own endpoint owns.</li>
<li><code>Common/IndexController.cs</code> and <code>Common/IndexService.cs</code> — the type probe both pickers use, and
the store the two aliases are registered into.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="../../technique/mechanism/transformers.html">Transformers</a></li>
<li><a href="geom-manage-control.html">Geometry Management Panel</a> — the switchboard this editor embeds for its
inner geometry, and the only place a wrap or extract exists</li>
<li><a class="xref" href="transformer-panel.html">Transformer Select Panel</a> — the inner-transformer picker and its seven
kinds</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,317 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Transformer Select Panel | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Transformer Select Panel | HiAPI-C# 2025 ">
<link rel="icon" href="../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../index.html">
<img id="logo" class="svg" src="../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-Transformers">
<h1 id="transformer-select-panel">Transformer Select Panel</h1>
<p>The Transformer Select Panel is the switchboard that picks which <a class="xref" href="../../api/Hi.Mech.Topo.ITransformer.html">ITransformer</a>
occupies a transformer-valued slot and edits the one that is there. Both clients carry it under the
same name: <code>wwwroot-src/src/components/topo/TransformerSelectPanel.vue</code> on the web,
<code>Mech/Topo/TransformerSelectPanel.xaml</code> on WPF. For the model behind the picker — what each
transformer does to a frame — see
<a class="xref" href="../../technique/mechanism/transformers.html">Handle Transform Matrix by ITransformer</a>.</p>
<h2 id="surface">Surface</h2>
<p>The panel's principal home is a <strong>Transformer slot</strong> of the General Setup page's Control Tree
(<code>/general-setup</code>). Four slots on that tree are Transformer slots:</p>
<ul>
<li><code>equipment/fixture/geom-to-workpiece</code>&ldquo;Geom To Workpiece&rdquo;</li>
<li><code>equipment/fixture/geom-to-table</code>&ldquo;Geom To Table&rdquo;</li>
<li><code>equipment/workpiece/anchor/geom-to-fixture</code>&ldquo;Geom To Fixture&rdquo;</li>
<li><code>equipment/workpiece/anchor/geom-to-program-zero</code>&ldquo;Geom To Program Zero&rdquo;</li>
</ul>
<p>A fifth shape appears wherever a <a class="xref" href="../../api/Hi.Geom.TransformationGeom.html">TransformationGeom</a> is grown as a tree node: its
<code>.../inner-transformer</code> child is a Transformer slot too.</p>
<p>Two surfaces embed the panel outside any tree — the <a class="xref" href="../util/mech-builder.html">Mechanism Builder Page</a>
(<code>/util/mech-builder</code>), for the selected branch's transformer, and the inner-transformer card of
<a href="transformation-geom-control.html">Transformation Geometry Control</a>.</p>
<h2 id="layout">Layout</h2>
<ul>
<li>Transformer Select Panel
<ul>
<li>Transformer Type Dropdown — one dense, outlined <code>q-select</code> listing the kinds this host allows.
Its label is the host's <code>label</code> prop, falling back to <strong>Transformer type</strong>; the inner-transformer
card labels it <strong>Inner transformer</strong>. Picking a kind commits immediately; there is no Apply button.</li>
<li>Separator — drawn only when a kind is active.</li>
<li>Active Kind's Editor — the component the kind maps to in <code>TRANSFORMER_EDITORS</code>, bound to the
IndexService key the picker resolved for the live transformer.</li>
</ul>
</li>
</ul>
<p>The panel takes a <code>selectorOnly</code> prop that drops everything below the dropdown, which gives it two
forms:</p>
<table>
<thead>
<tr>
<th>Form</th>
<th>Where it is used</th>
<th>What renders</th>
</tr>
</thead>
<tbody>
<tr>
<td>Embedded (default)</td>
<td>Mechanism Builder page; the inner-transformer card of <code>TransformationGeomEditor.vue</code></td>
<td>dropdown, separator, and the active kind's editor in one column</td>
</tr>
<tr>
<td>Selector only</td>
<td>every Control-Tree Transformer slot, through <code>TransformerSlotPanel.vue</code></td>
<td>the dropdown alone</td>
</tr>
</tbody>
</table>
<p>In a Control Tree the picker and the editor sit on two different nodes. <code>buildTransformerChildren()</code>
grows <strong>at most one</strong> child under the slot, typed with the current kind, and that child's panel is
<code>SoleEditorPanel.vue</code>, which resolves the kind through the same <code>TRANSFORMER_EDITORS</code> map and binds
it to the slot's own key. The builder first probes the slot's key with <code>getIndexType</code> and returns no
child at all when that probe fails or names a type outside the seven kinds, so a slot holding
nothing is a picker with nothing beneath it. Selecting the slot shows the picker; selecting the
child shows the kind's editor.</p>
<h3 id="transformer-type-dropdown">Transformer Type Dropdown</h3>
<p>The dropdown gets and sets the <a class="xref" href="../../api/Hi.Mech.Topo.ITransformer.html">ITransformer</a> behind the bound key. On mount, and
again whenever that key changes, it probes the object's type with <code>getIndexType</code>; picking a different
kind creates the replacement through <code>POST /api/{Kind}/New</code> unless the host supplied an <code>onCreate</code>
hook, in which case that hook runs instead so the owning domain object is rebound alongside the
IndexService entry.</p>
<p>Which kinds it offers is the <code>allowedKinds</code> prop, defaulting to all seven. A Control-Tree slot feeds
it from the node's <code>ctx.allowedKinds</code>; none of the four General Setup slots narrows it, so all seven
appear there. The WPF panel restricts through four properties on its code-behind instead:
<code>AllowedTransformerTypes</code>, <code>SelectionFilter</code>, <code>ShowNoTransform</code> and <code>ShowNotSet</code>.</p>
<p>When the probed kind falls outside <code>allowedKinds</code>, both the active and the selected kind reset to
empty: the dropdown blanks, and the embedded form prints <em>No transformer attached.</em> in place of an
editor. The out-of-list transformer is not rendered. The WPF client resolves that case the other
way — <code>UpdateUI()</code> leaves the combo unselected when no item matches, then still calls
<code>UpdateTransformerPanel()</code> and builds the current transformer's content panel.</p>
<h2 id="transformer-kinds">Transformer Kinds</h2>
<p>The web client offers seven kinds, one editor each.</p>
<table>
<thead>
<tr>
<th>Kind</th>
<th>Editor</th>
<th>What the editor holds</th>
</tr>
</thead>
<tbody>
<tr>
<td><a class="xref" href="../../api/Hi.Mech.Topo.StaticTranslation.html">StaticTranslation</a></td>
<td><code>StaticTranslationEditor.vue</code></td>
<td>one Vec3 input, the constant offset</td>
</tr>
<tr>
<td><a class="xref" href="../../api/Hi.Mech.Topo.StaticRotation.html">StaticRotation</a></td>
<td><code>StaticRotationEditor.vue</code></td>
<td>axis Vec3 input (with Normalize), angle in degrees, pivot Vec3 input</td>
</tr>
<tr>
<td><a class="xref" href="../../api/Hi.Mech.Topo.StaticFreeform.html">StaticFreeform</a></td>
<td><code>StaticFreeformEditor.vue</code></td>
<td>a 4×4 matrix grid with Identity and Invert (stored column-major, displayed row-major)</td>
</tr>
<tr>
<td><a class="xref" href="../../api/Hi.Mech.Topo.DynamicTranslation.html">DynamicTranslation</a></td>
<td><code>DynamicTranslationEditor.vue</code></td>
<td>axis Vec3 input (with Normalize) and a step in mm</td>
</tr>
<tr>
<td><a class="xref" href="../../api/Hi.Mech.Topo.DynamicRotation.html">DynamicRotation</a></td>
<td><code>DynamicRotationEditor.vue</code></td>
<td>axis Vec3 input (with Normalize), angle in degrees, pivot Vec3 input</td>
</tr>
<tr>
<td><a class="xref" href="../../api/Hi.Mech.Topo.GeneralTransform.html">GeneralTransform</a></td>
<td><code>GeneralTransformEditor.vue</code></td>
<td>a scale field over two embedded sub-transformer cards</td>
</tr>
<tr>
<td><a class="xref" href="../../api/Hi.Mech.Topo.NoTransform.html">NoTransform</a></td>
<td><code>NoTransformEditor.vue</code></td>
<td>an identity-transform notice; no controls</td>
</tr>
</tbody>
</table>
<p>The WPF client offers six of them — <code>NoTransform</code>, <code>StaticTranslation</code>, <code>StaticFreeform</code>,
<code>GeneralTransform</code>, <code>DynamicTranslation</code>, <code>DynamicRotation</code> — plus a <em>Not Set</em> entry that leaves the
slot holding a null transformer. It has no <code>StaticRotation</code> panel, and selecting <code>NoTransform</code> there
simply empties its content area. The web picker offers no null entry of either sort: identity is
expressed by choosing <code>NoTransform</code>.</p>
<div class="NOTE">
<h5>Note</h5>
<p>The <a class="xref" href="../../api/Hi.Mech.Topo.GeneralTransform.html">GeneralTransform</a> editor is a composition, not a flat form: a scale field,
then the <a class="xref" href="../../api/Hi.Mech.Topo.StaticRotation.html">StaticRotation</a> and <a class="xref" href="../../api/Hi.Mech.Topo.StaticTranslation.html">StaticTranslation</a> editors
embedded in their own bordered cards, titled <em>Rotation sub-transformer</em> and <em>Translation
sub-transformer</em>. It keys the two sub-editors off <code>POST /api/GeneralTransform/IndexRotation</code> and
<code>/IndexTranslation</code>, edits the scale itself, and owns no vector widget — every
<a class="xref" href="../../api/Hi.Geom.Vec3d.html">Vec3d</a> field on it comes from the nested editors. The effective transform is
<code>T × R × scale × I</code>. Its WPF counterpart <code>Mech/Topo/GeneralTransformPanel.xaml</code> takes the other
approach: three Vec3dControls of its own (translation, rotation axis, rotation pivot) plus scale
and angle text boxes, embedding no sub-panel.</p>
</div>
<h2 id="key-model">Key Model</h2>
<ul>
<li><a class="xref" href="../../api/Hi.Mech.Topo.ITransformer.html">ITransformer</a> — the interface the slot holds and the dropdown swaps.</li>
<li><a class="xref" href="../../api/Hi.Mech.Topo.StaticTranslation.html">StaticTranslation</a>, <a class="xref" href="../../api/Hi.Mech.Topo.StaticRotation.html">StaticRotation</a>,
<a class="xref" href="../../api/Hi.Mech.Topo.StaticFreeform.html">StaticFreeform</a>, <a class="xref" href="../../api/Hi.Mech.Topo.DynamicTranslation.html">DynamicTranslation</a>,
<a class="xref" href="../../api/Hi.Mech.Topo.DynamicRotation.html">DynamicRotation</a>, <a class="xref" href="../../api/Hi.Mech.Topo.GeneralTransform.html">GeneralTransform</a> and
<a class="xref" href="../../api/Hi.Mech.Topo.NoTransform.html">NoTransform</a> — the seven implementations the web picker lists.</li>
<li><a class="xref" href="../../api/Hi.Geom.Vec3d.html">Vec3d</a> — the axis, pivot and translation fields the static and dynamic editors bind.</li>
</ul>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../index.html">HiNC App Anatomy</a> for git repository links.</p>
<p>The switchboard and its editors:</p>
<ul>
<li><code>wwwroot-src/src/components/topo/TransformerSelectPanel.vue</code> — the picker, the create call and the
optional inline editor; props <code>modelKey</code> / <code>label</code> / <code>allowedKinds</code> / <code>onCreate</code> / <code>selectorOnly</code>,
events <code>changed</code> / <code>typeChanged</code> / <code>error</code>.</li>
<li><code>wwwroot-src/src/components/topo/transformerEditors.ts</code><code>TRANSFORMER_EDITORS</code>, the single kind →
editor map, plus <code>isTransformerKind()</code> and <code>humanizeTransformerKind()</code>.</li>
<li><code>wwwroot-src/src/components/topo/StaticTranslationEditor.vue</code></li>
<li><code>wwwroot-src/src/components/topo/StaticRotationEditor.vue</code></li>
<li><code>wwwroot-src/src/components/topo/StaticFreeformEditor.vue</code></li>
<li><code>wwwroot-src/src/components/topo/DynamicTranslationEditor.vue</code></li>
<li><code>wwwroot-src/src/components/topo/DynamicRotationEditor.vue</code></li>
<li><code>wwwroot-src/src/components/topo/GeneralTransformEditor.vue</code></li>
<li><code>wwwroot-src/src/components/topo/NoTransformEditor.vue</code></li>
</ul>
<p>The Control-Tree glue and the two direct embedders:</p>
<ul>
<li><code>wwwroot-src/src/components/controlTree/TransformerSlotPanel.vue</code> — the slot's panel: this
switchboard in selector-only mode, fed <code>allowedKinds</code> and <code>onCreate</code> from the node context.</li>
<li><code>wwwroot-src/src/components/controlTree/SoleEditorPanel.vue</code> — the kind node's panel.</li>
<li><code>wwwroot-src/src/components/controlTree/itemTypes.ts</code> — registers <code>Transformer</code> as a slot type with
<code>buildTransformerChildren</code>, and each of the seven kinds against <code>SoleEditorPanel</code>.</li>
<li><code>wwwroot-src/src/pages/MechBuilderPage.vue</code> — embeds the panel for the selected branch.</li>
<li><code>wwwroot-src/src/components/geom/TransformationGeomEditor.vue</code> — embeds it for the inner transformer.</li>
</ul>
<p>The transport and the strings:</p>
<ul>
<li><code>wwwroot-src/src/api/transformer.ts</code> — the <code>TransformerKind</code> union, the per-kind <code>/api/{Kind}/New</code>
create endpoints, and the typed wrappers for every update and <code>Index*</code> call.</li>
<li><code>wwwroot-src/src/i18n/en/topo.ts</code> — every string on these panels, including
<code>topo.select.noTransformer</code>. Transformer type names are identity and stay verbatim.</li>
</ul>
<p>The REST controllers, one per kind:</p>
<ul>
<li><code>Mech/Topo/StaticTranslationController.cs</code></li>
<li><code>Mech/Topo/StaticRotationController.cs</code></li>
<li><code>Mech/Topo/StaticFreeformController.cs</code></li>
<li><code>Mech/Topo/DynamicTranslationController.cs</code></li>
<li><code>Mech/Topo/DynamicRotationController.cs</code></li>
<li><code>Mech/Topo/GeneralTransformController.cs</code> — also serves <code>IndexRotation</code> and <code>IndexTranslation</code>.</li>
<li><code>Mech/Topo/NoTransformController.cs</code><code>New</code> and <code>Get</code>; there is nothing on a <code>NoTransform</code> to update.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="../util/mech-builder.html">Mechanism Builder Page</a> — edits a mechanisms per-branch transformer through this switchboard</li>
<li><a href="transformation-geom-control.html">Transformation Geometry Control</a> — embeds this picker as its inner
transformer, with no kind restriction at all</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,245 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>HiNC App Anatomy | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="HiNC App Anatomy | HiAPI-C# 2025 ">
<link rel="icon" href="../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../public/docfx.min.css">
<link rel="stylesheet" href="../public/main.css">
<meta name="docfx:navrel" content="../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../index.html">
<img id="logo" class="svg" src="../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy">
<h1 id="hinc-app-anatomy">HiNC App Anatomy</h1>
<p>Anatomy is the <em>view ↔ model ↔ source</em> reference for the shipped HiNC applications: one page per
component, each naming the widget tree the user sees, the HiAPI types behind it, and the files that
implement it. It documents how the shipped apps are put together — it is not a build tutorial, and
for that see <a class="xref" href="../technique/api-foundations/getting-started.html">Getting Started</a>.</p>
<p>The section is grouped by <strong>the surface that owns a page</strong>: a folder per live route that hosts a
Control-Tree branch or several pages, a single page where a route needs only one, and — where the
shipped surface owns nothing, as with a reusable control or a cross-screen rule — the source
directory the components actually live in.</p>
<div class="IMPORTANT">
<h5>Important</h5>
<p>Anatomy makes <strong>no claim</strong> that Control-Tree ids are stable across versions, and no page may state
one. <code>treeRoutes.ts</code> guarantees something narrower: an old id keeps <em>resolving</em>, because each
regroup adds a migration hop. The ids themselves have moved repeatedly.</p>
</div>
<h2 id="finding-the-page-from-the-app">Finding the Page From the App</h2>
<table>
<thead>
<tr>
<th>What you are looking at</th>
<th>Where its anatomy is</th>
</tr>
</thead>
<tbody>
<tr>
<td>The window frame, its menu bar and its message bars</td>
<td><a href="shell/index.html">App Shell</a></td>
</tr>
<tr>
<td><code>/execution</code>, and <code>?tree=execution/…</code> on it</td>
<td><a href="execution/index.html">Execution Page</a> — its Mission branch is under <a href="execution/mission/index.html">Mission</a></td>
</tr>
<tr>
<td><code>/general-setup</code>, and <code>?tree=equipment/…</code> on it</td>
<td><a href="general-setup/index.html">General Setup Page</a> — its Controller branch is under <a href="general-setup/controller/index.html">Controller</a></td>
</tr>
<tr>
<td><code>/machine-tool</code></td>
<td><a href="general-setup/machine-tool.html">Machine Tool</a> — the route is a read-only preview of the same branch</td>
</tr>
<tr>
<td><code>/tool-house/:toolId?/:tab?/:subtab?</code>, and <code>?tree=toolhouse/…</code></td>
<td><a href="tool-house/index.html">Tool House Page</a></td>
</tr>
<tr>
<td><code>/controller/:tab?</code></td>
<td><a href="legacy-controller.html">Legacy Controller</a></td>
</tr>
<tr>
<td><code>/util/file-explorer</code>, <code>/util/mech-builder</code></td>
<td><a href="util/index.html">Utility Pages</a></td>
</tr>
<tr>
<td><code>/login</code>, <code>/preference/log</code></td>
<td><a href="platform/index.html">Platform</a> — the two routes that document the platform rather than a machining step</td>
</tr>
<tr>
<td>A geometry editor embedded in any of the above</td>
<td><a href="geom/index.html">Geometry Panels</a></td>
</tr>
<tr>
<td>A control that appears on more than one screen</td>
<td><a href="widget/index.html">Widgets</a></td>
</tr>
<tr>
<td>A rule that holds across screens rather than on one</td>
<td><a href="conventions/index.html">Conventions</a></td>
</tr>
<tr>
<td>Machinery under every screen — the Control-Tree engine, the <code>?tree=</code> id surface, the locale bundles, the host process</td>
<td><a href="platform/index.html">Platform</a></td>
</tr>
<tr>
<td>A directory in one of the source trees, rather than a screen</td>
<td><a href="by-source/index.html">By Source Directory</a></td>
</tr>
</tbody>
</table>
<h2 id="sections">Sections</h2>
<ul>
<li><a href="conventions/index.html">Conventions</a> — The rules and shared contracts every page assumes: messaging, file paths, numeric values, the hub patterns, the canvas transport</li>
<li><a href="platform/index.html">Platform</a> — The machinery every route sits on and no user can point at: the Control-Tree engine, the id and route surface, session state, the locale bundles, the login gate, the log viewer and the host process</li>
<li><a href="shell/index.html">App Shell</a> — The frame every route renders inside: the Main Panel, its two message bars, and the Preference menu</li>
<li><a href="widget/index.html">Widgets</a> — The reusable controls pages embed rather than own, most-embedded first</li>
<li><a href="geom/index.html">Geometry Panels</a> — The primitive solids, the operators that combine and mesh them, and the switchboards that host both</li>
<li><a href="execution/index.html">Execution Page</a> — The run cockpit: its tool bars, the step column, the charts, and the Mission branch it hosts</li>
<li><a href="general-setup/index.html">General Setup Page</a> — The equipment Control Tree: the machine, the spindle envelope, the scene, the fixture, the workpiece and the controller branch</li>
<li><a href="tool-house/index.html">Tool House Page</a> — The tool library and the per-tool editor tabs, cutter and holder</li>
<li><a href="legacy-controller.html">Legacy Controller</a> — The superseded HardNcEnv controller surface and its REST implementation</li>
<li><a href="util/index.html">Utility Pages</a> — The two <code>/util/</code> routes: the File Explorer and the Mechanism Builder</li>
<li><a href="by-source/index.html">By Source Directory</a> — The same knowledge keyed on the source tree instead of the screen: one directory index per half of the flagship</li>
</ul>
<h2 id="the-two-codebases">The Two Codebases</h2>
<p>A page's <em>Source Code Path</em> section lists the files that implement it in the flagship web service.
The desktop client's counterparts are not listed at all: it takes no new feature work, and a
hand-maintained map of its files went stale faster than anyone read it. The two codebases are not
peers.</p>
<p><strong>HiNC-2025-webservice</strong> is the flagship — a Quasar SPA (Vue 3 + TypeScript + Pinia) served by
ASP.NET Core, and the only target for new feature work. Repository:
<a href="https://superhightech-gitea.webredirect.org/HiAPI/HiNC-2025-webservice.git">HiNC-2025-webservice.git</a></p>
<p><strong>HiNC-2025-win-desktop</strong> is the outgoing WPF client, kept here only so a reader familiar with the
desktop UI can find the equivalent web-service component. Do not start new work against it.
Repository:
<a href="https://superhightech-gitea.webredirect.org/HiNC-Deploy/HiNC-2025-win-desktop.git">HiNC-2025-win-desktop.git</a></p>
<div class="TIP">
<h5>Tip</h5>
<p>The host process, its configuration and the address it listens on are documented in
<a href="platform/program-and-hosting.html">Program and Hosting</a>.</p>
</div>
<h2 id="writing-a-page-here">Writing a Page Here</h2>
<ol>
<li><strong>Name the surface it documents</strong> in prose — the route, and where the component lives in a
Control Tree, its <code>?tree=</code> id. Make no claim that the id is stable.</li>
<li><strong>Layout</strong> — the widget tree as the user meets it, nested as the UI nests.</li>
<li><strong>Key Model</strong> — the backing HiAPI types, as <code>&lt;xref:&gt;</code> so the API reference is one click away.</li>
<li><strong>Source Code Path</strong> — the implementing files <strong>in the web service</strong>. Backtick every path, with
its extension: <code>tools/check-source-paths.ps1</code> resolves a full path against the source tree and a
bare file name by its base name, and refuses an entry carrying no backticks at all. Do not add
desktop-client files.</li>
<li><strong><code>## See Also</code></strong> — mandatory, and audited for reciprocity. An entry is not landed until the
target links back.
Anatomy is <strong>authoritative</strong> on component and source facts: when Manual and Anatomy disagree,
Anatomy is corrected first and Manual follows.</li>
</ol>
<h2 id="see-also">See Also</h2>
<ul>
<li><a href="by-source/index.html">Anatomy by Source Directory</a> — the inverse index: enter by the directory a
change landed in rather than by the screen it shows up on</li>
<li><a href="../manual/index.html">Manual</a> — the same shipped screens as an operating procedure rather than a
component breakdown</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,575 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Legacy Controller Page | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Legacy Controller Page | HiAPI-C# 2025 ">
<link rel="icon" href="../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../public/docfx.min.css">
<link rel="stylesheet" href="../public/main.css">
<meta name="docfx:navrel" content="../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../index.html">
<img id="logo" class="svg" src="../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-ControllerPage">
<h1 id="legacy-controller-page">Legacy Controller Page</h1>
<p>The Legacy Controller page is the superseded controller face: the screen that edits
<a class="xref" href="../api/Hi.Numerical.HardNcEnv.html">HardNcEnv</a>, kept reachable while it still carries settings nothing else in the
application edits. It ships at the route <code>/controller/:tab?</code>, is reached from the app menu bar's
<strong>Page → Legacy-Controller</strong> entry, and owns no Control-Tree id of its own. Controller settings for
a project are edited on the General Setup page at <code>/general-setup</code> under the Control-Tree id
<code>equipment/controller</code><a class="xref" href="general-setup/controller/index.html">Controller Branch</a> — and the two surfaces edit
different models, so an edit on one is invisible to the other.</p>
<div class="IMPORTANT">
<h5>Important</h5>
<p>The <code>equipment/controller</code> id named above belongs to the Controller Branch rather than to this
page, and Anatomy makes no claim that it is stable across versions. The migration hop that keeps
an older value resolving is described in <a class="xref" href="platform/tree-ids-and-routes.html">Tree Ids and Routes</a>.</p>
</div>
<h2 id="the-model-it-edits-and-the-one-it-does-not">The Model It Edits, and the One It Does Not</h2>
<p>The project carries both models side by side and serializes both. <a class="xref" href="../api/Hi.Numerical.HardNcEnv.html">HardNcEnv</a> is
loaded from the project's own <code>NcEnv</code> element unconditionally; <a class="xref" href="../api/Hi.NcParsers.NcRunnerSuit.html">NcRunnerSuit</a> — the
runner plus the project's per-case dependency list — is read from its own nested element. The legacy
element is a <em>fallback source</em> for the suit rather than a shared store: a project file that carries
no runner element derives one from the legacy element at load time, and from then on the two drift
apart, because every later edit lands on one of them alone.</p>
<p>The legacy model is still read at play time. The legacy NC runner is constructed over a delegate
onto the project's <code>NcEnv</code>, so installing a replacement through this page's ⋮ menu propagates without
rewiring, and a project-level switch selects which of the two pipelines plays. That switch defaults
to the SoftNc pipeline and no screen in the web application changes it; it is a scripting property,
<a class="xref" href="../api/Hi.MachiningProcs.SessionShell.html#Hi_MachiningProcs_SessionShell_EnableSoftNcRunner_">EnableSoftNcRunner</a><small>(API)</small>. The legacy NC
optimisation route is the other live reader: it is taken whenever the switch is off or the session
holds no played SoftNc layers, and it is handed <code>MachiningProject.NcEnv</code> directly.</p>
<h2 id="what-only-this-screen-edits">What Only This Screen Edits</h2>
<p>Three settings on this screen have no editor anywhere else in the web application — neither on the
branch, nor on any other page.</p>
<h3 id="align-p0-and-its-undo-history">Align P0, and its undo history</h3>
<p>The Coordinate Table tab — that is the tab button's label; <strong>ISO Coordinate Table</strong> is the heading over the panel it opens — carries a third row action beside <strong>P0</strong> and <strong>M0</strong>: <strong>Align P0</strong>,
titled <em>&quot;Move workpiece+fixture so ProgramZero coincides with this ISO entry (mutates
Fixture.GeomToTableTransformer)&quot;</em>. It does not write the coordinate — it writes the <strong>fixture's
geometry-to-table transformer</strong>, moving the workpiece and fixture so that program zero lands on the
offset the row holds. The write goes to the authored setup equipment and reports the edit, so the
runtime face follows at the next rebuild. The branch's Work Coordinates leaf carries P0 and M0 and
no alignment.</p>
<p>The endpoint keeps no undo slot. It answers with the assigned translation plus the transformer as it
stood <strong>before</strong> and <strong>after</strong> the write, both serialized as XML, and the tab holds the history
itself: <strong>Undo Align</strong> and <strong>Redo Align</strong> buttons over two component-local stacks, the undo stack
capped at 32 entries and the oldest dropped past it. A third button sits beside them, <strong>Show on
Display</strong>, which belongs to the viewer rather than to the history: it is a bound toggle over the
<code>IsoCoordinate</code> rendering flag, carrying a <code>visibility</code> / <code>visibility_off</code> icon and writing through
the same flag endpoint the Scene dropdown writes. The two datum tabs carry the same button over
<code>HeidenhainCoordinate</code>. Each step posts a stashed snapshot back to a
stateless revert endpoint, which parses it and assigns the result as the fixture's transformer. Both
stacks are cleared whenever the tab's has-a-project prop changes, and the snapshot the component
believes is current is only what it last saw — a transformer changed from another surface is
overwritten wholesale by the next undo or redo rather than merged.</p>
<p>The engine exposes the same operation as a script call,
<a class="xref" href="../api/Hi.MachiningProcs.SessionShell.html#Hi_MachiningProcs_SessionShell_AlignWorkpieceProgramZeroToIso_">AlignWorkpieceProgramZeroToIso</a><small>(API)</small>; the
history is the screen's own.</p>
<h3 id="enable-shortest-rotary-path">Enable Shortest Rotary Path</h3>
<p>The <strong>Config</strong> tab's single toggle writes <a class="xref" href="../api/Hi.Numerical.HardNcEnv.html#Hi_Numerical_HardNcEnv_EnableShortestRotary">EnableShortestRotary</a>, which
constructs <code>true</code>. Its banner reads <em>&quot;<strong>Shortest Rotary Path:</strong> optimises rotary axis motion to use
the shortest angular distance between positions.&quot;</em> On the legacy pipeline the flag gates one step:
each rotary axis of a block is cycled into the ±180° window around the previous block's value. The
toggle is narrower than it reads, because that same cycle is applied unconditionally on
<a class="xref" href="../api/Hi.Numerical.CncBrand.html#Hi_Numerical_CncBrand_Heidenhain">Heidenhain</a> — the brand branch runs it before the flag is consulted, so
clearing the box changes nothing there.</p>
<p>The runner's dependency layer declares no counterpart: no brand parameter table, no generic config
and no branch leaf carries a shortest-rotary switch. The nearest thing the runner pipeline has is the
Heidenhain <code>M126</code> / <code>M127</code> pair, which is read from the program text rather than from any setting.</p>
<h3 id="heidenhain-master-axis-character">Heidenhain master-axis character</h3>
<p>The <strong>Brand</strong> tab grows a second card while the brand is Heidenhain, holding one select over <code>A</code>,
<code>B</code>, <code>C</code> labelled <strong>Master-axis character</strong>. It writes
<a class="xref" href="../api/Hi.Numerical.HardNcEnv.html#Hi_Numerical_HardNcEnv_HeidenhainMasterAxisChar">HeidenhainMasterAxisChar</a>, a character face over the integer axis
direction the <code>PLANE … SEQ</code> solution family is resolved against. Reads are normalised: anything that
is not <code>B</code> or <code>C</code> after trimming and upper-casing becomes <code>A</code>. On the runner pipeline the master
rotary is <strong>derived rather than configured</strong> — it is the first declared rotary axis — so the branch
has nothing to expose and no leaf for it.</p>
<h2 id="what-it-shares-with-the-branch">What It Shares With the Branch</h2>
<p>Naming what is <em>not</em> exclusive matters as much. The CNC brand, the stroke limits, the rapid feed,
the tool-change time, the tool offset table with its tool-house dependence, the work coordinates
with their P0 and M0 actions, and the Heidenhain datum preset and datum shift tables all have
editors on the branch — see
<a class="xref" href="general-setup/controller/brand-matrix.html">Brand Matrix</a> for which of those leaves each brand grows. The
<strong>Max Speed (rpm)</strong> column has a counterpart too: on the runner pipeline the rotary speed ceiling is
read from the rapid-feedrate config, and the legacy import funnels this field into it at rpm × 360
deg/min, so the branch's Rapid Feedrates leaf edits the runner-side number.</p>
<p>One control on this screen writes a value the branch also writes. <strong>Set ideal offset dependent on
tool house</strong> is a project-level configuration flag rather than a member of either NC model, and both
the Offset Table tab and the branch's Tool Offsets leaf read and write that one flag. The two
<em>tables</em> stay separate: this tab's <strong>Refresh from Tool House</strong> recomputes
<a class="xref" href="../api/Hi.Numerical.HardNcEnv.html#Hi_Numerical_HardNcEnv_MillingToolOffsetTable">MillingToolOffsetTable</a> from <a class="xref" href="../api/Hi.Machining.MachiningToolHouse.html">MachiningToolHouse</a>,
while the branch's refresh recomputes the runner's own tool-offset table.</p>
<h2 id="two-faces-of-a-work-coordinate">Two Faces of a Work Coordinate</h2>
<p>The work-coordinate marker is one displayee class serving two providers. This page's viewer draws it
from <a class="xref" href="../api/Hi.Numerical.HardNcEnv.html#Hi_Numerical_HardNcEnv_IsoCoordinateTable">IsoCoordinateTable</a> — a
<a class="xref" href="../api/Hi.NcParsers.Dependencys.Generic.IsoCoordinateTable.html">IsoCoordinateTable</a> instance owned by the legacy model, which
constructs with <code>G54</code><code>G59</code> and <code>G59.1</code><code>G59.9</code> all at zero. The General Setup canvas builds the
same displayee over the active runner's effective
<a class="xref" href="../api/Hi.NcParsers.Dependencys.IIsoCoordinateConfig.html">IIsoCoordinateConfig</a> instead, which on most brands is the brand
parameter table. Same marker, same code, two stores.</p>
<p>Selecting a row on the ISO Coordinate Table tab writes the marker's id onto the shared Execution
displayee, so it decides which offset the marker draws at. It decides that whether or not the marker
is drawn: all three coordinate flags are off in the shipped rendering-flag set — as are the machine
tool and the cutter, leaving the workpiece, the fixture, the dimension bar and the cutter-location
strip as the four that arrive on — so on an untouched project the selection column moves something
invisible until <strong>Show on Display</strong> is pressed. The datum tabs also carry a
single-selection column, but nothing is sent when it changes — there the selection is a highlight and
nothing more.</p>
<h2 id="the-tabs">The Tabs</h2>
<p>The left pane is a tab strip over seven panels, addressable as the route's optional segment:
<code>coordinate-table</code>, <code>datum-preset</code>, <code>datum-shift</code>, <code>offset-table</code>, <code>machine</code>, <code>brand</code>, <code>config</code>. A
bare <code>/controller</code> canonicalises to <code>coordinate-table</code>. Two tab <strong>buttons</strong> are conditional — Datum
Preset and Datum Shift render only while the brand reads Heidenhain — but their panels are always
present in the template, so both segments stay valid URL targets under any brand. Switching the brand
away from Heidenhain while one of those two is active moves the selection back to Coordinate Table.</p>
<table>
<thead>
<tr>
<th>Tab</th>
<th>Edits</th>
</tr>
</thead>
<tbody>
<tr>
<td>Coordinate Table</td>
<td><a class="xref" href="../api/Hi.Numerical.HardNcEnv.html#Hi_Numerical_HardNcEnv_IsoCoordinateTable">IsoCoordinateTable</a>, plus the P0 / M0 / Align P0 row actions</td>
</tr>
<tr>
<td>Datum Preset</td>
<td><a class="xref" href="../api/Hi.Numerical.HardNcEnv.html#Hi_Numerical_HardNcEnv_HeidenhainDatumPresetTable">HeidenhainDatumPresetTable</a>, keyed <code>Q339</code></td>
</tr>
<tr>
<td>Datum Shift</td>
<td><a class="xref" href="../api/Hi.Numerical.HardNcEnv.html#Hi_Numerical_HardNcEnv_HeidenhainDatumShiftTable">HeidenhainDatumShiftTable</a>, keyed <code>D</code></td>
</tr>
<tr>
<td>Offset Table</td>
<td><a class="xref" href="../api/Hi.Numerical.MillingToolOffsetTable.html">MillingToolOffsetTable</a> with row-level add, delete and key rename</td>
</tr>
<tr>
<td>Machine</td>
<td><a class="xref" href="../api/Hi.Numerical.HardNcEnv.html#Hi_Numerical_HardNcEnv_RapidFeedrate_mmdmin">RapidFeedrate_mmdmin</a>, <a class="xref" href="../api/Hi.Numerical.HardNcEnv.html#Hi_Numerical_HardNcEnv_ToolingTime">ToolingTime</a>, <a class="xref" href="../api/Hi.Numerical.HardNcEnv.html#Hi_Numerical_HardNcEnv_StrokeLimitXyz_mm">StrokeLimitXyz_mm</a>, <a class="xref" href="../api/Hi.Numerical.HardNcEnv.html#Hi_Numerical_HardNcEnv_StrokeLimitAbc_rad">StrokeLimitAbc_rad</a>, <a class="xref" href="../api/Hi.Numerical.HardNcEnv.html#Hi_Numerical_HardNcEnv_MaxRotarySpeedABC_radds">MaxRotarySpeedABC_radds</a></td>
</tr>
<tr>
<td>Brand</td>
<td><a class="xref" href="../api/Hi.Numerical.HardNcEnv.html#Hi_Numerical_HardNcEnv_CncBrand">CncBrand</a>, and the master-axis character on Heidenhain</td>
</tr>
<tr>
<td>Config</td>
<td><a class="xref" href="../api/Hi.Numerical.HardNcEnv.html#Hi_Numerical_HardNcEnv_EnableShortestRotary">EnableShortestRotary</a></td>
</tr>
</tbody>
</table>
<p>The brand select offers all five declared brands — <a class="xref" href="../api/Hi.Numerical.CncBrand.html#Hi_Numerical_CncBrand_Fanuc">Fanuc</a>,
<a class="xref" href="../api/Hi.Numerical.CncBrand.html#Hi_Numerical_CncBrand_Heidenhain">Heidenhain</a>, <a class="xref" href="../api/Hi.Numerical.CncBrand.html#Hi_Numerical_CncBrand_Mazak">Mazak</a>,
<a class="xref" href="../api/Hi.Numerical.CncBrand.html#Hi_Numerical_CncBrand_Siemens">Siemens</a> and <a class="xref" href="../api/Hi.Numerical.CncBrand.html#Hi_Numerical_CncBrand_Syntec">Syntec</a> — labelled with the
brand names verbatim rather than through the locale bundle, and warns above the select that
<em>&quot;Brand-specific settings may be lost when the brand changes.&quot;</em></p>
<p><strong>The Machine tab's axis rows are fixed, not chain-driven.</strong> It renders exactly <code>X</code>, <code>Y</code>, <code>Z</code> under
<strong>Linear Axis Stroke (mm)</strong> and exactly <code>A</code>, <code>B</code>, <code>C</code> under <strong>Rotary Axis Stroke (deg) &amp; Max Speed
(rpm)</strong>, because both row sets are literal in the template. Every write there sends the whole vector
— all six linear bounds, all six rotary bounds converted to radians, or all three speeds converted to
rad/s — rather than the one cell that changed. The branch's per-axis leaves take the opposite shape:
one row per machine-chain axis, one axis per write, described in
<a class="xref" href="general-setup/controller/machine/per-axis-tables.html">Per-Axis Tables</a>.</p>
<p>Both stroke limits construct as an infinite box, and the service serializes named floating-point
literals, so those bounds reach the browser as the tokens <code>Infinity</code> and <code>-Infinity</code> rather than as
numbers. The shared numeric field prints what it is handed and parses both words back, so an
unconfigured Machine tab opens showing those words rather than a blank and takes them typed in; the
rotary rows carry them through unconverted, because the degree conversion guards on a finite value.</p>
<p><strong>A stroke write does not survive its own re-read.</strong> The read and the write agree on the wire order:
the read emits the six bounds interleaved per axis, <code>[minX, maxX, minY, maxY, minZ, maxZ]</code>, and the
tab posts the same six back. The write then hands that vector to the box constructor, which reads its
arguments <em>grouped</em> — the whole minimum corner first, the whole maximum corner second. The vector is
de-interleaved on the way in, so only the first and the last number land where they were sent and the
four between them move to other rows; from the infinite default above, that is enough to leave the
middle axis reading a <code>Max</code> below its <code>Min</code> after any single edit. The rotary stroke endpoints share
the constructor and the behaviour; the three <strong>Max Speed</strong> values travel as a plain triple and are
unaffected. The regrouped box is not inert — with the stroke check on and no runner-side stroke
config resolving, the legacy play path validates every step against
<a class="xref" href="../api/Hi.Numerical.HardNcEnv.html#Hi_Numerical_HardNcEnv_StrokeLimitXyz_mm">StrokeLimitXyz_mm</a> and <a class="xref" href="../api/Hi.Numerical.HardNcEnv.html#Hi_Numerical_HardNcEnv_StrokeLimitAbc_rad">StrokeLimitAbc_rad</a>
as stored. The branch's per-axis leaves, writing one axis at a time, carry none of it.</p>
<h2 id="how-an-edit-commits">How an Edit Commits</h2>
<p>The tabs reuse the same numeric field the rest of the application uses, so the timing is that
widget's: commit on blur or on Enter, never per keystroke, with the bounds and parse behaviour set
out in <a class="xref" href="widget/numeric-input.html">Numeric Input</a>. Clearing a cell parses to null and every legacy
handler returns on null, so an emptied cell is not an edit — the one exception is the Offset Table's
tool-number cell, which forbids an empty value and shows a parse error in place instead. Selects,
toggles and row buttons commit on the click.</p>
<p>The optimistic write is the same capture-assign-await-restore shape the branch's panels use, and
<a class="xref" href="general-setup/controller/editing-contract.html">Editing Contract</a> is where that shape, its row-scoped
payloads and its rollback semantics are set out once. Four differences are this screen's own:</p>
<ul>
<li><strong>Three fields commit without a rollback.</strong> The Machine tab's <strong>Rapid Feedrate</strong> and <strong>Tooling
Time</strong> handlers assign the new value and await the write without capturing the old one, so a
refused write raises the toast and leaves the field showing the number the server rejected. The
Brand tab's <strong>Master-axis character</strong> select is the third and the least recoverable: its handler
captures nothing and restores nothing, and its two-way binding has already put the pick into the
tab's own state before the handler runs, so a refused write leaves the rejected character on screen
looking accepted. Every other value handler — the stroke and speed vectors, the coordinate and
datum cells, the offset row and its key rename, the brand select itself, the shortest-rotary toggle
(two-way bound as well, but reconstructing its old value from the new one) — restores the old value
on failure. Row actions are outside the shape entirely: <strong>P0</strong>, <strong>M0</strong>, the datum reset, an add and
a delete all write first and touch the local rows only once the request resolves.</li>
<li><strong>The REST surface answers with status codes, not a success envelope.</strong> A missing table, an
unknown index or an unexpected paste type is answered as <em>not found</em>, <em>bad request</em> or <em>conflict</em>.
Most of those answers are bare English sentences; four carry a code — no project loaded, no NcEnv
found, no NcEnv configuration and no fixture — which the client re-renders from the locale bundle
under any language but English. The branch's surface instead answers a missing dependency inside a
200 envelope; nothing here does.</li>
<li><strong>A refused load is silent on six of the seven tabs.</strong> Every write wrapper in this page's API
module goes through the plain-JSON helper, which throws on any non-2xx and returns the parsed body
otherwise, so a refused write always reaches a toast. Eight read wrappers deliberately bypass it
and answer a benign default on any non-2xx: an empty list for the coordinate table, both datum
tables and the offset table, <em>unknown</em> for the brand, <code>A</code> for the master-axis character, and <em>off</em>
for the tool-house dependence and shortest-rotary flags. A refused load therefore renders as an
empty or default tab, indistinguishable from one whose table really is empty, with no toast and no
console line. Only the Machine tab's five reads throw, so it is the one tab on which a refused load
shows.</li>
<li><strong>The tool-house dependence toggle writes before it flips.</strong> Turning it on awaits the flag write,
then flips the local toggle, then refreshes the offsets from the tool house and re-reads the table.
A failure in the refresh therefore leaves the flag committed on both sides rather than reverted.</li>
</ul>
<p>Every tab composes its failure toast the same way: negative, 3.5 seconds, the tab's own localized
context followed by the thrown error's own text, with a console line naming the component. That text
is built by the shared helper as <code>HTTP &lt;status&gt;: &lt;server message&gt;</code>, so the status code reaches the
user in the toast — where the branch's envelope failures surface the server's sentence alone.</p>
<p>Each tab fetches once on mount and again when its has-a-project prop turns true; the tab panels are
kept alive, so moving between tabs does not refetch. That prop is the indexed key rather than the
project store's own flag: Initialize answers <em>not found</em> where the project carries no <code>NcEnv</code>, so a
project can be open while every tab still reads as having none. Nothing pushes changes at a mounted
tab, and no tab re-reads after a cell edit — the Offset Table's two tool-house actions are the one
exception, because the server recomputes the table under them.</p>
<p>The four tables that render as data tables — the coordinate table, the two datum tables and the
offset table — are the only places in the application that use that component. None of them declares
a sortable column, and all four run unpaginated with every row shown.</p>
<h2 id="object-management-and-the-install-chain">Object Management and the Install Chain</h2>
<p>The left pane's head line carries the shared object-management ⋮ menu, the title <strong>Controller</strong>, and
a badge reading <em>ready</em> while a key is indexed and <em>no project</em> otherwise. The menu's entries are
Load, Save As, Copy, Paste and XML Mode; Load Resource is absent because the page passes no resource
directory. Load and Save As browse the server file system through the shared file-explorer dialog
filtered to <code>*.NcEnv / *.xml</code>, rooted at the project directory once a project is open; Save As
proposes the name <code>NcEnv.xml</code>; Paste is checked against the expected type
<code>Hi.Numerical.HardNcEnv, HiUniNc</code>.</p>
<p>Load, Paste and an XML apply swap only the indexed object, so the page then installs it: it posts the
indexed key to the install endpoint <strong>before</strong> re-running Initialize, because Initialize re-indexes
from the project and would otherwise resurrect the object that was replaced. A failed install stops
the chain with a toast. A successful one re-reads the brand, which is what re-gates the two Heidenhain
tabs, refreshes the rendering flags, and raises an informational toast naming the installed type. The
XML dialog's Apply raises the load event as well as its own, and the page listens only to the load
event, so the chain runs once rather than twice.</p>
<p>Initialize mints a fresh index key on every call, and the page registers each one for cleanup and
drops the key it replaced, so repeated installs do not accumulate entries.</p>
<h2 id="the-viewer">The Viewer</h2>
<p>The right pane is a rendering canvas with its own toolbar: the shared view toolbar, a <strong>Scene ▾</strong>
dropdown, and a badge reading <em>rendering</em> or <em>disconnected</em>. The canvas binds the <strong>shared Execution
displayee</strong> rather than a viewer of its own, so what is toggled here is what the Execution page
shows.</p>
<p>The Scene dropdown groups its checkboxes as <strong>Solid</strong> — Machine, Tool, Workpiece, Fixture —
<strong>Coordinate</strong> — Program Zero, ISO Coordinate, Heidenhain Coordinate — and <strong>Display Aids</strong>
Dimension Bar, Color Scale Bar. The Heidenhain Coordinate row is listed only while the brand reads
Heidenhain. Three tabs carry a <strong>Show on Display</strong> button of their own that flips the same shared
flags: the coordinate tab flips ISO Coordinate, and the two datum tabs both flip Heidenhain
Coordinate.</p>
<p>The Heidenhain marker is gated twice over, and the second gate is never satisfied. The displayee is
added to the scene only while its flag is set <em>and</em> the model's brand is Heidenhain; it then returns
without drawing until an active datum number or datum-shift argument has been assigned to it, and no
code path in the web service assigns either. So the marker stays absent whatever the flag reads.
Where it does draw, it resolves its offset through the legacy datum tables — the ones this page's two
datum tabs edit — and not through the runner's.</p>
<h2 id="layout">Layout</h2>
<ul>
<li><strong>Legacy Controller</strong> Page — <code>/controller/:tab?</code>, a two-pane splitter, left pane 55 % and draggable
between 25 % and 75 %
<ul>
<li>Management Pane
<ul>
<li>Head Line
<ul>
<li><a class="xref" href="widget/object-management-menu-button.html">Object Management Menu Button</a> (<code></code>) — Load, Save
As, Copy, Paste, XML Mode</li>
<li><strong>Controller</strong> Title</li>
<li>Status Badge — <em>ready</em> / <em>no project</em></li>
</ul>
</li>
<li>Tab Strip — <strong>Coordinate Table</strong>, <strong>Datum Preset</strong>, <strong>Datum Shift</strong>, <strong>Offset Table</strong>,
<strong>Machine</strong>, <strong>Brand</strong>, <strong>Config</strong>; the two datum buttons rendered only under Heidenhain</li>
<li>Tab Panels — kept alive; one shown at a time
<ul>
<li><strong>ISO Coordinate Table</strong> Panel
<ul>
<li>Toolbar — <strong>Undo Align</strong>, <strong>Redo Align</strong>, <strong>Show on Display</strong></li>
<li>Table — columns <strong>Index</strong>, <strong>X</strong>, <strong>Y</strong>, <strong>Z</strong>, <strong>Actions</strong>, with a single-selection column
<ul>
<li>Row Actions — <strong>P0</strong>, <strong>M0</strong>, <strong>Align P0</strong></li>
</ul>
</li>
</ul>
</li>
<li><strong>Datum Preset Table</strong> Panel — <strong>Show on Display</strong>; columns <strong>Q339</strong>, <strong>X</strong>, <strong>Y</strong>, <strong>Z</strong>,
<strong>Actions</strong>, the action being a reset-to-zero button</li>
<li><strong>Datum Shift Table</strong> Panel — the same shape, keyed <strong>D</strong></li>
<li><strong>Tool Offset Table</strong> Panel
<ul>
<li>Toolbar — <strong>Set ideal offset dependent on tool house</strong> toggle, and <strong>Refresh from Tool
House</strong> while it is on</li>
<li>Table — columns <strong>Tool #</strong>, <strong>Ideal Radius</strong>, <strong>Radial Wear</strong>, <strong>Ideal Height</strong>, <strong>Axial
Wear</strong>, and a delete button; the tool number is editable and the delete button present only
while the dependence is off</li>
<li><strong>Add</strong> Button — below the table, shown only while the dependence is off</li>
</ul>
</li>
<li><strong>Machine Configuration</strong> Panel
<ul>
<li><strong>General</strong> Card — <strong>Rapid Feedrate (mm/min)</strong> and <strong>Tooling Time (sec)</strong> numeric fields</li>
<li><strong>Linear Axis Stroke (mm)</strong> Card — <strong>Axis</strong> / <strong>Min</strong> / <strong>Max</strong> over fixed rows X, Y, Z</li>
<li><strong>Rotary Axis Stroke (deg) &amp; Max Speed (rpm)</strong> Card — <strong>Axis</strong> / <strong>Min (deg)</strong> /
<strong>Max (deg)</strong> / <strong>Max Speed (rpm)</strong> over fixed rows A, B, C</li>
</ul>
</li>
<li><strong>CNC Brand</strong> Panel
<ul>
<li><strong>Selection</strong> Card — the brand select over the five brands, above the brand-change warning
banner</li>
<li><strong>Heidenhain Settings</strong> Card — <strong>Master-axis character</strong> select over A / B / C; shown only
under Heidenhain</li>
</ul>
</li>
<li><strong>Configuration</strong> Panel
<ul>
<li><strong>Options</strong> Card — <strong>Enable Shortest Rotary Path</strong> toggle above its explanatory banner</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>Viewer Pane
<ul>
<li>Viewer Toolbar
<ul>
<li><a class="xref" href="widget/renderingcanvas-tool-bar.html">RenderingCanvas Tool Bar</a></li>
<li><strong>Scene ▾</strong> Dropdown — <strong>Solid</strong>, <strong>Coordinate</strong> and <strong>Display Aids</strong> groups</li>
<li>Connection Badge — <em>rendering</em> / <em>disconnected</em></li>
</ul>
</li>
<li>Rendering Canvas — bound to the shared Execution displayee</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="index.html">HiNC App Anatomy</a> for git repository links.</p>
<h3 id="web-application">Web Application</h3>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/pages/ControllerPage.vue</code> — the page shell: the splitter, the seven tab panels and
the Heidenhain gate over two of the buttons, the install-then-initialize chain, the rendering-flag
snapshot the per-tab toggles read, and the tab reset on leaving Heidenhain.</li>
<li><code>wwwroot-src/src/components/controller/CoordinateTableTab.vue</code> — the coordinate table: the per-cell
whole-row write, the row selection that names the marker, the P0 / M0 actions, and Align P0 with
its two client-side history stacks and their cap.</li>
<li><code>wwwroot-src/src/components/controller/DatumPresetTab.vue</code> and
<code>wwwroot-src/src/components/controller/DatumShiftTab.vue</code> — the two Heidenhain tables: the <code>Q339</code>
and <code>D</code> key columns, the reset-to-zero action, and the selection that is a highlight only.</li>
<li><code>wwwroot-src/src/components/controller/OffsetTableTab.vue</code> — the offset editor: row add, delete and
key rename with its duplicate guard, and the two-step tool-house dependence toggle.</li>
<li><code>wwwroot-src/src/components/controller/MachineTab.vue</code> — the fixed X/Y/Z and A/B/C row sets, the
whole-vector writes, the degree and rpm conversions with their infinity guards, and the two
handlers that commit without capturing a rollback value.</li>
<li><code>wwwroot-src/src/components/controller/BrandTab.vue</code> — the five-brand select with its captured
rollback, the change warning, and the Heidenhain master-axis card: its read normalisation, its
two-way binding and its write with no rollback.</li>
<li><code>wwwroot-src/src/components/controller/ConfigTab.vue</code> — the shortest-rotary toggle, its banner and
its reconstructed rollback.</li>
<li><code>wwwroot-src/src/components/controller/ControllerExtendedToolBar.vue</code> — the <strong>Scene</strong> dropdown: the
three flag groups and the brand test that hides the Heidenhain row.</li>
<li><code>wwwroot-src/src/api/controller.ts</code> — the typed wrappers over every endpoint above, the brand
constants and their literal labels, the align / revert payloads, the documented stroke-vector order,
and the eight read wrappers that answer a default instead of raising.</li>
<li><code>wwwroot-src/src/api/http.ts</code> — the plain-JSON helper every write goes through, its coded-error
keys, and the success-envelope helper this module does not use.</li>
<li><code>wwwroot-src/src/api/renderingFlags.ts</code> — the flag indices the Scene dropdown and the per-tab
toggles write.</li>
<li><code>wwwroot-src/src/components/widgets/NumericInput.vue</code> — the numeric cell: commit on blur or Enter,
the empty-to-null parse the handlers reject, the generic text path that prints the infinity tokens,
and the parse branch that takes them back.</li>
<li><code>wwwroot-src/src/components/widgets/ObjectManagementMenuButton.vue</code> — the ⋮ menu, its server file
browser and extension filter, the paste type check, and the load event an XML apply also raises.</li>
<li><code>wwwroot-src/src/components/RenderingCanvas.vue</code> and
<code>wwwroot-src/src/components/RenderingCanvasToolBar.vue</code> — the viewer pane's canvas and its shared
view toolbar.</li>
<li><code>wwwroot-src/src/composables/useRouteTabs.ts</code> — the tab-to-URL sync, the seven valid segments and
the default the bare route canonicalises to.</li>
<li><code>wwwroot-src/src/composables/useCleanupHub.ts</code> — the registration that drops each superseded index
key.</li>
<li><code>wwwroot-src/src/router/routes.ts</code> — the <code>/controller/:tab?</code> route beside <code>/general-setup</code>.</li>
<li><code>wwwroot-src/src/components/AppMenuBar.vue</code> — the <strong>Page</strong> dropdown carrying the
<strong>Legacy-Controller</strong> entry.</li>
<li><code>wwwroot-src/src/stores/project.ts</code> — the project flag the page watches in order to re-run
Initialize; the indexed key that call returns is what the seven tabs and the status badge read.</li>
<li><code>wwwroot-src/src/layouts/MainLayout.vue</code> — the keep-alive that holds the page across navigation and
rebuilds it on a project epoch change.</li>
<li><code>wwwroot-src/src/i18n/en/controller.ts</code> — every title, tab label, column header, banner and error
context quoted above.</li>
<li><code>wwwroot-src/src/i18n/en/menu.ts</code> — the <strong>Page</strong> and <strong>Legacy-Controller</strong> menu strings.</li>
<li><code>wwwroot-src/src/i18n/en/common.ts</code> — the shared column, action and status strings the tabs reuse.</li>
<li><code>Controller/ControllerController.cs</code> — the page's whole REST surface: the index-and-install pair,
the per-property readers and writers, the interleaved stroke reads beside the grouped stroke
writes, the four coded error payloads, the row-level offset CRUD, the align endpoint that snapshots
the transformer either side of the write, the stateless revert, and the display binding that
attaches the shared Execution displayee.</li>
<li><code>Common/ApiError.cs</code> — the four coded payloads named above.</li>
<li><code>Program.cs</code> — the named-floating-point-literal serializer the infinite bounds travel under.</li>
<li><code>Widget/ObjectManagementController.cs</code> — the server half of the ⋮ menu, including the paste that
rejects an object the expected-type string does not admit.</li>
<li><code>Disp/ExecutionDisplayee.cs</code> — the displayee this page's canvas binds: the two coordinate flags,
the extra brand test on the Heidenhain one, and the legacy tables both markers are built over.</li>
<li><code>Disp/IsoCoordinateEntryDisplayee.cs</code> — the one marker class serving both faces, over whichever
coordinate provider it is handed.</li>
<li><code>Disp/HeidenhainCoordinateEntryDisplayee.cs</code> — the Heidenhain marker: the brand guard, the datum
number and shift argument it draws nothing without, and the legacy tables it resolves through.</li>
<li><code>Common/ProjectDisplayeeService.cs</code> — where that single shared displayee is created.</li>
</ul>
<h3 id="hiapi-engine">HiAPI Engine</h3>
<ul>
<li><code>HiUniNc/Numerical/HardNcEnv.cs</code> — the model this page edits: the coordinate, datum and offset
tables, the stroke boxes that construct infinite, the rapid rate and tooling time, the
shortest-rotary flag with its Heidenhain exclusion, and the master-axis character over its integer
direction.</li>
<li><code>HiGeom/Geom/Box3d.cs</code> — the box the two stroke limits are, its infinite construction, and the
six-argument constructor whose grouped argument order the interleaved stroke writes are handed to.</li>
<li><code>HiUniNc/Numerical/HardNcLine.cs</code> — the legacy consumers of two of those settings: the
shortest-rotary path application and the <code>SEQ</code> solve that reads the master-axis direction.</li>
<li><code>HiUniNc/Numerical/MillingToolOffsetTable.cs</code> — the offset table and the tool-house recompute the
Refresh button calls.</li>
<li><code>HiMech/NcParsers/Dependencys/Generic/IsoCoordinateTable.cs</code> — the coordinate table type and the
fifteen G-code keys it constructs with, shared by name with the runner's brand-agnostic table.</li>
<li><code>HiMech/Machining/MachiningEquipmentUtils/MachiningEquipmentUtil.cs</code> — the alignment itself: the
translation written into the fixture's geometry-to-table transformer.</li>
<li><code>HiMech/NcParsers/SoftNcRunner.cs</code> — the legacy import: which legacy fields are funnelled into
which runner dependency, including the rotary speed conversion into the rapid-feedrate config.</li>
<li><code>HiMech/NcParsers/LogicSyntaxs/PolarInterpolationUtil.cs</code> — the rotary speed ceiling the runner
pipeline reads, and why it is the rapid-rate bucket rather than a setting of its own.</li>
<li><code>HiMech/NcParsers/LogicSyntaxs/Heidenhain/HeidenhainPlaneTiltSyntax.cs</code> — the derived master rotary
on the runner pipeline: the first declared rotary axis, with no configured alternative.</li>
<li><code>HiNc/MachiningProcs/MachiningProject.cs</code> — where both models hang off the project, and the load
path that derives a runner from the legacy element only when no runner element is present.</li>
<li><code>HiNc/MachiningProcs/LocalProjectService.cs</code> — the legacy runner built over a delegate onto the
project's model, the switch that selects which pipeline is active, and the per-step stroke check
that falls back to the legacy boxes when no runner-side stroke config resolves.</li>
<li><code>HiNc/Numerical/FilePlayers/HardNcRunner.cs</code> — the legacy runner itself.</li>
<li><code>HiNc/MachiningProcs/SessionShell.cs</code> — the scripting face of the pipeline switch and of the
alignment, and the optimisation route that passes the legacy model directly.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="general-setup/controller/index.html">Controller Branch</a> — the SoftNcRunner-native controller branch that supersedes this screen,
and where controller settings for a project are edited</li>
<li><a class="xref" href="general-setup/index.html">General Setup Page</a> — the page that hosts the branch above, and the rest of the equipment
tree beside it</li>
<li><a href="../manual/utilities/legacy-controller.html">Legacy Controller (manual)</a> — the end-user task: the settings this screen still
owns alone, and which face to edit for everything else</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,489 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Control Tree | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Control Tree | HiAPI-C# 2025 ">
<link rel="icon" href="../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../index.html">
<img id="logo" class="svg" src="../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-ControlTree">
<h1 id="control-tree">Control Tree</h1>
<p>The Control Tree is the engine shared by the two tree-shaped pages of the web client: the Execution
page at <code>/execution</code> and the General Setup page at <code>/general-setup</code>. Each page creates exactly one
host — <code>useControlTreeHost('execution')</code> or <code>useControlTreeHost('equipment')</code> — provides it to its
panes under one injection key, and the host owns everything after that: the node graph, the branch
builders, the selection and the <code>?tree=</code> query that carries it, the editor panel a selection resolves
to, and the mission enable ticks. The Tool House page at <code>/tool-house</code> reuses the same node shape and
the same item-type registry but renders a branch as nested tabs and creates no host.</p>
<div class="IMPORTANT">
<h5>Important</h5>
<p>Node ids are role paths, not identities that survive a version change. Anatomy makes no claim that
a <code>?tree=</code> id is stable across releases; see <a class="xref" href="tree-ids-and-routes.html">Tree Ids and Routes</a>
for the migration hop that keeps an older link resolving.</p>
</div>
<h2 id="the-node">The Node</h2>
<p>Every branch of every tree is built from one structure, <code>ControlNode</code>, declared in <code>itemTypes.ts</code>:</p>
<ul>
<li><code>id</code> — the node's role path, slash-separated, whose first segment is the owning page's scope
(<code>execution/…</code>, <code>equipment/…</code>, <code>toolhouse/…</code>). This is the addressing surface: the <code>?tree=</code> query,
the persisted expansion list and the persisted last selection all hold ids.</li>
<li><code>label</code>, <code>labelKey</code>, <code>labelParams</code> — the display text; see <a href="#display-labels">Display Labels</a>.</li>
<li><code>itemType</code> — the registry key that decides the node's editor and its child builder.</li>
<li><code>key</code> — the bound object. In the geometry and equipment branches it is an IndexService key,
re-minted on every re-index; in the Mission branch it is the Mission API command path (<code>0</code>, <code>1</code>,
the dotted <code>0.2</code> of a nested list entry); in the Tool House branch it is the tool id as a string.
Waves whose panels read a module-level state singleton rather than an indexed object leave it
empty — the SoftNc controller leaves, the spindle sections, the Background and Coolant leaves and
the Program branch all do.</li>
<li><code>ctx</code> — the parent-provided <code>SlotCtx</code>: the <code>afterChange</code> commit chain, a slot's <code>onCreate</code>
create-and-rebind hook, and a slot picker's <code>allowedKinds</code> / <code>allowNone</code> constraints.</li>
<li><code>children</code> — grown by the builders, not declared by the tree column.</li>
<li><code>selectable</code>, <code>info</code> / <code>infoKey</code>, and the <code>mission</code> / <code>program</code> bookkeeping records the Mission and
Program waves stamp on their own nodes.</li>
</ul>
<p>The split between <code>id</code> and <code>key</code> is the load-bearing one. A key is re-minted every time the host
re-indexes the model, so nothing durable may hang off it; an id names a <em>role</em> in the model and
therefore survives a rebuild, which is why expansion state, the URL and the landing selection are all
keyed on ids. Two id families are positional rather than role-based: Mission entries are
<code>${parentId}/${index}</code> and Program files are <code>${parentId}/${index}</code>, so moving or deleting a mission
command renumbers its siblings and a link to one names a position in the list rather than that
command.</p>
<h2 id="the-item-type-registry">The Item-Type Registry</h2>
<p><code>ITEM_TYPES</code> maps an <code>itemType</code> string onto a <code>TreeItemDef</code> carrying up to three members:</p>
<ul>
<li><code>panel</code> — the editor mounted in the dock's editor row for a node of this type.</li>
<li><code>contentPanel</code> — a large view for the General Setup page's content column.</li>
<li><code>buildChildren</code> — an async builder that returns this node's children.</li>
</ul>
<p>Two flavours of type share the map. <strong>Slot</strong> types (<code>Geometry</code>, <code>Transformer</code>) have a kind picker as
their panel and grow at most one child, the concrete kind, at the kind-independent id
<code>${slot.id}/type</code> — so switching a slot's kind keeps both the selection and the expansion. A slot
holding nothing grows no child at all, which for the four geometry slots whose picker offers <em>None</em> is
an ordinary state rather than a failure. <strong>Kind</strong> types (<code>Box3d</code>, <code>StaticTranslation</code>, …) register
<code>SoleEditorPanel</code>, which looks the concrete editor up in the geometry and transformer editor maps and
binds it to the node's key. The two composite geometry kinds do not embed nested editors:
<code>TransformationGeom</code> grows an inner-geometry and an
inner-transformer slot, and <code>GeomCombination</code> grows one slot per item, so the tree owns the structure
and every panel stays shallow.</p>
<p>The map is one flat namespace assembled in <code>itemTypes.ts</code>: the <code>Group</code> stem type, the geometry,
transformer, workpiece-leaf, thermal-condition and spindle entries, and the Execution root's own type
are written inline, and five per-wave registries are spread in beside them. The three roots the panel
host renders itself — Machine Tool, Fixture and Workpiece — have no entry here at all.</p>
<table>
<thead>
<tr>
<th>Wave</th>
<th>File</th>
<th>What it contributes</th>
</tr>
</thead>
<tbody>
<tr>
<td>Mission</td>
<td><code>missionItemTypes.ts</code></td>
<td><code>MissionRoot</code>, <code>MissionCommand</code>, <code>MissionSection</code>; section children at <code>${command.id}/${sectionId}</code>; the per-kind bespoke editors, with a generic field editor as the fallback</td>
</tr>
<tr>
<td>Program</td>
<td><code>programItemTypes.ts</code></td>
<td><code>ProgramRoot</code>, <code>ProgramFile</code>, <code>ProgramConversionFile</code>; writeback conversions at <code>${root.id}/dst-${index}</code></td>
</tr>
<tr>
<td>Tool House</td>
<td><code>toolHouseItemTypes.ts</code></td>
<td>the tool collection and the per-tool editor branches under <code>toolhouse/tool-&lt;id&gt;/…</code></td>
</tr>
<tr>
<td>SoftNc controller</td>
<td><code>softNcItemTypes.ts</code></td>
<td>the controller root and its leaves on two planes — <code>…/machine/&lt;seg&gt;</code> and <code>…/program-data/&lt;seg&gt;</code> — a core every runner grows plus the brand-driven ones</td>
</tr>
<tr>
<td>Runner suits</td>
<td><code>runnerSuitItemTypes.ts</code></td>
<td><code>CsvRunnerRoot</code> and <code>ClRunnerRoot</code>, flat single-panel leaves with no builder</td>
</tr>
</tbody>
</table>
<p>The Program branch is fetched whole: its root builder takes one file-tree response plus one
conversions response and stashes each file's raw subtree on the node, so the file nodes' builder only
maps what is already in hand. The SoftNc branch is the opposite — its builder reads the runner
snapshot once, and with no runner installed grows nothing at all. Where one resolves, the two plane
stems appear over a fixed core that stands for every brand: six machine leaves, and Work Coordinates
and Tool Offsets on the program-data plane. The leaves beside that core are the brand-driven ones —
each appears only where the snapshot reports its backing table, so a brand switch changes which nodes
exist. One flag does double duty: the Siemens <code>$TC_DP</code> flag adds its own two leaves <em>and</em> renames the
core Tool Offsets leaf to its ISO G43 H reading, so the two offset ledgers do not read as one.</p>
<p>The two runner-suit nodes are conditional, and the host — not the registry — decides. With a project
open, a CSV or CL controller node is built when its Preference checkbox is on, or when the current
<code>?tree=</code> value starts with that node's id. The checkbox is device-local and off by default; the route
is the only other
reveal, deliberately not the persisted last selection, so a node switched off while it is selected
does not resurrect itself. Stepping off comes first: when the selection sits on a node the user has
just unchecked, the host re-points the URL at the page root before rebuilding.</p>
<h2 id="building-a-branch">Building a Branch</h2>
<p><code>buildSubtree(node, services, depth)</code> is the whole builder. It looks the node's type up, returns
without touching <code>children</code> when the type declares no <code>buildChildren</code> or when <code>depth</code> has reached
<code>MAX_DEPTH</code> (12, a guard against a self-referencing model), and otherwise awaits the builder, recurses
into each returned child, and assigns the result. Building is eager: a branch is materialised in full
at build time rather than lazily on expansion.</p>
<p>Builders receive one service, <code>registerKey</code>, which registers a freshly minted IndexService key with
the page's cleanup hub — see <a class="xref" href="../conventions/webapi-hub-cleanup-pattern.html">Webapi with Hub-Cleanup Assistance</a>.</p>
<p><code>rebuildBranch(node)</code> is <code>buildSubtree</code> plus one thing: it adds the node's id to the expansion list so
the fresh children are visible, and reports a failure as a toast named after the node's display label.</p>
<h3 id="the-structure-changed-event">The structure-changed event</h3>
<p>A panel that has changed the <em>shape</em> of the model emits <code>structure-changed</code> (or <code>type-changed</code>, wired
to the same handler). The optional payload is a <code>StructureChangeRequest</code>:</p>
<ul>
<li><code>scopeId</code> — the branch to rebuild. Without it the scope is the selected node itself, which is
correct only while the change stays inside the emitting panel's own branch.</li>
<li><code>selectId</code> — the selection to adopt afterwards. It is assigned directly, <strong>bypassing the
dirty-switch gate</strong>, because the emitting panel is being replaced on purpose. A <code>null</code> clears the
selection.</li>
</ul>
<p>A mission command's move, duplicate and delete re-scope to the parent list, because those operations
rewrite the parent's children, and they name the post-operation selection: the clone's slot after a
duplicate, the neighbouring index after a delete, the parent list when the delete emptied it. Moving
an entry out of a nested list re-scopes to the grandparent. The controller brand switch and the
runner's Object-Management install re-scope to the controller root. The Tool House type selectors and
a flute add re-scope to their own node; everything that rewrites the tool collection — a new tool, a
duplicate, a delete, an id rename, an Object-Management install — re-scopes to the tool-house root,
and a flute delete re-scopes to its fluting parent.</p>
<h3 id="whole-tree-rebuilds-and-the-refreshes-that-are-not-rebuilds">Whole-tree rebuilds, and the refreshes that are not rebuilds</h3>
<p><code>rebuildTree()</code> replaces the root array outright, and runs after an Object-Management swap of the
machine tool or the fixture, after a blank machining chain is created, and after either runner-suit
preference flips. On the execution host, an
execution-status transition re-runs <code>buildSubtree</code> on the Program root <strong>directly</strong> rather than through
<code>rebuildBranch</code>, so a run does not force that branch open on every transition.</p>
<p>Two refreshes deliberately mutate existing nodes instead of rebuilding:</p>
<ul>
<li>A mission command's title is re-read from the entry list and written onto the node's label.</li>
<li>A file-backed geometry leaf's label is re-read when its source file changes.</li>
</ul>
<p>The mission one exists because a rebuild of the Mission branch re-mints the per-build <code>stamp</code> that
forms part of the editor's remount key, and remounting an open editor mid-edit would cost the user
their cursor and any staged autosave. The geometry one is the same principle without the stamp: a
title change and a source-file change alter no structure, so the label is all that has to move, and
replacing the node under an open file editor would buy nothing.</p>
<h3 id="the-commit-chain">The commit chain</h3>
<p>The tree has no component bubbling, so the full post-edit commit chain lives on each node as
<code>ctx.afterChange</code>, composed level by level as the branch is built. A <code>GeomCombination</code> item's chain
cleans that combination's aggregated cache before running its parent's chain; the fixture's chain ends
in a geometry-cache clear; the two workpiece geometry slots re-commit the swap-in before clearing
their own cache, and skip that re-commit when the slot has been set to none; the fixture's two anchor
transformers re-commit and then clear the geometry cache, while the workpiece's two re-commit and
clear nothing, since a placement change invalidates no cached solid. Tool-house nodes all share one
refresh. Every other family carries a no-op: the Mission, Program, spindle, SoftNc and
thermal-condition nodes and the workpiece's Mesh and Material leaves have panels that write straight
to the model, and the pages' canvases draw that model; the group stems and the Machine Tool and
Workpiece roots have no field editor to commit for at all.</p>
<h2 id="selection-and-the-dirty-switch-gate">Selection and the Dirty-Switch Gate</h2>
<p>Every selection change funnels through <code>requestSelect(id)</code>. It returns immediately when the id is
already selected; otherwise it awaits the mounted panel's optional <code>tryConsumeBeforeSwitch()</code> and
abandons the switch when that returns false.</p>
<p>Three entrances use it: a click in the tree, a change to the URL's <code>?tree=</code> value, and a <code>select-node</code>
event from the mounted panel — the wire the panel host puts on every registry panel, so a group stem's
child list, a mission list's entry row and the Program branch's conversion jumps all arrive through
that one. A fourth path — the <code>selectId</code> of a structure change — assigns the selection directly and
skips the gate.</p>
<p>There is no &ldquo;nothing selected&rdquo; state. Re-clicking the selected row makes the tree yield <code>null</code>, and
that is ignored; an empty <code>?tree=</code> lands on the persisted last selection for that page when the built
tree still contains it, and on the page's root otherwise. The editor row is therefore always alive,
and reclaiming its height is the row's own collapse toggle rather than a deselection.</p>
<p>The gate itself is registered by the panel host: the mounted registry panel is handed to the host
through a template ref, and the host asks that instance for <code>tryConsumeBeforeSwitch</code>. Panels that
write one field per request need no gate and register none. One panel exposes it —
<code>MissionCommandSlavePanel</code>:</p>
<ul>
<li>on a <code>list</code> entry it flushes the debounced title save while the command path is still live, then
allows the switch;</li>
<li>on any other entry it forwards its inner editor's gate. Today only the script editor has one: with
unsaved text it asks save, discard or cancel, returning false on cancel and on a flush that ends in
an error.</li>
</ul>
<p>The same gate is run before duplicating a command, so the clone is made from what has just been typed
rather than from the last saved state.</p>
<h2 id="display-labels">Display Labels</h2>
<p><code>nodeDisplayLabel(n)</code> returns the translation of <code>labelKey</code> — with <code>labelParams</code> interpolated — when
one is set, and the verbatim <code>label</code> otherwise. <code>labelKey</code> is therefore the form every role label the
client translates itself takes, while <code>label</code> is the storage for text it cannot: server-composed
mission titles, file-backed geometry paths and engine type names. The translator function is read
inside the call rather than captured, so a language change re-renders the whole tree's role labels.</p>
<p>Two labels are composed rather than looked up. A mission command's label is the title the server
composes for it — the command's kind name in the request language, with the command's own text in
brackets when there is one, as in <code>Script [Warm-Up]</code>. That title always arrives filled in, so the
node's <code>labelKey</code> is always dropped and the tree's own kind-name fallback is a safety net that never
fires in practice. Because titles are composed server-side per request, a language change also
re-pulls the command entries and rewrites those labels in place. A file-backed
geometry leaf carries its source file in brackets, so sibling instances read apart:
<code>StlFile [Geom/x.stl]</code>. The voxel kind is renamed for display, appearing as <code>MeshedGeomFile [...]</code>
rather than by its type name.</p>
<p>Group stems take the same treatment for their intro text: <code>infoKey</code> wins over <code>info</code>, and a stem with
neither falls back to a sentence naming the group.</p>
<h2 id="the-panel-host">The Panel Host</h2>
<p><code>PrimarySlavePanel</code> decides what the editor row shows, in this order:</p>
<ol>
<li><strong>The three inline root types</strong><code>MachineToolRoot</code>, <code>FixtureRoot</code>, <code>WorkpieceRoot</code> — are rendered
by the panel itself as a summary, and are never resolved from the registry. The two with a
stand-alone file surface, Machine Tool and Fixture, carry an Object-Management button above it;
the workpiece has none, since it is authored entirely through its child tree items.</li>
<li><strong>Otherwise the registry's <code>panel</code></strong> for the selection's item type, mounted with the node as its
only prop, with <code>changed</code>, <code>type-changed</code>, <code>structure-changed</code>, <code>select-node</code> and <code>error</code> wired to
the host.</li>
<li><strong>A spinner</strong> while the first build is still running, and the select-an-item hint after that when
the selection's type registers no panel.</li>
</ol>
<p>What decides a remount is the mounted component's key, <code>${id}|${key}|${stamp}</code> — the node id, the node
key, and the mission stamp where the node has one. So the editor remounts when the selection moves,
when the bound object's key is re-minted, and when the Mission branch was rebuilt beneath it. That
third term is exactly why the mission label refresh above mutates the node instead of rebuilding.</p>
<p>On the Execution page the editor row grows a header: the transport bar is pinned above the scroll area
whenever the Execution root or any of its descendants is selected, so the run controls never scroll
away; that one instance also owns the transport's function-key shortcuts, which are attached for as
long as it is mounted.</p>
<p>A pure <code>Group</code> stem is not a node without an editor. <code>Group</code> registers <code>GroupInfoPanel</code>, whose body is
the stem's intro line followed by a clickable list of its children; a row click emits <code>select-node</code>
and the host moves the selection there. The General Setup root, the two Anchor stems, the workpiece
Material stem and the controller's two plane stems are all of this kind.</p>
<p>The General Setup page adds a second slave, <code>ContentSlavePanel</code>, which resolves <code>contentPanel</code> from
the same registry. Three item types declare one — the spindle root and its scalar and contour section
types — and all three name the same contours view, so the charts stay mounted while the selection
moves across the spindle branch: the component takes no props and is mounted <strong>unkeyed</strong> on purpose.
Every other selection shows the column's empty hint.</p>
<p><code>NodeTabCascade</code> is the third consumer of the registry. It renders a branch as nested tabs rather than
tree rows: the node's own panel on top, a single child inlined below it, several children as a dense
tab strip in which only the active child mounts. The active tab is remembered per role path at module
scope, with the per-tool segment wildcarded, so switching tools keeps every level's tab. Its panels
report through an injected cascade host that carries the emitting node, because — unlike the tree's
one-panel-at-a-time model — several panels are mounted at once.</p>
<h2 id="the-tick-column">The Tick Column</h2>
<p>The tree runs a strict tick strategy, and a node hides its checkbox unless it is a mission command or
a mission section that carries its own enable flag. Writing the ticked set walks every command node at
any depth: a command whose state changed is PUT to the Mission API and, on success, has the new state
copied onto its section children; a section whose state changed is dispatched to its kind's writer. A
failed write raises a toast and leaves the model untouched, so the getter re-derives the old value and
the box snaps back.</p>
<p>A row dims when it is itself disabled — a disabled command, or a section whose own flag is off — or
when an ancestor command is disabled, which is how a disabled command greys its whole subtree while
each descendant keeps its own state. The switch decides only whether the command runs: a disabled
command stays fully editable.</p>
<h2 id="layout">Layout</h2>
<ul>
<li>Left Dock — both tree pages, one nav-bar button toggling the whole column
<ul>
<li><strong>Control Tree</strong> Expansion Row — collapses in place, keeping its header; stays mounted while
collapsed, so the tree keeps its scroll position
<ul>
<li>Tree — one root row per page: <strong>Execution</strong> or <strong>General Setup</strong>
<ul>
<li>Node Row — the label is a real link to this node's <code>?tree=</code> URL on the page, so the browser's
context menu offers open-in-new-tab and copy-link; a plain left click keeps the in-app
selection instead of navigating, while a modified or middle click opens a tab and leaves the
current selection where it is. No icons are rendered.</li>
<li>Node CheckBox — mission command rows, and mission section rows carrying an enable flag</li>
<li>Execution Status Badge — on the <strong>Execution</strong> root row only</li>
</ul>
</li>
<li>Spinner and &ldquo;Loading project…&rdquo; — shown instead of the tree until the first build lands</li>
</ul>
</li>
<li>Row Divider — a 5 px bar between the rows, shown only while both are open; dragging it upwards
grows the editor row, which keeps a stored pixel height while the tree above absorbs the change,
down to a 120 px editor floor and a 100 px tree floor</li>
<li><strong>Editor</strong> Expansion Row — its header carries the selection breadcrumb, the node path joined with
slashes, and falls back to <strong>Editor</strong>; stays mounted while collapsed
<ul>
<li>Execution Transport Bar — Execution page, while an Execution-scope node is selected</li>
<li>The selected node's editor panel, or &ldquo;Select an item in the Control Tree to edit it here.&rdquo;</li>
</ul>
</li>
</ul>
</li>
<li>Content Column — General Setup page only, one nav-bar button
<ul>
<li>The selected node's large content view, or &ldquo;The selected item has no expanded content.&rdquo;</li>
</ul>
</li>
</ul>
<p>Both dock rows and both column widths are device-local browser preferences, shared by the two pages
through one preferences module; each page keeps its own visibility record, and both pages share one
expansion list because their id spaces do not overlap.</p>
<h2 id="lifecycle">Lifecycle</h2>
<p>The host is created while its page's script runs and provided before the dock mounts; the page's
<code>onMounted</code> then awaits <code>initialize()</code>. That call connects the cleanup hub and builds the tree — on
the equipment host, after re-indexing the machine tool, the workpiece and the fixture in that order —
and finishes by adopting whatever the URL's <code>?tree=</code> names. A loading flag is held for the whole call,
which is what the tree and editor rows render their spinners from; later rebuilds mutate an
already-populated tree and never re-enter it. A missing project, or a project without a fixture or
workpiece, answers 404, and those are treated as empty states rather than errors, so no toast appears.
Without an open project the Mission, Program and controller branches are still declared, as childless
stems: their builders run only once a project is loaded.</p>
<p>Because the layout keys its keep-alive wrapper on a project epoch, every page is destroyed and rebuilt
when the project changes, and a host is therefore created once per project per page.</p>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../index.html">HiNC App Anatomy</a> for git repository links.</p>
<h3 id="web-application">Web Application</h3>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/controlTree/useControlTreeHost.ts</code> — the host: the two scopes' root
builders, the selection gate, the <code>?tree=</code> sync, the mission ticks, the rebuild entry points, the
re-index and Object-Management handlers, and the reactive surface the panes consume.</li>
<li><code>wwwroot-src/src/components/controlTree/itemTypes.ts</code> — the <code>ControlNode</code> and <code>TreeItemDef</code> shapes,
the <code>SlotCtx</code> hooks, the structure-change payload, <code>nodeDisplayLabel</code> / <code>nodeDisplayInfo</code>, the
geometry and transformer builders, the <code>ITEM_TYPES</code> map and <code>buildSubtree</code>.</li>
<li><code>wwwroot-src/src/components/controlTree/ControlTreePanel.vue</code> — the tree column: the node anchors,
the click handling that keeps a plain click in-app, and the execution-status badge.</li>
<li><code>wwwroot-src/src/components/controlTree/ControlTreeDock.vue</code> — the two-row dock and the height
divider that sets the editor row's stored pixel height.</li>
<li><code>wwwroot-src/src/components/controlTree/PrimarySlavePanel.vue</code> — the panel host: the three inline
root types, the registry lookup, the remount key, the gate registration and the transport header.</li>
<li><code>wwwroot-src/src/components/controlTree/ContentSlavePanel.vue</code> — the General Setup content column
and its unkeyed content panel.</li>
<li><code>wwwroot-src/src/components/controlTree/GroupInfoPanel.vue</code> — a group stem's intro and its
navigating child list.</li>
<li><code>wwwroot-src/src/components/controlTree/SoleEditorPanel.vue</code> — a kind node's editor, resolved from
the geometry and transformer editor maps.</li>
<li><code>wwwroot-src/src/components/controlTree/NodeTabCascade.vue</code> — the same branches rendered as nested
tabs, with the remembered per-role tab and the cascade host injection.</li>
<li><code>wwwroot-src/src/components/controlTree/missionItemTypes.ts</code> — the Mission wave: node bookkeeping,
the section table and its enable-flag readers and writers, the per-kind editors and display names.</li>
<li><code>wwwroot-src/src/components/controlTree/programItemTypes.ts</code> — the Program wave: the file tree from
one response, and the writeback conversion nodes.</li>
<li><code>wwwroot-src/src/components/controlTree/softNcItemTypes.ts</code> — the controller wave: the two planes,
the core leaves every runner grows, and the snapshot flags that decide which brand leaves join them.</li>
<li><code>wwwroot-src/src/components/controlTree/toolHouseItemTypes.ts</code> — the Tool House wave and its
<code>tool-&lt;id&gt;</code> role paths.</li>
<li><code>wwwroot-src/src/components/controlTree/runnerSuitItemTypes.ts</code> — the two runner-suit leaves.</li>
<li><code>wwwroot-src/src/components/controlTree/MissionCommandSlavePanel.vue</code> — the command control bar, and
the only panel that exposes a dirty-switch gate.</li>
<li><code>wwwroot-src/src/components/mission/ScriptCommandPanel.vue</code> — the autosaving editor whose gate is
forwarded, with its save / discard / cancel prompt.</li>
<li><code>wwwroot-src/src/components/panels/PanelExpansion.vue</code> — the expansion row both dock rows are built
from.</li>
<li><code>wwwroot-src/src/composables/useViewPrefs.ts</code> — the device-local expansion list, last selections,
dock sizes and the two runner-suit checkboxes.</li>
<li><code>wwwroot-src/src/composables/useCleanupHub.ts</code> — the key registration the builders call.</li>
<li><code>wwwroot-src/src/router/treeRoutes.ts</code> — the id migration hop and the page a given id belongs to.</li>
<li><code>wwwroot-src/src/pages/ExecutionPage.vue</code> — creates and provides the <code>execution</code> host.</li>
<li><code>wwwroot-src/src/pages/GeneralSetupPage.vue</code> — creates and provides the <code>equipment</code> host, and adds
the content column.</li>
<li><code>wwwroot-src/src/pages/ToolHousePage.vue</code> — the registry consumer that renders tabs instead of a
tree.</li>
<li><code>wwwroot-src/src/layouts/MainLayout.vue</code> — the project epoch that decides when a host is rebuilt.</li>
<li><code>wwwroot-src/src/i18n/en/tree.ts</code> — the dock labels, the group intros and the node role labels.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="tree-ids-and-routes.html">Tree Ids and Routes</a> — how a node id becomes a URL, which page owns a given id, and how
an older id keeps resolving</li>
<li><a class="xref" href="session-state.html">Session State</a> — the project epoch and the stores a host is rebuilt around</li>
<li><a class="xref" href="../execution/index.html">Execution Page</a> — the first live host: the run cockpit's Mission and Program branches</li>
<li><a class="xref" href="../general-setup/index.html">General Setup Page</a> — the second live host: the equipment branches and the content column</li>
<li><a class="xref" href="../general-setup/controller/index.html">Controller Branch</a> — the one branch whose node set is not fixed: it is regrown from a
snapshot, so which children exist depends on the controller in the project</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,422 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Internationalization | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Internationalization | HiAPI-C# 2025 ">
<link rel="icon" href="../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../index.html">
<img id="logo" class="svg" src="../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-I18n">
<h1 id="internationalization">Internationalization</h1>
<p>The web client ships three locales — <code>en</code>, <code>zh-Hant</code> and <code>zh-Hans</code> — over one message shape, and it
owns no route: the mechanism is a vue-i18n instance created in the <code>i18n</code> module, a boot file that
picks the locale before the application mounts, a single switch function every locale change goes
through, and two Node scripts that gate the production build. English is not one locale among three;
it is the schema the other two are written against and the text every unlocalized path falls back to.
The user-facing gesture that triggers a switch is documented under
<a class="xref" href="../shell/language-selection-submenu.html">Language Selection SubMenu</a>, and the vocabulary the
translations are held to under <a class="xref" href="../conventions/translation-remarks.html">Translation Remarks</a>.</p>
<h2 id="the-three-bundles">The Three Bundles</h2>
<p>Each locale is a folder under the i18n directory holding one file per namespace plus an index file
that re-exports them as a single object. The three folders carry identical file lists — the same
twenty-three namespaces, from <code>apiErrors</code> and <code>auth</code> through <code>tree</code> and <code>widgets</code> — because a
namespace's keys are added to all three locales in the same change. Keys are named
<code>namespace.area.element</code> in lowerCamelCase and are split by the UI region they serve rather than by
the source file that reads them.</p>
<p>English is the schema, mechanically and not by convention. <code>wwwroot-src/src/i18n/schema.ts</code> exports
<code>MessageSchema = typeof en</code>, and each Chinese locale's index file annotates its own message object
with that type, so a key missing from a Chinese namespace is a type error at the annotation rather
than a silent English fallback at runtime. <code>schema.d.ts</code> augments vue-i18n's <code>DefineLocaleMessage</code> with the
same shape, which registers it as the global message schema for call sites. Type checking is not part
of the production build, however: <code>wwwroot-src/package.json</code> runs <code>vue-tsc</code> under a separate <code>lint</code>
script, and <code>build</code> runs the two i18n scripts described below instead.</p>
<p>The instance is created with <code>legacy: false</code>, an initial <code>locale</code> of <code>en</code> and <code>fallbackLocale: 'en'</code>.
The resolution chain is therefore current locale → English → the key string itself, so a key missing
from a Chinese bundle renders English with no runtime signal, and a key missing everywhere renders as
its own dotted name.</p>
<p>Quasar's own component texts are a parallel bundle with different code names, mapped in the same
module: <code>en</code> to Quasar's <code>en-US</code> pack, <code>zh-Hant</code> to <code>zh-TW</code>, <code>zh-Hans</code> to <code>zh-CN</code>. All three packs are
static imports rather than a dynamic import built from the locale code, because a template-literal
import on a bare package specifier is not statically analysable by the bundler.</p>
<h2 id="applying-a-locale">Applying a Locale</h2>
<p><code>applyLocale(code)</code> in the i18n module is the only place a locale change happens. It first maps its
argument through <code>normalizeLocale</code>, falling back to <code>en</code>, and then does six things in one call:</p>
<ul>
<li>sets the vue-i18n instance's <code>locale</code> ref;</li>
<li>calls Quasar's <code>Lang.set()</code> with the mapped language pack;</li>
<li>writes <code>document.documentElement.lang</code>, which is what selects the CJK glyph variant;</li>
<li>rebuilds the exported <code>collator</code>, an <code>Intl.Collator</code> for the new locale with base sensitivity and
numeric ordering;</li>
<li>writes the locale into <code>localStorage</code> under <code>hinc.lang</code>, inside a <code>try</code>/<code>catch</code> because storage can
be unavailable;</li>
<li>calls the re-title hook the router registered, so a browser tab parked on a page without navigating
still gets its title in the new language.</li>
</ul>
<p><code>normalizeLocale</code> maps any language tag — the server's, the browser's, or a stored one — onto a
supported locale or <code>null</code>. An exact match wins; otherwise a tag beginning <code>en</code> becomes <code>en</code>, and a
tag beginning <code>zh</code> becomes <code>zh-Hant</code> when it carries the <code>hant</code> script subtag or a <code>TW</code>, <code>HK</code> or <code>MO</code>
region, and <code>zh-Hans</code> otherwise. Anything else yields <code>null</code>. <code>applyLocale</code> and the boot file's
browser-language fallback each turn that <code>null</code> into <code>en</code>; the boot file's read of the stored value
instead treats it as a cache miss and asks the server, so an unsupported stored code takes the
cold-start path rather than pinning English.</p>
<p>Only two modules call <code>applyLocale</code>: the boot file, and the app-state store's language action.
The store updates its own <code>languageCode</code> optimistically, POSTs the new value, adopts the <code>current</code> and
<code>available</code> the server echoes back, and only then applies the locale — so the UI text flips after the
server has accepted the value, and a failed POST rolls the store reference back without any visible
language change.</p>
<h2 id="boot">Boot</h2>
<p><code>wwwroot-src/quasar.config.ts</code> lists three boot files in the order <code>auth</code>, <code>i18n</code>, <code>routine-toast</code>.
The i18n file is placed after <code>auth</code> because <code>auth</code> patches the global <code>fetch</code> for the 401 login gate,
and the language request the i18n file makes must go through that patch on builds where login is
enabled.</p>
<p>The boot file installs the plugin and then reads the <code>hinc.lang</code> value:</p>
<ul>
<li><strong>Cache hit.</strong> The cached locale is applied immediately, so the first painted frame is already
translated, and a reconcile against the server is fired without being awaited. The server value is
the source of truth, so a disagreement — the language was changed from another browser — repaints
shortly after mount.</li>
<li><strong>Cache miss.</strong> The file returns a promise racing the language request against a four-second
timeout, and mount waits on it. A dead backend rejects quickly; a hung one is capped by the timeout
rather than blanking the application. Either failure falls back to the normalized browser language,
and then to English.</li>
</ul>
<p>Every locale step is individually guarded, because a boot file that throws does not degrade to
English — the client entry logs the error and never mounts. The one unguarded step is installing the
plugin itself, without which nothing in the application can render a translated string at all. The
worst case the guards preserve is an application that mounts in English.</p>
<p>The shipped <code>wwwroot-src/index.html</code> is static and carries <code>lang=&quot;en&quot;</code>, so the very first painted
frame always reports English on the root element; <code>applyLocale</code> corrects it in the same tick as mount.</p>
<h2 id="what-re-renders">What Re-Renders</h2>
<p>A locale change is a write to one reactive ref, so everything that reads a translation inside a
render or a computed re-evaluates on its own. Five things follow it in practice:</p>
<ul>
<li><strong>Component text.</strong> Every <code>t()</code> call in a template or computed.</li>
<li><strong>Quasar's built-in texts</strong>, through the language pack the same call sets.</li>
<li><strong>The tab title.</strong> Route records carry an i18n <em>key</em> in <code>meta.title</code>; the router resolves it against
the active locale and composes the title from it. It re-resolves both after a navigation and from
the hook <code>applyLocale</code> calls.</li>
<li><strong>Control-Tree node labels</strong>, described below.</li>
<li><strong>Engine session messages</strong>, which are re-resolved because the localizing helper reads the locale
ref, so rows built inside a computed rebuild on a switch.</li>
</ul>
<p>File Explorer sorting is the one thing that does <em>not</em> follow on its own. The shared <code>collator</code> is a
live <code>let</code> binding rather than a reactive one: <code>applyLocale</code> rebuilds it for the new locale, but no
render tracks it and <code>FileExplorer.vue</code> watches no locale, so a listing already on screen keeps its
pre-switch order until the next folder load or sort-spec change re-runs the comparison. Because the
binding is live, a caller must invoke <code>collator.compare(...)</code> in place; capturing the instance or
extracting its <code>compare</code> would pin the pre-switch locale for good. <code>explorerSort.ts</code> is its only
consumer, and no bare <code>localeCompare</code> — whose <code>undefined</code> locale argument means the <em>browser</em> locale
— is called anywhere in the client.</p>
<p>Numbers deliberately do not follow the locale. <code>Intl.NumberFormat</code> is used nowhere in the client, and
the only <code>toLocale*</code> calls are two triangle counts and two timestamps, each passed the app locale
explicitly rather than defaulting to the browser's. NC and machine-tool quantities go through no
locale-aware number formatting at all, so a coordinate never picks up a comma decimal separator from
a locale that uses one.</p>
<h2 id="control-tree-node-labels">Control-Tree Node Labels</h2>
<p>A Control-Tree node carries both a <code>label</code> and an optional <code>labelKey</code>, and <code>nodeDisplayLabel(n)</code> in
<code>itemTypes.ts</code> returns the translation of <code>labelKey</code> — with the node's <code>labelParams</code> interpolated —
whenever one is set, and the verbatim <code>label</code> otherwise. <code>nodeDisplayInfo</code> applies the same rule to a
group stem's intro text through <code>infoKey</code> over <code>info</code>. The translator function is read inside the call
rather than captured at build time, so the computed that maps nodes onto the rendered tree tracks the
locale ref and the whole tree's role labels change on a switch.</p>
<p>The two fields overlap on purpose. <code>label</code> is required on the node type and <code>labelKey</code> is optional,
so a fixed role label is written both ways: the key that actually renders, and the verbatim English
sitting beside it as the fallback <code>nodeDisplayLabel</code> returns when no key is set. <code>label</code> is also the
only storage for text that can never be a key at all — server-composed mission titles, file-backed
geometry paths, and engine type names. It is the first of those two jobs, not the second, that makes
the verbatim English on node labels the largest single class in the census allowlist: fixed role
labels the census can see but the screen never shows, classified rather than removed because the
node type requires the field.</p>
<p>Mission command nodes are the one family that never takes the key path. A node's label is the title
the server composes for the command — the kind name in the request language, with the command's own
text in brackets when there is one — and <code>Missions/MissionController.cs</code> fills that title in for every
entry: from the command's own composition where it implements the engine's title contract — whose rule
keeps the kind name at the front and never drops it — and from the kind's display name, resolved in the
request culture, where it does not. Because a title is therefore always present, the node's
<code>labelKey</code> is always dropped and the client's kind-name fallback never fires. Since the composition
happens on the server per request, the tree host watches the locale and re-reads the command entries,
rewriting those labels in place; it deliberately does not rebuild the branch, which would remount an
open command editor mid-edit. The kind keys are still built as <code>tree.mission.kind.${commandType}</code>, and
they do render — <code>AddCommandDialog.vue</code> prefers one over the server's label for every kind that has
one — while the key lint resolves the template shape by wildcard rather than reporting an orphan.</p>
<h2 id="the-server-half">The Server Half</h2>
<p>The language preference is server-held. <code>GET /api/preference/language</code> answers
<code>{ success, current, available }</code>, where <code>current</code> is the persisted <code>UserConfig.LanguageCode</code>
default <code>en</code> — and <code>available</code> is the server's own supported list, <code>en</code>, <code>zh-Hans</code>, <code>zh-Hant</code>. <code>POST</code>
rejects an unlisted code with 400 and otherwise writes the value and saves the user configuration,
which round-trips through the XML user config file. The <code>localStorage</code> key holds only a paint-time
hint and loses every disagreement with this value.</p>
<p>Some response text is composed on the server and never passes through a bundle.
<code>PresentCatalogService</code> resolves the effective language per request in this order: an explicit <code>?lang=</code>
query, then the request's <code>Accept-Language</code> header, then the persisted preference, then English.
Because the header sits <em>above</em> the saved preference, a browser set to a different language than the
application would win by default — so the client sends <code>?lang=</code> explicitly, from a <code>currentLang()</code>
helper returning the app locale, on the endpoints whose text is localized server-side: the
selected-step info, the execution strip chart, its step-property picker and its colour guide, the
mission command entries, catalog and field descriptors, and the step-present key list. The mission
controller resolves the same
chain into a per-request UI culture and injects it into the command text source, so the culture rides
on the request rather than on the thread.</p>
<p>Two further server-originated string classes are localized on the client instead:</p>
<ul>
<li><strong>Coded API errors.</strong> Ten error codes are mapped onto <code>apiErrors.*</code> keys; a failed response carrying
one of them renders the bundle value with the server's interpolation arguments. English is passed
straight through to the server's own message on purpose, because that message <em>is</em> the English
rendering and keeps per-site nuance the generic bundle value flattens.</li>
<li><strong>Engine notifications.</strong> Session messages arrive with a structured id and English text. The client
swaps in an <code>engineMessages.&lt;id&gt;</code> bundle entry, but only when the wire text agrees with the English
bundle entry for that id: a templated message must match the canonical English template exactly
before its arguments are re-interpolated, and an untemplated one is swapped only when the bundle
entry has no interpolation holes. Any mismatch — an id emitted with different templates at different
sites, or an engine built against a different bundle — falls back to the wire English rather than
rendering wrong text. The English <code>engineMessages</code> values are therefore load-bearing code, not a
description of the Chinese ones.</li>
</ul>
<p>The step-present labels have a third home again: they ship beside the executable as
<code>catalog.{lang}.json</code> files that overlay translations on top of the live English attribute data.
English never comes from those files, and a missing file, a missing key or a parse failure simply
means English.</p>
<h2 id="the-glossary-and-the-build-gate">The Glossary and the Build Gate</h2>
<p>Two Node scripts stand between the bundles and a shipped release. <code>wwwroot-src/package.json</code> defines
<code>lint:i18n</code> as <code>census.mjs</code> followed by <code>lint-glossary.mjs</code>, and <code>build</code> as <code>lint:i18n</code> followed by
<code>quasar build</code> — so either script failing stops the production build. The development server runs
neither.</p>
<p><code>lint-glossary.mjs</code> bundles each locale's index through esbuild, flattens the three message trees into
key/value maps, and applies four checks, every one of them a hard failure:</p>
<ol>
<li><strong>Key isomorphism.</strong> The three flattened key sets must be identical, compared in both directions,
so an extra key in a Chinese bundle fails as loudly as a missing one.</li>
<li><strong>Bidirectional key integrity.</strong> Every bundle-shaped string literal under the source tree —
excluding the bundles themselves — must resolve to an English key, and every English key must be
referenced from somewhere. A literal naming a namespace prefix counts as a reference to everything
beneath it; a template literal with an interpolation hole is expanded into a wildcard, which is how
<code>tree.mission.kind.${kind}</code> is matched; and <code>@:linked.key</code> references inside bundle values count as
well. A double-quoted literal in a Vue template is treated as a key only when its attribute is one
of the four the check knows to be key-valued, so an expression attribute whose local variable
happens to share a namespace name is not mistaken for one. The orphan half is what makes the check
bidirectional, and its allow-list is currently empty.</li>
<li><strong>Shape preservation.</strong> Never-translate tokens present in an English value must survive verbatim in
both Chinese values — product and brand names, file formats and unit symbols by literal match, and
NC codes, controller parameter names and function-key shortcuts by pattern. Placeholder sets must
match in both directions, so a Chinese value can neither lose a placeholder nor invent one.</li>
<li><strong>Per-locale vocabulary.</strong> An adjudicated forbidden-term table per locale, each entry naming its
replacement and its reason, plus a check for a Chinese value identical to its English source that
contains no CJK at all. Both carry narrow exemptions: named keys where a term is used in a
different sense, and a small identity set for values that legitimately are the identifier.</li>
</ol>
<p>The tables the third and fourth checks read are written at the top of the script itself.
<code>glossary.yaml</code>, the large harvested term list that sits beside the bundles, is generated data that
the application never imports and the lint never reads: it is where a reading is settled before it is
written into a bundle, not an input to the gate.</p>
<p><code>census.mjs</code> guards the opposite direction — text that never became a key. It scans the same tree for
user-visible English string literals reached through display-named template attributes, display-named
object properties, notification and dialog shorthands, and prose text nodes; subtracts a classified
allowlist of concrete text-and-file pairs; and compares what is left, per file, against a baseline.
The gate fails only when a file's residual count <em>rises</em>, so cleanup can land incrementally — but the
baseline is currently empty, which allows zero residual strings in any file and makes the effective
rule that a newly hard-coded display string fails the build unless it is added to the allowlist with
a reason.
Allowlist entries whose file field is a glob or a brace list are classification notes rather than
matchers; only concrete paths participate.</p>
<h2 id="strings-outside-the-bundles">Strings Outside the Bundles</h2>
<p>Three classes of user-visible English render in every locale — two because an interpolation hole
hides them from the census, which discards any literal containing one, and one because the allowlist
deliberately keeps it:</p>
<ul>
<li><strong>The shared numeric field's validation text.</strong> <code>NumericInput.vue</code> imports nothing from vue-i18n and
builds three messages as template literals directly: an invalid-number message quoting what was
typed, a &ldquo;must be ≥&rdquo; message naming the field's minimum, and a &ldquo;must be ≤&rdquo; message naming its
maximum. All three are produced on blur — the field commits on blur or on Enter — and every panel
that embeds the widget shows them in English. A <code>widgets</code> namespace exists for exactly this kind of
shared control text; this component does not use it.</li>
<li><strong>The geometry and topology editors' error prefixes.</strong> Those editors emit their failure context as a
template literal that leads with the type name and the operation, so the leading phrase is English
whatever the locale. The allowlist records the family as a ledger note rather than as matched
entries.</li>
<li><strong>The API-error default.</strong> The generic fallback message in the HTTP helper is allowlisted
deliberately, on the grounds that it sits in the same stream as the server's own English messages.</li>
</ul>
<p>A fourth class is untranslated by design rather than by escape: the dynamic <code>label</code> text on a
Control-Tree node — a file-backed path, an engine type name, and the user's own words inside a
server-composed mission title — and the identity strings the allowlist classifies alongside it, which
are backend type names rendered inside a translated sentence, axis and unit column headers, and brand
names. The role-label half of that same allowlist class is a different case again: stored English that
never reaches the screen at all, because a <code>labelKey</code> always resolves ahead of it.</p>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../index.html">HiNC App Anatomy</a> for git repository links.</p>
<h3 id="web-application">Web Application</h3>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/i18n/index.ts</code> — the vue-i18n instance and its fallback chain, <code>SUPPORTED_LOCALES</code>,
<code>normalizeLocale</code>, <code>applyLocale</code>, the Quasar language-pack map, the shared collator, the storage key
and the re-title registration.</li>
<li><code>wwwroot-src/src/i18n/schema.ts</code> — the English bundle as <code>MessageSchema</code>.</li>
<li><code>wwwroot-src/src/i18n/schema.d.ts</code> — the ambient augmentation that registers that shape as
vue-i18n's global message schema.</li>
<li><code>wwwroot-src/src/i18n/en/index.ts</code> — the English bundle: the twenty-three namespaces assembled into
one object.</li>
<li><code>wwwroot-src/src/i18n/zh-Hant/index.ts</code> and <code>wwwroot-src/src/i18n/zh-Hans/index.ts</code> — the same
assembly, each annotated with <code>MessageSchema</code>.</li>
<li><code>wwwroot-src/src/i18n/en/engineMessages.ts</code> — the engine-notification renderings keyed by structured
id; its English values are the eligibility gate for every swap.</li>
<li><code>wwwroot-src/src/i18n/en/routes.ts</code> — the strings the routes' <code>meta.title</code> keys resolve to.</li>
<li><code>wwwroot-src/src/i18n/en/widgets.ts</code> — the shared-widget namespace.</li>
<li><code>wwwroot-src/src/i18n/README.md</code> — the invariants written beside the bundles: the fallback rule, the
never-translate list, the number and sorting rules and the vocabulary table.</li>
<li><code>wwwroot-src/src/i18n/glossary.yaml</code> — generated term data, imported by nothing.</li>
<li><code>wwwroot-src/src/boot/i18n.ts</code> — the plugin installation, the cached-locale fast path with its
unawaited reconcile, the awaited race on a cache miss, and the guards around every locale step.</li>
<li><code>wwwroot-src/quasar.config.ts</code> — the boot list whose order places i18n after the authentication
fetch patch.</li>
<li><code>wwwroot-src/index.html</code> — the static shell whose <code>lang</code> attribute is corrected at mount.</li>
<li><code>wwwroot-src/package.json</code><code>build</code> as the i18n scripts followed by <code>quasar build</code>, and <code>vue-tsc</code>
as a separate script.</li>
<li><code>scripts/lint-glossary.mjs</code> — the four bundle checks and the adjudication tables they read.</li>
<li><code>scripts/census.mjs</code> — the raw display-string extraction, the allowlist subtraction and the per-file
ratchet.</li>
<li><code>scripts/i18n-allowlist.json</code> — the classified exemptions, each with its reason.</li>
<li><code>scripts/i18n-baseline.json</code> — the per-file residual ceiling the census compares against.</li>
<li><code>wwwroot-src/src/stores/appState.ts</code> — the language reference, the available-code list, and the
action that POSTs before applying the locale and rolls back on failure.</li>
<li><code>wwwroot-src/src/api/preference.ts</code> — the typed wrappers over the language endpoints and the
<code>?lang=</code>-bearing step-present key request.</li>
<li><code>wwwroot-src/src/api/http.ts</code><code>currentLang()</code>, the coded-error map and the app-locale rendering
that skips English.</li>
<li><code>wwwroot-src/src/api/sessionMessages.ts</code> — the engine-notification localizer, its template-equality
gate and the repeat-fold wrapper.</li>
<li><code>wwwroot-src/src/components/controlTree/itemTypes.ts</code> — the node's <code>label</code>, <code>labelKey</code> and
<code>labelParams</code> fields and the <code>nodeDisplayLabel</code> and <code>nodeDisplayInfo</code> resolvers.</li>
<li><code>wwwroot-src/src/components/controlTree/useControlTreeHost.ts</code> — the rendered-tree computed that
calls the resolver, and the locale watcher that re-pulls the server-composed mission command titles
without rebuilding the branch.</li>
<li><code>wwwroot-src/src/components/controlTree/missionItemTypes.ts</code> — the kind display names and their
<code>tree.mission.kind.*</code> key twin, and the rule that drops the key whenever the entry carries a title.</li>
<li><code>wwwroot-src/src/components/widgets/NumericInput.vue</code> — the shared numeric field: blur-and-Enter
commit, and the three hard-coded validation messages.</li>
<li><code>wwwroot-src/src/components/AppMenuBar.vue</code> — the language submenu, its hard-coded self-name map and
the action it calls.</li>
<li><code>wwwroot-src/src/components/explorerSort.ts</code> — the only consumer of the shared collator, calling it
in place so a switch is picked up.</li>
<li><code>wwwroot-src/src/router/index.ts</code> — the key-valued <code>meta.title</code> resolution and the hook the locale
switch calls to re-title a parked tab.</li>
<li><code>Environments/PreferenceController.cs</code> — the language endpoints, the supported-code list and the
400 on an unlisted code.</li>
<li><code>Environments/UserConfig.cs</code> — the persisted language code, its English default and its XML
round-trip.</li>
<li><code>Environments/PresentCatalogService.cs</code> — the per-request language resolution chain, the tag
normalizer and the shipped catalog overlay.</li>
<li><code>Missions/MissionController.cs</code> — the per-request UI culture taken from the same chain and injected
into command title composition.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="../shell/language-selection-submenu.html">Language Selection SubMenu</a> — the menu gesture that drives this mechanism, and the only
place a user changes the locale</li>
<li><a class="xref" href="../conventions/translation-remarks.html">Translation Remarks</a> — the terminology contract the bundles are written to, and the
readings the vocabulary lint enforces</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,140 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Platform | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Platform | HiAPI-C# 2025 ">
<link rel="icon" href="../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../index.html">
<img id="logo" class="svg" src="../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-Platform">
<h1 id="platform">Platform</h1>
<p>The machinery every route sits on and no user can point at. A page in this folder documents
something that has no owning screen: the Control-Tree engine two pages share, the id and route
surface three pages hang off, the state that survives a navigation, the locale bundles behind every
label, the gate in front of the whole app, and the host process under all of it.</p>
<p>The boundary with <a href="../conventions/index.html">Conventions</a> is which side of the page the reader
is on. Conventions holds a rule a page author must <strong>follow</strong>; Platform holds machinery a page
author must <strong>understand</strong> and never touches.</p>
<p>Two of these are ordinary routes rather than mechanisms — <code>/login</code> and <code>/preference/log</code>. They sit
here because what they document is the platform surface, not a step in a machining workflow.</p>
<p>Ordered from the machinery a page sits closest to, outward to the host process beneath it.</p>
<h2 id="pages">Pages</h2>
<ul>
<li><a href="control-tree.html">Control Tree</a> — The engine both tree pages share: the node shape, the item-type registry, branch building, the selection gate and the panel host</li>
<li><a href="tree-ids-and-routes.html">Tree Ids and Routes</a> — The URL surface: the route table, the redirect-only paths, and how a <code>?tree=</code> id is migrated and landed on the page that owns it</li>
<li><a href="session-state.html">Session State</a> — The project epoch behind the keep-alive, the stores and module singletons outside it, and what is device-local</li>
<li><a href="i18n.html">Internationalization</a> — Three locales over one English schema: the bundles, the single switch point, the build-gating lint, and the strings that never reach a bundle</li>
<li><a href="login-and-auth.html">Login and Authentication</a> — The optional login gate: the sign-in screen, the config switch that turns it on, the cookie scheme and fallback policy it registers, and the router guard in front of them</li>
<li><a href="log-viewer.html">Log Viewer Page</a> — The read-only <code>/preference/log</code> screen: today's server log file, its auto-refresh, and the daily file sink behind it</li>
<li><a href="program-and-hosting.html">Program and Hosting</a> — The ASP.NET Core host: the HiAPI start/stop pair, the all-singleton container, the SignalR hubs, the SPA fallback, and the configuration that pins the listening address</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a href="../conventions/index.html">Conventions</a> — the other cross-screen folder: the rules an author follows rather than the machinery an author reads</li>
<li><a class="xref" href="../by-source/index.html">Anatomy by Source Directory</a> — the inverse index, for entering by the directory a change landed in</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,318 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Log Viewer Page | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Log Viewer Page | HiAPI-C# 2025 ">
<link rel="icon" href="../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../index.html">
<img id="logo" class="svg" src="../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-LogViewer">
<h1 id="log-viewer-page">Log Viewer Page</h1>
<p>The Log Viewer is the read-only screen at <code>/preference/log</code>, route name <code>preference-log</code>, that
renders the web service's own log file for the current day. It is not a Control-Tree page and takes
no <code>?tree=</code> query: the whole page is one toolbar over one scrolling text area. Despite the route
path it is not an entry of the Preference dropdown, and it is not under <code>/util/</code>.</p>
<h2 id="reaching-the-page">Reaching the Page</h2>
<p>The only navigation into the page is the <code>Show Log</code> button on the menu bar. It sits in the bar's
right-hand group, after the active-page title and its separator and before the logout button, and it
carries the <code>article</code> icon with the tooltip <code>View the application log</code>. The button is rendered
unconditionally — no route, project, licence or authentication state hides it — so it is reachable
from every page in the shell. The Preference dropdown carries the Language submenu, the CSV and CL
Controller visibility checkboxes and Show Physics Options, and no log entry; the Page dropdown does
not list the route either.</p>
<p>The route sits inside the main layout, so the page renders under the same menu bar and footer as
every other page, and it is behind the login gate whenever that gate is on — the router's navigation
guard admits only the login route itself while the session is unauthenticated. The route carries an
i18n title key, so the browser tab reads <code>Log Viewer - HiNC</code> and the menu bar's active-page
indicator reads <code>Log Viewer</code>.</p>
<h2 id="what-it-reads">What It Reads</h2>
<p>Two endpoints, both on the project controller, and both served from the same file:</p>
<table>
<thead>
<tr>
<th>Request</th>
<th>Answer</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>GET /api/project/logs</code></td>
<td><code>{ date, content }</code> for today's file; <code>404</code> with a message when there is none</td>
</tr>
<tr>
<td><code>GET /api/project/download-log</code></td>
<td>the same file as a <code>text/plain</code> attachment; <code>404</code> with the same message when there is none</td>
</tr>
</tbody>
</table>
<p>Both compose <code>logs/log-{yyyy-MM-dd}.txt</code> beneath the process's current working directory and date the
name from the server's local clock. Neither supports a range or a tail: the whole file is read into
memory and returned in full on every call, so an auto-refresh at a short interval re-transfers the
entire log each tick. Any failure inside either handler answers <code>400</code> with the exception message.</p>
<p>The two client wrappers part company on that <code>404</code>. The wrapper over <code>logs</code> normalises it into an
empty result rather than an error, which is what gives the page a clean &ldquo;no log yet&rdquo; state
instead of a red panel; every other non-OK status there is raised as a typed error carrying the HTTP
status and the response body. The download wrapper normalises nothing — <code>404</code> included, every non-OK
status becomes that same typed error, a path the disabled-without-content <code>Download</code> button makes
hard to reach. Because the controller declares no anonymous carve-out, both endpoints fall under the
authenticated-user fallback policy when the login gate is enabled.</p>
<p>The date badge shows whatever date the last answer reported. On the <code>404</code> path that date is not the
server's — the client fills it from the browser's own clock in UTC — so with no log file present the
badge can read a different day than the file name the server would have used.</p>
<h2 id="refresh-and-the-auto-interval">Refresh and the Auto Interval</h2>
<p>The page loads once on mount. After that every reload is driven by one of three things: the
<code>Refresh</code> button, the <code>Retry</code> button inside the error panel, or the auto-refresh timer.</p>
<p>Auto-refresh is off by default. The <code>Auto</code> toggle arms it and the interval select beside it chooses
the period from a fixed list of <code>2 s</code>, <code>5 s</code>, <code>10 s</code> and <code>30 s</code>, defaulting to five seconds; the
select is disabled while the toggle is off, but it keeps its value. The period is picked from that
dropdown rather than typed, so it takes effect on selection. A change to either control tears the
existing timer down, and a fresh one starts only while the toggle is still armed: an interval change
therefore takes effect immediately but also restarts the countdown, while disarming the toggle
simply stops the timer. Arming the toggle schedules the first automatic load one full interval
later; it does not fetch straight away.</p>
<p>Ticks are not coalesced with a request already in flight — each tick starts the same load
unconditionally.</p>
<p>Neither the toggle nor the interval is persisted. They are page-local state, held in neither the
browser's local storage nor the server-side user preferences, so a browser reload returns both to
their defaults.</p>
<h3 id="the-page-is-kept-alive">The Page Is Kept Alive</h3>
<p>The shell caches routed pages in a keep-alive keyed on the project epoch. Leaving the Log Viewer
therefore deactivates it rather than unmounting it, and the page's teardown hook — the one that
stops the timer — does not run. Two consequences follow. An armed auto-refresh keeps polling the log
endpoint in the background after navigation away, until the keep-alive is rebuilt by a project change
or the browser reloads. And returning to the page does not re-run the initial load: the previous
content, its <code>last loaded</code> stamp, the toggle and the chosen interval are all still there, and the
view refreshes on the next tick or on <code>Refresh</code>.</p>
<h2 id="errors-and-scroll-position">Errors and Scroll Position</h2>
<p>A failed load fills the page's error state and also pushes one foreground line onto the shell's
routine-progress footer, so the failure is visible from any page. The error panel takes precedence
over content in the text area, so a failed refresh hides the text that was already loaded — but only
until the next attempt starts, because the error text is cleared before that request goes out. The
content itself is never discarded: it reappears the moment the attempt begins, and the error panel
returns only if that attempt fails as well. With auto-refresh armed against an endpoint that keeps
failing, the text and the panel therefore alternate on every tick.</p>
<p>After a successful load the view scrolls to the bottom, but only while it is already stuck there —
the scroll handler treats a distance of less than 30 px from the bottom as stuck. Scrolling up
therefore freezes the position through an auto-refresh, and scrolling back to the bottom re-arms the
follow behaviour.</p>
<h2 id="level-filtering-and-where-the-file-comes-from">Level Filtering, and Where the File Comes From</h2>
<p>The page applies no filtering of its own. There is no level selector, no category selector, no
search box and no date picker: it renders the file it is given, verbatim, in the order it was
written. Which entries reach the file at all is decided at write time, by the host's logging filters
rather than by anything on this screen — see <a class="xref" href="program-and-hosting.html">Program and Hosting</a> for those rules and how
they differ from the console provider's.</p>
<p>The sink is a daily file logger provider registered at startup against a <code>logs</code> folder under the
process's current working directory. It writes one entry per line as
<code>[yyyy-MM-dd HH:mm:ss.fff] [Level] Category - message</code>, appends the exception on its own lines when
there is one, serialises appends behind a lock and swallows every I/O failure, on the principle that
logging must never fault a request. Logging scopes are not rendered into the file. The application
writes one Information line immediately after the host is built, so the file for the day the service
starts has content before the first request arrives and a freshly started service shows something
rather than an empty state. The sink recomposes the file name from the clock on every append, so a
service left running past midnight has no file for the new day until something is logged, and the
page shows its empty state until then.</p>
<p>Only the current day's file is reachable. Previous days' files stay on disk and the page offers no
way to open one. A file that exists but is empty renders the same empty state as a missing one; only
the date badge can separate the two, since the empty file's date comes from the server's answer and
the missing file's from the browser's UTC clock.</p>
<p>The same controller also exposes a POST endpoint that appends a caller-supplied line to the same
daily file. It writes with its own formatting rather than through the logger provider, and nothing
in the shipped SPA calls it.</p>
<h2 id="copy-download-and-what-the-page-cannot-do">Copy, Download, and What the Page Cannot Do</h2>
<p><code>Copy</code> writes the loaded text to the system clipboard and reports the outcome as a toast, positive
or negative. <code>Download</code> fetches the file as a blob from the download endpoint and saves it
client-side under the name <code>log-&lt;date&gt;.txt</code>, repeating the date the last answer reported, and also
ends in a toast. Both buttons are disabled while there is nothing loaded.</p>
<p>The page cannot clear, truncate, rotate or delete the log — there is no destructive action on it at
all, and no endpoint behind it that would perform one. It cannot reach another day, filter by level
or text, or stream: refreshing is polling over the same whole-file request, with no hub connection
and no server push. It is a viewer.</p>
<h2 id="layout">Layout</h2>
<ul>
<li>Log Viewer Page — a viewport-locked page sized from the layout's measured header and footer, so
the window itself never scrolls
<ul>
<li>Top Tool Bar
<ul>
<li><code>Log Viewer</code> Title</li>
<li>Date Badge — outlined; carries the date the last answer reported, and reads <code>today</code> only
before any answer has set one. Coloured as the primary accent while content is loaded and grey
otherwise</li>
<li>Last-Loaded Caption — <code>last loaded: {time}</code>, shown once a load has succeeded. The time is
formatted in the <strong>application</strong> locale rather than the browser's, so it re-renders on a
language switch; the hover title carries the same instant as an ISO 8601 UTC string</li>
<li>Line Count Caption — shown only while content is loaded, where it reads <code>{count} lines</code>. The
plural string's <code>no lines</code> and <code>one line</code> forms are out of reach: a zero count is exactly the
state that hides the caption, and every writer ends its entry with a newline. The count is the
number of newline-separated segments, so a file ending in a newline counts one more than it
shows, and a multi-line exception counts as several</li>
<li><code>Auto</code> Toggle — arms auto-refresh; off by default. Tooltip <code>Re-fetch the log file periodically</code></li>
<li>Interval Select — <code>2 s</code> / <code>5 s</code> / <code>10 s</code> / <code>30 s</code>, defaulting to <code>5 s</code>; disabled while <code>Auto</code>
is off</li>
<li><code>Refresh</code> Button — reloads at once, and shows a spinner in place of its label while the
request is in flight. Tooltip <code>Reload the log file</code></li>
<li><code>Copy</code> Button — disabled without content. Tooltip <code>Copy log content to clipboard</code></li>
<li><code>Download</code> Button — disabled without content. Tooltip <code>Download today's log file</code></li>
</ul>
</li>
<li>Separator</li>
<li>Log Area — a dark, monospaced, scrolling region filling the rest of the page. Long lines are not
wrapped, so it scrolls in both directions. It shows exactly one of four things:
<ul>
<li>Error Panel — an error icon, the failure message, and a <code>Retry</code> Button that repeats the load</li>
<li>Empty Panel — a document icon, <code>No log file for today.</code>, and the hint
<code>Log file will appear once the webservice writes its first entry.</code></li>
<li>Log Content — the file's text, preformatted and read-only, with no folding, no line numbers and
no per-line affordance of any kind</li>
<li>Loading Spinner — with <code>Loading log...</code> beneath it, shown while a load is in flight and
nothing has been loaded yet</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../index.html">HiNC App Anatomy</a> for git repository links.</p>
<h3 id="web-application">Web Application</h3>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/pages/LogViewerPage.vue</code> — the whole page: the toolbar, the four states of the
log area, the auto-refresh timer and its watcher, the stick-to-bottom scroll rule, and the copy
and download handlers.</li>
<li><code>wwwroot-src/src/api/logs.ts</code> — the typed wrappers over the two endpoints, the <code>404</code>-to-empty
normalisation with its browser-clock date fallback, and the error type carrying the HTTP status.</li>
<li><code>wwwroot-src/src/router/routes.ts</code> — the <code>preference/log</code> path, the <code>preference-log</code> route name and
the title key the tab and the menu bar's active-page indicator resolve.</li>
<li><code>wwwroot-src/src/router/index.ts</code> — the login guard the route passes through, and the retitle hook
that names the browser tab.</li>
<li><code>wwwroot-src/src/components/AppMenuBar.vue</code> — the always-visible <code>Show Log</code> button in the bar's
right-hand group, and the Preference dropdown that carries no log entry.</li>
<li><code>wwwroot-src/src/layouts/MainLayout.vue</code> — the keep-alive that caches this page rather than
unmounting it, and the project epoch that rebuilds the cache.</li>
<li><code>wwwroot-src/src/stores/appState.ts</code> — the message action that forwards a load failure to the
footer's foreground channel.</li>
<li><code>wwwroot-src/src/stores/routineProgress.ts</code> — the foreground-message store that failure lands in.</li>
<li><code>wwwroot-src/src/utils/pageFit.ts</code> — the page-height function that locks the page to the measured
viewport so only the log area scrolls.</li>
<li><code>wwwroot-src/src/i18n/en/logs.ts</code> — the page's own English strings: the title, the empty and
loading states, the plural line count and the tooltips.</li>
<li><code>wwwroot-src/src/i18n/en/menu.ts</code> — the <code>Show Log</code> label and its tooltip.</li>
<li><code>wwwroot-src/src/i18n/en/common.ts</code> — the shared <code>Auto</code>, <code>Refresh</code>, <code>Copy</code> and <code>Download</code> labels.</li>
<li><code>wwwroot-src/src/i18n/en/routes.ts</code> — the <code>Log Viewer</code> route title.</li>
<li><code>Environments/ProjectController.cs</code> — the two endpoints the page reads, both resolving today's file
under the working directory, plus the append endpoint nothing calls.</li>
<li><code>Common/DailyFileLoggerProvider.cs</code> — the sink: the per-day file, the entry format, the lock, the
swallowed I/O failures and the null scope.</li>
<li><code>Program.cs</code> — registers that provider and its level filters, and writes the one startup entry
right after the host is built.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="../shell/main-panel.html">Main Panel</a> — the menu bar whose right-hand <code>Show Log</code> button is the only navigation into this page</li>
<li><a class="xref" href="../shell/preference-menu.html">Preference Menu Dropdown</a> — the dropdown this route's path is named after, and which carries no log entry of its own</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,362 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Login and Authentication | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Login and Authentication | HiAPI-C# 2025 ">
<link rel="icon" href="../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../index.html">
<img id="logo" class="svg" src="../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-LoginAndAuth">
<h1 id="login-and-authentication">Login and Authentication</h1>
<p>The sign-in screen is the <code>/login</code> route. Whether anyone ever reaches it is decided by one back-end
setting — the <code>Enabled</code> flag of the <code>Auth</code> configuration section: with the flag off the service is
open and the route sends every visitor straight back out, and with it on a cookie-authentication
scheme plus a global authorization fallback policy lock every hub and every controller but the
authentication one — the whole <code>api/</code> surface apart from <code>api/auth</code> — until a visitor signs in. The
route hosts no Control-Tree branch and takes no <code>tree</code> query argument.</p>
<h2 id="the-enable-switch-and-its-two-defaults">The Enable Switch, and Its Two Defaults</h2>
<p><code>AuthConfig</code> binds the <code>Auth</code> section of configuration and is registered as one instance, so the
authentication controller and the startup decision read the same object. Two different values answer
the question &ldquo;is the gate on by default&rdquo;, and conflating them gets the answer wrong on a real
instance:</p>
<ul>
<li><strong>The class default is off.</strong> <code>Enabled</code> initialises to <code>false</code>, so a service whose configuration
carries no <code>Auth</code> section — or an empty one — runs with no login at all.</li>
<li><strong>The shipped configuration turns it on.</strong> <code>appsettings.json</code> and the Development overlay both set
<code>Enabled</code> to <code>true</code> and both supply one credential entry, so a service started from the repository
as it ships demands a sign-in.</li>
</ul>
<p>Three further settings live in the same section. <code>SessionHours</code> defaults to 8 and is raised to 1 at
registration if a smaller number is configured. <code>SlidingExpiration</code> defaults to <code>true</code>.
<code>Users</code> is a list of username and password pairs and defaults to empty.</p>
<p>That list is the whole credential store. Passwords are held in the configuration file in clear text
and compared verbatim — a first-match scan for an exact username <em>and</em> password — so there is no
hashing, no lockout, no rate limit and no user database behind it. The class documents the clear-text
form as deliberate, because the project also ships as sample code. Every entry grants the same
access; there are no roles. <code>Enabled</code> set to <code>true</code> over an empty list therefore admits nobody.</p>
<h2 id="what-the-gate-registers">What the Gate Registers</h2>
<p>The flag guards exactly two registrations at startup: a cookie authentication scheme, which becomes
the application's default and only scheme, and a global authorization fallback policy requiring an
authenticated user.</p>
<p>The fallback policy is what does the locking. It applies to every endpoint carrying no authorization
metadata of its own — every controller in the host assembly except the authentication one, and all
eight SignalR hub routes. <code>UseAuthentication</code> and <code>UseAuthorization</code> are added to the request pipeline
unconditionally and are no-ops when neither registration happened. See
<a class="xref" href="program-and-hosting.html">Program and Hosting</a> for the pipeline order this sits in.</p>
<p>Three places in the host carry <code>AllowAnonymous</code>, and they are the only ones that do:</p>
<ul>
<li>The authentication controller itself, marked at class level, so all three of its endpoints stay
reachable under the policy that locks everything else.</li>
<li>Both SPA fallbacks, so the client shell — which carries no data of its own — can load and show its
own login screen.</li>
</ul>
<p>Two pipeline stages answer before the authorization stage is reached and are unaffected by the
policy: static-file serving and the Swagger middleware. The first of those is what lets the login
screen render before anyone has signed in — the built bundle and the brand image it draws are
physical files under the web root.</p>
<p>With the gate off, none of this is registered: every controller, every hub and both fallbacks are
open, the login endpoint answers success without inspecting anything, the client's navigation guard
short-circuits, and the login route bounces the visitor away as it mounts.</p>
<h2 id="the-cookie">The Cookie</h2>
<p>The scheme issues a cookie named <code>HiNC.Auth</code>. It is <code>HttpOnly</code>, its <code>SameSite</code> mode is <code>Lax</code>, and its
secure policy is <em>same as request</em> — marked <code>Secure</code> when the request itself arrived over HTTPS and
issued plainly otherwise, so a plain-HTTP run on a local network still works. That decision is one of
the reasons forwarded headers are applied first in the pipeline, the HTTPS-redirection stage being
the other: behind a TLS-terminating reverse proxy the scheme must be read from <code>X-Forwarded-Proto</code>
rather than from the plain hop between proxy and service.</p>
<p>The ticket carries exactly one claim, the name of the matched user. Its lifetime is the configured
session length, refreshed on activity when sliding expiration is left on. Sign-in passes no
authentication properties, so the ticket is not marked persistent and the cookie carries no <code>Expires</code>
attribute: the browser holds it for the browser session, and the ticket's own expiry bounds a session
left open.</p>
<p>One event pair is overridden, and it is what makes a single-page client possible at all. A challenge
answers <strong>401</strong> and a refusal answers <strong>403</strong>, in place of the framework's default redirect to a
server-rendered login page. Every protected call therefore fails as data the client can read rather
than as a 302 that would arrive at the fetch layer as an HTML page.</p>
<h2 id="the-auth-endpoints">The Auth Endpoints</h2>
<p>The controller is routed at <code>api/auth</code> and holds three endpoints.</p>
<table>
<thead>
<tr>
<th>Endpoint</th>
<th>Behaviour</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>GET status</code></td>
<td>Reports whether the gate is enabled, whether this caller is authenticated, the caller's name, and a version string. It is anonymous, so it answers before sign-in and whether or not the gate is on.</td>
</tr>
<tr>
<td><code>POST login</code></td>
<td>With the gate off, returns success without inspecting the body. With it on, scans the configured users for an exact match and either answers 401 with an English message plus the stable code <code>InvalidCredentials</code>, or signs the caller in.</td>
</tr>
<tr>
<td><code>POST logout</code></td>
<td>Signs the caller out when the gate is on, and reports &ldquo;not authenticated&rdquo; either way.</td>
</tr>
</tbody>
</table>
<p>The version the status endpoint reports is the HiNc assembly version,
<a class="xref" href="../../api/Hi.MachiningProcs.MachiningProject.html#Hi_MachiningProcs_MachiningProject_ApiVersion_">ApiVersion</a><small>(API)</small>. Because the endpoint is
anonymous, that value is readable before sign-in, which is what lets the login screen and the menu bar
show the same version mark.</p>
<p>Nothing else in the host reads the signed-in identity — the status endpoint is its only consumer. Every
application service is registered as a process-wide singleton — the host adds no scoped or transient
registration of its own — so authentication decides admission rather than identity: two signed-in
browsers drive the same project and the same session. See <a class="xref" href="session-state.html">Session State</a> for what that
shared state consists of.</p>
<h2 id="the-client-side">The Client Side</h2>
<h3 id="the-store">The Store</h3>
<p>A Pinia store holds the whole client-side picture: whether the gate is enabled, whether this session is
authenticated, the user name, the version string, and a ready flag set once the first status probe has
resolved. <code>refresh</code> reads the status endpoint; <code>login</code> and <code>logout</code> call their endpoints and update the
same fields; a fourth action flips the store to logged-out with no round trip, for the interceptor
below. Every transition re-drives the hub gate.</p>
<h3 id="the-navigation-guard">The Navigation Guard</h3>
<p>One global <code>beforeEach</code> guard runs the whole client-side decision, in order:</p>
<ol>
<li>If the store is not ready yet, hydrate it from the status endpoint, inside a <code>try</code>/<code>catch</code> that
swallows the failure. The ready flag is set only on success, so a failed probe is retried on the
next navigation.</li>
<li>If the gate reports disabled, allow the navigation.</li>
<li>If the target is the login route, allow it.</li>
<li>If the session is not authenticated, redirect to the login route with the blocked target's full
path — query arguments, <code>tree</code> included — parked in a <code>redirect</code> argument.</li>
<li>Otherwise allow it.</li>
</ol>
<p><strong>What the guard does not do</strong> is protect anything. It is a navigation redirect: it runs on router
navigations only, so it has no bearing on a direct <code>api/</code> request, on a hub negotiate, or on a static
asset, all of which are the fallback policy's business. It reads no per-route metadata, so there is no
list of public routes beyond the login route itself and no notion of a role. And it fails open by
construction — when the status probe throws, the store keeps the gate disabled and the navigation is
allowed, so a back-end hiccup cannot lock a user out of an installation that has no gate.</p>
<h3 id="the-401-interceptor">The 401 Interceptor</h3>
<p>A boot file wraps the global <code>fetch</code> once for the whole application. Every API module, and SignalR's
own negotiate, call that global, so one wrapper covers a session that expires mid-use: on any 401
response, and only while the store says the gate is enabled, it marks the store logged out and pushes
the login route with the current full path in <code>redirect</code>, unless the router is already there. The
response is handed back unchanged, so the calling code still sees its own failure and handles it.</p>
<p>Boot order is load-bearing here. The interceptor's boot file is declared ahead of the i18n one, and
boot files are awaited in declaration order, so the locale probe made during startup goes through the
patched <code>fetch</code>.</p>
<h3 id="the-hub-gate">The Hub Gate</h3>
<p>The shared-hub registry carries one tri-state gate that the store drives on every transition: undecided
while the status probe is still resolving, closed while the gate is on and the session is signed out,
and open for a signed-in session or a service with no gate. Undecided holds, so a first-paint race
never fires a negotiate that is certain to fail; closed keeps every hub idle rather than letting the
never-give-up reconnect schedule hammer a <code>/negotiate</code> that answers 401 by design. The store releases
the gate even when the status probe fails, so a probe hiccup never strands the hubs of a service that
has no login. Opening the gate resumes the connections a caller had already asked for, without the
caller asking again.</p>
<h3 id="after-a-successful-sign-in">After a Successful Sign-In</h3>
<p>The login screen navigates with a full page load rather than a router navigation. The application's
one-shot wiring — the project hub subscription and the first project-status fetch — sits behind a
watcher on the auth predicate and runs once per page load, so reloading is what re-runs it with the
cookie present.</p>
<h2 id="signing-out">Signing Out</h2>
<p>The logout control sits at the right of the menu bar and renders only while the gate is enabled <em>and</em>
the session is authenticated. It is labelled with the signed-in user name, falling back to <code>Logout</code>
when the status carries no name, and its tooltip reads <code>Log out</code>. It posts the logout endpoint and
then, in a <code>finally</code>, hard-navigates to <code>/login</code> — so a failed request still tears the page down and
rebuilds the client from scratch, rather than leaving the singletons of an ended session wired up.</p>
<h2 id="layout">Layout</h2>
<p>The route renders outside the shell layout, in a Quasar layout of its own — the same arrangement the
catch-all not-found route uses. Nothing of the application frame is present: no menu bar, and therefore
no Project, Page or Preference menu and no language submenu; no cached page panel; no
routine-progress footer. The browser tab reads <code>Login - HiNC</code>.</p>
<ul>
<li>Login Page — a single card centred on an empty page
<ul>
<li>Brand Section
<ul>
<li>Brand image, above the literal title <code>HiNC</code></li>
<li>Version caption, <code>v</code> followed by the version string — drawn only when the status probe returned
one</li>
<li>Prompt caption — <code>Please sign in to continue</code></li>
</ul>
</li>
<li>Separator</li>
<li>Sign-In Form
<ul>
<li><code>Username</code> Text Field — autofocused on arrival, disabled while a sign-in is in flight</li>
<li><code>Password</code> Text Field — masked, with a trailing eye icon that toggles the text visible; the icon's
accessible label alternates between <code>Show password</code> and <code>Hide password</code></li>
<li>Error caption — drawn in the negative colour, and only after a failed attempt</li>
<li><code>Sign In</code> Button — submits the form and shows a spinner while the request is in flight</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>Pressing Enter in either field submits the same form. Neither field carries a validation rule, so an
empty pair is submitted and refused by the server like any other wrong pair. The error caption shows
the localized &ldquo;Incorrect username or password&rdquo; whenever the server pairs its refusal with the
<code>InvalidCredentials</code> code, and the server's own English message for any other refusal that carries
one.</p>
<p>Two cases send the visitor away again as soon as the screen mounts. The mount hook hydrates the auth
status if the first probe has not resolved yet, and then replaces the route with the parked <code>redirect</code>
target — or with <code>/</code>, which redirects on to the Execution page — when the gate reports disabled or when
the session is already authenticated.</p>
<p>The locale the screen paints in is whatever the boot sequence resolved, because the stored language
preference is served by an endpoint the fallback policy locks. With a browser-local cached locale the
screen paints in that; without one the awaited probe fails and the locale falls back to the browser's
own language, and to English after that. The screen's own strings ship in all three locales.</p>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../index.html">HiNC App Anatomy</a> for git repository links.</p>
<h3 id="web-application">Web Application</h3>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>Common/AuthConfig.cs</code> — the bound shape: the enable flag and its class default, the session length,
the sliding-expiration flag, and the clear-text user list.</li>
<li><code>Common/AuthController.cs</code> — the anonymous <code>api/auth</code> controller: the status projection, the
first-match credential scan with its message and code pair, the single name claim it signs in, and
the sign-out.</li>
<li><code>Program.cs</code> — the flag-guarded registration of the cookie scheme and the fallback policy, the cookie
name and flags, the 401 / 403 event overrides, the forwarded-headers configuration ahead of them, and
the two anonymous SPA fallbacks.</li>
<li><code>wwwroot-src/src/pages/LoginPage.vue</code> — the screen: its own Quasar layout, the card and its fields,
the bounce on mount, and the full page load after a successful sign-in.</li>
<li><code>wwwroot-src/src/router/routes.ts</code> — the login route outside the shell layout, and the title key the
tab shows.</li>
<li><code>wwwroot-src/src/router/index.ts</code> — the navigation guard: the once-only hydration, the disabled and
login-route short circuits, and the redirect carrying the full path; also the retitle that composes
the tab text from the route's title key.</li>
<li><code>wwwroot-src/src/stores/auth.ts</code> — the client-side state, the four actions, and the hub gate each of
them re-drives.</li>
<li><code>wwwroot-src/src/api/auth.ts</code> — the three typed endpoint wrappers, and the mapping of the server's
refusal code onto the localized message.</li>
<li><code>wwwroot-src/src/boot/auth.ts</code> — the global <code>fetch</code> wrapper that turns a 401 into a logged-out store
and a push to the login route.</li>
<li><code>wwwroot-src/quasar.config.ts</code> — the boot-file order that puts the interceptor ahead of the locale
probe.</li>
<li><code>wwwroot-src/src/boot/i18n.ts</code> — the locale resolution the login screen paints in, and its fallback
chain when the language endpoint answers 401.</li>
<li><code>wwwroot-src/src/composables/useSharedHub.ts</code> — the tri-state hub gate and what each of its states
does to a connection.</li>
<li><code>wwwroot-src/src/App.vue</code> — the one-shot wiring held behind the auth predicate, which is what the
post-sign-in page load re-runs.</li>
<li><code>wwwroot-src/src/components/AppMenuBar.vue</code> — the logout control and the version badge, both drawn
from the auth store.</li>
<li><code>wwwroot-src/src/i18n/en/auth.ts</code> — the screen's English strings.</li>
<li><code>wwwroot-src/src/i18n/en/menu.ts</code> — the <code>Logout</code> label the control falls back to, and its <code>Log out</code>
tooltip.</li>
<li><code>wwwroot-src/src/i18n/en/routes.ts</code> — the <code>Login</code> route title the browser tab resolves.</li>
</ul>
<h3 id="hiapi-engine">HiAPI Engine</h3>
<ul>
<li><code>HiNc/MachiningProcs/MachiningProject.cs</code> — the assembly version the status endpoint reports and the
screen shows.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="program-and-hosting.html">Program and Hosting</a> — the host that binds the <code>Auth</code> section and registers the scheme,
the fallback policy and the anonymous carve-outs described here</li>
<li><a class="xref" href="../shell/main-panel.html">Main Panel</a> — the shell this screen renders outside of, and the menu bar that carries
the logout control and the version badge</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,499 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Program and Hosting | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Program and Hosting | HiAPI-C# 2025 ">
<link rel="icon" href="../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../index.html">
<img id="logo" class="svg" src="../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-ProgramAndHosting">
<h1 id="program-and-hosting">Program and Hosting</h1>
<p><code>Program.cs</code> is the entire host of the HiNC web service: one class whose <code>Main</code> builds an ASP.NET
Core application and runs it. It presents no screen of its own — it is the process that starts and
stops HiAPI, registers every service the controllers and hubs resolve, maps those controllers and
the eight SignalR hubs, serves the built Quasar SPA out of the web root, and falls back to the SPA
shell for every URL the Vue router owns. <code>BuildApp</code> is public and returns the built application
without starting it, so the build phase and the run phase are separable; <code>Main</code> calls it, writes one
startup line to the log, and runs.</p>
<h2 id="hiapi-lifetime">HiAPI Lifetime</h2>
<p>The host brackets its whole life in one pair of HiAPI calls.</p>
<p><a class="xref" href="../../api/Hi.HiNcKits.LocalApp.html#Hi_HiNcKits_LocalApp_AppBegin_">AppBegin</a><small>(API)</small> runs once during <code>BuildApp</code>, after the
service container is built and before the request pipeline is configured. It logs in every licence
<a class="xref" href="../../api/Hi.Licenses.License.html">License</a> holds, initialises the display engine (<a class="xref" href="../../api/Hi.Disp.DispEngine.html">DispEngine</a>), and
opens the SQLite step-cache database at the path it is given, publishing it as
<a class="xref" href="../../api/Hi.SqliteUtils.SqliteStepStorage.html">SqliteStepStorage</a>'s default instance. The overload the webservice calls takes a
logger and a cache-database path.</p>
<p><a class="xref" href="../../api/Hi.HiNcKits.LocalApp.html#Hi_HiNcKits_LocalApp_AppEnd_">AppEnd</a><small>(API)</small> unwinds the same set in reverse: it waits for
queued background CubeTree frees to drain so no native delete runs against a half-torn-down runtime,
disposes the step and identity storages, shuts the display engine down, and logs the licences out.</p>
<p>Three separate events can reach <code>AppEnd</code>, and a static latch guarantees it runs at most once:</p>
<ul>
<li><code>IHostApplicationLifetime.ApplicationStopping</code> — the ordinary graceful stop.</li>
<li><code>AppDomain.CurrentDomain.ProcessExit</code> — which still fires when a console window is closed outright.</li>
<li><code>Console.CancelKeyPress</code> — Ctrl+C and Ctrl+Break, which cancel the immediate termination and ask
the lifetime for a graceful stop instead, so the first path runs.</li>
</ul>
<p>The cache-database path is <code>Cache/{CacheDbId}.db</code> beneath the admin directory.
<a class="xref" href="../../api/Hi.HiNcKits.HiNcHost.html#Hi_HiNcKits_HiNcHost_CacheDbId">CacheDbId</a> is the <code>HiNC:CacheDbId</code> configuration value when one is set and
the hosting environment name otherwise. Supplying a path matters:
<a class="xref" href="../../api/Hi.SqliteUtils.SqliteStepStorage.html">SqliteStepStorage</a> given none falls back to a single per-user file, so several
instances on one account would otherwise share it. Two instances launched under different
<code>--environment</code> names get different cache files with no further configuration.</p>
<h2 id="startup-order">Startup Order</h2>
<p>Three registrations run before the builder exists, because each seeds a table that is read — or
frozen — the moment anything else touches it:</p>
<ul>
<li><code>LocalProjectService.Reg()</code> fills <a class="xref" href="../../api/Hi.Common.XmlUtils.XFactory.html">XFactory</a>'s default generator table with
every type the simulation pipeline may deserialise, which is what every project XML read resolves
against.</li>
<li><code>UserConfig.Reg()</code> adds the per-user preference file's own type to the same table; without it, a
persisted preference file cannot be read back and every <code>UserService</code> resolution fails once one
exists.</li>
<li><a class="xref" href="../../api/Hi.SessionCommands.Lang.html">Lang</a> is appended to <a class="xref" href="../../api/Hi.Common.StringLocalizer.html">StringLocalizer</a>'s extended type list, so
session-command titles resolve through that assembly's satellite resources. The type list is
static, but each localizer builds its own resource-manager list lazily on its first lookup and
keeps it, so a type added after a given localizer has been used never reaches that localizer.
Registering before the builder exists is what puts the addition ahead of every first lookup.</li>
</ul>
<p>After the container is built and before the pipeline is assembled, the host wires the native core's
log output into the application logger through <a class="xref" href="../../api/Hi.Common.CppLogUtil.html">CppLogUtil</a>, sets the cache
identifier, calls <code>AppBegin</code>, and then runs
<a class="xref" href="../../api/Hi.HiNcKits.ResourceSeeder.html#Hi_HiNcKits_ResourceSeeder_Seed_">Seed</a><small>(API)</small> over the admin <code>Resource</code> root. The
seeder copies in only the marked <code>.default</code> items, leaves unmarked items alone as user property, and
skips the whole pass when its version stamp already matches the shipped resource assembly.</p>
<h2 id="registered-services">Registered Services</h2>
<p>Every application service is registered as a <strong>singleton</strong>. The repository contains no <code>AddScoped</code>
and no <code>AddTransient</code> call, so no service this host registers has a per-request lifetime and any
state one of them holds is process-wide. Any shorter lifetime in the container is one of the
framework's own registrations rather than an application service.</p>
<p>Four registrations are worth reading closely:</p>
<ul>
<li><strong><code>UserService</code></strong> is built by a factory rather than by type, so that its configuration path is
assigned unconditionally — including when no preference file exists yet, which is the only way the
first save can create one. When the file does exist, the factory deserialises it through
<a class="xref" href="../../api/Hi.Common.XmlUtils.XFactory.html">XFactory</a>.</li>
<li><strong><code>ProxyConfig</code></strong> is bound by hand into a plain singleton from the <code>ProxyConfig</code> configuration
section. A separate <code>services.Configure&lt;ProxyConfig&gt;</code> call also registers it through the options
system, but nothing resolves <code>IOptions&lt;ProxyConfig&gt;</code>; the plain singleton is what
<a class="xref" href="../../api/Hi.MachiningProcs.ProxyProjectService.html">ProxyProjectService</a> and the startup code receive. That binding happens
once at startup and is not reloaded.</li>
<li><strong><a class="xref" href="../../api/Hi.MachiningProcs.ProxyProjectService.html">ProxyProjectService</a></strong> is registered twice — once under its own type and
once as <a class="xref" href="../../api/Hi.MachiningProcs.IProjectService.html">IProjectService</a> — so the container builds one instance per
registration. Both hold nothing but the same two injected singletons,
<a class="xref" href="../../api/Hi.MachiningProcs.LocalProjectService.html">LocalProjectService</a> and <a class="xref" href="../../api/Hi.HiNcKits.ProxyConfig.html">ProxyConfig</a>, which is where all
the state lives.</li>
<li><strong><code>AuthConfig</code></strong> is bound from the <code>Auth</code> section and registered as an instance, so the
authentication controller and the pipeline decision below read the same object. See
<a class="xref" href="login-and-auth.html">Login and Authentication</a> for what it switches on.</li>
</ul>
<p>Six singletons are resolved eagerly at the end of <code>BuildApp</code> so they subscribe to their engine events
at startup rather than when the first client happens to connect: the CL-strip broadcast service, the
four per-sink message broadcast services, and the NC-program registry.</p>
<p>Kestrel is configured with <code>AllowSynchronousIO = true</code>, which permits handlers to write to the
response body synchronously.</p>
<h2 id="controllers">Controllers</h2>
<p>Controllers are added with <code>AddControllers()</code>, with the host's own assembly registered as an explicit
application part, and reached by <code>MapControllers()</code> at the end of the pipeline. Every controller in
the host assembly is attribute-routed, and every route template begins with <code>api/</code> — most as
<code>api/[controller]</code>, the rest as an explicit kebab-case path such as <code>api/mech/machine-tool</code> or
<code>api/execution/cl-strip</code>. No controller route sits outside <code>api/</code>, which is what makes the
&ldquo;everything else is the SPA&rdquo; fallback rule below safe.</p>
<p>One controller is exempt from the login gate: the authentication controller carries
<code>[AllowAnonymous]</code> so it stays reachable when the fallback policy locks everything else.</p>
<h2 id="signalr-hubs">SignalR Hubs</h2>
<p><code>AddSignalR()</code> is called without protocol configuration, and eight hub routes are mapped:</p>
<table>
<thead>
<tr>
<th>Route</th>
<th>Hub type</th>
<th>What it carries</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>/renderingHub</code></td>
<td><code>RenderingHub</code></td>
<td>server-rendered canvas frames and the input that drives them, one display engine per connection</td>
</tr>
<tr>
<td><code>/shellMessageHub</code></td>
<td><code>ShellMessageHub</code></td>
<td>session-level routine and lifecycle messages</td>
</tr>
<tr>
<td><code>/ncDiagnosticHub</code></td>
<td><code>NcDiagnosticHub</code></td>
<td>NC-pipeline diagnostics</td>
</tr>
<tr>
<td><code>/stepDiagnosticHub</code></td>
<td><code>StepDiagnosticHub</code></td>
<td>step-anchored diagnostics</td>
</tr>
<tr>
<td><code>/ncManipulationDiagnosticHub</code></td>
<td><code>NcManipulationDiagnosticHub</code></td>
<td>NC-manipulation diagnostics — writeback conversion and optimisation</td>
</tr>
<tr>
<td><code>/executionStatusHub</code></td>
<td><code>ExecutionStatusHub</code></td>
<td><code>ExecutionStatusUpdated</code>, <code>SessionCursor</code> and <code>SessionStatusMessage</code>, broadcast to all clients; the client-callable <code>GetExecutionStatus()</code> answers the caller alone, on the same <code>ExecutionStatusUpdated</code> name</td>
</tr>
<tr>
<td><code>/clStripHub</code></td>
<td><code>ClStripHub</code></td>
<td>strip-chart display range, selected and entered step, and throttled data-update hints</td>
</tr>
<tr>
<td><code>/cleanupHub</code></td>
<td><code>CleanupHub</code></td>
<td>index keys, and optional follow-up action keys, that a client registers against its connection with <code>Add</code>; the disconnect sweep finds none of them — see below</td>
</tr>
</tbody>
</table>
<p>The four message hubs share one base class and one contract — the client calls <code>GetMessages(limit)</code>
and the server answers on <code>MessagesUpdate</code> — but they are deliberately separate routes rather than
one hub with a discriminator, so a client subscribes to exactly the sink it wants and an idle panel
means an idle hub rather than a lost connection.</p>
<p>The cleanup hub is the one whose registry does not outlive the call that fills it. SignalR builds a
fresh hub instance for every invocation, and the key dictionary <code>Add</code> writes to is an instance
member rather than shared state, so the dictionary the disconnect handler walks is a different,
empty one and releases nothing. Only <code>IndexService</code> — a singleton — is shared across those
instances. The client does not depend on the hub for the release: <code>useCleanupHub.ts</code> keeps its own
set of registered keys and posts each one to <code>/api/Index/Remove</code> before the component unmounts. That
is the path that actually frees an entry.</p>
<h2 id="json-serialisation">JSON Serialisation</h2>
<p>The controllers' serializer options carry two settings, both applied through <code>AddJsonOptions</code>.</p>
<p><strong><code>JsonNumberHandling.AllowNamedFloatingPointLiterals</code>.</strong> Several engine values use
<code>double.PositiveInfinity</code> as their &ldquo;no limit&rdquo; state — the optimizer's Max Feed Per Tooth and
Preferred Force among them — and the snapshots the SPA reads carry those doubles straight into the
response object. <code>System.Text.Json</code> refuses to write an infinite or NaN double as a JSON number, so
without this setting every such response would fault during serialisation instead of returning. With
it, those values travel as the JSON strings <code>&quot;Infinity&quot;</code>, <code>&quot;-Infinity&quot;</code> and <code>&quot;NaN&quot;</code>. The reading side
is not uniform: the numeric input widgets take all three spellings case-insensitively, while the
option-snapshot reader in the mission API recognises the two infinity spellings only and resolves
anything else non-numeric — <code>&quot;NaN&quot;</code> included — to the fallback its caller passed, which is positive
infinity for Max Feed Per Tooth and Preferred Force.
<a class="xref" href="../conventions/numeric-io-utilities.html">Numeric Input/Output</a> covers the client half of that boundary, and
<a class="xref" href="../execution/mission/NcOptOption-panel.html">NC Optimization Option Panel (NC Optimization Config)</a> the panel that leans hardest on it — including the two write
endpoints that take a string body so the same spelling survives the round trip.</p>
<p><strong><code>JsonStringEnumConverter</code>.</strong> Enums are read and written as their names rather than as integers,
which is what the API wrappers expect and what keeps the OpenAPI schema self-describing.</p>
<p>Both settings belong to the MVC controllers only. Hub payloads are serialised by SignalR's own
protocol, which is left at its defaults, so neither the named floating-point literals nor the string
enums extend to hub messages.</p>
<h2 id="the-request-pipeline">The Request Pipeline</h2>
<p>The order the middleware is added in is load-bearing, and the host sets it explicitly rather than
accepting the default arrangement:</p>
<ol>
<li><strong>Forwarded headers</strong> first, honouring <code>X-Forwarded-For</code> and <code>X-Forwarded-Proto</code> so that every
later stage sees the real client scheme. Both the known-proxy and the known-network lists are
cleared, so those headers are accepted from any caller.</li>
<li><strong>Swagger</strong> — the Swashbuckle document and its UI, both middleware. <code>MapOpenApi</code> beside them
registers the framework's own document as an <em>endpoint</em> instead, so that one is reached at
step 8 with everything else that is routed.</li>
<li><strong>Default files, then static files</strong>, serving the built SPA and everything else physically present
in the web root.</li>
<li><strong><code>UseRouting()</code> — placed here on purpose.</strong> An application that never calls it gets routing
inserted at the front of the pipeline instead, which would select the SPA fallback endpoint before
the static-file middleware ran; that middleware stands down once an endpoint is chosen, so a
directory URL under the web root would be answered by the Vue router's not-found view rather than
by the directory's own default document.</li>
<li><strong>CORS</strong>, applying the <code>AllowAll</code> policy described below.</li>
<li><strong>HTTPS redirection</strong>, skipped in the Development environment.</li>
<li><strong>Authentication, then authorization.</strong> Both are no-ops when the login gate is off, because no
scheme and no fallback policy are registered in that case.</li>
<li><strong>Endpoints</strong> — controllers, the eight hubs, and the two SPA fallbacks.</li>
</ol>
<p>A request answered before step 7 never reaches the authorization stage at all: the static-file
middleware and the Swagger middleware both sit ahead of it and short-circuit the request when they
match. The two SPA fallbacks are endpoints rather than middleware, so they do reach it, and both are
marked <code>AllowAnonymous</code> — which is what lets the SPA shell, carrying no data of its own, load and
show its own login view when the gate is on.</p>
<h3 id="the-spa-fallback">The SPA Fallback</h3>
<p>Two fallback registrations serve <code>index.html</code>:</p>
<ul>
<li><code>util/file-explorer/{**location}</code> — an explicit pattern with no file-name constraint.</li>
<li>The bare fallback, for everything else.</li>
</ul>
<p>The bare form's implicit route pattern carries a <code>nonfile</code> constraint that rejects any URL whose last
segment contains a dot. File Explorer deep links mirror real folder and file names into the URL, and
those names routinely contain dots, so reloading or pasting such a location needs the explicit
pattern. Genuine static assets cannot be shadowed by it, because the static-file middleware has
already run by the time either fallback is reached.</p>
<h2 id="serving-the-spa">Serving the SPA</h2>
<p>The Quasar build writes its output directly into the host's web root — the front end's build
configuration sets its distribution directory to the web service's <code>wwwroot</code> — so there is no copy
step between building the SPA and serving it. The router runs in history mode, which is what makes
the SPA fallback necessary: every client-side route is an extension-less URL the server has no
endpoint for.</p>
<p>The front-end build script runs the i18n census and glossary lint before invoking the Quasar build,
so a build that fails those checks produces no new bundle.</p>
<p>For front-end work the Quasar dev server runs on its own port and proxies the routes the backend owns
to the running service. That proxy list holds <code>/api</code>, <code>/swagger</code>, <code>/renderingHub</code>,
<code>/executionStatusHub</code>, <code>/clStripHub</code>, <code>/cleanupHub</code>, and <code>/sessionMessageHub</code> — a path the host maps
no hub on. The four per-sink message hubs are absent from it, so the Session Messages panels receive
no live messages through the dev server.</p>
<h2 id="openapi-and-swagger">OpenAPI and Swagger</h2>
<p>A single private constant holds the REST contract version. It is simultaneously the Swashbuckle
document group name — so it appears in the document's route — and the version the UI displays, which
is why it is defined in one place. It is raised by hand on a breaking REST change only, and is
deliberately not tied to the assembly version or to the project-file format version.</p>
<p>Both OpenAPI surfaces are exposed in every environment rather than behind a development gate, so an
automated caller always has a machine-readable contract. The Swashbuckle UI is served at <code>/swagger</code>
and its document at <code>/swagger/{version}/swagger.json</code>; the framework's own document is published
under <code>/openapi/</code>.</p>
<p>Schema ids are keyed on the full type name with the nested-type separator normalised. Without that,
two controllers' nested request types that share a simple name collide and the document generator
throws — which surfaces as an HTTP 500 from the document route rather than as a startup failure. XML
documentation comments are folded in from the host assembly and from every referenced assembly whose
name starts with <code>Hi</code>. Every read is skipped when its file is missing, and each referenced-assembly
read carries a catch-all of its own on top of that, so one whose file is present but unreadable is
ignored; the host assembly's read has no such catch-all.</p>
<h2 id="cors">CORS</h2>
<p>One policy, named <code>AllowAll</code>, permits any origin, any method and any header, and it is applied
globally. It does not allow credentials, so the response carries a wildcard origin and a cross-origin
browser call cannot use the session cookie. The SPA is served from the same origin as the API and the
hubs, so this constrains only callers hosted elsewhere.</p>
<h2 id="logging">Logging</h2>
<p>The default providers are cleared and two are added: a console provider, and a daily file provider
rooted at a <code>logs</code> folder beneath the process's current working directory. The file provider writes
one line per entry to <code>log-{yyyy-MM-dd}.txt</code> and swallows every I/O failure, on the principle that
logging must not fault a request or a startup.</p>
<p>The two providers are filtered independently. The console follows the <code>Logging:LogLevel</code> section of
configuration, which sets everything to Warning. The file provider carries its own code-set rules —
Information for the application's own categories, Warning for <code>Microsoft</code> and <code>System</code> — and
provider-specific rules take precedence over configuration's provider-neutral ones, so the file stays
useful while the console stays quiet.</p>
<p><code>Main</code> writes one Information line immediately after the application is built, so the file for the day
the service starts has content before the first request arrives. That guarantee stops at midnight: the
provider recomposes the file name from the clock on every append, and creates nothing ahead of time, so
a host left running into a new day has no file for it until the next entry is written.
<code>GET /api/project/logs</code> reads the same directory.</p>
<h2 id="configuration">Configuration</h2>
<p>Configuration is the standard chain: <code>appsettings.json</code>, then <code>appsettings.{Environment}.json</code>, then
environment variables, then command-line arguments. The keys this host reads are:</p>
<table>
<thead>
<tr>
<th>Key</th>
<th>What it controls</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>Logging:LogLevel</code></td>
<td>the console provider's levels</td>
</tr>
<tr>
<td><code>AllowedHosts</code></td>
<td>host filtering; shipped as a wildcard</td>
</tr>
<tr>
<td><code>Kestrel:Endpoints</code></td>
<td>the addresses the server binds</td>
</tr>
<tr>
<td><code>ProxyConfig:AdminDirectory</code></td>
<td>the admin working root, under which <code>Resource</code> and <code>Cache</code> live</td>
</tr>
<tr>
<td><code>Auth</code></td>
<td>the optional login gate</td>
</tr>
<tr>
<td><code>HiNC:CacheDbId</code></td>
<td>an explicit step-cache database id, overriding the environment name</td>
</tr>
</tbody>
</table>
<p>The Development overlay repeats the logging, admin-directory and authentication sections and declares
no <code>Kestrel</code> section, so the listening address is the same under either environment.</p>
<h3 id="kestrel-endpoints-win">Kestrel Endpoints Win</h3>
<p><code>Kestrel:Endpoints</code> is the authoritative source of the listening address. When it declares endpoints,
Kestrel binds those and <strong>ignores</strong> addresses supplied any other way — <code>ASPNETCORE_URLS</code>, <code>--urls</code>,
and the launch profile's <code>applicationUrl</code> alike — logging that it is overriding them. The shipped
configuration declares one HTTP endpoint on loopback port 5000. The <code>http</code> launch profile names the
same address, so the override is invisible there; the <code>https</code> profile also names an HTTPS address on
a second port, and that address is one of the ones Kestrel discards, so launching under it still
yields plain HTTP on 5000 and nothing else.</p>
<p>To move a local instance to another port, set the <code>Kestrel__Endpoints__Http__Url</code> environment
variable, which the environment-variable provider maps onto the same key. Setting <code>ASPNETCORE_URLS</code>
alone changes nothing.</p>
<p>The endpoint binds <code>localhost</code>, so the shipped configuration answers only on the machine it runs on.
Reaching it from elsewhere means either overriding that key or placing a reverse proxy in front,
which is the arrangement the forwarded-headers configuration handles.</p>
<h3 id="environment-and-working-directory">Environment and Working Directory</h3>
<p>The environment name reaches further here than usual. Both launch profiles set it to <code>Development</code>,
which selects the Development overlay, skips HTTPS redirection, and — unless <code>HiNC:CacheDbId</code>
overrides it — names the step-cache database file.</p>
<p>Two paths follow the process's current working directory rather than the content root: the <code>logs</code>
folder the file logger writes and the log endpoint reads, and the per-user preference file. An
instance launched from the project directory finds both where they are expected; one launched from
elsewhere puts them beside wherever it was started.</p>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../index.html">HiNC App Anatomy</a> for git repository links.</p>
<h3 id="web-application">Web Application</h3>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>Program.cs</code> — the whole subject of this page: the pre-builder registrations, every service
registration, the JSON and Swagger options, the CORS policy, the pipeline order, the eight
<code>MapHub</code> calls, the two SPA fallbacks, the <code>AppBegin</code> / <code>AppEnd</code> pair and the three shutdown
triggers.</li>
<li><code>appsettings.json</code> — the shipped listening address, the admin directory and the login section.</li>
<li><code>appsettings.Development.json</code> — the Development overlay, which declares no <code>Kestrel</code> section.</li>
<li><code>Properties/launchSettings.json</code> — the two launch profiles whose <code>applicationUrl</code> the <code>Kestrel</code>
section overrides.</li>
<li><code>Common/DailyFileLoggerProvider.cs</code> — the per-day file logger the log endpoint reads back.</li>
<li><code>Common/AuthConfig.cs</code> — the shape bound from the <code>Auth</code> section.</li>
<li><code>Common/AuthController.cs</code> — the anonymous carve-out that keeps login reachable under the fallback
policy.</li>
<li><code>Common/NamedRootResolver.cs</code> — resolves the admin, project and resource roots the seeder and the
file endpoints work against.</li>
<li><code>Common/CleanupHub.cs</code> — the cleanup hub, and the per-invocation dictionary its disconnect
handler walks.</li>
<li><code>Common/IndexController.cs</code> — the index remove endpoint the client calls before unmount.</li>
<li><code>Disp/RenderingHub.cs</code> — the canvas hub.</li>
<li><code>Execution/SessionSinkHub.cs</code> — the shared base and the four per-sink message hubs.</li>
<li><code>Execution/ExecutionStatusHub.cs</code> — the status, cursor and session-message hub.</li>
<li><code>Execution/ClStripHub.cs</code> — the strip-chart hub.</li>
<li><code>Environments/UserConfig.cs</code> — the preference type registered before the builder is created.</li>
<li><code>Environments/ProjectController.cs</code> — the log endpoints that read the daily file.</li>
<li><code>wwwroot-src/src/composables/useCleanupHub.ts</code> — the client's own key set and the index-remove
calls that release it.</li>
<li><code>wwwroot-src/src/api/mission.ts</code> — the option-snapshot reader that maps the named-literal strings
back to numbers, and the string-bodied setters that send them.</li>
<li><code>wwwroot-src/quasar.config.ts</code> — the build output directory that puts the bundle in the web root,
the history router mode, and the dev-server proxy list.</li>
<li><code>wwwroot-src/package.json</code> — the build script that runs the i18n lint before the Quasar build.</li>
</ul>
<h3 id="hiapi-engine">HiAPI Engine</h3>
<ul>
<li><code>HiNc/HiNcKits/LocalApp.cs</code><code>AppBegin</code> and <code>AppEnd</code>: licence log-in and log-out, display-engine
start and finish, and the step-storage open and dispose.</li>
<li><code>HiNc/HiNcKits/HiNcHost.cs</code> — the cache-database identifier the host assigns at startup.</li>
<li><code>HiNc/HiNcKits/ProxyConfig.cs</code> — the admin-directory setting and its own default.</li>
<li><code>HiNc/HiNcKits/ResourceSeeder.cs</code> — the marked-defaults seeding pass and its version stamp.</li>
<li><code>HiNc/SqliteUtils/SqliteStepStorage.cs</code> — the step cache, and the per-user default path that makes
an explicit per-instance path necessary.</li>
<li><code>HiNc/MachiningProcs/ProxyProjectService.cs</code> — the project service registered under two service
types, holding only the singletons it is given.</li>
<li><code>HiGeom/Common/StringLocalizer.cs</code> — the static extended type list, and the per-instance
resource-manager list each localizer builds on its own first lookup.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="session-state.html">Session State</a> — what survives a project change inside the singletons this host
registers, and what is rebuilt</li>
<li><a class="xref" href="login-and-auth.html">Login and Authentication</a> — the <code>Auth</code> section this host binds, and the cookie scheme and
fallback policy it registers when the gate is on</li>
<li><a class="xref" href="../conventions/rendering-canvas-web-service.html">Rendering Canvas on Web Service Application</a> — the canvas hub this host maps, and the per-connection
display engine behind it</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,428 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Session State | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Session State | HiAPI-C# 2025 ">
<link rel="icon" href="../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../index.html">
<img id="logo" class="svg" src="../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-SessionState">
<h1 id="session-state">Session State</h1>
<p>Session state is what the web client still holds after the user moves from one screen to the next. It
owns no route: the mechanism lives in the shell that wraps every route under <code>/</code> — a router outlet
inside a <code>&lt;keep-alive&gt;</code> keyed on a project epoch — together with four Pinia stores, a family of
module-level composables that deliberately sit outside the page lifecycle, and three browser storage
keys. Every value in the client answers the same three questions: does it survive a navigation, does
it survive a project change, and does it survive a browser reload.</p>
<h2 id="the-project-epoch">The Project Epoch</h2>
<p><code>MainLayout.vue</code> renders the routed page inside a <code>&lt;keep-alive&gt;</code> whose <code>:key</code> is <code>projectEpoch</code>. That
epoch is a plain integer <code>ref</code> declared in the layout component itself — it is not a store value, it
is not provided to anything, and nothing outside the file reads it. Two watchers in the same file are
the only writers, and each adds one to it: a watcher on the project store's <code>projectPath</code>, and a
watcher on the project store's <code>projectVersion</code>.</p>
<p>The key sits on the <code>&lt;keep-alive&gt;</code> element rather than on the page component inside it. Changing it
therefore discards the cache itself along with every page instance held in it and builds a fresh one,
which is why a page may rely on its own mount hook and never watch the project: both Control-Tree
pages call their host's <code>initialize()</code> from <code>onMounted</code>, and that runs once per project rather than
once per visit.</p>
<p>The two inputs move for different reasons.</p>
<ul>
<li><strong><code>projectPath</code></strong> is hydrated once at boot by the store's status fetch, assigned by New, Load and
Save As from the response each returns, cleared outright by Close, and driven thereafter by a
watcher inside the project store on the shared execution-status hub's status payload. A project
change made in another tab, from a different browser, or outside the browser altogether therefore
bumps the epoch here as well — and so, once, does the boot hydration on a service that already has
a project open.</li>
<li><strong><code>projectVersion</code></strong> is a counter the store raises in exactly one place: ReLoad. The backend raises
its project-changed event with the path it already had, so the broadcast cannot be told apart from
a no-op; the client-side counter is what makes a reload remount.</li>
</ul>
<p>Save on the current path moves neither, so it does not remount. The epoch only counts up, and because
it lives in the layout it starts again at zero on a browser reload — nothing reads its value, only its
transitions.</p>
<h2 id="what-a-navigation-costs">What a Navigation Costs</h2>
<p>The <code>&lt;keep-alive&gt;</code> carries no <code>include</code>, <code>exclude</code> or <code>max</code>, so every page reached under <code>/</code> is cached
until the epoch changes. Leaving a route deactivates its component instead of unmounting it, and
returning re-activates the same instance with its in-flight edits and its Control-Tree host intact.
Scroll offsets are not part of that: the router's <code>scrollBehavior</code> resolves every navigation to the
top of the page, and nothing saves an inner scroll position across a deactivation. The two routes
declared outside the layout — the login screen and the not-found catch-all — are not cached at all.</p>
<p>Three consequences the pages are written around:</p>
<ul>
<li>A cached page's watchers keep firing while another page is showing. The Control-Tree host compares
the current route name against the one its scope owns before it writes the <code>?tree=</code> query back, so
an off-screen host cannot rewrite the visible page's URL.</li>
<li>A global listener has to be dropped on deactivation, not on unmount. The Execution transport binds
its F5F8 shortcuts on mount and on activation and removes them on deactivation and unmount, so on
any other page F5 falls back to the browser's own reload instead of starting the run.</li>
<li>The Execution, General Setup and Tool House pages each track their own activation in a flag that
feeds the rendering canvas's <code>active</code> prop. Such a page keeps its canvas mounted and its connection
open while the backend engine stops rendering for it.</li>
</ul>
<p>The File Explorer page adds a route-leave guard that settles its editor buffer — flushing a pending
auto-save, or prompting before discarding manual edits — and can refuse the navigation.</p>
<h2 id="the-stores">The Stores</h2>
<p>Four Pinia stores are created against the single Pinia instance built at boot. None of them is inside
the <code>&lt;keep-alive&gt;</code>, so a project change resets none of them; only a browser reload rebuilds them.</p>
<ul>
<li><strong>project</strong> holds <code>projectPath</code>, <code>projectVersion</code>, the admin and project directories, a loading flag
and the <code>hasProject</code> computed. Its actions post to the project endpoints and each throws a typed busy
error on HTTP 409, which the backend returns when another project file operation is already running —
the request is refused rather than queued. The store also owns the subscription to the shared
execution-status hub and the watcher that adopts the path that hub broadcasts.</li>
<li><strong>appState</strong> holds the debug flag (initialised from whether the page is served from <code>localhost</code>), the
physics-options and physics-licensed flags, the language code with the list of codes the server
offers, and the Execution division-visibility record. <code>loadServerPreferences</code>, called from the
layout's mount hook, hydrates those three groups in independently guarded steps, so a failing
endpoint leaves the rest usable. A division-flag write is applied optimistically and rolled back on
failure, with a monotonic sequence number so a late response cannot overwrite newer state. Its
<code>setMessage</code> is a forwarder into the footer's foreground channel.</li>
<li><strong>auth</strong> holds whether the login gate is enabled, whether this session is authenticated, the user
name, the version string the login screen shows, and a ready flag set once the first status probe
resolves. Every transition re-drives the global hub gate described below.</li>
<li><strong>routineProgress</strong> backs the footer's two channels. The foreground channel keeps the latest entry
plus a history capped at one hundred entries, which the footer's history button lists; the background
channel is a single in-flight job with a message and an optional fraction and keeps no history at
all, so it disappears when the job ends. A boot patch mirrors every Quasar toast into the foreground
channel, so a toast stays reviewable after it fades.</li>
</ul>
<h2 id="state-outside-the-stores">State Outside the Stores</h2>
<p>A second family of state lives at module scope in composables rather than in a store. The module is
evaluated once per page load and its <code>ref</code>s are created there, so every caller of the composable
receives the same objects and nothing in a component's lifecycle creates or destroys them.</p>
<p>Three of these hold project data and share one lifecycle idiom — <code>useSpindleCapability</code>,
<code>useSoftNcRunner</code> and <code>useToolHouse</code>. Each exposes an idempotent <code>ensureInstalled()</code> that, on its first
call only, opens a detached effect scope and watches <code>hasProject</code>: the state reloads when a project
appears and is cleared when one closes. Every panel that reads the singleton calls <code>ensureInstalled()</code>
in its own setup, and every call after the first does nothing.</p>
<p>The consequence is the one that matters when reading a panel on screen: <strong>a mounted panel is not
refreshed by a change made elsewhere.</strong> A project change unmounts and rebuilds every page, but it does
not re-read a singleton — the installer is already installed, and the watch it installed is on a
boolean that the load and new actions leave true. What each singleton holds is replaced only by an
action on the composable itself, or by a consumer that asks for it explicitly: the Tool House page's
mount hook calls the composable's <code>reload()</code>, while the spindle and controller panels call
<code>ensureInstalled()</code> alone. The controller branch is the sharpest case. Its leaf panels fetch their own
tables on mount and the tree builder re-fetches the runner snapshot it grows children from, but the
shared snapshot the panels gate on — the brand, the chain axes, the per-group presence flags and the
object key — is replaced only by a brand switch, an Object-Management install, or a close.</p>
<p>The remaining module singletons carry no project data and exist to join components that are not in one
another's tree: the Execution runtime flags the run page publishes for the nav bar, the sentence cursor
shared by the Program file panel and the syntax view, the strip-chart group's reload tick and hovered
x label, the two cycle-chart cursor marks the sim and sensor chart groups share, the parked cross-panel
line jump, the transport's shared reset flag, and the shared-hub registry with its auth gate.</p>
<h2 id="device-local-state">Device-Local State</h2>
<p>Three <code>localStorage</code> keys hold state belonging to one browser profile on one machine. None of them is
written into the project file and none is sent to the server.</p>
<ul>
<li>The tree-page view preferences key holds, per tree page, the column-visibility record and the two
left-dock row states; and, shared across both pages, the dock pixel widths, the editor row height,
the strip/step ratio, the two chart legend widths, the strip x-axis mode, the per-panel resize
weights, the Control-Tree expansion list, the last selection per page, the two runner-suit
checkboxes, and the three device-local panel switches for the 3D canvas, the CWE canvas and the
sentence syntax view. Writes are debounced, and two earlier key names are read once as migration
seeds when the current key is absent.</li>
<li>The locale key is a paint-time hint only: the boot sequence applies it before mount so the first
frame is already translated, then reconciles against the server value, which wins.</li>
<li>The File Explorer key holds whether its editor pane is shown, the pane split, the auto-save switch
and the sort order.</li>
</ul>
<p>What is <strong>not</strong> device-local is easy to mistake for it. The physics-options switch, the interface
language and the Execution division-visibility flags live in the server's <code>UserConfig</code>, held by a
singleton service and written to an XML file in the service's working directory. They are per install,
not per browser: two browsers pointed at the same service read and write the same values. The
machining project is server-side in the same way — one project is loaded at a time and every connected
browser sees it, which is why the status-hub broadcast is what keeps a second tab honest.</p>
<p>The URL carries the rest: the route, the <code>?tree=</code> selection on the two Control-Tree pages, the browsed
path on the File Explorer route, and the tab segments on the Tool House and Controller routes. Those
come back on a reload from the address bar, and when a Control-Tree page is opened with no <code>?tree=</code> the
host lands on the stored last selection for that page, falling back to the page's root when the stored
id is not in the current project's tree.</p>
<table>
<thead>
<tr>
<th>What</th>
<th>Navigation</th>
<th>Project change</th>
<th>Browser reload</th>
</tr>
</thead>
<tbody>
<tr>
<td>A page's own component state, including its Control-Tree host</td>
<td>survives</td>
<td>rebuilt</td>
<td>rebuilt</td>
</tr>
<tr>
<td>The four Pinia stores</td>
<td>survives</td>
<td>survives</td>
<td>rebuilt</td>
</tr>
<tr>
<td>Module-singleton composables</td>
<td>survives</td>
<td>survives</td>
<td>rebuilt</td>
</tr>
<tr>
<td>The <code>localStorage</code> preferences</td>
<td>survives</td>
<td>survives</td>
<td>survives</td>
</tr>
<tr>
<td>Route, <code>?tree=</code>, browsed path and tab segments</td>
<td>this is what changes</td>
<td>survives</td>
<td>survives</td>
</tr>
<tr>
<td>Server preferences and the loaded project</td>
<td>survives</td>
<td>server-side</td>
<td>survives</td>
</tr>
</tbody>
</table>
<h2 id="signalr-connections">SignalR Connections</h2>
<p>Most hubs are shared singletons built by one factory: the execution-status hub, the CL strip hub, and
the four session-message sinks for shell, NC diagnostic, step diagnostic and NC-manipulation
diagnostic messages. There is one connection per hub for the whole application, reference-counted by
consumer. A component registers as a consumer when it calls the hub's composable and releases on its
own unmount; when the count reaches zero the teardown waits out a short grace window, so the
remount a project change forces — every consumer dropping and re-adding within the same render flush —
keeps its connections rather than renegotiating them.</p>
<p>The project store takes out a consumer registration on the execution-status hub when the store is
created and never releases it itself, so that hub keeps a consumer no matter which page is showing.</p>
<p>A global gate sits in front of all of them. While the optional login gate is enabled and the session is
not authenticated — and while auth status is still resolving — hubs stay idle rather than negotiating
into a 401. A logout or an intercepted 401 closes every connection; permitting again reconnects the
ones a caller had asked for, without the caller asking twice. Reconnection is a dense burst of
attempts, then a fixed cadence, then a slow tail that never gives up, and tab visibility or window
focus starts a fresh burst immediately.</p>
<p>Two kinds of connection are per instance rather than shared:</p>
<ul>
<li>Each rendering canvas opens its own connection to the rendering hub on mount and stops it on unmount.
Because pages are cached, navigating away does not close one, and whether the backend engine stops
drawing for it depends on the host. The Execution, General Setup and Tool House pages hand their
activation flag to the canvas's <code>active</code> prop, which pauses the engine while the page sits off-screen;
the Controller, Machine Tool and Mech Builder canvases and the File Explorer's STL preview bind no
such prop, and the Execution page's CWE canvas pins it true, so all of those keep rendering until
something unmounts them. Collapsing a panel does close one, unless that panel keeps its content
mounted: the Execution page's 3D canvas panel does, so it survives a collapse, while the CWE panel
beside it does not and its canvas connection goes with it.</li>
<li>The cleanup hub is opened once per Control-Tree host and once each on the Controller and Tool House
pages. Its composable also holds the set of IndexService keys that owner registered; on unmount it
posts a removal for every one of them and then stops the connection, so a teardown releases the
server-side objects the page had indexed even if the hub never connected.</li>
</ul>
<p>A shared connection therefore closes when its last consumer has been gone longer than the grace window,
or when the auth gate shuts. A per-instance connection closes when the component owning it unmounts —
which is what a project change does to every page at once, while the shared hubs ride that same remount
out inside their grace window.</p>
<h2 id="boot-order">Boot Order</h2>
<p><code>App.vue</code> renders nothing but the router outlet and holds the wiring that must run exactly once per
page load: subscribe the project hub, then fetch project status. Both sit behind a watcher on the auth
predicate with a latch rather than behind a mount hook, because auth status may still be resolving at
first paint. The router's own guard hydrates auth status before the first navigation and fails open, so
a status hiccup cannot lock out an installation that has no login gate. The layout's mount hook then
hydrates the server preferences. A successful sign-in navigates with a full page load rather than a
router push, so the whole sequence runs again with the authentication cookie present.</p>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../index.html">HiNC App Anatomy</a> for git repository links.</p>
<h3 id="web-application">Web Application</h3>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/layouts/MainLayout.vue</code> — the project epoch, its two watchers, the keyed
<code>&lt;keep-alive&gt;</code> around the router outlet, the footer, and the server-preference hydration call.</li>
<li><code>wwwroot-src/src/App.vue</code> — the auth-gated one-shot wiring: the project hub subscription and the
first status fetch.</li>
<li><code>wwwroot-src/src/stores/index.ts</code> — the Pinia instance the four stores are created against.</li>
<li><code>wwwroot-src/src/stores/project.ts</code> — the project path and version, the directories, the file actions
and their busy error, the hub subscription, and the watcher that adopts the broadcast path.</li>
<li><code>wwwroot-src/src/stores/appState.ts</code> — the debug and physics flags, the language state, the Execution
division config with its optimistic write and sequence guard, and the footer forwarder.</li>
<li><code>wwwroot-src/src/stores/auth.ts</code> — the login-gate state and the hub gate it drives.</li>
<li><code>wwwroot-src/src/stores/routineProgress.ts</code> — the footer's foreground history and its single live
background job.</li>
<li><code>wwwroot-src/src/composables/useViewPrefs.ts</code> — the device-local view preferences: the shape, the
defaults, the migration seeds and the debounced write.</li>
<li><code>wwwroot-src/src/composables/useSoftNcRunner.ts</code> — the controller singleton: the shared runner
snapshot, the object key, and the install-once project watch.</li>
<li><code>wwwroot-src/src/composables/useSpindleCapability.ts</code> — the spindle singleton on the same lifecycle
idiom.</li>
<li><code>wwwroot-src/src/composables/useToolHouse.ts</code> — the tool-house singleton and the coalesced reload the
page calls on mount.</li>
<li><code>wwwroot-src/src/composables/useSharedHub.ts</code> — the shared-hub factory: consumer counting, the
teardown grace window, the auth gate, the retry schedule and the focus recovery.</li>
<li><code>wwwroot-src/src/composables/useExecutionStatusHub.ts</code> — the status, cursor and message payloads the
store and the footer read.</li>
<li><code>wwwroot-src/src/composables/useClStripHub.ts</code> — the strip snapshot and update counter the charts
watch.</li>
<li><code>wwwroot-src/src/composables/useSessionSinkHub.ts</code> — the four message sinks and their
notify-and-re-pull contract.</li>
<li><code>wwwroot-src/src/composables/useCleanupHub.ts</code> — the per-instance cleanup connection and the key set
it releases on unmount.</li>
<li><code>wwwroot-src/src/composables/useExecutionTransport.ts</code> — the activation-scoped keyboard shortcuts and
the shared reset flag.</li>
<li><code>wwwroot-src/src/composables/useExecutionRuntime.ts</code> — the runtime flags the run page publishes.</li>
<li><code>wwwroot-src/src/composables/useSentenceCursor.ts</code> — the shared source cursor and its install-once
watch on step selection.</li>
<li><code>wwwroot-src/src/composables/useStripChartGroup.ts</code> — the group reload tick and the hovered x label.</li>
<li><code>wwwroot-src/src/composables/useCycleSyncMark.ts</code> — the per-group cycle-chart cursor mark the sim
and sensor charts share.</li>
<li><code>wwwroot-src/src/composables/useConversionJump.ts</code> — the parked cross-panel jump.</li>
<li><code>wwwroot-src/src/components/RenderingCanvas.vue</code> — the per-instance rendering connection, its mount
and unmount lifecycle, and the mounted guard that survives a mid-await teardown.</li>
<li><code>wwwroot-src/src/components/panels/PanelExpansion.vue</code> — the expansion row, and the keep-mounted flag
that decides whether a collapse unmounts its content.</li>
<li><code>wwwroot-src/src/components/AppFooter.vue</code> — the two footer channels and the recent-message list.</li>
<li><code>wwwroot-src/src/components/FileExplorer.vue</code> — the explorer's own device-local preference key.</li>
<li><code>wwwroot-src/src/components/controlTree/useControlTreeHost.ts</code> — the per-page host: the route-name
guard on the <code>?tree=</code> sync, the landing selection, and the cleanup registrations.</li>
<li><code>wwwroot-src/src/pages/ExecutionPage.vue</code> — the activation flag, the once-per-project initialization,
and the panel that keeps the 3D canvas mounted through a collapse.</li>
<li><code>wwwroot-src/src/pages/GeneralSetupPage.vue</code> — the same lifecycle for the equipment host.</li>
<li><code>wwwroot-src/src/pages/ToolHousePage.vue</code> — the page that reloads its singleton explicitly on mount.</li>
<li><code>wwwroot-src/src/pages/ControllerPage.vue</code>, <code>wwwroot-src/src/pages/MachineToolPage.vue</code>,
<code>wwwroot-src/src/pages/MechBuilderPage.vue</code> and <code>wwwroot-src/src/components/StlPreviewPane.vue</code>
the canvases mounted with no <code>active</code> binding.</li>
<li><code>wwwroot-src/src/components/execution/StepVolumePanel.vue</code> — the CWE canvas that pins <code>active</code> true
and is stopped only by its own unmount.</li>
<li><code>wwwroot-src/src/pages/FileExplorerPage.vue</code> — the route-leave guard that settles the editor buffer.</li>
<li><code>wwwroot-src/src/pages/LoginPage.vue</code> — the full-page navigation after a successful sign-in.</li>
<li><code>wwwroot-src/src/router/index.ts</code> — the auth guard that hydrates status before the first navigation.</li>
<li><code>wwwroot-src/src/router/routes.ts</code> — which routes render inside the layout and which do not.</li>
<li><code>wwwroot-src/src/boot/auth.ts</code> — the fetch wrapper that turns a 401 into a logged-out store and a
bounce to the login screen.</li>
<li><code>wwwroot-src/src/boot/i18n.ts</code> — the cached-locale fast path and the server reconcile.</li>
<li><code>wwwroot-src/src/boot/routine-toast.ts</code> — the toast mirror into the footer channel.</li>
<li><code>wwwroot-src/src/i18n/index.ts</code> — the locale storage key and the single place a locale change is
applied.</li>
<li><code>wwwroot-src/src/api/preference.ts</code> — the typed wrappers over the preference endpoints the app-state
store hydrates from.</li>
<li><code>Environments/ProjectController.cs</code> — the status, new, load, save, save-as, reload and close
endpoints, and the conflict reply that becomes the client's busy error.</li>
<li><code>Environments/PreferenceController.cs</code> — the endpoints behind the server-held preferences.</li>
<li><code>Environments/UserConfig.cs</code> — the persisted user configuration: the physics switch, the language
code, the graphic-cache limits, the step-present list and the Execution division config.</li>
<li><code>Environments/UserService.cs</code> — the singleton that owns that configuration and writes it to file.</li>
<li><code>Execution/ExecutionStatusService.cs</code> — the broadcast that carries a project change to every
connected browser.</li>
<li><code>Program.cs</code> — where the user-configuration service is registered and its file path fixed.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="control-tree.html">Control Tree</a> — the per-page host this epoch destroys and rebuilds, and the selection it
restores from device-local storage</li>
<li><a class="xref" href="program-and-hosting.html">Program and Hosting</a> — how the SPA is served and hosted, and where the server-side
singletons this page reads actually live</li>
<li><a class="xref" href="../shell/main-panel.html">Main Panel</a> — the shell around the router outlet: the menu bar that fires the project
actions and the footer that shows their outcome</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,43 @@
<div id="sidetoggle">
<div>
<div class="sidefilter">
<form class="toc-filter">
<span class="glyphicon glyphicon-filter filter-icon"></span>
<span class="glyphicon glyphicon-remove clear-icon" id="toc_filter_clear"></span>
<input type="text" id="toc_filter_input" placeholder="Filter by title" onkeypress="if(event.keyCode==13) {return false;}">
</form>
</div>
<div class="sidetoc">
<div class="toc" id="toc">
<ul class="nav level1">
<li>
<a href="index.html" name="" title="Overview">Overview</a>
</li>
<li>
<a href="control-tree.html" name="" title="Control Tree">Control Tree</a>
</li>
<li>
<a href="tree-ids-and-routes.html" name="" title="Tree Ids and Routes">Tree Ids and Routes</a>
</li>
<li>
<a href="session-state.html" name="" title="Session State">Session State</a>
</li>
<li>
<a href="i18n.html" name="" title="Internationalization">Internationalization</a>
</li>
<li>
<a href="login-and-auth.html" name="" title="Login and Auth">Login and Auth</a>
</li>
<li>
<a href="log-viewer.html" name="" title="Log Viewer">Log Viewer</a>
</li>
<li>
<a href="program-and-hosting.html" name="" title="Program and Hosting">Program and Hosting</a>
</li>
</ul>
</div>
</div>
</div>
</div>
@@ -0,0 +1,2 @@
{"items":[{"name":"Overview","href":"index.html","topicHref":"index.html"},{"name":"Control Tree","href":"control-tree.html","topicHref":"control-tree.html"},{"name":"Tree Ids and Routes","href":"tree-ids-and-routes.html","topicHref":"tree-ids-and-routes.html"},{"name":"Session State","href":"session-state.html","topicHref":"session-state.html"},{"name":"Internationalization","href":"i18n.html","topicHref":"i18n.html"},{"name":"Login and Auth","href":"login-and-auth.html","topicHref":"login-and-auth.html"},{"name":"Log Viewer","href":"log-viewer.html","topicHref":"log-viewer.html"},{"name":"Program and Hosting","href":"program-and-hosting.html","topicHref":"program-and-hosting.html"}]}
@@ -0,0 +1,508 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Tree Ids and Routes | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Tree Ids and Routes | HiAPI-C# 2025 ">
<link rel="icon" href="../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../index.html">
<img id="logo" class="svg" src="../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-TreeIdsAndRoutes">
<h1 id="tree-ids-and-routes">Tree Ids and Routes</h1>
<p>The web client addresses itself with two things: a vue-router path, and — on the two Control-Tree
pages — a <code>tree</code> query argument naming the selected tree node, as in
<code>/general-setup?tree=equipment/fixture/geometry</code>. This page is the reference for both surfaces: the
route table, the redirect-only paths that keep older bookmarks resolving, the id-migration chain,
and the resolver that decides which page owns a given <code>?tree=</code> id. The router runs in HTML5 history
mode, so every one of these paths is a real URL the server has to answer.</p>
<div class="IMPORTANT">
<h5>Important</h5>
<p>Nothing here promises that a Control-Tree id is stable. The guarantee is narrower and exact: an
id keeps <strong>resolving</strong>, because every regroup adds a migration hop to <code>migrateLegacyTreeId</code>
rather than rewriting the previous one. The ids themselves have moved repeatedly, and a page that
quotes one is quoting a value that can move again.</p>
</div>
<h2 id="the-route-table">The Route Table</h2>
<p><code>/</code> is the shell-layout record itself; every route below it except the login and catch-all records
is one of its children, so each renders inside the Main Panel's frame and menu bar.</p>
<table>
<thead>
<tr>
<th>Path</th>
<th>Route name</th>
<th>What it owns</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>/</code></td>
<td></td>
<td>The shell layout. It carries no page of its own; its empty child redirects to the Execution route.</td>
</tr>
<tr>
<td><code>/execution</code></td>
<td><code>execution</code></td>
<td>The run cockpit and the Execution Control Tree, whose selection rides <code>?tree=execution/…</code>.</td>
</tr>
<tr>
<td><code>/general-setup</code></td>
<td><code>general-setup</code></td>
<td>The equipment Control Tree, whose selection rides <code>?tree=equipment/…</code>.</td>
</tr>
<tr>
<td><code>/machine-tool</code></td>
<td><code>machine-tool</code></td>
<td>The machining chain on a canvas of its own, beside a left column that shows either a read-only identity panel or the chain's XML source — two states of one column, chosen with a GUI/XML toggle. A load button installs a chain file into the project. Carries no Page-menu entry and is reached by URL.</td>
</tr>
<tr>
<td><code>/controller/:tab?</code></td>
<td><code>controller</code></td>
<td>The superseded HardNcEnv controller surface. The optional segment mirrors the active tab.</td>
</tr>
<tr>
<td><code>/tool-house/:toolId(\d+)?/:tab?/:subtab?</code></td>
<td><code>tool-house</code></td>
<td>The tool library and the per-tool editor. The three optional segments carry the selected tool and the two nested tab levels.</td>
</tr>
<tr>
<td><code>/preference/log</code></td>
<td><code>preference-log</code></td>
<td>The Log Viewer, reached from the menu bar's <code>Show Log</code> button. It is not under <code>/util/</code>.</td>
</tr>
<tr>
<td><code>/util/file-explorer/:location(.*)*</code></td>
<td><code>util-file-explorer</code></td>
<td>The File Explorer. Its catch-all mirrors the browsed location — root display name followed by the relative segments — into the path.</td>
</tr>
<tr>
<td><code>/util/mech-builder</code></td>
<td><code>util-mech-builder</code></td>
<td>The Mechanism Builder.</td>
</tr>
<tr>
<td><code>/login</code></td>
<td><code>login</code></td>
<td>The sign-in form. It sits outside the shell layout and carries its own Quasar layout.</td>
</tr>
<tr>
<td><code>/:catchAll(.*)*</code></td>
<td></td>
<td>The 404 page, likewise outside the shell layout: it prints the unmatched full path and offers one button back to the Execution route.</td>
</tr>
</tbody>
</table>
<p>The menu bar's Page dropdown holds every one of these that is menu-reachable, in setup order: Tool
House, General Setup and Execution, then File Explorer and Mechanism Builder below a separator,
then the legacy Controller below a second one. The Log Viewer has a button of its own rather than a
menu entry, and <code>/machine-tool</code> has neither — it is reached by URL.</p>
<p>The Tool House route's tool segment is constrained to digits, but all three of its segments are
optional, so a non-numeric segment in the tool position is not rejected: the router skips the tool
parameter and reads that segment as the tab, which is what makes <code>/tool-house/cutter</code> a working tab
deep link with no tool named. The consequence is that the record absorbs three segments when the
first is a number and only two when it is not, and a URL carrying more than the record can absorb —
<code>/tool-house/abc/def/ghi</code> — falls through to the catch-all. An unrecognised tab name is not an error
either: the page canonicalises it to that level's default, as
<a href="#the-tool-house-translation">The Tool House Translation</a> sets out.</p>
<h2 id="redirect-only-paths">Redirect-Only Paths</h2>
<p>These records carry no component. They exist so that a link minted before the corresponding editor
moved into a Control Tree still lands on the editor.</p>
<table>
<thead>
<tr>
<th>Path</th>
<th>Lands on</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>/spindle-capability/:tab?</code></td>
<td><code>/general-setup?tree=equipment/spindle</code>, with <code>/&lt;tab&gt;</code> appended when the segment is one of <code>thermal</code>, <code>gear-shift</code>, <code>dry-run</code>, <code>power</code>, <code>torque</code>. An unrecognised segment is dropped and the branch root is selected.</td>
</tr>
<tr>
<td><code>/equipment/spindle</code></td>
<td><code>/general-setup?tree=equipment/spindle</code></td>
</tr>
<tr>
<td><code>/fixture/:rest(.*)*</code></td>
<td><code>/general-setup?tree=equipment/fixture</code> — any trailing path is discarded.</td>
</tr>
<tr>
<td><code>/workpiece/:rest(.*)*</code></td>
<td><code>/general-setup?tree=equipment/workpiece</code> — any trailing path is discarded.</td>
</tr>
<tr>
<td><code>/equipment/background-coolant</code></td>
<td><code>/general-setup?tree=equipment/background</code>. Background and Coolant are two separate tree nodes; the redirect picks Background.</td>
</tr>
<tr>
<td><code>/mission</code></td>
<td><code>/execution?tree=execution/mission</code></td>
</tr>
</tbody>
</table>
<p>The five spindle tab names are declared in <code>treeRoutes.ts</code> as <code>SPINDLE_TABS</code>, and imported by
<code>wwwroot-src/src/router/routes.ts</code> — which is the list the redirect tests its <code>:tab?</code> segment
against. The equipment tree does not import that constant: it spells the same five segments as
literals while building the <code>equipment/spindle/&lt;seg&gt;</code> children, so redirect and branch agree by
convention rather than through a shared list — renaming a spindle section is therefore an edit in
two places. The Tool House name lists below are the other case, genuinely shared.</p>
<h2 id="the-tree-query">The <code>?tree=</code> Query</h2>
<p>Selection and URL are synced two ways by the Control-Tree host, one instance per tree page.</p>
<p><strong>URL to selection.</strong> A watcher on the query drives <code>applyRouteSelection()</code>, and it is registered
<code>immediate</code>, so an id belonging to another page redirects on the first tick rather than after this
page's first tree build. The host also calls the same function once the tree has actually been
built, which is when a deep link can finally be honoured.</p>
<p><strong>Selection to URL.</strong> A watcher on the selected id replaces the <code>tree</code> argument, preserving the
rest of the query. It uses <code>router.replace</code>, so browsing the tree does not fill the browser's
history with one entry per node. The comparison that breaks the loop is made against the <strong>raw</strong>
query rather than the migrated form, which is what makes an older id canonicalise: opening
<code>?tree=controller</code> selects the node and then rewrites the URL to <code>?tree=equipment/controller</code>.</p>
<p>Neither watcher touches the URL unless the current route name is the page's own. The shell layout
keeps every visited page alive, so a host whose page is not showing keeps receiving route changes,
and without the guard it would write another page's URL. The selection watcher does one thing
before that guard: it records the id as this page's last selection. That ordering is load-bearing,
because a selection can move while its page is off screen — unticking a CSV or CL Controller
checkbox steps the equipment host off that node onto the equipment root, and the Preference menu
that carries those checkboxes is open on every page — and the landing selection has to have
followed it.</p>
<p>That same checkbox flip is a third writer of the <code>tree</code> argument, and the one place the guard is
absent. The equipment host watches both checkboxes, and when the standing selection is a node the
flip removes, it replaces <code>tree</code> with <code>equipment</code> — the rest of the query preserved — before
stepping the selection off. The route name is not consulted there, so the replacement goes to
whichever route is showing.</p>
<h3 id="what-applyrouteselection-does">What <code>applyRouteSelection</code> Does</h3>
<ol>
<li>The change is ignored unless this page owns the current route.</li>
<li>The raw query is migrated into today's id. If its first segment names a <strong>different</strong> page and
that segment is one of the known roots, the location is replaced with the route that owns it and
the pass stops.</li>
<li>An empty query falls back to the landing selection: the persisted last id for this page when
that id exists in the tree just built, otherwise the page's root node — which is always present
once the tree is built, so the editor column always has something to show.</li>
<li>An id the built tree does not contain is <strong>ignored</strong> — the current selection stands and the URL
keeps the unrecognised value until the next selection overwrites it. This is deliberate: the
redirect in step 2 fires only for known roots, because sending an unroutable id back to the same
page would loop.</li>
<li>Otherwise every ancestor prefix of the id is added to the expanded set so the node is visible,
and the selection is requested through the same gate a tree click uses. A panel holding unsaved
edits may refuse the switch, and a refusal re-points the URL at the selection that actually
stands.</li>
</ol>
<h3 id="the-id-the-url-names-before-the-tree-exists">The Id the URL Names, Before the Tree Exists</h3>
<p><code>treePathOfRoute()</code> — the raw query put through <code>migrateLegacyTreeId</code> — is not read only by the
selection logic. The equipment tree consults it while it is being <strong>built</strong>, to decide whether to
materialise the CSV Controller and CL Controller nodes: each is normally shown only when its
Preference checkbox is ticked, and a URL naming one reveals it regardless. That reveal has to
happen at build time precisely because step 4 above refuses an id the built tree lacks, so a
bookmark to a switched-off node would otherwise dead-end. Only the URL reveals a node this way; the
persisted last selection does not, so a node the user has just unticked while standing on it does
not resurrect itself.</p>
<h2 id="id-migration">Id Migration</h2>
<p><code>migrateLegacyTreeId</code> is one pass of ordered guards over the whole id; the first that matches
returns, and an id matching none is returned unchanged.</p>
<table>
<thead>
<tr>
<th>An id shaped like</th>
<th>Becomes</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>controller</code>, <code>controller/…</code></td>
<td><code>equipment/controller</code>, <code>equipment/controller/…</code></td>
</tr>
<tr>
<td><code>spindle</code>, <code>spindle/…</code></td>
<td><code>equipment/spindle</code>, <code>equipment/spindle/…</code></td>
</tr>
<tr>
<td><code>mission</code>, <code>mission/…</code></td>
<td><code>execution/mission</code>, <code>execution/mission/…</code></td>
</tr>
<tr>
<td><code>equipment/mission</code>, <code>equipment/mission/…</code></td>
<td><code>execution/mission</code>, <code>execution/mission/…</code></td>
</tr>
<tr>
<td>any id ending in <code>/contours/tray</code>, or containing <code>/contours/tray/</code></td>
<td>the same id with that segment spelled <code>fluting</code></td>
</tr>
</tbody>
</table>
<p>The last rule renames one segment in place and leaves its children — <code>baseline</code>, <code>flute-&lt;i&gt;</code>,
<code>side</code>, <code>bottom</code> — untouched, so a deep link into a flute contour survives the engine's Fluting
type naming. The two mission rules cover the two id shapes separately rather than chaining, because
the first matching guard returns.</p>
<p>The function is applied at four points, which is what makes the guarantee hold in practice:</p>
<ul>
<li>to the raw <code>?tree=</code> query, on every read;</li>
<li>to the persisted last selection, before it is looked up in the tree;</li>
<li>to the persisted list of expanded node ids, once when a host is constructed, so an older
expansion set still unfolds the branches it names;</li>
<li>as the first step of <code>routeForTreeId</code>, so the id-to-page resolver never sees an unmigrated id.</li>
</ul>
<p>Note what the first rule implies: a bare <code>?tree=controller</code> selects the <code>equipment/controller</code>
branch — the current controller editor on the General Setup page — and has nothing to do with the
<code>/controller</code> route, which is the separate legacy HardNcEnv surface.</p>
<h2 id="landing-an-id-on-its-page">Landing an Id on Its Page</h2>
<p><code>TREE_PAGE_ROOTS</code> lists the first segments that name a page: <code>execution</code>, <code>equipment</code> and
<code>toolhouse</code>. <code>spindle</code> is absent on purpose — migration folds it into <code>equipment/…</code> before any root
check runs, so no root check ever sees it.</p>
<p><code>routeForTreeId</code> migrates the id, then dispatches on its first segment:</p>
<ul>
<li><code>toolhouse</code> — the Tool House route, with the id's segments translated into path params (below).</li>
<li><code>equipment</code> — the General Setup route, carrying the migrated id as <code>?tree=</code>.</li>
<li>anything else — the Execution route, carrying the migrated id as <code>?tree=</code>. This is a
fall-through, not a test for <code>execution</code>, so an unrecognised id handed to this function lands on
the Execution page. Reaching it with a genuinely unroutable id is prevented upstream: the host
calls the function only for ids whose root is in <code>TREE_PAGE_ROOTS</code>.</li>
</ul>
<p>The returned location replaces the whole current location. Only the <code>tree</code> argument survives the
hop; any other query argument on the URL being redirected away from is dropped, and the Tool House
form carries no query at all.</p>
<h2 id="the-tool-house-translation">The Tool House Translation</h2>
<p>The Tool House page keeps its state in path segments rather than a query, so an id arriving from
another page has to be translated. <code>routeForTreeId</code> walks the segments of
<code>toolhouse/tool-&lt;n&gt;/&lt;tab&gt;/&lt;subtab&gt;</code>:</p>
<ul>
<li>segment two contributes <code>toolId</code> when it is <code>tool-</code> followed by an integer;</li>
<li>segment three contributes <code>tab</code> when it is one of <code>general</code>, <code>cutter</code>, <code>holder</code>, <code>clamping</code>,
<code>intelligent</code>;</li>
<li>segment four contributes <code>subtab</code> when it is one of the cutter sections — <code>material</code>, <code>profile</code>,
<code>contours</code>, <code>upper-beam</code>, <code>opt</code> — or one of the holder sections, <code>geometry</code> and <code>resolution</code>.</li>
</ul>
<p>Only the sub-tab step is nested inside the tab step: an unrecognised tab discards the sub-tab with
it, whereas the tool step is tested on its own, so a second segment that is not <code>tool-&lt;n&gt;</code> costs the
<code>toolId</code> param alone and the tab still translates. Everything deeper than the last segment the URL
can carry is discarded either way — an id pointing at a single flute contour under
<code>…/cutter/contours/fluting/…</code> lands on the nearest tab the URL can express. The same three name
lists are imported by the page itself, so the tab set and the translation cannot drift apart.</p>
<p>Once on the page, the tab segments are kept in step with the tabs by <code>useRouteTabs</code>, the composable
the Controller route uses as well. It gives each tab level one route param, a valid-name set and a
default; a nested level may declare which parent values make it apply, and while its parent is
something else the level is <strong>dormant</strong> — its segment is dropped from the URL while its reference
quietly remembers the last value, so returning to that parent tab returns to the sub-tab it had.
The Tool House sub-tab level applies under <code>cutter</code> and <code>holder</code> only, and both its valid set and
its default depend on which of the two is showing. A segment the level does not recognise resolves
to that default rather than failing. Canonicalisation runs on the first mount and on every
keep-alive re-entry, which is why a bare <code>/controller</code> becomes <code>/controller/coordinate-table</code> and a
bare <code>/tool-house</code> acquires its tab segments. The tool segment is not one of the composable's
levels: the page fills it in itself, adopting the URL's tool when the project holds it and the first
tool in the list otherwise, then replacing the URL with what it adopted. That resolver reacts to the
<code>toolId</code> param and to the tool list rather than to page activation, so it also re-points the URL
when the list changes underneath it.</p>
<p>The Tool House page never reads or writes <code>?tree=</code>. A <code>toolhouse/…</code> id reaches it only by way of
the redirect above, which is issued by one of the two tree pages' hosts.</p>
<h2 id="reading-an-id">Reading an Id</h2>
<p>An id's first segment names the page that owns it, and therefore the folder that documents it:</p>
<table>
<thead>
<tr>
<th><code>?tree=</code> root</th>
<th>Route</th>
<th>Documented under</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>execution/…</code></td>
<td><code>/execution</code></td>
<td><a class="xref" href="../execution/index.html">Execution Page</a></td>
</tr>
<tr>
<td><code>equipment/…</code></td>
<td><code>/general-setup</code></td>
<td><a class="xref" href="../general-setup/index.html">General Setup Page</a></td>
</tr>
<tr>
<td><code>toolhouse/…</code></td>
<td><code>/tool-house/…</code></td>
<td><a class="xref" href="../tool-house/index.html">Tool House Page</a></td>
</tr>
</tbody>
</table>
<p>The remaining segments are the branch path, one segment per level of the tree, and the branch's own
page sits in that folder — <code>equipment/workpiece/material/cutting-parameter</code> is the Cutting Parameter
item of the Workpiece branch on the General Setup page. Most segments are kebab-case role names;
the two list-backed branches, Mission entries and Program files, use a positional index instead.</p>
<p>Every segment comes from the <strong>node id</strong>, never from the label the user reads: the General Setup
page's root node is labelled <code>General Setup</code> and carries the id <code>equipment</code>. A label is a display
string — the translation of the node's <code>labelKey</code> where it has one, and otherwise raw data such as
an NC file name or a mission command's user-typed title, as <a class="xref" href="control-tree.html">Control Tree</a>
sets out — while an id is neither translated nor taken from data. Two consequences follow. A
<code>?tree=</code> link is locale-independent, and an id can never be read off what is on screen.</p>
<h2 id="titles">Titles</h2>
<p>A route's <code>meta.title</code> holds an i18n <strong>key</strong>, such as <code>routes.execution</code>, not a title. The router
resolves it through the active locale in an <code>afterEach</code> hook and sets the document title to the
translated name followed by <code> - HiNC</code>; a route with no key — the catch-all — leaves the bare
product name. The sibling <code>name:</code> is the route id used by every programmatic navigation and is
never translated; the two must not be confused, which is why the route table above lists them side
by side.</p>
<p>The same resolver is handed to the i18n module through <code>registerRetitle</code>, so that switching
language re-titles a parked tab without a navigation. The registration is indirect because the
router already imports the i18n module for its translation function, and importing back would close
a cycle.</p>
<h2 id="serving-these-urls">Serving These URLs</h2>
<p>History mode means the browser sends the whole path to the server on a refresh or a pasted link, so
the back end maps two SPA fallbacks. The bare fallback carries an implicit constraint that rejects
any URL whose last segment contains a dot, which is exactly the shape a File Explorer deep link
takes, since those mirror real file names. An explicit fallback for the explorer's path is
therefore mapped ahead of it with no such constraint. Static assets cannot be shadowed by either,
because static-file serving runs earlier in the pipeline. Both fallbacks allow anonymous access, so
the SPA itself always loads and the sign-in decision is made in the client.</p>
<p>That decision is a navigation guard: it hydrates the authentication status once, lets every
navigation through when the back end reports the feature disabled, and otherwise sends an
unauthenticated visitor to the login route with the original full path — <code>?tree=</code> included — parked
in a <code>redirect</code> query. A successful sign-in replays that path as a full page load rather than a
router navigation, so the application re-initialises with the cookie present. A visitor who is
already signed in, or who arrives while the feature is off, is sent straight back out of the login
route to the same parked path.</p>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../index.html">HiNC App Anatomy</a> for git repository links.</p>
<h3 id="web-application">Web Application</h3>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/router/routes.ts</code> — the route table, the redirect-only records, the login route
outside the shell layout, and the trailing catch-all.</li>
<li><code>wwwroot-src/src/router/treeRoutes.ts</code><code>migrateLegacyTreeId</code>, <code>TREE_PAGE_ROOTS</code>,
<code>routeForTreeId</code>, the three tab-name lists it shares with the Tool House page, and
<code>SPINDLE_TABS</code>, which only the spindle redirect reads.</li>
<li><code>wwwroot-src/src/router/index.ts</code> — the history-mode router factory, the authentication guard,
and the retitle hook registered with the i18n module.</li>
<li><code>wwwroot-src/src/components/controlTree/useControlTreeHost.ts</code> — the tree-page host:
<code>rawTreePathOfRoute</code>, <code>treePathOfRoute</code>, the two selection/URL watchers and the unguarded
controller-checkbox watcher beside them, <code>landingSelection</code>, <code>applyRouteSelection</code> and
<code>syncUrlBack</code>, plus the build-time reveal of a URL-named CSV or CL controller node.</li>
<li><code>wwwroot-src/src/composables/useRouteTabs.ts</code> — the tab-to-param sync used by the Tool House and
Controller routes, including dormant levels and the keep-alive re-entry canonicalisation.</li>
<li><code>wwwroot-src/src/pages/ToolHousePage.vue</code> — consumes the shared tab lists, resolves the tool from
its path param, and navigates by route name when the tool changes.</li>
<li><code>wwwroot-src/src/pages/FileExplorerPage.vue</code> — mirrors the browsed location into the route's
catch-all param and back.</li>
<li><code>wwwroot-src/src/pages/MachineToolPage.vue</code> — the component behind the URL-only machine-tool
route.</li>
<li><code>wwwroot-src/src/pages/NotFoundPage.vue</code> — the catch-all component, carrying its own layout.</li>
<li><code>wwwroot-src/src/pages/LoginPage.vue</code> — reads the guard's <code>redirect</code> argument and replays it.</li>
<li><code>wwwroot-src/src/composables/useViewPrefs.ts</code> — the browser-local store holding the per-page last
selected id and the expanded-node list that migration is applied to.</li>
<li><code>wwwroot-src/src/components/AppMenuBar.vue</code> — the menu entries that navigate by route name.</li>
<li><code>wwwroot-src/src/i18n/en/routes.ts</code> — the English strings the <code>meta.title</code> keys resolve to.</li>
<li><code>wwwroot-src/src/i18n/index.ts</code><code>registerRetitle</code> and the locale switch that calls it.</li>
<li><code>wwwroot-src/quasar.config.ts</code> — selects history mode and names the build output folder the
server serves.</li>
<li><code>Program.cs</code> — the two SPA fallbacks, the explorer-specific one ahead of the bare one.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="control-tree.html">Control Tree</a> — the tree these ids name: how a node id is formed and what a branch
is made of</li>
<li><a class="xref" href="../shell/main-panel.html">Main Panel</a> — the shell whose menus navigate to these routes and whose keep-alive
cache makes the route-name guards necessary</li>
<li><a class="xref" href="../execution/index.html">Execution Page</a> — the busiest consumer of <code>?tree=</code>, and the page a fall-through id lands
on</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,202 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Bottom Message Bar | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Bottom Message Bar | HiAPI-C# 2025 ">
<link rel="icon" href="../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../index.html">
<img id="logo" class="svg" src="../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-MessageSection">
<h1 id="bottom-message-bar">Bottom Message Bar</h1>
<p>The Bottom Message Bar displays application-level notifications at the bottom of the Main Panel. This is the <strong>UI Error Notification</strong> channel — see <a class="xref" href="../../technique/api-foundations/message-management.html">Message Management</a> for how it fits into the overall message architecture.</p>
<h2 id="message-handling">Message Handling</h2>
<p>The Bottom Message Bar is connected to <a class="xref" href="../../api/Hi.Common.Messages.MessageBoardUtil.html">MessageBoardUtil</a> (or <code>ILogger</code> with level-filtered treatment). When a notification is triggered:</p>
<ol>
<li>The <code>Brief Message Text Field</code> content is updated</li>
<li>The message is appended to the daily log file at <code>logs/log-{DateTime.Now:yyyy-MM-dd}.txt</code></li>
</ol>
<p>The second step belongs to the logging path rather than to the bar, and only one client shares it.
The WPF brief field is fed by a logger provider, so a message that reaches the field has already
gone to the file sink. The web footer is fed by the routine-progress store instead: what lands there
is client-side, and it reaches the server's daily log only when the server logged it too — no part
of the shell posts a message to the log-append endpoint.</p>
<h3 id="message-types">Message Types</h3>
<p>The <a class="xref" href="../../api/Hi.Common.Messages.MessageFlag.html">MessageFlag</a> determines the display behavior:</p>
<table>
<thead>
<tr>
<th>Flag</th>
<th>Display Behavior</th>
</tr>
</thead>
<tbody>
<tr>
<td><a class="xref" href="../../api/Hi.Common.Messages.MessageFlag.html#Hi_Common_Messages_MessageFlag_Exception">Exception</a></td>
<td>Alert style, shown in Message Bar</td>
</tr>
<tr>
<td><a class="xref" href="../../api/Hi.Common.Messages.MessageFlag.html#Hi_Common_Messages_MessageFlag_Warning">Warning</a> and above</td>
<td>Shown in Message Bar</td>
</tr>
<tr>
<td><a class="xref" href="../../api/Hi.Common.Messages.MessageFlag.html#Hi_Common_Messages_MessageFlag_Info">Info</a> and below</td>
<td>Logged only, not shown in Message Bar</td>
</tr>
</tbody>
</table>
<div class="NOTE">
<h5>Note</h5>
<p>When the message is an <a class="xref" href="https://learn.microsoft.com/dotnet/api/system.exception">Exception</a>, the brief message shows <a class="xref" href="https://learn.microsoft.com/dotnet/api/system.exception.message">Message</a> while the full exception details are logged to file.</p>
</div>
<h2 id="platform-specific-layouts">Platform-Specific Layouts</h2>
<h3 id="wpf-application">WPF Application</h3>
<p>The WPF version uses a fixed bottom bar:</p>
<ul>
<li><strong>Message Section Bottom Bar</strong>
<ul>
<li>Brief Message Text Field (selectable for copy)</li>
<li>Show Log Button</li>
</ul>
</li>
</ul>
<h3 id="web-application">Web Application</h3>
<p>The web version docks a single dense bar along the bottom of the layout — <code>AppFooter.vue</code>, not a
stack of toasts:</p>
<ul>
<li><strong>Routine Progress Footer Bar</strong>
<ul>
<li>Brief Message Text Field — the latest foreground message behind its severity glyph, reading
<code>Ready</code> while there is none, with the full text and caption on hover</li>
<li>Recent Messages Button — a history menu over the capped foreground list, with a clear action</li>
<li>Session Status Strip — the live execution cursor and latest session message, rendering nothing
at all until there is activity</li>
<li>Background Progress Zone — one in-flight job's spinner or progress ring and its message, which
disappears when the job ends</li>
</ul>
</li>
</ul>
<p>Transient notifications are a second channel beside the bar rather than the bar itself: toasts are
Quasar notifications, anchored bottom-right and auto-hiding on a timeout that does not vary with
severity. A boot patch mirrors every one of them into the footer's foreground history, which is what
keeps a toast reviewable after it fades.</p>
<h2 id="show-log-button">Show Log Button</h2>
<p>The <code>Show Log</code> button is the only entrance to the log screen, and the two clients answer it
differently.</p>
<p>In the web application it is a router link in the menu bar's right-hand group — not on this bar —
and it opens the Log Viewer as a full page inside the shell. See
<a class="xref" href="../platform/log-viewer.html">Log Viewer Page</a> for that screen's toolbar, its auto-refresh interval, its
copy and download actions and the states of its text area.</p>
<p>In the WPF client the button sits at the right end of this bar. It looks for the current day's file
under the client's own <code>logs</code> folder and, when it is there, opens it with whatever application the
operating system associates with the file type; when it is not, it reports that in a message box
instead of opening anything.</p>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="main-panel.html">Main Panel</a> — the window frame this bar is docked in</li>
<li><a class="xref" href="session-message-panel.html">Session Message Panel</a> — the session-scoped message surface, as distinct from this app-level one</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,134 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>App Shell | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="App Shell | HiAPI-C# 2025 ">
<link rel="icon" href="../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../index.html">
<img id="logo" class="svg" src="../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-Shell">
<h1 id="app-shell">App Shell</h1>
<p>The frame every route renders inside. <code>MainLayout.vue</code> is mounted once and stays mounted: the menu
bar across the top, the notification bar along the bottom, and a router outlet between them that
each page fills. Nothing here belongs to one screen, and nothing here disappears when the route
changes.</p>
<p>Ordered outward from the window frame: the panel that hosts every route, then the bars it docks,
then the menus it drops.</p>
<h2 id="pages">Pages</h2>
<ul>
<li><a href="main-panel.html">Main Panel</a> — The window itself: the menu bar, the router outlet, and the chrome that stays put while pages come and go</li>
<li><a href="bottom-message-bar.html">Bottom Message Bar</a> — The UI-notification channel docked at the bottom of the Main Panel, and how a message reaches it</li>
<li><a href="session-message-panel.html">Session Message Panel</a> — The four session message sinks and the tabbed panel that presents them; a runtime reset clears three of them and leaves the NC Manipulation sink standing</li>
<li><a href="preference-menu.html">Preference Menu</a> — The Preference dropdown: the settings it carries, and where the web client keeps the ones it does not</li>
<li><a href="language-selection-submenu.html">Language Selection SubMenu</a> — The nested language picker, and when the chosen locale is applied</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a href="../conventions/index.html">Conventions</a> — the rules the shell applies, message handling first</li>
<li><a href="../widget/index.html">Widgets</a> — the reusable controls the shell and every page embed</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,178 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Language Selection SubMenu | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Language Selection SubMenu | HiAPI-C# 2025 ">
<link rel="icon" href="../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../index.html">
<img id="logo" class="svg" src="../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-LanguageSelectionSubmenu">
<h1 id="language-selection-submenu">Language Selection SubMenu</h1>
<p>The submenu locates on the <a class="xref" href="preference-menu.html">Preference Menu Dropdown</a>. It is the only
place either client offers for changing the interface language. What that choice sets in motion in
the web client — the message bundles, the locale applied before the first frame, the single switch
function and everything that re-renders behind it — is
<a class="xref" href="../platform/i18n.html">Internationalization</a>; this page covers the gesture.</p>
<p>The web submenu's model is the application-state store rather than a service handed down by the
parent component: it reads the current code and the available-code list from that store and calls
the store's language action. Both values are hydrated by the store's server-preferences load, which
the shell layout runs on mount — a separate path from the one that decides which locale the first
painted frame uses. The WPF client resolves its language manager during start-up, and the manager
reads the persisted code out of the user configuration as it is constructed.</p>
<h2 id="layout">Layout</h2>
<p>The two clients draw the submenu differently.</p>
<ul>
<li><p>Language Selection SubMenu — WPF application</p>
<ul>
<li>English RadioButton</li>
<li>Simplified Chinese RadioButton</li>
<li>Traditional Chinese RadioButton</li>
</ul>
<p>Three fixed rows in one radio group, ticked at start-up to match the current language.</p>
</li>
<li><p>Language Selection SubMenu — web application</p>
<ul>
<li>Language Row — one plain row per code in the store's available-code list, so the rows are
whatever the server reports rather than a fixed set of three. Each carries the language's own
name from a hard-coded label map, falling back to the raw code for anything the map does not
name, with that code beneath it as a caption; the row matching the current language is drawn in
the active state. No row carries a radio button or a checkbox.</li>
</ul>
<p>The parent entry captions the current language, so it reads without opening the submenu.</p>
</li>
</ul>
<p>Choosing a row in the web client closes the popup and makes one call, the store's language action.
The store writes the new code optimistically, POSTs it, adopts the current and available lists the
server echoes back, and only then switches the live catalogue — so the interface text flips after
the server has accepted the value, and the confirmation toast already reads in the language just
picked. A rejected write rolls the store back and changes no interface text at all. In the WPF
client picking a radio swaps the merged string dictionary in place and saves the code to the user
configuration, and the labels follow without a restart.</p>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../index.html">HiNC App Anatomy</a> for git repository links.</p>
<h3 id="web-application">Web Application</h3>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/AppMenuBar.vue</code> — the nested <code>Preference → Language ▸</code> popup: the row
loop over the available codes, the hard-coded self-name map, the active-row marker, and the
handler whose single call is the store's language action.</li>
<li><code>wwwroot-src/src/stores/appState.ts</code> — the current-code and available-code state, the language
action with its optimistic write, its POST-then-apply order and its rollback, and the
server-preferences load the shell layout runs on mount.</li>
<li><code>wwwroot-src/src/api/preference.ts</code> — the typed wrappers over the two language endpoints.</li>
<li><code>Environments/PreferenceController.cs</code><code>GET /api/preference/language</code> and its <code>POST</code> twin, both
answering <code>success</code>, <code>current</code> and <code>available</code>; <code>languageCode</code> is the name of a field in the POST
request body only. An unlisted code is rejected with 400, and an accepted one is written to the
persisted <code>UserConfig.LanguageCode</code> and saved.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="../conventions/translation-remarks.html">Translation Remarks</a> — the terminology every translated label is held to</li>
<li><a class="xref" href="preference-menu.html">Preference Menu Dropdown</a> — the dropdown this sub-menu hangs from</li>
<li><a class="xref" href="../platform/i18n.html">Internationalization</a> — the bundles and the switch point this menu drives</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,306 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Main Panel | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Main Panel | HiAPI-C# 2025 ">
<link rel="icon" href="../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../index.html">
<img id="logo" class="svg" src="../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-MainPanel">
<h1 id="main-panel">Main Panel</h1>
<p>The Main Panel is the primary window of the HiNC application, providing navigation and access to all major features.</p>
<h2 id="key-models">Key Models</h2>
<ul>
<li>Project Service
<ul>
<li><strong>WPF Single-User Desktop Application</strong>: Uses self-hosted <a class="xref" href="../../api/Hi.MachiningProcs.LocalProjectService.html">LocalProjectService</a></li>
<li><strong>Web Service Application</strong>: injects <a class="xref" href="../../api/Hi.MachiningProcs.ProxyProjectService.html">ProxyProjectService</a>, the <a class="xref" href="../../api/Hi.MachiningProcs.IProjectService.html">IProjectService</a> implementation that reaches the session's project across the connection</li>
</ul>
</li>
<li><strong>User Service</strong>: <code>UserService</code></li>
</ul>
<h2 id="layout-structure">Layout Structure</h2>
<ul>
<li>Top <code>Navigation Menu</code>
<ul>
<li><p>Brand logo, and in the web application the HiAPI version as an outlined badge beside it. The
badge carries the HiNc assembly version reported by the anonymous authentication-status probe
the router runs before the first route resolves, so it needs neither a loaded project nor a
signed-in session; it is omitted only while that probe has reported no version.</p>
</li>
<li><p><code>Project Menu Dropdown</code></p>
<ul>
<li><code>Project Path Text Field</code> — readonly; reads &ldquo;No Project Loaded&rdquo; until a project is loaded</li>
<li><code>New MenuItem</code></li>
<li><code>Load MenuItem</code></li>
<li><code>ReLoad MenuItem</code> (web application only)</li>
<li><code>Save MenuItem</code></li>
<li><code>Save As MenuItem</code></li>
<li><code>Close Project MenuItem</code> (web application only, below a separator)</li>
</ul>
<p>In the web application ReLoad, Save, Save As and Close Project are disabled until a project is loaded, and New, Load and Save As open the shared file-picker dialog with a <code>.hincproj</code> filter. The picker is allowed the Admin and Project roots and is denied the Resource root, which holds read-only templates the project endpoints cannot save through; it asks for the Admin root by name, so that is where it opens, and it seeds its path box with the directory holding the loaded project, or with the admin root itself when there is none. The Project root is one of the two only while a project is loaded: the roots endpoint lists a root only when its directory resolves, and the project directory resolves to nothing until a project is open, so with no project the picker offers Admin alone. The WPF client's dropdown stops at Save As.</p>
</li>
<li><p><code>Environment Menu Dropdown</code> (WPF application only — the web application has no such dropdown; see below)</p>
<ul>
<li>Machine Tool MenuItem
Open <a class="xref" href="../general-setup/machine-tool.html">Machine Tool Page</a>
Sole window in WPF app.
The page manages <a class="xref" href="../../api/Hi.MachiningProcs.LocalProjectService.html#Hi_MachiningProcs_LocalProjectService_MachiningEquipment">MachiningEquipment</a>.<a class="xref" href="../../api/Hi.Machining.MachiningEquipmentUtils.MachiningEquipment.html#Hi_Machining_MachiningEquipmentUtils_MachiningEquipment_MachiningChain">MachiningChain</a>.</li>
<li>Controller MenuItem
Open <a class="xref" href="../legacy-controller.html">Legacy Controller Page</a></li>
<li>Tool House MenuItem</li>
<li>Fixture MenuItem
Open <a class="xref" href="../general-setup/fixture.html">Fixture Page</a></li>
<li>Workpiece MenuItem
Open <a class="xref" href="../general-setup/workpiece.html">Workpiece Page</a></li>
</ul>
</li>
<li><p>Mission MenuItem
Open <a class="xref" href="../execution/mission/mission-root-panel.html">Mission Page</a>
In the web application the Mission editor is a branch of the Execution page's Control Tree; <code>/mission</code> redirects there.</p>
</li>
<li><p><code>Page Menu Dropdown</code> (web application only) — the three workflow pages in setup order (fill the tool house, set up the equipment, run), then the utilities.</p>
<ul>
<li>Tool House MenuItem
Open <a class="xref" href="../tool-house/index.html">Tool House Page</a></li>
<li>General Setup MenuItem
The equipment Control Tree — Machine Tool, Spindle Capability, Background, Coolant, Fixture, Workpiece, Controller, in that order — plus the shared equipment canvas. Two further controller branches, CSV Controller and CL Controller, are <a class="xref" href="../general-setup/hidden-controllers.html">hidden by default</a> and are switched on from the Preference dropdown.</li>
<li>Execution MenuItem
Open <a class="xref" href="../execution/index.html">Execution Page</a></li>
<li>File Explorer MenuItem (below a separator). See <a href="../util/index.html">Util Pages</a>.</li>
<li>Mechanism Builder MenuItem
Open <a class="xref" href="../util/mech-builder.html">Mechanism Builder Page</a></li>
<li>Legacy-Controller MenuItem (below a separator)
Open <a class="xref" href="../legacy-controller.html">Legacy Controller Page</a></li>
</ul>
<p>The dropdown is not every route the application has. The Machine Tool page stays reachable by URL for its chain-only canvas preview, the Log Viewer is reached by the <code>Show Log</code> button, and the Fixture, Workpiece, Spindle Capability, Mission and Background / Coolant paths resolve as redirects into the two tree pages.</p>
</li>
<li><p><a class="xref" href="preference-menu.html">Preference Menu Dropdown</a></p>
</li>
<li><p><code>Debug Menu Dropdown</code> (WPF application only) — Transformers and Geometry Management, visible only while the client runs in debug mode</p>
</li>
<li><p>Help MenuItem</p>
<ul>
<li>HiAPI Version label
A label to show the HiNc library version.
The web application shows this as a version badge next to the brand logo, with no Help dropdown.</li>
</ul>
</li>
</ul>
</li>
<li><code>Show Log</code> Button — a button on both clients, not a menu item. In the web application it sits on the menu bar's right side and routes to the Log Viewer page; in the WPF client it sits at the right end of the bottom message bar.
The Log Viewer presents the server's application log for the current day, with a manual refresh, a selectable auto-refresh interval, copy to the clipboard, and download of the day's log file for offline analysis.</li>
<li>Central <code>Page Panel</code> — one router outlet wrapped in a keep-alive cache keyed on a project epoch: an
integer the layout owns, raised by two watchers and read by nothing else. The first watches the project
store's path, which New, Load and Save As each assign from the response they get back, which Close Project
clears, and which a store-level watcher on the shared execution-status hub adopts from every broadcast — so a
project change made in another tab, from another browser, or outside the browser altogether bumps the epoch
here as well, and so does the boot status fetch, once, on a service that already has a project open. The
second watches a counter the store raises in exactly one place, ReLoad, which is what makes a re-read of the
same path remount at all: the backend raises its project-changed event with the path it already had, so the
broadcast cannot be told apart from a no-op. Assigning the path it already holds moves neither watcher, so
Save on the current path does not remount and neither does a Save As written back onto the path already open.
A bump discards every cached page and rebuilds it, so each page initializes on its own mount hook rather than
watching for project changes itself.</li>
<li>Bottom footer — the routine-progress bar: the latest foreground message with a recent-messages history menu on the left, the live session status in the middle, and in-flight background job progress on the right. See <a class="xref" href="bottom-message-bar.html">Message Section on Main Panel</a>.</li>
</ul>
<p>In the WPF application the run tool bars belong to the menu shell and appear only while the central panel is the player. The web application does not carry them on the menu bar at all: the transport controls ride the <a class="xref" href="../execution/index.html">Execution Page</a>&lsquo;s primary panel header and the view controls ride its canvas panel header. What the menu bar keeps instead is the page's set of column quick-toggles, the connection badge, the active page's title, the <code>Show Log</code> button and — when authentication is on — the logout button. The toggles follow whichever tree page is current: four on the Execution page (control dock, canvas, strip charts, step info) and three on General Setup (control dock, content, canvas); the Tool House page lays itself out with plain splitters and gets none. The connection badge is Execution-only — the other pages&rsquo; canvases carry their own in-panel badges.</p>
<h2 id="project-menu-behavior">Project Menu Behavior</h2>
<p>The <code>Project Path Text Field</code> displays the current project path when a project is loaded. It is implemented as a pure text field (not a button) that allows users to select and copy the path.</p>
<p>The <code>Project</code> Menu manages <a class="xref" href="../../api/Hi.MachiningProcs.MachiningProject.html">MachiningProject</a> with the following operations:</p>
<table>
<thead>
<tr>
<th>Operation</th>
<th>Description</th>
<th>Example</th>
</tr>
</thead>
<tbody>
<tr>
<td>New</td>
<td>Creates a new project</td>
<td>See <code>DemoBuildGeomOnlyMachiningProject</code></td>
</tr>
<tr>
<td>Load</td>
<td>Opens an existing project</td>
<td>See <code>DemoUseMachiningProject</code></td>
</tr>
<tr>
<td>ReLoad</td>
<td>Re-reads the current project from disk, same path</td>
<td></td>
</tr>
<tr>
<td>Save</td>
<td>Saves the current project</td>
<td>See <code>DemoBuildGeomOnlyMachiningProject</code></td>
</tr>
<tr>
<td>Save As</td>
<td>Saves the project to a new location</td>
<td>See <code>DemoBuildGeomOnlyMachiningProject</code></td>
</tr>
<tr>
<td>Close</td>
<td>Closes the current project and clears the shell</td>
<td></td>
</tr>
</tbody>
</table>
<p>In the web application every Project action reports through two channels: a background progress line on the footer while it runs, then a toast when it lands — positive on success, a &ldquo;busy&rdquo; warning when a concurrent action has already claimed the service (HTTP 409), negative otherwise. In the WPF client the operations log through <code>ILogger</code> and, after a load, set the player panel's DispEngine to the isometric view. What a project change drives in the web shell is the layout's keep-alive epoch, not a canvas view: every cached page is discarded and rebuilt.</p>
<div class="NOTE">
<h5>Note</h5>
<p>The web implementation reports through the toast helper and the routine-progress footer store; the WPF client reports through <code>ILogger</code>. Neither routes project messages through MessageUtil. Project I/O is asynchronous on both clients, so the shell stays responsive during file I/O.</p>
</div>
<h2 id="platform-specific-differences">Platform-Specific Differences</h2>
<h3 id="wpf-application">WPF Application</h3>
<ul>
<li>Only a single instance of each sub-window (Mission, Workpiece, Fixture) can exist at a time</li>
<li>There is no menu entry for the run cockpit: the Main Panel itself is the player</li>
</ul>
<h3 id="web-application">Web Application</h3>
<ul>
<li>The <a class="xref" href="../execution/index.html">Execution Page</a> is the landing route — <code>/</code> redirects to <code>/execution</code></li>
<li>The page URL and panel state are synchronized (bi-directional navigation)</li>
<li>There is no Environment dropdown. Machine Tool, Fixture, Workpiece, Controller, Background / Coolant and Spindle Capability are branches of the General Setup page's Control Tree, and Tool House and Execution are pages of their own; the remaining paths resolve as redirects into those pages. The menu bar is therefore <code>Project ▾ | Page ▾ | Preference ▾</code>, and the Controller page is reached from <code>Page ▾</code> as <code>Legacy-Controller</code>.</li>
</ul>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../index.html">HiNC App Anatomy</a> for git repository links.</p>
<h3 id="web-application-1">Web Application</h3>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>Environments/PreferenceController.cs</code> — the endpoints behind the Preference dropdown.</li>
<li><code>Environments/ProjectController.cs</code> — the project endpoints: status, new, load, save, reload, saveas and close, plus the log endpoints.</li>
<li><code>Environments/UserService.cs</code> — the shell's user model.</li>
<li><code>wwwroot-src/src/components/AppMenuBar.vue</code> — the whole navigation menu.</li>
<li><code>wwwroot-src/src/components/AppFooter.vue</code> — the routine-progress footer the layout docks.</li>
<li><code>wwwroot-src/src/layouts/MainLayout.vue</code> — the shell that hosts the menu bar, the keep-alive page panel and the footer.</li>
<li><code>wwwroot-src/src/router/routes.ts</code> — the routes the Page dropdown targets and the redirect-only legacy paths.</li>
<li><code>wwwroot-src/src/i18n/en/menu.ts</code> — the menu bar's English labels.</li>
<li><code>wwwroot-src/src/stores/project.ts</code> — the six Project actions and the busy error a 409 becomes.</li>
<li><code>wwwroot-src/src/stores/routineProgress.ts</code> — the foreground-message and background-progress store the Project handlers share with the footer.</li>
<li><code>wwwroot-src/src/composables/useToast.ts</code> — the toasts every Project action ends in.</li>
<li><code>wwwroot-src/src/composables/useViewPrefs.ts</code> — the device-local per-page column visibility and the CSV / CL controller switches.</li>
<li><code>wwwroot-src/src/composables/useConnectionsHealth.ts</code> — the aggregate state behind the Execution connection badge.</li>
<li><code>wwwroot-src/src/components/widgets/FileExplorerDialog.vue</code> — the shared picker New, Load and Save As open.</li>
<li><code>wwwroot-src/src/components/widgets/ColumnToggleIcon.vue</code> — the glyph each column quick-toggle draws.</li>
<li><code>wwwroot-src/src/pages/LogViewerPage.vue</code> — the Log Viewer the <code>Show Log</code> button opens.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="../execution/index.html">Execution Page</a> — the landing page the shell routes to</li>
<li><a class="xref" href="preference-menu.html">Preference Menu Dropdown</a> — the third menu on the bar</li>
<li><a class="xref" href="bottom-message-bar.html">Bottom Message Bar</a> — the notification bar the shell docks along the bottom</li>
<li><a class="xref" href="../general-setup/index.html">General Setup Page</a> — the equipment page the Page menu reaches</li>
<li><a class="xref" href="../platform/tree-ids-and-routes.html">Tree Ids and Routes</a> — the route table this bar navigates, and how a <code>?tree=</code> id lands on the page that owns it</li>
<li><a class="xref" href="../platform/session-state.html">Session State</a> — the keep-alive this shell wraps the router view in, and what a project change destroys</li>
<li><a class="xref" href="../platform/login-and-auth.html">Login and Authentication</a> — the gate in front of this shell, and the sign-out entry on this bar</li>
<li><a class="xref" href="../platform/log-viewer.html">Log Viewer Page</a> — the screen the Show Log button opens</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,194 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Preference Menu Dropdown | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Preference Menu Dropdown | HiAPI-C# 2025 ">
<link rel="icon" href="../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../index.html">
<img id="logo" class="svg" src="../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-PreferenceMenu">
<h1 id="preference-menu-dropdown">Preference Menu Dropdown</h1>
<p>The Preference dropdown holds the application's display settings, and it is on the <a class="xref" href="main-panel.html">Main Panel</a>. The server-backed ones are service-wide rather than per-account: one <code>UserService</code> singleton holds one <code>UserConfig</code>, saved to a single <code>UserConfig.xml</code> resolved against the process working directory, so every browser signed in to that service reads and writes the same values. The sign-in cookie carries a user-name claim that nothing outside the authentication endpoints reads, and no preference endpoint is keyed on an account.</p>
<p>In the WPF application the menu binds <code>UserService</code>, whose <code>UserConfig</code> holds the persisted values. In the web application the dropdown has two models: Language and Show Physics Options bind the <code>appState</code> store, hydrated from and written back through <code>/api/preference/*</code>, where the server keeps them on <code>UserConfig</code>; the CSV / CL Controller checkboxes bind the device-local <code>useViewPrefs</code> singleton, persisted in the browser's <code>localStorage</code> and never sent to the server.</p>
<h2 id="layout">Layout</h2>
<ul>
<li>Preference Menu Dropdown
<ul>
<li><a class="xref" href="../execution/step-present-dialog.html">Step Present Preference</a> Button
<ul>
<li>WPF application: the first entry of the menu, opening the Step Present window.</li>
<li>Web application: the editor belongs to the Execution page's Step Info column, where a small icon button in the Step Properties panel header opens it as a modal next to the list it configures.</li>
</ul>
</li>
<li><a class="xref" href="../execution/graphic-cache-menu.html">Graphic-Cache Dropdown</a>
<ul>
<li>WPF application: a nested menu of cache fields inside the dropdown.</li>
<li>Web application: an entry of the Execution page's <a class="xref" href="../execution/execution-extended-renderingcanvas-tool-bar.html">extended tool bar</a>, under <code>Meshed Geom ▾</code>, which gathers the workpiece rendering-cache settings.</li>
<li>Its write is the one preference write that does not persist. <code>POST /api/preference/graphic-cache</code> clamps the requested size between the stored limits, assigns the three fields on the live <code>UserConfig</code> and returns, without calling <code>UserService.SaveUserConfig()</code>. The new value is service-wide and takes effect at once, but it reaches the file only when some later preference save writes the config out.</li>
</ul>
</li>
<li><a class="xref" href="language-selection-submenu.html">Language Selection SubMenu</a>
<ul>
<li>The persisted value is <code>UserConfig.LanguageCode</code>; the web application reaches it through <code>GET/POST /api/preference/language</code>, whose response also names the language codes the server supports.</li>
<li>In the web application the parent row captions the current language and the sub-menu marks the active code. A successful switch flips the interface text at once, so the confirmation toast already reads in the just-picked language.</li>
</ul>
</li>
<li>CSV Controller CheckBox
<ul>
<li>Web application only. The model is <code>useViewPrefs().showCsvController</code> — device-local, stored in the browser's <code>localStorage</code>, not in <code>UserConfig</code>. It is off by default.</li>
<li>Checking it adds the <code>CSV Controller</code> node to the General Setup page's Control Tree; unchecking removes it, moving the selection away first when that node is the selected one.</li>
<li>The caption under the box says whether the loaded project plays CSV — <code>This project plays CSV</code>, or <code>Not used by this project</code> — read from the <code>referenced</code> flag of <code>GET /api/mech/csv-runner</code>. Opening the dropdown fetches both runner snapshots in parallel, but the fetch is guarded on a loaded project: with none, neither request is made and both captions are cleared to blank. A snapshot that fails to arrive leaves its caption blank as well, so a blank caption states nothing about the project. The guard reaches the captions only — neither checkbox is disabled by it.</li>
</ul>
</li>
<li>CL Controller CheckBox
<ul>
<li>The same, for <code>useViewPrefs().showClController</code>, the <code>CL Controller</code> Control Tree node and <code>GET /api/mech/cl-runner</code>.</li>
</ul>
</li>
<li>Show Physics Options CheckBox
<ul>
<li>The model is <code>UserConfig.ShowPhysicsOptions</code>, reached in the web application through <code>GET/POST /api/preference/show-physics-options</code>.</li>
<li>The checkbox is disabled and unchecked if <code>UserService.IsPhysicsLicensed</code> is false: the GET returns the flag ANDed with the licence and the POST forces <code>false</code> without it.</li>
</ul>
</li>
<li>Show Log Button
<ul>
<li>See <a class="xref" href="bottom-message-bar.html">Message Section</a>.</li>
<li>It is not a Preference-dropdown entry on either platform: the WPF application puts it on the bottom message bar, and the web application puts it on the menu bar's right side as an always-visible button that opens the Log Viewer page.</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>So the web application's Preference dropdown carries the Language submenu, the CSV Controller and CL Controller visibility checkboxes, and the Show Physics Options checkbox.</p>
<p>A server write that fails raises a negative toast and rolls the item back in <code>appState</code>, so the dropdown never shows a language or a physics flag the server did not take.</p>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../index.html">HiNC App Anatomy</a> for git repository links.</p>
<ul>
<li><code>wwwroot-src/src/components/AppMenuBar.vue</code> — the <code>Preference ▾</code> dropdown itself and the menu bar's <code>Show Log</code> button.</li>
<li><code>wwwroot-src/src/stores/appState.ts</code> — the model of the two server-backed items, with the boot-time hydration and the write-back actions.</li>
<li><code>wwwroot-src/src/composables/useViewPrefs.ts</code> — the device-local <code>localStorage</code> singleton behind the two controller checkboxes.</li>
<li><code>wwwroot-src/src/api/preference.ts</code> — typed wrapper over <code>/api/preference/language</code> and <code>/api/preference/show-physics-options</code>.</li>
<li><code>wwwroot-src/src/api/csvRunner.ts</code>, <code>wwwroot-src/src/api/clRunner.ts</code> — the <code>referenced</code> flag each controller caption reports.</li>
<li><code>wwwroot-src/src/components/controlTree/useControlTreeHost.ts</code> — builds or omits the two controller nodes and rebuilds the tree when either checkbox flips.</li>
<li><code>wwwroot-src/src/i18n/en/menu.ts</code> — the dropdown's labels and captions.</li>
<li><code>wwwroot-src/src/pages/LogViewerPage.vue</code> — the page the <code>Show Log</code> button routes to.</li>
<li><code>Environments/PreferenceController.cs</code> — the endpoints. The step-present, show-physics-options, language and execution-layout writes persist through <code>UserService.SaveUserConfig()</code>; the graphic-cache write is the exception, and returns without one.</li>
<li><code>Environments/UserConfig.cs</code> — the persisted <code>LanguageCode</code> and <code>ShowPhysicsOptions</code> properties.</li>
<li><code>Environments/UserService.cs</code> — owns the single <code>UserConfig</code> the service holds, writes it to <code>UserConfigPath</code>, and answers the physics licence check.</li>
<li><code>Program.cs</code> — the one <code>UserService</code> registration behind every server-backed item here, and the configuration path it resolves against the process working directory.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="main-panel.html">Main Panel</a> — the menu bar this dropdown sits on</li>
<li><a class="xref" href="language-selection-submenu.html">Language Selection SubMenu</a> — the only sub-menu this dropdown nests</li>
<li><a class="xref" href="../execution/graphic-cache-menu.html">Graphic-Cache SubMenu</a> — one of the two entries the web client hosts elsewhere</li>
<li><a class="xref" href="../execution/step-present-dialog.html">Step Present Preference Page</a> — the other entry the web client hosts elsewhere</li>
<li><a class="xref" href="../platform/log-viewer.html">Log Viewer Page</a> — the log screen this menu's namesake route serves</li>
<li><a class="xref" href="../general-setup/hidden-controllers.html">Hidden Controller Branches</a> — the two equipment nodes the CSV and CL entries reveal, and what a link to one does while its box is off</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>
@@ -11,7 +11,7 @@
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
@@ -84,38 +84,49 @@
<nav id="breadcrumb"></nav>
</div>
<article data-uid="">
<article data-uid="Anatomy-SessionMessagePanel">
<h1 id="session-message-panel">Session Message Panel</h1>
<p>Session messages are partitioned by kind into three sinks on <a class="xref" href="../../api/Hi.MachiningProcs.LocalProjectService.html">LocalProjectService</a> (obtained via dependency injection):</p>
<p>Session messages are partitioned by kind into four sinks on <a class="xref" href="../../api/Hi.MachiningProcs.LocalProjectService.html">LocalProjectService</a> (obtained via dependency injection):</p>
<ul>
<li><a class="xref" href="../../api/Hi.MachiningProcs.LocalProjectService.html#Hi_MachiningProcs_LocalProjectService_ShellProgress">ShellProgress</a> — session-level routine / lifecycle messages (<a class="xref" href="../../api/Hi.MachiningProcs.ShellProgress.html">ShellProgress</a>). Session-scoped: the property is null outside <code>BeginSession</code>/<code>EndSession</code>.</li>
<li><a class="xref" href="../../api/Hi.MachiningProcs.LocalProjectService.html#Hi_MachiningProcs_LocalProjectService_NcDiagnosticProgress">NcDiagnosticProgress</a> — NC-pipeline diagnostics (<a class="xref" href="../../api/Hi.NcParsers.NcDiagnosticProgress.html">NcDiagnosticProgress</a>), each anchored to its NC source sentence.</li>
<li><a class="xref" href="../../api/Hi.MachiningProcs.LocalProjectService.html#Hi_MachiningProcs_LocalProjectService_NcDiagnosticProgress">NcDiagnosticProgress</a> play-time NC-pipeline diagnostics (<a class="xref" href="../../api/Hi.NcParsers.NcDiagnosticProgress.html">NcDiagnosticProgress</a>), anchored to the NC source block that raised them wherever the diagnostic has one.</li>
<li><a class="xref" href="../../api/Hi.MachiningProcs.LocalProjectService.html#Hi_MachiningProcs_LocalProjectService_StepDiagnosticProgress">StepDiagnosticProgress</a> — diagnostics anchored to a motion step (<a class="xref" href="../../api/Hi.MachiningProcs.StepDiagnosticProgress.html">StepDiagnosticProgress</a>).</li>
<li><a class="xref" href="../../api/Hi.MachiningProcs.LocalProjectService.html#Hi_MachiningProcs_LocalProjectService_NcManipulationDiagnosticProgress">NcManipulationDiagnosticProgress</a> — NC-manipulation diagnostics: a second <a class="xref" href="../../api/Hi.NcParsers.NcDiagnosticProgress.html">NcDiagnosticProgress</a>, written by <a class="xref" href="../../api/Hi.MachiningProcs.MachiningSession.html#Hi_MachiningProcs_MachiningSession_ConvertClToNcFiles_">ConvertClToNcFiles</a> and <a class="xref" href="../../api/Hi.MachiningProcs.MachiningSession.html#Hi_MachiningProcs_MachiningSession_OptimizeNcFiles_">OptimizeNcFiles</a>, held apart from the play-time sink so writeback findings never mix into the play pipeline's.</li>
</ul>
<p>The panel surfaces each sink in its own tab, so one tab holds one message kind.</p>
<p>The panel surfaces each sink in its own tab, so one tab holds one message kind. <a class="xref" href="../../api/Hi.MachiningProcs.LocalProjectService.html#Hi_MachiningProcs_LocalProjectService_ResetRuntime_">ResetRuntime</a> — which a project change runs — empties the first three, so each of them holds the current project only; the manipulation sink is outside that sweep, cleared only at the start of the conversion or optimization run that fills it, and its rows therefore survive a project change.</p>
<h2 id="layout">Layout</h2>
<ul>
<li>Tab Bar
<li>Tab Bar (each tab carries a floating count badge, drawn only while its sink holds messages)
<ul>
<li>Shell Tab</li>
<li>NC Diagnostics Tab</li>
<li>Step Diagnostics Tab</li>
<li>NC Manipulation Tab</li>
</ul>
</li>
<li>Per-Tab Content
<ul>
<li>Filter Toolbar
<ul>
<li><code>Severity</code> Filter Dropdown</li>
<li><code>Category</code> Filter Dropdown</li>
<li>Message Text Filter Input</li>
<li><code>Reset</code> Button (this tab's three filters, nothing else)</li>
<li><code>Export</code> Button</li>
<li>Matched / Total Badge (also the tab's hub connection indicator)</li>
</ul>
</li>
<li>Message Table</li>
</ul>
</li>
</ul>
<h2 id="message-table-per-tab">Message Table (per tab)</h2>
<p>Each tab renders its sink's message list — <a class="xref" href="../../api/Hi.MachiningProcs.ShellProgress.html#Hi_MachiningProcs_ShellProgress_Messages">Messages</a>, <a class="xref" href="../../api/Hi.NcParsers.NcDiagnosticProgress.html#Hi_NcParsers_NcDiagnosticProgress_Diagnostics">Diagnostics</a>, or <a class="xref" href="../../api/Hi.MachiningProcs.StepDiagnosticProgress.html#Hi_MachiningProcs_StepDiagnosticProgress_Messages">Messages</a> — as rows of:</p>
<p>Each tab renders its sink's message list — <a class="xref" href="../../api/Hi.MachiningProcs.ShellProgress.html#Hi_MachiningProcs_ShellProgress_Messages">Messages</a>, <a class="xref" href="../../api/Hi.MachiningProcs.StepDiagnosticProgress.html#Hi_MachiningProcs_StepDiagnosticProgress_Messages">Messages</a>, or <a class="xref" href="../../api/Hi.NcParsers.NcDiagnosticProgress.html#Hi_NcParsers_NcDiagnosticProgress_Diagnostics">Diagnostics</a> for the two NC-diagnostic sinks — as rows of:</p>
<ul>
<li><code>Severity</code> (colour-coded via <a class="xref" href="../../api/Hi.Common.Messages.IMessage.html#Hi_Common_Messages_IMessage_GetSeverity">GetSeverity()</a>)</li>
<li><code>Anchor</code> — the kind-specific position: the NC sentence index (<code>Sn</code>) for NC diagnostics, the step index for step diagnostics, none for shell messages</li>
<li><code>Message</code><a class="xref" href="../../api/Hi.Common.Messages.IMessage.html#Hi_Common_Messages_IMessage_GetId">GetId()</a> and <a class="xref" href="../../api/Hi.Common.Messages.IMessage.html#Hi_Common_Messages_IMessage_GetNotification">GetNotification()</a></li>
<li><code>Anchor</code> — the kind-specific position: none for shell messages, the NC sentence ordinal (<code>Sn &lt;n&gt;</code>) for both the play-time and the manipulation NC diagnostics, and the motion step with its sentence ordinal (<code>S&lt;step&gt; · Sn &lt;n&gt;</code>) for step diagnostics; an NC diagnostic raised at pipeline level rather than at a source block carries none</li>
<li><code>Message</code><a class="xref" href="../../api/Hi.Common.Messages.IMessage.html#Hi_Common_Messages_IMessage_GetCategory">GetCategory()</a>, <a class="xref" href="../../api/Hi.Common.Messages.IMessage.html#Hi_Common_Messages_IMessage_GetId">GetId()</a> and <a class="xref" href="../../api/Hi.Common.Messages.IMessage.html#Hi_Common_Messages_IMessage_GetNotification">GetNotification()</a></li>
</ul>
<p>Only take the last filtered elements (e.g. 5001000) for user experience. Find the usage example in the code:</p>
<pre><code class="lang-csharp" name="Demo_UseSessionMessageHost">internal static void DemoUseSessionMessageHost(LocalProjectService localProjectService)
@@ -153,7 +164,7 @@
$&quot;[{m.GetSeverity()}] {m.GetId()}: {m.GetNotification()}&quot;));
}
</code></pre>
<p>Add the update-table event per sink: <a class="xref" href="../../api/Hi.MachiningProcs.StepDiagnosticProgress.html#Hi_MachiningProcs_StepDiagnosticProgress_MessageAdded">MessageAdded</a> / <a class="xref" href="../../api/Hi.NcParsers.NcDiagnosticProgress.html#Hi_NcParsers_NcDiagnosticProgress_MessageAdded">MessageAdded</a>, and for the session-scoped shell sink the app-lifetime bridge <a class="xref" href="../../api/Hi.MachiningProcs.LocalProjectService.html#Hi_MachiningProcs_LocalProjectService_OnShellMessageAdded">OnShellMessageAdded</a> (with the matching <code>Cleared</code> events). The updating process has to be called by <a href="../general-rules.html">Loose Manner</a> for user experience.</p>
<p>Add the update-table event per sink: <a class="xref" href="../../api/Hi.MachiningProcs.StepDiagnosticProgress.html#Hi_MachiningProcs_StepDiagnosticProgress_MessageAdded">MessageAdded</a> / <a class="xref" href="../../api/Hi.NcParsers.NcDiagnosticProgress.html#Hi_NcParsers_NcDiagnosticProgress_MessageAdded">MessageAdded</a>, and for the session-scoped shell sink the app-lifetime bridge <a class="xref" href="../../api/Hi.MachiningProcs.LocalProjectService.html#Hi_MachiningProcs_LocalProjectService_OnShellMessageAdded">OnShellMessageAdded</a> (with the matching <code>Cleared</code> events). The updating process has to be called by <a class="xref" href="../conventions/index.html">Loose Manner</a> for user experience.</p>
<div class="TIP">
<h5>Tip</h5>
<p>On window desktop application (WPF), consider use textarea instead of datagrid to MessageTable for better performance. Use padding to show the different columns. And use the font in the textarea that with consistent width.</p>
@@ -163,23 +174,23 @@
<p>The message display should be real-time.</p>
</div>
<h2 id="behavior-of-export-button">Behavior of Export Button</h2>
<p>Export ALL filtered elements of the active tab's sink.</p>
<p>Every tab has its own <code>Export</code>, and it carries that tab's sink alone. It writes the rows the tab is holding — the recent window pulled from that sink, narrowed by that tab's severity, category and text filters — as a CSV of <code>Index,Count,Severity,Category,Id,Anchor,Notification,Detail</code>, named after the sink it came from. It is disabled while the filters match nothing.</p>
<h2 id="signalr-implementation-webapi-only">SignalR Implementation (Webapi Only)</h2>
<p>One hub per sink — <code>/shellMessageHub</code>, <code>/ncDiagnosticHub</code>, <code>/stepDiagnosticHub</code> — each with a <code>GetMessages(int limit)</code> pull returning <code>MessagesUpdate { messages, totalCount }</code>. A per-sink broadcast service subscribes its sink's <code>MessageAdded</code>/<code>Cleared</code> and raises a coalesced <code>MessagesChanged</code> notification via <a class="xref" href="../../api/Hi.Common.LooseRunner.html">LooseRunner</a>; the client re-pulls the recent window on each notification (loss-free regardless of how many appends coalesced). The JavaScript components connect to the three hubs to receive real-time updates.</p>
<p>One hub per sink — <code>/shellMessageHub</code>, <code>/ncDiagnosticHub</code>, <code>/stepDiagnosticHub</code>, <code>/ncManipulationDiagnosticHub</code> — each with a <code>GetMessages(int limit)</code> pull returning <code>MessagesUpdate { messages, totalCount }</code>. A per-sink broadcast service subscribes its sink's <code>MessageAdded</code>/<code>Cleared</code> and raises a coalesced <code>MessagesChanged</code> notification via <a class="xref" href="../../api/Hi.Common.LooseRunner.html">LooseRunner</a>; the client re-pulls the recent window on each notification (loss-free regardless of how many appends coalesced). The client components connect to the hubs they display to receive real-time updates.</p>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a href="../index.html">this page</a> for git repository.</p>
<h3 id="wpf-application-source-code-path">WPF Application Source Code Path</h3>
<p>See <a class="xref" href="../index.html">HiNC App Anatomy</a> for git repository links.</p>
<ul>
<li>Play/SessionMessagePanel</li>
<li>wwwroot-src/src/components/execution/SessionMessagePanel.vue (tabbed panel)</li>
<li>wwwroot-src/src/components/execution/SessionMessageTab.vue + MessageRow.vue (per-tab list)</li>
<li>wwwroot-src/src/composables/useSessionSinkHub.ts (the four hub composables)</li>
<li>Execution/SessionSinkHub.cs (the SignalR hub base; one hub per sink)</li>
<li>Execution/SessionSinkBroadcastService.cs + ShellMessageBroadcastService.cs, NcDiagnosticBroadcastService.cs, StepDiagnosticBroadcastService.cs, NcManipulationDiagnosticBroadcastService.cs (subscribe-and-rebroadcast)</li>
<li>Execution/SessionSinkDtos.cs (typed DTOs)</li>
</ul>
<h3 id="web-page-application-source-code-path">Web Page Application Source Code Path</h3>
<h2 id="see-also">See Also</h2>
<ul>
<li>wwwroot-src/src/components/player/SessionMessagePanel.vue (tabbed panel)</li>
<li>wwwroot-src/src/components/player/SessionMessageTab.vue + MessageRow.vue (per-tab list)</li>
<li>wwwroot-src/src/composables/useSessionSinkHub.ts (the three hub composables)</li>
<li>Players/SessionSinkHub.cs (SignalR hubs for the three sinks)</li>
<li>Players/SessionSinkBroadcastService.cs + {Shell,Nc,Step}*BroadcastService.cs (subscribe-and-rebroadcast)</li>
<li>Players/SessionSinkDtos.cs (typed DTOs)</li>
<li><a class="xref" href="../execution/index.html">Execution Page</a> — the page whose main column hosts this panel</li>
<li><a class="xref" href="bottom-message-bar.html">Bottom Message Bar</a> — the app-level notification bar, as distinct from these session sinks</li>
</ul>
</article>
@@ -0,0 +1,37 @@
<div id="sidetoggle">
<div>
<div class="sidefilter">
<form class="toc-filter">
<span class="glyphicon glyphicon-filter filter-icon"></span>
<span class="glyphicon glyphicon-remove clear-icon" id="toc_filter_clear"></span>
<input type="text" id="toc_filter_input" placeholder="Filter by title" onkeypress="if(event.keyCode==13) {return false;}">
</form>
</div>
<div class="sidetoc">
<div class="toc" id="toc">
<ul class="nav level1">
<li>
<a href="index.html" name="" title="Overview">Overview</a>
</li>
<li>
<a href="main-panel.html" name="" title="Main Panel">Main Panel</a>
</li>
<li>
<a href="bottom-message-bar.html" name="" title="Bottom Message Bar">Bottom Message Bar</a>
</li>
<li>
<a href="session-message-panel.html" name="" title="Session Message Panel">Session Message Panel</a>
</li>
<li>
<a href="preference-menu.html" name="" title="Preference Menu">Preference Menu</a>
</li>
<li>
<a href="language-selection-submenu.html" name="" title="Language Selection SubMenu">Language Selection SubMenu</a>
</li>
</ul>
</div>
</div>
</div>
</div>

Some files were not shown because too many files have changed in this diff Show More