Siemens Tool Offsets
Siemens Tool Offsets is the Sinumerik $TC_DP 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 /general-setup, under the Control-Tree ids
equipment/controller/program-data/siemens-tool-offsets and
equipment/controller/program-data/tool-names, 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.
Important
Anatomy makes no claim that any id named here is stable across versions. The migration hop that
keeps an older ?tree= value resolving is described in
Tree Ids and Routes.
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 Editing Contract. What is these leaves' own is everything below.
Two Nodes Over One Table
The tree labels differ, the panels differ, and the object behind them does not.
| Node id | Tree label | Which half of the object |
|---|---|---|
equipment/controller/program-data/siemens-tool-offsets |
Tool Offsets ($TC_DP) | Edges — the offset rows |
equipment/controller/program-data/tool-names |
Tool Names | ToolNames — the name map |
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 SiemensToolOffsetTable over the proxy-resolved dependency list, and the Siemens preset is the only shipped brand list that carries the proxy for it — Brand Matrix.
The same flag does one more thing: it relabels the ungated generic offset leaf one row above from Tool Offsets to Tool Offsets (ISO G43 H), 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 Tool Offsets.
Each panel reads only its own half. The $TC_DP 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, "No Siemens tool-name table on the active runner.",
names a table that is not a type of its own.
The Key Has Two Parts
ISiemensToolOffsetConfig addresses an offset by a tool
number and a cutting edge number together, where
IToolOffsetConfig — 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 D word after the tool is mounted.
The pair is the dictionary key, and both halves are rendered as bold plain text with a hard-coded
letter prefix — T on the tool cell and D 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 $TC_DP 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.
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.
The Columns
Ten labelled columns: the two key columns, then eight value columns in millimetres over the four geometry components and their four wear counterparts.
| Column header | $TC_DP index |
Stored as | Read by the pipeline |
|---|---|---|---|
| Tool T | — | the key's tool number | the tool the offsets belong to |
| Edge D | — | the key's edge number | the D word |
| Length 1 (Z) | 3 | Length1_mm | the height path |
| Length 2 (X) | 4 | Length2_mm | nothing |
| Length 3 (Y) | 5 | Length3_mm | nothing |
| Radius | 6 | Radius_mm | nothing |
| Length 1 Wear | 12 | WearLength1_mm | the height path |
| Length 2 Wear | 13 | WearLength2_mm | nothing |
| Length 3 Wear | 14 | WearLength3_mm | nothing |
| Radius Wear | 15 | WearRadius_mm | nothing |
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 generic 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.
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.
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.
The fields the row carries but does not interpret
A row also holds VerbatimDpFields, a
bag keyed by $TC_DP 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 +N 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.
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 Edge entry
per key with the eight components as named attributes and the bag as DP-indexed ones, and one
ToolName entry per mapping; the reader accepts both spellings of a component, so a DP-indexed
geometry value lands on the typed property rather than in the bag.
The Sign Convention
On this table an effective value is the geometry plus the wear. 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 negative wear value. The panel states the addition twice: the description above the table ends "Wear adds onto geometry (enter a shortened tool as negative wear).", and the footnote below it reads "All values in mm. Effective length 1 = Length 1 (Z) + Length 1 Wear; same for the other columns."
Warning
The generic ledger one node above uses the opposite convention. On ToolOffsetTable an effective value is the ideal minus the wear — FullHeight_mm is the ideal height less the axial wear and FullRadius_mm 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 Tool Offsets, which carries this warning from the other side.
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.
How a D Word Resolves
The Siemens height path consumes a standalone D word from the block and writes the same downstream
state as the ISO G43 path: a tool-height compensation section marked with the term D, 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.
Resolution runs in this order.
D0cancels. The section is written with an offset of 0 and an identity transform, with no table lookup and no diagnostic.- The active tool number comes from the block's tool-change section, or from the previous block's when this one carries none. A numeric tool id is taken as it stands.
- A string tool id is resolved through the tool-name map on this same table. An unmapped name raises an unsupported warning and leaves the tool number at 0.
- The
(T, D)pair is looked up here. A configured row answers with length 1 plus its wear. - A pair with no row falls back to the generic ledger, described next.
A block that carries no D word re-resolves the previous block's edge modally, and only while that
previous section is still owned by the D 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 D word out, so a single
missing row reports once instead of on every block that inherits it.
What a missing row falls back to
For a (T, D) pair this table has no row for — and equally when no Siemens offset table resolves at
all — the height is taken from the generic tool-offset table's height for the tool number,
through the single-integer contract the ISO G43 H path already reads. Every Siemens runner carries
that table, so the fallback always has a source. It exists so that an unfilled per-case $TC_DP
table follows the tool-house-fed ledger instead of resolving 0 and machining the whole program one
tool length low.
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 NaN — 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.
The usable-height arm carries the code SiemensToolOffset--TcdpRowMissing; the NaN arm carries
SiemensToolOffset--TcdpRowMissingGenericHeightNaN.
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 $TC_DP 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.
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.
What the Tool-Name Map Is For
Siemens programs may name a tool as a string rather than a number, as in the T="D16R3Z6" 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.
Two consumers read it, and they fail differently.
- The tool change itself. The tool-change semantic resolves a string tool id through this map
before it emits a tooling step. An unmapped name raises a
ToolChange--NameUnresolvedwarning and emits no step at all, so the tool change is not simulated rather than simulated with the wrong tool. - The
Dword. The height path resolves the same name for its own(T, D)lookup and, on a miss, raisesSiemensToolOffset--ToolUnresolvedand carries on with tool number 0.
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.
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.
Its footer is an Add / Set 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.
Row Life Cycle
The two leaves spell adding and deleting differently, and the difference is which side mints the key.
The $TC_DP table's adds are fieldless toolbar and row buttons, because the server mints both
halves of the key. Add Tool 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
"Add a cutting edge for T{tool}", 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.
The Tool Names table's add is the footer pair described above, since its key is typed rather than minted. A blank name is refused before any request with "Tool name must not be empty.", and a number that is not a positive integer with "Tool # must be a positive integer."; a successful write clears both fields and re-reads the table. The endpoint refuses a blank name a second time.
Deletion is guarded on one leaf and not the other. A $TC_DP row opens a confirmation dialog first —
Remove tool offset row over "Remove the offset row for (T{tool}, D{edge})?" — 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.
Cell edits differ from both. Every numeric cell on the $TC_DP table sends the whole row — 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.
What Materializes the Table
Three of the writes re-wire the runner suit's dependency proxies before resolving the table: the
$TC_DP row write, the $TC_DP 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.
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.
When the Tables Are Absent
Both leaves carry the branch's two empty layers. The shared one renders "No NC runner — load a project first." while the snapshot reports no runner. Each panel's own read then gates on whether the Siemens table resolved, and renders "No Siemens tool-offset table on the active runner." or "No Siemens tool-name table on the active runner." when it did not.
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, No SiemensToolOffsetTable on the active runner; the $TC_DP add builds its own envelope instead and answers word for word with the
$TC_DP panel's own absent-table line. Either way the panel shows its localized context followed by
the sentence the server sent.
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 — Program Data Plane.
Layout
- General Setup Control Tree — the left dock of
/general-setup- Controller Node Row
- Program Data Group Row
- Tool Offsets ($TC_DP) Node Row —
equipment/controller/program-data/siemens-tool-offsets - Tool Names Node Row —
equipment/controller/program-data/tool-names
- Tool Offsets ($TC_DP) Node Row —
- Program Data Group Row
- Controller Node Row
- Editor Row — the panel of whichever node is selected
- Tool Offsets ($TC_DP) Panel
- No-Runner Body — "No NC runner — load a project first."; shown instead of everything below
- Absent-Table Body — "No Siemens tool-offset table on the active runner."; shown instead of the body
- Description Caption — "Sinumerik $TC_DP tool offsets, one row per (tool T, cutting edge D). A
D1word 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).", withD1set in code style - Toolbar — right-aligned
- Add Tool Button — primary, add icon
- Offset Table — dense, flat, bordered
- Header Row — Tool T, Edge D, Length 1 (Z), Length 2 (X), Length 3 (Y), Radius, Length 1 Wear, Length 2 Wear, Length 3 Wear, Radius Wear, then an unlabelled action column
- Offset Row, one per
(tool, edge)pair, ascending by tool then edge- Tool Cell — bold plain text, prefixed
T - Edge Cell — bold plain text, prefixed
D - Eight Numeric Fields — the four geometry components, then their four wear components; no bounds and no unit suffix
- Add-Cutting-Edge Button — flat,
playlist_addicon, tooltipped "Add a cutting edge for T{tool}" - Delete Button — flat, delete icon
- Verbatim-Field Badge — a grey +N; shown only where the row carries unconsumed
$TC_DPfields, tooltipped that they are stored and saved with the project and not used by the simulation
- Tool Cell — bold plain text, prefixed
- Footnote — "All values in mm. Effective length 1 = Length 1 (Z) + Length 1 Wear; same for the other columns."
- Remove-Row Confirmation Dialog — Remove tool offset row, "Remove the offset row for (T{tool}, D{edge})?", with a cancel button
- Tool Names Panel
- No-Runner Body — "No NC runner — load a project first."
- Absent-Table Body — "No Siemens tool-name table on the active runner."
- Hint Caption — "Maps the tool names written in the NC program (
T="D16R3Z6") to tool numbers. Names are case-insensitive. An unmapped name cannot mount a tool and itsDword resolves no offset.", with the call form and theDword set in code style - Name Table — dense, flat, bordered
- Header Row — Tool Name, Tool #, then an unlabelled action column
- Name Row, one per mapping, ordered case-insensitively by name
- Name Cell — bold plain text
- Tool Number Numeric Field — minimum 1
- Delete Button — flat, delete icon, no confirmation
- Add Row Footer
- Tool name Text Field
- Tool # Numeric Field — minimum 1
- Add / Set Button — primary; the only submit path, since neither field submits on Enter
- Tool Offsets ($TC_DP) Panel
Source Code Path
See HiNC App Anatomy for git repository links.
Web Application
HiNC-2025-webservice (Quasar CLI SPA):
wwwroot-src/src/components/controlTree/SoftNcSiemensToolOffsetsPanel.vue— the$TC_DPpanel: 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.wwwroot-src/src/components/controlTree/SoftNcToolNamesPanel.vue— the tool-name panel: the plain-text key column, the bounded tool-number cell, the unconfirmed delete, and the Add / Set footer with its two client-side refusals and its full re-read.wwwroot-src/src/components/controlTree/softNcItemTypes.ts— 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.wwwroot-src/src/api/softNcRunner.ts— 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.wwwroot-src/src/composables/useSoftNcRunner.ts— the shared snapshot behind the first empty layer and behind the tree's flag test.wwwroot-src/src/components/controlTree/SoftNcEmptyState.vue— the shared no-runner line both panels open with.wwwroot-src/src/components/widgets/NumericInput.vue— the numeric cell: commit on blur or Enter, the minimum rejected inside the widget, and the empty-text-to-null parse both panels drop.wwwroot-src/src/components/controlTree/SoftNcToolOffsetsPanel.vue— the neighbouring generic ledger, its subtracting sign rule, and the Siemens caveat it shows from the brand string.wwwroot-src/src/i18n/en/softNc.ts— 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.wwwroot-src/src/i18n/en/common.ts— the shared Tool # header and the Add / Set label.wwwroot-src/src/i18n/en/dialog.ts— the$TC_DPremove-row dialog's title and message, a different key pair from the generic ledger's.Mech/SoftNcRunnerController.cs— 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.
HiAPI Engine
HiMech/NcParsers/Dependencys/Siemens/SiemensToolOffsetTable.cs— 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.HiMech/NcParsers/Dependencys/Siemens/ISiemensToolOffsetConfig.cs— the two-key offset contract: the try-form that is the only miss signal, and the direction-indexed and radius accessors nothing calls.HiMech/NcParsers/Dependencys/Siemens/SiemensToolOffsetTableProxy.cs— the seedless get-or-create placeholder the Siemens preset carries, and the bare table it installs into a project holding none.HiMech/NcParsers/SoftNcRunner.cs— the five brand presets, of which only the Siemens list carries that proxy, and the proxy resolution every read of the table goes through.HiMech/NcParsers/NcRunnerSuit.cs— the per-case list this table is stored in, and the proxy wiring the three writes re-run.HiMech/NcParsers/LogicSyntaxs/Siemens/SiemensToolOffsetSyntax.cs— theDpath: 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.HiMech/NcParsers/LogicSyntaxs/ToolHeightOffsetSyntax.cs— the ISO sibling, and the shared composition of the height as one replaceable entry of the transform chain.HiMech/NcParsers/Semantics/ToolChangeSemantic.cs— 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.HiMech/NcParsers/PostLogicSyntaxs/RadiusCompensationSyntax.cs— the radius side, which reads the generic single-integer table rather than this one.HiMech/NcParsers/Dependencys/Generic/ToolOffsetTable.cs— the fallback source: the subtracting effective values, and the zero it answers for an offset number it does not hold.HiMech/NcParsers/Dependencys/Generic/ToolOffsetTableProxy.cs— the placeholder every brand preset carries, which is why the fallback always resolves a table on Siemens.
See Also
- Program Data Plane — the plane both leaves sit on, where this table is stored, and what a brand switch does to it
- Tool Offsets — 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