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.mscript that will be re-run from scratch - The architecture uses interface dictionaries, stereotypes/profiles, or allocation sets —
model_edithas no primitives for any of these - Debugging SC-specific API failures (CST
connectshadow, compositeArchitecturePorterrors,dict.save+ re-fetch,profile.savepath,createAllocationSetsignature 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-workflowis 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 for the full parameterized function:
buildMySystemModel(modelName, dictFile, archDir)
Phase 3: Profile & Stereotypes
See 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 ArchitecturePorts 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