diff --git a/capellambse/aird/__init__.py b/capellambse/aird/__init__.py
index d62e07013..e683b1a02 100644
--- a/capellambse/aird/__init__.py
+++ b/capellambse/aird/__init__.py
@@ -213,7 +213,9 @@ def parse_diagram(
def _element_from_xml(ebd: C.ElementBuilder) -> diagram.DiagramElement:
"""Construct a single diagram element from the model XML."""
- if ebd.data_element.get("element") is not None:
+ element = ebd.data_element.get("element")
+ tag = ebd.melodyloader[element].tag if element else None
+ if element is not None and tag != "ownedRepresentationDescriptors":
factory = _semantic.from_xml
else:
factory = _visual.from_xml
diff --git a/capellambse/aird/_visual.py b/capellambse/aird/_visual.py
index a8cdd80a6..68494eb9f 100644
--- a/capellambse/aird/_visual.py
+++ b/capellambse/aird/_visual.py
@@ -65,7 +65,13 @@ def shape_factory(ebd: c.ElementBuilder) -> diagram.Box:
assert ebd.target_diagram.styleclass is not None
uid = ebd.data_element.attrib[c.ATT_XMID]
- label = ebd.data_element.get("description", "")
+ element = ebd.data_element.get("element")
+ if element is not None:
+ label = ebd.melodyloader[element].attrib["name"]
+ description = ebd.data_element.get("description", "")
+ else:
+ label = ebd.data_element.get("description", "")
+ description = None
parent = ebd.data_element.getparent()
while parent.tag == "children":
parent_uid = parent.attrib.get("element") or parent.attrib.get(
@@ -97,14 +103,21 @@ def shape_factory(ebd: c.ElementBuilder) -> diagram.Box:
int(layout.attrib.get("width", "0")),
int(layout.attrib.get("height", "0")),
)
- styleclass = ebd.data_element.attrib["type"]
+
+ if element is not None:
+ styleclass = "RepresentationLink"
+ else:
+ styleclass = "Note"
+
styleoverrides = _styling.apply_visualelement_styles(
ebd.target_diagram.styleclass, f"Box.{styleclass}", ebd.data_element
)
+
return diagram.Box(
pos,
size,
label=label,
+ description=description,
uuid=uid,
parent=parent,
styleclass=styleclass,
diff --git a/capellambse/diagram/_diagram.py b/capellambse/diagram/_diagram.py
index cbd7c4fdd..ef5f198eb 100644
--- a/capellambse/diagram/_diagram.py
+++ b/capellambse/diagram/_diagram.py
@@ -62,6 +62,7 @@ def __init__(
size: diagram.Vec2ish,
*,
label: Box | str | None = None,
+ description: str | None = None,
uuid: str | None = None,
parent: Box | None = None,
collapsed: bool = False,
@@ -87,6 +88,8 @@ def __init__(
Box' label text and contained children.
label
This box' label text.
+ description
+ Optional label text used only by Representation Links.
uuid
UUID of the semantic element this box represents.
parent
@@ -124,6 +127,7 @@ def __init__(
self.minsize = minsize
self.maxsize = maxsize
self.label: Box | str | None = label
+ self.description: str | None = description
self.collapsed: bool = collapsed
self.features: cabc.MutableSequence[str] | None = features
diff --git a/capellambse/diagram/_json_enc.py b/capellambse/diagram/_json_enc.py
index 586d05350..8d6c14c5c 100644
--- a/capellambse/diagram/_json_enc.py
+++ b/capellambse/diagram/_json_enc.py
@@ -69,6 +69,8 @@ def __encode_box(o: diagram.Box) -> object:
}
if o.label is not None and not o.hidelabel:
jsonobj["label"] = _encode_label(o.label)
+ if o.description is not None:
+ jsonobj["description"] = o.description
if o.styleoverrides:
jsonobj["style"] = _encode_styleoverrides(o.styleoverrides)
if o.features:
diff --git a/capellambse/diagram/capstyle.py b/capellambse/diagram/capstyle.py
index 229e8c1a3..dc600f298 100644
--- a/capellambse/diagram/capstyle.py
+++ b/capellambse/diagram/capstyle.py
@@ -280,6 +280,11 @@ class in the form::
"stroke": RGB(255, 204, 102),
"text_fill": RGB(0, 0, 0),
},
+ "Box.RepresentationLink": {
+ "fill": RGB(255, 255, 203),
+ "stroke": RGB(255, 204, 102),
+ "text_fill": RGB(0, 0, 0),
+ },
"Box.Requirement": { # ReqVP_Requirement
"fill": COLORS["light_purple"],
"stroke": COLORS["dark_purple"],
diff --git a/capellambse/svg/decorations.py b/capellambse/svg/decorations.py
index 9499c3226..204858236 100644
--- a/capellambse/svg/decorations.py
+++ b/capellambse/svg/decorations.py
@@ -36,6 +36,7 @@
"Class",
"Enumeration",
"Note",
+ "RepresentationLink",
"OperationalActivity",
"PhysicalComponent",
}
diff --git a/capellambse/svg/drawing.py b/capellambse/svg/drawing.py
index 8c29eb705..37db32cb3 100644
--- a/capellambse/svg/drawing.py
+++ b/capellambse/svg/drawing.py
@@ -120,6 +120,7 @@ def add_rect(
*,
class_: str = "",
label: LabelDict | None = None,
+ description: str | None = None,
features: cabc.Sequence[str] = (),
id_: str | None = None,
children: bool = False,
@@ -143,6 +144,21 @@ def add_rect(
rect: shapes.Rect = self.__drawing.rect(**rectparams)
grp.add(rect)
+ if description is not None and label is not None:
+ new_label: LabelDict = copy.deepcopy(label)
+ new_label["text"] = description
+ self._draw_box_label(
+ LabelBuilder(
+ new_label,
+ grp,
+ labelstyle=text_style,
+ class_=class_,
+ text_anchor="middle",
+ y_margin=None,
+ icon=False,
+ )
+ )
+
if features or class_ in decorations.needs_feature_line:
self._draw_feature_line(rect, grp, rect_style)
if features:
@@ -589,6 +605,7 @@ def _draw_box(
children_: cabc.Sequence[str] = (),
features_: cabc.Sequence[str] = (),
label_: str | LabelDict | None = None,
+ description_: str | None = None,
id_: str,
class_: str,
obj_style: style.Styling,
@@ -621,6 +638,7 @@ def _draw_box(
class_=class_,
id_=id_,
label=label,
+ description=description_,
features=features_,
children=bool(children_),
)
diff --git a/capellambse/svg/style.py b/capellambse/svg/style.py
index 769ccbcb9..ee0819f81 100644
--- a/capellambse/svg/style.py
+++ b/capellambse/svg/style.py
@@ -21,6 +21,7 @@
"__GLOBAL__": (
"ErrorSymbol",
"RequirementSymbol",
+ "RepresentationLinkSymbol",
),
"Error": (),
"Class Diagram Blank": ("ClassSymbol",),
diff --git a/capellambse/svg/symbols.py b/capellambse/svg/symbols.py
index 2c36117fe..a8f378ec7 100644
--- a/capellambse/svg/symbols.py
+++ b/capellambse/svg/symbols.py
@@ -1144,6 +1144,53 @@ def class_symbol(id_: str = "ClassSymbol") -> container.Symbol:
return symb
+@decorations.deco_factories
+def representation_link_symbol(
+ id_: str = "RepresentationLinkSymbol",
+) -> container.Symbol:
+ symb = container.Symbol(id=id_, viewBox="0 0 16 16")
+ grp = symb.add(container.Group(style="stroke-width:0.5;"))
+ grp.add(
+ shapes.Rect(
+ insert=(5.95, 2.96),
+ size=(4.98, 2.7),
+ rx=0.5,
+ style="fill:#d9d297;stroke:#a48a44;",
+ )
+ )
+ grp.add(
+ shapes.Rect(
+ insert=(1.66, 10.1),
+ size=(4.98, 2.7),
+ rx=0.5,
+ style="fill:#abc0c9;stroke:#557099;",
+ )
+ )
+ grp.add(
+ shapes.Rect(
+ insert=(10.12, 10.1),
+ size=(4.98, 2.7),
+ rx=0.5,
+ style="fill:#acbd57;stroke:#326f46;",
+ )
+ )
+ grp.add(
+ path.Path(
+ d="m 8.4526548,7.7355622 -4.3161119,-0.00711 0.00491,"
+ "2.2573969 m 4.3112023,-2.250294 4.3161128,-0.00711 "
+ "-0.0049,2.2573968",
+ style="fill:none;stroke:#557099;",
+ )
+ )
+ grp.add(
+ path.Path(
+ d="m 8.5000114,5.7276095 0.00519,2.0502552",
+ style="fill:none;stroke:#557099;",
+ )
+ )
+ return symb
+
+
@decorations.deco_factories
def fine_arrow_mark(
id_: str = "FineArrow", *, style: style_.Styling, **kw
diff --git a/tests/data/melodymodel/6_0/Melody Model Test.aird b/tests/data/melodymodel/6_0/Melody Model Test.aird
index 35b8c5439..5b672cf68 100644
--- a/tests/data/melodymodel/6_0/Melody Model Test.aird
+++ b/tests/data/melodymodel/6_0/Melody Model Test.aird
@@ -79,7 +79,7 @@
-
+
@@ -146,7 +146,7 @@
-
+
@@ -158,7 +158,7 @@
-
+
@@ -170,7 +170,7 @@
-
+
@@ -1975,16 +1975,27 @@
-
-
-
+
+
+
-
-
-
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -2453,22 +2464,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -2517,22 +2512,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -2869,13 +2848,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
@@ -2918,7 +2929,7 @@
-
+
@@ -3022,14 +3033,20 @@
-
-
-
-
- KEEP_LOCATION
- KEEP_SIZE
- KEEP_RATIO
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -3183,7 +3200,7 @@
-
+
@@ -3202,10 +3219,10 @@
-
+
-
+
@@ -3635,17 +3652,6 @@
-
-
-
-
- routingStyle
-
-
-
-
-
-
@@ -3655,16 +3661,6 @@
-
-
-
-
- routingStyle
-
-
-
-
-
@@ -3711,6 +3707,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -5194,28 +5209,28 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -5265,21 +5280,21 @@
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
-
-
-
+
+
+
+
+
@@ -5308,8 +5323,8 @@
-
-
+
+
@@ -5324,7 +5339,7 @@
-
+
KEEP_LOCATION
@@ -5368,11 +5383,11 @@
-
-
-
-
-
+
+
+
+
+
@@ -5466,31 +5481,11 @@
-
-
-
-
-
-
-
-
-
-
-
- KEEP_LOCATION
- KEEP_SIZE
- KEEP_RATIO
-
-
-
-
-
-
-
-
- routingStyle
+
+
+
-
+
@@ -8345,26 +8340,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -8379,6 +8354,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -8413,6 +8410,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -8430,6 +8447,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -8465,6 +8498,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
@@ -8505,29 +8556,6 @@
-
-
-
-
-
-
-
- KEEP_LOCATION
- KEEP_SIZE
- KEEP_RATIO
-
-
-
-
-
- KEEP_LOCATION
- KEEP_SIZE
- KEEP_RATIO
-
-
-
-
-
@@ -8573,6 +8601,39 @@
+
+
+
+
+
+
+
+ KEEP_LOCATION
+ KEEP_SIZE
+ KEEP_RATIO
+
+
+
+
+
+ KEEP_LOCATION
+ KEEP_SIZE
+ KEEP_RATIO
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -9245,7 +9306,7 @@
-
+
@@ -9305,7 +9366,7 @@
-
+
@@ -9382,7 +9443,7 @@
-
+
@@ -9393,7 +9454,7 @@
-
+
@@ -9404,7 +9465,7 @@
-
+
@@ -9464,7 +9525,7 @@
-
+
@@ -9479,7 +9540,7 @@
-
+
@@ -9492,6 +9553,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -9784,7 +9871,22 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -9857,7 +9959,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
italic
@@ -10150,7 +10252,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -10191,36 +10293,39 @@
-
+
-
-
-
+
+
+
+
-
+
-
+
+ size
routingStyle
strokeColor
- size
-
-
+
+
+
-
+
-
+
+ size
routingStyle
strokeColor
- size
-
-
+
+
+
@@ -10376,16 +10481,15 @@
-
-
+
+
+
+
+
-
-
-
-