HOW IT'S BUILT

Platform Architecture

Three tiers — a Capital plugin suite, a neutral hub service, and Teamcenter 2506 — connected by one JSON contract. Every mechanism on this page ran live against Capital 2512 and Teamcenter 2506, July 3–5, 2026.

DOMAIN AUTHORING SysML / Cameo MIL-STD-1808 spine · functions (one-way in) Capital Systems Modeler functional design Capital Systems Architect platform architecture + functional Capital Logic Designer logic detail — devices · connectors · nets NX (Diagramming) fluid systems — pumps · reservoirs · probes THE SYSTEMS CENTER SYSTEMSCENTER HUB Neutral exchange bundle one contract every tool speaks Store merged head + revision history Enrichment provenance · effectivity · parentage stable identity · multi-domain Idempotent Teamcenter push PLM — THE MASTER Teamcenter one common Logical BOM ▸ REQ-000123 (Requirement) ▾ UAV ▾ [24] Electrical Power ▾ [24 50] AC Fuel CBs ◆ Invert Fuel Pump CB ▾ [28] Fuel ▾ [28 10] Storage-Inverted ◆ Fuel Pump ≥151 ▸ Fuel Pump-DEV (logic) Fnd0LogicalBlock · Functionality Seg0Allocate · Fnd0LogicConn Requirement · seg0Kind publish materialize push_bundle extract write into the common structure read / materialize back into the tool one-way (architecture source) Every domain keeps its native tool. The structure they share lives once — in Teamcenter — and Systemscenter is how it stays alive between them.
The systems center: every domain tool reads and writes one common logical structure, mastered in Teamcenter.
TIERS
3 — plugin · hub · Teamcenter
CONTRACT
Neutral JSON bundle, v1.2, 8 arrays
HUB
Zero-dependency Python, port 8080
PROVEN SCALE
12 comps · 40 fns · 38 sigs, zero-create round trip

The three tiers

Tier 1 — the Capital plugin suite

Everything on the Capital side ships in one JAR, deployed to <CapitalHome>\plugins\XceleratorTcImport. We build it with Capital's own toolchain — the Eclipse compiler and Java 21 JRE that ship inside the install, plus Capital's own Jackson jars — so there is no JDK, no Maven, and no classloader conflict (build.ps1 -Deploy is the whole pipeline). The jar registers against Capital 2512's documented extension points: publish actions for platform, functional, and logic designs (IXPlatformArchitectureDesignAction, IXFunctionDesignAction, IXLogicAction), the Assign Structure Parent… action, the import/sync action, and Teamcenter Browser panels on all three apps — Systems Architect, Systems Modeler, and Logic Designer — via the design-tree tab and Design Inspector panel interfaces. proven live

The write model shapes the design Capital's plugin API can stage attribute and property updates on design objects — applied atomically only when the action returns true — but it cannot create design objects or make allocations. So the plugin publishes and audits; creation flows the other way, through generated project XML that any Capital seat imports natively. That constraint is why the hub exists.

Tier 2 — the tc-service hub

The hub is a deliberately boring piece of engineering: a zero-dependency Python stdlib service on port 8080. It has three jobs — a store (one merged head per project plus append-only revision history), an enrichment layer (the "engineers type nothing" machinery, below), and a SOA client that speaks Teamcenter's JSON gateway directly. Its REST surface is the whole integration contract: GET /health, GET /export/bundle?project=, GET /export/spine?under=&depth=, GET /export/capital-xml?project=, POST /import/bundle, plus the Systemscenter views at /map. Anything that can produce or consume the bundle — Capital, NX, a Cameo export, a seed script — plugs in without touching the others.

Tier 3 — Teamcenter 2506

Teamcenter is the configuration-managed master. The hub logs into the JSON SOA gateway at https://<teamcenter-gateway> (CSRF-token handshake, cookie session) and materializes the bundle as real, typed objects: components become Fnd0LogicalBlock items nested as Fnd0LogicalBOMLine occurrences, functions become Functionality items with directed Network_Port pins, allocations become Seg0Allocate relations, conductors become Fnd0LogicConn items, and requirements become Requirement items related via IMAN_requirement. One Logical BOM spans the functional, platform, logic, and fluid layers — that shared tree is the systems center, and Systemscenter is the layer that renders and exchanges it.

The neutral exchange bundle (v1.2)

Every tier speaks one JSON document, defined in docs/exchange-format.md. Eight optional data arrays under a required meta block; every object carries a free-form attributes map for anything without a first-class field. The schema is additive across versions — a v1.0 bundle still imports unchanged.

ArrayCarriesTeamcenter mapping
componentsLRUs, ECUs, spine nodes; parentId · provenance · effectivity · domains[]Fnd0LogicalBlock + BOM occurrence under its resolved parent
functionsLogical functions + hierarchy (SysML-mastered)Functionality items with port GDEs
allocationsFunction → component decisions (Capital SA-mastered)Seg0Allocate, revision level
networksBuses/carriers, protocol, baudcarrier items (Fnd0LogicConn)
signalsE/E payload with source/target functionsSignal + Network exchange items
messagesFrames grouping signalsE/E payload grouping
connectionsComponent-to-component edges, any domain; attributes.tcKind distinguishes logic nets/wires from platform edgesFnd0LogicConn trace-linked to both endpoints
requirementLinksRequirement → element traceabilityRequirement + IMAN_requirement (relations only, never LBOM occurrences)

Trimmed to essentials, a Capital publish of one electrical add looks like this:

{
  "meta": {
    "schemaVersion": "1.2",
    "source": "capital-systems-architect",
    "project": "UAV Platform",
    "effectivity": "unit>=151"
  },
  "components": [{
    "id": "ELEC-IFPCB",
    "name": "Invert Fuel Pump Circuit Breaker",
    "type": "ECU",
    "domain": "ELECTRICAL",
    "parentId": "LOG-ACFSCB",
    "provenance": "elec-add",
    "effectivity": "unit>=151",
    "attributes": { "milStd1808": "24 50" }
  }],
  "functions":   [{ "id": "FN-PUMPCTL", "name": "Control pump outflow" }],
  "allocations": [{ "functionId": "FN-PUMPCTL", "componentId": "LOG-CGMC" }],
  "connections": [{
    "id": "CN-FPPWR", "domain": "ELECTRICAL",
    "fromComponentId": "ELEC-FPP", "toComponentId": "FUEL-PUMP",
    "attributes": { "tcKind": "power" }
  }],
  "requirementLinks": [{
    "requirementId": "REQ-000123", "targetType": "COMPONENT",
    "targetId": "LOG-STGINV", "linkType": "derives"
  }]
}
Property names are load-bearing A Capital cluster property named bare provenance or effectivity collides with a native Capital attribute and makes the 2512 importer silently drop the entire cluster — empty design, no error. The contract mandates the TC_-prefixed names (TC_PARENT_ID, TC_PROVENANCE, TC_EFFECTIVITY) everywhere they ride in Capital. We learned this live; it is now a hard format rule.

The hub's store

Every import lands in three places at once. First, the merged head — one bundle per project, upserted by external id, which is what /export/bundle serves and what the Teamcenter push reads. Second, an append-only revision history under store-data\revisions\<project>\: each delivery is snapshotted pristine, as sent — before any enrichment touches it — so we always have the configuration-management trail of exactly what each tool delivered. Third, a content-hash dedup: the Capital plugin delivers every publish twice for resilience (outbox file and HTTP POST), and the store hashes each delivery with SHA-256 so the second arrival is acknowledged but skips both the merge and the Teamcenter push. Dual delivery, single effect. proven live

Systemscenter structure tree rendering the merged head as the nested LBOM with provenance colors and effectivity pills
The merged head, rendered: Systemscenter's tree view reads the same store the Teamcenter push reads — one nested structure, colored by who authored each node.

Hub enrichment — engineers type nothing

The hub's rule is that a publishing engineer supplies exactly one fact — the structure parent of a new element — and everything else is derived or preserved. Six behaviors in store.import_bundle make that true:

BehaviorWhat it guarantees
Merge-preserveAn update never erases enrichment the incoming tool didn't send: empty parentId/provenance/effectivity keep their prior values, and prior attributes survive unless explicitly overwritten. A naive re-publish cannot strip the structure — this closed a real incident where a stale plugin briefly wiped head parentage.
Auto-provenanceNew components inherit authorship from meta.source: nx*fuel-add, cameo|sysmlsysml-add, capital*elec-add. Nobody types a provenance string.
Effectivity defaultNew components inherit meta.effectivity (which the Capital plugin in turn fills from a design-level TC_EFFECTIVITY property, set once per change wave by the lead).
Name→parent patch mapstc-service/patch-maps/parentid-patch.<domain>.json, read fresh on every import: delivered files that carry no parentage get it filled by component name. The safety net that makes third-party files nest correctly with no plugin rebuild.
Stable-identity guardRegenerating a Capital logic design mints all-new design-local UIDs; the store dedups devices by Component_Base_ID, connectors by name, and logic nets/signals by (tcKind, name) — migrating head references when an id changes. Regenerate + republish means updates, never duplicates.
Derived multi-domain participationdomains[] is recomputed over the merged head on every import: primary domain ∪ declared domains ∪ evidence (a power connection implies ELECTRICAL, a fluid line implies FUEL, a Software-typed allocated function implies SOFTWARE). A pump that is fuel + electrically powered + firmware-bearing stays one element with one identity, visible in all three filtered views.
Capital Teamcenter Browser tree-table showing the Domain column with multi-domain participation like FUEL +SW +ELEC
Hub-derived participation surfacing in Capital: the Teamcenter Browser's Domain column reads FUEL +SW +ELEC on the fuel pump — one element, three disciplines.

Delivery paths — from a Ctrl+S to a nested BOM line

Publish from the canvas

Right-click → Custom → Publish to Teamcenter… walks the open design into a bundle. External ids resolve in order: stamped TC_IDElement ID → Capital UID, so re-publishing a Teamcenter-materialized design resolves the original items.

Dual delivery

The plugin writes an atomic outbox file (logical-bom-<project>.json) and POSTs to /import/bundle. The hub's outbox watcher claims files into staging, then moves them to processed/ — or quarantines failures to failed/, never silently dropping one. SHA-256 dedup collapses the two arrivals into one import.

Merge, enrich, snapshot

The store applies merge-preserve, auto-provenance, effectivity defaults, and patch maps; records the pristine revision; recomputes domains[].

Idempotent Teamcenter push

push_bundle ensures rather than creates: ensure_item looks each id up live before minting anything, and ensure_structure opens the parent's BOM window, expands existing children, and adds only missing occurrences. Parents resolve first among items created this push, then live in Teamcenter — so a fuel publish nests under a spine node the SysML seed created weeks earlier. Unresolvable parents are reported in summary.errors and fall back to the root: a visible failure, never silent wrong-nesting.

Summary back to the humans

The push summary (items created/existing, occurrences added, nested parents, errors) lands in the hub log and the Systemscenter activity feed. The Wildfire round trip's summary read items_created 0 / items_existing 134 / relations_existing 199 / errors [] — the zero-create proof. proven live

Two namespacing mechanisms keep a shared dev instance sane: runTag prefixes every created id per test round (and must be empty in production — cross-publish parent resolution depends on untagged spine ids), and teamcenter.legacyRunTags lets a production push adopt items minted under an older tag instead of re-minting them.

Capital right-click Custom menu with Publish to Teamcenter action
The engineer-facing end of the pipeline: right-click → Custom → Publish to Teamcenter…
Gateway quirk: properties are create-time only On the 2506 JSON gateway, every setProperties variant returns success and writes nothing — a silent no-op, probe-verified. Type-distinguishing data like the logic layer's seg0Kind (LogicDevice/LogicConnector) must therefore be stamped at creation via createObjects compound input, and seg0Kind is multi-valued (string array props). The hub bakes both rules in.

Closing the loop — Teamcenter back into Capital

Capital design platform · functional · logic Element ID carries TC identity Hub store + enrichment merge-preserve · auto-provenance stable ids · patch maps · domains Teamcenter LBOM ensure-item / ensure-structure idempotent — re-push adopts, never dupes 1 publish → outbox/POST 2 push_bundle 3 extract structure 4 project XML → import 5 re-publish: items created 0 · relations created 0 · errors 0 THE ROUND TRIP — CLOSURE IS MEASURED, NOT ASSUMED A design authored in Teamcenter materializes as a real Capital project; publishing it back adopts every original object. Proven at scale on a 12-component / 40-function program.
The bi-directional round trip — closure is measured by re-publishing (zero creates, zero errors).

The reverse direction is what makes this bi-directional rather than a publish pipe. teamcenter_export.py inverts every relation the push writes: it walks the LBOM structure into components with parentage, reads GDE ports with directions, resolves allocations, carriers, and signal routing, and extracts the logic detail layer — nested devices with their pins, root connectors, and conductors with device endpoints. The extraction was semantically lossless against the Wildfire seed on the first full run: zero diffs.

capital_project_xml.py then renders that bundle as Capital project XML — the same project.dtd format the Cameo bridge writes, loadable on any Capital seat via Project Manager → File → Import, no plugin required. One generated file carries three sections: a <functiondesign> (functions, pins, directed nets), a <platformtopologydesign> (clusters, interfaces, carriers, signal routing, function allocations cross-referenced into the functional section), and — with ?logic=1 — a real <logicaldesign> whose devices carry Element ID = the Teamcenter detail item and Component_Base_ID = the source component, so a publish-back from Capital resolves the exact original items. The generator also emits the OTI section: Capital's per-project property dictionary, giving every engineer a TC_PARENT_ID Quick-Access-Panel dropdown whose legal values are derived from the Teamcenter-backed structure — never hand-typed. One deliberate exclusion: pathway/diagram graphics stay Capital-native (layout is presentation, not data), so the generator defaults to the proven importable shape.

Capital Project Manager File Import dialog loading the generated project XML
Materialization needs no plugin: any Capital seat imports the generated project XML through Project Manager.
ONE ELEMENT, ONE IDENTITY — THREE PLACES In Capital component property Element ID = FUEL-PUMP generated logic device property Component_Base_ID → source cluster survives project renames and revisions In the hub stable bundle ids — regeneration-proof FUEL-PUMP · <base>-DEV · LC-<owner>-P1 LNET-COND1 · REQ-000123 identity guard: a regenerated design updates its rows — never duplicates them In Teamcenter item ids follow hub conventions FUEL-PUMP (Fnd0LogicalBlock) FUEL-PUMP-DEV (seg0Kind LogicDevice) <PROJ>-LC-P1 · LNET-COND1 runTag namespaces test rounds; legacy tags are adopted, not re-minted The join key travels as a plain property, so any tool — including ones we haven't integrated yet — can carry it. Duplication per domain or per tool is never needed and never allowed.
One element, one identity, three places — the join key travels as a plain property.

Identity across the loop is the subtle part, because Capital re-keys all baseids when it bootstraps a project from XML — only the Element ID properties survive. So the generator runs two baseid strategies:

Scenariobaseid strategyWhat happens in Capital
Bootstrap (first import)Deterministic — TCX + SHA-1 derived from the Teamcenter idsCapital creates the project and mints its own internal baseids; the stamped Element ID properties become the durable join keys
Merge (every later import)Harvested — the real Capital baseids, exported once via PM → File → Export → Select Project… and stored in config.json capitalBaseidsImport opens the Import Designs to Project dialog against the existing project: Copy As Revision adds design revision B/C/… (safe; does not refresh project OTI), Overwrite converges the design exactly to the XML
Capital Quick Access Panel showing the TC_PARENT_ID dropdown populated with spine ids
The round trip's payoff for the engineer: a Teamcenter-derived parent pick-list, delivered through the generated OTI at bootstrap.
Two bytes that break an import Capital rejects any XML that does not begin literally with <?xml — the generator writes UTF-8 without BOM — and the DOCTYPE's DTD path must resolve on the importing machine (the service stamps it from config; point it at <CapitalHome>\dtd\project.dtd). Both were found the hard way and are now enforced by the generator.

Put together, the loop closes in both directions: Teamcenter-authored structure materializes as native, editable Capital designs; Capital-authored elements publish back and nest into the shared spine; and a full re-publish of a materialized design resolves every original object with zero creates. That closed loop — not any single tier — is the architecture. proven live