Skip to content

Commit

Permalink
feat: Implement Cable Tree View
Browse files Browse the repository at this point in the history
  • Loading branch information
huyenngn committed Sep 25, 2024
1 parent 0fe7900 commit c4966ce
Show file tree
Hide file tree
Showing 3 changed files with 163 additions and 0 deletions.
13 changes: 13 additions & 0 deletions capellambse_context_diagrams/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ def init() -> None:
register_tree_view()
register_realization_view()
register_data_flow_view()
register_cable_tree_view()
# register_functional_context() XXX: Future


Expand Down Expand Up @@ -283,3 +284,15 @@ def register_data_flow_view() -> None:
for class_, dgcls, default_render_params in supported_classes:
accessor = context.DataFlowAccessor(dgcls.value, default_render_params)
m.set_accessor(class_, "data_flow_view", accessor)


def register_cable_tree_view() -> None:
"""Add the `cable_tree_view` attribute to `PhysicalLink`s."""
m.set_accessor(
cs.PhysicalLink,
"cable_tree",
context.CableTreeAccessor(
DiagramType.PAB.value,
{},
),
)
112 changes: 112 additions & 0 deletions capellambse_context_diagrams/collectors/cable_tree.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# SPDX-FileCopyrightText: 2022 Copyright DB InfraGO AG and the capellambse-context-diagrams contributors
# SPDX-License-Identifier: Apache-2.0

from __future__ import annotations

import copy
import typing as t

import capellambse.model as m

from .. import _elkjs, context
from . import makers

DEFAULT_LAYOUT_OPTIONS: _elkjs.LayoutOptions = {
"algorithm": "layered",
"edgeRouting": "ORTHOGONAL",
"elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX",
}


class CableTreeCollector:
def __init__(
self,
diagram: context.ContextDiagram,
params: dict[str, t.Any],
) -> None:
self.diagram = diagram
self.obj: m.ModelElement = self.diagram.target
self.data = makers.make_diagram(diagram)
self.data.layoutOptions = DEFAULT_LAYOUT_OPTIONS
self.params = params
self.boxes: dict[str, _elkjs.ELKInputChild] = {}
self.edges: dict[str, _elkjs.ELKInputEdge] = {}
self.ports: dict[str, _elkjs.ELKInputPort] = {}

def __call__(self) -> _elkjs.ELKInputData:
src_obj = self.obj.source
tgt_obj = self.obj.target
target_link = self.make_edge(self.obj, src_obj, tgt_obj)
target_link.layoutOptions = copy.deepcopy(
_elkjs.EDGE_STRAIGHTENING_LAYOUT_OPTIONS
)
self.make_tree(src_obj)
self.make_tree(tgt_obj, reverse=True)
return self.data

def make_tree(
self, port_obj: m.ModelElement, reverse: bool = False
) -> _elkjs.ELKInputChild:
box = self.make_port_and_owner(port_obj)
for link in port_obj.links:
if self.edges.get(link.uuid):
continue
if link.source.uuid == port_obj.uuid:
obj = link.target
else:
obj = link.source
if reverse:
self.make_edge(link, obj, port_obj)
else:
self.make_edge(link, port_obj, obj)
self.make_tree(obj, reverse=reverse)
return box

def make_edge(
self,
link: m.ModelElement,
src_obj: m.ModelElement,
tgt_obj: m.ModelElement,
) -> _elkjs.ELKInputEdge:
edge = _elkjs.ELKInputEdge(
id=link.uuid, sources=[src_obj.uuid], targets=[tgt_obj.uuid]
)
self.data.edges.append(edge)
self.edges[link.uuid] = edge
return edge

def _make_box(
self,
obj: t.Any,
**kwargs: t.Any,
) -> _elkjs.ELKInputChild:
box = makers.make_box(
obj,
**kwargs,
)
self.boxes[obj.uuid] = box
return box

def make_port_and_owner(
self, port_obj: m.ModelElement
) -> _elkjs.ELKInputChild:
owner_obj = port_obj.owner
if not (box := self.boxes.get(owner_obj.uuid)):
box = self._make_box(
owner_obj,
layout_options=makers.DEFAULT_LABEL_LAYOUT_OPTIONS,
)
self.data.children.append(box)
if port := self.ports.get(port_obj.uuid):
return box
port = makers.make_port(port_obj.uuid)
# port.labels = makers.make_label(port_obj.name)
box.ports.append(port)
self.ports[port_obj.uuid] = port
return box


def collector(
diagram: context.ContextDiagram, params: dict[str, t.Any]
) -> _elkjs.ELKInputData:
return CableTreeCollector(diagram, params)()
38 changes: 38 additions & 0 deletions capellambse_context_diagrams/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

from . import _elkjs, filters, serializers, styling
from .collectors import (
cable_tree,
dataflow_view,
exchanges,
get_elkdata,
Expand Down Expand Up @@ -181,6 +182,22 @@ def __get__( # type: ignore
return self._get(obj, DataFlowViewDiagram)


class CableTreeAccessor(ContextAccessor):
"""Provides access to the cable tree diagrams."""

def __get__( # type: ignore
self,
obj: m.T | None,
objtype: type | None = None,
) -> m.Accessor | ContextDiagram:
"""Make a CableTreeView for the given model object."""
del objtype
if obj is None: # pragma: no cover
return self
assert isinstance(obj, m.ModelElement)
return self._get(obj, CableTreeViewDiagram)


class ContextDiagram(m.AbstractDiagram):
"""An automatically generated context diagram.
Expand Down Expand Up @@ -694,6 +711,27 @@ def _create_diagram(self, params: dict[str, t.Any]) -> cdiagram.Diagram:
return super()._create_diagram(params)


class CableTreeViewDiagram(ContextDiagram):
"""An automatically generated CableTreeView."""

@property
def uuid(self) -> str: # type: ignore
"""Returns the UUID of the diagram."""
return f"{self.target.uuid}_cable_tree"

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

def _create_diagram(self, params: dict[str, t.Any]) -> cdiagram.Diagram:
data = cable_tree.collector(self, params)
layout = try_to_layout(data)
return self.serializer.make_diagram(
layout,
transparent_background=params.get("transparent_background", False),
)


def try_to_layout(data: _elkjs.ELKInputData) -> _elkjs.ELKOutputData:
"""Try calling elkjs, raise a JSONDecodeError if it fails."""
try:
Expand Down

0 comments on commit c4966ce

Please sign in to comment.