THE NX BRANCH

NX Diagramming: an Authoring Seat, Not a Report

The fluid leg is not "generate a drawing and admire it". Cameo holds the skeleton, Teamcenter holds the definition, and the fuel or hydraulic SME details it out inside NX Schematic Designer — then the detail comes back into Teamcenter as real objects. The loop is closed for both components and wires, with receipts, an undo, and a guard that stops a pipeline the moment anything is lost.

The loop

NX DIAGRAMMING IS AN AUTHORING SEAT, NOT A REPORT 1 Teamcenter 11 fuel components 11 connections the skeleton the architect pushed generate 2 NX skeleton 11 symbols placed 11 wires drawn every symbol stamped with its TC id 3 The fuel SME adds a relief valve, renames, re-wires real domain knowledge that was never in SysML compare 4 The difference stamped = already known unstamped = brand new only the new things get written back new parts and new wires become real Teamcenter objects One-way on purpose Detail never flows back up into Cameo. Teamcenter is permanently a superset of the SysML model. never Cameo
The NX round trip. The generator writes the skeleton, the SME adds the domain knowledge, and only the unstamped additions are written back to Teamcenter.

Four decisions define the contract this was built against. If one of them changes, the build changes.

#DecisionConsequence
1Enrichment goes to Teamcenter, never back to Cameo.Teamcenter is permanently a superset of the SysML model. Cameo is not, and must never be treated as, a complete picture of the fuel or hydraulic system.
2SME components become Fnd0LogicalBlock under the existing revision.Same type the skeleton already uses, so the Capital and MADe branches read the enriched architecture with no changes. Skeleton and enrichment are distinguished by provenance, not by type.
3The schematic becomes a managed NX part item, not a dataset.A first-class Teamcenter object with its own revision and ownership. blocked — managed NX mode is regressed on this machine; the dataset route is shipping instead.
4A later Cameo skeleton push must merge, never delete.The highest-risk item in the whole loop, and not on the NX side at all. See the merge guard below.

★ Why this is the one thing that cannot run headless

Everything else in this repository runs in run_journal.exe. This does not, and the failure mode is worth writing down because it is silent and it points at the wrong subsystem.

SchematicManager.CreateNodeBuilder works fine in batch. Committing a node whose symbol comes from the reuse library does not:

Trying to use the Routing reuse library outside of a Routing application.

The only way into an NX application is Session.ApplicationSwitchImmediate, which NX Open documents as "only supported when running interactively. It is not supported in batch mode." In batch it returns without raising and Session.ApplicationName stays APP_NONE — so the switch looks like it worked, and the reuse library rejects you several calls later, pointing at a completely different part of the system.

The way in: the USER_STARTUP user exit User exits are the one NX execution method that is Interactive-or-Batch and accepts Python journals. tools/nx/run-nx-schematic.sh sets USER_STARTUP, launches a real ugraf.exe, waits for the journal's result JSON, and terminates NX. Roughly 25–30 seconds per build including startup. A user-exit journal gets no argv, so paths arrive as NX_SCHEMATIC_SPEC and NX_SCHEMATIC_RESULT. This generalises: any NX capability needing an application context can never be driven by a batch-only worker.

What it produces

SheetSourceDisciplineResult
InvertedFlightFuelSystem_PID.prtTC item 041908, live readPiping ANSI/ISO, inch, D size11 symbols, 11 connections, 0 errors
InvertedFlightHydraulic_Schematic.prtTC item 042326, live readHydraulic ISO 1219, mm, A113 symbols, 16 connections, 0 errors
InvertedFlightPlatform.prtthe two specs abovePiping, inch, D size2 blocks, tie check 2/2

Counts are read back out of the saved part, not counted in the write loop. Each part is saved with a PDF beside it.

NX ISO 1219 hydraulic schematic with reservoir, pump, filters, accumulator, actuators and directional control valve
The hydraulic sheet on the ISO 1219 discipline. Real fluid-power symbols from the shipped NX reuse library — directional control valve, accumulator, two double-acting actuators, three filters, oil cooler.
NX title block reading Inverted Flight Hydraulic System - Hydraulic and TC 042326
TC 042326 — after promotion, the hydraulic sheet carries a real Teamcenter item, not a file path.

Platform view and drill-down — settled by probing, not by reading API names

The ask was: "I need the ability to go both but have both tie. I can see us wanting to see the full platform and be able to dive into the detail."

The answer is yes, and the mechanism is not the one the API names suggest. Four candidate APIs were probed against a live NX rather than assumed, because existing is not working — the same class of trap as ApplicationSwitchImmediate.

CandidateVerdict
SchematicManager.CreateSheet() — two sheets in one partNo. It does add a sheet and SchematicManager.Sheets grows, but DiagrammingManager.Sheets does not, the new sheet can never be made active (ActiveSheet wants a Diagramming.Sheet), and CreateSheetTemplateBuilder only re-templates an existing one. A sheet made this way is never drawable.
CreateOffSheetConnectorBuilderCommits. The call is SetConnection(ConnectionEndType.End, connection) — end type first — plus a Style, on a connection with one free end via SetEndLocation (SetEndPoint does not exist).
OffSheetConnectorBuilder.SetDestination — the navigable linkBlocked, precisely. "The owning connection of destination off sheet connector is not in a run." NX-native navigation needs Schematic Designer runs, which the shipped templates do not set up. A committed connector also never appears in SchematicManager.OffSheetConnectors (reads 0).
CreateSubNodeBuilder / NodeBuilder.GroupingAllowed / .ExpandedNo. Sub-nodes reject an ordinary symbol ("The selected symbol is not valid as main symbol") — they are for instrument attachments, not nesting. The grouping flags do not exist on this release at all.

So the shape NX imposes is one drawing sheet per part, therefore one part per level — and the tie is the stamp.

This section describes the first, two-block platform sheet That shape held, and the sheet it produces has grown considerably since: every domain on one sheet, a generated port-exact symbol per logical block, an implementation band inside each block fed by the coverage gate's verdict on the built part, and a status panel carrying the rollup, the working list and a burn-down. It has its own page now — the Platform Schematic — including what a second, structurally different product broke when it was run through the same four commands.
SEE THE WHOLE PLATFORM, THEN DIVE INTO THE DETAIL Platform sheet — one .prt Fuel System 041908 Hydraulic System 042326 DRILL_TO DRILL_TO Fuel detail sheet — its own .prt 11 symbols, 11 connections, P&ID (inch) every symbol stampedSOURCE_TC_ITEM 041908 same item as the platform block above it Hydraulic detail sheet — its own .prt 13 symbols, 16 connections, ISO 1219 (mm) every symbol stampedSOURCE_TC_ITEM 042326 same item as the platform block above it Why two files? NX allows exactly one drawing sheet per part. Not a workaround — the shape
One drawing sheet per part, therefore one part per level. The tie between them is the stamp, not a link — and the stamp reads back out of the saved drawing.
NX platform sheet showing two blocks, Fuel System and Hydraulic System
The platform sheet, cropped to its content. Two blocks — deliberately thin, because the information that matters is not drawn: it is stamped. Each block carries SOURCE_TC_ITEM, DRILL_TO and DRILL_SHEET, and those come back intact when the saved part is read again.
tie check: 2/2 blocks tied  (2 to a Teamcenter item, 0 by the detail part alone)
   OK   Fuel System        11 components share its Teamcenter item
   OK   Hydraulic System   13 components share its Teamcenter item

verifyPlatformTie is symmetric and refuses to grade a weak tie as a strong one: it catches a block pointing at a part that is not in the set, a detail sheet about a different Teamcenter item even when the path matches, an empty detail sheet, and a detail sheet no block drills into.

Still open, named exactly NX-native click-through between the two levels needs Schematic Designer runs on the connections before SetDestination will bind, plus a way to read an off-sheet connector back that is not the empty collection. Until then the descent is recorded in the drawing and verifiable from it, but a user follows it by opening the part DRILL_TO names rather than by clicking a symbol.

The return leg, as built

Every symbol the generator places carries SOURCE_ID (the Teamcenter component id), SOURCE_TC_ITEM and the component name. A symbol with a SOURCE_ID is skeleton and already exists; a symbol the SME added has none and is new. That key is already in the parts on disk.

Read-back is complete — nodes and wire endpoints — after three dead ends worth recording:

RouteResult
Schematic.ConnectionBuilder.GetStartNode()returns None on a committed connection, even though SetStartNode created it
Schematic.Portcarries no owner reference at all
Diagramming.NodeBuilder.GetAllPorts()lists only static ports, never the dynamic ones a connection makes at commit
What worksentirely in the Diagramming layer: Diagramming.ConnectionBuilder.Start/.EndDiagramming.PortPortBuilder.GetOwningConnectableElement() → the element. Then join that element to its Schematic node by position — the two layers hand out different object tags but both report the one location set via SetLocation.

The diff was proven on a real edit. A simulated SME placed an unstamped angle valve called ReliefValve, wired it to the pump, and renamed FuelFilter:

nodesUnchanged 11   nodesAdded 1   nodesRemoved 0   nodesRenamed 1   nodesAmbiguous 0
wiresUnchanged 11   wiresAdded 1   wiresRemoved 0   wiresUnresolved 0   wiresArtifacts 1
needsReview 0

added node   VA000004  ReliefValve   reason: no SOURCE_ID
added wire   ElectricBoostPump -> ReliefValve
             reason: wire touches a component with no SOURCE_ID, so the generator did not draw it

Four design points in that diff matter for the write-back:

  • Diff against the spec, not the exchange bundle. The generator deliberately omits the composite whole and out-of-discipline components, so a bundle diff would call them "deleted".
  • A copied symbol inherits SOURCE_ID. Identity is the pair (SOURCE_ID, NX tag). First occurrence matches; every later one is a new component with a stale stamp. Getting this wrong duplicates objects in Teamcenter.
  • Wires are a multiset of (from → to). The fuel system genuinely has two FuelControlUnit → ElectricBoostPump wires — command and power — and a set would swallow one.
  • A wire touching an unstamped component is ADDED, not "unresolved" — it cannot be keyed by id because its endpoint has no id yet.

The obvious implementation cannot work, and it fails silently

fnd0bl_connected_end1 / fnd0bl_connected_end2 are occurrence properties, and neither write route reaches them:

AttemptResult
Core-2007-01-DataManagement/setProperties on the connector's BOMLineHTTP 200, empty partialErrors, both ends still empty on read-back
itemLineProperties on the addOrUpdateChildrenToParentLine that creates that occurrenceaccepted, and likewise ignored

What works is Teamcenter's own operation for this exact gesture: Diagramming-2014-06-DNDManagement/createConnectionPortsAndConnect with connectCase = create2PortsConxAndConnToPorts, then saveBOMWindows, which is what commits. Proven live:

OK   ElectricBoostPump -> ReliefValve
     ports fuelOut1 on ElectricBoostPump, fuelIn on ReliefValve
     connector 042321; model now reports 12 connection(s) with both ends bound
     typing both ports typed to FuelInterface

end1: fuelOut1.1 on 041948/-/ElectricBoostPump  direction=Output implements=041932/-/FuelInterface
end2: fuelIn.1   on 042320/-/ReliefValve        direction=Input  implements=FuelInterface
Three undocumented costs (a) CreateAndPasteInputInfo declares clientId, opType and pasteRelation all as required. Omit any one and the tier answers "An error has occurred during the JSON parsing" — which reads like a malformed body, not a missing field. The same message means the same thing on ItemElementLineInfo2 (needs clientId + occType) and on executeSavedQuery (needs limit). Treat that message as "you dropped a required attribute". (b) The paste target is the BOMLine, not the revision — a revision comes back as "invalid object instance tag … property bl_bomview". (c) The operation does not type the ports it creates. Typing is what makes a port routable, so it is applied afterwards with the same GRM relation and read back to confirm.

Choosing where the new component goes

A Cameo push puts every block in two places — under the owning package (a namespace listing) and under the composing block (the architecture). The 11 fuel components therefore vote 11–11, and the original rule (demand unanimity) could never fire. The tie is broken on the model's own evidence: the existing connectors hang under exactly one of the candidates, and that is by definition the composite where this system is wired.

Do not pick the parent by heuristic "The row with the most children" chose Behavior (12 functions) over FuelSystem (11 components), and filed a fuel component under behaviour. The data answers it instead: the diff's UNCHANGED nodes are exactly what the generator drew on that sheet, so their common BOM parent is the parent. If they disagree, skip and report rather than guess.

Receipts and undo

The write-back mutates the system of record, so every created uid is written to a receipt as it is created — not at the end, because a run that dies half way is exactly the run whose objects need removing. The undo order is not negotiable; each step exists because of a failure that reported success:

Remove the BOM lines first, then saveBOMWindows

deleteObjects on an item that a saved BOM line still references returns "ok" and deletes nothing.

Drop the GRM relations

A related object refuses with error 9043 ("may be checked out, or referenced").

Delete the revision, then the item

Deleting a revision leaves the item as a childless husk that still answers a type query.

Second pass

A port hangs on its block through the block's BOMView revision, so a port whose owner is being deleted in the same run cannot go until that owner has gone.

Every delete is confirmed by re-reading the uid. Round trip proven: apply → verify → undo → the model is back to 11 components and 11 connectors, with no orphans of any type.

Decision 4: the merge guard

The moment Teamcenter holds more than Cameo, the Cameo push stops being a replace and has to become a merge. This cannot be prevented from here — the push happens inside the HCL connector, Check-Out discards local state before it pulls, and the operation is a replace. So the guard is external and evidential.

THE FAILURE THIS CATCHES IS SILENT Once Teamcenter holds more than Cameo, a careless skeleton re-push is what destroys the SME's work — and the push reports success. Before the push 49 objects 29 ports 11 connections snapshot The Cameo push runs Check-Out discards local state, then replaces. We do not own it. the guard cannot prevent — only detect verify After the push — exit code 1 REMOVED 042324 ReliefValve ← ENRICHMENT REMOVED ElectricBoostPump.fuelOut1 ← ENRICHMENT REMOVED 041948/fuelOut1 → 042324/fuelIn Identity is the whole difficulty — nothing may be keyed on what a re-push re-mints Components, connectors, interfacesitem id Ports (a port has no item id, and seven parts each own a "fuelOut")(owning item, port name) Connections (two real FuelControlUnit → pump wires exist, so a set would swallow one)multiset of endpoints BOM linesnothing — they change between two reads seconds apart
The merge guard: it cannot prevent the loss, because the push lives inside the connector. It detects it, names it, and exits 1 so a pipeline stops.
node bin/guard-tc-model.mjs --item 041908 --snapshot out/guard/041908.before.json
# ... run the Cameo -> Teamcenter push ...
node bin/guard-tc-model.mjs --item 041908 --verify out/guard/041908.before.json \
     --receipt out/nx/SME_EditTest.writeback.json

--verify exits 1 when anything was removed, so a pipeline stops. --receipt marks which removals destroyed Teamcenter-side enrichment — work that exists nowhere else. Proven in both directions: baseline 49/29/11 → OK; after enrichment 50/31/12 → added 1 object, 2 ports, 1 wire, 0 removed; after a destructive push → exit 1 with all four removals flagged ENRICHMENT by name.

Two subtleties the 27 tests pin down. A component legitimately has two parents, so parents are compared as a sorted set. And a port that survives with its typing stripped is a removal in substance — the object is still there, but Seg0Implements is what makes a port routable, so losing it trips the rule on its own and is reported by name with what it used to be.

What the guard does not do It does not restore. Restoration is the write-back re-run from its diff, which is why the receipt exists and why enrichment is separately counted. Making the push itself merge-safe would have to happen inside the connector.

Facts about the shipped symbol library

320 symbols under <NX>\DIAGRAMMING\schematic\library\contents, disciplines Piping (ANSI/ISO/DIN), Hydraulic (ISO 1219) and HVAC. Things that cost real time:

FindingConsequence
Branch fittings cannot be placed at all. Eleven symbols — every tee, cross and junction across all disciplines — carry no <Annotation>, and Commit fails with "Cannot place the symbol because it has not defined the label" under every combination of NodeCreationType and NodeType.They are flagged placeable: false and the generator refuses to emit one, so a future mapping rule cannot silently pick one and fail inside NX with a message that reads like a data problem. NX makes a tee by branching a connection (SetStartTeeSymbolId, untried).
No active sheet, no builder. CreateNodeBuilder raises "No Diagramming Sheet is found in the part" until SheetManager.ActiveSheet is assigned — even though the template contains a sheet and DiagrammingManager.Sheets lists it.It is a plain settable property. Nothing sets it outside the GUI; assigning it is the whole fix.
Sheet y grows downward from a top-left origin.The opposite of modelling coordinates. A node at y=19.5 on a 22-inch sheet lands on top of the title block.
A missing percent attribute means zero, not centre.<Location y_percent="0.5"/> on the filter inlet is x=0 (left edge), y=50%.
Ports are single-use — every shipped port declares numberAllowedConnections="1".Handing the same port to two wires loses one silently, so the generator keeps a per-node ledger of spent port ids.
<Unit> is per symbol: Piping is inches, Hydraulic is millimetres.One sheet per discipline, matching the template. --discipline pins it; an unpinned mixed run splits the system across two sheets and loses the pathways that cross.
The PDF builder commits and writes nothing unless SourceBuilder.SetSheets names the sheet.It hangs off Part.PlotManager, not Session.

Design decisions worth knowing

  • Pathway direction is repaired, not trusted. The bundle's from/to reflects the SysML connector's authoring order, not flow — 041908 contains FuelFilter.fuelIn → CheckValve.fuelOut, which is the wire drawn backwards. Port direction is the evidence that fixes it; 5 of 11 fuel pathways were re-oriented, and the count is reported.
  • Back edges do not drive layout. Every real fluid system is a closed loop. Ranking the hydraulic return line as if it were a chain put the reservoir at the far right of the sheet while it also fed the leftmost component. Cycle-closing edges are dropped from the ranking graph — still drawn, just not laid out against.
  • Name beats description. InvertedPickupSelector's description mentions a reservoir; matching prose first drew a selector as a tank.
  • The composite whole is skipped, and says so. A component with no ports and no connections is the assembly, not an item on a P&ID. FuelSystem is dropped and reported in stats.skipped.
  • A regulator is drawn as a globe valve. The bare actuator symbol has vertical-only ports and forces a jog on a horizontal run. Overlaying the actuator glyph needs SetAttachedSymbol, but no shipped valve declares a mounting port for it, so this is left alone rather than guessed at.

Getting the drawings into Teamcenter

Both schematics are checked in, hanging off item 041908 by IMAN_specification, each a Zip of the .prt plus its PDF. Getting the bytes there took finding the one upload shape this tier accepts:

POST http://<fsc>:4544/fms/fmsupload/?ticket=<TICKET VERBATIM>
multipart/form-data, ONE part named  fmsfile_0
+ live session cookies + X-XSRF-TOKEN
The ticket is already URL-encoded It arrives containing %2f %3a %3d and + for spaces, and carries an FMS_SHA256_SIGNATURE over its own text. Running encodeURIComponent on it turns %2f into %252f, the signature stops matching, and you get TICKET_VALIDATION_FAIL_0. That bug led to a wrong conclusion that the file server was network-blocked. The error grammar is diagnostic: MISSING_TICKET_0 = no ticket parsed (wrong path or field form); TOO_FEW_TICKETS_0 = ticket fine, file part misnamed; TICKET_VALIDATION_FAIL_0 = ticket mangled. Decode the ticket — it names the volume, the user and the expiry.

Cleanup gotcha: a dataset attached by GRM will not delete (code 9043). deleteRelations first, then deleteObjects.

Not done

  • Real NX tee objects. Branches currently share an anchor point on the upstream symbol's outlet, which reads correctly on the drawing but is not an NX fitting object.
  • Instrument balloon field text. Three of the eleven fuel symbols keep the bare tag: the ISO instrumentation balloon draws its own two-field text rather than a plain annotation. They are named in the result's notRelabelled rather than passed over.
  • Managed NX mode — see the status board for the two independent breakages behind it, both diagnosed, neither fixed.
  • The Teamcenter-side scoping rule. Which elements define the fuel or hydraulic specification still comes from the port-domain registry. It should eventually come from a classification, attribute, branch or saved query — never guessed from names.