diff --git a/.gitattributes b/.gitattributes index 153aa61d..8347dd83 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Copyright DB Netz AG and the capellambse-context-diagrams contributors +# SPDX-FileCopyrightText: 2022 Copyright DB InfraGO AG and the capellambse-context-diagrams contributors # SPDX-License-Identifier: CC0-1.0 # “.gitattributes Best Practices - Muhammad Rehan Saeed” {{{ diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 55473d55..2b054033 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Copyright DB Netz AG and the capellambse-context-diagrams contributors +# SPDX-FileCopyrightText: 2022 Copyright DB InfraGO AG and the capellambse-context-diagrams contributors # SPDX-License-Identifier: CC0-1.0 * @ewuerger @vik378 diff --git a/.github/workflows/build-test-publish.yml b/.github/workflows/build-test-publish.yml index 1ecfbb27..5ab9d391 100644 --- a/.github/workflows/build-test-publish.yml +++ b/.github/workflows/build-test-publish.yml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Copyright DB Netz AG and the capellambse-context-diagrams contributors +# SPDX-FileCopyrightText: 2022 Copyright DB InfraGO AG and the capellambse-context-diagrams contributors # SPDX-License-Identifier: Apache-2.0 name: Build diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 1a7a1159..0098f24e 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Copyright DB Netz AG and the capellambse-context-diagrams contributors +# SPDX-FileCopyrightText: 2022 Copyright DB InfraGO AG and the capellambse-context-diagrams contributors # SPDX-License-Identifier: Apache-2.0 name: Docs diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 206f6a6d..d0840338 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Copyright DB Netz AG and the capellambse-context-diagrams contributors +# SPDX-FileCopyrightText: 2022 Copyright DB InfraGO AG and the capellambse-context-diagrams contributors # SPDX-License-Identifier: Apache-2.0 name: Lint diff --git a/.gitignore b/.gitignore index 67f7ae9a..701276a4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Copyright DB Netz AG and the capellambse-context-diagrams contributors +# SPDX-FileCopyrightText: 2022 Copyright DB InfraGO AG and the capellambse-context-diagrams contributors # SPDX-License-Identifier: CC0-1.0 # Templates from diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7ed76d1b..1d3d5abd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Copyright DB Netz AG and the capellambse-context-diagrams contributors +# SPDX-FileCopyrightText: 2022 Copyright DB InfraGO AG and the capellambse-context-diagrams contributors # SPDX-License-Identifier: CC0-1.0 exclude: '^(versioneer\.py|.*/_version\.py)$' @@ -47,7 +47,7 @@ repos: - --license-filepath - license_header.txt - --comment-style - - '#' + - "#" - id: insert-license name: Insert Licence for HTML/XML/SVG files files: '\.html$|\.md$|\.svg$' @@ -56,7 +56,7 @@ repos: - --license-filepath - license_header.txt - --comment-style - - '' + - "" - id: insert-license name: Insert Licence for CSS files files: '\.css$' @@ -65,7 +65,7 @@ repos: - --license-filepath - license_header.txt - --comment-style - - '/*| *| */' + - "/*| *| */" - repo: https://github.com/fsfe/reuse-tool rev: v2.1.0 hooks: diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 25882f8b..00000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - "name": "Test ClassTree Diagram", - "type": "python", - "request": "launch", - "console": "integratedTerminal", - "envFile": ".env", - "pythonArgs": ["-Xdev"], - "module": "capellambse.repl", - "args": ["ros-msg"] - } - ] -} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1075dc65..949e32f9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,5 +1,5 @@ @@ -110,7 +110,7 @@ Example: > Integration test for capability context diagrams seen in `tests/test_capability_diagrams.py`: > > ```python -> # SPDX-FileCopyrightText: 2022 Copyright DB Netz AG and the capellambse-context-diagrams contributors +> # SPDX-FileCopyrightText: 2022 Copyright DB InfraGO AG and the capellambse-context-diagrams contributors > # SPDX-License-Identifier: Apache-2.0 > > import capellambse @@ -143,14 +143,14 @@ Example: > > Documentation can look like this `docs/index.md`: > ```markdown -> - ??? example "🔥Brand-new🔥 [`ctx.Mission`][capellambse.model.layers.ctx.Mission] (MCB) 🔥Brand-new🔥" +> - ??? example "[`ctx.Mission`][capellambse.model.layers.ctx.Mission] (MCB)" > > ``` py > import capellambse > > model = capellambse.MelodyModel("tests/data/ContextDiagram.aird") > diag = model.by_uuid("5bf3f1e3-0f5e-4fec-81d5-c113d3a1b3a6").context_diagram -> diag.render("svgdiagram").save_drawing(pretty=True) +> diag.render("svgdiagram").save(pretty=True) > ``` >
> diff --git a/README.md b/README.md index afc98b6f..5f0d394a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ @@ -35,7 +35,7 @@ Special thanks goes to the developers and maintainers of [Eclipse Layout Kernel Copyright and license information added and maintained via the reuse tool from [Reuse Software](https://reuse.software/). -***Copyright 2022 DB Netz AG, own contributions licensed under Apache 2.0 (see full text in [LICENSES/Apache-2.0](https://github.com/DSD-DBS/capellambse-context-diagrams/blob/master/LICENSES/Apache-2.0.txt))*** +***Copyright 2022 DB InfraGO AG, own contributions licensed under Apache 2.0 (see full text in [LICENSES/Apache-2.0](https://github.com/DSD-DBS/capellambse-context-diagrams/blob/master/LICENSES/Apache-2.0.txt))*** ***Copyright (c) 2021 Kiel University and others, ELK/Sprotty contributions ([elkgraph-json.js](https://github.com/DSD-DBS/capellambse-context-diagrams/blob/master/capellambse_context_diagrams/elkgraph-json.js) & [elkgraph-to-sprotty.js](https://github.com/DSD-DBS/capellambse-context-diagrams/blob/master/capellambse_context_diagrams/elkgraph-to-sprotty.js)) licensed under EPL-2.0*** diff --git a/capellambse_context_diagrams/__init__.py b/capellambse_context_diagrams/__init__.py index 130af425..00ced73a 100644 --- a/capellambse_context_diagrams/__init__.py +++ b/capellambse_context_diagrams/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Copyright DB Netz AG and the capellambse-context-diagrams contributors +# SPDX-FileCopyrightText: 2022 Copyright DB InfraGO AG and the capellambse-context-diagrams contributors # SPDX-License-Identifier: Apache-2.0 """The Context Diagrams model extension. @@ -20,6 +20,7 @@ import collections.abc as cabc import logging +import typing as t from importlib import metadata from capellambse.diagram import COLORS, CSSdef, capstyle @@ -36,8 +37,10 @@ __version__ = "0.0.0+unknown" del metadata -ClassPair = tuple[type[common.GenericElement], DiagramType] - +DefaultRenderParams = dict[str, t.Any] +ClassPair = tuple[ + type[common.GenericElement], DiagramType, DefaultRenderParams +] logger = logging.getLogger(__name__) ATTR_NAME = "context_diagram" @@ -48,30 +51,34 @@ def init() -> None: register_classes() register_interface_context() register_tree_view() + register_realization_view() # register_functional_context() XXX: Future def register_classes() -> None: """Add the `context_diagram` property to the relevant model objects.""" supported_classes: list[ClassPair] = [ - (oa.Entity, DiagramType.OAB), - (oa.OperationalActivity, DiagramType.OAIB), - (oa.OperationalCapability, DiagramType.OCB), - (ctx.Mission, DiagramType.MCB), - (ctx.Capability, DiagramType.MCB), - (ctx.SystemComponent, DiagramType.SAB), - (ctx.SystemFunction, DiagramType.SDFB), - (la.LogicalComponent, DiagramType.LAB), - (la.LogicalFunction, DiagramType.LDFB), - (pa.PhysicalComponent, DiagramType.PAB), - (pa.PhysicalFunction, DiagramType.PDFB), + (oa.Entity, DiagramType.OAB, {}), + (oa.OperationalActivity, DiagramType.OAIB, {}), + (oa.OperationalCapability, DiagramType.OCB, {}), + (ctx.Mission, DiagramType.MCB, {}), + (ctx.Capability, DiagramType.MCB, {"display_symbols_as_boxes": False}), + ( + ctx.SystemComponent, + DiagramType.SAB, + {"display_symbols_as_boxes": True}, + ), + (ctx.SystemFunction, DiagramType.SDFB, {}), + (la.LogicalComponent, DiagramType.LAB, {}), + (la.LogicalFunction, DiagramType.LDFB, {}), + (pa.PhysicalComponent, DiagramType.PAB, {}), + (pa.PhysicalFunction, DiagramType.PDFB, {}), ] patch_styles(supported_classes) class_: type[common.GenericElement] - for class_, dgcls in supported_classes: - common.set_accessor( - class_, ATTR_NAME, context.ContextAccessor(dgcls.value) - ) + for class_, dgcls, default_render_params in supported_classes: + accessor = context.ContextAccessor(dgcls.value, default_render_params) + common.set_accessor(class_, ATTR_NAME, accessor) def patch_styles(classes: cabc.Iterable[ClassPair]) -> None: @@ -94,7 +101,7 @@ def patch_styles(classes: cabc.Iterable[ClassPair]) -> None: "Box.OperationalCapability" ] = cap circle_style = {"fill": COLORS["_CAP_xAB_Function_Border_Green"]} - for _, dt in classes: + for _, dt, _ in classes: capstyle.STYLES[dt.value]["Circle.FunctionalExchange"] = circle_style @@ -152,9 +159,45 @@ def register_functional_context() -> None: def register_tree_view() -> None: - """Add the `tree_view` attribute to ``Class``es.""" + """Add the ``tree_view`` attribute to ``Class``es.""" common.set_accessor( information.Class, "tree_view", context.ClassTreeAccessor(DiagramType.CDB.value), ) + + +def register_realization_view() -> None: + """Add the ``realization_view`` attribute to various objects. + + Adds ``realization_view`` to Activities, Functions and Components + of all layers. + """ + supported_classes: list[ClassPair] = [ + (oa.Entity, DiagramType.OAB, {}), + (oa.OperationalActivity, DiagramType.OAIB, {}), + (ctx.SystemComponent, DiagramType.SAB, {}), + (ctx.SystemFunction, DiagramType.SDFB, {}), + (la.LogicalComponent, DiagramType.LAB, {}), + (la.LogicalFunction, DiagramType.LDFB, {}), + (pa.PhysicalComponent, DiagramType.PAB, {}), + (pa.PhysicalFunction, DiagramType.PDFB, {}), + ] + styles: dict[str, dict[str, capstyle.CSSdef]] = {} + for class_, dgcls, _ in supported_classes: + common.set_accessor( + class_, + "realization_view", + context.RealizationViewContextAccessor("RealizationView Diagram"), + ) + styles.update(capstyle.STYLES.get(dgcls.value, {})) + + capstyle.STYLES["RealizationView Diagram"] = styles + capstyle.STYLES["RealizationView Diagram"].update( + capstyle.STYLES["__GLOBAL__"] + ) + capstyle.STYLES["RealizationView Diagram"]["Edge.Realization"] = { + "stroke": capstyle.COLORS["dark_gray"], + "marker-end": "FineArrowMark", + "stroke-dasharray": "5", + } diff --git a/capellambse_context_diagrams/_elkjs.py b/capellambse_context_diagrams/_elkjs.py index 7ebe3995..b84053e0 100644 --- a/capellambse_context_diagrams/_elkjs.py +++ b/capellambse_context_diagrams/_elkjs.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Copyright DB Netz AG and the capellambse-context-diagrams contributors +# SPDX-FileCopyrightText: 2022 Copyright DB InfraGO AG and the capellambse-context-diagrams contributors # SPDX-License-Identifier: Apache-2.0 """ @@ -43,7 +43,7 @@ NODE_HOME = Path(capellambse.dirs.user_cache_dir, "elkjs", "node_modules") PATH_TO_ELK_JS = Path(__file__).parent / "elk.js" REQUIRED_NPM_PKG_VERSIONS: t.Dict[str, str] = { - "elkjs": "0.8.2", + "elkjs": "0.9.0", } """npm package names and versions required by this Python module.""" @@ -149,18 +149,24 @@ class ELKSize(t.TypedDict): height: t.Union[int, float] -class ELKOutputData(t.TypedDict): - """Data that comes from ELK.""" +class ELKOutputElement(t.TypedDict): + """Base class for all elements that comes out of ELK.""" id: str + + style: dict[str, t.Any] + + +class ELKOutputData(ELKOutputElement): + """Data that comes from ELK.""" + type: t.Literal["graph"] children: cabc.MutableSequence[ELKOutputChild] # type: ignore -class ELKOutputNode(t.TypedDict): +class ELKOutputNode(ELKOutputElement): """Node that comes out of ELK.""" - id: str type: t.Literal["node"] children: cabc.MutableSequence[ELKOutputChild] # type: ignore @@ -168,20 +174,18 @@ class ELKOutputNode(t.TypedDict): size: ELKSize -class ELKOutputJunction(t.TypedDict): +class ELKOutputJunction(ELKOutputElement): """Exchange-Junction that comes out of ELK.""" - id: str type: t.Literal["junction"] position: ELKPoint size: ELKSize -class ELKOutputPort(t.TypedDict): +class ELKOutputPort(ELKOutputElement): """Port that comes out of ELK.""" - id: str type: t.Literal["port"] children: cabc.MutableSequence[ELKOutputLabel] @@ -189,10 +193,9 @@ class ELKOutputPort(t.TypedDict): size: ELKSize -class ELKOutputLabel(t.TypedDict): +class ELKOutputLabel(ELKOutputElement): """Label that comes out of ELK.""" - id: str type: t.Literal["label"] text: str @@ -200,11 +203,11 @@ class ELKOutputLabel(t.TypedDict): size: ELKSize -class ELKOutputEdge(t.TypedDict): +class ELKOutputEdge(ELKOutputElement): """Edge that comes out of ELK.""" - id: str type: t.Literal["edge"] + sourceId: str targetId: str routingPoints: cabc.MutableSequence[ELKPoint] diff --git a/capellambse_context_diagrams/collectors/__init__.py b/capellambse_context_diagrams/collectors/__init__.py index 756d10f3..6ef9da61 100644 --- a/capellambse_context_diagrams/collectors/__init__.py +++ b/capellambse_context_diagrams/collectors/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Copyright DB Netz AG and the capellambse-context-diagrams contributors +# SPDX-FileCopyrightText: 2022 Copyright DB InfraGO AG and the capellambse-context-diagrams contributors # SPDX-License-Identifier: Apache-2.0 """ diff --git a/capellambse_context_diagrams/collectors/default.py b/capellambse_context_diagrams/collectors/default.py index c9785e4f..10127e41 100644 --- a/capellambse_context_diagrams/collectors/default.py +++ b/capellambse_context_diagrams/collectors/default.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Copyright DB Netz AG and the capellambse-context-diagrams contributors +# SPDX-FileCopyrightText: 2022 Copyright DB InfraGO AG and the capellambse-context-diagrams contributors # SPDX-License-Identifier: Apache-2.0 """ Collection of [`ELKInputData`][capellambse_context_diagrams._elkjs.ELKInputData] @@ -50,7 +50,7 @@ def collector( "output": -makers.NEIGHBOR_VMARGIN, } global_boxes = {centerbox["id"]: centerbox} - child_boxes = list[_elkjs.ELKInputChild]() + child_boxes: list[_elkjs.ELKInputChild] = [] for i, local_ports, side in port_context_collector(ex_datas, ports): _, label_height = helpers.get_text_extent(i.name) height = max( @@ -66,7 +66,9 @@ def collector( ) box["height"] += height else: - box = makers.make_box(i, height=height) + box = makers.make_box( + i, height=height, no_symbol=diagram.display_symbols_as_boxes + ) box["ports"] = [makers.make_port(j.uuid) for j in local_ports] if i.parent.uuid == centerbox["id"]: child_boxes.append(box) diff --git a/capellambse_context_diagrams/collectors/exchanges.py b/capellambse_context_diagrams/collectors/exchanges.py index c0ce776c..4835d601 100644 --- a/capellambse_context_diagrams/collectors/exchanges.py +++ b/capellambse_context_diagrams/collectors/exchanges.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Copyright DB Netz AG and the capellambse-context-diagrams contributors +# SPDX-FileCopyrightText: 2022 Copyright DB InfraGO AG and the capellambse-context-diagrams contributors # SPDX-License-Identifier: Apache-2.0 from __future__ import annotations diff --git a/capellambse_context_diagrams/collectors/generic.py b/capellambse_context_diagrams/collectors/generic.py index 955f7656..e72182d7 100644 --- a/capellambse_context_diagrams/collectors/generic.py +++ b/capellambse_context_diagrams/collectors/generic.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Copyright DB Netz AG and the capellambse-context-diagrams contributors +# SPDX-FileCopyrightText: 2022 Copyright DB InfraGO AG and the capellambse-context-diagrams contributors # SPDX-License-Identifier: Apache-2.0 """ Functionality for collection of model data from an instance of [`MelodyModel`][capellambse.model.MelodyModel] diff --git a/capellambse_context_diagrams/collectors/makers.py b/capellambse_context_diagrams/collectors/makers.py index 469ae5b5..605baa42 100644 --- a/capellambse_context_diagrams/collectors/makers.py +++ b/capellambse_context_diagrams/collectors/makers.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Copyright DB Netz AG and the capellambse-context-diagrams contributors +# SPDX-FileCopyrightText: 2022 Copyright DB InfraGO AG and the capellambse-context-diagrams contributors # SPDX-License-Identifier: Apache-2.0 from __future__ import annotations diff --git a/capellambse_context_diagrams/collectors/portless.py b/capellambse_context_diagrams/collectors/portless.py index 8e2f1929..a887182f 100644 --- a/capellambse_context_diagrams/collectors/portless.py +++ b/capellambse_context_diagrams/collectors/portless.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Copyright DB Netz AG and the capellambse-context-diagrams contributors +# SPDX-FileCopyrightText: 2022 Copyright DB InfraGO AG and the capellambse-context-diagrams contributors # SPDX-License-Identifier: Apache-2.0 """ diff --git a/capellambse_context_diagrams/collectors/realization_view.py b/capellambse_context_diagrams/collectors/realization_view.py index 19e2aec8..03c35c2b 100644 --- a/capellambse_context_diagrams/collectors/realization_view.py +++ b/capellambse_context_diagrams/collectors/realization_view.py @@ -1,5 +1,6 @@ -# SPDX-FileCopyrightText: 2022 Copyright DB Netz AG and the capellambse-context-diagrams contributors +# SPDX-FileCopyrightText: 2022 Copyright DB InfraGO AG and the capellambse-context-diagrams contributors # SPDX-License-Identifier: Apache-2.0 + """This submodule defines the collector for the RealizationView diagram.""" from __future__ import annotations @@ -30,13 +31,14 @@ def collector( data["layoutOptions"] = layout_options _collector = COLLECTORS[params.get("search_direction", "ALL")] lay_to_els = _collector(diagram.target, params.get("depth", 1)) - min_layer_box_width = 0.0 - min_layer_box_height = 0.0 layer_layout_options: _elkjs.LayoutOptions = layout_options | { # type: ignore[operator] "nodeSize.constraints": "[NODE_LABELS,MINIMUM_SIZE]", } # type: ignore[assignment] edges: list[_elkjs.ELKInputEdge] = [] - for layer, elements in lay_to_els.items(): + for layer in ("Operational", "System", "Logical", "Physical"): + if not (elements := lay_to_els.get(layer)): # type: ignore[call-overload] + continue + labels = [makers.make_label(layer)] width, height = makers.calculate_height_and_width(labels) layer_box: _elkjs.ELKInputChild = { @@ -48,8 +50,7 @@ def collector( } children: dict[str, _elkjs.ELKInputChild] = {} for elt in elements: - element_box = makers.make_box(elt["element"], no_symbol=True) - children[elt["element"].uuid] = element_box + assert elt["element"] is not None if elt["origin"] is not None: edges.append( { @@ -59,51 +60,48 @@ def collector( } ) - if params.get("show_owners", False): - layer_box["children"].append(element_box) + if elt.get("reverse", False): + source = elt["element"] + target = elt["origin"] else: - owner = elt["element"].owner - if not isinstance(owner, (fa.Function, cs.Component)): - layer_box["children"].append(element_box) - continue - - if not (owner_box := children.get(owner.uuid)): - owner_box = makers.make_box(owner, no_symbol=True) - owner_box["height"] += element_box["height"] - children[owner.uuid] = owner_box - layer_box["children"].append(owner_box) - - owner_box.setdefault("children", []).append(element_box) - owner_box["width"] += element_box["width"] - if ( - elt["origin"] is not None - and elt["origin"].owner.uuid in children - and owner.uuid in children - ): - eid = f'{elt["origin"].owner.uuid}_{owner.uuid}' - edges.append( - { - "id": eid, - "sources": [elt["origin"].owner.uuid], - "targets": [owner.uuid], - } - ) - - layer_box_width = sum(b["width"] for b in layer_box["children"]) - if layer_box_width > min_layer_box_width: - min_layer_box_width = float(layer_box_width) - layer_box_height = max(b["height"] for b in layer_box["children"]) - if layer_box_height > min_layer_box_height: - min_layer_box_height = float(layer_box_height) + source = elt["origin"] + target = elt["element"] - data["children"].append(layer_box) + if not (element_box := children.get(target.uuid)): + element_box = makers.make_box(target, no_symbol=True) + children[target.uuid] = element_box + layer_box["children"].append(element_box) + index = len(layer_box["children"]) - 1 + + if params.get("show_owners"): + owner = target.owner + if not isinstance(owner, (fa.Function, cs.Component)): + continue + + if not (owner_box := children.get(owner.uuid)): + owner_box = makers.make_box(owner, no_symbol=True) + owner_box["height"] += element_box["height"] + children[owner.uuid] = owner_box + layer_box["children"].append(owner_box) + + del layer_box["children"][index] + owner_box.setdefault("children", []).append(element_box) + owner_box["width"] += element_box["width"] + if ( + source is not None + and source.owner.uuid in children + and owner.uuid in children + ): + eid = f"{source.owner.uuid}_{owner.uuid}" + edges.append( + { + "id": eid, + "sources": [source.owner.uuid], + "targets": [owner.uuid], + } + ) - for layer_box in data["children"]: - layer_box["width"] = min_layer_box_width - layer_box["layoutOptions"][ - "nodeSize.minimum" - ] = f"({min_layer_box_width},{min_layer_box_height})" - data["children"] = data["children"][::-1] + data["children"].append(layer_box) return data, edges @@ -123,7 +121,7 @@ def collect_realizing( def collect_all( start: common.GenericElement, depth: int -) -> dict[LayerLiteral, list[common.GenericElement]]: +) -> dict[LayerLiteral, list[dict[str, t.Any]]]: """Collect all elements in both ABOVE and BELOW directions.""" above = collect_realized(start, depth) below = collect_realizing(start, depth) @@ -135,13 +133,27 @@ def collect_elements( depth: int, direction: str, attribute_prefix: str, - origin: common.GenericElement = None, + origin: common.GenericElement | None = None, ) -> dict[LayerLiteral, list[dict[str, t.Any]]]: """Collect elements based on the specified direction and attribute name.""" layer_obj, layer = find_layer(start) - collected_elements: dict[LayerLiteral, list[dict[str, t.Any]]] = { - layer: [{"element": start, "origin": origin, "layer": layer_obj}] - } + collected_elements: dict[LayerLiteral, list[dict[str, t.Any]]] = {} + if direction == "ABOVE" or origin is None: + collected_elements = { + layer: [{"element": start, "origin": origin, "layer": layer_obj}] + } + elif direction == "BELOW" and origin is not None: + collected_elements = { + layer: [ + { + "element": origin, + "origin": start, + "layer": layer_obj, + "reverse": True, + } + ] + } + if ( (direction == "ABOVE" and layer == "Operational") or (direction == "BELOW" and layer == "Physical") @@ -199,3 +211,4 @@ def find_layer( "ABOVE": collect_realized, "BELOW": collect_realizing, } +"""The functions to receive the diagram elements for every layer.""" diff --git a/capellambse_context_diagrams/collectors/tree_view.py b/capellambse_context_diagrams/collectors/tree_view.py index 29470f05..bfe88472 100644 --- a/capellambse_context_diagrams/collectors/tree_view.py +++ b/capellambse_context_diagrams/collectors/tree_view.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Copyright DB Netz AG and the capellambse-context-diagrams contributors +# SPDX-FileCopyrightText: 2022 Copyright DB InfraGO AG and the capellambse-context-diagrams contributors # SPDX-License-Identifier: Apache-2.0 """This submodule defines the collector for the Class-Tree diagram.""" from __future__ import annotations diff --git a/capellambse_context_diagrams/context.py b/capellambse_context_diagrams/context.py index 28be2b1a..67cec473 100644 --- a/capellambse_context_diagrams/context.py +++ b/capellambse_context_diagrams/context.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Copyright DB Netz AG and the capellambse-context-diagrams contributors +# SPDX-FileCopyrightText: 2022 Copyright DB InfraGO AG and the capellambse-context-diagrams contributors # SPDX-License-Identifier: Apache-2.0 """ Definitions of Custom Accessor- and Diagram-Classtypes based on @@ -13,10 +13,11 @@ import typing as t from capellambse import diagram as cdiagram +from capellambse import helpers from capellambse.model import common, diagram, modeltypes from . import _elkjs, filters, serializers, styling -from .collectors import exchanges, get_elkdata, tree_view +from .collectors import exchanges, get_elkdata, realization_view, tree_view logger = logging.getLogger(__name__) @@ -33,12 +34,15 @@ class ContextAccessor(common.Accessor): """Provides access to the context diagrams.""" - def __init__(self, dgcls: str) -> None: + def __init__( + self, dgcls: str, render_params: dict[str, t.Any] | None = None + ) -> None: super().__init__() self._dgcls = dgcls + self._default_render_params = render_params or {} @t.overload - def __get__(self, obj: None, objtype=None) -> common.Accessor: + def __get__(self, obj: None, objtype: type[t.Any]) -> ContextAccessor: ... @t.overload @@ -84,7 +88,9 @@ def _get( except KeyError: pass - new_diagram = diagram_class(self._dgcls, obj) + new_diagram = diagram_class( + self._dgcls, obj, **self._default_render_params + ) new_diagram.filters.add(filters.NO_UUID) cache[diagram_id] = new_diagram return new_diagram @@ -96,8 +102,10 @@ class InterfaceContextAccessor(ContextAccessor): def __init__( # pylint: disable=super-init-not-called self, diagclass: dict[type[common.GenericElement], str], + render_params: dict[str, t.Any] | None = None, ) -> None: self.__dgclasses = diagclass + self._default_render_params = render_params or {} def __get__( # type: ignore self, @@ -134,15 +142,18 @@ class ClassTreeAccessor(ContextAccessor): """Provides access to the tree view diagrams.""" # pylint: disable=super-init-not-called - def __init__(self, diagclass: str) -> None: + def __init__( + self, diagclass: str, render_params: dict[str, t.Any] | None = None + ) -> None: self._dgcls = diagclass + self._default_render_params = render_params or {} def __get__( # type: ignore self, obj: common.T | None, objtype: type | None = None, ) -> common.Accessor | ContextDiagram: - """Make a ContextDiagram for the given model object.""" + """Make a ClassTreeDiagram for the given model object.""" del objtype if obj is None: # pragma: no cover return self @@ -150,6 +161,29 @@ def __get__( # type: ignore return self._get(obj, ClassTreeDiagram, "{}_class_tree") +class RealizationViewContextAccessor(ContextAccessor): + """Provides access to the realization view diagrams.""" + + # pylint: disable=super-init-not-called + def __init__( + self, diagclass: str, render_params: dict[str, t.Any] | None = None + ) -> None: + self._dgcls = diagclass + self._default_render_params = render_params or {} + + def __get__( # type: ignore + self, + obj: common.T | None, + objtype: type | None = None, + ) -> common.Accessor | ContextDiagram: + """Make a RealizationViewDiagram for the given model object.""" + del objtype + if obj is None: # pragma: no cover + return self + assert isinstance(obj, common.GenericElement) + return self._get(obj, RealizationViewDiagram, "{}_realization_view") + + class ContextDiagram(diagram.AbstractDiagram): """An automatically generated context diagram. @@ -275,12 +309,8 @@ def render(self, fmt: str | None, /, **params) -> t.Any: return super().render(fmt, **rparams) def _create_diagram(self, params: dict[str, t.Any]) -> cdiagram.Diagram: - try: - data = params.get("elkdata") or get_elkdata(self, params) - layout = _elkjs.call_elkjs(data) - except json.JSONDecodeError as error: - logger.error(json.dumps(data, indent=4)) - raise error + data = params.get("elkdata") or get_elkdata(self, params) + layout = try_to_layout(data) return self.serializer.make_diagram(layout) @property # type: ignore @@ -384,17 +414,122 @@ def _create_diagram(self, params: dict[str, t.Any]) -> cdiagram.Diagram: def add_context(data: _elkjs.ELKOutputData) -> None: ids: set[str] = set() - def get_ids(obj: _elkjs.ELKOutputChild): + def get_ids(obj: _elkjs.ELKOutputNode) -> None: if obj["id"] and not obj["id"].startswith("g_"): ids.add(obj["id"]) for cobj in obj.get("children", []): - get_ids(cobj) + if cobj["type"] == "node": + get_ids(cobj) for child in data["children"]: - get_ids(child) + if child["type"] == "node": + get_ids(child) for child in data["children"]: - child["context"] = list(ids) + child["context"] = list(ids) # type: ignore[typeddict-unknown-key] + + +class RealizationViewDiagram(ContextDiagram): + """An automatically generated RealizationViewDiagram Diagram. + + This diagram is exclusively for ``Activity``, ``Function``s, + ``Entity`` and ``Components`` of all layers. + """ + + def __init__(self, class_: str, obj: common.GenericElement, **kw) -> None: + super().__init__(class_, obj, **kw, display_symbols_as_boxes=True) + + @property + def uuid(self) -> str: # type: ignore + """Returns the UUID of the diagram.""" + return f"{self.target.uuid}_realization_view" + + @property + def name(self) -> str: # type: ignore + """Returns the name of the diagram.""" + return f"Realization view of {self.target.name}" + + def _create_diagram(self, params: dict[str, t.Any]) -> cdiagram.Diagram: + params.setdefault("depth", params.get("depth", 1)) + params.setdefault( + "search_direction", params.get("search_direction", "ALL") + ) + params.setdefault("show_owners", params.get("show_owners", True)) + params.setdefault("layer_sizing", params.get("layer_sizing", "UNION")) + data, edges = realization_view.collector(self, params) + layout = try_to_layout(data) + adjust_layer_sizing(data, layout, params["layer_sizing"]) + layout = try_to_layout(data) + for edge in edges: + layout["children"].append( + { + "id": edge["id"], + "type": "edge", + "sourceId": edge["sources"][0], + "targetId": edge["targets"][0], + "routingPoints": [], + "styleclass": "Realization", + } # type: ignore[arg-type] + ) + self._add_layer_labels(layout) + return self.serializer.make_diagram(layout) + + def _add_layer_labels(self, layout: _elkjs.ELKOutputData) -> None: + for layer in layout["children"]: + if layer["type"] != "node": + continue + + layer_obj = self.serializer.model.by_uuid(layer["id"]) + _, layer_name = realization_view.find_layer(layer_obj) + pos = layer["position"]["x"], layer["position"]["y"] + size = layer["size"]["width"], layer["size"]["height"] + width, height = helpers.get_text_extent(layer_name) + x, y, tspan_y = calculate_label_position(*pos, *size) + label_box: _elkjs.ELKOutputChild = { + "type": "label", + "id": "None", + "text": layer_name, + "position": {"x": x, "y": y}, + "size": {"width": width, "height": height}, + "style": { + "text_transform": f"rotate(-90, {x}, {y}) {tspan_y}", + "text_fill": "grey", + }, + } + layer["children"].insert(0, label_box) + layer["style"] = {"stroke": "grey", "rx": 5, "ry": 5} + + +def try_to_layout(data: _elkjs.ELKInputData) -> _elkjs.ELKOutputData: + """Try calling elkjs, raise a JSONDecodeError if it fails.""" + try: + return _elkjs.call_elkjs(data) + except json.JSONDecodeError as error: + logger.error(json.dumps(data, indent=4)) + raise error + + +def adjust_layer_sizing( + data: _elkjs.ELKInputData, + layout: _elkjs.ELKOutputData, + layer_sizing: t.Literal["UNION", "WIDTH", "HEIGHT"], +) -> None: + """Set `nodeSize.minimum` config in the layoutOptions.""" + + def calculate_min(key: t.Literal["width", "height"] = "width") -> float: + return max(child["size"][key] for child in layout["children"]) # type: ignore[typeddict-item] + + if layer_sizing not in {"UNION", "WIDTH", "HEIGHT"}: + raise NotImplementedError( + "For ``layer_sizing`` only UNION, WIDTH or HEIGHT is supported" + ) + + min_w = calculate_min() + 15.0 if layer_sizing in {"UNION", "WIDTH"} else 0 + min_h = ( + calculate_min("height") if layer_sizing in {"UNION", "HEIGHT"} else 0 + ) + for layer in data["children"]: + layer["layoutOptions"]["nodeSize.minimum"] = f"({min_w},{min_h})" def stack_diagrams( @@ -403,9 +538,43 @@ def stack_diagrams( axis: t.Literal["x", "y"] = "x", ) -> None: """Add the diagram elements from ``right`` to left inline.""" - offset = first.viewport.pos + first.viewport.size - offset @= (1, 0) if axis == "x" else (0, 1) - for element in second: - new = copy.deepcopy(element) - new.move(offset) - first += new + if first.viewport: + offset = first.viewport.pos + first.viewport.size + offset @= (1, 0) if axis == "x" else (0, 1) + for element in second: + new = copy.deepcopy(element) + new.move(offset) + first += new + else: + for element in second: + new = copy.deepcopy(element) + first += new + + +def calculate_label_position( + x: float, + y: float, + width: float, + height: float, + padding: float = 10, +) -> tuple[float, float, float]: + """Calculate the position of the label and tspan. + + The function calculates the center of the rectangle and uses the + rectangle's width and height to adjust its position within it. The + text is assumed to be horizontally and vertically centered within + the rectangle. The tspan y coordinate is for positioning the label + right under the left side of the rectangle. + + Parameters + ---------- + + Returns + ------- + tuple + A tuple containing the x- and y-coordinate for the text element + and the adjusted y-coordinate for the tspan element. + """ + center_y = y + height / 2 + tspan_y = center_y - width / 2 + padding + return (x + width / 2, center_y, tspan_y) diff --git a/capellambse_context_diagrams/elk.js b/capellambse_context_diagrams/elk.js index 278a5532..85b69309 100644 --- a/capellambse_context_diagrams/elk.js +++ b/capellambse_context_diagrams/elk.js @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Copyright DB Netz AG and the capellambse-context-diagrams contributors +// SPDX-FileCopyrightText: 2022 Copyright DB InfraGO AG and the capellambse-context-diagrams contributors // SPDX-License-Identifier: Apache-2.0 const elkgraphsprotty = require("./elkgraph-to-sprotty"); diff --git a/capellambse_context_diagrams/filters.py b/capellambse_context_diagrams/filters.py index b64c530b..ba3494b2 100644 --- a/capellambse_context_diagrams/filters.py +++ b/capellambse_context_diagrams/filters.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Copyright DB Netz AG and the capellambse-context-diagrams contributors +# SPDX-FileCopyrightText: 2022 Copyright DB InfraGO AG and the capellambse-context-diagrams contributors # SPDX-License-Identifier: Apache-2.0 """Functions and registry for filter functionality.""" from __future__ import annotations diff --git a/capellambse_context_diagrams/serializers.py b/capellambse_context_diagrams/serializers.py index 3218db9f..39aaff29 100644 --- a/capellambse_context_diagrams/serializers.py +++ b/capellambse_context_diagrams/serializers.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Copyright DB Netz AG and the capellambse-context-diagrams contributors +# SPDX-FileCopyrightText: 2022 Copyright DB InfraGO AG and the capellambse-context-diagrams contributors # SPDX-License-Identifier: Apache-2.0 """This submodule provides a serializer that transforms data from an ELK- @@ -10,6 +10,7 @@ from __future__ import annotations import logging +import typing as t from capellambse import diagram from capellambse.svg import decorations @@ -146,12 +147,20 @@ class type that stores all previously named classes. self.diagram.add_element(element) self._cache[child["id"]] = element elif child["type"] == "edge": + styleclass = child.get("styleclass", styleclass) # type: ignore[assignment] styleclass = REMAP_STYLECLASS.get(styleclass, styleclass) # type: ignore[arg-type] - element = diagram.Edge( - [ + if child["routingPoints"]: + refpoints = [ ref + (point["x"], point["y"]) for point in child["routingPoints"] - ], + ] + else: + source = self._cache[child["sourceId"]] + target = self._cache[child["targetId"]] + refpoints = route_shortest_connection(source, target) + + element = diagram.Edge( + refpoints, uuid=child["id"], source=self.diagram[child["sourceId"]], target=self.diagram[child["targetId"]], @@ -166,12 +175,13 @@ class type that stores all previously named classes. if isinstance(parent, diagram.Box) and not parent.port: if parent.JSON_TYPE != "symbol": parent.label = child["text"] + parent.styleoverrides |= self.get_styleoverrides(child) else: parent.label = diagram.Box( ref + (child["position"]["x"], child["position"]["y"]), (child["size"]["width"], child["size"]["height"]), label=child["text"], - # parent=parent, + styleoverrides=self.get_styleoverrides(child), ) else: assert isinstance(parent, diagram.Edge) @@ -232,21 +242,25 @@ def get_styleclass(self, uuid: str) -> str | None: def get_styleoverrides( self, child: _elkjs.ELKOutputChild - ) -> diagram.StyleOverrides | None: + ) -> diagram.StyleOverrides: """Return [`styling.CSSStyles`][capellambse_context_diagrams.styling.CSSStyles] from a given [`_elkjs.ELKOutputChild`][capellambse_context_diagrams._elkjs.ELKOutputChild]. """ style_condition = self._diagram.render_styles.get(child["type"]) - styleoverrides = None + styleoverrides: dict[str, t.Any] = {} if style_condition is not None: if child["type"] != "junction": obj = self._diagram._model.by_uuid(child["id"]) else: obj = None - styleoverrides = style_condition(obj, self) + styleoverrides = style_condition(obj, self) or {} + + style: dict[str, t.Any] + if style := child.get("style", {}): + styleoverrides |= style return styleoverrides def order_children(self) -> None: @@ -279,3 +293,24 @@ def handle_features(child: _elkjs.ELKOutputNode) -> list[str]: features.append(c["text"]) child["children"] = child["children"][:1] return features + + +def route_shortest_connection( + source: diagram.Box, + target: diagram.Box, +) -> list[diagram.Vector2D]: + """Calculate shortest path between boxes with 'Oblique' style. + + Calculate the intersection points of the line from source.center to + target.center with the bounding boxes of the source and target. + """ + line_start = source.center + line_end = target.center + + source_intersection = source.vector_snap( + line_start, source=line_end, style=diagram.RoutingStyle.OBLIQUE + ) + target_intersection = target.vector_snap( + line_end, source=line_start, style=diagram.RoutingStyle.OBLIQUE + ) + return [source_intersection, target_intersection] diff --git a/capellambse_context_diagrams/styling.py b/capellambse_context_diagrams/styling.py index 23b8206c..22442c56 100644 --- a/capellambse_context_diagrams/styling.py +++ b/capellambse_context_diagrams/styling.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Copyright DB Netz AG and the capellambse-context-diagrams contributors +# SPDX-FileCopyrightText: 2022 Copyright DB InfraGO AG and the capellambse-context-diagrams contributors # SPDX-License-Identifier: Apache-2.0 """Functions for style overrides of diagram elements.""" from __future__ import annotations diff --git a/docs/assets/images/Context of Left.svg b/docs/assets/images/Context of Left.svg index 56aae0c6..ad286d03 100644 --- a/docs/assets/images/Context of Left.svg +++ b/docs/assets/images/Context of Left.svg @@ -1,5 +1,5 @@ diff --git a/docs/assets/images/Interface Context of Left to right.svg b/docs/assets/images/Interface Context of Left to right.svg index 366edde7..f39d3d27 100644 --- a/docs/assets/images/Interface Context of Left to right.svg +++ b/docs/assets/images/Interface Context of Left to right.svg @@ -1,5 +1,5 @@ diff --git a/docs/assets/images/favicon.ico.license b/docs/assets/images/favicon.ico.license index de4b43b4..680fb902 100644 --- a/docs/assets/images/favicon.ico.license +++ b/docs/assets/images/favicon.ico.license @@ -1,2 +1,2 @@ -SPDX-FileCopyrightText: 2022 Copyright DB Netz AG and the capellambse-context-diagrams contributors +SPDX-FileCopyrightText: 2022 Copyright DB InfraGO AG and the capellambse-context-diagrams contributors SPDX-License-Identifier: Apache-2.0 diff --git a/docs/assets/images/favicon.png.license b/docs/assets/images/favicon.png.license index de4b43b4..680fb902 100644 --- a/docs/assets/images/favicon.png.license +++ b/docs/assets/images/favicon.png.license @@ -1,2 +1,2 @@ -SPDX-FileCopyrightText: 2022 Copyright DB Netz AG and the capellambse-context-diagrams contributors +SPDX-FileCopyrightText: 2022 Copyright DB InfraGO AG and the capellambse-context-diagrams contributors SPDX-License-Identifier: Apache-2.0 diff --git a/docs/credits.md b/docs/credits.md index a6de69e5..0a4aa730 100644 --- a/docs/credits.md +++ b/docs/credits.md @@ -5,7 +5,7 @@ hide: --- diff --git a/docs/css/base.css b/docs/css/base.css index 9892ade1..1fd5514c 100644 --- a/docs/css/base.css +++ b/docs/css/base.css @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022 Copyright DB Netz AG and the capellambse-context-diagrams contributors + * SPDX-FileCopyrightText: 2022 Copyright DB InfraGO AG and the capellambse-context-diagrams contributors * SPDX-License-Identifier: Apache-2.0 */ diff --git a/docs/extras/filters.md b/docs/extras/filters.md index f6f60e69..f7c4cb66 100644 --- a/docs/extras/filters.md +++ b/docs/extras/filters.md @@ -1,5 +1,5 @@ @@ -26,7 +26,7 @@ Currently the supported filters are: diag = obj.context_diagram assert filters.EX_ITEMS == "show.exchange.items.filter" diag.filters.add(filters.EX_ITEMS) - diag.render("svgdiagram").save_drawing(pretty=True) + diag.render("svgdiagram").save(pretty=True) ```
@@ -43,7 +43,7 @@ Currently the supported filters are: diag = obj.context_diagram assert filters.FEX_EX_ITEMS == "show.functional.exchanges.exchange.items.filter" filters.filters = {filters.FEX_EX_ITEMS} - diag.render("svgdiagram").save_drawing(pretty=True) + diag.render("svgdiagram").save(pretty=True) ```
@@ -62,7 +62,7 @@ Currently the supported filters are: diag = obj.context_diagram assert filters.FEX_OR_EX_ITEMS == "capellambse_context_diagrams-show.functional.exchanges.or.exchange.items.filter" filters.filters.add(filters.FEX_OR_EX_ITEMS) - diag.render("svgdiagram").save_drawing(pretty=True) + diag.render("svgdiagram").save(pretty=True) ```
diff --git a/docs/extras/styling.md b/docs/extras/styling.md index 142f5c1a..8f336d7e 100644 --- a/docs/extras/styling.md +++ b/docs/extras/styling.md @@ -1,5 +1,5 @@ @@ -22,7 +22,7 @@ Capella. These appear to be blue. model = capellambse.MelodyModel("tests/data/ContextDiagram.aird") diag = model.by_uuid("957c5799-1d4a-4ac0-b5de-33a65bf1519c").context_diagram - diag.render("svgdiagram").save_drawing(pretty=True) + diag.render("svgdiagram").save(pretty=True) ``` produces
@@ -35,10 +35,10 @@ py-capellambse. # No symbol rendering -There are some ModelObjects that are displayed as symbols in a diagram -(e.g. Capabilities or Missions). The `.display_symbols_as_boxes` attribute -gives you the control to render these as boxes such that the symbol is -displayed as an icon beside the box-label. +There are some ModelObjects that are displayed as symbols in a diagram (e.g. +Capabilities or Missions). The `.display_symbols_as_boxes` attribute gives you +the control to render these as boxes such that the symbol is displayed as an +icon beside the box-label. Per default this attribute is set to `True`. ??? example "Box-only style for Context diagram of Middle OperationalCapability [OCB]" @@ -46,8 +46,8 @@ displayed as an icon beside the box-label. from capellambse import aird diag = model.by_uuid("da08ddb6-92ba-4c3b-956a-017424dbfe85").context_diagram - diag.display_symbols_as_boxes = True - diag.render("svgdiagram").save_drawing(pretty=True) + diag.display_symbols_as_boxes = True # per default + diag.render("svgdiagram").save(pretty=True) ``` produces
@@ -61,8 +61,8 @@ displayed as an icon beside the box-label. from capellambse import aird diag = model.by_uuid("9390b7d5-598a-42db-bef8-23677e45ba06").context_diagram - diag.display_symbols_as_boxes = True - diag.render("svgdiagram").save_drawing(pretty=True) + diag.display_symbols_as_boxes = True # per default + diag.render("svgdiagram").save(pretty=True) ``` produces
@@ -81,7 +81,7 @@ The `no_edgelabels` render parameter prevents edge labels from being displayed. model = capellambse.MelodyModel("tests/data/ContextDiagram.aird") diag = model.by_uuid("957c5799-1d4a-4ac0-b5de-33a65bf1519c").context_diagram - diag.render("svgdiagram", no_edgelabels=True).save_drawing(pretty=True) + diag.render("svgdiagram", no_edgelabels=True).save(pretty=True) ```
@@ -101,7 +101,7 @@ You can switch to py-capellambse default styling by overriding the diag = model.by_uuid("957c5799-1d4a-4ac0-b5de-33a65bf1519c").context_diagram diag.render_styles = {} - diag.render("svgdiagram").save_drawing(pretty=True) + diag.render("svgdiagram").save(pretty=True) ``` produces
@@ -126,7 +126,7 @@ Style your diagram elements ([ElkChildType][capellambse_context_diagrams.seriali styling.BLUE_ACTOR_FNCS, junction=lambda obj, serializer: {"stroke": aird.RGB(220, 20, 60)}, ) - diag.render("svgdiagram").save_drawing(pretty=True) + diag.render("svgdiagram").save(pretty=True) ``` produces
diff --git a/docs/gen_images.py b/docs/gen_images.py index 35370863..2dae034f 100644 --- a/docs/gen_images.py +++ b/docs/gen_images.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Copyright DB Netz AG and the capellambse-context-diagrams contributors +# SPDX-FileCopyrightText: 2022 Copyright DB InfraGO AG and the capellambse-context-diagrams contributors # SPDX-License-Identifier: Apache-2.0 from __future__ import annotations @@ -33,6 +33,8 @@ hierarchy_context = "16b4fcc5-548d-4721-b62a-d3d5b1c1d2eb" diagram_uuids = general_context_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" def generate_index_images() -> None: @@ -112,6 +114,22 @@ def generate_class_tree_images() -> None: ) +def generate_realization_view_images() -> None: + for uuid in (realization_fnc_uuid, realization_comp_uuid): + obj = model.by_uuid(uuid) + diag = obj.realization_view + with mkdocs_gen_files.open(f"{str(dest / diag.name)}.svg", "w") as fd: + print( + diag.render( + "svg", + depth=3, + search_direction="ALL", + show_owners=True, + ), + file=fd, + ) + + generate_index_images() generate_hierarchy_image() generate_no_symbol_images() @@ -134,3 +152,4 @@ def generate_class_tree_images() -> None: ) generate_styling_image(wizard, {}, "no_styles") generate_class_tree_images() +generate_realization_view_images() diff --git a/docs/gen_ref_pages.py b/docs/gen_ref_pages.py index 91db103d..a1f553d8 100644 --- a/docs/gen_ref_pages.py +++ b/docs/gen_ref_pages.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Copyright DB Netz AG and the capellambse-context-diagrams contributors +# SPDX-FileCopyrightText: 2022 Copyright DB InfraGO AG and the capellambse-context-diagrams contributors # SPDX-License-Identifier: Apache-2.0 """Generate the code reference pages.""" diff --git a/docs/index.md b/docs/index.md index 3a75e2ee..93ab4d46 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,5 +1,5 @@ @@ -41,7 +41,7 @@ Available via `.context_diagram` on a [`ModelObject`][capellambse.model.common.e model = capellambse.MelodyModel("tests/data/ContextDiagram.aird") diag = model.by_uuid("e37510b9-3166-4f80-a919-dfaac9b696c7").context_diagram - diag.render("svgdiagram").save_drawing(pretty=True) + diag.render("svgdiagram").save(pretty=True) ```
@@ -55,7 +55,7 @@ Available via `.context_diagram` on a [`ModelObject`][capellambse.model.common.e model = capellambse.MelodyModel("tests/data/ContextDiagram.aird") diag = model.by_uuid("8bcb11e6-443b-4b92-bec2-ff1d87a224e7").context_diagram - diag.render("svgdiagram").save_drawing(pretty=True) + diag.render("svgdiagram").save(pretty=True) ```
@@ -69,7 +69,7 @@ Available via `.context_diagram` on a [`ModelObject`][capellambse.model.common.e model = capellambse.MelodyModel("tests/data/ContextDiagram.aird") diag = model.by_uuid("da08ddb6-92ba-4c3b-956a-017424dbfe85").context_diagram - diag.render("svgdiagram").save_drawing(pretty=True) + diag.render("svgdiagram").save(pretty=True) ```
@@ -83,7 +83,7 @@ Available via `.context_diagram` on a [`ModelObject`][capellambse.model.common.e model = capellambse.MelodyModel("tests/data/ContextDiagram.aird") diag = model.by_uuid("5bf3f1e3-0f5e-4fec-81d5-c113d3a1b3a6").context_diagram - diag.render("svgdiagram").save_drawing(pretty=True) + diag.render("svgdiagram").save(pretty=True) ```
@@ -97,7 +97,7 @@ Available via `.context_diagram` on a [`ModelObject`][capellambse.model.common.e model = capellambse.MelodyModel("tests/data/ContextDiagram.aird") diag = model.by_uuid("9390b7d5-598a-42db-bef8-23677e45ba06").context_diagram - diag.render("svgdiagram").save_drawing(pretty=True) + diag.render("svgdiagram").save(pretty=True) ```
@@ -113,7 +113,7 @@ Available via `.context_diagram` on a [`ModelObject`][capellambse.model.common.e model = capellambse.MelodyModel("tests/data/ContextDiagram.aird") diag = model.by_uuid("a5642060-c9cc-4d49-af09-defaa3024bae").context_diagram - diag.render("svgdiagram").save_drawing(pretty=True) + diag.render("svgdiagram").save(pretty=True) ```
@@ -127,7 +127,7 @@ Available via `.context_diagram` on a [`ModelObject`][capellambse.model.common.e model = capellambse.MelodyModel("tests/data/ContextDiagram.aird") diag = model.by_uuid("f632888e-51bc-4c9f-8e81-73e9404de784").context_diagram - diag.render("svgdiagram").save_drawing(pretty=True) + diag.render("svgdiagram").save(pretty=True) ```
@@ -141,7 +141,7 @@ Available via `.context_diagram` on a [`ModelObject`][capellambse.model.common.e model = capellambse.MelodyModel("tests/data/ContextDiagram.aird") diag = model.by_uuid("957c5799-1d4a-4ac0-b5de-33a65bf1519c").context_diagram - diag.render("svgdiagram").save_drawing(pretty=True) + diag.render("svgdiagram").save(pretty=True) ```
@@ -167,7 +167,7 @@ Hierarchy is identified and supported: model = capellambse.MelodyModel("tests/data/ContextDiagram.aird") obj = model.by_uuid("16b4fcc5-548d-4721-b62a-d3d5b1c1d2eb") diagram = obj.context_diagram.render("svgdiagram", include_inner_objects=True) - diagram.save_drawing(pretty=True) + diagram.save(pretty=True) ```
@@ -185,7 +185,7 @@ The data is collected by [get_elkdata_for_exchanges][capellambse_context_diagram model = capellambse.MelodyModel("tests/data/ContextDiagram.aird") diag = model.by_uuid("3ef23099-ce9a-4f7d-812f-935f47e7938d").context_diagram - diag.render("svgdiagram").save_drawing(pretty=True) + diag.render("svgdiagram").save(pretty=True) ```
diff --git a/docs/license.md b/docs/license.md index d376b528..6e81c06e 100644 --- a/docs/license.md +++ b/docs/license.md @@ -1,5 +1,5 @@ diff --git a/docs/quickstart.md b/docs/quickstart.md index 9e87bd6d..0ba83259 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -1,5 +1,5 @@ diff --git a/docs/realization_view.md b/docs/realization_view.md new file mode 100644 index 00000000..50ae7bcf --- /dev/null +++ b/docs/realization_view.md @@ -0,0 +1,73 @@ + + +# 🔥Brand-new🔥 Tree View Diagram 🔥Brand-new🔥 + +With release +[`v0.5.42`](https://github.com/DSD-DBS/py-capellambse/releases/tag/v0.5.42) of +[py-capellambse](https://github.com/DSD-DBS/py-capellambse) you can access the +`.realization_view` on a Component or Function from any layer. A realization +view diagram reveals the realization map that the layers of your model +implement currently. The diagram elements are collected from the +`.realized_components` or `.realized_functions` attribute for the direction +`ABOVE` and `.realizing_components` or `.realizing_functions` for direction +`BELOW`. + +??? example "Realization View Diagram of `LogicalFunction` `advise Harry`" + + ``` py + import capellambse + + model = capellambse.MelodyModel("tests/data/ContextDiagram.aird") + diag = model.by_uuid("beaf5ba4-8fa9-4342-911f-0266bb29be45").realization_view + diag.render( + "svgdiagram", + depth=3, # 1-3 + search_direction="ALL", # BELOW; ABOVE and ALL + show_owners=True, + layer_sizing="UNION", # UNION; WIDTH and HEIGHT + ).save(pretty=True) + ``` +
+ +
[CDB] Realization View Diagram of advise Harry
+
+ +??? example "Realization View Diagram of `PhysicalComponent` `Physical System`" + + ``` py + import capellambse + + model = capellambse.MelodyModel("tests/data/ContextDiagram.aird") + diag = model.by_uuid("b9f9a83c-fb02-44f7-9123-9d86326de5f1").realization_view + diag.render( + "svgdiagram", + depth=3, + search_direction="ALL", + show_owners=True, + layer_sizing="UNION", + ).save(pretty=True) + ``` +
+ +
[CDB] Realization View Diagram of Physical System
+
+ +Additional rendering parameters enable showing owning functions or components, +as well as the depth of traversion (i.e. `1`-`3`) and control on sizing of the +layer boxes. They are put to display the maximum amount of diagram elements per +default. + +??? bug "Alignment of diagram elements" + + As of [elkjs@0.9.0](https://eclipse.dev/elk/downloads/releasenotes/release-0.9.0.html) ELK's rectpacking algorithm isn't correctly using the + content alignment enumeration. While developing the Realization View + [a fix for the horizontal alignment was proposed](https://github.com/eclipse/elk/issues/989). + +## Check out the code + +To understand the collection have a look into the +[`realization_view`][capellambse_context_diagrams.collectors.realization_view] +module. diff --git a/docs/tree_view.md b/docs/tree_view.md index f3147ed7..e054a0f6 100644 --- a/docs/tree_view.md +++ b/docs/tree_view.md @@ -1,5 +1,5 @@ @@ -17,7 +17,7 @@ parent class. model = capellambse.MelodyModel("tests/data/ContextDiagram.aird") diag = model.by_uuid("b7c7f442-377f-492c-90bf-331e66988bda").tree_view - diag.render("svgdiagram").save_drawing(pretty=True) + diag.render("svgdiagram").save(pretty=True) ```
@@ -68,7 +68,7 @@ before rendering: direction="Right", # partitioning=False, # edgeLabelsSide="ALWAYS_DOWN", - ).save_drawing(pretty=True) + ).save(pretty=True) ```
diff --git a/license_header.txt b/license_header.txt index de4b43b4..680fb902 100644 --- a/license_header.txt +++ b/license_header.txt @@ -1,2 +1,2 @@ -SPDX-FileCopyrightText: 2022 Copyright DB Netz AG and the capellambse-context-diagrams contributors +SPDX-FileCopyrightText: 2022 Copyright DB InfraGO AG and the capellambse-context-diagrams contributors SPDX-License-Identifier: Apache-2.0 diff --git a/mkdocs.yml b/mkdocs.yml index a0463c35..88d9ead7 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Copyright DB Netz AG and the capellambse-context-diagrams contributors +# SPDX-FileCopyrightText: 2022 Copyright DB InfraGO AG and the capellambse-context-diagrams contributors # SPDX-License-Identifier: Apache-2.0 site_name: Context diagrams for py-capellambse @@ -91,6 +91,8 @@ nav: - Styling: extras/styling.md - Tree View: - Overview: tree_view.md + - Realization View: + - Overview: realization_view.md - Code Reference: reference/ extra_css: @@ -102,7 +104,7 @@ extra: - icon: fontawesome/brands/github link: https://dsd-dbs.github.io/capellambse-context-diagrams/ - icon: custom/db - name: DB Netz AG - SET + name: DB InfraGO AG - SET link: https://github.com/DSD-DBS -copyright: Copyright © 2022 DB Netz AG +copyright: Copyright © 2022 DB InfraGO AG diff --git a/overrides/.icons/custom/db.svg b/overrides/.icons/custom/db.svg index 3bfb9353..a36fb239 100644 --- a/overrides/.icons/custom/db.svg +++ b/overrides/.icons/custom/db.svg @@ -1,6 +1,6 @@ diff --git a/pyproject.toml b/pyproject.toml index 65d204b9..2b5868f2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Copyright DB Netz AG and the capellambse-context-diagrams contributors +# SPDX-FileCopyrightText: 2022 Copyright DB InfraGO AG and the capellambse-context-diagrams contributors # SPDX-License-Identifier: Apache-2.0 [build-system] @@ -14,7 +14,7 @@ readme = "README.md" requires-python = ">=3.9" license = { file = "LICENSES/Apache-2.0.txt" } authors = [ - { name = "DB Netz AG" }, + { name = "DB InfraGO AG" }, ] keywords = ["capella", "mbse", "context", "diagram", "automatic diagrams"] classifiers = [ diff --git a/setup.py b/setup.py index 9b0e0470..67883402 100755 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -# SPDX-FileCopyrightText: 2022 Copyright DB Netz AG and the capellambse-context-diagrams contributors +# SPDX-FileCopyrightText: 2022 Copyright DB InfraGO AG and the capellambse-context-diagrams contributors # SPDX-License-Identifier: Apache-2.0 import setuptools diff --git a/tests/conftest.py b/tests/conftest.py index 4fd14ab3..61d2c79c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Copyright DB Netz AG and the capellambse-context-diagrams contributors +# SPDX-FileCopyrightText: 2022 Copyright DB InfraGO AG and the capellambse-context-diagrams contributors # SPDX-License-Identifier: Apache-2.0 """Global fixtures for pytest""" diff --git a/tests/data/.project.license b/tests/data/.project.license index de4b43b4..680fb902 100644 --- a/tests/data/.project.license +++ b/tests/data/.project.license @@ -1,2 +1,2 @@ -SPDX-FileCopyrightText: 2022 Copyright DB Netz AG and the capellambse-context-diagrams contributors +SPDX-FileCopyrightText: 2022 Copyright DB InfraGO AG and the capellambse-context-diagrams contributors SPDX-License-Identifier: Apache-2.0 diff --git a/tests/data/ContextDiagram.afm b/tests/data/ContextDiagram.afm index dc32e25b..02f4486c 100644 --- a/tests/data/ContextDiagram.afm +++ b/tests/data/ContextDiagram.afm @@ -1,6 +1,6 @@ - - - + + + diff --git a/tests/data/ContextDiagram.afm.license b/tests/data/ContextDiagram.afm.license index de4b43b4..680fb902 100644 --- a/tests/data/ContextDiagram.afm.license +++ b/tests/data/ContextDiagram.afm.license @@ -1,2 +1,2 @@ -SPDX-FileCopyrightText: 2022 Copyright DB Netz AG and the capellambse-context-diagrams contributors +SPDX-FileCopyrightText: 2022 Copyright DB InfraGO AG and the capellambse-context-diagrams contributors SPDX-License-Identifier: Apache-2.0 diff --git a/tests/data/ContextDiagram.aird.license b/tests/data/ContextDiagram.aird.license index de4b43b4..680fb902 100644 --- a/tests/data/ContextDiagram.aird.license +++ b/tests/data/ContextDiagram.aird.license @@ -1,2 +1,2 @@ -SPDX-FileCopyrightText: 2022 Copyright DB Netz AG and the capellambse-context-diagrams contributors +SPDX-FileCopyrightText: 2022 Copyright DB InfraGO AG and the capellambse-context-diagrams contributors SPDX-License-Identifier: Apache-2.0 diff --git a/tests/data/ContextDiagram.capella b/tests/data/ContextDiagram.capella index abcda219..7a48804d 100644 --- a/tests/data/ContextDiagram.capella +++ b/tests/data/ContextDiagram.capella @@ -1536,6 +1536,21 @@ The predator is far away source="#00e7b925-cf4c-4cb0-929e-5409a1cd872b" target="#85d41db2-9e17-438b-95cf-49342452ddf3"/> + + + + + @@ -2211,6 +2226,9 @@ The predator is far away + + id="e7ea5a90-3b07-499d-b17f-4e61917c850a" name="CP 2" orientation="OUT" kind="FLOW"/> + id="beaf5ba4-8fa9-4342-911f-0266bb29be45" name="advise Harry"> + @@ -3258,10 +3285,6 @@ The predator is far away name="Right" abstractType="#37dfa5e6-a121-4ce9-8aa4-09a0c73dc2e9"/> - - @@ -3296,7 +3319,7 @@ The predator is far away name="School" abstractType="#a58821df-c5b4-4958-9455-0d30755be6b1"/> + actor="true"> @@ -3317,6 +3340,12 @@ The predator is far away + + @@ -3481,6 +3510,73 @@ The predator is far away id="f0e39876-6665-4d6c-9eec-b0c0f1dd8399" targetElement="#b7f2d2ef-3aef-43a9-9309-cf172e50ebb7" sourceElement="#58f89f0a-7647-4077-b29b-8fe2bf270f02"/> + + + + + + + + + + + + + + + + + + + + + + + + + @@ -3495,69 +3591,6 @@ The predator is far away sourceElement="#1286b0fe-dd57-46ba-8303-bd57eb04178d"/> - - - - - - - - - - - - - - - - - - - - - - - id="d254c2aa-3fc4-4deb-a684-6191ace32c42" name="Child Function"> + @@ -3869,6 +3905,9 @@ The predator is far away + @@ -3886,6 +3925,9 @@ The predator is far away id="da53c21f-4fb8-4da7-8d58-12d7a600bfa6" name="Apping around"> + + id="b9f9a83c-fb02-44f7-9123-9d86326de5f1" name="Physical System" nature="NODE"> + @@ -4024,6 +4069,24 @@ The predator is far away + + + + + + None: + obj = model.by_uuid(uuid) + + diag = obj.realization_view + + assert diag.render(fmt) + + +@pytest.mark.parametrize("uuid", [TEST_FNC_UUID, TEST_CMP_UUID]) +@pytest.mark.parametrize("depth", list(range(1, 4))) +@pytest.mark.parametrize("search_direction", ["ABOVE", "BELOW"]) +@pytest.mark.parametrize("show_owners", [True, False]) +@pytest.mark.parametrize("layer_sizing", ["UNION", "WIDTH", "HEIGHT"]) +def test_tree_view_renders_with_additional_params( + model: capellambse.MelodyModel, + depth: int, + search_direction: str, + show_owners: bool, + layer_sizing: str, + uuid: str, +) -> None: + obj = model.by_uuid(uuid) + + diag = obj.realization_view + + assert diag.render( + "svgdiagram", + depth=depth, + search_direction=search_direction, + show_owners=show_owners, + layer_sizing=layer_sizing, + ) diff --git a/tests/test_tree_views.py b/tests/test_tree_views.py index 04526076..235a5d0e 100644 --- a/tests/test_tree_views.py +++ b/tests/test_tree_views.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Copyright DB Netz AG and the capellambse-context-diagrams contributors +# SPDX-FileCopyrightText: 2022 Copyright DB InfraGO AG and the capellambse-context-diagrams contributors # SPDX-License-Identifier: Apache-2.0 import capellambse