Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
12 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/unreleased/cameo-suppressed-compartments.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- `sysml -migrate sysml` keeps the compartments a modeler collapsed on a Cameo symbol: the symbol's `SUPPRESS_*` compartment properties, its own and the ones inherited from the diagram's symbol styles, are written as `showAttributes`, `showParts`, `showReferences`, `showPorts`, `showConstraints`, `showItems` and `showActions = false` on the symbol's `DiagramLayout::Layout`, and the migration report counts them (`# compartments suppressed: N on M symbols`). The positioned-tree renderer honours the switches per symbol: a hidden compartment is neither listed in the box nor counted among the rows clipped by its stated size, while a rendering's `showCompartments = false` still hides every compartment.
9 changes: 9 additions & 0 deletions docs/guide/11-migrating-from-sysml-v1.md
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,15 @@ symbol stream and writes it beside the view it produces as
[DiagramLayout](../project/diagram-layout-annotations.md) metadata — `Layout` boxes, `Route`
waypoints, a `Canvas` the size of the diagram frame, a `Style` for a symbol drawn in its own
colours or font, and a `Note` for every comment and text box — so nothing has to be laid out again.
The compartments a modeler collapsed on a symbol (Cameo's `SUPPRESS_*` symbol properties, stated
on the symbol or inherited from the diagram's symbol styles) become the `Layout`'s `show…`
switches, by the kind their members take in v2 — parts `showParts`, references `showReferences`,
properties, values and class attributes `showAttributes`, constraints `showConstraints`, flow
properties `showItems`, proxy, full and class ports `showPorts`, operations `showActions` — so a
box the modeler sized for the compartments left visible is drawn with those alone, with no
clipped-row notice for the rest; the report counts them (`# compartments suppressed: N on M
symbols`). A suppressed compartment with no v2 kind (structure, behaviors, signal receptions) is
listed among the unsupported presentation properties.
No export from another tool is needed; an [MTIP](https://github.com/Open-MBEE/mtip-cameo) export
given with `-layout` is still honoured and takes precedence for every element its record places
or routes, the stream supplying the elements the record leaves out
Expand Down
25 changes: 23 additions & 2 deletions docs/project/diagram-layout-annotations.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,13 @@ library package DiagramLayout {
attribute width : ScalarValues::Real[0..1];
attribute height : ScalarValues::Real[0..1];
attribute collapsed : ScalarValues::Boolean[0..1];
attribute showAttributes : ScalarValues::Boolean[0..1];
attribute showParts : ScalarValues::Boolean[0..1];
attribute showReferences : ScalarValues::Boolean[0..1];
attribute showPorts : ScalarValues::Boolean[0..1];
attribute showConstraints : ScalarValues::Boolean[0..1];
attribute showItems : ScalarValues::Boolean[0..1];
attribute showActions : ScalarValues::Boolean[0..1];
}

metadata def Route {
Expand Down Expand Up @@ -248,7 +255,21 @@ For a (view, element) pair the **effective layout** is, in order:
3. none: the element is laid out by the consumer.

A `Route` resolves the same way for an edge's declaring element. A `Canvas` belongs to a
view alone, stated in its body. Where a view's body states two `about` annotations of one
view alone, stated in its body.

A `Layout`'s `show…` switches are per symbol, as its box is: `showParts = false` hides the
parts compartment of the annotated element's box in that view (and, inline, in every view
that states nothing), while the same element drawn in another view, or by another `Layout`,
keeps its compartments. Unset, a switch leaves the compartment to the rendering, whose
`showCompartments = false` hides every compartment whatever the switches say. The seven
switches follow the compartments a modelling tool lets a modeler collapse symbol by symbol:
`showAttributes` the attributes, `showParts` the composite part rows, `showReferences` the
`ref` rows of every compartment, `showPorts`, `showConstraints`, `showItems` and `showActions`
the compartments of those kinds. A hidden compartment's members are neither listed in the
box nor counted among the rows its stated size clips; the folding notice counts them apart
(`9 node(s) without a position: 7 drawn as compartment rows of the node above them, 2 in
compartments its Layout hides left undrawn`). The SysML v1 migrator writes them from a
Cameo symbol's `SUPPRESS_*` properties (see [migrating from SysML v1](../guide/11-migrating-from-sysml-v1.md#publishing-a-migrated-document-with-cameo-style-diagrams)). Where a view's body states two `about` annotations of one
kind for one element, the first in declaration order applies and the second is a warning.

With no view — a `#tree`, `#interconnection:X`, `#case` or `#mixed` pseudo-view, and the
Expand All @@ -265,7 +286,7 @@ annotation stated anywhere else is element-level.
### The rendering tree

`view.Node` carries `Geometry *Geometry` (`X`, `Y`, `Width`, `Height`, `HasSize`,
`Collapsed`), `view.Edge` carries `Route []Point`, and `view.Rendering` carries
`Collapsed`, `Hidden`), `view.Edge` carries `Route []Point`, and `view.Rendering` carries
`Canvas *Canvas` (`Unit`, `Width`, `Height`, `HasSize`). They are values: the tree stays free of AST
nodes and symbols, and `Rendering.Clone` copies them. Every graph-shaped rendering
populates them — the containment tree, the interconnection diagram, the state machine
Expand Down
2 changes: 1 addition & 1 deletion docs/project/pilot-differential-baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
"dir": "internal/workspace/libs/stdlib/OpenSysML Libraries",
"origin": "ours",
"files": 16,
"digest": "sha256:478be4a232db162cf4de51048786b7f78013bb8495de861c2647ef0e73523925"
"digest": "sha256:c399daea865e29d2c056db2746eb3aad94ab26e591cf7669ffcb060583dfee0b"
}
],
"recorded": "2026-10-16"
Expand Down
6 changes: 3 additions & 3 deletions docs/project/sysml-v1-transformation-census-baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
},
{
"name": "fixtures",
"files": 123
"files": 124
}
],
"counts": {
Expand Down Expand Up @@ -140,7 +140,7 @@
},
"uml:Class": {
"pssm": 311,
"fixtures": 567
"fixtures": 570
},
"uml:ClearAssociationAction": {
"pssm": 0,
Expand Down Expand Up @@ -248,7 +248,7 @@
},
"uml:Property": {
"pssm": 25,
"fixtures": 566
"fixtures": 571
},
"uml:Property[qualifier]": {
"pssm": 0,
Expand Down
2 changes: 1 addition & 1 deletion docs/project/sysml-v1-transformation-census.md
Original file line number Diff line number Diff line change
Expand Up @@ -1094,7 +1094,7 @@ SysML profile namespace. The counts are elements affected, not effort.
<!-- census:begin gaps -->
| Rank | OMG mappings | Status | Scope | PSSM suite | Fixtures | Total | Reason |
|---|---|---|---|---|---|---|---|
| 1 | 1: `PropertyUntyped_Mapping` | ⚠️ approximate | `uml:Property` | 25 | 566 | 591 | an untyped property is written as the usage its tool's property-kind marker names, as a `ref` typed by its default individual, else as a `part` when composite and an `attribute` otherwise, rather than always as a reference usage; the scope counts every uml:Property, so it is an upper bound |
| 1 | 1: `PropertyUntyped_Mapping` | ⚠️ approximate | `uml:Property` | 25 | 571 | 596 | an untyped property is written as the usage its tool's property-kind marker names, as a `ref` typed by its default individual, else as a `part` when composite and an `attribute` otherwise, rather than always as a reference usage; the scope counts every uml:Property, so it is an upper bound |
| 2 | 8: `EndToSubsettedFeatureChaining_Mapping`, `EndToSubsettedFeature_Mapping`, `NonOwnedEndFeatureTyping_Mapping`, `NonOwnedEndMembership_Mapping`, `NonOwnedEndSubsettingMembership_Mapping`, `NonOwnedEndSubsetting_Mapping`, `NonOwnedEndToSubsettedFeatureMembership_Mapping`, `NonOwnedEnd_Mapping` | ⚠️ approximate | `uml:Association` | 4 | 37 | 41 | a classifier-owned end is written as that classifier's property and as an end of the connection def crossing it through the opposite end, `end wheels : Wheel crosses car.wheels;`, the mapping's feature chain; the cross is left out, and the association reported approximated with the reason, where it would be unsound: the opposite end is untyped, the end's type is not written or is written as a usage (an actor), the association is not binary, or the property is not written as a feature of the opposite end's type |
| 3 | 1: `TimeTriggerReferenceUsage2_Mapping` | ⚠️ approximate | `uml:TimeEvent` | 0 | 16 | 16 | a relative time event is written `accept after <duration>` and an absolute one `accept at <instant>` on the simulation clock; a time that is neither a number with a time unit nor an expression is unmapped (fixtures: 2 of 10 time events) |
| 4 | 3: `EqualOperatorExpressionFeatureValue_Mapping`, `EqualOperatorExpressionFeature_Mapping`, `EqualOperatorExpressionOperandParameterMembership_Mapping` | ❌ not implemented | `uml:TestIdentityAction` | 14 | 0 | 14 | these are used only by TIAOperatorExpression_Mapping, as the private first/second operands of a TestIdentityAction's `==`, not by Expression trees; they are written with TestIdentityAction, which is not migrated (PSSM: 14) |
Expand Down
6 changes: 5 additions & 1 deletion docs/project/view-rendering-forms.md
Original file line number Diff line number Diff line change
Expand Up @@ -901,7 +901,11 @@ digraph "PlantViews::placedView" {
does, so no row is ever empty). No edge is written to a member drawn as a row, and the
notice counts them (`4 node(s) without a position, drawn as compartment rows of the node
above them`); a row shows the member alone, so the members nested under a row are left undrawn
and the notice counts them apart (`, and 1 node(s) under them left undrawn`). A member with a
and the notice counts them apart (`, and 1 node(s) under them left undrawn`). A member of a
compartment the owner's `Layout` hides with a `show…` switch (`showParts = false`; see
[DiagramLayout annotations](diagram-layout-annotations.md)) is not drawn as a row and the
notice says so (`9 node(s) without a position: 7 drawn as compartment rows of the node above
them, 2 in compartments its Layout hides left undrawn`); its edges are dropped as a row's are. A member with a
`Layout` of its own stays a node (a compartment row by its box when inside the owner's, an edge
when outside), as does an unplaced member with a positioned node under it, so that node keeps
its owner. An unpositioned tree is unchanged: every member
Expand Down
24 changes: 24 additions & 0 deletions docs/reference/sysml-v1-migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,30 @@ as several members — a control flow written as a succession into a join the mi
and from that join on — is pinned to the members whose both ends a diagram symbol stands at, so
the route is drawn where Cameo drew it and not into a node no symbol stands for.

The compartments a modeler collapsed on a symbol are kept with its box. A Cameo symbol's
`SUPPRESS_*` compartment properties — `BooleanProperty` elements in its `properties`, a `value`
of `true` meaning suppressed and no `value` the tool's default, shown — are read from the symbol
itself and then from the chain of symbol styles the stream's `symbolStyles` hold (the property
manager its `symbolStyleID` names and each `parentPropertyManager` above it), the symbol's own
setting winning over an inherited one. Each suppressed compartment is written as a `show…`
switch of the symbol's `Layout`, by the kind its members take in v2: parts (`SUPPRESS_PARTS_COMPARTMENT`,
`SUPPRESS_IBD_PARTS_COMPARTMENT`) `showParts = false`; references (`…REFERENCES_COMPARTMENT`)
`showReferences`; properties, values and class attributes (`…PROPERTIES_COMPARTMENT`,
`…VALUES_COMPARTMENT`, `SUPPRESS_CLASS_ATTRIBUTES`, the IBD values, UML-property and initial-value
compartments) `showAttributes`; constraints (`…CONSTRAINTS_COMPARTMENT`) `showConstraints`; flow
properties (`…FLOWPROPERTIES_COMPARTMENT`) `showItems`; proxy, full and class ports
(`SUPPRESS_PROXYPORTS_COMPARTMENT`, `SUPPRESS_FULLPORTS_COMPARTMENT`, `SUPPRESS_CLASS_PORTS`)
`showPorts`; operations (`SUPPRESS_CLASS_OPERATIONS`, `SUPPRESS_IBD_OPERATIONS_COMPARTMENT`)
`showActions`; a v2 compartment lists what several Cameo compartments list between them, so its
switch is written only when every one of them the symbol states is suppressed (a block's class
attributes compartment is suppressed by Cameo's own style while its values compartment is shown:
`showAttributes` stays unset). The report's layout summary counts them (`# compartments suppressed: N on M
symbols`; `compartmentsSuppressed` and `symbolsSuppressing` in the sidecar). A suppressed
compartment no v2 compartment answers to (`SUPPRESS_STRUCTURE`, `SUPPRESS_BEHAVIORS`,
`SUPPRESS_SIGNAL_RECEPTIONS`) is counted among the unsupported presentation properties and
dropped. The renderer then lists only the compartments left on and counts only their rows against
the box's stated size (see [DiagramLayout annotations](../project/diagram-layout-annotations.md)).

A note the diagram shows is written as `metadata DiagramLayout::Note about <ref> { text; x; y;
width; height; }` — a `Note` symbol whatever it names, a comment's symbol, or a free text box
saying something of its own — with the comment's body when it stands for a comment and the
Expand Down
26 changes: 26 additions & 0 deletions internal/ir/view/compartments.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"slices"
"strings"

"github.com/Open-MBEE/OpenSysML/internal/semantic/symbols"
"github.com/Open-MBEE/OpenSysML/internal/syntax/source"
)

Expand Down Expand Up @@ -139,6 +140,31 @@ func (l labeller) compartments(node *Node) []Compartment {
return node.Compartments
}

// referenceRow reports whether sym lists as a reference row, the `ref part`
// a tool's references compartment holds: a structural usage that refers to
// its value rather than owning it. An attribute, referential by definition,
// is an attribute row.
func referenceRow(sym *symbols.Symbol) bool {
switch sym.Kind {
case symbols.SymbolAttributeUsage, symbols.SymbolEnumerationUsage:
return false
}
return structuralUsage(sym) && !compositeUsage(sym)
}

// hiddenRow reports whether node's Layout hides the compartment row would
// list in: a hidden "references" hides every referential row, a hidden kind
// the compartment of that name.
func hiddenRow(node, row *Node) bool {
if node.Geometry == nil || !node.Geometry.Hidden.Any() {
return false
}
if row.Referential && !row.StandIn {
return node.Geometry.Hidden.Hides("references")
}
return node.Geometry.Hidden.Hides(compartmentName(row.Kind))
Comment on lines +162 to +165

@devin-ai-integration devin-ai-integration Bot Oct 16, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suppressed kinds leave reference rows visible

When showParts = false, hiddenRow leaves a ref part visible unless showReferences is also false. The reference check bypasses the row's kind, so suppressed kind compartments still show referential members.

Learn more

A tree can put a ref part in the parts compartment, as compartmentsOf groups rows by keyword. The new reference check prevents the kind check from running. It therefore keeps referential members of a hidden kind in the box when references remain enabled.

Example: With showParts = false and showReferences unset, ref part mount : Mount remains listed in parts, while an ordinary part lens : Lens is removed.

Recommended fix: Hide a referential row when either its references switch or its own kind's switch hides it; retain the kind check after the reference check.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intended, and documented in docs/project/diagram-layout-annotations.md: showReferences is the switch of the ref rows wherever they are listed, showParts the switch of the composite part rows. The switches mirror the tool's compartments: Cameo lists a reference property in its own "references" compartment, apart from "parts", and SUPPRESS_PARTS_COMPARTMENT leaves the references visible there. A ref part in a box whose Layout says showParts = false with showReferences unset is therefore still listed, as it was in the source diagram; a modeler who collapsed both gets showReferences = false too. Hiding it under showParts would drop a row the diagram showed.

One correction did come out of checking this: an attribute is referential by Usage::isReference, so the first revision treated attribute rows as reference rows and showAttributes = false did not hide them. referenceRow now counts only structural non-composite usages (ref part, ref item, …) as reference rows (2d43de8).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Handled in 96e0ce3. The rule is deliberate: a ref part belongs to the references compartment (SysML v2 graphical notation; Cameo's SUPPRESS_REFERENCES_COMPARTMENT is what hides it there), so showReferences governs it and showParts does not — otherwise a Cameo symbol that suppresses parts but shows references would lose its reference rows. What 96e0ce3 fixed is the over-wide classification: referenceRow now marks only structural non-composite usages as referential, so attribute rows (which Usage::isReference also reports) follow showAttributes. Covered by TestGoldenSuppressedCompartments and TestSuppressedCompartmentsAreNotListedNorClipped (internal/ir/view; the selective view shows a ref part under showParts = false and hides it under showReferences = false).

}

// rowCount is how many rows a node's compartments hold.
func (l labeller) rowCount(node *Node) int {
n := 0
Expand Down
14 changes: 10 additions & 4 deletions internal/ir/view/geometry.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,19 @@ type Point struct {

// Geometry is where a node is drawn, from the DiagramLayout::Layout positioning
// it in the rendered view; HasSize says whether Width and Height were given.
// Hidden are the compartments the Layout's show… switches turn off for this
// symbol, which its box neither lists nor counts as clipped.
type Geometry struct {
X, Y float64
Width, Height float64
HasSize bool
Collapsed bool
Hidden semantics.HiddenCompartments
}

// geometryFromLayout is the Geometry a resolved Layout gives.
func geometryFromLayout(l *semantics.Layout) *Geometry {
return &Geometry{X: l.X, Y: l.Y, Width: l.Width, Height: l.Height, HasSize: l.HasSize, Collapsed: l.Collapsed, Hidden: l.Hidden}
}

// centre is the middle of the geometry, its corner when it has no size.
Expand Down Expand Up @@ -201,8 +209,7 @@ func (r *Renderer) geometryOf(view, elem *symbols.Symbol, out *Rendering) *Geome
if site.Layout == nil {
return nil
}
l := site.Layout
return &Geometry{X: l.X, Y: l.Y, Width: l.Width, Height: l.Height, HasSize: l.HasSize, Collapsed: l.Collapsed}
return geometryFromLayout(site.Layout)
}

// memberGeometryOf is the Geometry positioning, in view, the member owner has
Expand All @@ -223,8 +230,7 @@ func (r *Renderer) memberGeometryOf(view, owner *symbols.Symbol, name string, ou
if site.Layout == nil {
return nil
}
l := site.Layout
return &Geometry{X: l.X, Y: l.Y, Width: l.Width, Height: l.Height, HasSize: l.HasSize, Collapsed: l.Collapsed}
return geometryFromLayout(site.Layout)
}

// memberDress gives node the Style of, and adds the Notes anchored to, the
Expand Down
48 changes: 32 additions & 16 deletions internal/ir/view/placement.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,13 +150,16 @@ func foldCompartments(r *Rendering) *Rendering {
out := *r
out.Roots = cloneNodes(r.Roots)
rows := map[string]bool{}
drawn, nested := p.foldNodes(out.Roots, rows)
if drawn == 0 {
folded := p.foldNodes(out.Roots, rows)
if folded.drawn+folded.hidden == 0 {
return r
}
notice := fmt.Sprintf("%d node(s) without a position, drawn as compartment rows of the node above them", drawn)
if nested > 0 {
notice += fmt.Sprintf(", and %d node(s) under them left undrawn", nested)
notice := fmt.Sprintf("%d node(s) without a position, drawn as compartment rows of the node above them", folded.drawn)
if folded.hidden > 0 {
notice = fmt.Sprintf("%d node(s) without a position: %d drawn as compartment rows of the node above them, %d in compartments its Layout hides left undrawn", folded.drawn+folded.hidden, folded.drawn, folded.hidden)
}
if folded.nested > 0 {
notice += fmt.Sprintf(", and %d node(s) under them left undrawn", folded.nested)
}
out.Edges = make([]Edge, 0, len(r.Edges))
for _, edge := range r.Edges {
Expand All @@ -182,27 +185,40 @@ func foldCompartments(r *Rendering) *Rendering {
return &out
}

// folding counts what folding drew as compartment rows, what it left undrawn
// because the node's Layout hides the row's compartment, and the nodes under
// the rows, which a row does not show.
type folding struct {
drawn, hidden, nested int
}

// foldNodes folds the unplaced members of each placed node under nodes into
// its compartments, in place, recording the IDs of the rows and of every
// node under them in rows; it counts the rows drawn and the nodes under
// them, which a row does not show.
func (p *placement) foldNodes(nodes []*Node, rows map[string]bool) (drawn, nested int) {
// its compartments, in place, less the rows of the compartments the node's
// Layout hides, recording the IDs of the folded members and of every node
// under them in rows.
func (p *placement) foldNodes(nodes []*Node, rows map[string]bool) folding {
var f folding
for _, node := range nodes {
if p.placed[node.ID] {
var folded []*Node
var folded, listed []*Node
node.Children, folded = p.rowsOf(node)
node.Compartments = compartmentsOf(folded)
for _, row := range folded {
before := len(rows)
recordIDs(row, rows)
drawn++
nested += len(rows) - before - 1
f.nested += len(rows) - before - 1
if hiddenRow(node, row) {
f.hidden++
continue
}
f.drawn++
listed = append(listed, row)
}
node.Compartments = compartmentsOf(listed)
}
d, n := p.foldNodes(node.Children, rows)
drawn, nested = drawn+d, nested+n
sub := p.foldNodes(node.Children, rows)
f.drawn, f.hidden, f.nested = f.drawn+sub.drawn, f.hidden+sub.hidden, f.nested+sub.nested
}
return drawn, nested
return f
}

// recordIDs records the ID of node and of every node under it in ids.
Expand Down
Loading
Loading