From c106357cea9d52cb219655feb6e4ad4d0d193a1a Mon Sep 17 00:00:00 2001 From: ewuerger Date: Mon, 9 Dec 2024 16:55:42 +0100 Subject: [PATCH 1/5] test: Raise Capella version to 7.0 --- tests/data/ContextDiagram.afm | 6 +- tests/data/ContextDiagram.aird | 640 +++++++++++++++--------------- tests/data/ContextDiagram.capella | 34 +- 3 files changed, 340 insertions(+), 340 deletions(-) diff --git a/tests/data/ContextDiagram.afm b/tests/data/ContextDiagram.afm index 02f4486c..ca9e0093 100644 --- a/tests/data/ContextDiagram.afm +++ b/tests/data/ContextDiagram.afm @@ -1,6 +1,6 @@ - - - + + + diff --git a/tests/data/ContextDiagram.aird b/tests/data/ContextDiagram.aird index 071a79e2..96149b41 100644 --- a/tests/data/ContextDiagram.aird +++ b/tests/data/ContextDiagram.aird @@ -1,6 +1,6 @@ - - + + ContextDiagram.afm ContextDiagram.capella @@ -8,14 +8,14 @@ - + - +
@@ -23,7 +23,7 @@ - +
@@ -31,7 +31,7 @@ - +
@@ -39,7 +39,7 @@ - +
@@ -53,15 +53,15 @@ - + - + - + @@ -71,30 +71,30 @@ - + - + - + - + - + - +
@@ -102,7 +102,7 @@ - +
@@ -110,7 +110,7 @@ - +
@@ -118,7 +118,7 @@ - +
@@ -126,7 +126,7 @@ - +
@@ -134,7 +134,7 @@ - +
@@ -142,7 +142,7 @@ - +
@@ -150,7 +150,7 @@ - +
@@ -158,7 +158,7 @@ - +
@@ -166,11 +166,11 @@ - + - +
@@ -178,7 +178,7 @@ - +
@@ -186,7 +186,7 @@ - +
@@ -892,7 +892,7 @@ - + @@ -901,7 +901,7 @@ - + @@ -922,7 +922,7 @@ - + @@ -934,7 +934,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -943,7 +943,7 @@ - + @@ -952,7 +952,7 @@ - + @@ -971,7 +971,7 @@ - + @@ -983,7 +983,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -992,12 +992,12 @@ - + - + @@ -1009,7 +1009,7 @@ - + @@ -1018,7 +1018,7 @@ - + @@ -1027,7 +1027,7 @@ - + @@ -1035,7 +1035,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -1046,7 +1046,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -1063,7 +1063,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -1072,7 +1072,7 @@ - + @@ -1091,7 +1091,7 @@ - + @@ -1100,7 +1100,7 @@ - + @@ -1108,7 +1108,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -1122,7 +1122,7 @@ - + @@ -1131,7 +1131,7 @@ - + @@ -1153,7 +1153,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -1165,7 +1165,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -1174,7 +1174,7 @@ - + @@ -1183,12 +1183,12 @@ - + - + @@ -1202,7 +1202,7 @@ - + @@ -1211,7 +1211,7 @@ - + @@ -1230,7 +1230,7 @@ - + @@ -1238,7 +1238,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -1751,7 +1751,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -1762,7 +1762,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -1795,7 +1795,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -1806,7 +1806,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -1817,7 +1817,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -1828,7 +1828,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -1839,7 +1839,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -1850,7 +1850,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -1861,7 +1861,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -2568,7 +2568,7 @@ - + @@ -2577,7 +2577,7 @@ - + @@ -2599,7 +2599,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -2608,7 +2608,7 @@ - + @@ -2617,7 +2617,7 @@ - + @@ -2625,7 +2625,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -2637,7 +2637,7 @@ - + @@ -2646,7 +2646,7 @@ - + @@ -2654,7 +2654,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -2666,7 +2666,7 @@ - + @@ -2674,7 +2674,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -2688,7 +2688,7 @@ - + @@ -2707,7 +2707,7 @@ - + @@ -2716,7 +2716,7 @@ - + @@ -2725,7 +2725,7 @@ - + @@ -2734,7 +2734,7 @@ - + @@ -2743,12 +2743,12 @@ - + - + @@ -2911,7 +2911,7 @@ - + @@ -2927,12 +2927,12 @@ - + - + @@ -3506,7 +3506,7 @@ - + @@ -3515,7 +3515,7 @@ - + @@ -3534,7 +3534,7 @@ - + @@ -3543,7 +3543,7 @@ - + @@ -3552,7 +3552,7 @@ - + @@ -3561,7 +3561,7 @@ - + @@ -3570,7 +3570,7 @@ - + @@ -3579,7 +3579,7 @@ - + @@ -3588,7 +3588,7 @@ - + @@ -3596,7 +3596,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + italic @@ -3611,7 +3611,7 @@ - + @@ -3620,7 +3620,7 @@ - + @@ -3632,7 +3632,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -3651,7 +3651,7 @@ - + @@ -3660,7 +3660,7 @@ - + @@ -3668,7 +3668,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -3728,7 +3728,7 @@ - + @@ -3737,7 +3737,7 @@ - + @@ -3746,7 +3746,7 @@ - + @@ -3765,7 +3765,7 @@ - + @@ -3774,7 +3774,7 @@ - + @@ -3783,7 +3783,7 @@ - + @@ -3792,7 +3792,7 @@ - + @@ -3800,7 +3800,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -5640,7 +5640,7 @@ - + @@ -5649,7 +5649,7 @@ - + @@ -5658,7 +5658,7 @@ - + @@ -5667,7 +5667,7 @@ - + @@ -5676,7 +5676,7 @@ - + @@ -5685,7 +5685,7 @@ - + @@ -5694,7 +5694,7 @@ - + @@ -5703,7 +5703,7 @@ - + @@ -5712,7 +5712,7 @@ - + @@ -5721,7 +5721,7 @@ - + @@ -5740,7 +5740,7 @@ - + @@ -5749,7 +5749,7 @@ - + @@ -5758,7 +5758,7 @@ - + @@ -5767,7 +5767,7 @@ - + @@ -5776,7 +5776,7 @@ - + @@ -5785,7 +5785,7 @@ - + @@ -5794,7 +5794,7 @@ - + @@ -5802,7 +5802,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -5816,7 +5816,7 @@ - + @@ -5825,7 +5825,7 @@ - + @@ -5837,7 +5837,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -5846,7 +5846,7 @@ - + @@ -5855,7 +5855,7 @@ - + @@ -5864,7 +5864,7 @@ - + @@ -5873,7 +5873,7 @@ - + @@ -5882,7 +5882,7 @@ - + @@ -5903,7 +5903,7 @@ - + @@ -5912,7 +5912,7 @@ - + @@ -5921,7 +5921,7 @@ - + @@ -5930,7 +5930,7 @@ - + @@ -5939,7 +5939,7 @@ - + @@ -5948,7 +5948,7 @@ - + @@ -5957,7 +5957,7 @@ - + @@ -5966,7 +5966,7 @@ - + @@ -5975,7 +5975,7 @@ - + @@ -5984,7 +5984,7 @@ - + @@ -5993,7 +5993,7 @@ - + @@ -6002,7 +6002,7 @@ - + @@ -6011,7 +6011,7 @@ - + @@ -6020,7 +6020,7 @@ - + @@ -6029,7 +6029,7 @@ - + @@ -6038,7 +6038,7 @@ - + @@ -6047,7 +6047,7 @@ - + @@ -6059,7 +6059,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -6078,7 +6078,7 @@ - + @@ -6087,7 +6087,7 @@ - + @@ -6096,7 +6096,7 @@ - + @@ -6105,7 +6105,7 @@ - + @@ -6114,7 +6114,7 @@ - + @@ -6123,7 +6123,7 @@ - + @@ -6132,7 +6132,7 @@ - + @@ -6140,7 +6140,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -7920,7 +7920,7 @@ - + @@ -7929,7 +7929,7 @@ - + @@ -7941,7 +7941,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -7950,7 +7950,7 @@ - + @@ -7970,7 +7970,7 @@ - + @@ -7979,7 +7979,7 @@ - + @@ -8000,7 +8000,7 @@ - + @@ -8022,7 +8022,7 @@ - + @@ -8040,7 +8040,7 @@ - + @@ -8061,7 +8061,7 @@ - + @@ -8082,7 +8082,7 @@ - + @@ -9040,7 +9040,7 @@ - + @@ -9059,7 +9059,7 @@ - + @@ -9067,7 +9067,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -9079,7 +9079,7 @@ - + @@ -9087,7 +9087,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -9593,7 +9593,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -9604,7 +9604,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -9626,7 +9626,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -9637,7 +9637,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -9648,7 +9648,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -9659,7 +9659,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -9670,7 +9670,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -9681,7 +9681,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -9702,7 +9702,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -9713,7 +9713,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -9736,7 +9736,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -9824,7 +9824,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -9835,7 +9835,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -9846,7 +9846,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -9857,7 +9857,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -10769,7 +10769,7 @@ - + @@ -10778,7 +10778,7 @@ - + @@ -10790,7 +10790,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -10799,7 +10799,7 @@ - + @@ -10819,7 +10819,7 @@ - + @@ -10828,7 +10828,7 @@ - + @@ -10847,7 +10847,7 @@ - + @@ -10856,12 +10856,12 @@ - + - + @@ -10874,7 +10874,7 @@ - + @@ -10883,7 +10883,7 @@ - + @@ -10892,7 +10892,7 @@ - + @@ -10900,7 +10900,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -10913,7 +10913,7 @@ - + @@ -10922,7 +10922,7 @@ - + @@ -10945,7 +10945,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -10964,7 +10964,7 @@ - + @@ -10973,7 +10973,7 @@ - + @@ -10982,7 +10982,7 @@ - + @@ -10991,12 +10991,12 @@ - + - + @@ -11033,7 +11033,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -11052,7 +11052,7 @@ - + @@ -11061,7 +11061,7 @@ - + @@ -11069,7 +11069,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -11086,7 +11086,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -11105,7 +11105,7 @@ - + @@ -11113,7 +11113,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -11245,7 +11245,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -11264,7 +11264,7 @@ - + @@ -11273,12 +11273,12 @@ - + - + @@ -11669,7 +11669,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -11691,7 +11691,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -11700,7 +11700,7 @@ - + @@ -11708,7 +11708,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -11720,7 +11720,7 @@ - + @@ -11729,7 +11729,7 @@ - + @@ -11737,7 +11737,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -11761,7 +11761,7 @@ - + @@ -11769,7 +11769,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -11783,7 +11783,7 @@ - + @@ -11805,7 +11805,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -11813,7 +11813,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -11837,7 +11837,7 @@ - + @@ -11845,7 +11845,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -11872,7 +11872,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -11880,7 +11880,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -12197,7 +12197,7 @@ - + @@ -12595,7 +12595,7 @@ - + @@ -12625,7 +12625,7 @@ - + @@ -12634,7 +12634,7 @@ - + @@ -12642,7 +12642,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -12655,7 +12655,7 @@ - + @@ -12664,7 +12664,7 @@ - + @@ -12676,12 +12676,12 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + - + @@ -12695,7 +12695,7 @@ - + @@ -12736,7 +12736,7 @@ - + @@ -12744,7 +12744,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -12757,7 +12757,7 @@ - + @@ -12766,7 +12766,7 @@ - + @@ -12774,7 +12774,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -12787,7 +12787,7 @@ - + @@ -12796,7 +12796,7 @@ - + @@ -12805,7 +12805,7 @@ - + @@ -12813,7 +12813,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + italic @@ -12826,7 +12826,7 @@ - + @@ -12834,7 +12834,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -12846,7 +12846,7 @@ - + @@ -12854,7 +12854,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -13421,7 +13421,7 @@ - + @@ -13459,7 +13459,7 @@ - + @@ -14471,7 +14471,7 @@ - + @@ -14480,7 +14480,7 @@ - + @@ -14500,7 +14500,7 @@ - + @@ -14509,7 +14509,7 @@ - + @@ -14517,7 +14517,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -14591,7 +14591,7 @@ - + @@ -14600,7 +14600,7 @@ - + @@ -14612,7 +14612,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -14632,7 +14632,7 @@ - + @@ -14641,7 +14641,7 @@ - + @@ -14650,7 +14650,7 @@ - + @@ -14659,12 +14659,12 @@ - + - + @@ -14689,7 +14689,7 @@ - + @@ -14698,7 +14698,7 @@ - + @@ -14707,7 +14707,7 @@ - + @@ -14715,7 +14715,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -14829,7 +14829,7 @@ - + @@ -14837,7 +14837,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -15226,7 +15226,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -15235,7 +15235,7 @@ - + @@ -15243,7 +15243,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -15267,7 +15267,7 @@ - + @@ -15275,7 +15275,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -15307,7 +15307,7 @@ - + @@ -15315,7 +15315,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -15430,7 +15430,7 @@ - + bold @@ -15438,7 +15438,7 @@ - + @@ -15446,7 +15446,7 @@ - + @@ -16498,7 +16498,7 @@ - + @@ -16507,7 +16507,7 @@ - + @@ -16516,7 +16516,7 @@ - + @@ -16525,7 +16525,7 @@ - + @@ -16545,7 +16545,7 @@ - + @@ -16566,7 +16566,7 @@ - + @@ -16575,7 +16575,7 @@ - + @@ -16596,7 +16596,7 @@ - + @@ -16618,7 +16618,7 @@ - + @@ -16640,7 +16640,7 @@ - + @@ -16661,7 +16661,7 @@ - + @@ -16921,7 +16921,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -16942,7 +16942,7 @@ - + @@ -17001,7 +17001,7 @@ - + @@ -17010,7 +17010,7 @@ - + @@ -17019,7 +17019,7 @@ - + @@ -17031,7 +17031,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -17166,7 +17166,7 @@ - + @@ -17175,7 +17175,7 @@ - + @@ -17183,7 +17183,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + @@ -17195,7 +17195,7 @@ - + @@ -17203,7 +17203,7 @@ KEEP_LOCATION KEEP_SIZE KEEP_RATIO - + diff --git a/tests/data/ContextDiagram.capella b/tests/data/ContextDiagram.capella index 775800a9..f5c33ca5 100644 --- a/tests/data/ContextDiagram.capella +++ b/tests/data/ContextDiagram.capella @@ -1,24 +1,24 @@ - + From d3224b5c9b5174a94014c81d395fe2fa32b8998d Mon Sep 17 00:00:00 2001 From: ewuerger Date: Mon, 9 Dec 2024 16:56:24 +0100 Subject: [PATCH 2/5] test: Remove unnecessary module --- tests/test_capability_diagrams.py | 21 --------------------- tests/test_context_diagrams.py | 22 ++++++++++++++++++---- 2 files changed, 18 insertions(+), 25 deletions(-) delete mode 100644 tests/test_capability_diagrams.py diff --git a/tests/test_capability_diagrams.py b/tests/test_capability_diagrams.py deleted file mode 100644 index 27219b5b..00000000 --- a/tests/test_capability_diagrams.py +++ /dev/null @@ -1,21 +0,0 @@ -# SPDX-FileCopyrightText: 2022 Copyright DB InfraGO AG and the capellambse-context-diagrams contributors -# SPDX-License-Identifier: Apache-2.0 - -import capellambse -import capellambse.metamodel as mm -import pytest - -# pylint: disable-next=relative-beyond-top-level, useless-suppression -from .conftest import SYSTEM_ANALYSIS_PARAMS # type: ignore[import] - -TEST_TYPES = (mm.oa.OperationalCapability, mm.sa.Capability, mm.sa.Mission) - - -@pytest.mark.parametrize("uuid", SYSTEM_ANALYSIS_PARAMS) -def test_context_diagrams(model: capellambse.MelodyModel, uuid: str) -> None: - obj = model.by_uuid(uuid) - assert isinstance(obj, TEST_TYPES), "Precondition failed" - - diag = obj.context_diagram - - assert diag.nodes diff --git a/tests/test_context_diagrams.py b/tests/test_context_diagrams.py index 6c3d0fa6..3092aeda 100644 --- a/tests/test_context_diagrams.py +++ b/tests/test_context_diagrams.py @@ -2,8 +2,12 @@ # SPDX-License-Identifier: Apache-2.0 import capellambse +import capellambse.metamodel as mm import pytest +# pylint: disable-next=relative-beyond-top-level, useless-suppression +from .conftest import SYSTEM_ANALYSIS_PARAMS # type: ignore[import] + TEST_CAP_SIZING_UUID = "b996a45f-2954-4fdd-9141-7934e7687de6" TEST_HUMAN_ACTOR_SIZING_UUID = "e95847ae-40bb-459e-8104-7209e86ea2d1" TEST_ACTOR_SIZING_UUID = "6c8f32bf-0316-477f-a23b-b5239624c28d" @@ -27,6 +31,20 @@ TEST_SYS_FNC_UUID = "a5642060-c9cc-4d49-af09-defaa3024bae" TEST_DERIVATION_UUID = "4ec45aec-0d6a-411a-80ee-ebd3c1a53d2c" +TEST_TYPES = (mm.oa.OperationalCapability, mm.sa.Capability, mm.sa.Mission) + + +@pytest.mark.parametrize("uuid", SYSTEM_ANALYSIS_PARAMS) +def test_capability_and_mission_context_diagrams( + model: capellambse.MelodyModel, uuid: str +) -> None: + obj = model.by_uuid(uuid) + assert isinstance(obj, TEST_TYPES), "Precondition failed" + + diag = obj.context_diagram + + assert diag.nodes + @pytest.mark.parametrize( "uuid", @@ -296,10 +314,6 @@ def test_context_diagram_display_unused_ports( obj = model.by_uuid("446d3f9f-644d-41ee-bd57-8ae0f7662db2") unused_port_uuid = "5cbc4d2d-1b9c-4e10-914e-44d4526e4a2f" - obj.context_diagram.render("svgdiagram", display_unused_ports=True).save( - pretty=True - ) - adiag = obj.context_diagram.render(None, display_unused_ports=False) bdiag = obj.context_diagram.render(None, display_unused_ports=True) From 0b3f281024c307d4286f14594801b42cabb1c487 Mon Sep 17 00:00:00 2001 From: ewuerger Date: Mon, 9 Dec 2024 16:57:58 +0100 Subject: [PATCH 3/5] feat: Add Capella diagram layouter --- capellambse_context_diagrams/__init__.py | 7 +- .../collectors/diagram_view.py | 173 ++++++++++++++++++ capellambse_context_diagrams/context.py | 82 +++++++++ 3 files changed, 261 insertions(+), 1 deletion(-) create mode 100644 capellambse_context_diagrams/collectors/diagram_view.py diff --git a/capellambse_context_diagrams/__init__.py b/capellambse_context_diagrams/__init__.py index c3d947ec..285b5867 100644 --- a/capellambse_context_diagrams/__init__.py +++ b/capellambse_context_diagrams/__init__.py @@ -19,7 +19,6 @@ from __future__ import annotations -import collections.abc as cabc import logging import typing as t from importlib import metadata @@ -69,6 +68,7 @@ def init() -> None: register_realization_view() register_data_flow_view() register_cable_tree_view() + register_diagram_layout_accessor() # register_functional_context() XXX: Future @@ -313,3 +313,8 @@ def register_cable_tree_view() -> None: {}, ), ) + + +def register_diagram_layout_accessor() -> None: + """Add the `auto_layout` attribute to `Diagram`s.""" + m.set_accessor(m.Diagram, "auto_layout", context.DiagramLayoutAccessor()) diff --git a/capellambse_context_diagrams/collectors/diagram_view.py b/capellambse_context_diagrams/collectors/diagram_view.py new file mode 100644 index 00000000..8f5ea86a --- /dev/null +++ b/capellambse_context_diagrams/collectors/diagram_view.py @@ -0,0 +1,173 @@ +# SPDX-FileCopyrightText: 2022 Copyright DB InfraGO AG and the capellambse-context-diagrams contributors +# SPDX-License-Identifier: Apache-2.0 +""" +Functionality for collecting all model elements from a [`Diagram`][capellambse.model.diagram.Diagram] +and conversion of it into [`_elkjs.ELKInputData`][capellambse_context_diagrams._elkjs.ELKInputData] +for an automated layout. +""" + +from __future__ import annotations + +import logging +import typing as t + +from capellambse import model as m +from capellambse.metamodel import fa + +from .. import _elkjs, context +from . import generic, makers + +logger = logging.getLogger(__name__) + + +def is_part(node: m.ModelElement) -> bool: + """Check if the ``node`` is a part.""" + return node.xtype.endswith("Part") + + +def is_exchange(node: m.ModelElement) -> bool: + """Check if the ``node`` is an exchange.""" + return hasattr(node, "source") and hasattr(node, "target") + + +def is_port(node: m.ModelElement) -> bool: + """Check if the ``node`` is a port.""" + return node.xtype.endswith("Port") + + +class Collector: + """Collects all model elements from a diagram for ELK.""" + + def __init__(self, diagram: context.ELKDiagram): + self.diagram = diagram + self._diagram = diagram.target + self.data = generic.collector(self.diagram, no_symbol=True) + self.data.children = [] + + self.made_elements: dict[ + str, _elkjs.ELKInputChild | _elkjs.ELKInputEdge | _elkjs.ELKInputPort + ] = {} + self.made_boxes: dict[str, _elkjs.ELKInputChild] = {} + self.made_ports: dict[str, _elkjs.ELKInputPort] = {} + self.exchanges: dict[str, fa.AbstractExchange] = {} + self.global_boxes: dict[str, _elkjs.ELKInputChild] = {} + self.ports: dict[str, fa.Port] = {} + self.boxes_to_delete: set[str] = set() + + def __call__(self, params: dict[str, t.Any]) -> _elkjs.ELKInputData: + self._get_data(params) + self._adjust_box_sizes(params) + self._solve_hierarchy(params) + return self.data + + def _get_data(self, params: dict[str, t.Any]): + del params # No use for it now + for node in self._diagram.nodes: + if is_part(node): + self.make_all_owner_boxes(node.type) + elif is_exchange(node): + self.exchanges[node.uuid] = node + edge = _elkjs.ELKInputEdge( + id=node.uuid, + sources=[node.source.uuid], + targets=[node.target.uuid], + labels=makers.make_label( + node.name, max_width=makers.MAX_LABEL_WIDTH + ), + ) + self.ports[node.source.uuid] = node.source + self.ports[node.target.uuid] = node.target + self.data.edges.append(edge) + elif is_port(node): + port = makers.make_port(node.uuid) + if self.diagram._display_port_labels: + text = node.name or "UNKNOWN" + port.labels = makers.make_label(text) + + self.made_ports[node.uuid] = port + self.made_boxes[node.owner.uuid].ports.append(port) + + if leftover_ports := set(self.ports) - set(self.made_ports): + logger.debug( + "There are ports missing in the diagram nodes: %r", + leftover_ports, + ) + for uuid in leftover_ports: + port_obj = self.ports[uuid] + self.made_ports[uuid] = (port := makers.make_port(uuid)) + self.made_boxes[port_obj.owner.uuid].ports.append(port) + + for uuid in self.boxes_to_delete: + del self.global_boxes[uuid] + + self.data.children.extend(self.global_boxes.values()) + + def make_all_owner_boxes(self, obj: m.ModelElement): + """Make boxes for all owners of the given object. + + Notes + ----- + Also makes a box for the object itself. + """ + if not (obj_box := self.made_boxes.get(obj.uuid)): + obj_box = self._make_box(obj, no_symbol=True, slim_width=False) + self.made_boxes[obj.uuid] = obj_box + + current = obj + while ( + current + and hasattr(current, "owner") + and not isinstance(current.owner, generic.PackageTypes) + ): + current = self._make_owner_box(current) + + def _make_owner_box(self, obj: m.ModelElement) -> m.ModelElement: + if obj.owner.uuid in self.diagram._hide_elements: + return None + + if not (parent_box := self.made_boxes.get(obj.owner.uuid)): + parent_box = self._make_box( + obj.owner, + no_symbol=True, + layout_options=makers.DEFAULT_LABEL_LAYOUT_OPTIONS, + ) + assert (obj_box := self.made_boxes.get(obj.uuid)) + for box in (children := parent_box.children): + if box.id == obj.uuid: + box = obj_box + break + else: + children.append(obj_box) + for label in parent_box.labels: + label.layoutOptions = makers.DEFAULT_LABEL_LAYOUT_OPTIONS + + self.boxes_to_delete.add(obj.uuid) + return obj.owner + + def _make_box(self, obj: t.Any, **kwargs: t.Any) -> _elkjs.ELKInputChild: + box = makers.make_box(obj, **kwargs) + self.global_boxes[obj.uuid] = box + self.made_boxes[obj.uuid] = box + return box + + def _adjust_box_sizes(self, params: dict[str, t.Any]): + del params # No use for it now + for box in self.made_boxes.values(): + box.height += (makers.PORT_SIZE + 2 * makers.PORT_PADDING) * ( + len(box.ports) + 1 + ) + + def _solve_hierarchy(self, params: dict[str, t.Any]): + del params # No use for it now + generic.move_parent_boxes_to_owner( + self.made_boxes, self.diagram.target, self.data + ) + generic.move_edges(self.made_boxes, self.exchanges.values(), self.data) + + +def collect_from_diagram( + diagram: context.ELKDiagram, params: dict[str, t.Any] +) -> _elkjs.ELKInputData: + """Returns ``ELKInputData`` from a diagram.""" + diagram._slim_center_box = False + return Collector(diagram)(params) diff --git a/capellambse_context_diagrams/context.py b/capellambse_context_diagrams/context.py index d82eddc3..3a25e4ae 100644 --- a/capellambse_context_diagrams/context.py +++ b/capellambse_context_diagrams/context.py @@ -21,6 +21,7 @@ from .collectors import ( cable_tree, dataflow_view, + diagram_view, exchanges, get_elkdata, realization_view, @@ -205,6 +206,41 @@ def __get__( # type: ignore return self._get(obj, CableTreeViewDiagram) +class DiagramLayoutAccessor(m.Accessor): + """Provides access to the context diagrams.""" + + def __init__(self, render_params: dict[str, t.Any] | None = None) -> None: + super().__init__() + self._default_render_params = render_params or {} + + @t.overload + def __get__( + self, obj: None, objtype: type[t.Any] + ) -> DiagramLayoutAccessor: ... + @t.overload + def __get__( + self, obj: m.T, objtype: type[m.T] | None = None + ) -> DiagramLayoutAccessor: ... + def __get__( + self, obj: m.T | None, objtype: type | None = None + ) -> m.Accessor | DiagramLayoutAccessor: + """Make a ContextDiagram for the given model object.""" + del objtype + if obj is None: # pragma: no cover + return self + assert isinstance(obj, m.Diagram) + return self._get(obj) + + def _get(self, obj: m.Diagram) -> m.Accessor | ELKDiagram: + new_diagram = ELKDiagram( + obj.type.value, + obj, + default_render_parameters=self._default_render_params, + ) + new_diagram.filters.add(filters.NO_UUID) + return new_diagram + + class ContextDiagram(m.AbstractDiagram): """An automatically generated context diagram. @@ -857,6 +893,52 @@ def name(self) -> str: # type: ignore return f"Cable Tree View of {self.target.name}" +class ELKDiagram(ContextDiagram): + """A former diagram layouted by ELKJS.""" + + _hide_elements: set[str] + + def __init__( + self, + class_: str, + obj: m.Diagram, + *, + render_styles: dict[str, styling.Styler] | None = None, + default_render_parameters: dict[str, t.Any], + ) -> None: + default_render_parameters = { + "hide_elements": set() + } | default_render_parameters + super().__init__( + class_, + obj, + render_styles=render_styles, + default_render_parameters=default_render_parameters, + ) + self.collector = diagram_view.collect_from_diagram + self.target: m.Diagram = obj + + self.__nodes: m.MixedElementList | None = None + + @property + def uuid(self) -> str: # type: ignore + """Returns diagram UUID.""" + return f"{self.target.uuid}_elk" + + @property + def name(self) -> str: # type: ignore + """Returns the diagram name.""" + return f"ELK layout of {self.target.name.replace('/', '- or -')}" + + @property + def nodes(self) -> m.MixedElementList: + """Return a list of all nodes visible in this diagram.""" + if not self.__nodes: + self.__nodes = super().nodes + assert self.__nodes is not None + return self.__nodes + + def try_to_layout(data: _elkjs.ELKInputData) -> _elkjs.ELKOutputData: """Try calling elkjs, raise a JSONDecodeError if it fails.""" try: From 3e9920e10d3aa5fa26a4e74998ed9697421df662 Mon Sep 17 00:00:00 2001 From: ewuerger Date: Mon, 9 Dec 2024 18:34:59 +0100 Subject: [PATCH 4/5] fix: Handle functions and ports correctly --- capellambse_context_diagrams/__init__.py | 101 ++++++++++-------- .../collectors/diagram_view.py | 40 +++++-- .../collectors/makers.py | 4 +- capellambse_context_diagrams/context.py | 50 +++++++-- 4 files changed, 129 insertions(+), 66 deletions(-) diff --git a/capellambse_context_diagrams/__init__.py b/capellambse_context_diagrams/__init__.py index 285b5867..92b7e553 100644 --- a/capellambse_context_diagrams/__init__.py +++ b/capellambse_context_diagrams/__init__.py @@ -26,7 +26,6 @@ import capellambse.model as m from capellambse.diagram import COLORS, CSSdef, capstyle from capellambse.metamodel import cs, fa, information, la, oa, pa, sa -from capellambse.model import DiagramType from . import _elkjs, context, styling @@ -38,7 +37,7 @@ DefaultRenderParams = dict[str, t.Any] SupportedContextClass = tuple[ - type[m.ModelElement], DiagramType, DefaultRenderParams + type[m.ModelElement], m.DiagramType, DefaultRenderParams ] SupportedInterfaceContextClass = tuple[ type[m.ModelElement], dict[type[m.ModelElement], str], DefaultRenderParams @@ -75,22 +74,22 @@ def init() -> None: def register_classes() -> None: """Add the `context_diagram` property to the relevant model objects.""" supported_classes: list[SupportedContextClass] = [ - (oa.Entity, DiagramType.OAB, {}), + (oa.Entity, m.DiagramType.OAB, {}), ( oa.OperationalActivity, - DiagramType.OAB, + m.DiagramType.OAB, {"display_parent_relation": True}, ), - (oa.OperationalCapability, DiagramType.OCB, {}), - (sa.Mission, DiagramType.MCB, {}), + (oa.OperationalCapability, m.DiagramType.OCB, {}), + (sa.Mission, m.DiagramType.MCB, {}), ( sa.Capability, - DiagramType.MCB, + m.DiagramType.MCB, {"display_symbols_as_boxes": False}, ), ( sa.SystemComponent, - DiagramType.SAB, + m.DiagramType.SAB, { "display_symbols_as_boxes": True, "display_parent_relation": True, @@ -100,7 +99,7 @@ def register_classes() -> None: ), ( sa.SystemFunction, - DiagramType.SAB, + m.DiagramType.SAB, { "display_symbols_as_boxes": True, "display_parent_relation": True, @@ -109,7 +108,7 @@ def register_classes() -> None: ), ( la.LogicalComponent, - DiagramType.LAB, + m.DiagramType.LAB, { "display_symbols_as_boxes": True, "display_parent_relation": True, @@ -119,7 +118,7 @@ def register_classes() -> None: ), ( la.LogicalFunction, - DiagramType.LAB, + m.DiagramType.LAB, { "display_symbols_as_boxes": True, "display_parent_relation": True, @@ -128,14 +127,14 @@ def register_classes() -> None: ), ( pa.PhysicalComponent, - DiagramType.PAB, + m.DiagramType.PAB, { "display_parent_relation": True, "display_port_labels": True, "display_derived_interfaces": True, }, ), - (pa.PhysicalFunction, DiagramType.PAB, {}), + (pa.PhysicalFunction, m.DiagramType.PAB, {}), ] cap: dict[str, CSSdef] = { "fill": [COLORS["_CAP_Entity_Gray_min"], COLORS["_CAP_Entity_Gray"]], @@ -166,32 +165,32 @@ def register_interface_context() -> None: ( oa.CommunicationMean, { - oa.EntityPkg: DiagramType.OAB.value, - oa.Entity: DiagramType.OAB.value, + oa.EntityPkg: m.DiagramType.OAB.value, + oa.Entity: m.DiagramType.OAB.value, }, {"include_interface": True}, ), ( fa.ComponentExchange, { - sa.SystemComponentPkg: DiagramType.SAB.value, - sa.SystemComponent: DiagramType.SAB.value, - la.LogicalComponentPkg: DiagramType.LAB.value, - la.LogicalComponent: DiagramType.LAB.value, - pa.PhysicalComponentPkg: DiagramType.PAB.value, - pa.PhysicalComponent: DiagramType.PAB.value, + sa.SystemComponentPkg: m.DiagramType.SAB.value, + sa.SystemComponent: m.DiagramType.SAB.value, + la.LogicalComponentPkg: m.DiagramType.LAB.value, + la.LogicalComponent: m.DiagramType.LAB.value, + pa.PhysicalComponentPkg: m.DiagramType.PAB.value, + pa.PhysicalComponent: m.DiagramType.PAB.value, }, {"include_interface": True, "include_port_allocations": True}, ), ( cs.PhysicalLink, { - sa.SystemComponentPkg: DiagramType.SAB.value, - sa.SystemComponent: DiagramType.SAB.value, - la.LogicalComponentPkg: DiagramType.LAB.value, - la.LogicalComponent: DiagramType.LAB.value, - pa.PhysicalComponentPkg: DiagramType.PAB.value, - pa.PhysicalComponent: DiagramType.PAB.value, + sa.SystemComponentPkg: m.DiagramType.SAB.value, + sa.SystemComponent: m.DiagramType.SAB.value, + la.LogicalComponentPkg: m.DiagramType.LAB.value, + la.LogicalComponent: m.DiagramType.LAB.value, + pa.PhysicalComponentPkg: m.DiagramType.PAB.value, + pa.PhysicalComponent: m.DiagramType.PAB.value, }, {"include_interface": True, "display_port_labels": True}, ), @@ -215,7 +214,7 @@ def register_interface_context() -> None: "stroke-dasharray": "2", "text_fill": COLORS["black"], } - for dt in (DiagramType.SAB, DiagramType.LAB, DiagramType.PAB): + for dt in (m.DiagramType.SAB, m.DiagramType.LAB, m.DiagramType.PAB): capstyle.STYLES[dt.value][ "Edge.PortInputAllocation" ] = port_alloc_input_style @@ -232,11 +231,11 @@ def register_functional_context() -> None: The functional context diagrams will be available soon. """ attr_name = f"functional_{ATTR_NAME}" - supported_classes: list[tuple[type[m.ModelElement], DiagramType]] = [ - (oa.Entity, DiagramType.OAB), - (sa.SystemComponent, DiagramType.SAB), - (la.LogicalComponent, DiagramType.LAB), - (pa.PhysicalComponent, DiagramType.PAB), + supported_classes: list[tuple[type[m.ModelElement], m.DiagramType]] = [ + (oa.Entity, m.DiagramType.OAB), + (sa.SystemComponent, m.DiagramType.SAB), + (la.LogicalComponent, m.DiagramType.LAB), + (pa.PhysicalComponent, m.DiagramType.PAB), ] class_: type[m.ModelElement] for class_, dgcls in supported_classes: @@ -252,7 +251,7 @@ def register_tree_view() -> None: m.set_accessor( information.Class, "tree_view", - context.ClassTreeAccessor(DiagramType.CDB.value), + context.ClassTreeAccessor(m.DiagramType.CDB.value), ) @@ -263,14 +262,14 @@ def register_realization_view() -> None: of all layers. """ supported_classes: list[SupportedContextClass] = [ - (oa.Entity, DiagramType.OAB, {}), - (oa.OperationalActivity, DiagramType.OAIB, {}), - (sa.SystemComponent, DiagramType.SAB, {}), - (sa.SystemFunction, DiagramType.SDFB, {}), - (la.LogicalComponent, DiagramType.LAB, {}), - (la.LogicalFunction, DiagramType.LDFB, {}), - (pa.PhysicalComponent, DiagramType.PAB, {}), - (pa.PhysicalFunction, DiagramType.PDFB, {}), + (oa.Entity, m.DiagramType.OAB, {}), + (oa.OperationalActivity, m.DiagramType.OAIB, {}), + (sa.SystemComponent, m.DiagramType.SAB, {}), + (sa.SystemFunction, m.DiagramType.SDFB, {}), + (la.LogicalComponent, m.DiagramType.LAB, {}), + (la.LogicalFunction, m.DiagramType.LDFB, {}), + (pa.PhysicalComponent, m.DiagramType.PAB, {}), + (pa.PhysicalFunction, m.DiagramType.PDFB, {}), ] styles: dict[str, dict[str, capstyle.CSSdef]] = {} for class_, dgcls, _ in supported_classes: @@ -294,8 +293,8 @@ def register_realization_view() -> None: def register_data_flow_view() -> None: supported_classes: list[SupportedContextClass] = [ - (oa.OperationalCapability, DiagramType.OAIB, {}), # portless - (sa.Capability, DiagramType.SDFB, {}), # default + (oa.OperationalCapability, m.DiagramType.OAIB, {}), # portless + (sa.Capability, m.DiagramType.SDFB, {}), # default ] class_: type[m.ModelElement] for class_, dgcls, default_render_params in supported_classes: @@ -309,7 +308,7 @@ def register_cable_tree_view() -> None: cs.PhysicalLink, "cable_tree", context.CableTreeAccessor( - DiagramType.PAB.value, + m.DiagramType.PAB.value, {}, ), ) @@ -317,4 +316,14 @@ def register_cable_tree_view() -> None: def register_diagram_layout_accessor() -> None: """Add the `auto_layout` attribute to `Diagram`s.""" - m.set_accessor(m.Diagram, "auto_layout", context.DiagramLayoutAccessor()) + render_params = { + m.DiagramType.SAB: {"display_symbols_as_boxes": True}, + m.DiagramType.LAB: {"display_symbols_as_boxes": True}, + m.DiagramType.PAB: {"display_port_labels": True}, + } + + m.set_accessor( + m.Diagram, + "auto_layout", + context.DiagramLayoutAccessor(render_params), + ) diff --git a/capellambse_context_diagrams/collectors/diagram_view.py b/capellambse_context_diagrams/collectors/diagram_view.py index 8f5ea86a..a27f1d85 100644 --- a/capellambse_context_diagrams/collectors/diagram_view.py +++ b/capellambse_context_diagrams/collectors/diagram_view.py @@ -20,6 +20,11 @@ logger = logging.getLogger(__name__) +def is_function(node: m.ModelElement) -> bool: + """Check if the ``node`` is a function.""" + return isinstance(node, fa.Function) + + def is_part(node: m.ModelElement) -> bool: """Check if the ``node`` is a part.""" return node.xtype.endswith("Part") @@ -30,6 +35,11 @@ def is_exchange(node: m.ModelElement) -> bool: return hasattr(node, "source") and hasattr(node, "target") +def is_allocation(node: m.ModelElement) -> bool: + """Check if the ``node`` is an allocation.""" + return node.xtype.endswith("PortAllocation") + + def is_port(node: m.ModelElement) -> bool: """Check if the ``node`` is a port.""" return node.xtype.endswith("Port") @@ -45,7 +55,8 @@ def __init__(self, diagram: context.ELKDiagram): self.data.children = [] self.made_elements: dict[ - str, _elkjs.ELKInputChild | _elkjs.ELKInputEdge | _elkjs.ELKInputPort + str, + _elkjs.ELKInputChild | _elkjs.ELKInputEdge | _elkjs.ELKInputPort, ] = {} self.made_boxes: dict[str, _elkjs.ELKInputChild] = {} self.made_ports: dict[str, _elkjs.ELKInputPort] = {} @@ -63,9 +74,11 @@ def __call__(self, params: dict[str, t.Any]) -> _elkjs.ELKInputData: def _get_data(self, params: dict[str, t.Any]): del params # No use for it now for node in self._diagram.nodes: - if is_part(node): + if is_function(node): + self.make_all_owner_boxes(node) + elif is_part(node): self.make_all_owner_boxes(node.type) - elif is_exchange(node): + elif is_exchange(node) and not is_allocation(node): self.exchanges[node.uuid] = node edge = _elkjs.ELKInputEdge( id=node.uuid, @@ -79,12 +92,7 @@ def _get_data(self, params: dict[str, t.Any]): self.ports[node.target.uuid] = node.target self.data.edges.append(edge) elif is_port(node): - port = makers.make_port(node.uuid) - if self.diagram._display_port_labels: - text = node.name or "UNKNOWN" - port.labels = makers.make_label(text) - - self.made_ports[node.uuid] = port + self.made_ports[node.uuid] = (port := self._make_port(node)) self.made_boxes[node.owner.uuid].ports.append(port) if leftover_ports := set(self.ports) - set(self.made_ports): @@ -94,7 +102,7 @@ def _get_data(self, params: dict[str, t.Any]): ) for uuid in leftover_ports: port_obj = self.ports[uuid] - self.made_ports[uuid] = (port := makers.make_port(uuid)) + self.made_ports[uuid] = (port := self._make_port(port_obj)) self.made_boxes[port_obj.owner.uuid].ports.append(port) for uuid in self.boxes_to_delete: @@ -144,12 +152,22 @@ def _make_owner_box(self, obj: m.ModelElement) -> m.ModelElement: self.boxes_to_delete.add(obj.uuid) return obj.owner - def _make_box(self, obj: t.Any, **kwargs: t.Any) -> _elkjs.ELKInputChild: + def _make_box( + self, obj: m.ModelElement, **kwargs: t.Any + ) -> _elkjs.ELKInputChild: box = makers.make_box(obj, **kwargs) self.global_boxes[obj.uuid] = box self.made_boxes[obj.uuid] = box return box + def _make_port(self, obj: m.ModelElement) -> _elkjs.ELKInputPort: + if self.diagram._display_port_labels: + label = obj.name or "UNKNOWN" + else: + label = "" + + return makers.make_port(obj.uuid, label=label) + def _adjust_box_sizes(self, params: dict[str, t.Any]): del params # No use for it now for box in self.made_boxes.values(): diff --git a/capellambse_context_diagrams/collectors/makers.py b/capellambse_context_diagrams/collectors/makers.py index 553b20b9..8f152f56 100644 --- a/capellambse_context_diagrams/collectors/makers.py +++ b/capellambse_context_diagrams/collectors/makers.py @@ -208,13 +208,15 @@ def is_symbol(obj: str | m.ModelElement | None) -> bool: return type(obj).__name__ in BOX_TO_SYMBOL -def make_port(uuid: str) -> _elkjs.ELKInputPort: +def make_port(uuid: str, label: str = "") -> _elkjs.ELKInputPort: """Return an [`ELKInputPort`][capellambse_context_diagrams._elkjs.ELKInputPort]. """ + labels = make_label(label) if label else [] return _elkjs.ELKInputPort( id=uuid, width=PORT_SIZE, height=PORT_SIZE, + labels=labels, layoutOptions={"borderOffset": -4 * PORT_PADDING}, ) diff --git a/capellambse_context_diagrams/context.py b/capellambse_context_diagrams/context.py index 3a25e4ae..7f063cae 100644 --- a/capellambse_context_diagrams/context.py +++ b/capellambse_context_diagrams/context.py @@ -209,9 +209,14 @@ def __get__( # type: ignore class DiagramLayoutAccessor(m.Accessor): """Provides access to the context diagrams.""" - def __init__(self, render_params: dict[str, t.Any] | None = None) -> None: + def __init__( + self, + dgls_to_render_params: ( + dict[m.DiagramType, dict[str, t.Any]] | None + ) = None, + ) -> None: super().__init__() - self._default_render_params = render_params or {} + self._dgls_to_render_params = dgls_to_render_params or {} @t.overload def __get__( @@ -232,10 +237,11 @@ def __get__( return self._get(obj) def _get(self, obj: m.Diagram) -> m.Accessor | ELKDiagram: + default_render_params = self._dgls_to_render_params.get(obj.type, {}) new_diagram = ELKDiagram( obj.type.value, obj, - default_render_parameters=self._default_render_params, + default_render_parameters=default_render_params, ) new_diagram.filters.add(filters.NO_UUID) return new_diagram @@ -537,14 +543,14 @@ def _yield_port_allocations( self, node: _elkjs.ELKOutputNode, interface_port: _elkjs.ELKOutputPort ) -> cabc.Iterator[_elkjs.ELKOutputEdge]: ref = cdiagram.Vector2D(node.position.x, node.position.y) + interface_middle = _calculate_middle( + interface_port.position, interface_port.size, node.position + ) for position, port in _get_all_ports(node, ref=ref): if port == interface_port: continue port_middle = _calculate_middle(position, port.size) - interface_middle = _calculate_middle( - interface_port.position, interface_port.size, node.position - ) styleclass = self.serializer.get_styleclass(port.id) if styleclass in {"FIP", "FOP"}: yield _create_edge( @@ -896,6 +902,7 @@ def name(self) -> str: # type: ignore class ELKDiagram(ContextDiagram): """A former diagram layouted by ELKJS.""" + _include_port_allocations: bool _hide_elements: set[str] def __init__( @@ -907,7 +914,8 @@ def __init__( default_render_parameters: dict[str, t.Any], ) -> None: default_render_parameters = { - "hide_elements": set() + "include_port_allocations": True, + "hide_elements": set(), } | default_render_parameters super().__init__( class_, @@ -918,6 +926,7 @@ def __init__( self.collector = diagram_view.collect_from_diagram self.target: m.Diagram = obj + self.__port_allocations = [] self.__nodes: m.MixedElementList | None = None @property @@ -938,6 +947,31 @@ def nodes(self) -> m.MixedElementList: assert self.__nodes is not None return self.__nodes + def _create_diagram(self, params: dict[str, t.Any]) -> cdiagram.Diagram: + data = self.elk_input_data(params) + assert not isinstance(data, tuple) + layout = try_to_layout(data) + # if self._include_port_allocations: + # self._add_port_allocations(layout) + + add_context(layout) + return self.serializer.make_diagram( + layout, transparent_background=self._transparent_background + ) + + # def _add_port_allocations(self, layout: _elkjs.ELKOutputData) -> None: + # for allocation in self.__port_allocations: + # if isinstance(allocation.source, fa.ComponentPort): + # interface_port = allocation.source + # port = allocation.target + # else: + # interface_port = allocation.target + # port = allocation.source + + # allocation.source + + # allocation.target + def try_to_layout(data: _elkjs.ELKInputData) -> _elkjs.ELKOutputData: """Try calling elkjs, raise a JSONDecodeError if it fails.""" @@ -1032,7 +1066,7 @@ def calculate_label_position( def _get_all_ports( node: _elkjs.ELKOutputNode, ref: cdiagram.Vector2D ) -> cabc.Iterator[tuple[cdiagram.Vector2D, _elkjs.ELKOutputPort]]: - """Yield all ports from""" + """Yield all ports from a given ``node`` and its children.""" for child in node.children: if isinstance(child, _elkjs.ELKOutputPort): yield ref + (child.position.x, child.position.y), child From c5f2b1bf11166344165dcfef83e7c5330706c9e8 Mon Sep 17 00:00:00 2001 From: ewuerger Date: Mon, 9 Dec 2024 18:35:43 +0100 Subject: [PATCH 5/5] test: Add test case for diagram auto layout --- tests/data/ContextDiagram.aird | 28 ++++++++++++++-------------- tests/data/ContextDiagram.capella | 12 ++++++------ tests/test_diagram_view.py | 29 +++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 20 deletions(-) create mode 100644 tests/test_diagram_view.py diff --git a/tests/data/ContextDiagram.aird b/tests/data/ContextDiagram.aird index 96149b41..ec1c8701 100644 --- a/tests/data/ContextDiagram.aird +++ b/tests/data/ContextDiagram.aird @@ -23,7 +23,7 @@ - +
@@ -166,7 +166,7 @@ - + @@ -14091,7 +14091,7 @@ - + @@ -14397,7 +14397,7 @@ - + @@ -14444,7 +14444,7 @@ - + @@ -14540,7 +14540,7 @@ - + @@ -14552,7 +14552,7 @@ - + @@ -14564,7 +14564,7 @@ - + @@ -14790,7 +14790,7 @@ - + @@ -15353,9 +15353,9 @@ - - - + + + @@ -15419,7 +15419,7 @@ - + bold @@ -15430,7 +15430,7 @@ - + bold diff --git a/tests/data/ContextDiagram.capella b/tests/data/ContextDiagram.capella index f5c33ca5..715b229e 100644 --- a/tests/data/ContextDiagram.capella +++ b/tests/data/ContextDiagram.capella @@ -5076,7 +5076,7 @@ The predator is far away id="793e6da2-d019-4716-a5c5-af8ad550ca5e" name="Sub PC" kind="FIRMWARE" nature="NODE"> + id="38c69508-f12f-4e1d-a8c1-a422cbf8f358" name="Sub BUS"> @@ -5217,19 +5217,19 @@ The predator is far away id="b51ccc6f-5f96-4e28-b90e-72463a3b50cf" name="Network Switch" kind="HARDWARE" nature="NODE"> + id="53c9fe29-18e2-4642-906e-b7507bf0ff39" name="X NSwitch"> + id="f1c0db71-1927-4874-bf3e-0603a88f1d9b" name="X Switch"> + id="76d9c301-c0ad-4615-9f02-b804b018decf" name="X FSwitch"> @@ -5249,7 +5249,7 @@ The predator is far away id="5bfc516b-c20d-4007-9a38-5ba0e889d0a4" name="Camera Assembly" kind="HARDWARE" nature="NODE"> + id="2d1e2ad5-3439-4820-baa8-189966407ba2" name="X CA"> @@ -5260,7 +5260,7 @@ The predator is far away id="8a6c6ec9-095d-4d8b-9728-69bc79af5f27" name="Deploy Sub PC" kind="PERSON" nature="NODE"> + id="7400c2a3-913f-4a08-aca7-8dd7c389e5e9" name="Deploy BUS 1"> diff --git a/tests/test_diagram_view.py b/tests/test_diagram_view.py new file mode 100644 index 00000000..6b547ea5 --- /dev/null +++ b/tests/test_diagram_view.py @@ -0,0 +1,29 @@ +# SPDX-FileCopyrightText: 2022 Copyright DB InfraGO AG and the capellambse-context-diagrams contributors +# SPDX-License-Identifier: Apache-2.0 + +import capellambse +import capellambse.model as m +import pytest + + +@pytest.mark.parametrize( + "name", + [ + pytest.param("[SAB] Example Interface Context", id="Simple SAB diagram"), + pytest.param("[LAB] Hierarchy", id="Simple LAB diagram"), + pytest.param("[LAB] Hierarchy Function example", id="Nested LAB diagram"), + pytest.param("[PAB] Sub-component Cable Tree", id="Simple PAB diagram"), + pytest.param( + "[PAB] Example Physical Function Context Diagram", id="Nested PAB diagram" + ), + ], +) +def test_capability_and_mission_context_diagrams( + model: capellambse.MelodyModel, name: str +) -> None: + obj = model.diagrams.by_name(name) + assert isinstance(obj, m.Diagram), "Precondition failed" + + diag = obj.auto_layout + + assert diag.nodes