THE PLATFORM VIEW

The Platform Schematic

One NX sheet holding the whole platform's logical architecture — fluid, electrical, signal and thermal together, each in its own colour — with every block showing how much of itself the domains have actually implemented. It is generated from a live Teamcenter read, it is scored against the built drawing rather than against the intent, and it runs on a product it has never seen.

The gap it fills

Capital owns the electrical view. NX Schematic Designer owns the fluid view. Both are excellent, and both are silos: neither can draw the wire that leaves a controller as a signal and arrives at a pump that also carries fuel. The place those domains meet is exactly the place nobody had a picture of.

The Platform Schematic is that picture. It is not a replacement for either tool — it holds no pipe sizes and no conductor gauges. It holds the logical architecture, which is the one thing all four domains genuinely share, plus the status of everything downstream of it.

The Inverted Flight fuel system as a platform schematic: twelve logical blocks, fluid lines solid and signal lines dashed, each block carrying a domain implementation band, with the status panel bottom-left and the Siemens title block bottom-right
Item 041908, the Inverted Flight fuel system. Blue is fluid, orange is electrical, green is signal — the same hue on the wire, on the port it lands on, and on the badge row that scores it. Nothing here was placed by hand.
ONE MODEL, THREE READINGS — EACH ANSWERS A DIFFERENT QUESTION L0 — the architecture platform_041908.prt Tank Pump FCU Engine every domain on one sheet solid = fluid dashed = signal "what is connected to what, across all four domains" L2 — the same sheet, scored + --coverage --history Pump FLUID 2/2 SIGNAL 2/3 STATUS PANEL rollup by domain open, by block open cells over time the gate's verdict "is each domain done, and are we converging" L3 — one block, opened detail_ElectricBoostPump.prt port NX CAP SW fuelIn.1 pwrIn.1 cmdIn.1 "which tool still owes this port, and what did it land on" All three are generated from one Teamcenter read. None of them is drawn by hand, and none is edited to stay true. tc-to-logical-blocks 040819 → make-platform-sheet → build_logical_platform.py (NX) → readback_for_gate.py → audit-port-coverage --nx-part The badge is not the generator's opinion. It is the gate's verdict, and the gate scores the BUILT part. The last step reads the saved .prt back and asks what each wire actually landed on — a declared port, a borrowed one, or an anchor NX minted at commit. A block whose badge reads 2/3 is 2/3 by measurement of the artifact, not by counting what the generator intended to do.
One model, three readings. L0 is the architecture, L2 is the same sheet with every block scored by domain, L3 opens one block into a column per tool. All three are generated from one Teamcenter read.

Four domains on one sheet, and the trap underneath it

NX Diagramming will do this, and the mechanism is not the one the UI implies. Discipline is a property of the OBJECT, not of the sheetNodeBuilder, ConnectionBuilder and PortBuilder each take SetDisciplines(), so one sheet legitimately carries a Piping pump, an Electrical bus and a Data link side by side.

DISCIPLINE is not validated, and that is the whole reason the coverage gate exists SetDisciplines(["Banana"]) is accepted and round-trips. NX will let a fuel line land on a signal port and report nothing, which means a drawing that looks right is not evidence that it is right. Every claim on this page is checked by reading the saved part back, never by trusting the write.
DomainDrawn asComes from
fluidPiping, solid lineSeg0Implements → Seg0Interface, or the port-name token registry
electricalElectrical, dashedsame two carriers
signalData, dashedsame two carriers
thermalThermal, tracedsame two carriers

The interface relation is semantic truth and only exists where it was authored; the name token is lossy but survives every hop. Both are read, the conflict is reported rather than resolved silently, and each port records which carrier answered.

L2: the badge reads in DOMAINS, not in tool names

Each block carries a band showing how much of itself is implemented. The first version labelled those rows NX, CAP, SW — the tool that owes the work. That is the right answer for whoever has to go and do it, and the wrong answer for whoever is reading a platform.

A systems engineer does not ask how Capital is doing. They ask whether the electrical architecture is settled — and the tool that answers that question will change (a Polarion route for signals, a second thermal tool) without the question changing. Domain is the durable noun; the tool is this month's answer to it.

Two logical blocks on the platform sheet, each with a band inside showing FLUID, ELEC and SIGNAL rows with a filled bar and a count
Every block scores itself by domain, and the badge row is drawn in the same hue as the wires it is scoring. ElectricBoostPump is complete on fluid and power and 2 of 3 on signal; FuelControlUnit is 6 of 9. The bar is for the across-the-room glance, the n/m is the authoritative reading, and both are the gate's numbers rather than the generator's.

Nothing is lost by aggregating. A domain row sums the branches that can carry it, and it carries the list, so SIGNAL 2/3 can always be asked "of what?" — the answer is on the L3 sheet, which keeps the tool columns precisely because L3 is where you go to find out who still owes it. The two levels now split cleanly: L2 asks whether the domain is done, L3 asks who is holding it up.

The status panel — and why it is not a full accountability table

The obvious ask is a row per block per domain. On the fuel system that is 34 rows; on the CubeSat it is 108, and it would still be 108 rows on the day everything is finished. A table whose size is set by the model rather than by the problem gets read once. Worse, it duplicates the badges — every one of those rows is already drawn inside the block it describes.

So the panel carries the readings a badge cannot give, because they are properties of the whole sheet:

The status panel: a rollup by domain, a list of open blocks, a burn-down of open cells over time, and the gate's verdict
Bottom-left of every sheet, opposite the title block. Rollup, working list, trend, verdict, and the date of the ledger it was drawn from.
SectionWhat it answersWhy it is on the drawing
Rollup, one row per domainIs the fluid architecture settled?No single block can answer a question about the platform.
Open, by blockWhat do I do next?The named reason a rollup row is not 100%. Capped at seven and the overflow counted — "+15 more" is information; a quiet cut is a lie.
Open cells over timeAre we converging?A rollup says what is open today and cannot say whether today is better than last week.
The gate's verdict, verbatimWould this pass?Restating it in the panel's own words is how the two drift apart.
The burn-down is derived from git, never kept A snapshot directory has to be maintained, and one missed run puts a straight line through the gap that reads as "no change that week" when it means "nobody measured that week". bin/coverage-history.mjs reads past versions of the ledger out of the commits that produced them, so every point is a real measurement taken at the date it claims and cannot be back-filled. The ledger itself deliberately carries no timestamp — it is a pure function of its inputs, so an unchanged rerun produces no diff, and that is exactly what makes the derived series meaningful. The panel dates it from the file instead.

Drawing it found a defect that reading it had not

The panel's first build read 31 open two lines above the gate's own verdict reading 29 blocking — on the same sheet, in the same plot. Both numbers were computed correctly by their own rules, and the rules disagreed: the panel counted with the badge's bucket map, and UNWIRED_IN_SOURCE had been added to the gate one commit earlier and never to that map, so it fell through to the "problem" default.

Two fixes, and the second is the one that matters. The panel now counts with BLOCKING_STATES — the gate's own policy — so the two numbers cannot diverge by construction rather than by vigilance. And the bucket map gains the state explicitly as missing, because the default had been drawing it hatched: "it landed but does not reconcile", said about a port the source model never wired in the first place.

Colour: hue is identity, never status

The sheet is drawn in colour by domain, and the colour was in the model the whole time. NX ships LineTypes carrying strokeColorPrimary.xml, which every fluid wire here already used, is 0 0 225 — and the plot was throwing it away at the last step with PrintPDFBuilder.Color.BlackOnWhite. Measured rather than assumed: the same part plotted with PartColors produced 18,083 saturated pixels against 7,177, and all of the latter were the Siemens logo in the title block.

The shipped palette is still the wrong one for this drawing, because it was chosen for P&IDs:

DomainNX shipsWhy that fails hereUsed instead
fluid0 0 225 blue0 114 178 blue
electrical0 153 255 cyana second blue, beside the domain it sits next to most often213 94 0 vermillion
signal0 0 0 blackthe colour of every outline, stub and label — the commonest domain reads as "not a domain"0 158 115 bluish green
thermal255 0 0 redred is what a drawing should keep for something is wrong230 159 0 orange

The replacement palette is Okabe-Ito, which stays distinguishable under the common forms of colour blindness, and it deliberately leaves red unassigned.

Hue is identity. Status keeps the channels it already had. Colour answers "which domain is this", never "is it finished". Status already has three redundant encodings — bar fill, hatching, and the n/m count — every one of which survives a mono plot and a photocopier. Putting hue on status as well would give one channel two meanings and turn a schematic into 37 traffic lights with an architecture somewhere behind them. A block whose FLUID badge is empty is drawn in exactly the same blue as one whose badge is full. Line font and width are untouched, so nothing is lost when this prints in black and white.

The status panel's rollup rows are drawn in their own domain's hue, so the sheet carries its own key rather than needing a separate legend box. Colour is opt-in behind --colour: without it an existing pipeline plots exactly what it plotted yesterday.

The trap: a line type is resolved by library, not by path

SetLineType takes an absolute path, and it is ignored unless that path sits inside the library tree Schematic Designer indexes. Nothing reports this. The path exists, so an os.path.exists check passes. Commit() succeeds. And GetLineType() reads back an empty string on a committed connection whether it worked or not. The only symptom is that a wire keeps the colour its discipline would have given it — which looks like a perfectly plausible drawing.

Two experiments settled it, in the order that eliminates the most:

ExperimentResultWhat it eliminates
Force every wire to the shipped Tracing.xml (red)10,075 red pixelsSetLineType works at all
Copy our file into the shipped folder16,780 palette-blue pixels, zero NX bluethe file was fine; the folder was the problem
And the one that actually explains it: a discipline NX has never heard of Signal wires plotted black for a while after all of the above was working, and the cause was none of the things it looked like. NX honours a connection's line type only when the connection's discipline is one the library configuration declares, and the shipped file declares exactly four: Piping, HVAC, Hydraulic, Electrical. This pipeline draws signal on Data and thermal on Thermal. An undeclared discipline has no <LineTypePath>, so there is nowhere to resolve a line type from and NX falls back to black — while SetDisciplines accepts the value, Commit succeeds, and the connection reads back carrying Data. Thermal was black for the same reason and nobody had noticed, because the only thermal port in either model is unwired.
SuspectedTestVerdict
the folderfluid and electrical from the same directorycolour fine — not the folder
stroke widthforce NX_Width_6 to match the working onesno change
line prioritydrop it to a low valueno change
the RGB valuetry magenta insteadplotted black too
the disciplinechange ONE wire from Data to Piping, holding the line type0 → 2,217 green pixels
Two of those wrong answers came from a measurement error, not from NX Whole-sheet pixel counts include the badge labels and port glyphs, which are always coloured because they are drawn from our own SVG rather than by a line type. Counted that way, two failed experiments looked like successes. Only a crop containing nothing but the two sensor→FuelControlUnit wires is diagnostic — and that region read 0 green against 17,103 black on every attempt until the real fix. Choosing the measurement is part of choosing the experiment.
The upper left of the fuel system sheet: two sensors connected to the fuel control unit by green dashed signal wires, an orange dashed electrical wire, and a blue solid fluid line, each matching the port glyph it lands on
After the fix. Green dashed signal, orange dashed electrical, blue solid fluid — each wire the same hue as the port glyph it lands on and the badge row that scores it.

The fix is a generated library configuration that clones the Electrical discipline entry once per missing discipline, installed by bin/install-nx-library-config.mjs. It modifies one file in the NX installation and says so; the original is backed up into the repository and --revert puts it back. The file carries the read-only attribute rather than restrictive permissions, which is why the first attempt read as "Permission denied" and looked like it needed elevation.

The supported customisation route does not work here, and that is worth knowing before trying it NX's own header comment says to copy the configuration out and point the customer default Schematic_Library_ConfigurationFile_WIN at the copy. The preference exists — found by enumerating all 23,045 customer defaults rather than guessing names — and is read at startup, so UGII_USER_DIR is the only hook available. A minimal .dpv placed there is simply not read: with the file present and the variable set, the preference still returned the shipped path. The shipped .dpv files carry Application/Category/SubCategory/Tab attributes that are not discoverable from the API, which is the likely reason.

Scored against the built part, not against the intent

The coverage gate originally graded the spec — what the generator meant to draw. That is grading your own homework: anything the generator gets wrong is wrong identically on both sides of the comparison. It now reads the saved .prt back and scores the artifact, which was possible only after establishing that a built part really does retain port identity.

VerdictMeans
REALIZEDbound to a declared attach point that can carry this port's domain
DEGRADEDit landed, but not provably on the right thing — a borrowed port, or an anchor NX minted at commit
MISBOUNDit landed on something of the wrong domain
NO_ARTIFACTthat branch has produced nothing for this block at all
UNWIRED_IN_SOURCEthe model never wired the port, so nobody downstream was ever asked for it — not blocking, and stated separately so it cannot inflate the realized count
WAIVEDa blocking state a named person accepted on the record
A gate with no waiver path gets switched off the first week it blocks a release A waiver is honoured only when it carries a branch, a state, an owner, an ISO date, a reason, and a specific component — there is no wildcard across components, because waiving something is supposed to mean a person looked at that thing. Three are on the record today, all the same finding: the shipped Standard_tank symbol declares zero ports, so no wire on a tank can be proved to be on the right nozzle. That is the symbol working as designed, and a tank is worth drawing as a tank.

What retyping one port revealed

The CubeSat's thermistor declared tempOut, and the token registry read temp as thermal. A domain says what a port carries, and a thermistor carries a measurement, not heat — the heat flows into it. Typed thermal, the model read Thermistor.tempOut → Radiator.heatIn: heat flowing out of a sensor into a radiator, backwards, and consistent-looking, so nothing flagged it. It also made Thermistor.tempOut → Heater.ctrlIn cross-domain, which the P&ID refused to draw, leaving the heater's command input unrealized.

Moving one token fixed the heater and, more usefully, made the remaining error visible instead of fixing it. The radiator wire is now reported cross-domain, next to a second one nobody had noticed: Thruster.cmdIn ← Valve.propellantValveOut, a command input wired to a propellant outlet. Both are defects in the source model, and neither is the transform's to paper over.

It runs on a product it has never seen

Nothing above is fitted to the fuel system. The second product — a 37-block CubeSat, four domains, structurally nothing like an aircraft fuel system — was run to find out what had been quietly hard-coded, and it found five things.

The CubeSat platform schematic: 37 logical blocks across electrical, signal, fluid and thermal domains on one E-size sheet with the status panel bottom-left
CubeSat 040819, generated by the same four commands. E size, chosen by the layout rather than configured: the sheet escalates B→C→D→E and keeps the first size its own self-check reports no warnings on.
What the second product brokeWhat it had been assuming
Four wires silently droppedPorts are single-use. The fuel system happens to have no fan-in; the CubeSat feeds one controller input from two sensors. The symbol XSD's own default is unlimited.
Six blocks off the bottom edgeA D-size sheet, hard-coded from the only model that had ever been drawn.
A meaningless AMES badgeThe label table listed only the branches the fuel system exercises; thermal routes to Amesim.
An 8-port symbol for a 4-port pumpExchange bundle ports are per-occurrence, and the same port appears once per occurrence path.
Four L3 nodes overwriting each otherA symbol's file identity and its display name are the same string. Four ports of one block landing on the same symbol generated into one folder, and every node silently used whichever was written last.

Only the last was caught by a person reading output; the rest were caught by the gate refusing to agree with the drawing. That is the argument for scoring the artifact in one sentence.

Running it

# 1. Teamcenter -> logical blocks, ports, domains, wires
node bin/tc-to-logical-blocks.mjs --item 040819 --out out/nx/040819-blocks.json

# 2. the burn-down series, out of the commits that produced each ledger
node bin/coverage-history.mjs --ledger out/nx/cubesat/CubeSat040819.portcoverage.artifact.json \
     --out out/nx/cubesat/CubeSat040819.history.json

# 3. symbols + layout + status panel -> the NX journal spec
node bin/make-platform-sheet.mjs --blocks out/nx/040819-blocks.json \
     --symbols out/nx/cubesat/sheet-symbols \
     --coverage out/nx/cubesat/CubeSat040819.portcoverage.artifact.json \
     --history  out/nx/cubesat/CubeSat040819.history.json \
     --out out/nx/cubesat/platform-040819-spec.json --out-dir '...\out\nx\cubesat'

# 4. draw it (interactive NX - see below), then read the SAVED part back and score it
NX_SCHEMATIC_JOURNAL=tools/nx/build_logical_platform.py tools/nx/run-nx-schematic.sh ...
node bin/audit-port-coverage.mjs --master exchange.json --nx-part artifact.json --gate

--badge-by branch restores the tool labels if a site wants them. --panel-scale trades panel legibility against sheet size: at full block scale the panel pushed a twelve-block system from a D sheet to an E.

Step 4 is the one thing that cannot run headless Schematic Designer is reachable only from an interactive ugraf.exe through the USER_STARTUP user exit. run_journal.exe accepts ApplicationSwitchImmediate, returns without raising, and leaves ApplicationName at APP_NONE — so the switch looks like it worked and the reuse library rejects you several calls later, pointing at a different subsystem entirely. For the Dispatcher this is solved by a long-lived NX session host consuming a directory queue, because a Dispatcher service cannot start a GUI. See the NX branch page.

Not done

  • Two branches have produced nothing. 6 cells on the fuel system and 29 on the CubeSat are NO_ARTIFACT against software and Amesim. Both gates therefore still exit non-zero, on that alone. They are deliberately left red rather than waived: nobody has decided those domains are out of scope.
  • NX-native click-through between levels. The descent is stamped into the drawing (DRILL_TO, DRILL_LEVEL) and reads back out of the saved part, but a user follows it by opening the part the stamp names. Binding it to a symbol needs Schematic Designer runs, which the shipped templates do not set up.
  • The panel is drawn, not linked. Its numbers are a snapshot of the ledger at plot time. A sheet reopened a week later still shows the week-old panel — correct for a drawing, and worth knowing before treating it as a dashboard.