Use this skill when authoring reusable, idempotent MATLAB scripts that build System Composer architecture models via the architecture-modeling API — `systemcomposer.createModel`, `addComponent`, `addPort`, `setInterface`, `connect(srcPort, dstPort)`, interface dictionaries (.sldd) with `addInterface`/`addElement`, profiles/stereotypes with `Profile.createProfile` and `addStereotype`, or `systemcomposer.allocation.createAllocationSet`. Also trigger when debugging these APIs (connections that d...
Scanned 9/6/2026
Install to Claude Code
npx -y skills add matlab/skills --skill system-composer --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of System Composer?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/matlab-system-composer-agent-skills-playground)More formats (shields.io, HTML) on the badges page.
---
name: system-composer
description: Use this skill when authoring reusable, idempotent MATLAB scripts that build System Composer architecture models via the architecture-modeling API — `systemcomposer.createModel`, `addComponent`, `addPort`, `setInterface`, `connect(srcPort, dstPort)`, interface dictionaries (.sldd) with `addInterface`/`addElement`, profiles/stereotypes with `Profile.createProfile` and `addStereotype`, or `systemcomposer.allocation.createAllocationSet`. Also trigger when debugging these APIs (connections that don't appear, interfaces that don't resolve, profile save errors, `createAllocationSet` signature-mismatch errors). Do NOT trigger for ad-hoc structural edits to an already-built model (adding one SubSystem, rewiring a port) — use `building-simulink-models` with `model_edit` for that.
license: MathWorks BSD-3-Clause (see LICENSE)
metadata:
author: MathWorks
version: "1.0"
---
# MATLAB System Composer — Programmatic Authoring Guide
System Composer lets you model multi-domain architectures in MATLAB. This skill captures the
correct API patterns, common gotchas, and a proven script structure for building models reliably.
---
## When to use this skill vs. `building-simulink-models` (SATK)
Both skills can edit System Composer `.slx` files. They work at **different API layers** — don't mix them in one script.
| Concern | This skill (architecture-modeling API) | `building-simulink-models` with `model_edit` (block-diagram API) |
|---|---|---|
| Component creation | `addComponent(arch, "Name")` — returns `systemcomposer.Component` | `add_block` with `type: "SubSystem"` — returns a `blk_id` |
| Ports | `addPort(arch, "Name", "in", iface)` — typed, interface-aware | Bus Element blocks (`In Bus Element` / `Out Bus Element`) inside the SubSystem |
| Connections | `connect(srcPort, dstPort)` — port objects | `{"op": "connect", "target": "blk_X.y1 -> blk_Y.PortName"}` |
| Interface dictionaries (`.sldd`) | First-class (`createDictionary`, `addInterface`, `setInterface`) | Not addressed |
| Profiles / stereotypes | First-class (`Profile.createProfile`, `addStereotype`, `applyStereotype`) | Not addressed |
| Allocation sets (`.mldatx`) | First-class (`systemcomposer.allocation.createAllocationSet`) | Not addressed |
| Auto-layout | **Call `Simulink.BlockDiagram.arrangeSystem` explicitly** before `save` — programmatic adds all land at (0,0) | `model_edit` runs autolayout automatically; its guardrail forbids manual `arrangeSystem` |
**Use this skill when:**
- Writing an idempotent `buildMyModel.m` / `buildMyArchitecture.m` script that will be re-run from scratch
- The architecture uses interface dictionaries, stereotypes/profiles, or allocation sets — `model_edit` has no primitives for any of these
- Debugging SC-specific API failures (CST `connect` shadow, composite `ArchitecturePort` errors, `dict.save` + re-fetch, `profile.save` path, `createAllocationSet` signature mismatch)
**Defer to `building-simulink-models` with `model_edit` when:**
- Making a one-off structural change to an already-built SC model (add one SubSystem, rewire one port, tweak a parameter)
- The user just wants "add a component called X" and the model has no interface dictionary / profile / allocation set the change needs to stay consistent with
- The MBSE workflow in `mbse-workflow` is not involved
**Do not mix in one script.** `model_edit` adds components via `add_block` with `type: "SubSystem"`; this skill adds them via `addComponent`. The two produce different object types and the architecture-modeling APIs in this skill (`setInterface`, `applyStereotype`, `addPort`) may not work on SubSystem-block-created components. Pick one layer per script.
---
## Recommended Script Structure
Keep profile creation in the same script as the architecture — add it at the end,
after the model and connections are built:
```
buildMySystemModel.m ← architecture + interface dictionary + profile/stereotypes
```
This keeps both artifacts in sync on every rebuild, and avoids the "profile already
applied" uniqueness error that occurs when a separate profile script re-applies a
profile to an already-profiled model.
The script is idempotent: it deletes and recreates all artifacts on every run.
---
## Phase 1+2: Architecture Model + Interface Dictionary
### Skeleton
See [`code/buildMySystemModel.m`](code/buildMySystemModel.m) for the full parameterized function:
```
buildMySystemModel(modelName, dictFile, archDir)
```
---
## Phase 3: Profile & Stereotypes
See [`code/buildMySystemProfile.m`](code/buildMySystemProfile.m) for the full parameterized function:
```
buildMySystemProfile(profileName, modelName, archDir)
```
---
## Critical API Gotchas
These will silently fail or throw cryptic errors without warning:
| What you want | Correct API | Wrong / common mistake |
|---|---|---|
| Connect two component ports | `connect(srcPort, dstPort)` | `connect(arch, srcPort, dstPort)` — silently fails; dispatches to Control System Toolbox |
| Wire a composite boundary port to a sub-component port | `connect(boundaryArchPort, subComp.getPort("Name"))` — boundary is the saved `ArchitecturePort` ref; sub-component is its `ComponentPort` via `getPort` | Connecting two `ArchitecturePort`s across the boundary, or using the boundary's `ComponentPort` — both throw "incompatible directions" |
| Assign interface to port | `port.setInterface(iface)` | `port.Interface = iface` — read-only property error |
| Add interface element | `addElement(iface, "Name", Type="double")` | `Type="MyValueTypeName"` — value type names resolve to `Simulink.ValueType` objects which the bus compiler cannot use; always use a Simulink base type directly |
| Create model | `systemcomposer.createModel(name)` | `systemcomposer.createModel(name, true)` — invalid 2nd arg |
| Set string stereotype property | `setProperty(comp, path, '"value"')` | `setProperty(comp, path, 'value')` — evaluates as MATLAB variable |
| Set string default in addProperty | `DefaultValue='"standard"'` | `DefaultValue="standard"` — evaluates, throws error |
| Apply stereotype property | `setProperty(comp, ...)` | `setPropertyValue(comp, ...)` — wrong function name |
| Close all profiles | `systemcomposer.profile.Profile.closeAll()` | `...closeAll("-discard")` — too many arguments |
| Create profile | `createProfile(name)` | `createProfile(name, "file.xml")` — invalid 2nd arg |
| Look up a component by path | Keep component vars when adding, or use `resolveComponent(arch, 'Parent/Child')` helper | `arch.lookup('Path', ...)` — does not exist |
| Create allocation set | `createAllocationSet(name, srcModelName, dstModelName)` — model **names** | `createAllocationSet(name, srcModelObj, dstModelObj)` — model objects fail on R2025b with unhelpful AllocationAppCatalog signature error |
---
## Why `connect(srcPort, dstPort)` Must Have No Architecture Argument
`connect` is shadowed by Control System Toolbox's `connect.m`. When you write
`connect(arch, srcPort, dstPort)`, MATLAB dispatches to the CST version (which connects
LTI models) instead of System Composer — it returns an empty 0×0 Connector silently.
The two-argument form `connect(srcPort, dstPort)` dispatches correctly via the port object's
class method. Always use this form for explicit port-to-port connections.
For component-to-component auto-wiring by matching port names, the form
`connect(arch, [srcComp,...], [dstComp,...])` is also safe since `arch` dispatches correctly.
---
## Port Multiplicity: Fan-Out Works, Fan-In Needs Separate Ports
A single **output port** can be connected to any number of input ports — just call
`connect(out, in1); connect(out, in2); ...`. This is clean 1→N fan-out; use it for
broadcast buses (e.g. `Supervisory.Schedule` feeding every production unit).
A single **input port** cannot receive connections from multiple sources. SC is a
structural modelling tool with no merge semantics — attempting to hook two
outputs into one input either errors or silently fails. Give the destination
component **separate named input ports** for each source (e.g.
`Status_Cook`, `Status_Pack`, `Status_Load`) and wire 1:1. This pattern is
verbose but unambiguous and matches how an MBSE diagram will render for review.
---
## Composite Components: Mix ArchitecturePort and ComponentPort for Internal Wiring
When a component has sub-components (a composite), its boundary ports have two views:
| View | Accessed via | Class | Used for |
|---|---|---|---|
| External | `comp.Ports` / `comp.getPort(name)` | `ComponentPort` | Connections in the **parent** arch |
| Internal | return value of `addPort(comp.Architecture, ...)` | `ArchitecturePort` | Connections **inside** the composite, to sub-component ports |
A boundary "in" port acts as a *source* from inside the composite's sub-architecture (it delivers data to the sub-components), but its external `ComponentPort` still reports direction `in`. Using the wrong view throws:
```
Unable to connect ports because they have incompatible directions.
```
### The exact rule (verified on R2025b)
Internal connections inside a composite require **specific port classes on each side** — verified empirically; mixing them wrong fails every time:
| Connection inside the composite | Source port | Destination port | `connect` call |
|---|---|---|---|
| Boundary → sub-component | Saved boundary `ArchitecturePort` | Sub-component `ComponentPort` (via `getPort`) | `connect(boundaryRef, sub.getPort("Name"))` |
| Sub-component → boundary | Sub-component `ComponentPort` (via `getPort`) | Saved boundary `ArchitecturePort` | `connect(sub.getPort("Name"), boundaryRef)` |
| Sub-component → sub-component | `ComponentPort` (via `getPort`) | `ComponentPort` (via `getPort`) | `connect(a.getPort("Out"), b.getPort("In"))` — same as top-level |
**Two `ArchitecturePort`s on opposite sides of the boundary fail** with "incompatible directions." That means the `motorPow` pattern — passing the return value of `addPort(sub.Architecture, ...)` to `connect` alongside a boundary `ArchitecturePort` — does **not** work. Use `sub.getPort(name)` instead for the sub-component side.
### Pattern
```matlab
function port = addTypedPort(comp, name, direction, iface)
port = addPort(comp.Architecture, name, direction);
port.setInterface(iface);
end
% Composite creation — keep the boundary refs
conv = addComponent(arch, "ConveyorSystem");
convPowerIn = addTypedPort(conv, "PowerIn", "in", ifPower); % ArchitecturePort
convDiagOut = addTypedPort(conv, "DiagOut", "out", ifDiag); % ArchitecturePort
% Sub-components — no need to save their port refs
motor = addComponent(conv.Architecture, "Motor");
addTypedPort(motor, "PowerIn", "in", ifPower);
addTypedPort(motor, "DiagOut", "out", ifDiag);
% Boundary -> sub: saved ArchitecturePort + sub's ComponentPort
connect(convPowerIn, motor.getPort("PowerIn")); % ✓ correct
% Sub -> boundary: sub's ComponentPort + saved ArchitecturePort
connect(motor.getPort("DiagOut"), convDiagOut); % ✓ correct
% DO NOT: two ArchitecturePorts across the boundary
% motorPow = addTypedPort(motor, "PowerIn", "in", ifPower);
% connect(convPowerIn, motorPow); % ✗ incompatible directions
```
External connections from the parent arch continue to use `conv.getPort(name)` (ComponentPort) as normal, the same as top-level connections between non-composite components.
### Why `addTypedPort(comp, ...)` takes the Component (not its Architecture)
The helper called with a Component does `addPort(comp.Architecture, ...)` internally, so either calling convention works. Passing the Component is slightly cleaner for the common case and matches how non-composite components add ports. For a composite, the *return value* is still the internal `ArchitecturePort` — save it when you'll need it for boundary-to-sub wiring.
---
## Variant Components
A **variant component** is a sibling concept to a composite: one wrapper with N alternative internal architectures (*choices*), only one of which is active at a time. Use this for architecture-options studies where you want to compare candidate topologies without duplicating the whole model file.
See [`code/buildMyVariantComposite.m`](code/buildMyVariantComposite.m) for a working build template and [`code/tradeStudy.m`](code/tradeStudy.m) for the driver that enumerates variants and emits a markdown comparison report.
### Canonical build sequence
```matlab
% 1. Build a regular composite with its boundary ports + the baseline content
comp = addComponent(arch, 'CookingLine');
addPort(comp.Architecture, 'RecipeData', 'in');
% ... add sub-components for the baseline ...
% 2. Convert to a variant wrapper. The baseline content becomes the first
% auto-created choice (named the same as the wrapper).
vc = comp.makeVariant();
% 3. Re-fetch the wrapper -- the old `comp` reference is STALE (see below).
cookingLine = arch.getComponent('CookingLine');
% 4. Rename the auto-choice AND sync its variant condition to match.
ch = vc.getChoices();
ch(1).Name = 'V0_Baseline';
vc.setCondition(ch(1), 'V0_Baseline');
% 5. Add additional choices; each choice is a regular Component with its own
% Architecture. Wire internal content inside each choice.
v1 = vc.addChoice({'V1_Parallel'});
addPort(v1.Architecture, 'RecipeData', 'in');
addComponent(v1.Architecture, 'ParallelA');
addComponent(v1.Architecture, 'ParallelB');
% 6. Choose the default active variant.
vc.setActiveChoice('V0_Baseline');
```
### Gotcha — the original `Component` reference is stale after `makeVariant`
Once `makeVariant` returns, port lookups on the original variable fail with `"not in same architecture scope"`. External connections placed *before* `makeVariant` survive the conversion (verified empirically), but any subsequent `comp.getPort(...)` or `connect(...)` calls must use a fresh reference:
```matlab
vc = comp.makeVariant();
freshComp = arch.getComponent('CookingLine'); % returns VariantComponent
% Wrong -- stale scope:
connect(other.getPort('Out'), comp.getPort('RecipeData')); % ✗
% Right:
connect(other.getPort('Out'), freshComp.getPort('RecipeData')); % ✓
```
### Gotcha — rename + `setCondition` must stay in sync
`ch.Name = 'V0_Baseline'` does *not* update the variant condition. `setActiveChoice` matches the given string against conditions, so without re-syncing:
```matlab
ch(1).Name = 'V0_Baseline';
vc.setActiveChoice('V0_Baseline');
% Error: Setting active choice for variant block '...' with variant control
% 'V0_Baseline' is not supported.
```
Fix by calling `vc.setCondition(ch(1), 'V0_Baseline')` immediately after the rename.
### Gotcha — `applyStereotype` on the wrapper throws
```
Unable to apply stereotype 'Profile.Stereotype' on variant architecture '.../Wrapper'.
```
Apply the stereotype to each *choice* instead. Numeric property values set on the active choice propagate to the wrapper's instance at `instantiate` time, so the rollup callback sees the right value when it visits the wrapper. This is the mechanism that makes per-variant rollup estimates work.
### Gotcha — **string** stereotype properties do NOT propagate choice → wrapper instance
Verified empirically on R2025b. A `Type="double"` property set on the active choice shows up on the wrapper's instance (`instance.hasValue(...) == true`, `getValue(...)` returns the choice's number). A `Type="string"` property shows up as `hasValue == false` on the wrapper's instance.
This matters when you want the rollup callback to branch on variant-specific behavior (e.g. MIN aggregation for serial, SUM for parallel). **Encode the flag as a number, not a string:**
```matlab
% Works across the choice → wrapper instance boundary:
addProperty(st, "UseParallelThroughput", Type="double", DefaultValue="0");
setProperty(parallelChoice, stPath + ".UseParallelThroughput", "1");
% Does NOT work:
addProperty(st, "ThroughputAggregation", Type="string", DefaultValue='"MIN"');
setProperty(parallelChoice, stPath + ".ThroughputAggregation", '"SUM"');
% -> wrapper's instance reports hasValue == false for this property
```
See [`mbse-architecture/references/analysis.md#topology-dependent-rollup`](../mbse-architecture/references/analysis.md#topology-dependent-rollup) for the rollup callback pattern that reads the numeric flag.
### Gotcha — auto-layout skips inactive choices
`comp.Architecture.Components` on a `VariantComponent` returns only the *active* choice's sub-components, so a generic recursive `arrangeComposites` walker that follows `.Architecture` never lays out the inactive variants. Iterate `vc.getChoices()` explicitly:
```matlab
function arrangeComposites(arch, pathPrefix)
for comp = arch.Components
subPath = [pathPrefix, '/', comp.Name];
if isa(comp, 'systemcomposer.arch.VariantComponent')
Simulink.BlockDiagram.arrangeSystem(subPath);
for ch = comp.getChoices()
choicePath = [subPath, '/', ch.Name];
Simulink.BlockDiagram.arrangeSystem(choicePath);
arrangeComposites(ch.Architecture, choicePath);
end
elseif ~isempty(comp.Architecture) && ~isempty(comp.Architecture.Components)
Simulink.BlockDiagram.arrangeSystem(subPath);
arrangeComposites(comp.Architecture, subPath);
end
end
end
```
Each choice's Simulink path is `Model/Wrapper/ChoiceName` — regular `arrangeSystem` works on them.
### Minor — `updatePortsFromChoices` requires the `'Mode'` kwarg
```matlab
vc.updatePortsFromChoices(); % error: 'Mode' must be specified
vc.updatePortsFromChoices('Mode','addPorts'); % ✓ adds missing wrapper ports from choices
```
Use this when choices define their own boundary ports and you want the wrapper to pick them up. If instead you add ports on the wrapper's `Architecture` *before* `makeVariant` and mirror them on each choice manually, you don't need this call.
---
## Sequence Diagrams
A **sequence diagram** (SC calls these "Interactions") is a behavioral view layered on top of an architecture model. It shows how components collaborate over time for a specific scenario: which messages pass between them, in what order, under what guards. Unlike a Mermaid/PlantUML sequence diagram, every SC message is **bound to real ports on real components** — rename a port upstream and the sequence-diagram build errors, which is the feature not a bug.
See [`code/buildMySequenceDiagram.m`](code/buildMySequenceDiagram.m) for a working template.
### Mental model
```
Model
└── Interaction (one per scenario)
├── Lifelines (one per participant, bound to a Component)
└── RootFragment
└── Operands(1) (pre-created default operand; messages live here)
├── addMessage(...)
└── addFragment('Alt' | 'Loop' | 'Opt' | 'Par')
└── Operands(1..N) → more messages, recursively
```
Messages hang off *operands*, not directly off fragments or the interaction. `Interaction.RootFragment.Operands(1)` is where the straight-line message sequence goes. `Alt` creates a fragment with two operands; each has its own `Guard` string and its own `addMessage`.
### Canonical build sequence
```matlab
model = systemcomposer.openModel('MyModel');
destroyInteractionIfPresent(model, 'MyScenario'); % idempotent rebuild
diagram = model.addInteraction('MyScenario');
L1 = diagram.addLifeline('MyModel/CompA'); % path OR Component object
L2 = diagram.addLifeline('MyModel/CompB');
op = diagram.RootFragment.Operands(1);
op.addMessage(L1, 'OutPort', L2, 'InPort', 'request');
op.addMessage(L2, 'ReplyOut', L1, 'ReplyIn', 'reply');
save_system('MyModel'); % interactions live INSIDE the .slx
open(diagram); % show in Sequence Viewer
```
### Gotcha — `addMessage` is 5 arguments, not 3
```matlab
op.addMessage(L1, L2, 'request') % ✗ "Function requires 3 more input(s)"
op.addMessage('request', L1, L2, 1) % ✗ various complaints
op.addMessage(L1, 'OutPort', L2, 'InPort', 'request')% ✓ (src, srcPort, dst, dstPort, guard)
```
The 2nd and 4th args are **port names that must exist on the components underlying the lifelines**. If you pass a name that doesn't match a port on the source component, you get `"Name must match a port on the component corresponding to the lifeline"`. This ties every message to the structural model and is the main reason to prefer programmatic SC sequence diagrams over free-form Mermaid.
### Gotcha — messages do NOT live on Fragments directly
```matlab
diagram.RootFragment.addMessage(...) % ✗ no such method
diagram.RootFragment.Operands(1).addMessage(...) % ✓
```
Same for `Alt`/`Loop`/`Opt` fragments: get `.Operands(i).addMessage(...)`. `Alt.Operands` has length 2 after `addFragment('Alt')`; set `op1.Guard = "cond1"` / `op2.Guard = "cond2"`.
### Gotcha — idempotent rebuild requires explicit `destroy()`
`model.addInteraction(name)` errors on duplicate name. Always delete the existing one first:
```matlab
function destroyInteractionIfPresent(model, name)
try, ixns = model.getInteractions(); catch, ixns = []; end
for i = 1:numel(ixns)
if strcmp(ixns(i).Name, name), ixns(i).destroy(); return; end
end
end
```
Put the build step AFTER the architecture-model build step in `buildAll` — a rebuild of the architecture model wipes interactions along with everything else, so the sequence diagram must be re-created each time.
### Guard syntax
Guards accept trigger names, boolean expressions in braces, or both:
```
'cookComplete' event name
'{Accepted==1}' boolean on interface fields
'rocketDocked{RocketPresent==1}' event + condition
'rising(sw-1){sw==1}' signal transition + condition
```
Use interface-element names (from the dictionary — e.g., `Accepted` on `QCVerdict`) so the guard text stays consistent with the rest of the model.
### Duration constraints
`message.Start` and `message.End` return `MessageEvent` objects that can be passed to `addDurationConstraint`:
```matlab
t0 = msg1.End;
t1 = msg2.End;
diagram.addDurationConstraint(t0, t1, 't < 10sec'); % assertion on the render + runtime check
```
### Persistence + viewing
- Interactions are serialized *inside* the model's `.slx` — `save_system(modelName)` commits them; no separate file to track.
- `open(diagram)` opens the SC Sequence Viewer canvas. `diagram.open()` also works.
### Requirement traceability — see the slreq skill
slreq does not accept an `Interaction` object as a `createLink` source on R2025b, and the struct-based workaround (`domain='linktype_sc_interaction'`) fails to persist when the containing `.slx` already hosts `linktype_rmi_simulink` Implement links. See [`simulink-requirements/SKILL.md`](../simulink-requirements/SKILL.md) — search for "Interaction"; it covers the recommended convention-based trace pattern.
---
## Why dict.save() + Re-fetch Is Required
`systemcomposer.createDictionary()` creates the file but interface objects in memory aren't
fully resolved until after `dict.save()`. If you call `port.setInterface(iface)` before saving,
the model links the port to an unresolvable interface name — connections will silently fail and
reopening the model shows "Unable to resolve interface" errors.
Pattern to always follow:
```matlab
% 1. Add all value types and interfaces
thermalIface = addInterface(dict, "ThermalFluid");
addElement(thermalIface, "Temperature", Type="Temperature");
% 2. Save
dict.save();
% 3. Re-fetch — now safe to pass to setInterface
thermalIface = dict.getInterface("ThermalFluid");
```
---
## Auto-layout
Always call `Simulink.BlockDiagram.arrangeSystem` before `save(model)` — programmatically
added components all start at position (0,0) and stack on top of each other without it.
**For hierarchical models, arrange every decomposed sub-architecture as well as the top level.**
Use the Simulink subsystem path `modelName + "/ComponentName"` for each level:
```matlab
%% Layout and Save
Simulink.BlockDiagram.arrangeSystem(modelName + "/Powertrain"); % sub-levels first
Simulink.BlockDiagram.arrangeSystem(modelName + "/Drivetrain");
Simulink.BlockDiagram.arrangeSystem(modelName); % top level last
save(model);
```
Arrange sub-levels before the top level so the top-level layout has accurate size information
for each component block. A sub-architecture that was never arranged remains a collapsed pile,
and the top-level arrange won't fix it.
---
## Re-run Safety for Profile Scripts
Calling `applyProfile` on a model that already has the profile throws a "uniqueness constraint"
error. The cleanest solution is to rebuild the model from scratch at the top of the profile
script — this guarantees a clean slate and makes both scripts independently idempotent.
`buildMySystemProfile` already does this: its first call is always `buildMySystemModel(...)`.
---
## Multi-Domain Interface Patterns
Use `Type="double"` for all elements and document physical units in comments.
Do not use `addValueType` for physical quantities — it creates `Simulink.ValueType`
objects the bus compiler cannot resolve (breaks "update diagram").
See [`code/addCommonInterfaces.m`](code/addCommonInterfaces.m) for an illustrative starting
point covering Thermal, Electrical, Mechanical, and UserCommand interfaces:
```
ifaces = addCommonInterfaces(dict)
```
Returns a struct (`ifaces.ThermalFluid`, `ifaces.ElectricalPower`, etc.). Remember to call
`dict.save()` and re-fetch interfaces before passing them to `setInterface()` — see the
re-fetch pattern above.
---
## Architecture Views — filtered lenses on a large model
Once a model grows beyond a couple of dozen components, navigating it becomes a drag. System Composer's **view architectures** are named, saved lenses that filter the architecture by a stereotype-property query. A view appears in the model canvas dropdown and in the Views Gallery (`openViews(model)`), and matching components glow in the color you gave the view. This is how Gulfstream's eSAM method routinely surfaces cost drivers, high-power components, per-supplier subsets, etc. without hand-drawn diagrams that rot.
Two mechanisms:
**Query-driven views** — a single stereotype-property constraint picks members automatically:
```matlab
import systemcomposer.query.*;
q = PropertyValue("MyProfile.ComponentProperties.Cost_credits") > 150000;
v = createView(model, "CostDrivers", Select=q, Color="#D62728");
```
**Color gotcha:** `Color` accepts hex strings universally (`"#D62728"`) and some but not all named colors — `"red"` and `"blue"` work, `"magenta"` errors with *"The value of 'Color' is invalid. The color must be a hex color or RGB value."* Prefer hex.
`PropertyValue(path)` returns an object that overloads `>`, `<`, `>=`, `<=`, `==`, `~=` to build a query constraint — so the `pv > 150000` expression builds a `systemcomposer.query.Compare` object. Pass it as `Select=`. The view refreshes automatically as stereotype properties change.
To check ad-hoc what a query matches without committing to a view:
```matlab
matches = find(model, q); % returns a cell array of qualified-name STRINGS,
% not Component objects (easy mistake)
for i = 1:numel(matches), disp(string(matches{i})); end
```
**Explicit-element views** — for anything a single property query can't express (allocation-driven groupings, hand-picked subsets, per-supplier partitions), create an empty view and add elements by hand:
```matlab
v = createView(model, "ControlRealization", Color="blue");
v.Root.addElement(arch.getComponent('ControlCabinet'));
for sub = arch.getComponent('ControlCabinet').Architecture.Components
v.Root.addElement(sub);
end
```
Both mechanisms are additive — you can start with a query and then `addElement` to include extras that didn't match.
### Idempotency when rebuilding
Views are saved *inside* the `.slx`. Our build scripts recreate the `.slx` from scratch on every run, which wipes views along with everything else. So a view-creation script has to run **after** the relevant `buildXxx.m` and be idempotent itself:
```matlab
try, deleteView(model, "CostDrivers"); end %#ok<TRYNC> % guard against first-run-missing
v = createView(model, "CostDrivers", Select=q, Color="red");
```
See [`code/buildMyViews.m`](code/buildMyViews.m) for a parameterised helper that takes a list of view specs and creates them all.
### `find()` returns strings, not components
A recurring mistake: `find(model, constraint)` returns a `cell` of qualified-name *strings* like `"MyModel/Parent/Sub"`, not `systemcomposer.arch.Component` objects. If you need the component object to, e.g., call `getPropertyValue`, use `model.lookup("Path", pathString)` to resolve it.
---
## Verifying Your Model
Run these checks after every build. They catch real problems that the build step itself
won't flag.
### 1. Check for unconnected ports
Ports that were added but never wired are silently valid at build time but represent
incomplete or inconsistent architecture. See [`code/checkUnconnectedPorts.m`](code/checkUnconnectedPorts.m):
```
checkUnconnectedPorts(modelName)
```
### 2. Update diagram
`set_param` update catches any remaining type resolution issues (e.g. a bad interface
element type that slipped through):
```matlab
set_param("MySystem", "SimulationCommand", "update");
```
**Expected warning for pure architecture models:** `Architecture model contains no
components or all components are virtual.` This is normal — architecture components
have no simulation behaviour. It does not indicate a problem with your model.
If you see a type resolution error instead (e.g. `DataType 'X' did not resolve`), the
cause is almost always an `addElement` call using a value type name instead of a
Simulink base type — see the gotchas table above.
### 3. Check stereotype properties (if using profiles)
```matlab
comp = arch.getComponent("ComponentA");
props = comp.getStereotypeProperties();
for i = 1:numel(props)
fprintf(" %s = %s\n", props(i), comp.getPropertyValue(props(i)));
end
```
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!