SysML v2 diagram views let you define which elements to include in a diagram and how to
render them. A view selects elements using expose statements and optionally narrows
the results with filter expressions. Syside also supports additional diagram
customisation through configurable attributes.
Configurable Attributes
The following attributes control how each view renders.
2 Applicable to
Modeler CLI only. These attributes are used for file-based diagram export.
Note
Except for render, which comes from the standard Views library, these
attributes are Syside-specific and have no effect in other tools.
Command-line options and [viz] settings interact with these attributes in a fixed
order of precedence.
depth Explained
depth controls how many levels of each exposed element’s subtree to traverse.
Traversal happens after filtering, so the diagram may include element types that
were filtered out. To avoid this, set depth to 0 or 1 when using filters.
0 shows only the filtered elements. Because filtering applies to elements rather
than relationships, no relationships between them appear at this depth.
1 shows the filtered elements and the relationships between them — ownership, state
transitions, connections, and similar. Use this depth when relationships need to be
visible.
-1 (the default) means infinite depth, which is useful for exploratory diagrams but
typically too detailed for documentation and may contain elements that should be
filtered out.
render Explained
render controls the layout style used to display the diagram. The default Nested
style shows child elements inside their parents; Tree arranges them as a branching
tree instead. Both correspond to the “Load Children as Nested” and “Load Children as
Tree” tools in the interactive Viewer.
To switch to Tree layout, add a render Views::asTreeDiagram; statement to the view.
Understanding Exposing
Each SysML v2 view must have at least one expose statement. It defines the initial
set of elements that are shown in the view. A view can have multiple expose
statements, and their results are combined. Once all the expose statements are
evaluated, the combined list of elements is passed on to filtering, so getting the
initial expose right is crucial.
Note
Unlike other customisation options (such as filter), expose is not
inherited. If a view definition declares expose, view usages typed by that
definition must redeclare it.
There are multiple ways to write expose statements and each will have a different
effect. To make them easier to understand, consider the following simple example model.
package ExposeExample {
occurrence def SystemContext {
part rocketShip {
part firstStage;
part secondStage;
}
part missionControl;
}
view systemContextView {
expose <EXPOSE_PLACEHOLDER>;
}
}
The following four statements can each replace <EXPOSE_PLACEHOLDER> with a different
result:
Multiple expose statements can be combined to select elements not covered by a
single statement. For example, to expose SystemContext, rocketShip, and
missionControl without their children:
view systemContextView {
expose SystemContext;
expose SystemContext::*;
}
A filter can also be attached directly to an expose statement using square brackets
— useful when a view has multiple exposes that need different filters. See inline filters in the next section.
Understanding Filters
A filter expression inside a view narrows the exposed elements to only those that
match a condition. Filters can test an element’s type using classification operators
defined in the KerML specification (see section KerML 7.4.9.2).
For example, filter @ SysML::PartUsage checks each exposed element and keeps only
those whose type matches PartUsage.
There are four classification operators:
@ — at least one classification matches (includes subtypes). Returns false
on null.
istype — all classifications must match (includes subtypes). Returns true
on null.
hastype — all classifications must match (exact type only, no subtypes).
Returns true on null.
as (cast) — selects values classified by the given type. Useful for accessing
metadata attributes in filter expressions.
Consider the following model. It uses the standard library’s RiskMetadata package
and a custom FlightCritical metadata tag:
package FilterExample {
private import RiskMetadata::*;
metadata def FlightCritical;
part def DroneSystem {
part sensor;
#FlightCritical part controller;
part camera { @Risk { technicalRisk = RiskLevelEnum::high; } }
part radio { @Risk { technicalRisk = RiskLevelEnum::medium; } }
#FlightCritical part battery { @Risk { technicalRisk = RiskLevelEnum::low; } }
action processData;
connection dataLink connect sensor to controller;
allocate processData to controller;
connect camera to controller;
}
}
Applying different filters to expose DroneSystem::* produces different results:
* Unnamed elements created by allocate and connect statements.
The sections below explain why each filter returns what it does.
Matching types and subtypes
filter @ SysML::PartUsage selects every element whose type is PartUsage or any
of its specializations. In SysML v2, several usage types specialize PartUsage,
including ConnectionUsage and AllocationUsage. This means a view that filters
for parts will also include connections and allocations, as shown in the table above.
Note
This is standard SysML v2 behavior, not specific to Syside. The SysML v2 spec
(section SysML 7.26.2)
uses filter @SysML::PartUsage in its view examples, so users naturally reach for
it first. Be aware of the subtype-inclusive semantics.
istype works the same way but with a stricter condition: @ requires at least
one of the element’s classifications to match, while istype requires all of
them to match. In practice, this means istype fails on elements with metadata
annotations (e.g., #FlightCritical part controller), while @ still matches them.
Matching exact types
filter hastype SysML::PartUsage selects only elements whose declared type is
exactly PartUsage, excluding specializations. It also fails on elements with
metadata annotations, just like istype.
In the model above, only sensor matches because it is the only part with no metadata
and no subtype classification.
Warning
Both hastype and istype fail on elements with metadata annotations (e.g.,
#FlightCritical part controller). Use @ if metadata is present and you don’t
need exact type matching.
Combining operators
To filter for an exact type while still matching elements with metadata, combine as
with hastype:
filter (as SysML::PartUsage) hastype SysML::PartUsage;
The as cast isolates the PartUsage classification, and hastype checks that
it is exactly PartUsage, not a subtype. This returns all five parts regardless of
metadata, while keeping connections and allocations out.
Tip
Start with hastype for precise filtering, then broaden to @ if you need
subtypes included.
Inline filters
A filter expression can be attached directly to an expose statement using square
brackets, instead of being declared as a separate filter statement. This form is
useful when a view has multiple expose statements that need different filters, or
when keeping the filter visually adjacent to its source makes the view easier to read.
For a view with a single expose, the two forms produce the same result:
// Separate filter
view V {
expose MyPackage::*;
filter hastype SysML::PartUsage;
}
// Inline filter (equivalent for this view)
view V {
expose MyPackage::*[hastype SysML::PartUsage];
}
The difference shows up when a view has more than one expose. A separate filter
declaration applies to every expose in the view, while an inline filter applies
only to the expose it is attached to. This makes inline filters the only way to
apply different filters to different sources within a single view:
// Separate filter — same condition applied to both exposes
view V1 {
expose MyPackage::*; // has part a, part b
expose MyOtherPackage::*; // has part c, action d
filter hastype SysML::PartUsage;
}
// Result: part a, part b, part c
// Inline filters — each expose has its own condition
view V2 {
expose MyPackage::*[hastype SysML::PartUsage]; // has part a, part b
expose MyOtherPackage::*[hastype SysML::ActionUsage]; // has part c, action d
}
// Result: part a, part b, action d
Name resolution inside inline filters
An inline filter expression is resolved in the scope of the elements it filters, not in
the scope of the enclosing view. This usually does what you want — names declared near
the source are reachable without imports — but it has one consequence worth calling out:
if the filter would hide the element it references, the reference cannot be resolved.
Consider a model that defines metadata in one package and the parts that use it in
another:
package ExposeFilterTest {
package MetadataDefinitions {
metadata def MyMetadata;
}
package MyParts {
public import MetadataDefinitions::*;
part partOuter {
part partInnerWithMetadata { @MyMetadata; }
part partInnerWithoutMetadata;
}
part partOuterWithMetadata { @MyMetadata; }
}
part def Views {
private import MetadataDefinitions::MyMetadata;
view allPartsWithMetadata {
// Fails: the filter excludes MyMetadata from the exposed
// scope, so the unqualified reference cannot resolve.
expose MyParts::*::**[@MyMetadata];
}
}
}
The expose recursively walks MyParts and applies [@MyMetadata] to each element.
Because the filter is evaluated against the exposed scope (which includes MyMetadata
as an imported member of MyParts), and MyMetadata itself is not annotated with
@MyMetadata, the filter excludes it — which leaves the filter’s own reference
unresolvable.
The fix is to use a fully qualified name that does not depend on what the filter
includes:
view allPartsWithMetadata {
expose MyParts::*::**[@MetadataDefinitions::MyMetadata];
}
Tip
When in doubt, qualify identifiers in inline filter expressions. A separate
filter declaration is evaluated outside the exposed scope and so does not have
this restriction — that can be a reason to prefer the separate form for filters that
reference metadata or types defined alongside the elements you are filtering.
For a complete example of inline filters in views, see the Filter Evaluation example.
Common Pitfalls
The following behaviors are correct but regularly surprise view authors.
View name becomes diagram frame title
The view name is not an internal identifier: it renders verbatim as the diagram title in
the frame, so a view named 05_power_distribution shows that 05_ to every reader.
The fileName attribute sets the output file name independently, so ordering prefixes
can live there while the title stays presentable.
depth demotes elements instead of hiding them
Elements beyond the depth limit collapse into compartment rows of their parent.
Relationship edges that target a demoted element (verify, satisfy, allocate, and
similar) cannot render as arrows and surface as textual compartment entries instead.
To keep relationship arrows, keep both endpoints within the depth limit, or leave the
depth unlimited.
Relationship edges need both endpoints as nodes
If one end of a keyword relationship is absent or demoted to a compartment row, the edge
disappears or degrades to text.
Interconnection views need part contents
An interconnection view draws features as nodes: part usages with their ports and
wiring. A part definition is not a feature, so a directly exposed definition
(MyDef::**) is skipped rather than drawn. Expose the definition’s contents
(MyDef::*::**) or a part-usage subtree instead. Alternatively, the promoted
expose mode keeps the definition’s contents by
promoting them to the view frame, prefixed with their origin path.