Color Guide System
A step's color comes from the project's color index — the one step property the Color Index Time Chart plots and the 3D view colors the tool path and the cut faces by — and from the color guide under that key: a color scale over an amount, one color per ID for a key that names something, or the leaves of a color tree. A color tree is how steps are colored by condition: conditions over the step properties, held as data and evaluated as each step is built, so that the views draw what each step stored. A guide written in code, an IColorGuide(API), is the advanced path, for a coloring no tree can state.
What Colors a Step
- The color index. InspectingKey(API) names one step property and is saved with the project. The Color Index Time Chart plots it, and the guide under its key colors the tool path and the cut faces; every view of the project follows it.
- The guide under it. The project keeps its guides by key, in
DictionaryColorGuide(API), and the color
index selects the one under its key, building one when the key has none:
- an amount — a ratio, a force, a feedrate — gets a color scale, FuncRangeColorGuide(API), from a floor to a ceiling, the range set on the Color Index Time Chart's Colors dropdown (Watching the Run);
- a key whose value names something — the tool ID, the file number — gets one color per ID (One Color per ID);
- a color tree's key gets one color per leaf of the tree (Color Trees).
- When a tree is the right tool. A color scale shows how much of one amount a step has, and an ID which tool or which part of the program a step belongs to. When the question is which case a step is — rapid, above a limit on any of three ratios, cutting within them, anything else — the answer is several conditions over several step properties, read as categories: a color tree, data that is checked and previewed before it is applied and saved with the project.
Color Trees
A color tree (StepColorTree(API)) is a step property whose value on a step is the id of the leaf the step falls into, with a color per leaf. Its key shares the key space of the step variables, and the property is categorical: as the color index it is colored one color per leaf, by the tree's own hues. The project keeps its trees in StepColorTreeList(API), saved with it and registered from the load on, in every session; a mission script registers one for its session (Defining a Tree).
How a Step Is Sorted
- The first node that holds takes the step. The nodes of a list are tried in order. A leaf (StepColorLeaf(API)) whose condition holds gives the step its id; a group (StepColorGroup(API)) whose condition holds hands the step to its own children, and only they are tried then. A node without a condition takes every step that reaches it: the list's “otherwise”. A step no leaf takes — no node of a list held, or a group took the step and none of its children did — has no value under the tree's key, and the 3D view draws it in the no-value green.
- A condition (StepColorCondition(API)) joins its terms by
AllorAny; without terms,Allholds for every step andAnyfor none. A term (StepColorTerm(API)) names a step property by its key and compares the step's value with a threshold —Gt,Ge,Lt,Le,Eq,Ne— or asks whether the step has a value under the key,HasValueorNoValue, with no threshold (StepColorOp(API)). - No value. A step without a value under the key — a number that is null or NaN, a text that is
null or empty — satisfies no comparison,
Neincluded;NoValueholds for it. A yes / no property reads as 1 or 0, soEqwith 1 asks for yes, and a text reads as a value when it is not empty. The physics values have none on a play without the physics, so a tree over them passes those steps on to the nodes below. - What a term can name. Any step property: the built-in ones, the machine coordinates, the
project's step variables, and the keys a script, a mapping or a training registers. The built-in ones
include the yes / no
IsRapid(commanded as a rapid traverse) andIsTouched, and four that span steps:SpindleOverloadRevolutionsandYieldingStressOverloadRevolutions, the spindle revolutions of the run of consecutive steps above 100 % the step is in,SpindleOverloadPeakRatio, the peak of the spindle's run, andIsRapidCut, a rapid step that removed stock (Step Field Reference). A key that matches no step property reads as no value, and the session messages say so after the play. A tree's own key, or another tree's, reads as no value too: a tree is not read inside a tree.
Leaves: Hues, Ids and Values
- The hue. A leaf's Hue_deg(API) is the HSL hue of its color in degrees, drawn at full saturation and half lightness: a leaf's color is a hue alone. A hue picked by hand may be any, red included; a value outside 0 to 360 is taken modulo 360, with a warning, and NaN or an infinity counts as none. A leaf without one gets an automatic hue from its id, the one the id has under one color per ID, which keeps off 300° to 30° and 220° to 270°.
- The id. A node's Id(API) is from 1 to 1,048,575 (2²⁰ − 1). A node without one is numbered from the tree's NextId(API) when the tree is written, cloned or compiled — 1, 2, 3… in tree order for a tree built without ids, the same numbers every time — and a number is never given to a second node, so a deleted node's number stays free. The leaf's id is the tree's value on a step: what a step file writes under the tree's key, and what each step stores while the tree is the color index.
- The value key. A leaf may name a step property, ValueKey(API), whose values the Color Index Time Chart draws for the leaf's steps: a band inside the leaf's lane, from the least to the greatest value of each bucket, scaled between the least and the greatest of the leaf's values shown. The colors read the id alone, so a value key changes no color: the leaf's steps keep its one color in the 3D view. A value key that is a text shows 1 for a text and no value for none.
- Where the leaves show. While a tree is the color index, the 3D view draws each step in its leaf's
color, the Color Index Time Chart draws a lane per leaf — the first leaf at the top, named on the axis,
each lane kept whether or not a step fell into it — and the chart's Colors dropdown lists the
leaves with their steps and time share over the display range
(Strip Charts). Step Properties
prints a tree's row as the leaf's id and name —
4 · Withinfor the tree below — and-for a step in no leaf.
Refusals and Warnings
A tree that cannot be evaluated as written is not registered; one that can, however odd, is registered as it is, with a warning. FindProblems(API) lists a tree's problems, each with its place in the tree, without reporting them.
- Refused — a tree without a key (a script's is reported; a project's is skipped silently, absent from the Step Colors root and the HTTP API's list), a term without a key, a comparison without a threshold, an id repeated or out of range, and a tree this version does not understand: an element, a comparison or a join it does not know, or a number it cannot read. A project's refused tree stays in the project and is saved back as written.
- Under a key that is taken, and not registered — a key named like a built-in step property, or
under
MC., which the machine coordinates reserve; for a project's tree, a key a project step variable or an earlier tree has; for a script's, a key the project defines a step variable or a tree under. - Registered with a warning — more than 32 leaves, past which the Color Index Time Chart no longer
stripes a pixel column by leaf (it still draws a lane per leaf), a list without a node that takes every step, a node after one that takes every step, which no step reaches,
a condition without terms, a group without children, a hue outside 0 to 360, a
NextIdoutside its range. - Checked against the step properties of the moment, on a tree as it is edited, by the Step Colors
root and the HTTP API's
check— a color tree's key, which reads as no value; a physics key the interface hides, which the tree reads all the same; a key no step property has yet, which reads as no value until a play or a script registers it; a value key not a quantity. - Put right on the load — a node without an id, or with one another node has or that is out of range, is numbered on the load, with a warning, and saved back with the number it got.
Each is reported but a project's tree without a key: a project's with the messages of its load, of each session's begin and of a refresh; a script's once per key in the session's messages. A key whose read throws on a step leaves that step in no leaf, said once a session.
Defining a Tree
A tree is defined in one of four places, each described on its own page:
- On the Execution page, under the Control Tree's Step Colors root, with a check and a preview before Apply: Step Colors.
- Over the web service's HTTP API, under
/api/execution/step-color-trees, with the same check and preview: Step Color Trees over HTTP, and from a question to a colored run in Workflow: Coloring Steps by Condition over the HTTP API. - In a mission script, for the session alone: RegisterStepColorTree(API) registers a tree that goes with the session and is never saved (Registering a Color Tree); a host does the same with RegisterStepColorTree(API).
- Over the SDK, in the project's list, below.
The check reports a tree's problems, and the preview sorts the played steps under it — the steps and the time each leaf takes, and the nodes one step went through; neither changes the project, the color index or the colors. A tree colors the run once it is the color index.
What is registered is a compiled copy of the tree, so a change to the tree object changes nothing until the tree is registered again. The tree the examples use, built as a script or a host builds it; its threshold, 1 for each ratio's 100 %, only illustrates the shape and is no limit HiNC sets:
StepColorTerm Above(string key) => new(key, StepColorOp.Gt, 1);
var tree = new StepColorTree
{
Key = "RatioCheck",
Name = "Ratio check",
Nodes =
[
new StepColorLeaf { Name = "Rapid",
When = StepColorCondition.All(new StepColorTerm("IsRapid", StepColorOp.Eq, 1)) },
new StepColorGroup { Name = "Ratios known",
When = StepColorCondition.All(StepColorTerm.HasValue("YieldingStressRatio")),
Children =
[
new StepColorLeaf { Name = "Above 100 %", Hue_deg = 0,
When = StepColorCondition.Any(Above("YieldingStressRatio"),
Above("MaxSpindleTorqueRatio"), Above("MaxSpindlePowerRatio")) },
new StepColorLeaf { Name = "Within", ValueKey = "MaxSpindlePowerRatio" },
] },
new StepColorLeaf { Name = "Other" }, // no When: every step that reaches it
],
};
In the project file the trees follow the step variables, under StepColorTreeList, written only when
the project has one; a group holds its condition, then its children. The same tree, as saved:
<StepColorTreeList>
<StepColorTree Key="RatioCheck" Name="Ratio check" NextId="6">
<Leaf Id="1" Name="Rapid">
<When Join="All">
<Term Key="IsRapid" Op="Eq" Threshold="1" />
</When>
</Leaf>
<Group Id="2" Name="Ratios known">
<When Join="All">
<Term Key="YieldingStressRatio" Op="HasValue" />
</When>
<Leaf Id="3" Name="Above 100 %" Hue_deg="0">
<When Join="Any">
<Term Key="YieldingStressRatio" Op="Gt" Threshold="1" />
<Term Key="MaxSpindleTorqueRatio" Op="Gt" Threshold="1" />
<Term Key="MaxSpindlePowerRatio" Op="Gt" Threshold="1" />
</When>
</Leaf>
<Leaf Id="4" Name="Within" ValueKey="MaxSpindlePowerRatio" />
</Group>
<Leaf Id="5" Name="Other" />
</StepColorTree>
</StepColorTreeList>
The rapid steps go to Rapid first. On a play without the physics the ratios have no value, so every
other step fails the group's condition and goes to Other; with the physics, Within draws the max
spindle power ratio of its steps as a band in its lane.
Editing the project's trees over the SDK. Replace StepColorTreeList(API) with a new list rather than changing it in place — it is enumerated while the project is saved — and call RefreshStepVariables(API) after it: the refresh compiles the project's step variables and color trees again, registers them in place of the ones registered before and reports what it does not register. A host that edits from more than one thread takes StepDefinitionEditLock(API) around the edit and the refresh that follows; the refresh takes the same lock itself, and readers of the step properties never take it.
// lps: the LocalProjectService the project is loaded in; tree: the StepColorTree above
lock (lps.StepDefinitionEditLock)
{
lps.MachiningProject.StepColorTreeList = [.. lps.MachiningProject.StepColorTreeList, tree];
lps.RefreshStepVariables();
}
When a Tree Is Evaluated
A tree is evaluated once per step, not once per frame. Each step keeps one stored value of the color index — for a tree, the leaf the step fell into and the leaf's value — computed as the step is built, after the step-built handlers have run. A background scan computes it again for every step whenever what it was computed from changes: another color index, a step variable or a tree registered or refreshed, a session's begin, a recompute of the step physics or a change to the environment the physics reads, values written onto the steps. Until the scan has reached a step, the step's stored value reads as no value. A run of overload is known only when it closes, and a rapid line that removed stock only when the line ends, so the stored values of a tree that reads them are computed again for the steps of a run or a line as it closes; until then the steps of an open run hold the running total as it stood when each was computed.
The 3D recolor and the Color Index Time Chart read the stored values: neither evaluates the tree per redraw. Step Properties, a step file and a preview evaluate the tree for the steps they show, from the steps themselves.
An edit that changes only the names and the hues of a project's tree — on the Step Colors root or over the HTTP API — keeps the stored values: the leaf ids are the same, so nothing is computed again, and the colors and the names follow at once. Any other edit, and every refresh over the SDK, registers the project's definitions again, and the scan follows.
A tree reads every key it names on every step it sorts, whichever node takes the step, and the scan reads what the steps keep in the step cache a segment of steps at a time.
Values written onto steps after they were built. A value a step-built handler — a script's
SessionStepBuilt(API), a host's
MachiningStepBuilt(API) — writes onto the
step being built is in the step's stored value as the step is published. A batch written with
WriteStepVariables(API) marks the stored values
stale once per batch, a single step["key"] = value after the play marks them itself, and so does a
mapping of measured data onto the steps; the scan follows. Host code that writes through the indexer
onto an earlier step while a play runs calls
NotifyStepValuesWritten(API) once its
writes are done: such a write can travel to the step cache with that step's own results, and then marks
nothing stale.
One Color per ID
A range guide such as FuncRangeColorGuide(API) maps an amount onto a color scale between a floor and a ceiling. A step property whose value names something rather than measures it — the tool ID, the file number and a color tree's key (Color Trees above), the properties IsCategorical(API) marks — is colored by FuncDiscreteColorGuide(API) instead: one color per ID, with no range to set. The file number counts what a run reads rather than files: each top-level NC file, each subprogram or macro call — every repetition included — and each jump that reads a program again, such as a GOTO or the next pass of a loop, takes the next number.
- Without a hue table the color depends on the ID alone — the guide of the tool ID and the file number — so an ID has the same color in every program, every run and every project. A step with no ID — no value, a value that is not a 32-bit integer, or a value function that throws — gets the no-value green, NoValueGreen(API).
- The ID decides only the hue (GetDiscreteHue(API)); saturation and lightness stay at 1 and 0.5, the values the color scale uses. Each next ID moves on by 0.618 of the usable hues, the fractional part of the golden ratio, measured by perceived color difference rather than in degrees. IDs next to each other, or 2 or 4 apart, differ widely; IDs 5, 8, 13 or 21 apart — the Fibonacci numbers from 5 on — can come close, the closer the larger the gap: 3 and 16 are both blue, 1 and 22 both cyan. From ID 378 on, two IDs can round to the same 8-bit color: 1 and 378 are both
#00FFFF. - Without a hue table two hue ranges are never used: 300° to 30°, from just past magenta through red to red-orange, where the colors that already carry a meaning sit — the red of a collision or of a value at a color scale's ceiling, the pale pink of a NaN value, the pale purple of a step not yet colored; and 220° to 270°, deep blue to blue-violet, which a dark 3D background swallows.
- A hue table. The guide built with one — the two-argument constructor, the table read back as IdHues(API), an HSL hue from 0 to 1 by ID, kept as given and only read — draws an ID the table names in that hue at full saturation and half lightness, and the table may name any hue, red included. An ID outside the table is colored as without one, by GetDiscreteRgb(API), and a step with no ID still gets the no-value green. GetIdRgb(API) is the color of an ID under the guide's table; the static GetRgb(API) reads no table. A color tree's key gets such a guide, its table the hues of the tree's leaves by their ids, and its IDs the leaf ids the steps stored.
- No priority to rank by. The guide gives every step a color, with a NaN attachment priority, since an ID has no magnitude.
- Run time only. The guide is built when its key is inspected and is never written to a project file (see XML Serialization). A guide a script or the SDK puts under the tool ID or the file number at run time is used as given until the project is read again; reading a project drops any guide stored under those keys — a color range an older version saved, or a guide a script or the SDK saved there — so the key gets the one-color-per-ID guide again. Under a color tree's key, a color scale stored there — one a file saved before the key was a tree's — gives way to the tree's guide when the key is inspected; the tree's guide is built anew when the tree is compiled again or its hues are edited, and gives way in turn to the guide of the key's kind when the key is no tree's any more.
Writing Your Own Guide
A guide is an IColorGuide(API) a host's code implements: it colors a step by whatever the code computes from it. It is the path for a coloring no tree can state — a color mixed from several values, a gradient inside a category — and the advanced one for coloring by condition, for the reasons at the end of this section.
The contract. The 3D view hands the guide the step, a
MachiningStep(API), and asks (GetColorGuide returns the guide
itself):
| Member | Answers |
|---|---|
| TryGetRgbWithPriority(API) | The step's color and its attachment priority — the call the 3D view makes. True when the guide has a color for the step; false, and the step is drawn white. The priority is set on either answer. |
| TryGetRgb(API) | The same color without the priority. |
| MakeXmlSource(API) | The guide's XML for the project file, or null for a guide that is not saved. |
The shipped guides answer true for every step and give a step without a value a color of their own — one color per ID the no-value green, NoValueGreen(API) — so that no step falls back to white.
The attachment priority. The cube tree (CubeTree(API)) — the cut faces — reads it, and nothing else does. Where the faces are drawn coarser than the cuts, so that one node of the tree holds the cuts of several steps, the node is drawn and picked as the step with the highest priority; NaN ranks lowest. A node takes its step as the cut is attached, by the priority the step carried then — the one the selected guide gave as the play added the step — so a recolor changes a node's color, not which step the node shows. A color scale ranks a step by its value, so such a node shows the step of the largest value; one color per ID and a tree's leaves rank nothing.
Using a guide. The project keeps its guides by key in KeyToColorGuide(API) of DictionaryColorGuide(API), and the one under the color index's key is the selected guide. Put the guide under a key in a copy of the dictionary rather than adding it to the one in place, which other threads read, and make the key the color index:
// lps: the LocalProjectService the project is loaded in; guide: the application's own IColorGuide
var colors = lps.DictionaryColorGuide;
colors.KeyToColorGuide = new Dictionary<string, IColorGuide>(colors.KeyToColorGuide) { ["MyScheme"] = guide };
lps.InspectingKey = "MyScheme"; // selects the guide under the key and recolors
When the key is the color index already, call RefreshSelectedColorGuide(API) and RefreshDrawing(API) instead. A selection made on SelectedKey(API) alone gives way to the color index's guide the next time the step properties are registered: a project read, a session's begin, a refresh. Under a key no step property has, the guide names a scheme of its own: the 3D view colors by it, the Color Index Time Chart has nothing to plot for the key, and the session messages after a play do not report the key as unresolved. Under a step property's key, the chart plots the property and the 3D view takes the guide's colors; under a color tree's key, the guide takes the place of the tree's colors in the 3D view.
Saving it. A guide is saved with the project as its MakeXmlSource writes it, and not at all when
that returns null (XML Serialization). When a project is read, each saved guide
is built again through XFactory(API) by its element's name,
from what its XML holds alone, so a function a guide takes at run time is given to it again after the
load. A guide that saves itself is therefore read only by code that registers it before the project is
read — an XName, and a Reg that adds its generator (About XML IO). A project
file that names a guide the reading code has not registered is not read, and the HiNC application
registers only the guides it ships: a guide written for one application returns null from
MakeXmlSource, unless every application that opens the project registers it.
Why it is the advanced path for coloring by condition. The selected guide is asked for a step's color as the play adds the step, and for every step of the strip at each recolor — after a scan, a change of the color index, an edit of the color range — inside a frame, on the render thread. A guide's cost is paid there, once per step per recolor, and nothing between the recolor and the render loop catches an exception a guide throws: a guide must be cheap and must not throw. A guide that reads a step's physics values reads them from the step cache, for every step of every recolor. A color tree is evaluated once per step, off the render thread, when its stored values are computed (When a Tree Is Evaluated); its guide draws the stored leaf ids, and its conditions are data any HiNC application reads back, which the Color Index Time Chart, Step Properties, the step files and the HTTP API know.
XML Serialization
The project file holds the trees (StepColorTreeList, Defining a Tree), then the
color index (InspectingKey) and the guides with the selected key (DictionaryColorGuide, inside
MillingGuide). How an object is written and read back is in About XML IO.
A guide whose MakeXmlSource(API) returns null is not written: DictionaryColorGuide(API) leaves its entry out of the project file, still writing the selected key, so the guide exists only at run time — one color per ID and a tree's guide are such guides. A custom IColorGuide(API) can use the same contract for a guide that is not meant to be saved; one that is saved must be readable wherever the project is read (Writing Your Own Guide).
Source Code Path
HiMech/Coloring/IColorGuide.cs,HiMech/Coloring/DictionaryColorGuide.cs— the guide's contract; the project's guides by key and their XMLHiMech/Coloring/FuncRangeColorGuide.cs,HiMech/Coloring/FuncDiscreteColorGuide.cs— the color scale; one color per ID, with a hue table for a tree's leavesHiGeom/Coloring/ColorUtil.cs—GetDiscreteRgbandGetDiscreteHue, the hue sequenceHiNc/MachiningProcs/StepColorTree.cs,HiNc/MachiningProcs/StepColorNode.cs,HiNc/MachiningProcs/StepColorCondition.cs,HiNc/MachiningProcs/StepColorTreeProblem.cs,HiNc/MachiningProcs/CompiledStepColorTree.cs— the tree, its nodes and conditions, its problems, its XML, and the compiled form a step is sorted byHiNc/MachiningProcs/LocalProjectService.ProjectStepColorTrees.cs— the project's trees and a script's, registered as categorical step properties; the edit of names and hues in place, the check and the previewHiNc/MachiningProcs/LocalProjectService.cs—SelectAndBuildColorGuide; the value each step keeps of the color index, its scan, andNotifyStepValuesWrittenHiNc/MachiningProcs/LocalProjectService.ProjectStepVariables.cs,HiNc/MachiningProcs/SessionShell.cs—RefreshStepVariables;RegisterStepColorTreeandWriteStepVariablesHiMech/CutterLocations/ClStrips/ClStrip.cs,HiCbtr/Cbtr/CbtrPickable.cs— the recolor inside a frame; the attachment priority
See HiNC App Anatomy for git repository links.
See Also
- Step Colors — the Execution page's root where the project's trees are defined, checked and previewed
- Step Colors Branch — how that root is built, and the routes it calls
- Step Color Trees over HTTP — the trees' HTTP routes, their bodies, answers and refusals, and the shared color index
- Workflow: Coloring Steps by Condition over the HTTP API — a tree built, checked, previewed and shown over the HTTP API, end to end
- Step — registering a color tree for a session from a mission script, and writing values onto steps
- Strip Charts — the Color Index Time Chart: a lane per leaf, the value bands, and the IDs of the tool ID and the file number in the colors this page gives them
- Drawing — the rendering unit whose color and priority this system decides
- About XML IO — how a guide and a tree are written with the project and read back