Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support mixed component exchanges #111

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 8 additions & 8 deletions capellambse_context_diagrams/collectors/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ def __init__(
self.made_boxes = {self.centerbox.id: self.centerbox}
self.boxes_to_delete = {self.centerbox.id}
self.edges: list[fa.AbstractExchange] = []
self.stack_heights: dict[str, float | int] = {
"input": -makers.NEIGHBOR_VMARGIN,
"output": -makers.NEIGHBOR_VMARGIN,
}
if self.diagram.display_parent_relation:
self.diagram_target_owners = generic.get_all_owners(
self.diagram.target
Expand All @@ -72,11 +76,7 @@ def process_context(self):
box.children = [self.centerbox]
del self.data.children[0]

stack_heights: dict[str, float | int] = {
"input": -makers.NEIGHBOR_VMARGIN,
"output": -makers.NEIGHBOR_VMARGIN,
}
self._process_ports(stack_heights)
self._process_ports()

if self.diagram.display_parent_relation and self.diagram.target.owner:
current = self.diagram.target.owner
Expand Down Expand Up @@ -108,7 +108,7 @@ def process_context(self):
generic.move_edges(owner_boxes, self.edges, self.data)

self.centerbox.height = max(
self.centerbox.height, *stack_heights.values()
self.centerbox.height, *self.stack_heights.values()
)

def _process_exchanges(self) -> tuple[
Expand Down Expand Up @@ -147,7 +147,7 @@ def _process_exchanges(self) -> tuple[

return ports, ex_datas

def _process_ports(self, stack_heights: dict[str, float | int]) -> None:
def _process_ports(self) -> None:
ports, ex_datas = self._process_exchanges()
for port, local_ports, side in port_context_collector(ex_datas, ports):
_, label_height = helpers.get_text_extent(port.name)
Expand Down Expand Up @@ -182,7 +182,7 @@ def _process_ports(self, stack_heights: dict[str, float | int]) -> None:
current = self._make_owner_box(self.diagram, current)
self.common_owners.add(current.uuid)

stack_heights[side] += makers.NEIGHBOR_VMARGIN + height
self.stack_heights[side] += makers.NEIGHBOR_VMARGIN + height

def _make_box(
self,
Expand Down
183 changes: 119 additions & 64 deletions capellambse_context_diagrams/collectors/exchanges.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from __future__ import annotations

import abc
import collections.abc as cabc
import logging
import operator
import typing as t
Expand Down Expand Up @@ -56,11 +57,11 @@ def __init__(
self.obj = self.diagram.target
self.params = params

src, trg, alloc_fex, fncs = self.intermap[diagram.type]
src, trg, alloc_fex, alloc_fncs = self.intermap[diagram.type]
self.get_source = operator.attrgetter(src)
self.get_target = operator.attrgetter(trg)
self.get_alloc_fex = operator.attrgetter(alloc_fex)
self.get_alloc_functions = operator.attrgetter(fncs)
self.get_alloc_functions = operator.attrgetter(alloc_fncs)

def get_functions_and_exchanges(
self, comp: common.GenericElement, interface: common.GenericElement
Expand Down Expand Up @@ -154,8 +155,9 @@ def make_ports_and_update_children_size(
)
child.height = height
stack_height += makers.NEIGHBOR_VMARGIN + height

data.height = stack_height
self.make_ports_and_update_children_size(child, exchanges)
if data.children:
data.height = stack_height

@abc.abstractmethod
def collect(self) -> None:
Expand Down Expand Up @@ -186,65 +188,114 @@ class InterfaceContextCollector(ExchangeCollector):
for building the interface context.
"""

left: _elkjs.ELKInputChild | None
left_box: _elkjs.ELKInputChild | None
"""Left (source) Component Box of the interface."""
right: _elkjs.ELKInputChild | None
right_box: _elkjs.ELKInputChild | None
"""Right (target) Component Box of the interface."""
outgoing_edges: dict[str, common.GenericElement]
incoming_edges: dict[str, common.GenericElement]
outgoing_edges: list[common.GenericElement]
incoming_edges: list[common.GenericElement]
global_boxes: dict[str, _elkjs.ELKInputChild]
boxes_to_delete: set[str]

def __init__(
self,
diagram: context.InterfaceContextDiagram,
data: _elkjs.ELKInputData,
params: dict[str, t.Any],
) -> None:
self.left = None
self.right = None
self.incoming_edges = {}
self.outgoing_edges = {}
self.left_box = None
self.right_box = None
self.incoming_edges = []
self.outgoing_edges = []
self.global_boxes = {}
self.boxes_to_delete = set()

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

self.get_left_and_right()
if diagram.include_interface:
self.add_interface()

def get_left_and_right(self) -> None:
made_children: set[str] = set()
def _find_or_make_box(
self,
obj: t.Any,
**kwargs: t.Any,
) -> _elkjs.ELKInputChild:
if not (box := self.global_boxes.get(obj.uuid)):
box = makers.make_box(
obj,
**kwargs,
)
self.global_boxes[obj.uuid] = box
else:
for key, value in kwargs.items():
setattr(box, key, value)
return box

def _process_ports(self, element: common.GenericElement) -> None:
for port in element.inputs + element.outputs:
for ex in port.exchanges:
elem = (
self.get_source(ex)
if port in element.inputs
else self.get_target(ex)
)
parent_comp = self._find_or_make_box(
elem.owner, no_symbol=True
)
if box := self.make_boxes(
elem, elem.functions, [], self._process_ports
):
parent_comp.children.append(box)
self.boxes_to_delete.add(elem.uuid)
if port in element.inputs:
self.incoming_edges.extend(port.exchanges)
else:
self.outgoing_edges.extend(port.exchanges)

def make_boxes(
self,
element: common.GenericElement,
functions: list[common.GenericElement],
components: list[common.GenericElement],
process_ports: (
cabc.Callable[[common.GenericElement], None] | None
) = None,
) -> _elkjs.ELKInputChild | None:
children = []
if element.uuid in self.global_boxes:
return None
for fnc in functions:
if process_ports:
process_ports(fnc)
if fnc_box := self.make_boxes(
fnc, fnc.functions, [], self._process_ports
):
children.append(fnc_box)
self.boxes_to_delete.add(fnc.uuid)
for cmp in components:
if child := self.make_boxes(**cmp):
children.append(child)
self.boxes_to_delete.add(cmp["element"].uuid)
if children:
layout_options = makers.DEFAULT_LABEL_LAYOUT_OPTIONS
else:
layout_options = makers.CENTRIC_LABEL_LAYOUT_OPTIONS

box = self._find_or_make_box(
element, no_symbol=True, layout_options=layout_options
)
box.children = children
return box

def get_left_and_right(self) -> None:
def get_capella_order(
comp: common.GenericElement, functions: list[common.GenericElement]
comp: common.GenericElement,
functions: list[common.GenericElement],
) -> list[common.GenericElement]:
alloc_functions = self.get_alloc_functions(comp)
return [fnc for fnc in alloc_functions if fnc in functions]

def make_boxes(cntxt: dict[str, t.Any]) -> _elkjs.ELKInputChild | None:
comp = cntxt["element"]
functions = cntxt["functions"]
components = cntxt["components"]
if comp.uuid not in made_children:
children = [
makers.make_box(fnc)
for fnc in functions
if fnc in self.get_alloc_functions(comp)
]
for cmp in components:
if child := make_boxes(cmp):
children.append(child)
if children:
layout_options = makers.DEFAULT_LABEL_LAYOUT_OPTIONS
else:
layout_options = makers.CENTRIC_LABEL_LAYOUT_OPTIONS

box = makers.make_box(
comp, no_symbol=True, layout_options=layout_options
)
box.children = children
made_children.add(comp.uuid)
return box
return None

try:
comp = self.get_source(self.obj)
left_context, incs, outs = self.collect_context(comp, self.obj)
Expand All @@ -258,26 +309,30 @@ def make_boxes(cntxt: dict[str, t.Any]) -> _elkjs.ELKInputChild | None:
_out_port_ids = set(ex.source.uuid for ex in incs.values())
_port_spread = len(_out_port_ids) - len(_inc_port_ids)

left_context["functions"] = get_capella_order(
comp, left_context["functions"]
)
right_context["functions"] = get_capella_order(
_comp, right_context["functions"]
)
left_context["functions"] = [
f
for f in get_capella_order(comp, left_context["functions"])
if f in self.get_alloc_functions(left_context["element"])
]
right_context["functions"] = [
f
for f in get_capella_order(_comp, right_context["functions"])
if f in self.get_alloc_functions(right_context["element"])
]
if port_spread >= _port_spread:
self.incoming_edges = incs
self.outgoing_edges = outs
self.incoming_edges = list(incs.values())
self.outgoing_edges = list(outs.values())
else:
self.incoming_edges = outs
self.outgoing_edges = incs
self.incoming_edges = list(outs.values())
self.outgoing_edges = list(incs.values())
left_context, right_context = right_context, left_context

if left_child := make_boxes(left_context):
self.data.children.append(left_child)
self.left = left_child
if right_child := make_boxes(right_context):
self.data.children.append(right_child)
self.right = right_child
self.left_box = self.make_boxes(**left_context)
self.right_box = self.make_boxes(**right_context)

for uuid in self.boxes_to_delete:
del self.global_boxes[uuid]
self.data.children.extend(self.global_boxes.values())
except AttributeError:
pass

Expand All @@ -290,19 +345,19 @@ def add_interface(self) -> None:
is_hierarchical=False,
)
src, tgt = generic.exchange_data_collector(ex_data)
assert self.right is not None
if self.get_source(self.obj).uuid == self.right.id:
assert self.right_box is not None
if self.get_source(self.obj).uuid == self.right_box.id:
self.data.edges[-1].sources = [tgt.uuid]
self.data.edges[-1].targets = [src.uuid]

assert self.left is not None
self.left.ports.append(makers.make_port(src.uuid))
self.right.ports.append(makers.make_port(tgt.uuid))
assert self.left_box is not None
self.left_box.ports.append(makers.make_port(src.uuid))
self.right_box.ports.append(makers.make_port(tgt.uuid))

def collect(self) -> None:
"""Collect all allocated `FunctionalExchange`s in the context."""
try:
for ex in (self.incoming_edges | self.outgoing_edges).values():
for ex in self.incoming_edges + self.outgoing_edges:
ex_data = generic.ExchangeData(
ex,
self.data,
Expand All @@ -312,7 +367,7 @@ def collect(self) -> None:
)
src, tgt = generic.exchange_data_collector(ex_data)

if ex in self.incoming_edges.values():
if ex in self.incoming_edges:
self.data.edges[-1].sources = [tgt.uuid]
self.data.edges[-1].targets = [src.uuid]

Expand Down
Loading
Loading