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
@@ -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 1–6 double as G54–G59.&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 1–6
double as G54–G59.&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 1–6, 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>G54–G59 and G54.1P1–G54.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>G54–G59 and G54.1P1–G54.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>G54–G57 and G505–G599</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>G54–G59</td>
<td><em>&quot;G54–G59 map onto Heidenhain datum preset rows 1–6.&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 G54–G57 and the extended series, and has no G58 or G59 row at all; the
Heidenhain table yields one id per preset row 1–6 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 G54–G59 rows of Work Coordinates <em>are</em> preset rows 1–6 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 — G54–G59 and all forty-eight G54.1 P offsets
on the brand table, every address seeded, plus the extended G59.1–G59.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 G54–G57, the frame table listing no G58, no G59, no
G59.x and no G54.1 P id at all; a switch into Heidenhain carries G54–G59 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 G54–G59 onto preset rows 1–6.</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>500–999</td>
<td>0–999</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 0–999 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 R0–R999 (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 # (500–999)</strong> or <strong>Parameter # (0–999)</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 500–999.&quot;</em> or <em>&quot;Parameter number must be an
integer in 0–999.&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 # (500–999)</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 R0–R999 (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 # (0–999)</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;G54–G59 map onto Heidenhain datum preset rows 1–6.&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>G54–G59, G54.1P1–G54.1P48; G59.1–G59.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>G54–G59, G54.1P1–G54.1P48; G59.1–G59.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>G54–G57, G505–G599</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>G54–G59</td>
<td>all six</td>
<td>none</td>
</tr>
<tr>
<td>—</td>
<td>brand-neutral table (standalone)</td>
<td>G54–G59, G59.1–G59.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 G505–G599 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 G54–G59 onto <code>#5221</code> with a
stride of 20 and G54.1 P1–P48 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 1–20 and
shift rows 1–20, and its ISO face aliases only preset rows 1–6 onto G54–G59. Rows 7–20, 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 1–6.
<a class="xref" href="datum-tables.html">Datum Tables</a> shows the same six cells under the <strong>Q339</strong>
column, alongside rows 7–20 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 4–9, 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 G54–G59 and G59.1–G59.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 G54–G59 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 G54–G59'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.1–G59.9 ids live on a second table behind the brand table.</strong> The Fanuc-family
tables map G54–G59 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 7–20 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
G54–G57 and G505–G599 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 G54–G59 alias onto preset rows 1–6, 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>