Skip to content

Commit

Permalink
fix(context-diagrams)!: Handle nested functions
Browse files Browse the repository at this point in the history
Possibly with no owners faulty state.
  • Loading branch information
ewuerger committed Jul 17, 2024
1 parent 454e178 commit a575505
Show file tree
Hide file tree
Showing 8 changed files with 558 additions and 460 deletions.
4 changes: 2 additions & 2 deletions capellambse_context_diagrams/collectors/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ def __init__(
self.boxes_to_delete = {self.centerbox.id}
self.edges: list[fa.AbstractExchange] = []
if self.diagram._display_parent_relation:
self.diagram_target_owners = generic.get_all_owners(
self.diagram.target
self.diagram_target_owners = list(
generic.get_all_owners(self.diagram.target)
)
self.common_owners: set[str] = set()

Expand Down
88 changes: 50 additions & 38 deletions capellambse_context_diagrams/collectors/exchanges.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,31 +144,6 @@ def __init__(

super().__init__(diagram, data, params)

self._functional_exchanges: common.ElementList[
common.GenericElement
] = self.get_alloc_fex(diagram.target)
self._derived_functional_exchanges: dict[
str, common.GenericElement
] = {}
self._ex_validity: dict[
str, dict[str, common.GenericElement | None]
] = {
fex.uuid: {"source": None, "target": None}
for fex in self._functional_exchanges
}

self.get_left_and_right()
if diagram.hide_functions:
assert self.left is not None
self.left.children = [] # type: ignore[unreachable]
assert self.right is not None
self.right.children = []
self.incoming_edges = {}
self.outgoing_edges = {}

if diagram.include_interface or diagram.hide_functions:
self.add_interface()

def get_left_and_right(self) -> None:
try:
self.collect_context()
Expand Down Expand Up @@ -197,13 +172,36 @@ def collect_context(self):
self.right.id: self.right,
}
for fex in self.get_alloc_fex(self.obj):
srid = self.make_all_owners(fex.source, boxes)
trid = self.make_all_owners(fex.target, boxes)
if [srid, trid] == [self.right.id, self.left.id]:
try:
src_id = self.make_all_owners(fex.source, boxes)
except ValueError as error:
logger.warning(error.args[0])
continue

try:
trg_id = self.make_all_owners(fex.target, boxes)
except ValueError as error:
src_port = boxes[fex.source.owner.uuid].ports.pop()
assert src_port.id == fex.source.uuid
logger.warning(error.args[0])
continue

if [src_id, trg_id] == [self.right.id, self.left.id]:
self.incoming_edges[fex.uuid] = fex
elif [srid, trid] == [self.left.id, self.right.id]:
elif [src_id, trg_id] == [self.left.id, self.right.id]:
self.outgoing_edges[fex.uuid] = fex

for uuid, box in boxes.items():
element = self.obj._model.by_uuid(uuid)
if isinstance(element, fa.AbstractFunction) and (
parent_box := boxes.get(element.parent.uuid)
):
owner_box = boxes[element.owner.uuid]
owner_box.children.remove(box)
parent_box.children.append(box)
for label in parent_box.labels:
label.layoutOptions = makers.DEFAULT_LABEL_LAYOUT_OPTIONS

if self.left.children:
for label in self.left.labels:
label.layoutOptions = makers.DEFAULT_LABEL_LAYOUT_OPTIONS
Expand All @@ -212,7 +210,9 @@ def collect_context(self):
label.layoutOptions = makers.DEFAULT_LABEL_LAYOUT_OPTIONS

def make_all_owners(
self, obj: fa.AbstractFunction, boxes: dict[str, _elkjs.ELKInputChild]
self,
obj: fa.AbstractFunction | fa.FunctionPort,
boxes: dict[str, _elkjs.ELKInputChild],
) -> str:
owners: list[fa.AbstractFunction | cs.Component] = []
assert self.right is not None and self.left is not None
Expand All @@ -225,10 +225,15 @@ def make_all_owners(

owners.append(element)

assert root is not None, f"No root found for {obj._short_repr_()}"
if root is None:
raise ValueError(f"No root found for {obj._short_repr_()}")

owner_box: common.GenericElement = root
for owner in owners[::-1]:
for owner in reversed(owners):
if isinstance(owner, fa.FunctionPort):
if owner.uuid in (p.id for p in owner_box.ports):
continue

owner_box.ports.append(makers.make_port(owner.uuid))
else:
if owner.uuid in (b.id for b in owner_box.children):
Expand All @@ -242,6 +247,7 @@ def make_all_owners(
for label in owner_box.labels:
label.layoutOptions = makers.DEFAULT_LABEL_LAYOUT_OPTIONS
owner_box = box

return root.id

def add_interface(self) -> None:
Expand All @@ -260,6 +266,18 @@ def add_interface(self) -> None:

def collect(self) -> None:
"""Collect all allocated `FunctionalExchange`s in the context."""
self.get_left_and_right()
if self.diagram._hide_functions:
assert self.left is not None
self.left.children = []
assert self.right is not None
self.right.children = []
self.incoming_edges = {}
self.outgoing_edges = {}

if self.diagram._include_interface or self.diagram._hide_functions:
self.add_interface()

try:
for ex in (self.incoming_edges | self.outgoing_edges).values():
ex_data = generic.ExchangeData(
Expand All @@ -270,12 +288,6 @@ def collect(self) -> None:
is_hierarchical=False,
)
src, tgt = generic.exchange_data_collector(ex_data)
if ex.uuid in self._derived_functional_exchanges:
class_ = type(ex).__name__
self.data.edges[-1].id = (
f"{makers.STYLECLASS_PREFIX}-{class_}:{ex.uuid}"
)

if ex in self.incoming_edges.values():
self.data.edges[-1].sources = [tgt.uuid]
self.data.edges[-1].targets = [src.uuid]
Expand Down
15 changes: 5 additions & 10 deletions capellambse_context_diagrams/collectors/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,8 +228,8 @@ def move_edges(
"""Move edges to boxes."""
edges_to_remove: list[str] = []
for c in connections:
source_owner_uuids = get_all_owners(c.source)
target_owner_uuids = get_all_owners(c.target)
source_owner_uuids = list(get_all_owners(c.source))
target_owner_uuids = list(get_all_owners(c.target))
if c.source == c.target:
source_owner_uuids.remove(c.source.uuid)
target_owner_uuids.remove(c.source.uuid)
Expand All @@ -252,14 +252,9 @@ def move_edges(
data.edges = [e for e in data.edges if e.id not in edges_to_remove]


def get_all_owners(obj: common.GenericElement) -> list[str]:
def get_all_owners(obj: common.GenericElement) -> cabc.Iterator[str]:
"""Return the UUIDs from all owners of ``obj``."""
owners: list[str] = []
current = obj
while current is not None:
owners.append(current.uuid)
try:
current = current.owner
except AttributeError:
break
return owners
yield current.uuid
current = getattr(current, "owner", None)
32 changes: 4 additions & 28 deletions capellambse_context_diagrams/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,30 +76,12 @@ def _get(
diagram_class: type[ContextDiagram],
diagram_id: str = "{}_context",
) -> common.Accessor | ContextDiagram:
try:
cache = getattr(
obj._model, ".".join((__name__, diagram_class.__qualname__))
)
except AttributeError:
cache = {}
setattr(
obj._model,
".".join((__name__, diagram_class.__qualname__)),
cache,
)
diagram_id = diagram_id.format(obj.uuid)
try:
return cache[diagram_id]
except KeyError:
pass

new_diagram = diagram_class(
self._dgcls,
obj,
default_render_parameters=self._default_render_params,
)
new_diagram.filters.add(filters.NO_UUID)
cache[diagram_id] = new_diagram
return new_diagram


Expand Down Expand Up @@ -348,7 +330,7 @@ def _create_diagram(self, params: dict[str, t.Any]) -> cdiagram.Diagram:
if not isinstance(
self, (ClassTreeDiagram, InterfaceContextDiagram)
) and has_single_child(data):
self.display_derived_interfaces = True
self._display_derived_interfaces = True
data = get_elkdata(self, params)

layout = try_to_layout(data)
Expand Down Expand Up @@ -387,17 +369,13 @@ class InterfaceContextDiagram(ContextDiagram):
context diagram target: The interface ComponentExchange.
* hide_functions — Boolean flag to enable white box view: Only
displaying Components or Entities.
* display_derived_exchanges — Boolean flag to enable inclusion of
functional exchanges that are not allocated to the interface but
connect allocated functions of collected components.
In addition to all other render parameters of
[`ContextDiagram`][capellambse_context_diagrams.context.ContextDiagram].
"""

_include_interface: bool
_hide_functions: bool
_display_derived_exchanges: bool

def __init__(
self,
Expand All @@ -410,7 +388,6 @@ def __init__(
default_render_parameters = {
"include_interface": False,
"hide_functions": False,
"display_derived_exchanges": False,
"display_symbols_as_boxes": True,
} | default_render_parameters
super().__init__(
Expand All @@ -420,21 +397,20 @@ def __init__(
default_render_parameters=default_render_parameters,
)

self.dangling_functional_exchanges: list[fa.AbstractExchange] = []

@property
def name(self) -> str: # type: ignore
return f"Interface Context of {self.target.name}"

def _create_diagram(self, params: dict[str, t.Any]) -> cdiagram.Diagram:
super_params = params.copy()
params = self._default_render_parameters | params
for param_name in self._default_render_parameters:
setattr(self, f"_{param_name}", params.pop(param_name))

params["elkdata"] = exchanges.get_elkdata_for_exchanges(
super_params["elkdata"] = exchanges.get_elkdata_for_exchanges(
self, exchanges.InterfaceContextCollector, params
)
return super()._create_diagram(params)
return super()._create_diagram(super_params)


class FunctionalContextDiagram(ContextDiagram):
Expand Down
65 changes: 26 additions & 39 deletions docs/gen_images.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,26 +17,26 @@
dest = pathlib.Path("assets") / "images"
model_path = pathlib.Path(__file__).parent.parent / "tests" / "data"
model = MelodyModel(path=model_path, entrypoint="ContextDiagram.aird")
general_context_diagram_uuids: dict[str, tuple[str, dict[str, t.Any]]] = {
"Environment": ("e37510b9-3166-4f80-a919-dfaac9b696c7", {}),
"Eat": ("8bcb11e6-443b-4b92-bec2-ff1d87a224e7", {}),
"Middle": ("da08ddb6-92ba-4c3b-956a-017424dbfe85", {}),
"Capability": ("9390b7d5-598a-42db-bef8-23677e45ba06", {}),
"Lost": ("a5642060-c9cc-4d49-af09-defaa3024bae", {}),
"Left": ("f632888e-51bc-4c9f-8e81-73e9404de784", {}),
"educate Wizards": ("957c5799-1d4a-4ac0-b5de-33a65bf1519c", {}),
"Weird guy": ("098810d9-0325-4ae8-a111-82202c0d2016", {}),
"Top secret": ("5bf3f1e3-0f5e-4fec-81d5-c113d3a1b3a6", {}),
"Physical Node": ("fdb34c92-7c49-491d-bf11-dd139930786e", {}),
"Physical Behavior": ("313f48f4-fb7e-47a8-b28a-76440932fcb9", {}),
"Maintain": ("ee745644-07d7-40b9-ad7a-910dc8cbb805", {}),
general_context_diagram_uuids: dict[str, str] = {
"Environment": "e37510b9-3166-4f80-a919-dfaac9b696c7",
"Eat": "8bcb11e6-443b-4b92-bec2-ff1d87a224e7",
"Middle": "da08ddb6-92ba-4c3b-956a-017424dbfe85",
"Capability": "9390b7d5-598a-42db-bef8-23677e45ba06",
"Lost": "a5642060-c9cc-4d49-af09-defaa3024bae",
"Left": "f632888e-51bc-4c9f-8e81-73e9404de784",
"educate Wizards": "957c5799-1d4a-4ac0-b5de-33a65bf1519c",
"Weird guy": "098810d9-0325-4ae8-a111-82202c0d2016",
"Top secret": "5bf3f1e3-0f5e-4fec-81d5-c113d3a1b3a6",
"Physical Node": "fdb34c92-7c49-491d-bf11-dd139930786e",
"Physical Behavior": "313f48f4-fb7e-47a8-b28a-76440932fcb9",
"Maintain": "ee745644-07d7-40b9-ad7a-910dc8cbb805",
}
interface_context_diagram_uuids: dict[str, tuple[str, dict[str, t.Any]]] = {
"Left to right": ("3ef23099-ce9a-4f7d-812f-935f47e7938d", {}),
"Interface": ("2f8ed849-fbda-4902-82ec-cbf8104ae686", {}),
interface_context_diagram_uuids: dict[str, str] = {
"Left to right": "3ef23099-ce9a-4f7d-812f-935f47e7938d",
"Interface": "2f8ed849-fbda-4902-82ec-cbf8104ae686",
}
hierarchy_context = "16b4fcc5-548d-4721-b62a-d3d5b1c1d2eb"
diagram_uuids = general_context_diagram_uuids | interface_context_diagram_uuids
diagram_uuids = interface_context_diagram_uuids
class_tree_uuid = "b7c7f442-377f-492c-90bf-331e66988bda"
realization_fnc_uuid = "beaf5ba4-8fa9-4342-911f-0266bb29be45"
realization_comp_uuid = "b9f9a83c-fb02-44f7-9123-9d86326de5f1"
Expand All @@ -45,16 +45,15 @@


def generate_index_images() -> None:
for uuid, render_params in diagram_uuids.values():
for uuid in diagram_uuids.values():
diag: context.ContextDiagram = model.by_uuid(uuid).context_diagram
with mkdocs_gen_files.open(f"{str(dest / diag.name)}.svg", "w") as fd:
render_params["transparent_background"] = False # type: ignore[index]
print(diag.render("svg", **render_params), file=fd) # type: ignore[arg-type]
print(diag.render("svg", transparent_background=False), file=fd) # type: ignore[arg-type]


def generate_symbol_images() -> None:
for name in ("Capability", "Middle"):
uuid, _ = general_context_diagram_uuids[name]
uuid = general_context_diagram_uuids[name]
diag: context.ContextDiagram = model.by_uuid(uuid).context_diagram
diag._display_symbols_as_boxes = True
diag.invalidate_cache()
Expand Down Expand Up @@ -175,17 +174,16 @@ def generate_derived_image() -> None:


def generate_interface_with_hide_functions_image():
uuid = interface_context_diagram_uuids["Interface"][0]
uuid = interface_context_diagram_uuids["Interface"]
diag: context.ContextDiagram = model.by_uuid(uuid).context_diagram
params = {"hide_functions": True}
with mkdocs_gen_files.open(
f"{str(dest / diag.name)}-hide-functions.svg", "w"
) as fd:
print(diag.render("svg", **params), file=fd)
print(diag.render("svg", hide_functions=True), file=fd)


def generate_interface_with_hide_interface_image():
uuid = interface_context_diagram_uuids["Interface"][0]
uuid = interface_context_diagram_uuids["Interface"]
diag: context.ContextDiagram = model.by_uuid(uuid).context_diagram
params = {"include_interface": False}
with mkdocs_gen_files.open(
Expand All @@ -194,24 +192,14 @@ def generate_interface_with_hide_interface_image():
print(diag.render("svg", **params), file=fd)


def generate_interface_with_display_derived_exchanges_image():
uuid = "86a1afc2-b7fd-4023-bbd5-ab44f5dc2c28"
diag: context.ContextDiagram = model.by_uuid(uuid).context_diagram
params = {"display_derived_exchanges": True}
with mkdocs_gen_files.open(
f"{str(dest / diag.name)}-derived-exchanges.svg", "w"
) as fd:
print(diag.render("svg", **params), file=fd)


generate_index_images()
generate_hierarchy_image()
generate_symbol_images()

wizard_uuid = general_context_diagram_uuids["educate Wizards"][0]
wizard_uuid = general_context_diagram_uuids["educate Wizards"]
generate_no_edgelabel_image(wizard_uuid)

lost_uuid = general_context_diagram_uuids["Lost"][0]
lost_uuid = general_context_diagram_uuids["Lost"]
generate_filter_image(lost_uuid, filters.EX_ITEMS, "ex")
generate_filter_image(lost_uuid, filters.SHOW_EX_ITEMS, "fex and ex")
generate_filter_image(lost_uuid, filters.EXCH_OR_EX_ITEMS, "fex or ex")
Expand All @@ -229,6 +217,5 @@ def generate_interface_with_display_derived_exchanges_image():
generate_realization_view_images()
generate_data_flow_image()
generate_derived_image()
# generate_interface_with_hide_functions_image()
generate_interface_with_hide_functions_image()
generate_interface_with_hide_interface_image()
generate_interface_with_display_derived_exchanges_image()
Loading

0 comments on commit a575505

Please sign in to comment.