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 5/27/2026
Install via CLI
openskills install matlab/agent-skills-playground---
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
```
No comments yet. Be the first to comment!