From 3a26823f593e1a5f01bbd0d20777f573019011b1 Mon Sep 17 00:00:00 2001 From: Damian Birchler Date: Wed, 18 Jan 2023 10:44:25 +0100 Subject: [PATCH 01/33] Add first try at prototype to convert XMLTMF to DDCK file. --- .../TRIHP_dualSource/TRIHP_dualSource.json | 152 +++-- proforma/.gitignore | 6 + proforma/Type71.xmltmf | 484 ++++++++++++++++ proforma/convertXmlTmfToDdck.py | 264 +++++++++ proforma/dev-requirements.in | 9 + proforma/dev-requirements.txt | 94 ++++ proforma/pylintrc | 523 ++++++++++++++++++ proforma/pytest.ini | 10 + proforma/requirements.in | 2 + proforma/requirements.txt | 15 + proforma/testConvertXmlTmfToDdck.py | 32 ++ proforma/xmltmf.xsd | 95 ++++ 12 files changed, 1634 insertions(+), 52 deletions(-) create mode 100644 proforma/.gitignore create mode 100644 proforma/Type71.xmltmf create mode 100644 proforma/convertXmlTmfToDdck.py create mode 100644 proforma/dev-requirements.in create mode 100644 proforma/dev-requirements.txt create mode 100644 proforma/pylintrc create mode 100644 proforma/pytest.ini create mode 100644 proforma/requirements.in create mode 100644 proforma/requirements.txt create mode 100644 proforma/testConvertXmlTmfToDdck.py create mode 100644 proforma/xmltmf.xsd diff --git a/data/examplesToBeCompleted/TRIHP_dualSource/TRIHP_dualSource.json b/data/examplesToBeCompleted/TRIHP_dualSource/TRIHP_dualSource.json index 7da3d43c..c8c056b3 100644 --- a/data/examplesToBeCompleted/TRIHP_dualSource/TRIHP_dualSource.json +++ b/data/examplesToBeCompleted/TRIHP_dualSource/TRIHP_dualSource.json @@ -826,7 +826,7 @@ }, "Connection-3830": { ".__ConnectionDict__": true, - "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", + "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", "connectionId": 857, "diameterInCm": 2.0, "fromPortId": 1619, @@ -851,13 +851,14 @@ -269.0 ] ], + "shallBeSimulated": true, "toPortId": 2027, "trnsysId": 1240, "uValueInWPerM2K": 0.8333 }, "Connection-5976": { ".__ConnectionDict__": true, - "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", + "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", "connectionId": 1353, "diameterInCm": 2.0, "fromPortId": 3264, @@ -878,13 +879,14 @@ -411.0 ] ], + "shallBeSimulated": true, "toPortId": 309, "trnsysId": 1938, "uValueInWPerM2K": 0.8333 }, "Connection-6007": { ".__ConnectionDict__": true, - "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", + "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", "connectionId": 1361, "diameterInCm": 2.0, "fromPortId": 5982, @@ -905,13 +907,14 @@ -420.0 ] ], + "shallBeSimulated": true, "toPortId": 5990, "trnsysId": 1948, "uValueInWPerM2K": 0.8333 }, "Connection-6010": { ".__ConnectionDict__": true, - "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", + "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", "connectionId": 1362, "diameterInCm": 2.0, "fromPortId": 5991, @@ -932,13 +935,14 @@ -351.0 ] ], + "shallBeSimulated": true, "toPortId": 3084, "trnsysId": 1949, "uValueInWPerM2K": 0.8333 }, "Connection-6013": { ".__ConnectionDict__": true, - "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", + "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", "connectionId": 1363, "diameterInCm": 2.0, "fromPortId": 5980, @@ -963,13 +967,14 @@ -480.0 ] ], + "shallBeSimulated": true, "toPortId": 78, "trnsysId": 1950, "uValueInWPerM2K": 0.8333 }, "Connection-6039": { ".__ConnectionDict__": true, - "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", + "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", "connectionId": 1371, "diameterInCm": 2.0, "fromPortId": 6035, @@ -990,13 +995,14 @@ -91.0 ] ], + "shallBeSimulated": true, "toPortId": 62, "trnsysId": 1959, "uValueInWPerM2K": 0.8333 }, "Connection-6049": { ".__ConnectionDict__": true, - "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", + "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", "connectionId": 1375, "diameterInCm": 2.0, "fromPortId": 56, @@ -1021,13 +1027,14 @@ -151.0 ] ], + "shallBeSimulated": true, "toPortId": 52, "trnsysId": 1963, "uValueInWPerM2K": 0.8333 }, "Connection-6081": { ".__ConnectionDict__": true, - "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", + "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", "connectionId": 1385, "diameterInCm": 2.0, "fromPortId": 6074, @@ -1048,13 +1055,14 @@ -289.0 ] ], + "shallBeSimulated": true, "toPortId": 5318, "trnsysId": 1974, "uValueInWPerM2K": 0.8333 }, "Connection-6085": { ".__ConnectionDict__": true, - "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", + "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", "connectionId": 1387, "diameterInCm": 2.0, "fromPortId": 3804, @@ -1079,13 +1087,14 @@ -529.0 ] ], + "shallBeSimulated": true, "toPortId": 1982, "trnsysId": 1976, "uValueInWPerM2K": 0.8333 }, "Connection-6098": { ".__ConnectionDict__": true, - "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", + "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", "connectionId": 1390, "diameterInCm": 2.0, "fromPortId": 6091, @@ -1106,13 +1115,14 @@ -290.0 ] ], + "shallBeSimulated": true, "toPortId": 173, "trnsysId": 1980, "uValueInWPerM2K": 0.8333 }, "Connection-6103": { ".__ConnectionDict__": true, - "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", + "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", "connectionId": 1393, "diameterInCm": 2.0, "fromPortId": 178, @@ -1137,13 +1147,14 @@ 60.0 ] ], + "shallBeSimulated": true, "toPortId": 3772, "trnsysId": 1983, "uValueInWPerM2K": 0.8333 }, "Connection-6110": { ".__ConnectionDict__": true, - "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", + "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", "connectionId": 1394, "diameterInCm": 2.0, "fromPortId": 6107, @@ -1168,13 +1179,14 @@ -20.0 ] ], + "shallBeSimulated": true, "toPortId": 176, "trnsysId": 1985, "uValueInWPerM2K": 0.8333 }, "Connection-6113": { ".__ConnectionDict__": true, - "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", + "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", "connectionId": 1395, "diameterInCm": 2.0, "fromPortId": 6109, @@ -1195,13 +1207,14 @@ 39.667 ] ], + "shallBeSimulated": true, "toPortId": 3773, "trnsysId": 1986, "uValueInWPerM2K": 0.8333 }, "Connection-6119": { ".__ConnectionDict__": true, - "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", + "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", "connectionId": 1397, "diameterInCm": 2.0, "fromPortId": 3774, @@ -1226,13 +1239,14 @@ 60.0 ] ], + "shallBeSimulated": true, "toPortId": 3776, "trnsysId": 1988, "uValueInWPerM2K": 0.8333 }, "Connection-6134": { ".__ConnectionDict__": true, - "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", + "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", "connectionId": 1402, "diameterInCm": 2.0, "fromPortId": 3777, @@ -1253,13 +1267,14 @@ 60.0 ] ], + "shallBeSimulated": true, "toPortId": 365, "trnsysId": 1993, "uValueInWPerM2K": 0.8333 }, "Connection-6195": { ".__ConnectionDict__": true, - "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", + "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", "connectionId": 1422, "diameterInCm": 2.0, "fromPortId": 3066, @@ -1284,13 +1299,14 @@ -331.0 ] ], + "shallBeSimulated": true, "toPortId": 6190, "trnsysId": 2017, "uValueInWPerM2K": 0.8333 }, "Connection-6206": { ".__ConnectionDict__": true, - "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", + "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", "connectionId": 1425, "diameterInCm": 2.0, "fromPortId": 3067, @@ -1311,13 +1327,14 @@ -474.333 ] ], + "shallBeSimulated": true, "toPortId": 6201, "trnsysId": 2021, "uValueInWPerM2K": 0.8333 }, "Connection-6209": { ".__ConnectionDict__": true, - "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", + "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", "connectionId": 1426, "diameterInCm": 2.0, "fromPortId": 6202, @@ -1342,13 +1359,14 @@ -509.0 ] ], + "shallBeSimulated": true, "toPortId": 3263, "trnsysId": 2022, "uValueInWPerM2K": 0.8333 }, "Connection-6230": { ".__ConnectionDict__": true, - "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", + "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", "connectionId": 1433, "diameterInCm": 2.0, "fromPortId": 6089, @@ -1373,13 +1391,14 @@ -529.0 ] ], + "shallBeSimulated": true, "toPortId": 3803, "trnsysId": 2030, "uValueInWPerM2K": 0.8333 }, "Connection-6234": { ".__ConnectionDict__": true, - "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", + "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", "connectionId": 1435, "diameterInCm": 2.0, "fromPortId": 174, @@ -1404,13 +1423,14 @@ -269.0 ] ], + "shallBeSimulated": true, "toPortId": 1618, "trnsysId": 2032, "uValueInWPerM2K": 0.8333 }, "Connection-6237": { ".__ConnectionDict__": true, - "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", + "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", "connectionId": 1436, "diameterInCm": 2.0, "fromPortId": 5317, @@ -1435,13 +1455,14 @@ -270.0 ] ], + "shallBeSimulated": true, "toPortId": 172, "trnsysId": 2033, "uValueInWPerM2K": 0.8333 }, "Connection-6245": { ".__ConnectionDict__": true, - "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", + "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", "connectionId": 1440, "diameterInCm": 2.0, "fromPortId": 2028, @@ -1466,13 +1487,14 @@ -71.0 ] ], + "shallBeSimulated": true, "toPortId": 4284, "trnsysId": 2037, "uValueInWPerM2K": 0.8333 }, "Connection-6255": { ".__ConnectionDict__": true, - "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", + "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", "connectionId": 1444, "diameterInCm": 2.0, "fromPortId": 80, @@ -1497,13 +1519,14 @@ -331.0 ] ], + "shallBeSimulated": true, "toPortId": 3083, "trnsysId": 2041, "uValueInWPerM2K": 0.8333 }, "Connection-6261": { ".__ConnectionDict__": true, - "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", + "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", "connectionId": 1446, "diameterInCm": 2.0, "fromPortId": 311, @@ -1528,13 +1551,14 @@ -331.0 ] ], + "shallBeSimulated": true, "toPortId": 5956, "trnsysId": 2043, "uValueInWPerM2K": 0.8333 }, "Connection-6264": { ".__ConnectionDict__": true, - "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", + "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", "connectionId": 1447, "diameterInCm": 2.0, "fromPortId": 5957, @@ -1559,13 +1583,14 @@ -331.0 ] ], + "shallBeSimulated": true, "toPortId": 3068, "trnsysId": 2044, "uValueInWPerM2K": 0.8333 }, "Connection-6271": { ".__ConnectionDict__": true, - "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", + "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", "connectionId": 1450, "diameterInCm": 2.0, "fromPortId": 6191, @@ -1590,13 +1615,14 @@ -529.0 ] ], + "shallBeSimulated": true, "toPortId": 3262, "trnsysId": 2047, "uValueInWPerM2K": 0.8333 }, "Connection-6275": { ".__ConnectionDict__": true, - "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", + "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", "connectionId": 1452, "diameterInCm": 2.0, "fromPortId": 6213, @@ -1621,13 +1647,14 @@ -269.0 ] ], + "shallBeSimulated": true, "toPortId": 189, "trnsysId": 2049, "uValueInWPerM2K": 0.8333 }, "Connection-6278": { ".__ConnectionDict__": true, - "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", + "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", "connectionId": 1453, "diameterInCm": 2.0, "fromPortId": 188, @@ -1652,13 +1679,14 @@ -269.0 ] ], + "shallBeSimulated": true, "toPortId": 6034, "trnsysId": 2050, "uValueInWPerM2K": 0.8333 }, "Connection-6281": { ".__ConnectionDict__": true, - "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", + "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", "connectionId": 1454, "diameterInCm": 2.0, "fromPortId": 6033, @@ -1683,13 +1711,14 @@ -269.0 ] ], + "shallBeSimulated": true, "toPortId": 55, "trnsysId": 2051, "uValueInWPerM2K": 0.8333 }, "Connection-6293": { ".__ConnectionDict__": true, - "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", + "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", "connectionId": 1458, "diameterInCm": 2.0, "fromPortId": 184, @@ -1714,13 +1743,14 @@ -71.0 ] ], + "shallBeSimulated": true, "toPortId": 6212, "trnsysId": 2055, "uValueInWPerM2K": 0.8333 }, "Connection-6297": { ".__ConnectionDict__": true, - "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", + "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", "connectionId": 1460, "diameterInCm": 2.0, "fromPortId": 53, @@ -1745,13 +1775,14 @@ -71.0 ] ], + "shallBeSimulated": true, "toPortId": 63, "trnsysId": 2057, "uValueInWPerM2K": 0.8333 }, "Connection-6300": { ".__ConnectionDict__": true, - "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", + "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", "connectionId": 1461, "diameterInCm": 2.0, "fromPortId": 61, @@ -1776,13 +1807,14 @@ -71.0 ] ], + "shallBeSimulated": true, "toPortId": 186, "trnsysId": 2058, "uValueInWPerM2K": 0.8333 }, "Connection-6705": { ".__ConnectionDict__": true, - "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", + "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", "connectionId": 1552, "diameterInCm": 2.0, "fromPortId": 4285, @@ -1803,13 +1835,14 @@ -379.0 ] ], + "shallBeSimulated": true, "toPortId": 4288, "trnsysId": 2189, "uValueInWPerM2K": 0.8333 }, "Connection-6708": { ".__ConnectionDict__": true, - "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", + "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", "connectionId": 1553, "diameterInCm": 2.0, "fromPortId": 1983, @@ -1834,13 +1867,14 @@ -399.0 ] ], + "shallBeSimulated": true, "toPortId": 4287, "trnsysId": 2190, "uValueInWPerM2K": 0.8333 }, "Connection-6711": { ".__ConnectionDict__": true, - "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", + "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", "connectionId": 1554, "diameterInCm": 2.0, "fromPortId": 4289, @@ -1865,13 +1899,14 @@ -398.0 ] ], + "shallBeSimulated": true, "toPortId": 6072, "trnsysId": 2191, "uValueInWPerM2K": 0.8333 }, "Connection-7414": { ".__ConnectionDict__": true, - "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", + "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", "connectionId": 1709, "diameterInCm": 2.0, "fromPortId": 366, @@ -1896,13 +1931,14 @@ 60.0 ] ], + "shallBeSimulated": true, "toPortId": 137, "trnsysId": 2416, "uValueInWPerM2K": 0.8333 }, "Connection-7417": { ".__ConnectionDict__": true, - "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", + "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", "connectionId": 1710, "diameterInCm": 2.0, "fromPortId": 6162, @@ -1927,13 +1963,14 @@ -20.0 ] ], + "shallBeSimulated": true, "toPortId": 140, "trnsysId": 2417, "uValueInWPerM2K": 0.8333 }, "Connection-7420": { ".__ConnectionDict__": true, - "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", + "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", "connectionId": 1711, "diameterInCm": 2.0, "fromPortId": 139, @@ -1958,13 +1995,14 @@ -20.0 ] ], + "shallBeSimulated": true, "toPortId": 6108, "trnsysId": 2418, "uValueInWPerM2K": 0.8333 }, "Connection-7423": { ".__ConnectionDict__": true, - "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", + "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", "connectionId": 1712, "diameterInCm": 2.0, "fromPortId": 136, @@ -1985,13 +2023,14 @@ -0.333 ] ], + "shallBeSimulated": true, "toPortId": 141, "trnsysId": 2419, "uValueInWPerM2K": 0.8333 }, "Connection-7426": { ".__ConnectionDict__": true, - "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", + "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", "connectionId": 1713, "diameterInCm": 2.0, "fromPortId": 135, @@ -2016,13 +2055,14 @@ -91.0 ] ], + "shallBeSimulated": true, "toPortId": 6159, "trnsysId": 2420, "uValueInWPerM2K": 0.8333 }, "Connection-7534": { ".__ConnectionDict__": true, - "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", + "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", "connectionId": 1741, "diameterInCm": 2.0, "fromPortId": 312, @@ -2047,13 +2087,14 @@ -480.0 ] ], + "shallBeSimulated": true, "toPortId": 5981, "trnsysId": 2463, "uValueInWPerM2K": 0.8333 }, "Connection-7535": { ".__ConnectionDict__": true, - "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", + "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", "connectionId": 1742, "diameterInCm": 2.0, "fromPortId": 3082, @@ -2078,13 +2119,14 @@ -331.0 ] ], + "shallBeSimulated": true, "toPortId": 310, "trnsysId": 2464, "uValueInWPerM2K": 0.8333 }, "Connection-7536": { ".__ConnectionDict__": true, - "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", + "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", "connectionId": 1743, "diameterInCm": 2.0, "fromPortId": 179, @@ -2109,13 +2151,14 @@ -51.0 ] ], + "shallBeSimulated": true, "toPortId": 185, "trnsysId": 2465, "uValueInWPerM2K": 0.8333 }, "Connection-7537": { ".__ConnectionDict__": true, - "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", + "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", "connectionId": 1744, "diameterInCm": 2.0, "fromPortId": 190, @@ -2136,13 +2179,14 @@ 60.0 ] ], + "shallBeSimulated": true, "toPortId": 177, "trnsysId": 2466, "uValueInWPerM2K": 0.8333 }, "Connection-7547": { ".__ConnectionDict__": true, - "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", + "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", "connectionId": 1754, "diameterInCm": 2.0, "fromPortId": 6164, @@ -2167,13 +2211,14 @@ -529.0 ] ], + "shallBeSimulated": true, "toPortId": 6090, "trnsysId": 2476, "uValueInWPerM2K": 0.8333 }, "Connection-7548": { ".__ConnectionDict__": true, - "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", + "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", "connectionId": 1755, "diameterInCm": 2.0, "fromPortId": 6073, @@ -2198,13 +2243,14 @@ -131.0 ] ], + "shallBeSimulated": true, "toPortId": 6161, "trnsysId": 2477, "uValueInWPerM2K": 0.8333 }, "Connection-7549": { ".__ConnectionDict__": true, - "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", + "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", "connectionId": 1756, "diameterInCm": 2.0, "fromPortId": 6163, @@ -2229,13 +2275,14 @@ -269.0 ] ], + "shallBeSimulated": true, "toPortId": 5319, "trnsysId": 2478, "uValueInWPerM2K": 0.8333 }, "Connection-7550": { ".__ConnectionDict__": true, - "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", + "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", "connectionId": 1757, "diameterInCm": 2.0, "fromPortId": 4283, @@ -2260,19 +2307,20 @@ -71.0 ] ], + "shallBeSimulated": true, "toPortId": 6160, "trnsysId": 2479, "uValueInWPerM2K": 0.8333 }, "IDs": { - "GlobalId": 7559, + "GlobalId": 7560, "__idDct__": true, - "globalConnID": 1766, - "trnsysID": 2488 + "globalConnID": 1767, + "trnsysID": 2489 }, "Strings": { "DiagramName": "TRIHP_dualSource.json", - "ProjectFolder": "C:\\GIT\\pytrnsys_gui\\data\\examples\\TRIHP_dualSource", + "ProjectFolder": "C:\\Users\\damian.birchler\\src\\pytrnsys\\wd1\\pytrnsys_gui\\data\\examplesToBeCompleted\\TRIHP_dualSource", "__nameDct__": true } }, diff --git a/proforma/.gitignore b/proforma/.gitignore new file mode 100644 index 00000000..3c25b444 --- /dev/null +++ b/proforma/.gitignore @@ -0,0 +1,6 @@ +/.mypy_cache/ +/.pytest_cache/ +/__pycache__/ +/.vscode/ +/venv/ +/.idea/ diff --git a/proforma/Type71.xmltmf b/proforma/Type71.xmltmf new file mode 100644 index 00000000..b19ddf56 --- /dev/null +++ b/proforma/Type71.xmltmf @@ -0,0 +1,484 @@ + + + Solar Collector; Evacuated Tube + Contributors are listed in manuals + Solar Energy Laboratory, University of Wisconsin - Madison + TESS + TRNSYS v7.5 + May 2011 + 1 + 16 + C:\Users\damian.birchler\dev\pytrnsys\issues\gui\proforma-to-ddck\Type71.bmp + 71 + 20 + +
Because the Solar Ratings and Certification Commission (SRCC) defines the efficiency of + an evacuated tube collector bank using the same equations as those for a flat plat, the main + difference (from a modeling point of view) between an evacuated tube collector and a flat + plate collector is in the treatment of incidence angle modifiers (IAMs). Type 71 is + therefore based on the Type 1 code with the major difference being that Type 71 reads a text + file containing a list of transverse and longitudinal IAMs. This component models the + thermal performance of a variety of an evacuated tube collector types using theory. The + total collector array may consist of collectors connected in series and in parallel. The + thermal performance of the total collector array is determined by the number of modules in + series and the characteristics of each module. The user must provide results from standard + tests of efficiency versus a ratio of fluid temperature minus ambient temperature to + radiation (DT/IT). The fluid temperature may be an inlet, average, or outlet temperature. + The model assumes that the efficiency vs. DT/IT curve can be modeled as a quadratic + equation. (Changed from version 13 where efficiency vs. DT/IT was assumed linear.) + Corrections are applied to the slope, intercept, and curvature parameters to account for + identical collectors in series, and flow rates other than those at test conditions. + + The effects of off-normal solar incidence are modeled by the provision of a bi-axial + incidence angle modifier data file.
+ + + 1 + Inlet temperature + input + Temperature + C + real + -Inf + +Inf + [ ; ] + 20.0 + SN + The temperature of the fluid entering the the solar collector. + + + 2 + Inlet flowrate + input + Flow Rate + kg/hr + real + 0.0 + +Inf + [ ; ] + 100.0 + SN + The flow rate of the fluid entering the solar collector. + + + 3 + Ambient temperature + input + Temperature + C + real + -Inf + +Inf + [ ; ] + 10.0 + SN + The temperature of the environment in which the solar collector is located. + This temperature will be used for loss calculations. + + + 4 + Incident radiation + input + Flux + kJ/hr.m^2 + real + 0.0 + +Inf + [ ; ] + 0. + SN + The total (beam + diffuse) radiation incident on the plane of the solar + collector per unit area. + This input is commonly hooked up to the TYPE 16 "total radiation on surface 1" + output. + + + 5 + Incident diffuse radiation + input + Flux + kJ/hr.m^2 + real + 0.0 + +Inf + [ ; ] + 0.0 + SN + The incident diffuse solar radiation in the plane of the collector, per unit + area + + + 6 + Solar incidence angle + input + Direction (Angle) + degrees + real + -360 + +360 + [ ; ] + 0.0 + SN + Incidence angle of beam radiation on the collector's surface + + + 7 + Solar zenith angle + input + Direction (Angle) + degrees + real + -360 + +360 + [ ; ] + 0.0 + SN + The solar zenith angle is the angle between the vertical and the line of + sight of the sun + + + 8 + Solar azimuth angle + input + Direction (Angle) + degrees + real + -360 + +360 + [ ; ] + 0.0 + SN + The solar azimuth angle is the angle between the local meridian and the + projection + of the line of sight of the sun onto the horizontal plane + + + 9 + Collector slope + input + Direction (Angle) + degrees + real + -360 + +360 + [ ; ] + 45 + SN + The slope of the collector is the angle between the collector surface and + the horizontal + 0= horizontal, 90= vertical + The angle is positive when facing towards the collector surface azimuth + As a general rule, the performance of the collector is somewhat optimiszed when the + sollector slope is set to the latitude + + + 10 + Collector azimuth + input + Direction (Angle) + degrees + real + -360 + +360 + [ ; ] + 0.0 + SN + The collector surface azimuth is the angle between the local meridian and + the projection of the normal to the surface onto the horizontal plane + 0 = facing the equator + 90 = facing West + 180 = facing North in northern hemisphere, South in Southern hemisphere + 270 = facing East + + + 11 + Outlet temperature + output + Temperature + C + real + -Inf + +Inf + [ ; ] + 0 + SN + The temperature of the fluid exiting the solar collector array. + + + 12 + Outlet flowrate + output + Flow Rate + kg/hr + real + 0.0 + +Inf + [ ; ] + 0 + SN + The flowrate of the fluid exiting the solar collecor array. In this model: + mdot,in = mdot,out + + + 13 + Useful energy gain + output + Power + kJ/hr + real + 0.0 + +Inf + [ ; ] + 0 + SN + The rate of useful energy gain by the solar collector fluid: + Qu = mdot * Cp * (Tout - Tin) + + + 14 + Number in series + parameter + Dimensionless + - + integer + 1 + +Inf + [ ; ] + 1 + SN + The solar collector model can simulate an array of identical solar + collectors hooked up in series. This parameter is used to specify how many + collectors are hooked up in a series arrangement where the output of the first + collector is the inlet to the second collector. NOTE: increasing this value does not + change the array area. Total array area is set by parameter 2 + + + 15 + Collector area + parameter + Area + m^2 + real + 0.0 + +Inf + [ ; ] + 2.0 + SN + The total area of the solar collector array consistent with the supplied + efficiency parameters (typically gross area and not net area). + + + 16 + Fluid specific heat + parameter + Specific Heat + kJ/kg.K + real + 0.0 + +Inf + [ ; ] + 4.190 + SN + The specific heat of the fluid flowing through the solar collector array. + + + 17 + Efficiency mode + parameter + Dimensionless + - + integer + 1 + 3 + [ ; ] + 1 + SN + The collector efficiency equation can be written as a function of the inlet, + average or outlet temperature. + Specify 1 if the collector efficiency parameters are given as a function of the + inlet temperature + Specify 2 for a function of the collector average temperature + Specify 3 for a function of the collector outlet temperature + + + 18 + Flow rate at test conditions + parameter + Flow/area + kg/hr.m^2 + real + 0.0 + +Inf + [ ; ] + 50.0 + SN + Collector Flow rate per unit area for efficiency test conditions + + + 19 + Intercept efficiency + parameter + Dimensionless + - + real + 0.0 + 1.0 + [ ; ] + 0.7 + SN + This parameter is the y intercept of the collector efficiency curve versus + the temperature difference / radiation ratio + In equation form, this parameter is a0 in the following eq: + Eff = a0 - a1 * (Tc-Tamb)/Rad. - a2 * (Tc-Tamb)^2 / Rad. + where Tc is the collector inlet, average or outlet temperature according to + parameter 4 + + + 20 + Negative of first order efficiency coeficient + parameter + Heat Transfer Coeff. + kJ/hr.m^2.K + real + 0.0 + +Inf + [ ; ] + 10 + SN + This parameter is the slope of the collector efficiency curve versus the + temperature difference / radiation ratio + In equation form, this parameter is a1 in the following eq: + Eff. = a0 - a1 * (Tc-Tamb)/Rad. - a2 * (Tc-Tamb)^2 /Rad. + Where Tc is the collector inlet, average or outlet temperature according to + parameter 4 + + + 21 + Negative of second order efficiency coeficient + parameter + Temp. Dependent Loss Coeff. + kJ/hr.m^2.K^2 + real + 0.0 + +Inf + [ ; ] + 0.03 + SN + This parameter is the curvature of the efficiency curve versus the + temperature difference / radiation ratio + In equation form, this parameter is a2 in the following eq: + Eff. = a0 - a1 * (Tc-Tamb) /Rad.- a2 * (Tc-Tamb)^2/Rad + where Tc is the collector inlet, average or outlet temperature according to + parameter 4 + + + 22 + Logical unit of file containing biaxial IAM data + parameter + Dimensionless + - + integer + 10 + 100 + [ ; ] + 13 + SN + FORTRAN Logical unit for file containing biaxial IAM data Make sure that + each logical unit specified in an assembly is unique + + + 23 + Number of longitudinal angles for which IAMs are provided + parameter + Dimensionless + - + integer + 1 + +Inf + [ ; ] + 7 + SN + Number of data points for the IAM (longitudinal direction) + + + 24 + Number of transverse angles for which IAMs are provided + parameter + Dimensionless + - + integer + 1 + +Inf + [ ; ] + 7 + SN + Number of data points for the IAM (transverse direction) + + + 25 + Collector efficiency + output + dimensionless + - + real + -Inf + +Inf + [ ; ] + 0 + SN + + + 26 + Incidence angle modifier - overall + output + dimensionless + - + real + -Inf + +Inf + [ ; ] + 0 + SN + + + + + + + + 1 + + + + + 2 + + + + 16 + + + + + + 11 + + + + + + + + + What file contains the 2D IAM data? + .\Examples\Data Files\Type71-EvacuatedTubeSolarCollector-IAMData.dat + + .\Examples\Data Files\Type71-EvacuatedTubeSolarCollector-IAMData.dat + + Logical unit of file containing biaxial IAM data + no + + + df /c + .\SourceCode\Types\Type71.f90 +
\ No newline at end of file diff --git a/proforma/convertXmlTmfToDdck.py b/proforma/convertXmlTmfToDdck.py new file mode 100644 index 00000000..549dc148 --- /dev/null +++ b/proforma/convertXmlTmfToDdck.py @@ -0,0 +1,264 @@ +import abc as _abc +import dataclasses as _dc +import pathlib as _pl +import typing as _tp + +import jinja2 as _jj +import xmlschema as _xml + +_CONTAINING_DIR_PATH = _pl.Path(__file__).parent +_SCHEMA_FILE_PATH = _CONTAINING_DIR_PATH / "xmltmf.xsd" + + +def _createDdckJinjaTemplate() -> _jj.Template: + environment = _jj.Environment( + loader=_jj.PackageLoader(__name__), + autoescape=_jj.select_autoescape(), + lstrip_blocks=True, + undefined=_jj.StrictUndefined, + ) + template = environment.get_template("ddck.jinja") + return template + + +_JINJA_TEMPLATE = _createDdckJinjaTemplate() + + +def convertXmltmfToDdck(xmlTmfFilePath: _pl.Path, ddckFilePath: _pl.Path) -> None: + xmlTmfContent = xmlTmfFilePath.read_text(encoding="utf8") + try: + ddckContent = convertXmlTmfStringToDdck(xmlTmfContent) + except ValueError as exception: + raise ValueError(f"Error parsing {xmlTmfFilePath}") from exception + + ddckFilePath.write_text(ddckContent, encoding="utf8") + + +_StringMapping = _tp.Mapping[str, _tp.Any] +_Variable = _StringMapping + + +@_dc.dataclass +class _HydraulicConnectionsData: + portName: str + variableNamePrefix: str + propertyName: str + + @property + def variableName(self) -> str: + capitalizedPortName = self.portName.capitalize() + return f":{self.variableNamePrefix}{capitalizedPortName}" + + @property + def rhs(self) -> str: + return f"@{self.propertyName}({self.portName})" + + @staticmethod + def createForTemperature(portName: str) -> "_HydraulicConnectionsData": + return _HydraulicConnectionsData(portName, "T", "temp") + + @staticmethod + def createForMassFlowRate(portName: str) -> "_HydraulicConnectionsData": + return _HydraulicConnectionsData(portName, "M", "mfr") + + @staticmethod + def createForFluidHeatCapacity(portName: str) -> "_HydraulicConnectionsData": + return _HydraulicConnectionsData(portName, "Cp", "cp") + + @staticmethod + def createForFluidDensity(portName: str) -> "_HydraulicConnectionsData": + return _HydraulicConnectionsData(portName, "Rho", "rho") + + +@_dc.dataclass +class _PortBase(_abc.ABC): + name: str + + @_abc.abstractmethod + def getHydraulicConnectionsData(self, variable: _Variable) -> _HydraulicConnectionsData: + raise NotImplementedError() + + @_abc.abstractmethod + def getVariableOrders(self) -> _tp.Sequence[int]: + raise NotImplementedError() + + +@_dc.dataclass +class _InputPort(_PortBase): + massFlowRate: _Variable + temperature: _Variable + fluidDensity: _tp.Optional[_Variable] + fluidHeatCapacity: _Variable + + def getHydraulicConnectionsData(self, variable: _Variable) -> _HydraulicConnectionsData: + if variable is self.massFlowRate: + return _HydraulicConnectionsData.createForMassFlowRate(self.name) + + if variable is self.temperature: + return _HydraulicConnectionsData.createForTemperature(self.name) + + if variable is self.fluidDensity: + return _HydraulicConnectionsData.createForFluidDensity(self.name) + + if variable is self.fluidHeatCapacity: + return _HydraulicConnectionsData.createForFluidHeatCapacity(self.name) + + raise ValueError(f"Variable `{variable['name']}` is associated with port `{self.name}`.") + + def getVariableOrders(self) -> _tp.Sequence[int]: + variables = [self.massFlowRate, self.temperature, self.fluidDensity, self.fluidHeatCapacity] + orders = [v["order"] for v in variables if v] + return orders + + +@_dc.dataclass +class _OutputPort(_PortBase): + temperature: _Variable + + def getHydraulicConnectionsData(self, variable: _Variable) -> _HydraulicConnectionsData: + if variable is self.temperature: + return _HydraulicConnectionsData.createForTemperature(self.name) + + raise ValueError(f"Variable `{variable['name']}` is associated with port `{self.name}`.") + + def getVariableOrders(self) -> _tp.Sequence[int]: + return [(self.temperature["order"])] + + +@_dc.dataclass +class _ProcessedVariable: + tmfName: str + hydraulicConnectionsData: _tp.Optional[_HydraulicConnectionsData] + roleOrder: int + unit: str + bounds: str + defaultValue: _tp.Union[float, int] + + +def _createProcessedVariables( + role: str, + variables: _tp.Sequence[_StringMapping], + connectionsDataByOrder: _tp.Mapping[int, _HydraulicConnectionsData], +) -> _tp.Sequence[_ProcessedVariable]: + variables = _getVariablesWithRole(role, variables) + sortedVariables = sorted(variables, key=lambda v: v["order"]) + + processedVariables = [] + for roleOrder, variable in enumerate(sortedVariables, start=1): + order = variable["order"] + hydraulicConnectionsData = connectionsDataByOrder.get(order) + bounds = _getBounds(variable) + processedVariable = _ProcessedVariable( + variable["name"], hydraulicConnectionsData, roleOrder, variable["unit"], bounds, variable["default"] + ) + processedVariables.append(processedVariable) + + return processedVariables + + +def convertXmlTmfStringToDdck(xmlTmfContent: str) -> str: + schema = _xml.XMLSchema11(_SCHEMA_FILE_PATH) + + try: + schema.validate(xmlTmfContent) + except _xml.XMLSchemaValidationError as validationError: + raise ValueError("Failed to validate schema.") from validationError + + proforma: _tp.Any = schema.to_dict(xmlTmfContent) + + variables = proforma["variables"]["variable"] + hydraulicConnections = proforma["hydraulicConnections"]["connection"] + + connectionsDataByOrder = _createHydraulicConnectionDataByOrder(hydraulicConnections) + + parameters = _createProcessedVariables("parameter", variables, connectionsDataByOrder) + inputs = _createProcessedVariables("input", variables, connectionsDataByOrder) + outputs = _createProcessedVariables("output", variables, connectionsDataByOrder) + + ddckContent = _JINJA_TEMPLATE.render(type=proforma["type"], parameters=parameters, inputs=inputs, outputs=outputs) + + return ddckContent + + +def _createHydraulicConnectionDataByOrder( + hydraulicConnections: _tp.Sequence[_StringMapping], +) -> _tp.Mapping[int, _HydraulicConnectionsData]: + hydraulicConnectionDataByOrder: dict[int, _HydraulicConnectionsData] = {} + for hydraulicConnection in hydraulicConnections: + dataForInput = _getHydraulicConnectionDataByOrderForInput(hydraulicConnection["input"]) + dataByOrderForOutput = _getHydraulicConnectionDataByOrderForOutput(hydraulicConnection["output"]) + hydraulicConnectionDataByOrder |= dataForInput | dataByOrderForOutput + + return hydraulicConnectionDataByOrder + + +def _getHydraulicConnectionDataByOrderForInput(inputPort: _StringMapping) -> dict[int, _HydraulicConnectionsData]: + portName = inputPort["@name"] + mfrOrder = _getOrder(inputPort["massFlowRate"]) + tempOrder = _getOrder(inputPort["temperature"]) + + densityVariableReference = inputPort.get("fluidDensity") + densityOrder = _getOrder(densityVariableReference) if densityVariableReference else None + + heatCapacityReference = inputPort.get("fluidHeatCapacity") + heatCapacityOrder = _getOrder(heatCapacityReference) if heatCapacityReference else None + + hydraulicConnectionDataByOrder = { + mfrOrder: _HydraulicConnectionsData.createForMassFlowRate(portName), + tempOrder: _HydraulicConnectionsData.createForTemperature(portName), + } + + if densityOrder: + hydraulicConnectionDataByOrder[densityOrder] = _HydraulicConnectionsData.createForFluidDensity(portName) + + if heatCapacityOrder: + hydraulicConnectionDataByOrder[heatCapacityOrder] = _HydraulicConnectionsData.createForFluidHeatCapacity( + portName + ) + + return hydraulicConnectionDataByOrder + + +def _getHydraulicConnectionDataByOrderForOutput( + outputPort: _StringMapping, +) -> _tp.Mapping[int, _HydraulicConnectionsData]: + portName = outputPort["@name"] + tempOrder = _getOrder(outputPort["temperature"]) + hydraulicConnectionData = _HydraulicConnectionsData.createForTemperature(portName) + + return {tempOrder: hydraulicConnectionData} + + +def _getOrder(variableReference: _StringMapping) -> int: + return variableReference["variableReference"]["order"] + + +def _getVariable(port: _StringMapping, tag: str, variablesByOrder: _tp.Mapping[int, _StringMapping]) -> _StringMapping: + variable = _getOptionalVariable(port, tag, variablesByOrder) + portName = port["@name"] + assert variable, f"No associated `{tag}` variable for port {portName}" + return variable + + +def _getOptionalVariable( + port, tag: str, variablesByOrder: _tp.Mapping[int, _StringMapping] +) -> _tp.Optional[_StringMapping]: + child = port.get(tag) + if not child: + return None + + order = child["variableReference"]["order"] + variable = variablesByOrder[order] + return variable + + +def _getVariablesWithRole(role: str, variables: _tp.Sequence[_StringMapping]) -> _tp.Sequence[_StringMapping]: + return [v for v in variables if v["role"] == role] + + +def _getBounds(variable: _StringMapping) -> str: + leftBracket, rightBracket = [b.strip() for b in variable["boundaries"].split(";")] + minimum = variable["min"] + maximum = variable["max"] + bounds = f"{leftBracket}{minimum},{maximum}{rightBracket}" + return bounds diff --git a/proforma/dev-requirements.in b/proforma/dev-requirements.in new file mode 100644 index 00000000..1620b75a --- /dev/null +++ b/proforma/dev-requirements.in @@ -0,0 +1,9 @@ +pip-compile-multi +pip-tools + +pytest +black +mypy +pylint + +-r requirements.in diff --git a/proforma/dev-requirements.txt b/proforma/dev-requirements.txt new file mode 100644 index 00000000..78ff2ea7 --- /dev/null +++ b/proforma/dev-requirements.txt @@ -0,0 +1,94 @@ +# SHA1:5ea4eb15fe3c2c6435f8054688826a40be7ffce6 +# +# This file is autogenerated by pip-compile-multi +# To update, run: +# +# pip-compile-multi +# +-r requirements.txt +astroid==2.13.2 + # via pylint +attrs==22.2.0 + # via pytest +black==22.12.0 + # via -r dev-requirements.in +build==0.10.0 + # via pip-tools +click==8.1.3 + # via + # black + # pip-compile-multi + # pip-tools +colorama==0.4.6 + # via + # build + # click + # pylint + # pytest +dill==0.3.6 + # via pylint +exceptiongroup==1.1.0 + # via pytest +iniconfig==2.0.0 + # via pytest +isort==5.11.4 + # via pylint +lazy-object-proxy==1.9.0 + # via astroid +mccabe==0.7.0 + # via pylint +mypy==0.991 + # via -r dev-requirements.in +mypy-extensions==0.4.3 + # via + # black + # mypy +packaging==23.0 + # via + # build + # pytest +pathspec==0.10.3 + # via black +pip-compile-multi==2.6.1 + # via -r dev-requirements.in +pip-tools==6.12.1 + # via + # -r dev-requirements.in + # pip-compile-multi +platformdirs==2.6.2 + # via + # black + # pylint +pluggy==1.0.0 + # via pytest +pylint==2.15.10 + # via -r dev-requirements.in +pyproject-hooks==1.0.0 + # via build +pytest==7.2.1 + # via -r dev-requirements.in +tomli==2.0.1 + # via + # black + # build + # mypy + # pylint + # pytest +tomlkit==0.11.6 + # via pylint +toposort==1.9 + # via pip-compile-multi +typing-extensions==4.4.0 + # via + # astroid + # black + # mypy + # pylint +wheel==0.38.4 + # via pip-tools +wrapt==1.14.1 + # via astroid + +# The following packages are considered to be unsafe in a requirements file: +# pip +# setuptools diff --git a/proforma/pylintrc b/proforma/pylintrc new file mode 100644 index 00000000..a345b5bf --- /dev/null +++ b/proforma/pylintrc @@ -0,0 +1,523 @@ +[MASTER] + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. +extension-pkg-allow-list=PyQt5 + +# Specify a score threshold to be exceeded before program exits with error. +fail-under=10.0 + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS + +# Add files or directories matching the regex patterns to the blacklist. The +# regex matches against base names, not paths. +ignore-patterns=_?UI_.+_generated.py, _?QRC_.+_generated.py + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use. +jobs=1 + +# Control the amount of potential inferred values when inferring a single +# object. This can help the performance when dealing with large functions or +# complex, nested conditions. +limit-inference-results=100 + +# List of plugins (as comma separated values of python module names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages. +suggestion-mode=yes + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. +confidence= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once). You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +disable=raw-checker-failed, + bad-inline-option, + locally-disabled, + file-ignored, + suppressed-message, + useless-suppression, + deprecated-pragma, + use-symbolic-message-instead, + too-few-public-methods, + missing-module-docstring, + duplicate-code + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable=c-extension-no-member + + +[REPORTS] + +# Python expression which should return a score less than or equal to 10. You +# have access to the variables 'error', 'warning', 'refactor', and 'convention' +# which contain the number of messages in each category, as well as 'statement' +# which is the total number of statements analyzed. This score is used by the +# global evaluation report (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details. +#msg-template= + +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio). You can also give a reporter class, e.g. +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Tells whether to display a full report or only the messages. +reports=no + +# Activate the evaluation score. +score=yes + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit + + +[BASIC] + +# Naming style matching correct argument names. +argument-naming-style=camelCase + +# Regular expression matching correct argument names. Overrides argument- +# naming-style. +#argument-rgx= + +# Naming style matching correct attribute names. +attr-naming-style=camelCase + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. +#attr-rgx= + +# Bad variable names which should always be refused, separated by a comma. +bad-names=foo, + bar, + baz, + toto, + tutu, + tata + +# Bad variable names regexes, separated by a comma. If names match any regex, +# they will always be refused +bad-names-rgxs= + +# Naming style matching correct class attribute names. +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. +#class-attribute-rgx= + +# Naming style matching correct class names. +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming- +# style. +#class-rgx= + +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style. +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming style matching correct function names. +function-naming-style=camelCase + +# Regular expression matching correct function names. Overrides function- +# naming-style. +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma. +good-names=i, + j, + k, + ex, + Run, + _ + +# Good variable names regexes, separated by a comma. If names match any regex, +# they will always be accepted +good-names-rgxs= + +# Include a hint for the correct naming format with invalid-name. +include-naming-hint=no + +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. +#inlinevar-rgx= + +# Naming style matching correct method names. +method-naming-style=camelCase + +# Regular expression matching correct method names. Overrides method-naming- +# style. +#method-rgx= + +# Naming style matching correct module names. +module-naming-style=camelCase + +# Regular expression matching correct module names. Overrides module-naming- +# style. +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^.* + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +# These decorators are taken in consideration only for invalid-name. +property-classes=abc.abstractproperty + +# Naming style matching correct variable names. +variable-naming-style=camelCase + +# Regular expression matching correct variable names. Overrides variable- +# naming-style. +#variable-rgx= + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=121 + +# Maximum number of lines in a module. +max-module-lines=1000 + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[LOGGING] + +# The type of string formatting that logging methods do. `old` means using % +# formatting, `new` is for `{}` formatting. +logging-format-style=old + +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules=logging + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + +# Regular expression of note tags to take in consideration. +#notes-rgx= + + +[SIMILARITIES] + +# Ignore comments when computing similarities. +ignore-comments=no + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=yes + +# Minimum lines number of a similarity. +min-similarity-lines=8 + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + +# Spelling dictionary name. Available dictionaries: none. To make it work, +# install the python-enchant package. +spelling-dict= + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains the private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to the private dictionary (see the +# --spelling-private-dict-file option) instead of raising a message. +spelling-store-unknown-words=no + + +[STRING] + +# This flag controls whether inconsistent-quotes generates a warning when the +# character used as a quote delimiter is used inconsistently within a module. +check-quote-consistency=no + +# This flag controls whether the implicit-str-concat should generate a warning +# on implicit string concatenation in sequences defined over several lines. +check-str-concat-over-line-jumps=no + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis). It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + +# List of decorators that change the signature of a decorated function. +signature-mutators= + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore. +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io + + +[CLASSES] + +# Warn about protected attribute access inside special methods +check-protected-access-in-special-methods=no + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp, + __post_init__ + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict, + _fields, + _replace, + _source, + _make + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=cls + + +[DESIGN] + +# Maximum number of arguments for function / method. +max-args=7 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Maximum number of boolean expressions in an if statement (see R0916). +max-bool-expr=5 + +# Maximum number of branch for function / method body. +max-branches=12 + +# Maximum number of locals for function / method body. +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body. +max-returns=6 + +# Maximum number of statements in function / method body. +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[IMPORTS] + +# List of modules that can be imported at any level, not just the top level +# one. +allow-any-import-level= + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules=optparse,tkinter.tix + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled). +ext-import-graph= + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled). +import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + +# Couples of modules and preferred modules, separated by a comma. +preferred-modules= + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "BaseException, Exception". +overgeneral-exceptions=BaseException, + Exception diff --git a/proforma/pytest.ini b/proforma/pytest.ini new file mode 100644 index 00000000..9c5e8c13 --- /dev/null +++ b/proforma/pytest.ini @@ -0,0 +1,10 @@ +[pytest] +python_files = test* +python_classes = Test* +python_functions = test* + +markers = + manual: ignore when running all the tests (deselect with '-m "not manual"') + tool: not really a test, more like a script that you can run from the IDE + linux_ci: during CI, only run on Linux + diff --git a/proforma/requirements.in b/proforma/requirements.in new file mode 100644 index 00000000..d02307dd --- /dev/null +++ b/proforma/requirements.in @@ -0,0 +1,2 @@ +xmlschema +Jinja2 \ No newline at end of file diff --git a/proforma/requirements.txt b/proforma/requirements.txt new file mode 100644 index 00000000..7ed2ac65 --- /dev/null +++ b/proforma/requirements.txt @@ -0,0 +1,15 @@ +# SHA1:f34c69e65ccce82f35aefa8d900e9d32e0e0b1bc +# +# This file is autogenerated by pip-compile-multi +# To update, run: +# +# pip-compile-multi +# +elementpath==3.0.2 + # via xmlschema +jinja2==3.1.2 + # via -r requirements.in +markupsafe==2.1.1 + # via jinja2 +xmlschema==2.1.1 + # via -r requirements.in diff --git a/proforma/testConvertXmlTmfToDdck.py b/proforma/testConvertXmlTmfToDdck.py new file mode 100644 index 00000000..30b8927e --- /dev/null +++ b/proforma/testConvertXmlTmfToDdck.py @@ -0,0 +1,32 @@ +import pathlib as _pl +import pprint as _pp + +import xmlschema as _xml + +import convertXmlTmfToDdck as _tmf + +_CONTAINING_DIR_PATH = _pl.Path(__file__).parent + + +def testValidateXmlTmf() -> None: + xsdFilePath = _CONTAINING_DIR_PATH / "xmltmf.xsd" + schema = _xml.XMLSchema11(xsdFilePath) + + xmlFilePath = _CONTAINING_DIR_PATH / "Type71.xmltmf" + schema.validate(xmlFilePath) + + +def testDecodeXmlTmf() -> None: + xsdFilePath = _CONTAINING_DIR_PATH / "xmltmf.xsd" + schema = _xml.XMLSchema11(xsdFilePath) + + xmlFilePath = _CONTAINING_DIR_PATH / "Type71.xmltmf" + deserializedData = schema.to_dict(xmlFilePath) + _pp.pprint(deserializedData, indent=4) + + +def testConvertXmlTmfStringToDdck() -> None: + xmlFilePath = _CONTAINING_DIR_PATH / "Type71.xmltmf" + xmlFileContent = xmlFilePath.read_text(encoding="utf8") + ddckContent = _tmf.convertXmlTmfStringToDdck(xmlFileContent) + print(ddckContent) diff --git a/proforma/xmltmf.xsd b/proforma/xmltmf.xsd new file mode 100644 index 00000000..33d47e33 --- /dev/null +++ b/proforma/xmltmf.xsd @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 5e94fd38567ce045138252af6045ea6c18abeb9c Mon Sep 17 00:00:00 2001 From: Damian Birchler Date: Wed, 18 Jan 2023 11:09:47 +0100 Subject: [PATCH 02/33] Add `Jinja` template. --- proforma/templates/ddck.jinja | 46 +++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 proforma/templates/ddck.jinja diff --git a/proforma/templates/ddck.jinja b/proforma/templates/ddck.jinja new file mode 100644 index 00000000..1c472fd0 --- /dev/null +++ b/proforma/templates/ddck.jinja @@ -0,0 +1,46 @@ +*********************************** +** Inputs from hydraulic solver +*********************************** +{% set inputsWithHydraulicData = (parameters + inputs)|rejectattr("hydraulicConnectionsData", "none")|list -%} +EQUATIONS {{inputsWithHydraulicData|length}} +{% for input in inputsWithHydraulicData -%} + {{input.hydraulicConnectionsData.variableName}} = {{input.hydraulicConnectionsData.rhs}} +{% endfor %} +UNIT 1 TYPE {{type}} +PARAMETERS {{parameters|length}} +{% for parameter in parameters -%} + {% if parameter.hydraulicConnectionsData -%} + {{parameter.hydraulicConnectionsData.variableName}} ! {{parameter.roleOrder}}: {{parameter.tmfName}} [{{parameter.unit}}] ({{parameter.bounds}}) + {% else -%} + {{parameter.defaultValue}} ! {{parameter.roleOrder}}: {{parameter.tmfName}} [{{parameter.unit}}] ({{parameter.bounds}}) + {% endif -%} +{% endfor -%} +INPUTS {{inputs|length}} +{% for input in inputs -%} + {% if input.hydraulicConnectionsData -%} + {{input.hydraulicConnectionsData.variableName}} ! {{input.roleOrder}}: {{input.tmfName}} [{{input.unit}}] ({{input.bounds}}) + {% else -%} + 0,0 ! {{input.roleOrder}}: {{input.tmfName}} [{{input.unit}}] ({{input.bounds}}) + {% endif -%} +{% endfor -%} +** initial values +{% for input in inputs -%} + {{input.defaultValue}} ! {{input.roleOrder}}: {{input.tmfName}} - initial value +{% endfor %} +{% set outputsWithHydraulicData = outputs|rejectattr("hydraulicConnectionsData", "none")|list -%} +EQUATIONS {{outputsWithHydraulicData|length}} ! {{outputs|length}} +{% for output in outputs -%} + {% if output.hydraulicConnectionsData -%} + {{output.hydraulicConnectionsData.variableName}} = [1, {{output.roleOrder}}] ! {{output.tmfName}} [{{output.unit}}] ({{output.bounds}}) + {% else -%} + ! XXX = [1, {{output.roleOrder}}] ! {{output.tmfName}} [{{output.unit}}] ({{output.bounds}}) + {% endif -%} +{% endfor %} +*********************************** +** Outputs to hydraulic solver +*********************************** +{% for output in outputs -%} + {% if output.hydraulicConnectionsData -%} + {{output.hydraulicConnectionsData.rhs}} = {{output.hydraulicConnectionsData.variableName}} + {% endif -%} +{% endfor -%} From b6cf615de33a27f0d0faa461ead66614ec0bcbfb Mon Sep 17 00:00:00 2001 From: Damian Birchler Date: Wed, 18 Jan 2023 11:10:21 +0100 Subject: [PATCH 03/33] Compare against expected ddck in test. --- proforma/Type71.ddck | 55 +++++++++++++++++++++++++++++ proforma/testConvertXmlTmfToDdck.py | 9 +++-- 2 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 proforma/Type71.ddck diff --git a/proforma/Type71.ddck b/proforma/Type71.ddck new file mode 100644 index 00000000..9c5b313f --- /dev/null +++ b/proforma/Type71.ddck @@ -0,0 +1,55 @@ +*********************************** +** Inputs from hydraulic solver +*********************************** +EQUATIONS 3 +:CpIn = @cp(In) +:TIn = @temp(In) +:MIn = @mfr(In) + +UNIT 1 TYPE 71 +PARAMETERS 11 +1.0 ! 1: Number in series [-] ([1,+Inf]) +2.0 ! 2: Collector area [m^2] ([0.0,+Inf]) +:CpIn ! 3: Fluid specific heat [kJ/kg.K] ([0.0,+Inf]) +1.0 ! 4: Efficiency mode [-] ([1,3]) +50.0 ! 5: Flow rate at test conditions [kg/hr.m^2] ([0.0,+Inf]) +0.7 ! 6: Intercept efficiency [-] ([0.0,1.0]) +10.0 ! 7: Negative of first order efficiency coeficient [kJ/hr.m^2.K] ([0.0,+Inf]) +0.03 ! 8: Negative of second order efficiency coeficient [kJ/hr.m^2.K^2] ([0.0,+Inf]) +13.0 ! 9: Logical unit of file containing biaxial IAM data [-] ([10,100]) +7.0 ! 10: Number of longitudinal angles for which IAMs are provided [-] ([1,+Inf]) +7.0 ! 11: Number of transverse angles for which IAMs are provided [-] ([1,+Inf]) +INPUTS 10 +:TIn ! 1: Inlet temperature [C] ([-Inf,+Inf]) +:MIn ! 2: Inlet flowrate [kg/hr] ([0.0,+Inf]) +0,0 ! 3: Ambient temperature [C] ([-Inf,+Inf]) +0,0 ! 4: Incident radiation [kJ/hr.m^2] ([0.0,+Inf]) +0,0 ! 5: Incident diffuse radiation [kJ/hr.m^2] ([0.0,+Inf]) +0,0 ! 6: Solar incidence angle [degrees] ([-360,+360]) +0,0 ! 7: Solar zenith angle [degrees] ([-360,+360]) +0,0 ! 8: Solar azimuth angle [degrees] ([-360,+360]) +0,0 ! 9: Collector slope [degrees] ([-360,+360]) +0,0 ! 10: Collector azimuth [degrees] ([-360,+360]) +** initial values +20.0 ! 1: Inlet temperature - initial value +100.0 ! 2: Inlet flowrate - initial value +10.0 ! 3: Ambient temperature - initial value +0.0 ! 4: Incident radiation - initial value +0.0 ! 5: Incident diffuse radiation - initial value +0.0 ! 6: Solar incidence angle - initial value +0.0 ! 7: Solar zenith angle - initial value +0.0 ! 8: Solar azimuth angle - initial value +45.0 ! 9: Collector slope - initial value +0.0 ! 10: Collector azimuth - initial value + +EQUATIONS 1 ! 5 +:TOut = [1, 1] ! Outlet temperature [C] ([-Inf,+Inf]) +! XXX = [1, 2] ! Outlet flowrate [kg/hr] ([0.0,+Inf]) +! XXX = [1, 3] ! Useful energy gain [kJ/hr] ([0.0,+Inf]) +! XXX = [1, 4] ! Collector efficiency [-] ([-Inf,+Inf]) +! XXX = [1, 5] ! Incidence angle modifier - overall [-] ([-Inf,+Inf]) + +*********************************** +** Outputs to hydraulic solver +*********************************** +@temp(Out) = :TOut diff --git a/proforma/testConvertXmlTmfToDdck.py b/proforma/testConvertXmlTmfToDdck.py index 30b8927e..04d48857 100644 --- a/proforma/testConvertXmlTmfToDdck.py +++ b/proforma/testConvertXmlTmfToDdck.py @@ -28,5 +28,10 @@ def testDecodeXmlTmf() -> None: def testConvertXmlTmfStringToDdck() -> None: xmlFilePath = _CONTAINING_DIR_PATH / "Type71.xmltmf" xmlFileContent = xmlFilePath.read_text(encoding="utf8") - ddckContent = _tmf.convertXmlTmfStringToDdck(xmlFileContent) - print(ddckContent) + + actualDdckContent = _tmf.convertXmlTmfStringToDdck(xmlFileContent) + + expectedDdckFilePath = _CONTAINING_DIR_PATH / "Type71.ddck" + expectedDdckContent = expectedDdckFilePath.read_text(encoding="utf8") + + assert actualDdckContent == expectedDdckContent From 5c08a77e606d30459918c867f60cc19b8dd5f9a1 Mon Sep 17 00:00:00 2001 From: Damian Birchler Date: Wed, 18 Jan 2023 11:10:38 +0100 Subject: [PATCH 04/33] Add README. --- proforma/README.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 proforma/README.md diff --git a/proforma/README.md b/proforma/README.md new file mode 100644 index 00000000..3f30e0ff --- /dev/null +++ b/proforma/README.md @@ -0,0 +1,28 @@ +### Introduction ### +A prototype script to convert a TRNSYS simulation studio Proforma in the XMLTMF format to a ddck: +`Type71.xmltmf` is converted to `Type71.ddck`. + +The additional information needed for the conversion is contained within the `` +element. This element does not exist in Proformas yet and would have to be added. + +### Installation ### + +1. Install Python 3.9 64bit from here https://www.python.org/ftp/python/3.9.13/python-3.9.13-amd64.exe +2. Create a virtual environment: + ```commandline + py -3.9 -m venv venv + ``` +3. Activate the environment: + ```commandline + venv\Scripts\activate + ``` +4. Install the dependencies: + ```commandline + python -m pip install -r dev-requirements.txt + ``` + +### Run tests ### +Run +```commandline +pytest -rA . +``` From f3903a797d96f93907e7abc034d9f24cdbb5a831 Mon Sep 17 00:00:00 2001 From: Damian Birchler Date: Mon, 22 Jul 2024 13:09:57 +0200 Subject: [PATCH 05/33] Integrate as is into code structure. --- proforma/.gitignore | 6 - proforma/dev-requirements.in | 9 - proforma/dev-requirements.txt | 94 ---- proforma/pylintrc | 523 ------------------ proforma/pytest.ini | 10 - proforma/requirements.in | 2 - proforma/requirements.txt | 15 - requirements/release-3rd-party.in | 4 +- requirements/release-3rd-party.txt | 6 +- setup.py | 3 +- .../trnsysGUI/proforma}/Type71.ddck | 0 .../trnsysGUI/proforma}/Type71.xmltmf | 0 tests/trnsysGUI/proforma/__init__.py | 0 .../trnsysGUI/proforma}/templates/ddck.jinja | 0 .../proforma}/testConvertXmlTmfToDdck.py | 18 +- {proforma => trnsysGUI/proforma}/README.md | 0 trnsysGUI/proforma/__init__.py | 0 .../proforma}/convertXmlTmfToDdck.py | 0 trnsysGUI/proforma/templates/ddck.jinja | 46 ++ {proforma => trnsysGUI/proforma}/xmltmf.xsd | 0 20 files changed, 68 insertions(+), 668 deletions(-) delete mode 100644 proforma/.gitignore delete mode 100644 proforma/dev-requirements.in delete mode 100644 proforma/dev-requirements.txt delete mode 100644 proforma/pylintrc delete mode 100644 proforma/pytest.ini delete mode 100644 proforma/requirements.in delete mode 100644 proforma/requirements.txt rename {proforma => tests/trnsysGUI/proforma}/Type71.ddck (100%) rename {proforma => tests/trnsysGUI/proforma}/Type71.xmltmf (100%) create mode 100644 tests/trnsysGUI/proforma/__init__.py rename {proforma => tests/trnsysGUI/proforma}/templates/ddck.jinja (100%) rename {proforma => tests/trnsysGUI/proforma}/testConvertXmlTmfToDdck.py (67%) rename {proforma => trnsysGUI/proforma}/README.md (100%) create mode 100644 trnsysGUI/proforma/__init__.py rename {proforma => trnsysGUI/proforma}/convertXmlTmfToDdck.py (100%) create mode 100644 trnsysGUI/proforma/templates/ddck.jinja rename {proforma => trnsysGUI/proforma}/xmltmf.xsd (100%) diff --git a/proforma/.gitignore b/proforma/.gitignore deleted file mode 100644 index 3c25b444..00000000 --- a/proforma/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -/.mypy_cache/ -/.pytest_cache/ -/__pycache__/ -/.vscode/ -/venv/ -/.idea/ diff --git a/proforma/dev-requirements.in b/proforma/dev-requirements.in deleted file mode 100644 index 1620b75a..00000000 --- a/proforma/dev-requirements.in +++ /dev/null @@ -1,9 +0,0 @@ -pip-compile-multi -pip-tools - -pytest -black -mypy -pylint - --r requirements.in diff --git a/proforma/dev-requirements.txt b/proforma/dev-requirements.txt deleted file mode 100644 index 78ff2ea7..00000000 --- a/proforma/dev-requirements.txt +++ /dev/null @@ -1,94 +0,0 @@ -# SHA1:5ea4eb15fe3c2c6435f8054688826a40be7ffce6 -# -# This file is autogenerated by pip-compile-multi -# To update, run: -# -# pip-compile-multi -# --r requirements.txt -astroid==2.13.2 - # via pylint -attrs==22.2.0 - # via pytest -black==22.12.0 - # via -r dev-requirements.in -build==0.10.0 - # via pip-tools -click==8.1.3 - # via - # black - # pip-compile-multi - # pip-tools -colorama==0.4.6 - # via - # build - # click - # pylint - # pytest -dill==0.3.6 - # via pylint -exceptiongroup==1.1.0 - # via pytest -iniconfig==2.0.0 - # via pytest -isort==5.11.4 - # via pylint -lazy-object-proxy==1.9.0 - # via astroid -mccabe==0.7.0 - # via pylint -mypy==0.991 - # via -r dev-requirements.in -mypy-extensions==0.4.3 - # via - # black - # mypy -packaging==23.0 - # via - # build - # pytest -pathspec==0.10.3 - # via black -pip-compile-multi==2.6.1 - # via -r dev-requirements.in -pip-tools==6.12.1 - # via - # -r dev-requirements.in - # pip-compile-multi -platformdirs==2.6.2 - # via - # black - # pylint -pluggy==1.0.0 - # via pytest -pylint==2.15.10 - # via -r dev-requirements.in -pyproject-hooks==1.0.0 - # via build -pytest==7.2.1 - # via -r dev-requirements.in -tomli==2.0.1 - # via - # black - # build - # mypy - # pylint - # pytest -tomlkit==0.11.6 - # via pylint -toposort==1.9 - # via pip-compile-multi -typing-extensions==4.4.0 - # via - # astroid - # black - # mypy - # pylint -wheel==0.38.4 - # via pip-tools -wrapt==1.14.1 - # via astroid - -# The following packages are considered to be unsafe in a requirements file: -# pip -# setuptools diff --git a/proforma/pylintrc b/proforma/pylintrc deleted file mode 100644 index a345b5bf..00000000 --- a/proforma/pylintrc +++ /dev/null @@ -1,523 +0,0 @@ -[MASTER] - -# A comma-separated list of package or module names from where C extensions may -# be loaded. Extensions are loading into the active Python interpreter and may -# run arbitrary code. -extension-pkg-allow-list=PyQt5 - -# Specify a score threshold to be exceeded before program exits with error. -fail-under=10.0 - -# Add files or directories to the blacklist. They should be base names, not -# paths. -ignore=CVS - -# Add files or directories matching the regex patterns to the blacklist. The -# regex matches against base names, not paths. -ignore-patterns=_?UI_.+_generated.py, _?QRC_.+_generated.py - -# Python code to execute, usually for sys.path manipulation such as -# pygtk.require(). -#init-hook= - -# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the -# number of processors available to use. -jobs=1 - -# Control the amount of potential inferred values when inferring a single -# object. This can help the performance when dealing with large functions or -# complex, nested conditions. -limit-inference-results=100 - -# List of plugins (as comma separated values of python module names) to load, -# usually to register additional checkers. -load-plugins= - -# Pickle collected data for later comparisons. -persistent=yes - -# When enabled, pylint would attempt to guess common misconfiguration and emit -# user-friendly hints instead of false-positive error messages. -suggestion-mode=yes - -# Allow loading of arbitrary C extensions. Extensions are imported into the -# active Python interpreter and may run arbitrary code. -unsafe-load-any-extension=no - - -[MESSAGES CONTROL] - -# Only show warnings with the listed confidence levels. Leave empty to show -# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. -confidence= - -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifiers separated by comma (,) or put this -# option multiple times (only on the command line, not in the configuration -# file where it should appear only once). You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use "--disable=all --enable=classes -# --disable=W". -disable=raw-checker-failed, - bad-inline-option, - locally-disabled, - file-ignored, - suppressed-message, - useless-suppression, - deprecated-pragma, - use-symbolic-message-instead, - too-few-public-methods, - missing-module-docstring, - duplicate-code - -# Enable the message, report, category or checker with the given id(s). You can -# either give multiple identifier separated by comma (,) or put this option -# multiple time (only on the command line, not in the configuration file where -# it should appear only once). See also the "--disable" option for examples. -enable=c-extension-no-member - - -[REPORTS] - -# Python expression which should return a score less than or equal to 10. You -# have access to the variables 'error', 'warning', 'refactor', and 'convention' -# which contain the number of messages in each category, as well as 'statement' -# which is the total number of statements analyzed. This score is used by the -# global evaluation report (RP0004). -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) - -# Template used to display messages. This is a python new-style format string -# used to format the message information. See doc for all details. -#msg-template= - -# Set the output format. Available formats are text, parseable, colorized, json -# and msvs (visual studio). You can also give a reporter class, e.g. -# mypackage.mymodule.MyReporterClass. -output-format=text - -# Tells whether to display a full report or only the messages. -reports=no - -# Activate the evaluation score. -score=yes - - -[REFACTORING] - -# Maximum number of nested blocks for function / method body -max-nested-blocks=5 - -# Complete name of functions that never returns. When checking for -# inconsistent-return-statements if a never returning function is called then -# it will be considered as an explicit return statement and no message will be -# printed. -never-returning-functions=sys.exit - - -[BASIC] - -# Naming style matching correct argument names. -argument-naming-style=camelCase - -# Regular expression matching correct argument names. Overrides argument- -# naming-style. -#argument-rgx= - -# Naming style matching correct attribute names. -attr-naming-style=camelCase - -# Regular expression matching correct attribute names. Overrides attr-naming- -# style. -#attr-rgx= - -# Bad variable names which should always be refused, separated by a comma. -bad-names=foo, - bar, - baz, - toto, - tutu, - tata - -# Bad variable names regexes, separated by a comma. If names match any regex, -# they will always be refused -bad-names-rgxs= - -# Naming style matching correct class attribute names. -class-attribute-naming-style=any - -# Regular expression matching correct class attribute names. Overrides class- -# attribute-naming-style. -#class-attribute-rgx= - -# Naming style matching correct class names. -class-naming-style=PascalCase - -# Regular expression matching correct class names. Overrides class-naming- -# style. -#class-rgx= - -# Naming style matching correct constant names. -const-naming-style=UPPER_CASE - -# Regular expression matching correct constant names. Overrides const-naming- -# style. -#const-rgx= - -# Minimum line length for functions/classes that require docstrings, shorter -# ones are exempt. -docstring-min-length=-1 - -# Naming style matching correct function names. -function-naming-style=camelCase - -# Regular expression matching correct function names. Overrides function- -# naming-style. -#function-rgx= - -# Good variable names which should always be accepted, separated by a comma. -good-names=i, - j, - k, - ex, - Run, - _ - -# Good variable names regexes, separated by a comma. If names match any regex, -# they will always be accepted -good-names-rgxs= - -# Include a hint for the correct naming format with invalid-name. -include-naming-hint=no - -# Naming style matching correct inline iteration names. -inlinevar-naming-style=any - -# Regular expression matching correct inline iteration names. Overrides -# inlinevar-naming-style. -#inlinevar-rgx= - -# Naming style matching correct method names. -method-naming-style=camelCase - -# Regular expression matching correct method names. Overrides method-naming- -# style. -#method-rgx= - -# Naming style matching correct module names. -module-naming-style=camelCase - -# Regular expression matching correct module names. Overrides module-naming- -# style. -#module-rgx= - -# Colon-delimited sets of names that determine each other's naming style when -# the name regexes allow several styles. -name-group= - -# Regular expression which should only match function or class names that do -# not require a docstring. -no-docstring-rgx=^.* - -# List of decorators that produce properties, such as abc.abstractproperty. Add -# to this list to register other decorators that produce valid properties. -# These decorators are taken in consideration only for invalid-name. -property-classes=abc.abstractproperty - -# Naming style matching correct variable names. -variable-naming-style=camelCase - -# Regular expression matching correct variable names. Overrides variable- -# naming-style. -#variable-rgx= - - -[FORMAT] - -# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. -expected-line-ending-format= - -# Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines=^\s*(# )??$ - -# Number of spaces of indent required inside a hanging or continued line. -indent-after-paren=4 - -# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 -# tab). -indent-string=' ' - -# Maximum number of characters on a single line. -max-line-length=121 - -# Maximum number of lines in a module. -max-module-lines=1000 - -# Allow the body of a class to be on the same line as the declaration if body -# contains single statement. -single-line-class-stmt=no - -# Allow the body of an if to be on the same line as the test if there is no -# else. -single-line-if-stmt=no - - -[LOGGING] - -# The type of string formatting that logging methods do. `old` means using % -# formatting, `new` is for `{}` formatting. -logging-format-style=old - -# Logging modules to check that the string format arguments are in logging -# function parameter format. -logging-modules=logging - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -notes=FIXME, - XXX, - TODO - -# Regular expression of note tags to take in consideration. -#notes-rgx= - - -[SIMILARITIES] - -# Ignore comments when computing similarities. -ignore-comments=no - -# Ignore docstrings when computing similarities. -ignore-docstrings=yes - -# Ignore imports when computing similarities. -ignore-imports=yes - -# Minimum lines number of a similarity. -min-similarity-lines=8 - - -[SPELLING] - -# Limits count of emitted suggestions for spelling mistakes. -max-spelling-suggestions=4 - -# Spelling dictionary name. Available dictionaries: none. To make it work, -# install the python-enchant package. -spelling-dict= - -# List of comma separated words that should not be checked. -spelling-ignore-words= - -# A path to a file that contains the private dictionary; one word per line. -spelling-private-dict-file= - -# Tells whether to store unknown words to the private dictionary (see the -# --spelling-private-dict-file option) instead of raising a message. -spelling-store-unknown-words=no - - -[STRING] - -# This flag controls whether inconsistent-quotes generates a warning when the -# character used as a quote delimiter is used inconsistently within a module. -check-quote-consistency=no - -# This flag controls whether the implicit-str-concat should generate a warning -# on implicit string concatenation in sequences defined over several lines. -check-str-concat-over-line-jumps=no - - -[TYPECHECK] - -# List of decorators that produce context managers, such as -# contextlib.contextmanager. Add to this list to register other decorators that -# produce valid context managers. -contextmanager-decorators=contextlib.contextmanager - -# List of members which are set dynamically and missed by pylint inference -# system, and so shouldn't trigger E1101 when accessed. Python regular -# expressions are accepted. -generated-members= - -# Tells whether missing members accessed in mixin class should be ignored. A -# mixin class is detected if its name ends with "mixin" (case insensitive). -ignore-mixin-members=yes - -# Tells whether to warn about missing members when the owner of the attribute -# is inferred to be None. -ignore-none=yes - -# This flag controls whether pylint should warn about no-member and similar -# checks whenever an opaque object is returned when inferring. The inference -# can return multiple potential results while evaluating a Python object, but -# some branches might not be evaluated, which results in partial inference. In -# that case, it might be useful to still emit no-member and other checks for -# the rest of the inferred objects. -ignore-on-opaque-inference=yes - -# List of class names for which member attributes should not be checked (useful -# for classes with dynamically set attributes). This supports the use of -# qualified names. -ignored-classes=optparse.Values,thread._local,_thread._local - -# List of module names for which member attributes should not be checked -# (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis). It -# supports qualified module names, as well as Unix pattern matching. -ignored-modules= - -# Show a hint with possible names when a member name was not found. The aspect -# of finding the hint is based on edit distance. -missing-member-hint=yes - -# The minimum edit distance a name should have in order to be considered a -# similar match for a missing member name. -missing-member-hint-distance=1 - -# The total number of similar names that should be taken in consideration when -# showing a hint for a missing member. -missing-member-max-choices=1 - -# List of decorators that change the signature of a decorated function. -signature-mutators= - - -[VARIABLES] - -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid defining new builtins when possible. -additional-builtins= - -# Tells whether unused global variables should be treated as a violation. -allow-global-unused-variables=yes - -# List of strings which can identify a callback function by name. A callback -# name must start or end with one of those strings. -callbacks=cb_, - _cb - -# A regular expression matching the name of dummy variables (i.e. expected to -# not be used). -dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ - -# Argument names that match this expression will be ignored. Default to name -# with leading underscore. -ignored-argument-names=_.*|^ignored_|^unused_ - -# Tells whether we should check for unused import in __init__ files. -init-import=no - -# List of qualified module names which can have objects that can redefine -# builtins. -redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io - - -[CLASSES] - -# Warn about protected attribute access inside special methods -check-protected-access-in-special-methods=no - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__, - __new__, - setUp, - __post_init__ - -# List of member names, which should be excluded from the protected access -# warning. -exclude-protected=_asdict, - _fields, - _replace, - _source, - _make - -# List of valid names for the first argument in a class method. -valid-classmethod-first-arg=cls - -# List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=cls - - -[DESIGN] - -# Maximum number of arguments for function / method. -max-args=7 - -# Maximum number of attributes for a class (see R0902). -max-attributes=7 - -# Maximum number of boolean expressions in an if statement (see R0916). -max-bool-expr=5 - -# Maximum number of branch for function / method body. -max-branches=12 - -# Maximum number of locals for function / method body. -max-locals=15 - -# Maximum number of parents for a class (see R0901). -max-parents=7 - -# Maximum number of public methods for a class (see R0904). -max-public-methods=20 - -# Maximum number of return / yield for function / method body. -max-returns=6 - -# Maximum number of statements in function / method body. -max-statements=50 - -# Minimum number of public methods for a class (see R0903). -min-public-methods=2 - - -[IMPORTS] - -# List of modules that can be imported at any level, not just the top level -# one. -allow-any-import-level= - -# Allow wildcard imports from modules that define __all__. -allow-wildcard-with-all=no - -# Analyse import fallback blocks. This can be used to support both Python 2 and -# 3 compatible code, which means that the block might have code that exists -# only in one or another interpreter, leading to false positives when analysed. -analyse-fallback-blocks=no - -# Deprecated modules which should not be used, separated by a comma. -deprecated-modules=optparse,tkinter.tix - -# Create a graph of external dependencies in the given file (report RP0402 must -# not be disabled). -ext-import-graph= - -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report RP0402 must not be disabled). -import-graph= - -# Create a graph of internal dependencies in the given file (report RP0402 must -# not be disabled). -int-import-graph= - -# Force import order to recognize a module as part of the standard -# compatibility libraries. -known-standard-library= - -# Force import order to recognize a module as part of a third party library. -known-third-party=enchant - -# Couples of modules and preferred modules, separated by a comma. -preferred-modules= - - -[EXCEPTIONS] - -# Exceptions that will emit a warning when being caught. Defaults to -# "BaseException, Exception". -overgeneral-exceptions=BaseException, - Exception diff --git a/proforma/pytest.ini b/proforma/pytest.ini deleted file mode 100644 index 9c5e8c13..00000000 --- a/proforma/pytest.ini +++ /dev/null @@ -1,10 +0,0 @@ -[pytest] -python_files = test* -python_classes = Test* -python_functions = test* - -markers = - manual: ignore when running all the tests (deselect with '-m "not manual"') - tool: not really a test, more like a script that you can run from the IDE - linux_ci: during CI, only run on Linux - diff --git a/proforma/requirements.in b/proforma/requirements.in deleted file mode 100644 index d02307dd..00000000 --- a/proforma/requirements.in +++ /dev/null @@ -1,2 +0,0 @@ -xmlschema -Jinja2 \ No newline at end of file diff --git a/proforma/requirements.txt b/proforma/requirements.txt deleted file mode 100644 index 7ed2ac65..00000000 --- a/proforma/requirements.txt +++ /dev/null @@ -1,15 +0,0 @@ -# SHA1:f34c69e65ccce82f35aefa8d900e9d32e0e0b1bc -# -# This file is autogenerated by pip-compile-multi -# To update, run: -# -# pip-compile-multi -# -elementpath==3.0.2 - # via xmlschema -jinja2==3.1.2 - # via -r requirements.in -markupsafe==2.1.1 - # via jinja2 -xmlschema==2.1.1 - # via -r requirements.in diff --git a/requirements/release-3rd-party.in b/requirements/release-3rd-party.in index aa26c3db..8d0ed2e5 100644 --- a/requirements/release-3rd-party.in +++ b/requirements/release-3rd-party.in @@ -20,4 +20,6 @@ typing_extensions packaging pydantic -pyyaml \ No newline at end of file +pyyaml + +xmlschema diff --git a/requirements/release-3rd-party.txt b/requirements/release-3rd-party.txt index dec312db..50448898 100644 --- a/requirements/release-3rd-party.txt +++ b/requirements/release-3rd-party.txt @@ -1,4 +1,4 @@ -# SHA1:9d82ad464f04b0b0b3a59deb0d928af94dc2cd0a +# SHA1:c2d4f7c8829e3fe3cf7f2140842b3c64470bba1a # # This file is autogenerated by pip-compile-multi # To update, run: @@ -37,6 +37,8 @@ debugpy==1.8.0 # via ipykernel decorator==5.1.1 # via ipython +elementpath==4.4.0 + # via xmlschema executing==2.0.1 # via stack-data fonttools==4.46.0 @@ -201,5 +203,7 @@ tzdata==2023.3 # via pandas wcwidth==0.2.12 # via prompt-toolkit +xmlschema==3.3.1 + # via -r requirements\release-3rd-party.in xyzservices==2023.10.1 # via bokeh diff --git a/setup.py b/setup.py index 69491d9a..a58b991f 100644 --- a/setup.py +++ b/setup.py @@ -51,7 +51,7 @@ def _getInstallRequirements(): "dev_template": _MASTER_VERSION_TEMPLATE, "dirty_template": f"{_MASTER_VERSION_TEMPLATE}.dirty", }, - author_email="martin.neugebauer@ost.ch", + author_email="damian.birchler@ost.ch", description="A GUI for Trnsys", long_description=long_description, long_description_content_type="text/markdown", @@ -70,6 +70,7 @@ def _getInstallRequirements(): "templates/generic/*.ddck", ], "trnsysGUI.components.plugin": ["data/*/*.svg", "data/*/*.yaml"], + "trnsysGUI.proforma": ["templates/ddck.jinja", "xmltmf.xsd"], }, data_files=_getDataFilePairs(), install_requires=_getInstallRequirements(), diff --git a/proforma/Type71.ddck b/tests/trnsysGUI/proforma/Type71.ddck similarity index 100% rename from proforma/Type71.ddck rename to tests/trnsysGUI/proforma/Type71.ddck diff --git a/proforma/Type71.xmltmf b/tests/trnsysGUI/proforma/Type71.xmltmf similarity index 100% rename from proforma/Type71.xmltmf rename to tests/trnsysGUI/proforma/Type71.xmltmf diff --git a/tests/trnsysGUI/proforma/__init__.py b/tests/trnsysGUI/proforma/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/proforma/templates/ddck.jinja b/tests/trnsysGUI/proforma/templates/ddck.jinja similarity index 100% rename from proforma/templates/ddck.jinja rename to tests/trnsysGUI/proforma/templates/ddck.jinja diff --git a/proforma/testConvertXmlTmfToDdck.py b/tests/trnsysGUI/proforma/testConvertXmlTmfToDdck.py similarity index 67% rename from proforma/testConvertXmlTmfToDdck.py rename to tests/trnsysGUI/proforma/testConvertXmlTmfToDdck.py index 04d48857..c715f614 100644 --- a/proforma/testConvertXmlTmfToDdck.py +++ b/tests/trnsysGUI/proforma/testConvertXmlTmfToDdck.py @@ -1,35 +1,41 @@ import pathlib as _pl import pprint as _pp +import pkgutil as _pu import xmlschema as _xml -import convertXmlTmfToDdck as _tmf +import trnsysGUI.proforma.convertXmlTmfToDdck as _pc _CONTAINING_DIR_PATH = _pl.Path(__file__).parent def testValidateXmlTmf() -> None: - xsdFilePath = _CONTAINING_DIR_PATH / "xmltmf.xsd" - schema = _xml.XMLSchema11(xsdFilePath) + schema = _getSchema() xmlFilePath = _CONTAINING_DIR_PATH / "Type71.xmltmf" schema.validate(xmlFilePath) def testDecodeXmlTmf() -> None: - xsdFilePath = _CONTAINING_DIR_PATH / "xmltmf.xsd" - schema = _xml.XMLSchema11(xsdFilePath) + schema = _getSchema() xmlFilePath = _CONTAINING_DIR_PATH / "Type71.xmltmf" deserializedData = schema.to_dict(xmlFilePath) _pp.pprint(deserializedData, indent=4) +def _getSchema() -> _xml.XMLSchema11: + data = _pu.get_data(_pc.__name__, "xmltmf.xsd") + string = data.decode("UTF8") + schema = _xml.XMLSchema11(string) + return schema + + def testConvertXmlTmfStringToDdck() -> None: xmlFilePath = _CONTAINING_DIR_PATH / "Type71.xmltmf" xmlFileContent = xmlFilePath.read_text(encoding="utf8") - actualDdckContent = _tmf.convertXmlTmfStringToDdck(xmlFileContent) + actualDdckContent = _pc.convertXmlTmfStringToDdck(xmlFileContent) expectedDdckFilePath = _CONTAINING_DIR_PATH / "Type71.ddck" expectedDdckContent = expectedDdckFilePath.read_text(encoding="utf8") diff --git a/proforma/README.md b/trnsysGUI/proforma/README.md similarity index 100% rename from proforma/README.md rename to trnsysGUI/proforma/README.md diff --git a/trnsysGUI/proforma/__init__.py b/trnsysGUI/proforma/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/proforma/convertXmlTmfToDdck.py b/trnsysGUI/proforma/convertXmlTmfToDdck.py similarity index 100% rename from proforma/convertXmlTmfToDdck.py rename to trnsysGUI/proforma/convertXmlTmfToDdck.py diff --git a/trnsysGUI/proforma/templates/ddck.jinja b/trnsysGUI/proforma/templates/ddck.jinja new file mode 100644 index 00000000..1c472fd0 --- /dev/null +++ b/trnsysGUI/proforma/templates/ddck.jinja @@ -0,0 +1,46 @@ +*********************************** +** Inputs from hydraulic solver +*********************************** +{% set inputsWithHydraulicData = (parameters + inputs)|rejectattr("hydraulicConnectionsData", "none")|list -%} +EQUATIONS {{inputsWithHydraulicData|length}} +{% for input in inputsWithHydraulicData -%} + {{input.hydraulicConnectionsData.variableName}} = {{input.hydraulicConnectionsData.rhs}} +{% endfor %} +UNIT 1 TYPE {{type}} +PARAMETERS {{parameters|length}} +{% for parameter in parameters -%} + {% if parameter.hydraulicConnectionsData -%} + {{parameter.hydraulicConnectionsData.variableName}} ! {{parameter.roleOrder}}: {{parameter.tmfName}} [{{parameter.unit}}] ({{parameter.bounds}}) + {% else -%} + {{parameter.defaultValue}} ! {{parameter.roleOrder}}: {{parameter.tmfName}} [{{parameter.unit}}] ({{parameter.bounds}}) + {% endif -%} +{% endfor -%} +INPUTS {{inputs|length}} +{% for input in inputs -%} + {% if input.hydraulicConnectionsData -%} + {{input.hydraulicConnectionsData.variableName}} ! {{input.roleOrder}}: {{input.tmfName}} [{{input.unit}}] ({{input.bounds}}) + {% else -%} + 0,0 ! {{input.roleOrder}}: {{input.tmfName}} [{{input.unit}}] ({{input.bounds}}) + {% endif -%} +{% endfor -%} +** initial values +{% for input in inputs -%} + {{input.defaultValue}} ! {{input.roleOrder}}: {{input.tmfName}} - initial value +{% endfor %} +{% set outputsWithHydraulicData = outputs|rejectattr("hydraulicConnectionsData", "none")|list -%} +EQUATIONS {{outputsWithHydraulicData|length}} ! {{outputs|length}} +{% for output in outputs -%} + {% if output.hydraulicConnectionsData -%} + {{output.hydraulicConnectionsData.variableName}} = [1, {{output.roleOrder}}] ! {{output.tmfName}} [{{output.unit}}] ({{output.bounds}}) + {% else -%} + ! XXX = [1, {{output.roleOrder}}] ! {{output.tmfName}} [{{output.unit}}] ({{output.bounds}}) + {% endif -%} +{% endfor %} +*********************************** +** Outputs to hydraulic solver +*********************************** +{% for output in outputs -%} + {% if output.hydraulicConnectionsData -%} + {{output.hydraulicConnectionsData.rhs}} = {{output.hydraulicConnectionsData.variableName}} + {% endif -%} +{% endfor -%} diff --git a/proforma/xmltmf.xsd b/trnsysGUI/proforma/xmltmf.xsd similarity index 100% rename from proforma/xmltmf.xsd rename to trnsysGUI/proforma/xmltmf.xsd From e8763f6a2b8053c1ee168ab76a4843d78ca2c2e5 Mon Sep 17 00:00:00 2001 From: Damian Birchler Date: Mon, 22 Jul 2024 14:21:55 +0200 Subject: [PATCH 06/33] Remove dead code. --- trnsysGUI/proforma/convertXmlTmfToDdck.py | 55 ----------------------- 1 file changed, 55 deletions(-) diff --git a/trnsysGUI/proforma/convertXmlTmfToDdck.py b/trnsysGUI/proforma/convertXmlTmfToDdck.py index 549dc148..9c0a8d7e 100644 --- a/trnsysGUI/proforma/convertXmlTmfToDdck.py +++ b/trnsysGUI/proforma/convertXmlTmfToDdck.py @@ -70,61 +70,6 @@ def createForFluidDensity(portName: str) -> "_HydraulicConnectionsData": return _HydraulicConnectionsData(portName, "Rho", "rho") -@_dc.dataclass -class _PortBase(_abc.ABC): - name: str - - @_abc.abstractmethod - def getHydraulicConnectionsData(self, variable: _Variable) -> _HydraulicConnectionsData: - raise NotImplementedError() - - @_abc.abstractmethod - def getVariableOrders(self) -> _tp.Sequence[int]: - raise NotImplementedError() - - -@_dc.dataclass -class _InputPort(_PortBase): - massFlowRate: _Variable - temperature: _Variable - fluidDensity: _tp.Optional[_Variable] - fluidHeatCapacity: _Variable - - def getHydraulicConnectionsData(self, variable: _Variable) -> _HydraulicConnectionsData: - if variable is self.massFlowRate: - return _HydraulicConnectionsData.createForMassFlowRate(self.name) - - if variable is self.temperature: - return _HydraulicConnectionsData.createForTemperature(self.name) - - if variable is self.fluidDensity: - return _HydraulicConnectionsData.createForFluidDensity(self.name) - - if variable is self.fluidHeatCapacity: - return _HydraulicConnectionsData.createForFluidHeatCapacity(self.name) - - raise ValueError(f"Variable `{variable['name']}` is associated with port `{self.name}`.") - - def getVariableOrders(self) -> _tp.Sequence[int]: - variables = [self.massFlowRate, self.temperature, self.fluidDensity, self.fluidHeatCapacity] - orders = [v["order"] for v in variables if v] - return orders - - -@_dc.dataclass -class _OutputPort(_PortBase): - temperature: _Variable - - def getHydraulicConnectionsData(self, variable: _Variable) -> _HydraulicConnectionsData: - if variable is self.temperature: - return _HydraulicConnectionsData.createForTemperature(self.name) - - raise ValueError(f"Variable `{variable['name']}` is associated with port `{self.name}`.") - - def getVariableOrders(self) -> _tp.Sequence[int]: - return [(self.temperature["order"])] - - @_dc.dataclass class _ProcessedVariable: tmfName: str From e9d43b091406ace5527ff7220274992b6e3c2b65 Mon Sep 17 00:00:00 2001 From: Damian Birchler Date: Mon, 22 Jul 2024 15:12:24 +0200 Subject: [PATCH 07/33] Add possibility of not having a hydraulic section. With test. --- .../Type71-no-hydraulic-connections.ddck | 42 ++ .../Type71-no-hydraulic-connections.xmltmf | 458 ++++++++++++++++++ .../proforma/testConvertXmlTmfToDdck.py | 12 + trnsysGUI/proforma/convertXmlTmfToDdck.py | 44 +- trnsysGUI/proforma/xmltmf.xsd | 6 +- 5 files changed, 543 insertions(+), 19 deletions(-) create mode 100644 tests/trnsysGUI/proforma/Type71-no-hydraulic-connections.ddck create mode 100644 tests/trnsysGUI/proforma/Type71-no-hydraulic-connections.xmltmf diff --git a/tests/trnsysGUI/proforma/Type71-no-hydraulic-connections.ddck b/tests/trnsysGUI/proforma/Type71-no-hydraulic-connections.ddck new file mode 100644 index 00000000..e415decb --- /dev/null +++ b/tests/trnsysGUI/proforma/Type71-no-hydraulic-connections.ddck @@ -0,0 +1,42 @@ +UNIT 1 TYPE 71 +PARAMETERS 11 +1.0 ! 1: Number in series [-] ([1,+Inf]) +2.0 ! 2: Collector area [m^2] ([0.0,+Inf]) +4.19 ! 3: Fluid specific heat [kJ/kg.K] ([0.0,+Inf]) +1.0 ! 4: Efficiency mode [-] ([1,3]) +50.0 ! 5: Flow rate at test conditions [kg/hr.m^2] ([0.0,+Inf]) +0.7 ! 6: Intercept efficiency [-] ([0.0,1.0]) +10.0 ! 7: Negative of first order efficiency coeficient [kJ/hr.m^2.K] ([0.0,+Inf]) +0.03 ! 8: Negative of second order efficiency coeficient [kJ/hr.m^2.K^2] ([0.0,+Inf]) +13.0 ! 9: Logical unit of file containing biaxial IAM data [-] ([10,100]) +7.0 ! 10: Number of longitudinal angles for which IAMs are provided [-] ([1,+Inf]) +7.0 ! 11: Number of transverse angles for which IAMs are provided [-] ([1,+Inf]) +INPUTS 10 +0,0 ! 1: Inlet temperature [C] ([-Inf,+Inf]) +0,0 ! 2: Inlet flowrate [kg/hr] ([0.0,+Inf]) +0,0 ! 3: Ambient temperature [C] ([-Inf,+Inf]) +0,0 ! 4: Incident radiation [kJ/hr.m^2] ([0.0,+Inf]) +0,0 ! 5: Incident diffuse radiation [kJ/hr.m^2] ([0.0,+Inf]) +0,0 ! 6: Solar incidence angle [degrees] ([-360,+360]) +0,0 ! 7: Solar zenith angle [degrees] ([-360,+360]) +0,0 ! 8: Solar azimuth angle [degrees] ([-360,+360]) +0,0 ! 9: Collector slope [degrees] ([-360,+360]) +0,0 ! 10: Collector azimuth [degrees] ([-360,+360]) +** initial values +20.0 ! 1: Inlet temperature - initial value +100.0 ! 2: Inlet flowrate - initial value +10.0 ! 3: Ambient temperature - initial value +0.0 ! 4: Incident radiation - initial value +0.0 ! 5: Incident diffuse radiation - initial value +0.0 ! 6: Solar incidence angle - initial value +0.0 ! 7: Solar zenith angle - initial value +0.0 ! 8: Solar azimuth angle - initial value +45.0 ! 9: Collector slope - initial value +0.0 ! 10: Collector azimuth - initial value + +! EQUATIONS 5 +! XXX = [1, 1] ! Outlet temperature [C] ([-Inf,+Inf]) +! XXX = [1, 2] ! Outlet flowrate [kg/hr] ([0.0,+Inf]) +! XXX = [1, 3] ! Useful energy gain [kJ/hr] ([0.0,+Inf]) +! XXX = [1, 4] ! Collector efficiency [-] ([-Inf,+Inf]) +! XXX = [1, 5] ! Incidence angle modifier - overall [-] ([-Inf,+Inf]) diff --git a/tests/trnsysGUI/proforma/Type71-no-hydraulic-connections.xmltmf b/tests/trnsysGUI/proforma/Type71-no-hydraulic-connections.xmltmf new file mode 100644 index 00000000..d8b1b4f5 --- /dev/null +++ b/tests/trnsysGUI/proforma/Type71-no-hydraulic-connections.xmltmf @@ -0,0 +1,458 @@ + + + Solar Collector; Evacuated Tube + Contributors are listed in manuals + Solar Energy Laboratory, University of Wisconsin - Madison + TESS + TRNSYS v7.5 + May 2011 + 1 + 16 + C:\Users\damian.birchler\dev\pytrnsys\issues\gui\proforma-to-ddck\Type71.bmp + 71 + 20 + +
Because the Solar Ratings and Certification Commission (SRCC) defines the efficiency of + an evacuated tube collector bank using the same equations as those for a flat plat, the main + difference (from a modeling point of view) between an evacuated tube collector and a flat + plate collector is in the treatment of incidence angle modifiers (IAMs). Type 71 is + therefore based on the Type 1 code with the major difference being that Type 71 reads a text + file containing a list of transverse and longitudinal IAMs. This component models the + thermal performance of a variety of an evacuated tube collector types using theory. The + total collector array may consist of collectors connected in series and in parallel. The + thermal performance of the total collector array is determined by the number of modules in + series and the characteristics of each module. The user must provide results from standard + tests of efficiency versus a ratio of fluid temperature minus ambient temperature to + radiation (DT/IT). The fluid temperature may be an inlet, average, or outlet temperature. + The model assumes that the efficiency vs. DT/IT curve can be modeled as a quadratic + equation. (Changed from version 13 where efficiency vs. DT/IT was assumed linear.) + Corrections are applied to the slope, intercept, and curvature parameters to account for + identical collectors in series, and flow rates other than those at test conditions. + + The effects of off-normal solar incidence are modeled by the provision of a bi-axial + incidence angle modifier data file.
+ + + 1 + Inlet temperature + input + Temperature + C + real + -Inf + +Inf + [ ; ] + 20.0 + SN + The temperature of the fluid entering the the solar collector. + + + 2 + Inlet flowrate + input + Flow Rate + kg/hr + real + 0.0 + +Inf + [ ; ] + 100.0 + SN + The flow rate of the fluid entering the solar collector. + + + 3 + Ambient temperature + input + Temperature + C + real + -Inf + +Inf + [ ; ] + 10.0 + SN + The temperature of the environment in which the solar collector is located. + This temperature will be used for loss calculations. + + + 4 + Incident radiation + input + Flux + kJ/hr.m^2 + real + 0.0 + +Inf + [ ; ] + 0. + SN + The total (beam + diffuse) radiation incident on the plane of the solar + collector per unit area. + This input is commonly hooked up to the TYPE 16 "total radiation on surface 1" + output. + + + 5 + Incident diffuse radiation + input + Flux + kJ/hr.m^2 + real + 0.0 + +Inf + [ ; ] + 0.0 + SN + The incident diffuse solar radiation in the plane of the collector, per unit + area + + + 6 + Solar incidence angle + input + Direction (Angle) + degrees + real + -360 + +360 + [ ; ] + 0.0 + SN + Incidence angle of beam radiation on the collector's surface + + + 7 + Solar zenith angle + input + Direction (Angle) + degrees + real + -360 + +360 + [ ; ] + 0.0 + SN + The solar zenith angle is the angle between the vertical and the line of + sight of the sun + + + 8 + Solar azimuth angle + input + Direction (Angle) + degrees + real + -360 + +360 + [ ; ] + 0.0 + SN + The solar azimuth angle is the angle between the local meridian and the + projection + of the line of sight of the sun onto the horizontal plane + + + 9 + Collector slope + input + Direction (Angle) + degrees + real + -360 + +360 + [ ; ] + 45 + SN + The slope of the collector is the angle between the collector surface and + the horizontal + 0= horizontal, 90= vertical + The angle is positive when facing towards the collector surface azimuth + As a general rule, the performance of the collector is somewhat optimiszed when the + sollector slope is set to the latitude + + + 10 + Collector azimuth + input + Direction (Angle) + degrees + real + -360 + +360 + [ ; ] + 0.0 + SN + The collector surface azimuth is the angle between the local meridian and + the projection of the normal to the surface onto the horizontal plane + 0 = facing the equator + 90 = facing West + 180 = facing North in northern hemisphere, South in Southern hemisphere + 270 = facing East + + + 11 + Outlet temperature + output + Temperature + C + real + -Inf + +Inf + [ ; ] + 0 + SN + The temperature of the fluid exiting the solar collector array. + + + 12 + Outlet flowrate + output + Flow Rate + kg/hr + real + 0.0 + +Inf + [ ; ] + 0 + SN + The flowrate of the fluid exiting the solar collecor array. In this model: + mdot,in = mdot,out + + + 13 + Useful energy gain + output + Power + kJ/hr + real + 0.0 + +Inf + [ ; ] + 0 + SN + The rate of useful energy gain by the solar collector fluid: + Qu = mdot * Cp * (Tout - Tin) + + + 14 + Number in series + parameter + Dimensionless + - + integer + 1 + +Inf + [ ; ] + 1 + SN + The solar collector model can simulate an array of identical solar + collectors hooked up in series. This parameter is used to specify how many + collectors are hooked up in a series arrangement where the output of the first + collector is the inlet to the second collector. NOTE: increasing this value does not + change the array area. Total array area is set by parameter 2 + + + 15 + Collector area + parameter + Area + m^2 + real + 0.0 + +Inf + [ ; ] + 2.0 + SN + The total area of the solar collector array consistent with the supplied + efficiency parameters (typically gross area and not net area). + + + 16 + Fluid specific heat + parameter + Specific Heat + kJ/kg.K + real + 0.0 + +Inf + [ ; ] + 4.190 + SN + The specific heat of the fluid flowing through the solar collector array. + + + 17 + Efficiency mode + parameter + Dimensionless + - + integer + 1 + 3 + [ ; ] + 1 + SN + The collector efficiency equation can be written as a function of the inlet, + average or outlet temperature. + Specify 1 if the collector efficiency parameters are given as a function of the + inlet temperature + Specify 2 for a function of the collector average temperature + Specify 3 for a function of the collector outlet temperature + + + 18 + Flow rate at test conditions + parameter + Flow/area + kg/hr.m^2 + real + 0.0 + +Inf + [ ; ] + 50.0 + SN + Collector Flow rate per unit area for efficiency test conditions + + + 19 + Intercept efficiency + parameter + Dimensionless + - + real + 0.0 + 1.0 + [ ; ] + 0.7 + SN + This parameter is the y intercept of the collector efficiency curve versus + the temperature difference / radiation ratio + In equation form, this parameter is a0 in the following eq: + Eff = a0 - a1 * (Tc-Tamb)/Rad. - a2 * (Tc-Tamb)^2 / Rad. + where Tc is the collector inlet, average or outlet temperature according to + parameter 4 + + + 20 + Negative of first order efficiency coeficient + parameter + Heat Transfer Coeff. + kJ/hr.m^2.K + real + 0.0 + +Inf + [ ; ] + 10 + SN + This parameter is the slope of the collector efficiency curve versus the + temperature difference / radiation ratio + In equation form, this parameter is a1 in the following eq: + Eff. = a0 - a1 * (Tc-Tamb)/Rad. - a2 * (Tc-Tamb)^2 /Rad. + Where Tc is the collector inlet, average or outlet temperature according to + parameter 4 + + + 21 + Negative of second order efficiency coeficient + parameter + Temp. Dependent Loss Coeff. + kJ/hr.m^2.K^2 + real + 0.0 + +Inf + [ ; ] + 0.03 + SN + This parameter is the curvature of the efficiency curve versus the + temperature difference / radiation ratio + In equation form, this parameter is a2 in the following eq: + Eff. = a0 - a1 * (Tc-Tamb) /Rad.- a2 * (Tc-Tamb)^2/Rad + where Tc is the collector inlet, average or outlet temperature according to + parameter 4 + + + 22 + Logical unit of file containing biaxial IAM data + parameter + Dimensionless + - + integer + 10 + 100 + [ ; ] + 13 + SN + FORTRAN Logical unit for file containing biaxial IAM data Make sure that + each logical unit specified in an assembly is unique + + + 23 + Number of longitudinal angles for which IAMs are provided + parameter + Dimensionless + - + integer + 1 + +Inf + [ ; ] + 7 + SN + Number of data points for the IAM (longitudinal direction) + + + 24 + Number of transverse angles for which IAMs are provided + parameter + Dimensionless + - + integer + 1 + +Inf + [ ; ] + 7 + SN + Number of data points for the IAM (transverse direction) + + + 25 + Collector efficiency + output + dimensionless + - + real + -Inf + +Inf + [ ; ] + 0 + SN + + + 26 + Incidence angle modifier - overall + output + dimensionless + - + real + -Inf + +Inf + [ ; ] + 0 + SN + + + + + + What file contains the 2D IAM data? + .\Examples\Data Files\Type71-EvacuatedTubeSolarCollector-IAMData.dat + + .\Examples\Data Files\Type71-EvacuatedTubeSolarCollector-IAMData.dat + + Logical unit of file containing biaxial IAM data + no + + + df /c + .\SourceCode\Types\Type71.f90 +
\ No newline at end of file diff --git a/tests/trnsysGUI/proforma/testConvertXmlTmfToDdck.py b/tests/trnsysGUI/proforma/testConvertXmlTmfToDdck.py index c715f614..78742f6c 100644 --- a/tests/trnsysGUI/proforma/testConvertXmlTmfToDdck.py +++ b/tests/trnsysGUI/proforma/testConvertXmlTmfToDdck.py @@ -41,3 +41,15 @@ def testConvertXmlTmfStringToDdck() -> None: expectedDdckContent = expectedDdckFilePath.read_text(encoding="utf8") assert actualDdckContent == expectedDdckContent + + +def testConvertXmlTmfWithoutHydraulicConnectionsStringToDdck() -> None: + xmlFilePath = _CONTAINING_DIR_PATH / "Type71-no-hydraulic-connections.xmltmf" + xmlFileContent = xmlFilePath.read_text(encoding="utf8") + + actualDdckContent = _pc.convertXmlTmfStringToDdck(xmlFileContent) + + expectedDdckFilePath = _CONTAINING_DIR_PATH / "Type71-no-hydraulic-connections.ddck" + expectedDdckContent = expectedDdckFilePath.read_text(encoding="utf8") + + assert actualDdckContent == expectedDdckContent diff --git a/trnsysGUI/proforma/convertXmlTmfToDdck.py b/trnsysGUI/proforma/convertXmlTmfToDdck.py index 9c0a8d7e..50f82a50 100644 --- a/trnsysGUI/proforma/convertXmlTmfToDdck.py +++ b/trnsysGUI/proforma/convertXmlTmfToDdck.py @@ -40,6 +40,7 @@ def convertXmltmfToDdck(xmlTmfFilePath: _pl.Path, ddckFilePath: _pl.Path) -> Non @_dc.dataclass class _HydraulicConnectionsData: + name: str | None portName: str variableNamePrefix: str propertyName: str @@ -54,20 +55,20 @@ def rhs(self) -> str: return f"@{self.propertyName}({self.portName})" @staticmethod - def createForTemperature(portName: str) -> "_HydraulicConnectionsData": - return _HydraulicConnectionsData(portName, "T", "temp") + def createForTemperature(connectionName: str | None, portName: str) -> "_HydraulicConnectionsData": + return _HydraulicConnectionsData(connectionName, portName, "T", "temp") @staticmethod - def createForMassFlowRate(portName: str) -> "_HydraulicConnectionsData": - return _HydraulicConnectionsData(portName, "M", "mfr") + def createForMassFlowRate(connectionName: str | None, portName: str) -> "_HydraulicConnectionsData": + return _HydraulicConnectionsData(connectionName, portName, "M", "mfr") @staticmethod - def createForFluidHeatCapacity(portName: str) -> "_HydraulicConnectionsData": - return _HydraulicConnectionsData(portName, "Cp", "cp") + def createForFluidHeatCapacity(connectionName: str | None, portName: str) -> "_HydraulicConnectionsData": + return _HydraulicConnectionsData(connectionName, portName, "Cp", "cp") @staticmethod - def createForFluidDensity(portName: str) -> "_HydraulicConnectionsData": - return _HydraulicConnectionsData(portName, "Rho", "rho") + def createForFluidDensity(connectionName: str | None, portName: str) -> "_HydraulicConnectionsData": + return _HydraulicConnectionsData(connectionName, portName, "Rho", "rho") @_dc.dataclass @@ -112,7 +113,10 @@ def convertXmlTmfStringToDdck(xmlTmfContent: str) -> str: proforma: _tp.Any = schema.to_dict(xmlTmfContent) variables = proforma["variables"]["variable"] - hydraulicConnections = proforma["hydraulicConnections"]["connection"] + hydraulicConnections = proforma["hydraulicConnections"]["connection"] if "hydraulicConnections" in proforma else None + + if hydraulicConnections is None: + hydraulicConnections = [] connectionsDataByOrder = _createHydraulicConnectionDataByOrder(hydraulicConnections) @@ -130,14 +134,17 @@ def _createHydraulicConnectionDataByOrder( ) -> _tp.Mapping[int, _HydraulicConnectionsData]: hydraulicConnectionDataByOrder: dict[int, _HydraulicConnectionsData] = {} for hydraulicConnection in hydraulicConnections: - dataForInput = _getHydraulicConnectionDataByOrderForInput(hydraulicConnection["input"]) - dataByOrderForOutput = _getHydraulicConnectionDataByOrderForOutput(hydraulicConnection["output"]) + connectionName = hydraulicConnection.get("@name") + dataForInput = _getHydraulicConnectionDataByOrderForInput(connectionName, hydraulicConnection["input"]) + dataByOrderForOutput = _getHydraulicConnectionDataByOrderForOutput(connectionName, hydraulicConnection["output"]) hydraulicConnectionDataByOrder |= dataForInput | dataByOrderForOutput return hydraulicConnectionDataByOrder -def _getHydraulicConnectionDataByOrderForInput(inputPort: _StringMapping) -> dict[int, _HydraulicConnectionsData]: +def _getHydraulicConnectionDataByOrderForInput( + connectionName: str, inputPort: _StringMapping +) -> dict[int, _HydraulicConnectionsData]: portName = inputPort["@name"] mfrOrder = _getOrder(inputPort["massFlowRate"]) tempOrder = _getOrder(inputPort["temperature"]) @@ -149,27 +156,30 @@ def _getHydraulicConnectionDataByOrderForInput(inputPort: _StringMapping) -> dic heatCapacityOrder = _getOrder(heatCapacityReference) if heatCapacityReference else None hydraulicConnectionDataByOrder = { - mfrOrder: _HydraulicConnectionsData.createForMassFlowRate(portName), - tempOrder: _HydraulicConnectionsData.createForTemperature(portName), + mfrOrder: _HydraulicConnectionsData.createForMassFlowRate(connectionName, portName), + tempOrder: _HydraulicConnectionsData.createForTemperature(connectionName, portName), } if densityOrder: - hydraulicConnectionDataByOrder[densityOrder] = _HydraulicConnectionsData.createForFluidDensity(portName) + hydraulicConnectionDataByOrder[densityOrder] = _HydraulicConnectionsData.createForFluidDensity( + connectionName, portName + ) if heatCapacityOrder: hydraulicConnectionDataByOrder[heatCapacityOrder] = _HydraulicConnectionsData.createForFluidHeatCapacity( - portName + connectionName, portName ) return hydraulicConnectionDataByOrder def _getHydraulicConnectionDataByOrderForOutput( + connectionName: str, outputPort: _StringMapping, ) -> _tp.Mapping[int, _HydraulicConnectionsData]: portName = outputPort["@name"] tempOrder = _getOrder(outputPort["temperature"]) - hydraulicConnectionData = _HydraulicConnectionsData.createForTemperature(portName) + hydraulicConnectionData = _HydraulicConnectionsData.createForTemperature(connectionName, portName) return {tempOrder: hydraulicConnectionData} diff --git a/trnsysGUI/proforma/xmltmf.xsd b/trnsysGUI/proforma/xmltmf.xsd index 33d47e33..835811d4 100644 --- a/trnsysGUI/proforma/xmltmf.xsd +++ b/trnsysGUI/proforma/xmltmf.xsd @@ -52,13 +52,14 @@ - + + @@ -67,6 +68,7 @@ + @@ -89,7 +91,7 @@ - + \ No newline at end of file From 42c1c73279824e165597f00520132b0b69f5967a Mon Sep 17 00:00:00 2001 From: Damian Birchler Date: Mon, 22 Jul 2024 16:50:44 +0200 Subject: [PATCH 08/33] WIP --- trnsysGUI/proforma/_dialogs/__init__.py | 0 trnsysGUI/proforma/_dialogs/_connection.ui | 286 ++++++++++++++++++ trnsysGUI/proforma/_dialogs/_connections.ui | 95 ++++++ .../proforma/_dialogs/connectionsDialog.py | 74 +++++ trnsysGUI/proforma/convertXmlTmfToDdck.py | 26 +- trnsysGUI/proforma/models.py | 42 +++ trnsysGUI/proforma/templates/ddck.jinja | 29 +- trnsysGUI/pytrnsys-gui.pyproject | 2 +- 8 files changed, 532 insertions(+), 22 deletions(-) create mode 100644 trnsysGUI/proforma/_dialogs/__init__.py create mode 100644 trnsysGUI/proforma/_dialogs/_connection.ui create mode 100644 trnsysGUI/proforma/_dialogs/_connections.ui create mode 100644 trnsysGUI/proforma/_dialogs/connectionsDialog.py create mode 100644 trnsysGUI/proforma/models.py diff --git a/trnsysGUI/proforma/_dialogs/__init__.py b/trnsysGUI/proforma/_dialogs/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/trnsysGUI/proforma/_dialogs/_connection.ui b/trnsysGUI/proforma/_dialogs/_connection.ui new file mode 100644 index 00000000..1693ca2f --- /dev/null +++ b/trnsysGUI/proforma/_dialogs/_connection.ui @@ -0,0 +1,286 @@ + + + Dialog + + + + 0 + 0 + 382 + 319 + + + + Dialog + + + + + + Hydraulic connection + + + + + + + + + false + + + + + + + Name + + + + + + + + + + + 0 + 0 + + + + + 20 + 20 + + + + + 0 + 0 + + + + <html><head/><body><p>If your component has only one hydraulic connection you can typically leave this uncheked.</p></body></html> + + + image: url(:/tango-icons/status/tango-icon-theme-0.8.90/scalable/status/dialog-information.svg); + + + + + + + Mass flow rate + + + + + + + + + + + + + false + + + + + + + Fluid density + + + + + + + + + + + 0 + 0 + + + + + 20 + 20 + + + + + 0 + 0 + + + + <html><head/><body><p>Not all types need this. If it's not needed by your type, uncheck the box at the very left of this line.</p></body></html> + + + image: url(:/tango-icons/status/tango-icon-theme-0.8.90/scalable/status/dialog-information.svg); + + + + + + + + + + false + + + + + + + Fluid heat capacity + + + + + + + + + + + 0 + 0 + + + + + 20 + 20 + + + + + 0 + 0 + + + + <html><head/><body><p>Not all types need this. If it's not needed by your type, uncheck the box at the very left of this line.</p></body></html> + + + image: url(:/tango-icons/status/tango-icon-theme-0.8.90/scalable/status/dialog-information.svg); + + + + + + + Input temperature + + + + + + + + + + + + + + + + + Reverse flow input temperature + + + + + + + + + + + 0 + 0 + + + + + 20 + 20 + + + + + 0 + 0 + + + + <html><head/><body><p>The input temperature for when the mass flow rate is negative. Not all types can handle reversible flows, this input is not required by all types.</p></body></html> + + + image: url(:/tango-icons/status/tango-icon-theme-0.8.90/scalable/status/dialog-information.svg); + + + + + + + Output temperature + + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + Qt::Vertical + + + + 20 + 1 + + + + + + + + + diff --git a/trnsysGUI/proforma/_dialogs/_connections.ui b/trnsysGUI/proforma/_dialogs/_connections.ui new file mode 100644 index 00000000..2ea65c2a --- /dev/null +++ b/trnsysGUI/proforma/_dialogs/_connections.ui @@ -0,0 +1,95 @@ + + + HydraulicConnections + + + + 0 + 0 + 328 + 325 + + + + Define hydraulic connections + + + + + + Hydraulic connections + + + + + + + + + + + Qt::Horizontal + + + + 18 + 20 + + + + + + + + Add... + + + + + + + Edit... + + + + + + + Remove + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + diff --git a/trnsysGUI/proforma/_dialogs/connectionsDialog.py b/trnsysGUI/proforma/_dialogs/connectionsDialog.py new file mode 100644 index 00000000..b4bd4ac7 --- /dev/null +++ b/trnsysGUI/proforma/_dialogs/connectionsDialog.py @@ -0,0 +1,74 @@ +import collections.abc as _cabc + +import PyQt5.QtWidgets as _qtw +import PyQt5.QtCore as _qtc + +import trnsysGUI.common as _com +import trnsysGUI.common.cancelled as _cancel +import trnsysGUI.dialogs as _dlgs + + +from .. import models as _models + +_dlgs.assertThatLocalGeneratedUIModuleAndResourcesExist(__name__) + +from . import _UI_connections_generated as _uigen # type: ignore[import] # pylint: disable=wrong-import-position + + +class _ConnectionListItem(_qtw.QListWidgetItem): + def __init__(self, connection: _models.Connection) -> None: + super().__init__() + + self.connection = connection + self.setText(connection.name) + + @staticmethod + def create() -> "_ConnectionListItem": + connection = _models.Connection.createEmpty() + return _ConnectionListItem(connection) + + +class ConnectionsDialog(_qtw.QDialog, _uigen.Ui_HydraulicConnections): + def __init__(self) -> None: + super().__init__() + self.setupUi(self) + + self.connectionsListWidget.setSelectionMode(_qtw.QListWidget.SelectionMode.SingleSelection) + self.connectionsListWidget.itemSelectionChanged.connect(self._onSelectionChanged) + + self.addConnectionButton.clicked.connect(self._onAddConnection) + self.removeConnectionButton.clicked.connect(self._onRemoveConnection) + + def _onSelectionChanged(self) -> None: + selectedItems = self.connectionsListWidget.selectedItems() + + isItemSelected = bool(selectedItems) + + self.editConnectionButton.setEnabled(isItemSelected) + self.removeConnectionButton.setEnabled(isItemSelected) + + def _onAddConnection(self) -> None: + listItem = _ConnectionListItem.create() + self.connectionsListWidget.addItem(listItem) + + def _onRemoveConnection(self) -> None: + selectedItem = self._getSelectedConnectionListItem() + self.connectionsListWidget.removeItemWidget(selectedItem) + + def _getSelectedConnectionListItem(self) -> _ConnectionListItem: + selectedItems = self.connectionsListWidget.selectedItems() + selectedItem = _com.getSingle(selectedItems) + return selectedItem + + @staticmethod + def showDialogAndGetResults() -> _cancel.MaybeCancelled[_cabc.Set[_models.Connection]]: + connectionsDialog = ConnectionsDialog() + returnValue = connectionsDialog.exec() + + if returnValue == _qtw.QDialogButtonBox.StandardButton.Cancel: + return _cancel.CANCELLED + + connectionListWidget = connectionsDialog.connectionsListWidget + connections = {connectionListWidget.item(r) for r in range(connectionListWidget.count())} + + return connections diff --git a/trnsysGUI/proforma/convertXmlTmfToDdck.py b/trnsysGUI/proforma/convertXmlTmfToDdck.py index 50f82a50..f7181d2e 100644 --- a/trnsysGUI/proforma/convertXmlTmfToDdck.py +++ b/trnsysGUI/proforma/convertXmlTmfToDdck.py @@ -1,4 +1,4 @@ -import abc as _abc +import collections.abc as _cabc import dataclasses as _dc import pathlib as _pl import typing as _tp @@ -6,6 +6,9 @@ import jinja2 as _jj import xmlschema as _xml +from . import models as _models +from ._dialogs import connectionsDialog as _csd + _CONTAINING_DIR_PATH = _pl.Path(__file__).parent _SCHEMA_FILE_PATH = _CONTAINING_DIR_PATH / "xmltmf.xsd" @@ -116,7 +119,7 @@ def convertXmlTmfStringToDdck(xmlTmfContent: str) -> str: hydraulicConnections = proforma["hydraulicConnections"]["connection"] if "hydraulicConnections" in proforma else None if hydraulicConnections is None: - hydraulicConnections = [] + hydraulicConnections = _csd.ConnectionsDialog.showDialogAndGetResults() connectionsDataByOrder = _createHydraulicConnectionDataByOrder(hydraulicConnections) @@ -130,23 +133,26 @@ def convertXmlTmfStringToDdck(xmlTmfContent: str) -> str: def _createHydraulicConnectionDataByOrder( - hydraulicConnections: _tp.Sequence[_StringMapping], + hydraulicConnections: _cabc.Set[_models.Connection], ) -> _tp.Mapping[int, _HydraulicConnectionsData]: hydraulicConnectionDataByOrder: dict[int, _HydraulicConnectionsData] = {} for hydraulicConnection in hydraulicConnections: - connectionName = hydraulicConnection.get("@name") - dataForInput = _getHydraulicConnectionDataByOrderForInput(connectionName, hydraulicConnection["input"]) - dataByOrderForOutput = _getHydraulicConnectionDataByOrderForOutput(connectionName, hydraulicConnection["output"]) - hydraulicConnectionDataByOrder |= dataForInput | dataByOrderForOutput + dataByOrderForInput = _getHydraulicConnectionDataByOrderForInput( + hydraulicConnection.name, hydraulicConnection.inputPort + ) + dataByOrderForOutput = _getHydraulicConnectionDataByOrderForOutput( + hydraulicConnection.name, hydraulicConnection.outputPort + ) + hydraulicConnectionDataByOrder |= dataByOrderForInput | dataByOrderForOutput return hydraulicConnectionDataByOrder def _getHydraulicConnectionDataByOrderForInput( - connectionName: str, inputPort: _StringMapping + connectionName: str, inputPort: _models.InputPort ) -> dict[int, _HydraulicConnectionsData]: - portName = inputPort["@name"] - mfrOrder = _getOrder(inputPort["massFlowRate"]) + portName = inputPort.name + mfrOrder = inputPort.massFlowRate.order tempOrder = _getOrder(inputPort["temperature"]) densityVariableReference = inputPort.get("fluidDensity") diff --git a/trnsysGUI/proforma/models.py b/trnsysGUI/proforma/models.py new file mode 100644 index 00000000..dac8be43 --- /dev/null +++ b/trnsysGUI/proforma/models.py @@ -0,0 +1,42 @@ +import dataclasses as _dc + + +@_dc.dataclass +class Connection: + name: str | None + inputPort: "InputPort" + outputPort: "OutputPort" + + @staticmethod + def createEmpty() -> "Connection": + return Connection(name=None, inputPort=InputPort.createEmpty(), outputPort=OutputPort.createEmpty()) + + +@_dc.dataclass +class InputPort: + name: str + temperature: "Variable" | None + massFlowRate: "Variable" | None + fluidDensity: "Variable" | None + heatCapacity: "Variable" | None + + @staticmethod + def createEmpty() -> "InputPort": + return InputPort(None, None, None, None) + + +@_dc.dataclass +class OutputPort: + temperature: "Variable" | None + reverseTemperature: "Variable" | None + + @staticmethod + def createEmpty() -> "OutputPort": + return OutputPort(None, None) + + +@_dc.dataclass +class Variable: + description: str + role: str + roleOrder: int diff --git a/trnsysGUI/proforma/templates/ddck.jinja b/trnsysGUI/proforma/templates/ddck.jinja index 1c472fd0..cf1a50dd 100644 --- a/trnsysGUI/proforma/templates/ddck.jinja +++ b/trnsysGUI/proforma/templates/ddck.jinja @@ -1,11 +1,13 @@ +{% set inputsWithHydraulicData = (parameters + inputs)|rejectattr("hydraulicConnectionsData", "none")|list -%} +{% if inputsWithHydraulicData|length -%} *********************************** ** Inputs from hydraulic solver *********************************** -{% set inputsWithHydraulicData = (parameters + inputs)|rejectattr("hydraulicConnectionsData", "none")|list -%} EQUATIONS {{inputsWithHydraulicData|length}} -{% for input in inputsWithHydraulicData -%} - {{input.hydraulicConnectionsData.variableName}} = {{input.hydraulicConnectionsData.rhs}} -{% endfor %} + {% for input in inputsWithHydraulicData -%} + {{input.hydraulicConnectionsData.variableName}} = {{input.hydraulicConnectionsData.rhs}} + {% endfor %} +{% endif -%} UNIT 1 TYPE {{type}} PARAMETERS {{parameters|length}} {% for parameter in parameters -%} @@ -28,19 +30,24 @@ INPUTS {{inputs|length}} {{input.defaultValue}} ! {{input.roleOrder}}: {{input.tmfName}} - initial value {% endfor %} {% set outputsWithHydraulicData = outputs|rejectattr("hydraulicConnectionsData", "none")|list -%} -EQUATIONS {{outputsWithHydraulicData|length}} ! {{outputs|length}} +{% if outputsWithHydraulicData|length -%} + EQUATIONS {{outputsWithHydraulicData|length}} ! {{outputs|length}} +{% else -%} + ! EQUATIONS {{outputs|length}} +{% endif -%} {% for output in outputs -%} {% if output.hydraulicConnectionsData -%} {{output.hydraulicConnectionsData.variableName}} = [1, {{output.roleOrder}}] ! {{output.tmfName}} [{{output.unit}}] ({{output.bounds}}) {% else -%} ! XXX = [1, {{output.roleOrder}}] ! {{output.tmfName}} [{{output.unit}}] ({{output.bounds}}) {% endif -%} -{% endfor %} +{% endfor -%} +{% if outputsWithHydraulicData|length %} *********************************** ** Outputs to hydraulic solver *********************************** -{% for output in outputs -%} - {% if output.hydraulicConnectionsData -%} - {{output.hydraulicConnectionsData.rhs}} = {{output.hydraulicConnectionsData.variableName}} - {% endif -%} -{% endfor -%} + {% for outputWithHydraulicData in outputsWithHydraulicData -%} + {{outputWithHydraulicData.hydraulicConnectionsData.rhs}} = {{outputWithHydraulicData.hydraulicConnectionsData.variableName}} + {% endfor -%} +{% endif -%} + diff --git a/trnsysGUI/pytrnsys-gui.pyproject b/trnsysGUI/pytrnsys-gui.pyproject index e315ed35..986a8950 100644 --- a/trnsysGUI/pytrnsys-gui.pyproject +++ b/trnsysGUI/pytrnsys-gui.pyproject @@ -1,3 +1,3 @@ { - "files": ["resources/resources.qrc","hydraulicLoops/_dialogs/edit/_dialog.ui","dialogs/connections/_doublePipe.ui","hydraulicLoops/_dialogs/split/_dialog.ui","hydraulicLoops/_dialogs/merge/_dialog.ui","pumpsAndTaps/_dialog.ui"] + "files": ["proforma/_dialogs/_connections.ui","hydraulicLoops/_dialogs/merge/_dialog.ui","resources/resources.qrc","hydraulicLoops/_dialogs/split/_dialog.ui","hydraulicLoops/_dialogs/edit/_dialog.ui","pumpsAndTaps/_dialog.ui","dialogs/connections/_doublePipe.ui","proforma/_dialogs/_connection.ui"] } From 0ad432f4db747e89eb9e878612cc45c4704489af Mon Sep 17 00:00:00 2001 From: Damian Birchler Date: Wed, 24 Jul 2024 15:07:45 +0200 Subject: [PATCH 09/33] Use model in both cases. "Both" meaning when connection info is in proforma and when it is given by the user/the GUI. --- trnsysGUI/proforma/_dialogs/_connection.ui | 286 ---------------- trnsysGUI/proforma/_dialogs/_connections.ui | 95 ------ trnsysGUI/proforma/_dialogs/_convert.ui | 312 ++++++++++++++++++ ...{connectionsDialog.py => convertDialog.py} | 19 +- trnsysGUI/proforma/convertXmlTmfToDdck.py | 126 +++---- trnsysGUI/proforma/createModelConnections.py | 117 +++++++ trnsysGUI/proforma/modelConnection.py | 52 +++ trnsysGUI/proforma/models.py | 42 --- trnsysGUI/pytrnsys-gui.pyproject | 2 +- 9 files changed, 554 insertions(+), 497 deletions(-) delete mode 100644 trnsysGUI/proforma/_dialogs/_connection.ui delete mode 100644 trnsysGUI/proforma/_dialogs/_connections.ui create mode 100644 trnsysGUI/proforma/_dialogs/_convert.ui rename trnsysGUI/proforma/_dialogs/{connectionsDialog.py => convertDialog.py} (83%) create mode 100644 trnsysGUI/proforma/createModelConnections.py create mode 100644 trnsysGUI/proforma/modelConnection.py delete mode 100644 trnsysGUI/proforma/models.py diff --git a/trnsysGUI/proforma/_dialogs/_connection.ui b/trnsysGUI/proforma/_dialogs/_connection.ui deleted file mode 100644 index 1693ca2f..00000000 --- a/trnsysGUI/proforma/_dialogs/_connection.ui +++ /dev/null @@ -1,286 +0,0 @@ - - - Dialog - - - - 0 - 0 - 382 - 319 - - - - Dialog - - - - - - Hydraulic connection - - - - - - - - - false - - - - - - - Name - - - - - - - - - - - 0 - 0 - - - - - 20 - 20 - - - - - 0 - 0 - - - - <html><head/><body><p>If your component has only one hydraulic connection you can typically leave this uncheked.</p></body></html> - - - image: url(:/tango-icons/status/tango-icon-theme-0.8.90/scalable/status/dialog-information.svg); - - - - - - - Mass flow rate - - - - - - - - - - - - - false - - - - - - - Fluid density - - - - - - - - - - - 0 - 0 - - - - - 20 - 20 - - - - - 0 - 0 - - - - <html><head/><body><p>Not all types need this. If it's not needed by your type, uncheck the box at the very left of this line.</p></body></html> - - - image: url(:/tango-icons/status/tango-icon-theme-0.8.90/scalable/status/dialog-information.svg); - - - - - - - - - - false - - - - - - - Fluid heat capacity - - - - - - - - - - - 0 - 0 - - - - - 20 - 20 - - - - - 0 - 0 - - - - <html><head/><body><p>Not all types need this. If it's not needed by your type, uncheck the box at the very left of this line.</p></body></html> - - - image: url(:/tango-icons/status/tango-icon-theme-0.8.90/scalable/status/dialog-information.svg); - - - - - - - Input temperature - - - - - - - - - - - - - - - - - Reverse flow input temperature - - - - - - - - - - - 0 - 0 - - - - - 20 - 20 - - - - - 0 - 0 - - - - <html><head/><body><p>The input temperature for when the mass flow rate is negative. Not all types can handle reversible flows, this input is not required by all types.</p></body></html> - - - image: url(:/tango-icons/status/tango-icon-theme-0.8.90/scalable/status/dialog-information.svg); - - - - - - - Output temperature - - - - - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - - Qt::Vertical - - - - 20 - 1 - - - - - - - - - diff --git a/trnsysGUI/proforma/_dialogs/_connections.ui b/trnsysGUI/proforma/_dialogs/_connections.ui deleted file mode 100644 index 2ea65c2a..00000000 --- a/trnsysGUI/proforma/_dialogs/_connections.ui +++ /dev/null @@ -1,95 +0,0 @@ - - - HydraulicConnections - - - - 0 - 0 - 328 - 325 - - - - Define hydraulic connections - - - - - - Hydraulic connections - - - - - - - - - - - Qt::Horizontal - - - - 18 - 20 - - - - - - - - Add... - - - - - - - Edit... - - - - - - - Remove - - - - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - - - diff --git a/trnsysGUI/proforma/_dialogs/_convert.ui b/trnsysGUI/proforma/_dialogs/_convert.ui new file mode 100644 index 00000000..81f966ba --- /dev/null +++ b/trnsysGUI/proforma/_dialogs/_convert.ui @@ -0,0 +1,312 @@ + + + HydraulicConnections + + + + 0 + 0 + 644 + 671 + + + + Convert Proforma to ddck file... + + + + + + + + <html><head/><body><p>Note: Fields in <span style=" font-weight:700;">bold</span> are required. You can hover over light bulbs (<img src=":/tango-icons/status/tango-icon-theme-0.8.90/scalable/status/dialog-information.svg" height="20" widht="20"/>) and disabled buttons to get more information.</p></body></html> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + true + + + + Created file name: + + + + + + + + + + .ddck + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Hydraulic connections + + + + + + + + + + + + + + + Hydraulic connection "Evap" + + + + + + Fluid + + + + + + Density + + + + + + + + + + Heat capacity + + + + + + + + + + + + + Inlet port "In" + + + + + + + true + + + + Mass flow rate + + + + + + + + + + + true + + + + Temperature + + + + + + + + + + + + + Outlet port "Out" + + + + + + Reverse flow temperature + + + + + + + + + + + + + + true + + + + Temperature + + + + + + + + 0 + 0 + + + + + 20 + 20 + + + + + 0 + 0 + + + + <html><head/><body><p>The inlet temperature at the nominal output when the direction of the mass flow is the reverse of the nominal direction. Not all types support reversing the mass flow. If your type doens't support it, you can leave this field empty.</p></body></html> + + + image: url(:/tango-icons/status/tango-icon-theme-0.8.90/scalable/status/dialog-information.svg); + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Summary + + + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + diff --git a/trnsysGUI/proforma/_dialogs/connectionsDialog.py b/trnsysGUI/proforma/_dialogs/convertDialog.py similarity index 83% rename from trnsysGUI/proforma/_dialogs/connectionsDialog.py rename to trnsysGUI/proforma/_dialogs/convertDialog.py index b4bd4ac7..8f4e700f 100644 --- a/trnsysGUI/proforma/_dialogs/connectionsDialog.py +++ b/trnsysGUI/proforma/_dialogs/convertDialog.py @@ -1,22 +1,19 @@ import collections.abc as _cabc import PyQt5.QtWidgets as _qtw -import PyQt5.QtCore as _qtc import trnsysGUI.common as _com import trnsysGUI.common.cancelled as _cancel import trnsysGUI.dialogs as _dlgs +from .. import modelConnection as _mc +_dlgs.assertThatLocalGeneratedUIModuleAndResourcesExist(__name__, moduleName="_UI_convert_generated") -from .. import models as _models - -_dlgs.assertThatLocalGeneratedUIModuleAndResourcesExist(__name__) - -from . import _UI_connections_generated as _uigen # type: ignore[import] # pylint: disable=wrong-import-position +from . import _UI_convert_generated as _uigen # type: ignore[import] # pylint: disable=wrong-import-position class _ConnectionListItem(_qtw.QListWidgetItem): - def __init__(self, connection: _models.Connection) -> None: + def __init__(self, connection: _mc.Connection) -> None: super().__init__() self.connection = connection @@ -24,11 +21,11 @@ def __init__(self, connection: _models.Connection) -> None: @staticmethod def create() -> "_ConnectionListItem": - connection = _models.Connection.createEmpty() + connection = _mc.Connection.createEmpty() return _ConnectionListItem(connection) -class ConnectionsDialog(_qtw.QDialog, _uigen.Ui_HydraulicConnections): +class ConvertDialog(_qtw.QDialog, _uigen.Ui_HydraulicConnections): def __init__(self) -> None: super().__init__() self.setupUi(self) @@ -61,8 +58,8 @@ def _getSelectedConnectionListItem(self) -> _ConnectionListItem: return selectedItem @staticmethod - def showDialogAndGetResults() -> _cancel.MaybeCancelled[_cabc.Set[_models.Connection]]: - connectionsDialog = ConnectionsDialog() + def showDialogAndGetResults() -> _cancel.MaybeCancelled[_cabc.Set[_mc.Connection]]: + connectionsDialog = ConvertDialog() returnValue = connectionsDialog.exec() if returnValue == _qtw.QDialogButtonBox.StandardButton.Cancel: diff --git a/trnsysGUI/proforma/convertXmlTmfToDdck.py b/trnsysGUI/proforma/convertXmlTmfToDdck.py index f7181d2e..eb738d4b 100644 --- a/trnsysGUI/proforma/convertXmlTmfToDdck.py +++ b/trnsysGUI/proforma/convertXmlTmfToDdck.py @@ -6,8 +6,9 @@ import jinja2 as _jj import xmlschema as _xml -from . import models as _models -from ._dialogs import connectionsDialog as _csd +from . import modelConnection as _mc +from . import createModelConnections as _cmcs +from ._dialogs import convertDialog as _cd _CONTAINING_DIR_PATH = _pl.Path(__file__).parent _SCHEMA_FILE_PATH = _CONTAINING_DIR_PATH / "xmltmf.xsd" @@ -45,8 +46,8 @@ def convertXmltmfToDdck(xmlTmfFilePath: _pl.Path, ddckFilePath: _pl.Path) -> Non class _HydraulicConnectionsData: name: str | None portName: str - variableNamePrefix: str propertyName: str + variableNamePrefix: str | None @property def variableName(self) -> str: @@ -59,19 +60,23 @@ def rhs(self) -> str: @staticmethod def createForTemperature(connectionName: str | None, portName: str) -> "_HydraulicConnectionsData": - return _HydraulicConnectionsData(connectionName, portName, "T", "temp") + return _HydraulicConnectionsData(connectionName, portName, "temp", "T") + + @staticmethod + def createForReverseTemperature(connectionName: str | None, portName: str): + return _HydraulicConnectionsData(connectionName, portName, "revtemp", None) @staticmethod def createForMassFlowRate(connectionName: str | None, portName: str) -> "_HydraulicConnectionsData": - return _HydraulicConnectionsData(connectionName, portName, "M", "mfr") + return _HydraulicConnectionsData(connectionName, portName, "mfr", "M") @staticmethod def createForFluidHeatCapacity(connectionName: str | None, portName: str) -> "_HydraulicConnectionsData": - return _HydraulicConnectionsData(connectionName, portName, "Cp", "cp") + return _HydraulicConnectionsData(connectionName, portName, "cp", "Cp") @staticmethod def createForFluidDensity(connectionName: str | None, portName: str) -> "_HydraulicConnectionsData": - return _HydraulicConnectionsData(connectionName, portName, "Rho", "rho") + return _HydraulicConnectionsData(connectionName, portName, "rho", "Rho") @_dc.dataclass @@ -105,7 +110,9 @@ def _createProcessedVariables( return processedVariables -def convertXmlTmfStringToDdck(xmlTmfContent: str) -> str: +def convertXmlTmfStringToDdck( + xmlTmfContent: str, suggestedHydraulicConnections: _cabc.Set[_mc.Connection] | None = None +) -> str: schema = _xml.XMLSchema11(_SCHEMA_FILE_PATH) try: @@ -116,10 +123,14 @@ def convertXmlTmfStringToDdck(xmlTmfContent: str) -> str: proforma: _tp.Any = schema.to_dict(xmlTmfContent) variables = proforma["variables"]["variable"] - hydraulicConnections = proforma["hydraulicConnections"]["connection"] if "hydraulicConnections" in proforma else None - if hydraulicConnections is None: - hydraulicConnections = _csd.ConnectionsDialog.showDialogAndGetResults() + if "hydraulicConnections" in proforma: + serializedHydraulicConnections = proforma["hydraulicConnections"]["connection"] + hydraulicConnections = _cmcs.createModelConnectionsFromProforma(serializedHydraulicConnections, variables) + elif suggestedHydraulicConnections is not None: + hydraulicConnections = suggestedHydraulicConnections + else: + hydraulicConnections = [] connectionsDataByOrder = _createHydraulicConnectionDataByOrder(hydraulicConnections) @@ -133,84 +144,75 @@ def convertXmlTmfStringToDdck(xmlTmfContent: str) -> str: def _createHydraulicConnectionDataByOrder( - hydraulicConnections: _cabc.Set[_models.Connection], -) -> _tp.Mapping[int, _HydraulicConnectionsData]: + hydraulicConnections: _cabc.Set[_mc.Connection], +) -> _cabc.Mapping[int, _HydraulicConnectionsData]: hydraulicConnectionDataByOrder: dict[int, _HydraulicConnectionsData] = {} for hydraulicConnection in hydraulicConnections: - dataByOrderForInput = _getHydraulicConnectionDataByOrderForInput( - hydraulicConnection.name, hydraulicConnection.inputPort - ) + connectionName = hydraulicConnection.name + inputPort = hydraulicConnection.inputPort + dataByOrderForFluid = _getHydraulicConnectionDataByOrderForFluid(connectionName, hydraulicConnection, inputPort) + + dataByOrderForInput = _getHydraulicConnectionDataByOrderForInput(connectionName, inputPort) dataByOrderForOutput = _getHydraulicConnectionDataByOrderForOutput( - hydraulicConnection.name, hydraulicConnection.outputPort + connectionName, hydraulicConnection.outputPort ) - hydraulicConnectionDataByOrder |= dataByOrderForInput | dataByOrderForOutput + hydraulicConnectionDataByOrder |= dataByOrderForFluid | dataByOrderForInput | dataByOrderForOutput return hydraulicConnectionDataByOrder +def _getHydraulicConnectionDataByOrderForFluid(connectionName, hydraulicConnection, inputPort): + dataByOrderForFluid = {} + heatCapacityVariable = hydraulicConnection.fluid.heatCapacity + if heatCapacityVariable: + dataByOrderForFluid[heatCapacityVariable.order] = _HydraulicConnectionsData.createForFluidHeatCapacity( + connectionName, inputPort.name + ) + densityVariable = hydraulicConnection.fluid.density + if densityVariable: + dataByOrderForFluid[densityVariable.order] = _HydraulicConnectionsData.createForFluidDensity( + connectionName, inputPort.name + ) + return dataByOrderForFluid + + def _getHydraulicConnectionDataByOrderForInput( - connectionName: str, inputPort: _models.InputPort + connectionName: str, inputPort: _mc.InputPort ) -> dict[int, _HydraulicConnectionsData]: portName = inputPort.name mfrOrder = inputPort.massFlowRate.order - tempOrder = _getOrder(inputPort["temperature"]) - - densityVariableReference = inputPort.get("fluidDensity") - densityOrder = _getOrder(densityVariableReference) if densityVariableReference else None + tempOrder = inputPort.temperature.order - heatCapacityReference = inputPort.get("fluidHeatCapacity") - heatCapacityOrder = _getOrder(heatCapacityReference) if heatCapacityReference else None - - hydraulicConnectionDataByOrder = { + hydraulicConnectionData = { mfrOrder: _HydraulicConnectionsData.createForMassFlowRate(connectionName, portName), tempOrder: _HydraulicConnectionsData.createForTemperature(connectionName, portName), } - if densityOrder: - hydraulicConnectionDataByOrder[densityOrder] = _HydraulicConnectionsData.createForFluidDensity( - connectionName, portName - ) - - if heatCapacityOrder: - hydraulicConnectionDataByOrder[heatCapacityOrder] = _HydraulicConnectionsData.createForFluidHeatCapacity( - connectionName, portName - ) - - return hydraulicConnectionDataByOrder + return hydraulicConnectionData def _getHydraulicConnectionDataByOrderForOutput( connectionName: str, - outputPort: _StringMapping, + outputPort: _mc.OutputPort, ) -> _tp.Mapping[int, _HydraulicConnectionsData]: - portName = outputPort["@name"] - tempOrder = _getOrder(outputPort["temperature"]) - hydraulicConnectionData = _HydraulicConnectionsData.createForTemperature(connectionName, portName) - - return {tempOrder: hydraulicConnectionData} - - -def _getOrder(variableReference: _StringMapping) -> int: - return variableReference["variableReference"]["order"] + portName = outputPort.name + tempOrder = outputPort.temperature.order + revTempOrder = outputPort.reverseTemperature.order if outputPort.reverseTemperature else None + hydraulicConnectionData = { + tempOrder: _HydraulicConnectionsData.createForTemperature(connectionName, portName), + } -def _getVariable(port: _StringMapping, tag: str, variablesByOrder: _tp.Mapping[int, _StringMapping]) -> _StringMapping: - variable = _getOptionalVariable(port, tag, variablesByOrder) - portName = port["@name"] - assert variable, f"No associated `{tag}` variable for port {portName}" - return variable + if revTempOrder is not None: + hydraulicConnectionData[revTempOrder] = _HydraulicConnectionsData.createForReverseTemperature( + connectionName, portName + ) + return hydraulicConnectionData -def _getOptionalVariable( - port, tag: str, variablesByOrder: _tp.Mapping[int, _StringMapping] -) -> _tp.Optional[_StringMapping]: - child = port.get(tag) - if not child: - return None - order = child["variableReference"]["order"] - variable = variablesByOrder[order] - return variable +def _getOrder(variableReference: _StringMapping) -> int: + return variableReference["variableReference"]["order"] def _getVariablesWithRole(role: str, variables: _tp.Sequence[_StringMapping]) -> _tp.Sequence[_StringMapping]: diff --git a/trnsysGUI/proforma/createModelConnections.py b/trnsysGUI/proforma/createModelConnections.py new file mode 100644 index 00000000..933686aa --- /dev/null +++ b/trnsysGUI/proforma/createModelConnections.py @@ -0,0 +1,117 @@ +import collections.abc as _cabc +import typing as _tp + +import pytrnsys.utils.result as _res + +import trnsysGUI.internalPiping as _ip +import trnsysGUI.massFlowSolver.networkModel as _mfn + +from . import modelConnection as _mc + + +def createModelConnectionsFromInternalPiping( + internalPiping: _ip.InternalPiping, +) -> _res.Result[_cabc.Set[_mc.Connection]]: + connections = set() + for node in internalPiping.nodes: + if not isinstance(node, _mfn.Pipe): + return _res.Error(f"`{node.name}` is a `{node.__name__}`, but only direct pipes are supported.") + pipe = node + + inputPort = _mc.InputPort(pipe.fromPort.name) + outputPort = _mc.OutputPort(pipe.toPort.name) + + connection = _mc.Connection(pipe.name, inputPort, outputPort) + + connections.add(connection) + + return connections + + +_StringMapping = _tp.Mapping[str, _tp.Any] +_SerializedVariable = _StringMapping + + +def _createVariable(serializedVariable: _SerializedVariable) -> _mc.Variable: + variable = _mc.Variable( + serializedVariable["name"], serializedVariable.get("definition"), serializedVariable["order"] + ) + + return variable + + +def _createVariablesByOrder(serializedVariables: _cabc.Set[_SerializedVariable]) -> _cabc.Mapping[int, _mc.Variable]: + variables = [_createVariable(s) for s in serializedVariables] + variablesByOrder = {v.order: v for v in variables} + return variablesByOrder + + +def createModelConnectionsFromProforma( + serializedConnections: _cabc.Sequence[_StringMapping], serializedVariables: _cabc.Set[_StringMapping] +) -> _res.Result[_cabc.Set[_mc.Connection]]: + variablesByOrder = _createVariablesByOrder(serializedVariables) + + connections = {_createConnection(s, variablesByOrder) for s in serializedConnections} + + return connections + + +def _createConnection( + serializedConnection: _StringMapping, variablesByOrder: _cabc.Mapping[int, _mc.Variable] +) -> _mc.Connection: + serializedInputPort = serializedConnection["input"] + inputPort = _createInputPort(serializedInputPort, variablesByOrder) + + outputPort = _createOutputPort(serializedConnection, variablesByOrder) + + name = serializedConnection.get("@name") + + fluidDensityVariable = _getOptionalVariable(serializedInputPort, "fluidDensity", variablesByOrder) + fluidHeatCapacityVariable = _getOptionalVariable(serializedInputPort, "fluidHeatCapacity", variablesByOrder) + fluid = _mc.Fluid(fluidDensityVariable, fluidHeatCapacityVariable) + + connection = _mc.Connection(name, inputPort, outputPort, fluid) + + return connection + + +def _createInputPort( + serializedInputPort: _StringMapping, variablesByOrder: _cabc.Mapping[int, _mc.Variable] +) -> _mc.InputPort: + name = serializedInputPort["@name"] + massFlowRateVariable = _getVariable(serializedInputPort, "massFlowRate", variablesByOrder) + temperatureVariable = _getVariable(serializedInputPort, "temperature", variablesByOrder) + inputPort = _mc.InputPort(name, temperatureVariable, massFlowRateVariable) + return inputPort + + +def _createOutputPort( + serializedConnection: _StringMapping, variablesByOrder: _cabc.Mapping[int, _mc.Variable] +) -> _mc.OutputPort: + serializedOutputPort = serializedConnection["output"] + name = serializedOutputPort["@name"] + temperature = _getVariable(serializedOutputPort, "temperature", variablesByOrder) + reverseTemperature = _getOptionalVariable(serializedOutputPort, "reverseTemperature", variablesByOrder) + outputPort = _mc.OutputPort(name, temperature, reverseTemperature) + return outputPort + + +def _getVariable( + serializedPort: _StringMapping, variableName: str, variablesByOrder: _cabc.Mapping[int, _mc.Variable] +) -> _mc.Variable: + variable = _getOptionalVariable(serializedPort, variableName, variablesByOrder) + portName = serializedPort["@name"] + assert variable, f"No associated `{variableName}` variable for port {portName}" + return variable + + +def _getOptionalVariable( + serializedPort: _StringMapping, variableName: str, variablesByOrder: _cabc.Mapping[int, _mc.Variable] +) -> _tp.Optional[_mc.Variable]: + child = serializedPort.get(variableName) + if not child: + return None + + order = child["variableReference"]["order"] + variable = variablesByOrder[order] + return variable diff --git a/trnsysGUI/proforma/modelConnection.py b/trnsysGUI/proforma/modelConnection.py new file mode 100644 index 00000000..04f8efff --- /dev/null +++ b/trnsysGUI/proforma/modelConnection.py @@ -0,0 +1,52 @@ +from __future__ import annotations + +import dataclasses as _dc +import typing as _tp + + +@_dc.dataclass(frozen=True) +class Variable: + name: str + definition: str | None + order: int + + +class Unset: + pass + + +UNSET = Unset() + +RequiredVariable = _tp.Union[Variable, Unset] + + +@_dc.dataclass(frozen=True) +class Fluid: + density: Variable | None = None + heatCapacity: Variable | None = None + + @staticmethod + def empty() -> "Fluid": + return Fluid() + + +@_dc.dataclass(frozen=True) +class Connection: + name: str | None + inputPort: "InputPort" + outputPort: "OutputPort" + fluid: "Fluid" = _dc.field(default_factory=Fluid.empty) + + +@_dc.dataclass(frozen=True) +class InputPort: + name: str + temperature: "RequiredVariable" = UNSET + massFlowRate: "RequiredVariable" = UNSET + + +@_dc.dataclass(frozen=True) +class OutputPort: + name: str + temperature: "RequiredVariable" = UNSET + reverseTemperature: Variable | None = None diff --git a/trnsysGUI/proforma/models.py b/trnsysGUI/proforma/models.py deleted file mode 100644 index dac8be43..00000000 --- a/trnsysGUI/proforma/models.py +++ /dev/null @@ -1,42 +0,0 @@ -import dataclasses as _dc - - -@_dc.dataclass -class Connection: - name: str | None - inputPort: "InputPort" - outputPort: "OutputPort" - - @staticmethod - def createEmpty() -> "Connection": - return Connection(name=None, inputPort=InputPort.createEmpty(), outputPort=OutputPort.createEmpty()) - - -@_dc.dataclass -class InputPort: - name: str - temperature: "Variable" | None - massFlowRate: "Variable" | None - fluidDensity: "Variable" | None - heatCapacity: "Variable" | None - - @staticmethod - def createEmpty() -> "InputPort": - return InputPort(None, None, None, None) - - -@_dc.dataclass -class OutputPort: - temperature: "Variable" | None - reverseTemperature: "Variable" | None - - @staticmethod - def createEmpty() -> "OutputPort": - return OutputPort(None, None) - - -@_dc.dataclass -class Variable: - description: str - role: str - roleOrder: int diff --git a/trnsysGUI/pytrnsys-gui.pyproject b/trnsysGUI/pytrnsys-gui.pyproject index 986a8950..4db3aea8 100644 --- a/trnsysGUI/pytrnsys-gui.pyproject +++ b/trnsysGUI/pytrnsys-gui.pyproject @@ -1,3 +1,3 @@ { - "files": ["proforma/_dialogs/_connections.ui","hydraulicLoops/_dialogs/merge/_dialog.ui","resources/resources.qrc","hydraulicLoops/_dialogs/split/_dialog.ui","hydraulicLoops/_dialogs/edit/_dialog.ui","pumpsAndTaps/_dialog.ui","dialogs/connections/_doublePipe.ui","proforma/_dialogs/_connection.ui"] + "files": ["hydraulicLoops/_dialogs/split/_dialog.ui","pumpsAndTaps/_dialog.ui","resources/resources.qrc","hydraulicLoops/_dialogs/merge/_dialog.ui","dialogs/connections/_doublePipe.ui","hydraulicLoops/_dialogs/edit/_dialog.ui", "proforma/_dialogs/_convert.ui"] } From 547031f59c20b6581e889536982b9b4cc344ef95 Mon Sep 17 00:00:00 2001 From: Damian Birchler Date: Wed, 24 Jul 2024 16:17:27 +0200 Subject: [PATCH 10/33] User can kick off conversion from file system tree viewer. --- trnsysGUI/diagram/fileSystemTreeView.py | 17 +- trnsysGUI/proforma/_dialogs/_convert.ui | 257 ++++----------- .../_dialogs/_hydraulicConnections.ui | 308 ++++++++++++++++++ trnsysGUI/proforma/_dialogs/convertDialog.py | 71 ---- .../editHydraulicConnectionsDialog.py | 31 ++ trnsysGUI/proforma/convertXmlTmfToDdck.py | 4 +- trnsysGUI/pytrnsys-gui.pyproject | 2 +- 7 files changed, 412 insertions(+), 278 deletions(-) create mode 100644 trnsysGUI/proforma/_dialogs/_hydraulicConnections.ui delete mode 100644 trnsysGUI/proforma/_dialogs/convertDialog.py create mode 100644 trnsysGUI/proforma/_dialogs/editHydraulicConnectionsDialog.py diff --git a/trnsysGUI/diagram/fileSystemTreeView.py b/trnsysGUI/diagram/fileSystemTreeView.py index 0816c3db..7a1b1336 100644 --- a/trnsysGUI/diagram/fileSystemTreeView.py +++ b/trnsysGUI/diagram/fileSystemTreeView.py @@ -6,6 +6,8 @@ import PyQt5.QtGui as _qtg import PyQt5.QtWidgets as _qtw +import trnsysGUI.proforma.convertXmlTmfToDdck as _pro +import trnsysGUI.proforma.modelConnection as _mc import trnsysGUI.warningsAndErrors as _warn @@ -56,7 +58,9 @@ def _loadFileIntoFolder(self) -> None: return sourceFilePath = _pl.Path(sourceFilePathString) - targetFilePath = targetDirPath / sourceFilePath.name + + targetFilePathStem = targetDirPath / sourceFilePath.stem + targetFilePath = targetFilePathStem.with_suffix(".ddck") if targetFilePath.is_dir(): message = f"""\ @@ -73,7 +77,16 @@ def _loadFileIntoFolder(self) -> None: if standardButton != _qtw.QMessageBox.StandardButton.Yes: # pylint: disable=no-member return - _su.copy(sourceFilePath, targetFilePath) + if sourceFilePath.suffix != ".xmltmf": + _su.copy(sourceFilePath, targetFilePath) + else: + sourceFileContent = sourceFilePath.read_text() + suggestedHydraulicConnections = {_mc.Connection(None, _mc.InputPort("In"), _mc.OutputPort("Out"))} + targetFileContent = _pro.convertXmlTmfStringToDdck( + sourceFileContent, + suggestedHydraulicConnections, + ) + targetFilePath.write_text(targetFileContent) def _deleteCurrentFile(self) -> None: path = self._getCurrentPath() diff --git a/trnsysGUI/proforma/_dialogs/_convert.ui b/trnsysGUI/proforma/_dialogs/_convert.ui index 81f966ba..b287894c 100644 --- a/trnsysGUI/proforma/_dialogs/_convert.ui +++ b/trnsysGUI/proforma/_dialogs/_convert.ui @@ -7,7 +7,7 @@ 0 0 644 - 671 + 123 @@ -19,7 +19,7 @@ - <html><head/><body><p>Note: Fields in <span style=" font-weight:700;">bold</span> are required. You can hover over light bulbs (<img src=":/tango-icons/status/tango-icon-theme-0.8.90/scalable/status/dialog-information.svg" height="20" widht="20"/>) and disabled buttons to get more information.</p></body></html> + <html><head/><body><p>Note: You can hover overd disabled buttons to get more information.</p></body></html> Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop @@ -44,32 +44,28 @@ - + - true + false - Created file name: + Component - - - - - - .ddck - - + Qt::Horizontal + + QSizePolicy::Preferred + 40 @@ -78,196 +74,55 @@ + + + + + false + + + + Exported file path + + + + + + + 0 + + + + + + + + + 0 + 0 + + + + + 20 + 0 + + + + + 20 + 16777215 + + + + ... + + + + + - - - - Hydraulic connections - - - - - - - - - - - - - - - Hydraulic connection "Evap" - - - - - - Fluid - - - - - - Density - - - - - - - - - - Heat capacity - - - - - - - - - - - - - Inlet port "In" - - - - - - - true - - - - Mass flow rate - - - - - - - - - - - true - - - - Temperature - - - - - - - - - - - - - Outlet port "Out" - - - - - - Reverse flow temperature - - - - - - - - - - - - - - true - - - - Temperature - - - - - - - - 0 - 0 - - - - - 20 - 20 - - - - - 0 - 0 - - - - <html><head/><body><p>The inlet temperature at the nominal output when the direction of the mass flow is the reverse of the nominal direction. Not all types support reversing the mass flow. If your type doens't support it, you can leave this field empty.</p></body></html> - - - image: url(:/tango-icons/status/tango-icon-theme-0.8.90/scalable/status/dialog-information.svg); - - - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - Summary - - - - - - - - - - - - - diff --git a/trnsysGUI/proforma/_dialogs/_hydraulicConnections.ui b/trnsysGUI/proforma/_dialogs/_hydraulicConnections.ui new file mode 100644 index 00000000..53bea7ff --- /dev/null +++ b/trnsysGUI/proforma/_dialogs/_hydraulicConnections.ui @@ -0,0 +1,308 @@ + + + HydraulicConnections + + + + 0 + 0 + 644 + 684 + + + + Convert Proforma to ddck file... + + + + + + + + <html><head/><body><p>Note: Fields in <span style=" font-weight:700;">bold</span> are required. You can hover over light bulbs (<img src=":/tango-icons/status/tango-icon-theme-0.8.90/scalable/status/dialog-information.svg" height="20" widht="20"/>) and disabled buttons to get more information.</p></body></html> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Hydraulic connections + + + + + + + + + + + + + + + Hydraulic connection "Evap" + + + + + + Fluid + + + + + + Density + + + + + + + + + + Heat capacity + + + + + + + + + + + + + Inlet port "In" + + + + + + + true + + + + Mass flow rate + + + + + + + + + + + true + + + + Temperature + + + + + + + + + + + + + Outlet port "Out" + + + + + + Reverse flow temperature + + + + + + + + + + + + + + true + + + + Temperature + + + + + + + + 0 + 0 + + + + + 20 + 20 + + + + + 0 + 0 + + + + <html><head/><body><p>The inlet temperature at the nominal output when the direction of the mass flow is the reverse of the nominal direction. Not all types support reversing the mass flow. If your type doens't support it, you can leave this field empty.</p></body></html> + + + image: url(:/tango-icons/status/tango-icon-theme-0.8.90/scalable/status/dialog-information.svg); + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Advanced + + + false + + + true + + + false + + + + + + Default visibility + + + + + + + + Local + + + + + Global + + + + + + + + + + + Summary + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + diff --git a/trnsysGUI/proforma/_dialogs/convertDialog.py b/trnsysGUI/proforma/_dialogs/convertDialog.py deleted file mode 100644 index 8f4e700f..00000000 --- a/trnsysGUI/proforma/_dialogs/convertDialog.py +++ /dev/null @@ -1,71 +0,0 @@ -import collections.abc as _cabc - -import PyQt5.QtWidgets as _qtw - -import trnsysGUI.common as _com -import trnsysGUI.common.cancelled as _cancel -import trnsysGUI.dialogs as _dlgs -from .. import modelConnection as _mc - -_dlgs.assertThatLocalGeneratedUIModuleAndResourcesExist(__name__, moduleName="_UI_convert_generated") - -from . import _UI_convert_generated as _uigen # type: ignore[import] # pylint: disable=wrong-import-position - - -class _ConnectionListItem(_qtw.QListWidgetItem): - def __init__(self, connection: _mc.Connection) -> None: - super().__init__() - - self.connection = connection - self.setText(connection.name) - - @staticmethod - def create() -> "_ConnectionListItem": - connection = _mc.Connection.createEmpty() - return _ConnectionListItem(connection) - - -class ConvertDialog(_qtw.QDialog, _uigen.Ui_HydraulicConnections): - def __init__(self) -> None: - super().__init__() - self.setupUi(self) - - self.connectionsListWidget.setSelectionMode(_qtw.QListWidget.SelectionMode.SingleSelection) - self.connectionsListWidget.itemSelectionChanged.connect(self._onSelectionChanged) - - self.addConnectionButton.clicked.connect(self._onAddConnection) - self.removeConnectionButton.clicked.connect(self._onRemoveConnection) - - def _onSelectionChanged(self) -> None: - selectedItems = self.connectionsListWidget.selectedItems() - - isItemSelected = bool(selectedItems) - - self.editConnectionButton.setEnabled(isItemSelected) - self.removeConnectionButton.setEnabled(isItemSelected) - - def _onAddConnection(self) -> None: - listItem = _ConnectionListItem.create() - self.connectionsListWidget.addItem(listItem) - - def _onRemoveConnection(self) -> None: - selectedItem = self._getSelectedConnectionListItem() - self.connectionsListWidget.removeItemWidget(selectedItem) - - def _getSelectedConnectionListItem(self) -> _ConnectionListItem: - selectedItems = self.connectionsListWidget.selectedItems() - selectedItem = _com.getSingle(selectedItems) - return selectedItem - - @staticmethod - def showDialogAndGetResults() -> _cancel.MaybeCancelled[_cabc.Set[_mc.Connection]]: - connectionsDialog = ConvertDialog() - returnValue = connectionsDialog.exec() - - if returnValue == _qtw.QDialogButtonBox.StandardButton.Cancel: - return _cancel.CANCELLED - - connectionListWidget = connectionsDialog.connectionsListWidget - connections = {connectionListWidget.item(r) for r in range(connectionListWidget.count())} - - return connections diff --git a/trnsysGUI/proforma/_dialogs/editHydraulicConnectionsDialog.py b/trnsysGUI/proforma/_dialogs/editHydraulicConnectionsDialog.py new file mode 100644 index 00000000..3c7d2343 --- /dev/null +++ b/trnsysGUI/proforma/_dialogs/editHydraulicConnectionsDialog.py @@ -0,0 +1,31 @@ +import collections.abc as _cabc + +import PyQt5.QtWidgets as _qtw + +import trnsysGUI.common as _com +import trnsysGUI.common.cancelled as _cancel +import trnsysGUI.dialogs as _dlgs +from .. import modelConnection as _mc + +_dlgs.assertThatLocalGeneratedUIModuleAndResourcesExist(__name__, moduleName="_UI_hydraulicConnections_generated") + +from . import _UI_hydraulicConnections_generated as _uigen # type: ignore[import] # pylint: disable=wrong-import-position + + +class _ConnectionListItem(_qtw.QListWidgetItem): + def __init__(self, connection: _mc.Connection) -> None: + super().__init__() + + self.connection = connection + self.setText(connection.name) + + @staticmethod + def create() -> "_ConnectionListItem": + connection = _mc.Connection.createEmpty() + return _ConnectionListItem(connection) + + +class EditHydraulicConnectionsDialog(_qtw.QDialog, _uigen.Ui_HydraulicConnections): + def __init__(self) -> None: + super().__init__() + self.setupUi(self) diff --git a/trnsysGUI/proforma/convertXmlTmfToDdck.py b/trnsysGUI/proforma/convertXmlTmfToDdck.py index eb738d4b..d3f3493c 100644 --- a/trnsysGUI/proforma/convertXmlTmfToDdck.py +++ b/trnsysGUI/proforma/convertXmlTmfToDdck.py @@ -6,9 +6,8 @@ import jinja2 as _jj import xmlschema as _xml -from . import modelConnection as _mc from . import createModelConnections as _cmcs -from ._dialogs import convertDialog as _cd +from . import modelConnection as _mc _CONTAINING_DIR_PATH = _pl.Path(__file__).parent _SCHEMA_FILE_PATH = _CONTAINING_DIR_PATH / "xmltmf.xsd" @@ -39,7 +38,6 @@ def convertXmltmfToDdck(xmlTmfFilePath: _pl.Path, ddckFilePath: _pl.Path) -> Non _StringMapping = _tp.Mapping[str, _tp.Any] -_Variable = _StringMapping @_dc.dataclass diff --git a/trnsysGUI/pytrnsys-gui.pyproject b/trnsysGUI/pytrnsys-gui.pyproject index 4db3aea8..8a18ade4 100644 --- a/trnsysGUI/pytrnsys-gui.pyproject +++ b/trnsysGUI/pytrnsys-gui.pyproject @@ -1,3 +1,3 @@ { - "files": ["hydraulicLoops/_dialogs/split/_dialog.ui","pumpsAndTaps/_dialog.ui","resources/resources.qrc","hydraulicLoops/_dialogs/merge/_dialog.ui","dialogs/connections/_doublePipe.ui","hydraulicLoops/_dialogs/edit/_dialog.ui", "proforma/_dialogs/_convert.ui"] + "files": ["hydraulicLoops/_dialogs/split/_dialog.ui","pumpsAndTaps/_dialog.ui","resources/resources.qrc","hydraulicLoops/_dialogs/merge/_dialog.ui","dialogs/connections/_doublePipe.ui","hydraulicLoops/_dialogs/edit/_dialog.ui", "proforma/_dialogs/_convert.ui", "proforma/_dialogs/_hydraulicConnections.ui"] } From 6179c5caeb997270a75f28a201abfb7b11be12cf Mon Sep 17 00:00:00 2001 From: Damian Birchler Date: Wed, 24 Jul 2024 17:10:27 +0200 Subject: [PATCH 11/33] Add Type 137 proforma as test. --- .../trnsysGUI/proforma/data/actual/.gitignore | 0 .../proforma/data/expected/Type137.ddck | 78 +++++++++++++++++++ .../Type71-no-hydraulic-connections.ddck | 0 .../proforma/{ => data/expected}/Type71.ddck | 0 .../proforma/data/input/Type137.xmltmf | 2 + .../Type71-no-hydraulic-connections.xmltmf | 0 .../proforma/{ => data/input}/Type71.xmltmf | 0 tests/trnsysGUI/proforma/templates/ddck.jinja | 46 ----------- .../proforma/testConvertXmlTmfToDdck.py | 51 ++++++++---- trnsysGUI/proforma/xmltmf.xsd | 15 ++-- 10 files changed, 124 insertions(+), 68 deletions(-) create mode 100644 tests/trnsysGUI/proforma/data/actual/.gitignore create mode 100644 tests/trnsysGUI/proforma/data/expected/Type137.ddck rename tests/trnsysGUI/proforma/{ => data/expected}/Type71-no-hydraulic-connections.ddck (100%) rename tests/trnsysGUI/proforma/{ => data/expected}/Type71.ddck (100%) create mode 100644 tests/trnsysGUI/proforma/data/input/Type137.xmltmf rename tests/trnsysGUI/proforma/{ => data/input}/Type71-no-hydraulic-connections.xmltmf (100%) rename tests/trnsysGUI/proforma/{ => data/input}/Type71.xmltmf (100%) delete mode 100644 tests/trnsysGUI/proforma/templates/ddck.jinja diff --git a/tests/trnsysGUI/proforma/data/actual/.gitignore b/tests/trnsysGUI/proforma/data/actual/.gitignore new file mode 100644 index 00000000..e69de29b diff --git a/tests/trnsysGUI/proforma/data/expected/Type137.ddck b/tests/trnsysGUI/proforma/data/expected/Type137.ddck new file mode 100644 index 00000000..655a2a64 --- /dev/null +++ b/tests/trnsysGUI/proforma/data/expected/Type137.ddck @@ -0,0 +1,78 @@ +UNIT 1 TYPE 137 +PARAMETERS 20 +2.0 ! 1: Humidity Mode [-] ([2,2]) +4.19 ! 2: Cooling Fluid Specific Heat [kJ/kg.K] ([0.0,+Inf]) +4.19 ! 3: Heating Fluid Specific Heat [kJ/kg.K] ([0.0,+Inf]) +943.89 ! 4: Rated Volumetric Air Flow Rate [l/s] ([0.,+Inf]) +564.0 ! 5: Rated Fan Power [kJ/hr] ([0.,+Inf]) +10.0 ! 6: Logical Unit - Cooling Performance [-] ([10,+Inf]) +7.0 ! 7: Number of Drybulb Temperatures - Cooling [-] ([1,+Inf]) +7.0 ! 8: Number of Wetbulb Temperatures - Cooling [-] ([1,+Inf]) +6.0 ! 9: Number of Air Flows - Cooling [-] ([1,+Inf]) +8.0 ! 10: Number of Liquid Temperatures - Cooling [-] ([1,+Inf]) +2.0 ! 11: Number of Liquid Flow Rates - Cooling [-] ([1,+Inf]) +11.0 ! 12: Logical Unit - Heating Performance [-] ([10,+Inf]) +7.0 ! 13: Number of Air Temperatures - Heating [-] ([1,+Inf]) +6.0 ! 14: Number of Air Flows - Heating [-] ([1,+Inf]) +11.0 ! 15: Number of Liquid Temperatures - Heating [-] ([1,+Inf]) +2.0 ! 16: Number of Liquid Flow Rates - Heating [-] ([1,+Inf]) +12.0 ! 17: Logical Unit - Fan Corrections [-] ([10,+Inf]) +11.0 ! 18: Number of Fan Speeds [-] ([1,+Inf]) +0.9 ! 19: Efficiency of Fan Motor [-] ([0.,1.]) +1.0 ! 20: Fraction of Fan Heat to Air [-] ([0.,1.]) +INPUTS 17 +0,0 ! 1: Cooling Fluid Inlet Temperature [C] ([-Inf,+Inf]) +0,0 ! 2: Cooling Fluid Flow Rate [kg/hr] ([0.0,+Inf]) +0,0 ! 3: Heating Fluid Inlet Temperature [C] ([-Inf,+Inf]) +0,0 ! 4: Heating Fluid Flow Rate [kg/hr] ([0.0,+Inf]) +0,0 ! 5: Return Air Temperature [C] ([-Inf,+Inf]) +0,0 ! 6: Return Air Humidity Ratio [-] ([0.,+Inf]) +0,0 ! 7: Return Air % Relative Humidity [% (base 100)] ([0,100]) +0,0 ! 8: Return Air Pressure [atm] ([0.0,+Inf]) +0,0 ! 9: Air-Side Pressure Rise: Fan [atm] ([0.0,+Inf]) +0,0 ! 10: Air-Side Pressure Drop: Coils [atm] ([0.0,+Inf]) +0,0 ! 11: Fresh Air Temperature [C] ([-Inf,+Inf]) +0,0 ! 12: Fresh Air Humidity Ratio [-] ([0.,+Inf]) +0,0 ! 13: Fresh Air % Relative Humidity [% (base 100)] ([0,100]) +0,0 ! 14: Heating Control Signal [-] ([0.,1.]) +0,0 ! 15: Cooling Control Signal [-] ([0.,1.]) +0,0 ! 16: Fan Control Signal [-] ([0.,1.]) +0,0 ! 17: Fraction of Outside Air [-] ([0.,1.]) +** initial values +10.0 ! 1: Cooling Fluid Inlet Temperature - initial value +0.0 ! 2: Cooling Fluid Flow Rate - initial value +10.0 ! 3: Heating Fluid Inlet Temperature - initial value +0.0 ! 4: Heating Fluid Flow Rate - initial value +20.0 ! 5: Return Air Temperature - initial value +0.002 ! 6: Return Air Humidity Ratio - initial value +50.0 ! 7: Return Air % Relative Humidity - initial value +1.0 ! 8: Return Air Pressure - initial value +0.0 ! 9: Air-Side Pressure Rise: Fan - initial value +0.0 ! 10: Air-Side Pressure Drop: Coils - initial value +20.0 ! 11: Fresh Air Temperature - initial value +0.002 ! 12: Fresh Air Humidity Ratio - initial value +50.0 ! 13: Fresh Air % Relative Humidity - initial value +0.0 ! 14: Heating Control Signal - initial value +0.0 ! 15: Cooling Control Signal - initial value +0.0 ! 16: Fan Control Signal - initial value +0.0 ! 17: Fraction of Outside Air - initial value + +! EQUATIONS 18 +! XXX = [1, 1] ! Cooling Fluid Outlet Temperature [C] ([-Inf,+Inf]) +! XXX = [1, 2] ! Outlet Cooling Fluid Flow Rate [kg/hr] ([0.0,+Inf]) +! XXX = [1, 3] ! Heating Fluid Outlet Temperature [C] ([-Inf,+Inf]) +! XXX = [1, 4] ! Outlet Heating Fluid Flow Rate [kg/hr] ([0.0,+Inf]) +! XXX = [1, 5] ! Outlet Air Temperature [C] ([-Inf,+Inf]) +! XXX = [1, 6] ! Outlet Air Humidity Ratio [-] ([-Inf,+Inf]) +! XXX = [1, 7] ! Outlet Air % Relative Humidity [% (base 100)] ([-Inf,+Inf]) +! XXX = [1, 8] ! Outlet Air Flow Rate [kg/hr] ([-Inf,+Inf]) +! XXX = [1, 9] ! Outlet Air Pressure [atm] ([-Inf,+Inf]) +! XXX = [1, 10] ! Total Cooling Rate [kJ/hr] ([-Inf,+Inf]) +! XXX = [1, 11] ! Sensible Cooling Rate [kJ/hr] ([-Inf,+Inf]) +! XXX = [1, 12] ! Total Heating Rate [kJ/hr] ([-Inf,+Inf]) +! XXX = [1, 13] ! Fan Power [kJ/hr] ([-Inf,+Inf]) +! XXX = [1, 14] ! Fan Heat to Air Stream [kJ/hr] ([-Inf,+Inf]) +! XXX = [1, 15] ! Fan Heat to Ambient [kJ/hr] ([-Inf,+Inf]) +! XXX = [1, 16] ! Condensate Temperature [C] ([-Inf,+Inf]) +! XXX = [1, 17] ! Condensate Flow Rate [kg/hr] ([0.0,+Inf]) +! XXX = [1, 18] ! Conditioning Energy Rate [kJ/hr] ([-Inf,+Inf]) diff --git a/tests/trnsysGUI/proforma/Type71-no-hydraulic-connections.ddck b/tests/trnsysGUI/proforma/data/expected/Type71-no-hydraulic-connections.ddck similarity index 100% rename from tests/trnsysGUI/proforma/Type71-no-hydraulic-connections.ddck rename to tests/trnsysGUI/proforma/data/expected/Type71-no-hydraulic-connections.ddck diff --git a/tests/trnsysGUI/proforma/Type71.ddck b/tests/trnsysGUI/proforma/data/expected/Type71.ddck similarity index 100% rename from tests/trnsysGUI/proforma/Type71.ddck rename to tests/trnsysGUI/proforma/data/expected/Type71.ddck diff --git a/tests/trnsysGUI/proforma/data/input/Type137.xmltmf b/tests/trnsysGUI/proforma/data/input/Type137.xmltmf new file mode 100644 index 00000000..def6ee75 --- /dev/null +++ b/tests/trnsysGUI/proforma/data/input/Type137.xmltmf @@ -0,0 +1,2 @@ + +4-Pipe Fan Coil: Heating and CoolingTim McDowellThermal Energy System SpecialistsTPMv18June 2015434.\Studio\Proformas\HVAC\Fan Coil\Type137.bmp1379999Fan Coil4-Pipe Fan Coil
This component models a fan coil where the air is heated or cooled as it passes across coils containing hot and cold liquid flow streams. This model relies on user-provided external data files which contain the performance of the coils as a function of the entering air and fluid conditions. Refer to the sample data files which accompany this model for the format of these external files.
Performance Map 4-Pipe Fan Coil4-Pipe Fan Coil1Humidity ModeparameterDimensionless-integer22[ ; ]2SNThis parameter indicates whether the inputs for absolute humidity ratio (this parameter = 1) or percent relative humidity (this parameter = 2) should be used to set the inlet air conditions.2Cooling Fluid Specific HeatparameterSpecific HeatkJ/kg.Kreal0.0+Inf[ ; ]4.190SNThe specific heat of the liquid stream flowing through the fan coil cooling coils.3Heating Fluid Specific HeatparameterSpecific HeatkJ/kg.Kreal0.0+Inf[ ; ]4.190SNThe specific heat of the liquid stream flowing through the fan coil heating coils.4Rated Volumetric Air Flow RateparameterVolumetric Flow Ratel/sreal0.+Inf[ ; ]943.89SNThe volumetric flow rate of air through the device at its rated conditions.5Rated Fan PowerparameterPowerkJ/hrreal0.+Inf[ ; ]564.0SNThe fan power draw at its rated conditions.6Logical Unit - Cooling PerformanceparameterDimensionless-integer10+Inf[ ; ]10SNThe logical unit which wil be assigned to the external data file containing the total and sensible cooling load ratios as a function of the entering liquid temperature, entering liquid flow rate, the entering air dry bulb temperature, the entering air wet bulb temperature, and air flow rate. Logical units must be unique integers in each TRNSYS simulation.7Number of Drybulb Temperatures - CoolingparameterDimensionless-integer1+Inf[ ; ]7SNThe number of air drybulb temperatures for which cooling coil performance data will be provided in the user-provided external data file.8Number of Wetbulb Temperatures - CoolingparameterDimensionless-integer1+Inf[ ; ]7SNThe number of air wetbulb temperatures for which cooling coil performance data will be provided in the user-provided external data file.9Number of Air Flows - CoolingparameterDimensionless-integer1+Inf[ ; ]6SNThe number of normalized air flow rates for which cooling coil performance data will be provided in the user-provided external data file.10Number of Liquid Temperatures - CoolingparameterDimensionless-integer1+Inf[ ; ]8SNThe number of liquid (water typically) temperatures for which cooling coil performance data will be provided in the user-provided external data file.11Number of Liquid Flow Rates - CoolingparameterDimensionless-integer1+Inf[ ; ]2SNThe number of normalized liquid flow rates for which cooling coil performance data will be provided in the user-provided external data file.12Logical Unit - Heating PerformanceparameterDimensionless-integer10+Inf[ ; ]11SNThe logical unit which wil be assigned to the external data file containing the heating performance data as a function of the air inlet temperature and flow rate and the liquid entering temperature and flow rate. Logical units must be unique integers in each TRNSYS simulation.13Number of Air Temperatures - HeatingparameterDimensionless-integer1+Inf[ ; ]7SNThe number of air drybulb temperatures for which heating coil performance data will be provided in the user-provided external data file.14Number of Air Flows - HeatingparameterDimensionless-integer1+Inf[ ; ]6SNThe number of normalized air flow rates for which heating coil performance data will be provided in the user-provided external data file.15Number of Liquid Temperatures - HeatingparameterDimensionless-integer1+Inf[ ; ]11SNThe number of liquid (water typically) temperatures for which heating coil performance data will be provided in the user-provided external data file.16Number of Liquid Flow Rates - HeatingparameterDimensionless-integer1+Inf[ ; ]2SNThe number of normalized liquid flow rates for which heating coil performance data will be provided in the user-provided external data file.17Logical Unit - Fan CorrectionsparameterDimensionless-integer10+Inf[ ; ]12SNThe logical unit which wil be assigned to the external data file containing the fraction of fan full-load power as a function of the normalized fan speed.18Cooling Fluid Inlet TemperatureinputTemperatureCreal-Inf+Inf[ ; ]10.0SNThe temperature of the liquid stream fluid flowing into the fan coil unit's cooling coils.19Cooling Fluid Flow RateinputFlow Ratekg/hrreal0.0+Inf[ ; ]0.0SNThe flow rate of the cooling liquid stream fluid flowing into the fan coil unit's cooling coils.20Heating Fluid Inlet TemperatureinputTemperatureCreal-Inf+Inf[ ; ]10.0SNThe temperature of the liquid stream fluid flowing into the fan coil unit's heating coils.21Heating Fluid Flow RateinputFlow Ratekg/hrreal0.0+Inf[ ; ]0.0SNThe flow rate of the heating liquid stream fluid flowing into the fan coil unit's heating coils.22Return Air TemperatureinputTemperatureCreal-Inf+Inf[ ; ]20.0SNThe drybulb temperature of the return air entering the fan coil. This return air gets mixed with a user-specified fraction of outside air.23Return Air Humidity RatioinputDimensionless-real0.+Inf[ ; ]0.002SNThe absolute humidity ratio of the return air entering the fan coil. This return air gets mixed with a user-specified fraction of outside air.24Return Air % Relative HumidityinputPercentage% (base 100)real0100[ ; ]50.0SNThe percent relative humidity of the return air entering the fan coil. This return air gets mixed with a user-specified fraction of outside air.25Return Air PressureinputPressureatmreal0.0+Inf[ ; ]1.0SNThe absolute pressure of the air streams entering the fan coil.26Air-Side Pressure Rise: FaninputPressureatmreal0.0+Inf[ ; ]0.0SNThe pressure rise of the air stream as it flows across the fan.27Cooling Fluid Outlet TemperatureoutputTemperatureCreal-Inf+Inf[ ; ]0SNThe temperature of the liquid stream exiting the fan coil unit's cooling coils.28Outlet Cooling Fluid Flow RateoutputFlow Ratekg/hrreal0.0+Inf[ ; ]0SNThe flow rate of the liquid stream exiting the fan coil unit's cooling coils.29Heating Fluid Outlet TemperatureoutputTemperatureCreal-Inf+Inf[ ; ]0SNThe temperature of the liquid stream exiting the fan coil unit's heating coils.30Outlet Heating Fluid Flow RateoutputFlow Ratekg/hrreal0.0+Inf[ ; ]0SNThe flow rate of the liquid stream exiting the fan coil unit's heating coils.31Outlet Air TemperatureoutputTemperatureCreal-Inf+Inf[ ; ]0SNThe drybulb temperature of the air exiting the fan coil.32Outlet Air Humidity RatiooutputDimensionless-real-Inf+Inf[ ; ]0SNThe absolute humidity ratio of the air exiting the fan coil.33Outlet Air % Relative HumidityoutputPercentage% (base 100)real-Inf+Inf[ ; ]0SNThe percent relative humidity of the air exiting the fan coil.34Outlet Air Flow RateoutputFlow Ratekg/hrreal-Inf+Inf[ ; ]0SNThe flow rate of dry air exiting the fan coil.35Outlet Air PressureoutputPressureatmreal-Inf+Inf[ ; ]0SNThe absolute pressure of the air exiting the fan coil.36Total Cooling RateoutputPowerkJ/hrreal-Inf+Inf[ ; ]0SNThe rate at which energy is removed from the air stream (sensible plus latent) across the cooling coil.37Sensible Cooling RateoutputPowerkJ/hrreal-Inf+Inf[ ; ]0SNThe rate at which sensible energy is removed from the air stream across the cooling coil.38Total Heating RateoutputPowerkJ/hrreal-Inf+Inf[ ; ]0SNThe rate at which energy is added to the air stream across the heating coil.39Fan PoweroutputPowerkJ/hrreal-Inf+Inf[ ; ]0SNThe rate at which the fan consumes energy.40Fan Heat to Air StreamoutputPowerkJ/hrreal-Inf+Inf[ ; ]0SNThe rate at which energy is added to the air stream by the fan.41Fan Heat to AmbientoutputPowerkJ/hrreal-Inf+Inf[ ; ]0SNThe rate at which energy is rejected to the ambient by the fan.42Condensate TemperatureoutputTemperatureCreal-Inf+Inf[ ; ]20.0SNThe temperature of the condensed water from the air stream leaving the fan coil.43Condensate Flow RateoutputFlow Ratekg/hrreal0.0+Inf[ ; ]0SNThe rate at which condensed water from the air stream exits the fan coil.44Number of Fan SpeedsparameterDimensionless-integer1+Inf[ ; ]11SNThe number of normalized fan speeds for which fan performance data will be provided in the user-provided external data file.45Efficiency of Fan MotorparameterDimensionless-real0.1.[ ; ]0.9SNThe efficiency of the fan motor.46Fraction of Fan Heat to AirparameterDimensionless-real0.1.[ ; ]1.SNThe fraction of the fan power/heat that ends up in the air stream. Values are typically zero for fans motors mounted outside of the air stream and 1 for fan motors mounted within the air stream.47Air-Side Pressure Drop: CoilsinputPressureatmreal0.0+Inf[ ; ]0.0SNThe pressure drop of the air stream as it passes across the coils.48Fresh Air TemperatureinputTemperatureCreal-Inf+Inf[ ; ]20.0SNThe drybulb temperature of the ambient air entering the fan coil for mixing with the return air.49Fresh Air Humidity RatioinputDimensionless-real0.+Inf[ ; ]0.002SNThe absolute humidity ratio of the ambient air entering the fan coil for mixing with the return air.50Fresh Air % Relative HumidityinputPercentage% (base 100)real0100[ ; ]50.0SNThe percent relative humidity of the ambient air entering the fan coil for mixing with the return air.51Heating Control SignalinputDimensionless-real0.1.[ ; ]0.SNThe control signal for heating operation: 0 = Off and 1 = On.52Cooling Control SignalinputDimensionless-real0.1.[ ; ]0.SNThe control signal for cooling operation: 0 = Off and 1 = On.53Fan Control SignalinputDimensionless-real0.1.[ ; ]0.SNThe control signal for fan operation: 0 = Off, 1 = Full On, Values between 0 and 1 set the fraction of rated fan speed.54Fraction of Outside AirinputDimensionless-real0.1.[ ; ]0.SNThe control signal for outside air mixing: 0 = No outside air and 100% return air and 1 = 100% outside air and no return air. Values between 0 and 1 set the fraction of outside air.55Conditioning Energy RateoutputPowerkJ/hrreal-Inf+Inf[ ; ]0SNThe rate at which energy is transferred to the air stream by the coils; positive implies energy added to the air stream (heating). This term does not include energy added by the fan or energy associated with the condensate draining from the unit but is strictly a measure of the coil heat transfer.Which external file contains the cooling performance data?.\Examples\Sample Catalog Files\FanCoil_Cooling.dat.\Tess Models\SampleCatalogData\4-Pipe Performance Map Fan Coil\Normalized_FanCoil_Cooling.datLogical Unit - Cooling PerformancenoWhich external file contains the heating performance data?.\Examples\Sample Catalog Files\FanCoil_Heating.dat.\Tess Models\SampleCatalogData\4-Pipe Performance Map Fan Coil\Normalized_FanCoil_Heating.datLogical Unit - Heating PerformancenoWhich external file contains the fan performance data?.\Examples\Sample Catalog Files\FC_FanLawPerformance.dat.\Tess Models\SampleCatalogData\4-Pipe Performance Map Fan Coil\FanLawPerformance.datLogical Unit - Fan Correctionsno.\SourceCode\Types\Type137.f90
diff --git a/tests/trnsysGUI/proforma/Type71-no-hydraulic-connections.xmltmf b/tests/trnsysGUI/proforma/data/input/Type71-no-hydraulic-connections.xmltmf similarity index 100% rename from tests/trnsysGUI/proforma/Type71-no-hydraulic-connections.xmltmf rename to tests/trnsysGUI/proforma/data/input/Type71-no-hydraulic-connections.xmltmf diff --git a/tests/trnsysGUI/proforma/Type71.xmltmf b/tests/trnsysGUI/proforma/data/input/Type71.xmltmf similarity index 100% rename from tests/trnsysGUI/proforma/Type71.xmltmf rename to tests/trnsysGUI/proforma/data/input/Type71.xmltmf diff --git a/tests/trnsysGUI/proforma/templates/ddck.jinja b/tests/trnsysGUI/proforma/templates/ddck.jinja deleted file mode 100644 index 1c472fd0..00000000 --- a/tests/trnsysGUI/proforma/templates/ddck.jinja +++ /dev/null @@ -1,46 +0,0 @@ -*********************************** -** Inputs from hydraulic solver -*********************************** -{% set inputsWithHydraulicData = (parameters + inputs)|rejectattr("hydraulicConnectionsData", "none")|list -%} -EQUATIONS {{inputsWithHydraulicData|length}} -{% for input in inputsWithHydraulicData -%} - {{input.hydraulicConnectionsData.variableName}} = {{input.hydraulicConnectionsData.rhs}} -{% endfor %} -UNIT 1 TYPE {{type}} -PARAMETERS {{parameters|length}} -{% for parameter in parameters -%} - {% if parameter.hydraulicConnectionsData -%} - {{parameter.hydraulicConnectionsData.variableName}} ! {{parameter.roleOrder}}: {{parameter.tmfName}} [{{parameter.unit}}] ({{parameter.bounds}}) - {% else -%} - {{parameter.defaultValue}} ! {{parameter.roleOrder}}: {{parameter.tmfName}} [{{parameter.unit}}] ({{parameter.bounds}}) - {% endif -%} -{% endfor -%} -INPUTS {{inputs|length}} -{% for input in inputs -%} - {% if input.hydraulicConnectionsData -%} - {{input.hydraulicConnectionsData.variableName}} ! {{input.roleOrder}}: {{input.tmfName}} [{{input.unit}}] ({{input.bounds}}) - {% else -%} - 0,0 ! {{input.roleOrder}}: {{input.tmfName}} [{{input.unit}}] ({{input.bounds}}) - {% endif -%} -{% endfor -%} -** initial values -{% for input in inputs -%} - {{input.defaultValue}} ! {{input.roleOrder}}: {{input.tmfName}} - initial value -{% endfor %} -{% set outputsWithHydraulicData = outputs|rejectattr("hydraulicConnectionsData", "none")|list -%} -EQUATIONS {{outputsWithHydraulicData|length}} ! {{outputs|length}} -{% for output in outputs -%} - {% if output.hydraulicConnectionsData -%} - {{output.hydraulicConnectionsData.variableName}} = [1, {{output.roleOrder}}] ! {{output.tmfName}} [{{output.unit}}] ({{output.bounds}}) - {% else -%} - ! XXX = [1, {{output.roleOrder}}] ! {{output.tmfName}} [{{output.unit}}] ({{output.bounds}}) - {% endif -%} -{% endfor %} -*********************************** -** Outputs to hydraulic solver -*********************************** -{% for output in outputs -%} - {% if output.hydraulicConnectionsData -%} - {{output.hydraulicConnectionsData.rhs}} = {{output.hydraulicConnectionsData.variableName}} - {% endif -%} -{% endfor -%} diff --git a/tests/trnsysGUI/proforma/testConvertXmlTmfToDdck.py b/tests/trnsysGUI/proforma/testConvertXmlTmfToDdck.py index 78742f6c..be230641 100644 --- a/tests/trnsysGUI/proforma/testConvertXmlTmfToDdck.py +++ b/tests/trnsysGUI/proforma/testConvertXmlTmfToDdck.py @@ -1,25 +1,30 @@ import pathlib as _pl import pprint as _pp import pkgutil as _pu +import typing as _tp +import dataclasses as _dc + +import pytest as _pt import xmlschema as _xml import trnsysGUI.proforma.convertXmlTmfToDdck as _pc -_CONTAINING_DIR_PATH = _pl.Path(__file__).parent +_DATA_DIR_PATH = _pl.Path(__file__).parent / "data" +_INPUT_DIR_PATH = _DATA_DIR_PATH / "input" def testValidateXmlTmf() -> None: schema = _getSchema() - xmlFilePath = _CONTAINING_DIR_PATH / "Type71.xmltmf" + xmlFilePath = _INPUT_DIR_PATH / "Type71.xmltmf" schema.validate(xmlFilePath) def testDecodeXmlTmf() -> None: schema = _getSchema() - xmlFilePath = _CONTAINING_DIR_PATH / "Type71.xmltmf" + xmlFilePath = _INPUT_DIR_PATH / "Type71.xmltmf" deserializedData = schema.to_dict(xmlFilePath) _pp.pprint(deserializedData, indent=4) @@ -31,25 +36,37 @@ def _getSchema() -> _xml.XMLSchema11: return schema -def testConvertXmlTmfStringToDdck() -> None: - xmlFilePath = _CONTAINING_DIR_PATH / "Type71.xmltmf" - xmlFileContent = xmlFilePath.read_text(encoding="utf8") - - actualDdckContent = _pc.convertXmlTmfStringToDdck(xmlFileContent) +@_dc.dataclass +class TestCase: + inputFilePath: _pl.Path + actualOutputFilePath: _pl.Path + expectedOutputFilePath: _pl.Path - expectedDdckFilePath = _CONTAINING_DIR_PATH / "Type71.ddck" - expectedDdckContent = expectedDdckFilePath.read_text(encoding="utf8") + @property + def fileStem(self) -> str: + return self.inputFilePath.stem - assert actualDdckContent == expectedDdckContent + @staticmethod + def createForStem(fileNameStem: str) -> "TestCase": + inputFilePath = (_INPUT_DIR_PATH / fileNameStem).with_suffix(".xmltmf") + actualOutputFilePath = (_DATA_DIR_PATH / "actual" / fileNameStem).with_suffix(".ddck") + expectedOutputFilePath = (_DATA_DIR_PATH / "expected" / fileNameStem).with_suffix(".ddck") + testCase = TestCase(inputFilePath, actualOutputFilePath, expectedOutputFilePath) + return testCase -def testConvertXmlTmfWithoutHydraulicConnectionsStringToDdck() -> None: - xmlFilePath = _CONTAINING_DIR_PATH / "Type71-no-hydraulic-connections.xmltmf" - xmlFileContent = xmlFilePath.read_text(encoding="utf8") +def _getTestCases() -> _tp.Iterable[TestCase]: + inputDirPath = _INPUT_DIR_PATH + for inputFilePath in inputDirPath.iterdir(): + assert inputFilePath.is_file() - actualDdckContent = _pc.convertXmlTmfStringToDdck(xmlFileContent) + yield TestCase.createForStem(inputFilePath.stem) - expectedDdckFilePath = _CONTAINING_DIR_PATH / "Type71-no-hydraulic-connections.ddck" - expectedDdckContent = expectedDdckFilePath.read_text(encoding="utf8") +@_pt.mark.parametrize("testCase", [_pt.param(tc, id=tc.fileStem) for tc in _getTestCases()]) +def testConvertXmlTmfStringToDdck(testCase: TestCase) -> None: + xmlFileContent = testCase.inputFilePath.read_text(encoding="utf8") + actualDdckContent = _pc.convertXmlTmfStringToDdck(xmlFileContent) + testCase.actualOutputFilePath.write_text(actualDdckContent) + expectedDdckContent = testCase.expectedOutputFilePath.read_text(encoding="utf8") assert actualDdckContent == expectedDdckContent diff --git a/trnsysGUI/proforma/xmltmf.xsd b/trnsysGUI/proforma/xmltmf.xsd index 835811d4..d9f1227b 100644 --- a/trnsysGUI/proforma/xmltmf.xsd +++ b/trnsysGUI/proforma/xmltmf.xsd @@ -3,7 +3,7 @@ - + @@ -22,9 +22,14 @@ + + + + + - + @@ -86,9 +91,9 @@ - - - + + + From 7850733c8224ce884d67286592149879bf443c90 Mon Sep 17 00:00:00 2001 From: Damian Birchler Date: Wed, 24 Jul 2024 17:45:41 +0200 Subject: [PATCH 12/33] Start dealing with cancellation. Some minor improvements. --- .../proforma/testConvertXmlTmfToDdck.py | 2 +- trnsysGUI/diagram/fileSystemTreeView.py | 15 ++++- .../_dialogs/_hydraulicConnections.ui | 56 +++++++++---------- .../editHydraulicConnectionsDialog.py | 24 +++++++- trnsysGUI/proforma/convertXmlTmfToDdck.py | 19 ++++++- 5 files changed, 80 insertions(+), 36 deletions(-) diff --git a/tests/trnsysGUI/proforma/testConvertXmlTmfToDdck.py b/tests/trnsysGUI/proforma/testConvertXmlTmfToDdck.py index be230641..2fc13c5b 100644 --- a/tests/trnsysGUI/proforma/testConvertXmlTmfToDdck.py +++ b/tests/trnsysGUI/proforma/testConvertXmlTmfToDdck.py @@ -64,7 +64,7 @@ def _getTestCases() -> _tp.Iterable[TestCase]: @_pt.mark.parametrize("testCase", [_pt.param(tc, id=tc.fileStem) for tc in _getTestCases()]) -def testConvertXmlTmfStringToDdck(testCase: TestCase) -> None: +def testConvertXmlTmfStringToDdck(testCase: TestCase, qtbot) -> None: xmlFileContent = testCase.inputFilePath.read_text(encoding="utf8") actualDdckContent = _pc.convertXmlTmfStringToDdck(xmlFileContent) testCase.actualOutputFilePath.write_text(actualDdckContent) diff --git a/trnsysGUI/diagram/fileSystemTreeView.py b/trnsysGUI/diagram/fileSystemTreeView.py index 7a1b1336..8067fd9d 100644 --- a/trnsysGUI/diagram/fileSystemTreeView.py +++ b/trnsysGUI/diagram/fileSystemTreeView.py @@ -6,6 +6,7 @@ import PyQt5.QtGui as _qtg import PyQt5.QtWidgets as _qtw +import trnsysGUI.common.cancelled as _cancel import trnsysGUI.proforma.convertXmlTmfToDdck as _pro import trnsysGUI.proforma.modelConnection as _mc import trnsysGUI.warningsAndErrors as _warn @@ -59,8 +60,11 @@ def _loadFileIntoFolder(self) -> None: sourceFilePath = _pl.Path(sourceFilePathString) + sourceSuffix = sourceFilePath.suffix + targetSuffix = ".ddck" if sourceSuffix == ".xmltml" else sourceSuffix + targetFilePathStem = targetDirPath / sourceFilePath.stem - targetFilePath = targetFilePathStem.with_suffix(".ddck") + targetFilePath = targetFilePathStem.with_suffix(targetSuffix) if targetFilePath.is_dir(): message = f"""\ @@ -77,15 +81,20 @@ def _loadFileIntoFolder(self) -> None: if standardButton != _qtw.QMessageBox.StandardButton.Yes: # pylint: disable=no-member return - if sourceFilePath.suffix != ".xmltmf": + if sourceSuffix != ".xmltmf": _su.copy(sourceFilePath, targetFilePath) else: sourceFileContent = sourceFilePath.read_text() suggestedHydraulicConnections = {_mc.Connection(None, _mc.InputPort("In"), _mc.OutputPort("Out"))} - targetFileContent = _pro.convertXmlTmfStringToDdck( + + maybeCancelled = _pro.convertXmlTmfStringToDdck( sourceFileContent, suggestedHydraulicConnections, ) + if _cancel.isCancelled(maybeCancelled): + return + targetFileContent = maybeCancelled + targetFilePath.write_text(targetFileContent) def _deleteCurrentFile(self) -> None: diff --git a/trnsysGUI/proforma/_dialogs/_hydraulicConnections.ui b/trnsysGUI/proforma/_dialogs/_hydraulicConnections.ui index 53bea7ff..ba4ded57 100644 --- a/trnsysGUI/proforma/_dialogs/_hydraulicConnections.ui +++ b/trnsysGUI/proforma/_dialogs/_hydraulicConnections.ui @@ -7,7 +7,7 @@ 0 0 644 - 684 + 746
@@ -46,21 +46,21 @@ Hydraulic connections - - + + - - - - - - Hydraulic connection "Evap" - + + + + Hydraulic connection "Evap" + + + @@ -193,24 +193,24 @@ - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - + + + + + Qt::Horizontal + + + + 91 + 20 + + + + + +
- + Advanced @@ -249,7 +249,7 @@ - + Summary diff --git a/trnsysGUI/proforma/_dialogs/editHydraulicConnectionsDialog.py b/trnsysGUI/proforma/_dialogs/editHydraulicConnectionsDialog.py index 3c7d2343..07ad7e29 100644 --- a/trnsysGUI/proforma/_dialogs/editHydraulicConnectionsDialog.py +++ b/trnsysGUI/proforma/_dialogs/editHydraulicConnectionsDialog.py @@ -1,4 +1,6 @@ import collections.abc as _cabc +import dataclasses as _dc +import copy as _copy import PyQt5.QtWidgets as _qtw @@ -26,6 +28,26 @@ def create() -> "_ConnectionListItem": class EditHydraulicConnectionsDialog(_qtw.QDialog, _uigen.Ui_HydraulicConnections): - def __init__(self) -> None: + def __init__(self, suggestedHydraulicConnections: _cabc.Set[_mc.Connection]) -> None: super().__init__() self.setupUi(self) + + self.hydraulicConnections = self._getDeepCopiesSortedByName(suggestedHydraulicConnections) + + @staticmethod + def _getDeepCopiesSortedByName(suggestedHydraulicConnections): + hydraulicConnections = [_copy.deepcopy(c) for c in suggestedHydraulicConnections] + + def getConnectionName(connection: _mc.Connection) -> str: + return connection.name + + sortedHydraulicConnections = sorted(hydraulicConnections, key=getConnectionName) + return sortedHydraulicConnections + + @staticmethod + def showDialogAndGetResults( + suggestedHydraulicConnections: _cabc.Set[_mc.Connection], + ) -> _cancel.MaybeCancelled[_cabc.Set[_mc.Connection]]: + dialog = EditHydraulicConnectionsDialog(suggestedHydraulicConnections) + dialog.exec() + return _cancel.CANCELLED diff --git a/trnsysGUI/proforma/convertXmlTmfToDdck.py b/trnsysGUI/proforma/convertXmlTmfToDdck.py index d3f3493c..63f19e3d 100644 --- a/trnsysGUI/proforma/convertXmlTmfToDdck.py +++ b/trnsysGUI/proforma/convertXmlTmfToDdck.py @@ -6,8 +6,11 @@ import jinja2 as _jj import xmlschema as _xml +import trnsysGUI.common.cancelled as _cancel + from . import createModelConnections as _cmcs from . import modelConnection as _mc +from ._dialogs import editHydraulicConnectionsDialog as _ehcd _CONTAINING_DIR_PATH = _pl.Path(__file__).parent _SCHEMA_FILE_PATH = _CONTAINING_DIR_PATH / "xmltmf.xsd" @@ -27,10 +30,14 @@ def _createDdckJinjaTemplate() -> _jj.Template: _JINJA_TEMPLATE = _createDdckJinjaTemplate() -def convertXmltmfToDdck(xmlTmfFilePath: _pl.Path, ddckFilePath: _pl.Path) -> None: +def convertXmltmfToDdck(xmlTmfFilePath: _pl.Path, ddckFilePath: _pl.Path) -> _cancel.MaybeCancelled[None]: xmlTmfContent = xmlTmfFilePath.read_text(encoding="utf8") try: - ddckContent = convertXmlTmfStringToDdck(xmlTmfContent) + maybeCancelled = convertXmlTmfStringToDdck(xmlTmfContent) + if _cancel.isCancelled(maybeCancelled): + return _cancel.CANCELLED + + ddckContent = maybeCancelled except ValueError as exception: raise ValueError(f"Error parsing {xmlTmfFilePath}") from exception @@ -110,7 +117,7 @@ def _createProcessedVariables( def convertXmlTmfStringToDdck( xmlTmfContent: str, suggestedHydraulicConnections: _cabc.Set[_mc.Connection] | None = None -) -> str: +) -> _cancel.MaybeCancelled[str]: schema = _xml.XMLSchema11(_SCHEMA_FILE_PATH) try: @@ -130,6 +137,12 @@ def convertXmlTmfStringToDdck( else: hydraulicConnections = [] + if hydraulicConnections: + maybeCancelled = _ehcd.EditHydraulicConnectionsDialog.showDialogAndGetResults(hydraulicConnections) + if _cancel.isCancelled(maybeCancelled): + return _cancel.CANCELLED + hydraulicConnections = maybeCancelled + connectionsDataByOrder = _createHydraulicConnectionDataByOrder(hydraulicConnections) parameters = _createProcessedVariables("parameter", variables, connectionsDataByOrder) From 7b83e777b405587db51665630c53d32e7faeb6aa Mon Sep 17 00:00:00 2001 From: Damian Birchler Date: Thu, 25 Jul 2024 15:03:35 +0200 Subject: [PATCH 13/33] Fleshing out dialog some more. --- trnsysGUI/diagram/fileSystemTreeView.py | 6 +- .../_dialogs/_hydraulicConnections.ui | 6 +- .../editHydraulicConnectionsDialog.py | 66 ++++++++---- trnsysGUI/proforma/convertXmlTmfToDdck.py | 102 ++++++++++++------ trnsysGUI/proforma/createModelConnections.py | 61 ++++------- trnsysGUI/proforma/modelConnection.py | 52 --------- trnsysGUI/proforma/models.py | 77 +++++++++++++ trnsysGUI/proforma/templates/ddck.jinja | 6 +- 8 files changed, 226 insertions(+), 150 deletions(-) delete mode 100644 trnsysGUI/proforma/modelConnection.py create mode 100644 trnsysGUI/proforma/models.py diff --git a/trnsysGUI/diagram/fileSystemTreeView.py b/trnsysGUI/diagram/fileSystemTreeView.py index 8067fd9d..81c7754d 100644 --- a/trnsysGUI/diagram/fileSystemTreeView.py +++ b/trnsysGUI/diagram/fileSystemTreeView.py @@ -8,7 +8,7 @@ import trnsysGUI.common.cancelled as _cancel import trnsysGUI.proforma.convertXmlTmfToDdck as _pro -import trnsysGUI.proforma.modelConnection as _mc +import trnsysGUI.proforma.models as _models import trnsysGUI.warningsAndErrors as _warn @@ -85,7 +85,9 @@ def _loadFileIntoFolder(self) -> None: _su.copy(sourceFilePath, targetFilePath) else: sourceFileContent = sourceFilePath.read_text() - suggestedHydraulicConnections = {_mc.Connection(None, _mc.InputPort("In"), _mc.OutputPort("Out"))} + suggestedHydraulicConnections = { + _models.Connection(None, _models.InputPort("In"), _models.OutputPort("Out")) + } maybeCancelled = _pro.convertXmlTmfStringToDdck( sourceFileContent, diff --git a/trnsysGUI/proforma/_dialogs/_hydraulicConnections.ui b/trnsysGUI/proforma/_dialogs/_hydraulicConnections.ui index ba4ded57..e9268a82 100644 --- a/trnsysGUI/proforma/_dialogs/_hydraulicConnections.ui +++ b/trnsysGUI/proforma/_dialogs/_hydraulicConnections.ui @@ -6,7 +6,7 @@ 0 0 - 644 + 792 746 @@ -63,7 +63,7 @@ - + Fluid @@ -86,7 +86,7 @@ - + diff --git a/trnsysGUI/proforma/_dialogs/editHydraulicConnectionsDialog.py b/trnsysGUI/proforma/_dialogs/editHydraulicConnectionsDialog.py index 07ad7e29..ac15887b 100644 --- a/trnsysGUI/proforma/_dialogs/editHydraulicConnectionsDialog.py +++ b/trnsysGUI/proforma/_dialogs/editHydraulicConnectionsDialog.py @@ -1,44 +1,69 @@ import collections.abc as _cabc -import dataclasses as _dc import copy as _copy import PyQt5.QtWidgets as _qtw -import trnsysGUI.common as _com import trnsysGUI.common.cancelled as _cancel import trnsysGUI.dialogs as _dlgs -from .. import modelConnection as _mc +from .. import models as _models _dlgs.assertThatLocalGeneratedUIModuleAndResourcesExist(__name__, moduleName="_UI_hydraulicConnections_generated") from . import _UI_hydraulicConnections_generated as _uigen # type: ignore[import] # pylint: disable=wrong-import-position -class _ConnectionListItem(_qtw.QListWidgetItem): - def __init__(self, connection: _mc.Connection) -> None: +class EditHydraulicConnectionsDialog(_qtw.QDialog, _uigen.Ui_HydraulicConnections): + + def __init__( + self, + suggestedHydraulicConnections: _cabc.Sequence[_models.Connection], + variablesByRole: _models.VariablesByRole, + ) -> None: super().__init__() + self.setupUi(self) - self.connection = connection - self.setText(connection.name) + self._variablesByRole = variablesByRole + self.hydraulicConnections = self._getDeepCopiesSortedByName(suggestedHydraulicConnections) - @staticmethod - def create() -> "_ConnectionListItem": - connection = _mc.Connection.createEmpty() - return _ConnectionListItem(connection) + self._comboboxOptionsSeparator = object() + self._configureFluid() + self._configureInputs() + self._configureOutputs() -class EditHydraulicConnectionsDialog(_qtw.QDialog, _uigen.Ui_HydraulicConnections): - def __init__(self, suggestedHydraulicConnections: _cabc.Set[_mc.Connection]) -> None: - super().__init__() - self.setupUi(self) + def _configureFluid(self): + self._addParametersAndInputOptions(self.fluidDensityComboBox) + self._addParametersAndInputOptions(self.fluidHeatCapacityComboBox) - self.hydraulicConnections = self._getDeepCopiesSortedByName(suggestedHydraulicConnections) + def _addParametersAndInputOptions(self, comboBox): + self._addOptions(comboBox, self._variablesByRole.parameters) + comboBox.addItem("-----", self._comboboxOptionsSeparator) + self._addOptions(comboBox, self._variablesByRole.inputs, withUnset=False) + + def _configureInputs(self) -> None: + self._addOptions(self.massFlowRateComboBox, self._variablesByRole.inputs) + self._addOptions(self.inputTempComboBox, self._variablesByRole.inputs) + + def _configureOutputs(self) -> None: + self._addOptions(self.outputTempComboBox, self._variablesByRole.outputs) + self._addOptions(self.outputRevTempComboBox, self._variablesByRole.inputs) + + @staticmethod + def _addOptions( + comboBox: _qtw.QComboBox, variables: _cabc.Sequence[_models.Variable], withUnset: bool = True + ) -> None: + if withUnset: + comboBox.addItem("", _models.UNSET) + + for variable in variables: + text = variable.getInfo(withRole=True) + comboBox.addItem(text, variable) @staticmethod def _getDeepCopiesSortedByName(suggestedHydraulicConnections): hydraulicConnections = [_copy.deepcopy(c) for c in suggestedHydraulicConnections] - def getConnectionName(connection: _mc.Connection) -> str: + def getConnectionName(connection: _models.Connection) -> str: return connection.name sortedHydraulicConnections = sorted(hydraulicConnections, key=getConnectionName) @@ -46,8 +71,9 @@ def getConnectionName(connection: _mc.Connection) -> str: @staticmethod def showDialogAndGetResults( - suggestedHydraulicConnections: _cabc.Set[_mc.Connection], - ) -> _cancel.MaybeCancelled[_cabc.Set[_mc.Connection]]: - dialog = EditHydraulicConnectionsDialog(suggestedHydraulicConnections) + suggestedHydraulicConnections: _cabc.Sequence[_models.Connection], + variablesByRole: _models.VariablesByRole, + ) -> _cancel.MaybeCancelled[_cabc.Sequence[_models.Connection]]: + dialog = EditHydraulicConnectionsDialog(suggestedHydraulicConnections, variablesByRole) dialog.exec() return _cancel.CANCELLED diff --git a/trnsysGUI/proforma/convertXmlTmfToDdck.py b/trnsysGUI/proforma/convertXmlTmfToDdck.py index 63f19e3d..d13a2f87 100644 --- a/trnsysGUI/proforma/convertXmlTmfToDdck.py +++ b/trnsysGUI/proforma/convertXmlTmfToDdck.py @@ -7,10 +7,10 @@ import xmlschema as _xml import trnsysGUI.common.cancelled as _cancel - from . import createModelConnections as _cmcs -from . import modelConnection as _mc +from . import models as _models from ._dialogs import editHydraulicConnectionsDialog as _ehcd +from .models import VariablesByRole _CONTAINING_DIR_PATH = _pl.Path(__file__).parent _SCHEMA_FILE_PATH = _CONTAINING_DIR_PATH / "xmltmf.xsd" @@ -85,38 +85,69 @@ def createForFluidDensity(connectionName: str | None, portName: str) -> "_Hydrau @_dc.dataclass -class _ProcessedVariable: - tmfName: str - hydraulicConnectionsData: _tp.Optional[_HydraulicConnectionsData] - roleOrder: int - unit: str - bounds: str - defaultValue: _tp.Union[float, int] +class _VariableWithHydraulicConnectionsData(_models.Variable): + hydraulicConnectionsData: _HydraulicConnectionsData | None + + @staticmethod + def fromVariable( + variable: _models.Variable, connectionsDataByOrder: _tp.Mapping[int, _HydraulicConnectionsData] + ) -> "_VariableWithHydraulicConnectionsData": + hydraulicConnectionsData = connectionsDataByOrder.get(variable.order) + variableWithData = _VariableWithHydraulicConnectionsData( + **vars(variable), hydraulicConnectionsData=hydraulicConnectionsData + ) + return variableWithData def _createProcessedVariables( - role: str, - variables: _tp.Sequence[_StringMapping], + variables: _cabc.Sequence[_models.Variable], connectionsDataByOrder: _tp.Mapping[int, _HydraulicConnectionsData], -) -> _tp.Sequence[_ProcessedVariable]: - variables = _getVariablesWithRole(role, variables) - sortedVariables = sorted(variables, key=lambda v: v["order"]) +) -> _cabc.Sequence[_VariableWithHydraulicConnectionsData]: + + processedVariables = [ + _VariableWithHydraulicConnectionsData.fromVariable(v, connectionsDataByOrder) for v in variables + ] + + return processedVariables + + +def _createVariablesByRole( + serializedVariables: _cabc.Sequence[_StringMapping], +) -> VariablesByRole: + def getOrder(serializedVariable: _StringMapping) -> int: + return serializedVariable["order"] + + sortedSerializedVariables = sorted(serializedVariables, key=getOrder) - processedVariables = [] - for roleOrder, variable in enumerate(sortedVariables, start=1): + parameterVariables = _createVariablesForRole("parameter", sortedSerializedVariables) + inputVariables = _createVariablesForRole("input", sortedSerializedVariables) + outputVariables = _createVariablesForRole("output", sortedSerializedVariables) + + return VariablesByRole(parameterVariables, inputVariables, outputVariables) + + +_Role = _tp.Literal["parameter", "input", "output"] + + +def _createVariablesForRole( + role: _Role, sortedSerializedVariables: _cabc.Sequence[_StringMapping] +) -> _cabc.Sequence[_models.Variable]: + sortedSerializedVariablesForRole = _getSerializedVariablesWithRole(role, sortedSerializedVariables) + + variables = [] + for roleOrder, variable in enumerate(sortedSerializedVariablesForRole, start=1): order = variable["order"] - hydraulicConnectionsData = connectionsDataByOrder.get(order) bounds = _getBounds(variable) - processedVariable = _ProcessedVariable( - variable["name"], hydraulicConnectionsData, roleOrder, variable["unit"], bounds, variable["default"] + variable = _models.Variable( + variable["name"], order, role, roleOrder, variable["unit"], bounds, variable["default"] ) - processedVariables.append(processedVariable) + variables.append(variable) - return processedVariables + return variables def convertXmlTmfStringToDdck( - xmlTmfContent: str, suggestedHydraulicConnections: _cabc.Set[_mc.Connection] | None = None + xmlTmfContent: str, suggestedHydraulicConnections: _cabc.Sequence[_models.Connection] | None = None ) -> _cancel.MaybeCancelled[str]: schema = _xml.XMLSchema11(_SCHEMA_FILE_PATH) @@ -127,27 +158,32 @@ def convertXmlTmfStringToDdck( proforma: _tp.Any = schema.to_dict(xmlTmfContent) - variables = proforma["variables"]["variable"] + serializedVariables = proforma["variables"]["variable"] + variablesByRole = _createVariablesByRole(serializedVariables) if "hydraulicConnections" in proforma: serializedHydraulicConnections = proforma["hydraulicConnections"]["connection"] - hydraulicConnections = _cmcs.createModelConnectionsFromProforma(serializedHydraulicConnections, variables) + hydraulicConnections = _cmcs.createModelConnectionsFromProforma( + serializedHydraulicConnections, variablesByRole.allVariables + ) elif suggestedHydraulicConnections is not None: hydraulicConnections = suggestedHydraulicConnections else: hydraulicConnections = [] if hydraulicConnections: - maybeCancelled = _ehcd.EditHydraulicConnectionsDialog.showDialogAndGetResults(hydraulicConnections) + maybeCancelled = _ehcd.EditHydraulicConnectionsDialog.showDialogAndGetResults( + hydraulicConnections, variablesByRole + ) if _cancel.isCancelled(maybeCancelled): return _cancel.CANCELLED hydraulicConnections = maybeCancelled connectionsDataByOrder = _createHydraulicConnectionDataByOrder(hydraulicConnections) - parameters = _createProcessedVariables("parameter", variables, connectionsDataByOrder) - inputs = _createProcessedVariables("input", variables, connectionsDataByOrder) - outputs = _createProcessedVariables("output", variables, connectionsDataByOrder) + parameters = _createProcessedVariables(variablesByRole.parameters, connectionsDataByOrder) + inputs = _createProcessedVariables(variablesByRole.inputs, connectionsDataByOrder) + outputs = _createProcessedVariables(variablesByRole.outputs, connectionsDataByOrder) ddckContent = _JINJA_TEMPLATE.render(type=proforma["type"], parameters=parameters, inputs=inputs, outputs=outputs) @@ -155,7 +191,7 @@ def convertXmlTmfStringToDdck( def _createHydraulicConnectionDataByOrder( - hydraulicConnections: _cabc.Set[_mc.Connection], + hydraulicConnections: _cabc.Sequence[_models.Connection], ) -> _cabc.Mapping[int, _HydraulicConnectionsData]: hydraulicConnectionDataByOrder: dict[int, _HydraulicConnectionsData] = {} for hydraulicConnection in hydraulicConnections: @@ -188,7 +224,7 @@ def _getHydraulicConnectionDataByOrderForFluid(connectionName, hydraulicConnecti def _getHydraulicConnectionDataByOrderForInput( - connectionName: str, inputPort: _mc.InputPort + connectionName: str, inputPort: _models.InputPort ) -> dict[int, _HydraulicConnectionsData]: portName = inputPort.name mfrOrder = inputPort.massFlowRate.order @@ -204,7 +240,7 @@ def _getHydraulicConnectionDataByOrderForInput( def _getHydraulicConnectionDataByOrderForOutput( connectionName: str, - outputPort: _mc.OutputPort, + outputPort: _models.OutputPort, ) -> _tp.Mapping[int, _HydraulicConnectionsData]: portName = outputPort.name tempOrder = outputPort.temperature.order @@ -226,7 +262,9 @@ def _getOrder(variableReference: _StringMapping) -> int: return variableReference["variableReference"]["order"] -def _getVariablesWithRole(role: str, variables: _tp.Sequence[_StringMapping]) -> _tp.Sequence[_StringMapping]: +def _getSerializedVariablesWithRole( + role: _Role, variables: _tp.Sequence[_StringMapping] +) -> _tp.Sequence[_StringMapping]: return [v for v in variables if v["role"] == role] diff --git a/trnsysGUI/proforma/createModelConnections.py b/trnsysGUI/proforma/createModelConnections.py index 933686aa..3eabd0fe 100644 --- a/trnsysGUI/proforma/createModelConnections.py +++ b/trnsysGUI/proforma/createModelConnections.py @@ -6,22 +6,22 @@ import trnsysGUI.internalPiping as _ip import trnsysGUI.massFlowSolver.networkModel as _mfn -from . import modelConnection as _mc +from . import models as _models def createModelConnectionsFromInternalPiping( internalPiping: _ip.InternalPiping, -) -> _res.Result[_cabc.Set[_mc.Connection]]: +) -> _res.Result[_cabc.Set[_models.Connection]]: connections = set() for node in internalPiping.nodes: if not isinstance(node, _mfn.Pipe): return _res.Error(f"`{node.name}` is a `{node.__name__}`, but only direct pipes are supported.") pipe = node - inputPort = _mc.InputPort(pipe.fromPort.name) - outputPort = _mc.OutputPort(pipe.toPort.name) + inputPort = _models.InputPort(pipe.fromPort.name) + outputPort = _models.OutputPort(pipe.toPort.name) - connection = _mc.Connection(pipe.name, inputPort, outputPort) + connection = _models.Connection(pipe.name, inputPort, outputPort) connections.add(connection) @@ -29,36 +29,21 @@ def createModelConnectionsFromInternalPiping( _StringMapping = _tp.Mapping[str, _tp.Any] -_SerializedVariable = _StringMapping - - -def _createVariable(serializedVariable: _SerializedVariable) -> _mc.Variable: - variable = _mc.Variable( - serializedVariable["name"], serializedVariable.get("definition"), serializedVariable["order"] - ) - - return variable - - -def _createVariablesByOrder(serializedVariables: _cabc.Set[_SerializedVariable]) -> _cabc.Mapping[int, _mc.Variable]: - variables = [_createVariable(s) for s in serializedVariables] - variablesByOrder = {v.order: v for v in variables} - return variablesByOrder def createModelConnectionsFromProforma( - serializedConnections: _cabc.Sequence[_StringMapping], serializedVariables: _cabc.Set[_StringMapping] -) -> _res.Result[_cabc.Set[_mc.Connection]]: - variablesByOrder = _createVariablesByOrder(serializedVariables) + serializedConnections: _cabc.Sequence[_StringMapping], variables: _cabc.Sequence[_models.Variable] +) -> _res.Result[_cabc.Sequence[_models.Connection]]: + variablesByOrder = {v.order: v for v in variables} - connections = {_createConnection(s, variablesByOrder) for s in serializedConnections} + connections = [_createConnection(s, variablesByOrder) for s in serializedConnections] return connections def _createConnection( - serializedConnection: _StringMapping, variablesByOrder: _cabc.Mapping[int, _mc.Variable] -) -> _mc.Connection: + serializedConnection: _StringMapping, variablesByOrder: _cabc.Mapping[int, _models.Variable] +) -> _models.Connection: serializedInputPort = serializedConnection["input"] inputPort = _createInputPort(serializedInputPort, variablesByOrder) @@ -68,37 +53,37 @@ def _createConnection( fluidDensityVariable = _getOptionalVariable(serializedInputPort, "fluidDensity", variablesByOrder) fluidHeatCapacityVariable = _getOptionalVariable(serializedInputPort, "fluidHeatCapacity", variablesByOrder) - fluid = _mc.Fluid(fluidDensityVariable, fluidHeatCapacityVariable) + fluid = _models.Fluid(fluidDensityVariable, fluidHeatCapacityVariable) - connection = _mc.Connection(name, inputPort, outputPort, fluid) + connection = _models.Connection(name, inputPort, outputPort, fluid) return connection def _createInputPort( - serializedInputPort: _StringMapping, variablesByOrder: _cabc.Mapping[int, _mc.Variable] -) -> _mc.InputPort: + serializedInputPort: _StringMapping, variablesByOrder: _cabc.Mapping[int, _models.Variable] +) -> _models.InputPort: name = serializedInputPort["@name"] massFlowRateVariable = _getVariable(serializedInputPort, "massFlowRate", variablesByOrder) temperatureVariable = _getVariable(serializedInputPort, "temperature", variablesByOrder) - inputPort = _mc.InputPort(name, temperatureVariable, massFlowRateVariable) + inputPort = _models.InputPort(name, temperatureVariable, massFlowRateVariable) return inputPort def _createOutputPort( - serializedConnection: _StringMapping, variablesByOrder: _cabc.Mapping[int, _mc.Variable] -) -> _mc.OutputPort: + serializedConnection: _StringMapping, variablesByOrder: _cabc.Mapping[int, _models.Variable] +) -> _models.OutputPort: serializedOutputPort = serializedConnection["output"] name = serializedOutputPort["@name"] temperature = _getVariable(serializedOutputPort, "temperature", variablesByOrder) reverseTemperature = _getOptionalVariable(serializedOutputPort, "reverseTemperature", variablesByOrder) - outputPort = _mc.OutputPort(name, temperature, reverseTemperature) + outputPort = _models.OutputPort(name, temperature, reverseTemperature) return outputPort def _getVariable( - serializedPort: _StringMapping, variableName: str, variablesByOrder: _cabc.Mapping[int, _mc.Variable] -) -> _mc.Variable: + serializedPort: _StringMapping, variableName: str, variablesByOrder: _cabc.Mapping[int, _models.Variable] +) -> _models.Variable: variable = _getOptionalVariable(serializedPort, variableName, variablesByOrder) portName = serializedPort["@name"] assert variable, f"No associated `{variableName}` variable for port {portName}" @@ -106,8 +91,8 @@ def _getVariable( def _getOptionalVariable( - serializedPort: _StringMapping, variableName: str, variablesByOrder: _cabc.Mapping[int, _mc.Variable] -) -> _tp.Optional[_mc.Variable]: + serializedPort: _StringMapping, variableName: str, variablesByOrder: _cabc.Mapping[int, _models.Variable] +) -> _tp.Optional[_models.Variable]: child = serializedPort.get(variableName) if not child: return None diff --git a/trnsysGUI/proforma/modelConnection.py b/trnsysGUI/proforma/modelConnection.py deleted file mode 100644 index 04f8efff..00000000 --- a/trnsysGUI/proforma/modelConnection.py +++ /dev/null @@ -1,52 +0,0 @@ -from __future__ import annotations - -import dataclasses as _dc -import typing as _tp - - -@_dc.dataclass(frozen=True) -class Variable: - name: str - definition: str | None - order: int - - -class Unset: - pass - - -UNSET = Unset() - -RequiredVariable = _tp.Union[Variable, Unset] - - -@_dc.dataclass(frozen=True) -class Fluid: - density: Variable | None = None - heatCapacity: Variable | None = None - - @staticmethod - def empty() -> "Fluid": - return Fluid() - - -@_dc.dataclass(frozen=True) -class Connection: - name: str | None - inputPort: "InputPort" - outputPort: "OutputPort" - fluid: "Fluid" = _dc.field(default_factory=Fluid.empty) - - -@_dc.dataclass(frozen=True) -class InputPort: - name: str - temperature: "RequiredVariable" = UNSET - massFlowRate: "RequiredVariable" = UNSET - - -@_dc.dataclass(frozen=True) -class OutputPort: - name: str - temperature: "RequiredVariable" = UNSET - reverseTemperature: Variable | None = None diff --git a/trnsysGUI/proforma/models.py b/trnsysGUI/proforma/models.py new file mode 100644 index 00000000..70ee88ae --- /dev/null +++ b/trnsysGUI/proforma/models.py @@ -0,0 +1,77 @@ +from __future__ import annotations + +import dataclasses as _dc +import typing as _tp +from collections import abc as _cabc + + +@_dc.dataclass +class Variable: + tmfName: str + order: int + role: str + roleOrder: int + unit: str + bounds: str + defaultValue: _tp.Union[float, int] + + def getInfo(self, withRole: bool) -> str: + roleOrEmpty = f"{self.role.capitalize()} " if withRole else "" + info = f"{roleOrEmpty}{self.roleOrder}: {self.tmfName} [{self.unit}] ({self.bounds})" + return info + + @property + def info(self) -> str: + return self.getInfo(withRole=False) + + +class Unset: + pass + + +UNSET = Unset() + +RequiredVariable = _tp.Union[Variable, Unset] + + +@_dc.dataclass +class Fluid: + density: Variable | None = None + heatCapacity: Variable | None = None + + @staticmethod + def empty() -> "Fluid": + return Fluid() + + +@_dc.dataclass +class Connection: + name: str | None + inputPort: "InputPort" + outputPort: "OutputPort" + fluid: "Fluid" = _dc.field(default_factory=Fluid.empty) + + +@_dc.dataclass +class InputPort: + name: str + temperature: "RequiredVariable" = UNSET + massFlowRate: "RequiredVariable" = UNSET + + +@_dc.dataclass +class OutputPort: + name: str + temperature: "RequiredVariable" = UNSET + reverseTemperature: Variable | None = None + + +@_dc.dataclass +class VariablesByRole: + parameters: _cabc.Sequence[Variable] + inputs: _cabc.Sequence[Variable] + outputs: _cabc.Sequence[Variable] + + @property + def allVariables(self) -> _cabc.Sequence[Variable]: + return [*self.parameters, *self.inputs, *self.outputs] diff --git a/trnsysGUI/proforma/templates/ddck.jinja b/trnsysGUI/proforma/templates/ddck.jinja index cf1a50dd..664bec89 100644 --- a/trnsysGUI/proforma/templates/ddck.jinja +++ b/trnsysGUI/proforma/templates/ddck.jinja @@ -12,15 +12,15 @@ UNIT 1 TYPE {{type}} PARAMETERS {{parameters|length}} {% for parameter in parameters -%} {% if parameter.hydraulicConnectionsData -%} - {{parameter.hydraulicConnectionsData.variableName}} ! {{parameter.roleOrder}}: {{parameter.tmfName}} [{{parameter.unit}}] ({{parameter.bounds}}) + {{parameter.hydraulicConnectionsData.variableName}} ! {{parameter.info}} {% else -%} - {{parameter.defaultValue}} ! {{parameter.roleOrder}}: {{parameter.tmfName}} [{{parameter.unit}}] ({{parameter.bounds}}) + {{parameter.defaultValue}} ! {{parameter.info}} {% endif -%} {% endfor -%} INPUTS {{inputs|length}} {% for input in inputs -%} {% if input.hydraulicConnectionsData -%} - {{input.hydraulicConnectionsData.variableName}} ! {{input.roleOrder}}: {{input.tmfName}} [{{input.unit}}] ({{input.bounds}}) + {{input.hydraulicConnectionsData.variableName}} ! {{input.info}} {% else -%} 0,0 ! {{input.roleOrder}}: {{input.tmfName}} [{{input.unit}}] ({{input.bounds}}) {% endif -%} From 186c2a7e94c1c88d1c79bfbffe0416c495a12f26 Mon Sep 17 00:00:00 2001 From: Damian Birchler Date: Fri, 26 Jul 2024 16:38:20 +0200 Subject: [PATCH 14/33] Combo boxes working. --- .../proforma/testConvertXmlTmfToDdck.py | 7 +- .../_dialogs/_hydraulicConnections.ui | 2 +- .../editHydraulicConnectionsDialog.py | 207 +++++++++++++++--- trnsysGUI/proforma/models.py | 24 +- 4 files changed, 209 insertions(+), 31 deletions(-) diff --git a/tests/trnsysGUI/proforma/testConvertXmlTmfToDdck.py b/tests/trnsysGUI/proforma/testConvertXmlTmfToDdck.py index 2fc13c5b..7ae8683b 100644 --- a/tests/trnsysGUI/proforma/testConvertXmlTmfToDdck.py +++ b/tests/trnsysGUI/proforma/testConvertXmlTmfToDdck.py @@ -8,6 +8,7 @@ import xmlschema as _xml +import trnsysGUI.proforma.models as _models import trnsysGUI.proforma.convertXmlTmfToDdck as _pc _DATA_DIR_PATH = _pl.Path(__file__).parent / "data" @@ -66,7 +67,11 @@ def _getTestCases() -> _tp.Iterable[TestCase]: @_pt.mark.parametrize("testCase", [_pt.param(tc, id=tc.fileStem) for tc in _getTestCases()]) def testConvertXmlTmfStringToDdck(testCase: TestCase, qtbot) -> None: xmlFileContent = testCase.inputFilePath.read_text(encoding="utf8") - actualDdckContent = _pc.convertXmlTmfStringToDdck(xmlFileContent) + suggestedHydraulicConnections = [ + _models.Connection("Evap", _models.InputPort("In"), _models.OutputPort("Out")), + _models.Connection("Cond", _models.InputPort("In"), _models.OutputPort("Out")), + ] + actualDdckContent = _pc.convertXmlTmfStringToDdck(xmlFileContent, suggestedHydraulicConnections) testCase.actualOutputFilePath.write_text(actualDdckContent) expectedDdckContent = testCase.expectedOutputFilePath.read_text(encoding="utf8") assert actualDdckContent == expectedDdckContent diff --git a/trnsysGUI/proforma/_dialogs/_hydraulicConnections.ui b/trnsysGUI/proforma/_dialogs/_hydraulicConnections.ui index e9268a82..cc82c8b6 100644 --- a/trnsysGUI/proforma/_dialogs/_hydraulicConnections.ui +++ b/trnsysGUI/proforma/_dialogs/_hydraulicConnections.ui @@ -50,7 +50,7 @@ - + diff --git a/trnsysGUI/proforma/_dialogs/editHydraulicConnectionsDialog.py b/trnsysGUI/proforma/_dialogs/editHydraulicConnectionsDialog.py index ac15887b..c3808311 100644 --- a/trnsysGUI/proforma/_dialogs/editHydraulicConnectionsDialog.py +++ b/trnsysGUI/proforma/_dialogs/editHydraulicConnectionsDialog.py @@ -1,8 +1,10 @@ import collections.abc as _cabc import copy as _copy +import PyQt5.QtCore as _qtc import PyQt5.QtWidgets as _qtw +import trnsysGUI.common as _com import trnsysGUI.common.cancelled as _cancel import trnsysGUI.dialogs as _dlgs from .. import models as _models @@ -12,7 +14,23 @@ from . import _UI_hydraulicConnections_generated as _uigen # type: ignore[import] # pylint: disable=wrong-import-position +class _OnActivatedCallBack: + def __init__(self, dialog: "EditHydraulicConnectionsDialog", comboBox: _qtw.QComboBox) -> None: + self._dialog = dialog + self._comboBox = comboBox + + @staticmethod + def createAndConnect(dialog: "EditHydraulicConnectionsDialog", comboBox: _qtw.QComboBox) -> "_OnActivatedCallBack": + callback = _OnActivatedCallBack(dialog, comboBox) + comboBox.activated.connect(callback.onActivated) + return callback + + def onActivated(self, newIndex: int) -> None: + self._dialog.onComboBoxActivated(self._comboBox, newIndex) + + class EditHydraulicConnectionsDialog(_qtw.QDialog, _uigen.Ui_HydraulicConnections): + _DEFAULT_CONNECTION_LABEL = "" def __init__( self, @@ -25,33 +43,81 @@ def __init__( self._variablesByRole = variablesByRole self.hydraulicConnections = self._getDeepCopiesSortedByName(suggestedHydraulicConnections) - self._comboboxOptionsSeparator = object() + self._onActivatedCallbacks = list[_OnActivatedCallBack]() + + self._configureHydraulicConnections() + + self._configureComboBoxes() + + @staticmethod + def _getDeepCopiesSortedByName(suggestedHydraulicConnections): + hydraulicConnections = [_copy.deepcopy(c) for c in suggestedHydraulicConnections] + + def getConnectionName(connection: _models.Connection) -> str: + return connection.name + + sortedHydraulicConnections = sorted(hydraulicConnections, key=getConnectionName) + return sortedHydraulicConnections + + def _configureHydraulicConnections(self): + self.connectionsListWidget.setSelectionMode(_qtw.QAbstractItemView.SelectionMode.SingleSelection) + + for hydraulicConnection in self.hydraulicConnections: + name = hydraulicConnection.name if hydraulicConnection.name else self._DEFAULT_CONNECTION_LABEL + item = _qtw.QListWidgetItem(name) + item.setData(_qtc.Qt.UserRole, hydraulicConnection) + self.connectionsListWidget.addItem(item) - self._configureFluid() - self._configureInputs() - self._configureOutputs() + self.connectionsListWidget.setCurrentRow(0) - def _configureFluid(self): + self.connectionsListWidget.itemSelectionChanged.connect(self._onSelectedConnectionChanged) + + def _onSelectedConnectionChanged(self) -> None: + self._reconfigureGroupBoxLabels() + self._reloadSelectedComboBoxItems() + + def _reconfigureGroupBoxLabels(self) -> None: + selectedConnection = self._getSelectedConnection() + + connectionGroupBoxLabel = ( + f'"{selectedConnection.name}"' if selectedConnection.name else self._DEFAULT_CONNECTION_LABEL + ) + connectionGroupBoxTitle = f"Hydraulic connection {connectionGroupBoxLabel}" + self.hydraulicConnectionGroupBox.setTitle(connectionGroupBoxTitle) + + self.inletPortGroupBox.setTitle(f'Inlet port "{selectedConnection.inputPort.name}"') + + self.outletPortGroupBox.setTitle(f'Outlet port "{selectedConnection.outputPort.name}"') + + def _reconfigureComboBoxOptions(self) -> None: + self._reconfigureFluid() + self._reconfigureInputs() + self._reconfigureOutputs() + + def _reconfigureFluid(self): self._addParametersAndInputOptions(self.fluidDensityComboBox) self._addParametersAndInputOptions(self.fluidHeatCapacityComboBox) - def _addParametersAndInputOptions(self, comboBox): - self._addOptions(comboBox, self._variablesByRole.parameters) - comboBox.addItem("-----", self._comboboxOptionsSeparator) - self._addOptions(comboBox, self._variablesByRole.inputs, withUnset=False) + def _addParametersAndInputOptions(self, comboBox: _qtw.QComboBox) -> None: + self._addOptions(comboBox, self._getParameters(comboBox)) + comboBox.addItem("-----", _models.UNSET) + self._addOptions(comboBox, self._getInputs(comboBox), withUnset=False, clear=False) - def _configureInputs(self) -> None: - self._addOptions(self.massFlowRateComboBox, self._variablesByRole.inputs) - self._addOptions(self.inputTempComboBox, self._variablesByRole.inputs) + def _reconfigureInputs(self) -> None: + self._addOptions(self.massFlowRateComboBox, self._getInputs(self.massFlowRateComboBox)) + self._addOptions(self.inputTempComboBox, self._getInputs(self.inputTempComboBox)) - def _configureOutputs(self) -> None: - self._addOptions(self.outputTempComboBox, self._variablesByRole.outputs) - self._addOptions(self.outputRevTempComboBox, self._variablesByRole.inputs) + def _reconfigureOutputs(self) -> None: + self._addOptions(self.outputTempComboBox, self._getOutputs(self.outputTempComboBox)) + self._addOptions(self.outputRevTempComboBox, self._getInputs(self.outputRevTempComboBox)) @staticmethod def _addOptions( - comboBox: _qtw.QComboBox, variables: _cabc.Sequence[_models.Variable], withUnset: bool = True + comboBox: _qtw.QComboBox, variables: _cabc.Sequence[_models.Variable], withUnset: bool = True, clear: bool = True ) -> None: + if clear: + comboBox.clear() + if withUnset: comboBox.addItem("", _models.UNSET) @@ -59,15 +125,106 @@ def _addOptions( text = variable.getInfo(withRole=True) comboBox.addItem(text, variable) - @staticmethod - def _getDeepCopiesSortedByName(suggestedHydraulicConnections): - hydraulicConnections = [_copy.deepcopy(c) for c in suggestedHydraulicConnections] - - def getConnectionName(connection: _models.Connection) -> str: - return connection.name - - sortedHydraulicConnections = sorted(hydraulicConnections, key=getConnectionName) - return sortedHydraulicConnections + @property + def _selectedVariables(self) -> _cabc.Sequence[_models.Variable]: + selectedVariables = [v for c in self.hydraulicConnections for v in c.allVariables] + return selectedVariables + + def _getParameters(self, comboBox: _qtw.QComboBox) -> _cabc.Sequence[_models.Variable]: + return self._removeSelectedVariablesOfOtherComboBoxes(comboBox, self._variablesByRole.parameters) + + def _getInputs(self, comboBox: _qtw.QComboBox) -> _cabc.Sequence[_models.Variable]: + return self._removeSelectedVariablesOfOtherComboBoxes(comboBox, self._variablesByRole.inputs) + + def _getOutputs(self, comboBox: _qtw.QComboBox) -> _cabc.Sequence[_models.Variable]: + return self._removeSelectedVariablesOfOtherComboBoxes(comboBox, self._variablesByRole.outputs) + + def _removeSelectedVariablesOfOtherComboBoxes( + self, comboBox: _qtw.QComboBox, variables: _cabc.Sequence[_models.Variable] + ) -> _cabc.Sequence[_models.Variable]: + selectedVariableForComboBox = self._getVariableCorrespondingToComboBox(comboBox) + otherUnselectedVariables = [ + v for v in variables if v == selectedVariableForComboBox or v not in self._selectedVariables + ] + return otherUnselectedVariables + + def _getVariableCorrespondingToComboBox(self, comboBox: _qtw.QComboBox) -> _models.Variable | _models.Unset: + selectedConnection = self._getSelectedConnection() + + if comboBox == self.fluidDensityComboBox: + return selectedConnection.fluid.density + elif comboBox == self.fluidHeatCapacityComboBox: + return selectedConnection.fluid.heatCapacity + elif comboBox == self.massFlowRateComboBox: + return selectedConnection.inputPort.massFlowRate or _models.Unset + elif comboBox == self.inputTempComboBox: + return selectedConnection.inputPort.temperature or _models.Unset + elif comboBox == self.outputTempComboBox: + return selectedConnection.outputPort.temperature or _models.Unset + elif comboBox == self.outputRevTempComboBox: + return selectedConnection.outputPort.reverseTemperature + else: + raise AssertionError("Unknown combo box.") + + def _getSelectedConnection(self) -> _models.Connection: + selectedItems = self.connectionsListWidget.selectedItems() + selectedItem = _com.getSingle(selectedItems) + selectedConnection = selectedItem.data(_qtc.Qt.UserRole) + assert isinstance(selectedConnection, _models.Connection) + return selectedConnection + + def _configureComboBoxes(self) -> None: + for comboBox in self._comboBoxes: + onActivatedCallback = _OnActivatedCallBack.createAndConnect(self, comboBox) + self._onActivatedCallbacks.append(onActivatedCallback) + + self._reconfigureComboBoxOptions() + + def onComboBoxActivated(self, comboBox: _qtw.QComboBox, newIndex: int) -> None: + data = comboBox.itemData(newIndex) + self._setVariableCorrespondingToComboBox(comboBox, data) + self._reloadSelectedComboBoxItems() + + def _reloadSelectedComboBoxItems(self) -> None: + self._reconfigureComboBoxOptions() + + for comboBox in self._comboBoxes: + data = self._getVariableCorrespondingToComboBox(comboBox) + index = comboBox.findData(data) + comboBox.setCurrentIndex(index) + + @property + def _comboBoxes(self) -> _cabc.Sequence[_qtw.QComboBox]: + return [ + self.fluidDensityComboBox, + self.fluidHeatCapacityComboBox, + self.massFlowRateComboBox, + self.inputTempComboBox, + self.outputTempComboBox, + self.outputRevTempComboBox, + ] + + def _setVariableCorrespondingToComboBox( + self, comboBox: _qtw.QComboBox, value: _models.Variable | _models.Unset + ) -> None: + selectedConnection = self._getSelectedConnection() + + valueForOptionalVariables = value if isinstance(value, _models.Variable) else None + + if comboBox == self.fluidDensityComboBox: + selectedConnection.fluid.density = valueForOptionalVariables + elif comboBox == self.fluidHeatCapacityComboBox: + selectedConnection.fluid.heatCapacity = valueForOptionalVariables + elif comboBox == self.massFlowRateComboBox: + selectedConnection.inputPort.massFlowRate = value + elif comboBox == self.inputTempComboBox: + selectedConnection.inputPort.temperature = value + elif comboBox == self.outputTempComboBox: + selectedConnection.outputPort.temperature = value + elif comboBox == self.outputRevTempComboBox: + selectedConnection.outputPort.reverseTemperature = valueForOptionalVariables + else: + raise AssertionError("Unknown combo box.") @staticmethod def showDialogAndGetResults( diff --git a/trnsysGUI/proforma/models.py b/trnsysGUI/proforma/models.py index 70ee88ae..de46415d 100644 --- a/trnsysGUI/proforma/models.py +++ b/trnsysGUI/proforma/models.py @@ -39,9 +39,9 @@ class Fluid: density: Variable | None = None heatCapacity: Variable | None = None - @staticmethod - def empty() -> "Fluid": - return Fluid() + @property + def allVariables(self) -> _cabc.Sequence[Variable]: + return _removeUnsetAndNone(self.density, self.heatCapacity) @_dc.dataclass @@ -49,7 +49,11 @@ class Connection: name: str | None inputPort: "InputPort" outputPort: "OutputPort" - fluid: "Fluid" = _dc.field(default_factory=Fluid.empty) + fluid: "Fluid" = _dc.field(default_factory=Fluid) + + @property + def allVariables(self) -> _cabc.Sequence[Variable]: + return [*self.inputPort.allVariables, *self.outputPort.allVariables, *self.fluid.allVariables] @_dc.dataclass @@ -58,6 +62,10 @@ class InputPort: temperature: "RequiredVariable" = UNSET massFlowRate: "RequiredVariable" = UNSET + @property + def allVariables(self) -> _cabc.Sequence[Variable]: + return _removeUnsetAndNone(self.temperature, self.massFlowRate) + @_dc.dataclass class OutputPort: @@ -65,6 +73,14 @@ class OutputPort: temperature: "RequiredVariable" = UNSET reverseTemperature: Variable | None = None + @property + def allVariables(self) -> _cabc.Sequence[Variable]: + return _removeUnsetAndNone(self.temperature, self.reverseTemperature) + + +def _removeUnsetAndNone(*variables: Variable) -> _cabc.Sequence[Variable]: + return [v for v in variables if isinstance(v, Variable)] + @_dc.dataclass class VariablesByRole: From ce98f9b86e2d6c703c30c2228bea5b15bbc3a7b4 Mon Sep 17 00:00:00 2001 From: Damian Birchler Date: Mon, 29 Jul 2024 13:05:44 +0200 Subject: [PATCH 15/33] Dialog mostly implemented. --- .../editHydraulicConnectionsDialog.py | 44 ++++++- trnsysGUI/proforma/convertXmlTmfToDdck.py | 24 ++-- trnsysGUI/proforma/models.py | 115 +++++++++++++++++- 3 files changed, 166 insertions(+), 17 deletions(-) diff --git a/trnsysGUI/proforma/_dialogs/editHydraulicConnectionsDialog.py b/trnsysGUI/proforma/_dialogs/editHydraulicConnectionsDialog.py index c3808311..721949a6 100644 --- a/trnsysGUI/proforma/_dialogs/editHydraulicConnectionsDialog.py +++ b/trnsysGUI/proforma/_dialogs/editHydraulicConnectionsDialog.py @@ -41,13 +41,26 @@ def __init__( self.setupUi(self) self._variablesByRole = variablesByRole - self.hydraulicConnections = self._getDeepCopiesSortedByName(suggestedHydraulicConnections) + self.hydraulicConnections: _cancel.MaybeCancelled[_cabc.Sequence[_models.Connection]] = ( + self._getDeepCopiesSortedByName(suggestedHydraulicConnections) + ) self._onActivatedCallbacks = list[_OnActivatedCallBack]() + self._configureCancelButton() self._configureHydraulicConnections() - self._configureComboBoxes() + self.summaryTextEdit.setReadOnly(True) + + self._reloadConnections() + + def _configureCancelButton(self) -> None: + cancelButton = self.okCancelButtonBox.button(_qtw.QDialogButtonBox.StandardButton.Cancel) + cancelButton.clicked.connect(self._onCancelClicked) + + def _onCancelClicked(self) -> None: + self.hydraulicConnections = _cancel.CANCELLED + self.close() @staticmethod def _getDeepCopiesSortedByName(suggestedHydraulicConnections): @@ -178,12 +191,35 @@ def _configureComboBoxes(self) -> None: onActivatedCallback = _OnActivatedCallBack.createAndConnect(self, comboBox) self._onActivatedCallbacks.append(onActivatedCallback) - self._reconfigureComboBoxOptions() - def onComboBoxActivated(self, comboBox: _qtw.QComboBox, newIndex: int) -> None: data = comboBox.itemData(newIndex) self._setVariableCorrespondingToComboBox(comboBox, data) + self._reloadConnections() + + def _reloadConnections(self) -> None: + self._resetOkButton() self._reloadSelectedComboBoxItems() + self._reloadSummaryText() + + def _reloadSummaryText(self) -> None: + overallSummary = "\n\n".join(c.getSummary() for c in self.hydraulicConnections) + self.summaryTextEdit.setPlainText(overallSummary) + + def _resetOkButton(self) -> None: + okButton = self.okCancelButtonBox.button(_qtw.QDialogButtonBox.StandardButton.Ok) + areAnyRequiredVariablesUnset = self._areAnyRequiredVariablesUnset() + isEnabled = not areAnyRequiredVariablesUnset + okButton.setEnabled(isEnabled) + toolTip = ( + "Please specify the required properties (in bold) for all connections." + if areAnyRequiredVariablesUnset + else "" + ) + okButton.setToolTip(toolTip) + + def _areAnyRequiredVariablesUnset(self) -> bool: + areAnyRequiredVariablesUnset = any(c.areAnyRequiredVariablesUnset for c in self.hydraulicConnections) + return areAnyRequiredVariablesUnset def _reloadSelectedComboBoxItems(self) -> None: self._reconfigureComboBoxOptions() diff --git a/trnsysGUI/proforma/convertXmlTmfToDdck.py b/trnsysGUI/proforma/convertXmlTmfToDdck.py index d13a2f87..5fc25b0f 100644 --- a/trnsysGUI/proforma/convertXmlTmfToDdck.py +++ b/trnsysGUI/proforma/convertXmlTmfToDdck.py @@ -10,7 +10,6 @@ from . import createModelConnections as _cmcs from . import models as _models from ._dialogs import editHydraulicConnectionsDialog as _ehcd -from .models import VariablesByRole _CONTAINING_DIR_PATH = _pl.Path(__file__).parent _SCHEMA_FILE_PATH = _CONTAINING_DIR_PATH / "xmltmf.xsd" @@ -51,37 +50,38 @@ def convertXmltmfToDdck(xmlTmfFilePath: _pl.Path, ddckFilePath: _pl.Path) -> _ca class _HydraulicConnectionsData: name: str | None portName: str - propertyName: str - variableNamePrefix: str | None + stringConstants: _models.VariableStringConstants @property def variableName(self) -> str: capitalizedPortName = self.portName.capitalize() - return f":{self.variableNamePrefix}{capitalizedPortName}" + return f":{self.stringConstants.variableNamePrefix}{capitalizedPortName}" @property def rhs(self) -> str: - return f"@{self.propertyName}({self.portName})" + return f"@{self.stringConstants.propertyName}({self.portName})" @staticmethod def createForTemperature(connectionName: str | None, portName: str) -> "_HydraulicConnectionsData": - return _HydraulicConnectionsData(connectionName, portName, "temp", "T") + return _HydraulicConnectionsData(connectionName, portName, _models.AllVariableStringConstants.TEMPERATURE) @staticmethod def createForReverseTemperature(connectionName: str | None, portName: str): - return _HydraulicConnectionsData(connectionName, portName, "revtemp", None) + return _HydraulicConnectionsData( + connectionName, portName, _models.AllVariableStringConstants.REVERSE_TEMPERATURE + ) @staticmethod def createForMassFlowRate(connectionName: str | None, portName: str) -> "_HydraulicConnectionsData": - return _HydraulicConnectionsData(connectionName, portName, "mfr", "M") + return _HydraulicConnectionsData(connectionName, portName, _models.AllVariableStringConstants.MASS_FLOW_RATE) @staticmethod def createForFluidHeatCapacity(connectionName: str | None, portName: str) -> "_HydraulicConnectionsData": - return _HydraulicConnectionsData(connectionName, portName, "cp", "Cp") + return _HydraulicConnectionsData(connectionName, portName, _models.AllVariableStringConstants.HEAT_CAPACITY) @staticmethod def createForFluidDensity(connectionName: str | None, portName: str) -> "_HydraulicConnectionsData": - return _HydraulicConnectionsData(connectionName, portName, "rho", "Rho") + return _HydraulicConnectionsData(connectionName, portName, _models.AllVariableStringConstants.DENSITY) @_dc.dataclass @@ -113,7 +113,7 @@ def _createProcessedVariables( def _createVariablesByRole( serializedVariables: _cabc.Sequence[_StringMapping], -) -> VariablesByRole: +) -> _models.VariablesByRole: def getOrder(serializedVariable: _StringMapping) -> int: return serializedVariable["order"] @@ -123,7 +123,7 @@ def getOrder(serializedVariable: _StringMapping) -> int: inputVariables = _createVariablesForRole("input", sortedSerializedVariables) outputVariables = _createVariablesForRole("output", sortedSerializedVariables) - return VariablesByRole(parameterVariables, inputVariables, outputVariables) + return _models.VariablesByRole(parameterVariables, inputVariables, outputVariables) _Role = _tp.Literal["parameter", "input", "output"] diff --git a/trnsysGUI/proforma/models.py b/trnsysGUI/proforma/models.py index de46415d..8d33b1c5 100644 --- a/trnsysGUI/proforma/models.py +++ b/trnsysGUI/proforma/models.py @@ -1,8 +1,8 @@ from __future__ import annotations +import collections.abc as _cabc import dataclasses as _dc import typing as _tp -from collections import abc as _cabc @_dc.dataclass @@ -25,6 +25,7 @@ def info(self) -> str: return self.getInfo(withRole=False) +@_dc.dataclass(frozen=True) class Unset: pass @@ -34,6 +35,44 @@ class Unset: RequiredVariable = _tp.Union[Variable, Unset] +@_dc.dataclass +class VariableStringConstants: + propertyName: str + variableNamePrefix: str | None + + +class AllVariableStringConstants: + TEMPERATURE = VariableStringConstants("@temp", "T") + MASS_FLOW_RATE = VariableStringConstants("@mfr", "M") + REVERSE_TEMPERATURE = VariableStringConstants("@revtemp", None) + DENSITY = VariableStringConstants("@rho", "Rho") + HEAT_CAPACITY = VariableStringConstants("@cp", "Cp") + + +def _getSummaryLine( + qualifiedPortName: str, + variableStringConstants: VariableStringConstants, + variable: Variable | None | Unset, + direction: _tp.Literal["input", "output"], +) -> str: + if not variable or variable == UNSET: + return "" + + computedVariable = f"{variableStringConstants.propertyName}({qualifiedPortName})" + summaryLine = ( + f'"{variable.tmfName}" = {computedVariable}' + if direction == "input" + else f'{computedVariable} = "{variable.tmfName}"' + ) + + return summaryLine + + +def _joinNonEmptyStringsWithNewLines(*strings: str) -> str: + nonEmptyStrings = [s for s in strings if s] + return "\n".join(nonEmptyStrings) + + @_dc.dataclass class Fluid: density: Variable | None = None @@ -43,6 +82,18 @@ class Fluid: def allVariables(self) -> _cabc.Sequence[Variable]: return _removeUnsetAndNone(self.density, self.heatCapacity) + @property + def areAnyRequiredVariablesUnset(self) -> bool: + return False + + def getSummary(self, qualifiedPortName: str) -> str: + summary = _joinNonEmptyStringsWithNewLines( + _getSummaryLine(qualifiedPortName, AllVariableStringConstants.DENSITY, self.density, "input"), + _getSummaryLine(qualifiedPortName, AllVariableStringConstants.HEAT_CAPACITY, self.heatCapacity, "input"), + ) + + return summary + @_dc.dataclass class Connection: @@ -55,6 +106,40 @@ class Connection: def allVariables(self) -> _cabc.Sequence[Variable]: return [*self.inputPort.allVariables, *self.outputPort.allVariables, *self.fluid.allVariables] + @property + def areAnyRequiredVariablesUnset(self) -> bool: + return ( + self.inputPort.areAnyRequiredVariablesUnset + or self.outputPort.areAnyRequiredVariablesUnset + or self.fluid.areAnyRequiredVariablesUnset + ) + + def getSummary(self) -> str: + qualifiedInputPortName = _getQualifiedPortName(self.name, self.inputPort.name) + fluidSummary = self.fluid.getSummary(qualifiedInputPortName) + inputPortSummary = self.inputPort.getSummary(self.name) + outputPortSummary = self.outputPort.getSummary(self.name) + + subSummaries = _joinNonEmptyStringsWithNewLines(fluidSummary, inputPortSummary, outputPortSummary) + + if not subSummaries: + return "" + + summary = f"""\ +** {self.name} +{subSummaries} + +""" + return summary + + +def _getQualifiedPortName(connectionName: str | None, portName: str) -> str: + capitalizedPortName = portName.capitalize() + + qualifiedPortName = f"{connectionName.capitalize()}{capitalizedPortName}" if connectionName else capitalizedPortName + + return qualifiedPortName + @_dc.dataclass class InputPort: @@ -66,6 +151,19 @@ class InputPort: def allVariables(self) -> _cabc.Sequence[Variable]: return _removeUnsetAndNone(self.temperature, self.massFlowRate) + @property + def areAnyRequiredVariablesUnset(self) -> bool: + return self.temperature == UNSET or self.massFlowRate == UNSET + + def getSummary(self, connectionName: str | None) -> str: + qualifiedPortName = _getQualifiedPortName(connectionName, self.name) + summary = _joinNonEmptyStringsWithNewLines( + _getSummaryLine(qualifiedPortName, AllVariableStringConstants.MASS_FLOW_RATE, self.massFlowRate, "input"), + _getSummaryLine(qualifiedPortName, AllVariableStringConstants.TEMPERATURE, self.temperature, "input"), + ) + + return summary + @_dc.dataclass class OutputPort: @@ -77,6 +175,21 @@ class OutputPort: def allVariables(self) -> _cabc.Sequence[Variable]: return _removeUnsetAndNone(self.temperature, self.reverseTemperature) + @property + def areAnyRequiredVariablesUnset(self): + return self.temperature == UNSET + + def getSummary(self, connectionName: str | None) -> str: + qualifiedPortName = _getQualifiedPortName(connectionName, self.name) + summary = _joinNonEmptyStringsWithNewLines( + _getSummaryLine(qualifiedPortName, AllVariableStringConstants.TEMPERATURE, self.temperature, "output"), + _getSummaryLine( + qualifiedPortName, AllVariableStringConstants.REVERSE_TEMPERATURE, self.reverseTemperature, "input" + ), + ) + + return summary + def _removeUnsetAndNone(*variables: Variable) -> _cabc.Sequence[Variable]: return [v for v in variables if isinstance(v, Variable)] From 4e8a04c6db9e80d2db62d89c6eba204271c6e875 Mon Sep 17 00:00:00 2001 From: Damian Birchler Date: Mon, 29 Jul 2024 13:15:31 +0200 Subject: [PATCH 16/33] Fix test. --- tests/trnsysGUI/proforma/testConvertXmlTmfToDdck.py | 6 +----- .../_dialogs/editHydraulicConnectionsDialog.py | 12 ++++++++---- trnsysGUI/proforma/models.py | 12 ++++++------ 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/tests/trnsysGUI/proforma/testConvertXmlTmfToDdck.py b/tests/trnsysGUI/proforma/testConvertXmlTmfToDdck.py index 7ae8683b..123f7fde 100644 --- a/tests/trnsysGUI/proforma/testConvertXmlTmfToDdck.py +++ b/tests/trnsysGUI/proforma/testConvertXmlTmfToDdck.py @@ -67,11 +67,7 @@ def _getTestCases() -> _tp.Iterable[TestCase]: @_pt.mark.parametrize("testCase", [_pt.param(tc, id=tc.fileStem) for tc in _getTestCases()]) def testConvertXmlTmfStringToDdck(testCase: TestCase, qtbot) -> None: xmlFileContent = testCase.inputFilePath.read_text(encoding="utf8") - suggestedHydraulicConnections = [ - _models.Connection("Evap", _models.InputPort("In"), _models.OutputPort("Out")), - _models.Connection("Cond", _models.InputPort("In"), _models.OutputPort("Out")), - ] - actualDdckContent = _pc.convertXmlTmfStringToDdck(xmlFileContent, suggestedHydraulicConnections) + actualDdckContent = _pc.convertXmlTmfStringToDdck(xmlFileContent) testCase.actualOutputFilePath.write_text(actualDdckContent) expectedDdckContent = testCase.expectedOutputFilePath.read_text(encoding="utf8") assert actualDdckContent == expectedDdckContent diff --git a/trnsysGUI/proforma/_dialogs/editHydraulicConnectionsDialog.py b/trnsysGUI/proforma/_dialogs/editHydraulicConnectionsDialog.py index 721949a6..956efa41 100644 --- a/trnsysGUI/proforma/_dialogs/editHydraulicConnectionsDialog.py +++ b/trnsysGUI/proforma/_dialogs/editHydraulicConnectionsDialog.py @@ -47,14 +47,18 @@ def __init__( self._onActivatedCallbacks = list[_OnActivatedCallBack]() - self._configureCancelButton() + self.summaryTextEdit.setReadOnly(True) + + self._configureButtonBox() self._configureHydraulicConnections() self._configureComboBoxes() - self.summaryTextEdit.setReadOnly(True) self._reloadConnections() - def _configureCancelButton(self) -> None: + def _configureButtonBox(self) -> None: + okButton = self.okCancelButtonBox.button(_qtw.QDialogButtonBox.StandardButton.Ok) + okButton.clicked.connect(self.close) + cancelButton = self.okCancelButtonBox.button(_qtw.QDialogButtonBox.StandardButton.Cancel) cancelButton.clicked.connect(self._onCancelClicked) @@ -269,4 +273,4 @@ def showDialogAndGetResults( ) -> _cancel.MaybeCancelled[_cabc.Sequence[_models.Connection]]: dialog = EditHydraulicConnectionsDialog(suggestedHydraulicConnections, variablesByRole) dialog.exec() - return _cancel.CANCELLED + return dialog.hydraulicConnections diff --git a/trnsysGUI/proforma/models.py b/trnsysGUI/proforma/models.py index 8d33b1c5..adfb8019 100644 --- a/trnsysGUI/proforma/models.py +++ b/trnsysGUI/proforma/models.py @@ -42,11 +42,11 @@ class VariableStringConstants: class AllVariableStringConstants: - TEMPERATURE = VariableStringConstants("@temp", "T") - MASS_FLOW_RATE = VariableStringConstants("@mfr", "M") - REVERSE_TEMPERATURE = VariableStringConstants("@revtemp", None) - DENSITY = VariableStringConstants("@rho", "Rho") - HEAT_CAPACITY = VariableStringConstants("@cp", "Cp") + TEMPERATURE = VariableStringConstants("temp", "T") + MASS_FLOW_RATE = VariableStringConstants("mfr", "M") + REVERSE_TEMPERATURE = VariableStringConstants("revtemp", None) + DENSITY = VariableStringConstants("rho", "Rho") + HEAT_CAPACITY = VariableStringConstants("cp", "Cp") def _getSummaryLine( @@ -58,7 +58,7 @@ def _getSummaryLine( if not variable or variable == UNSET: return "" - computedVariable = f"{variableStringConstants.propertyName}({qualifiedPortName})" + computedVariable = f"@{variableStringConstants.propertyName}({qualifiedPortName})" summaryLine = ( f'"{variable.tmfName}" = {computedVariable}' if direction == "input" From 56d7b006c9b9f1162e26da98043d8b5fa6876e9f Mon Sep 17 00:00:00 2001 From: Damian Birchler Date: Mon, 29 Jul 2024 14:06:30 +0200 Subject: [PATCH 17/33] Find combo box item data by value, not reference. --- .../editHydraulicConnectionsDialog.py | 30 ++++++++++++------- trnsysGUI/proforma/models.py | 3 +- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/trnsysGUI/proforma/_dialogs/editHydraulicConnectionsDialog.py b/trnsysGUI/proforma/_dialogs/editHydraulicConnectionsDialog.py index 956efa41..cbdd9e3a 100644 --- a/trnsysGUI/proforma/_dialogs/editHydraulicConnectionsDialog.py +++ b/trnsysGUI/proforma/_dialogs/editHydraulicConnectionsDialog.py @@ -160,26 +160,26 @@ def _removeSelectedVariablesOfOtherComboBoxes( self, comboBox: _qtw.QComboBox, variables: _cabc.Sequence[_models.Variable] ) -> _cabc.Sequence[_models.Variable]: selectedVariableForComboBox = self._getVariableCorrespondingToComboBox(comboBox) - otherUnselectedVariables = [ + selectedOrOtherUnselectedVariables = [ v for v in variables if v == selectedVariableForComboBox or v not in self._selectedVariables ] - return otherUnselectedVariables + return selectedOrOtherUnselectedVariables def _getVariableCorrespondingToComboBox(self, comboBox: _qtw.QComboBox) -> _models.Variable | _models.Unset: selectedConnection = self._getSelectedConnection() if comboBox == self.fluidDensityComboBox: - return selectedConnection.fluid.density + return selectedConnection.fluid.density or _models.UNSET elif comboBox == self.fluidHeatCapacityComboBox: - return selectedConnection.fluid.heatCapacity + return selectedConnection.fluid.heatCapacity or _models.UNSET elif comboBox == self.massFlowRateComboBox: - return selectedConnection.inputPort.massFlowRate or _models.Unset + return selectedConnection.inputPort.massFlowRate elif comboBox == self.inputTempComboBox: - return selectedConnection.inputPort.temperature or _models.Unset + return selectedConnection.inputPort.temperature elif comboBox == self.outputTempComboBox: - return selectedConnection.outputPort.temperature or _models.Unset + return selectedConnection.outputPort.temperature elif comboBox == self.outputRevTempComboBox: - return selectedConnection.outputPort.reverseTemperature + return selectedConnection.outputPort.reverseTemperature or _models.UNSET else: raise AssertionError("Unknown combo box.") @@ -230,8 +230,7 @@ def _reloadSelectedComboBoxItems(self) -> None: for comboBox in self._comboBoxes: data = self._getVariableCorrespondingToComboBox(comboBox) - index = comboBox.findData(data) - comboBox.setCurrentIndex(index) + _setSelected(comboBox, data) @property def _comboBoxes(self) -> _cabc.Sequence[_qtw.QComboBox]: @@ -274,3 +273,14 @@ def showDialogAndGetResults( dialog = EditHydraulicConnectionsDialog(suggestedHydraulicConnections, variablesByRole) dialog.exec() return dialog.hydraulicConnections + + +def _setSelected(comboBox: _qtw.QComboBox, data: _models.Variable | _models.Unset) -> None: + # Cannot use `QComboBox.findData` as `findData` works by reference, but we want by value + for index in range(comboBox.count()): + rowData = comboBox.itemData(index) + if rowData == data: + comboBox.setCurrentIndex(index) + return + + raise AssertionError(f"{data} not a member of combo box.") diff --git a/trnsysGUI/proforma/models.py b/trnsysGUI/proforma/models.py index adfb8019..9e29747b 100644 --- a/trnsysGUI/proforma/models.py +++ b/trnsysGUI/proforma/models.py @@ -125,8 +125,9 @@ def getSummary(self) -> str: if not subSummaries: return "" + connectionName = self.name if self.name else "Default connection" summary = f"""\ -** {self.name} +** {connectionName} {subSummaries} """ From 095baff7c37ffd611cd944340ee82158054933dc Mon Sep 17 00:00:00 2001 From: Damian Birchler Date: Mon, 29 Jul 2024 14:39:12 +0200 Subject: [PATCH 18/33] By-pass dialog in test. --- .../proforma/testConvertXmlTmfToDdck.py | 19 ++++++++++++++----- trnsysGUI/proforma/convertXmlTmfToDdck.py | 3 ++- .../{_dialogs => dialogs}/__init__.py | 0 .../{_dialogs => dialogs}/_convert.ui | 0 .../_hydraulicConnections.ui | 0 .../editHydraulicConnectionsDialog.py | 0 6 files changed, 16 insertions(+), 6 deletions(-) rename trnsysGUI/proforma/{_dialogs => dialogs}/__init__.py (100%) rename trnsysGUI/proforma/{_dialogs => dialogs}/_convert.ui (100%) rename trnsysGUI/proforma/{_dialogs => dialogs}/_hydraulicConnections.ui (100%) rename trnsysGUI/proforma/{_dialogs => dialogs}/editHydraulicConnectionsDialog.py (100%) diff --git a/tests/trnsysGUI/proforma/testConvertXmlTmfToDdck.py b/tests/trnsysGUI/proforma/testConvertXmlTmfToDdck.py index 123f7fde..ab855c74 100644 --- a/tests/trnsysGUI/proforma/testConvertXmlTmfToDdck.py +++ b/tests/trnsysGUI/proforma/testConvertXmlTmfToDdck.py @@ -1,15 +1,14 @@ +import dataclasses as _dc import pathlib as _pl -import pprint as _pp import pkgutil as _pu +import pprint as _pp import typing as _tp -import dataclasses as _dc import pytest as _pt - import xmlschema as _xml -import trnsysGUI.proforma.models as _models import trnsysGUI.proforma.convertXmlTmfToDdck as _pc +import trnsysGUI.proforma.dialogs.editHydraulicConnectionsDialog as _ehcd _DATA_DIR_PATH = _pl.Path(__file__).parent / "data" _INPUT_DIR_PATH = _DATA_DIR_PATH / "input" @@ -65,8 +64,18 @@ def _getTestCases() -> _tp.Iterable[TestCase]: @_pt.mark.parametrize("testCase", [_pt.param(tc, id=tc.fileStem) for tc in _getTestCases()]) -def testConvertXmlTmfStringToDdck(testCase: TestCase, qtbot) -> None: +def testConvertXmlTmfStringToDdck(testCase: TestCase, qtbot, monkeypatch) -> None: xmlFileContent = testCase.inputFilePath.read_text(encoding="utf8") + + def returnConnectionsUnmodified(connections, _): + return connections + + monkeypatch.setattr( + _ehcd.EditHydraulicConnectionsDialog, + _ehcd.EditHydraulicConnectionsDialog.showDialogAndGetResults.__name__, + returnConnectionsUnmodified, + ) + actualDdckContent = _pc.convertXmlTmfStringToDdck(xmlFileContent) testCase.actualOutputFilePath.write_text(actualDdckContent) expectedDdckContent = testCase.expectedOutputFilePath.read_text(encoding="utf8") diff --git a/trnsysGUI/proforma/convertXmlTmfToDdck.py b/trnsysGUI/proforma/convertXmlTmfToDdck.py index 5fc25b0f..cf0f21c9 100644 --- a/trnsysGUI/proforma/convertXmlTmfToDdck.py +++ b/trnsysGUI/proforma/convertXmlTmfToDdck.py @@ -7,9 +7,10 @@ import xmlschema as _xml import trnsysGUI.common.cancelled as _cancel +import trnsysGUI.proforma.dialogs.editHydraulicConnectionsDialog as _ehcd + from . import createModelConnections as _cmcs from . import models as _models -from ._dialogs import editHydraulicConnectionsDialog as _ehcd _CONTAINING_DIR_PATH = _pl.Path(__file__).parent _SCHEMA_FILE_PATH = _CONTAINING_DIR_PATH / "xmltmf.xsd" diff --git a/trnsysGUI/proforma/_dialogs/__init__.py b/trnsysGUI/proforma/dialogs/__init__.py similarity index 100% rename from trnsysGUI/proforma/_dialogs/__init__.py rename to trnsysGUI/proforma/dialogs/__init__.py diff --git a/trnsysGUI/proforma/_dialogs/_convert.ui b/trnsysGUI/proforma/dialogs/_convert.ui similarity index 100% rename from trnsysGUI/proforma/_dialogs/_convert.ui rename to trnsysGUI/proforma/dialogs/_convert.ui diff --git a/trnsysGUI/proforma/_dialogs/_hydraulicConnections.ui b/trnsysGUI/proforma/dialogs/_hydraulicConnections.ui similarity index 100% rename from trnsysGUI/proforma/_dialogs/_hydraulicConnections.ui rename to trnsysGUI/proforma/dialogs/_hydraulicConnections.ui diff --git a/trnsysGUI/proforma/_dialogs/editHydraulicConnectionsDialog.py b/trnsysGUI/proforma/dialogs/editHydraulicConnectionsDialog.py similarity index 100% rename from trnsysGUI/proforma/_dialogs/editHydraulicConnectionsDialog.py rename to trnsysGUI/proforma/dialogs/editHydraulicConnectionsDialog.py From 34eeaa6c9309549f9e18535d31dea6aa92e4c190 Mon Sep 17 00:00:00 2001 From: Damian Birchler Date: Mon, 29 Jul 2024 15:42:13 +0200 Subject: [PATCH 19/33] Satisfy static checkers. --- .../proforma/testConvertXmlTmfToDdck.py | 5 +- trnsysGUI/common/cancelled.py | 2 +- trnsysGUI/diagram/fileSystemTreeView.py | 40 +++++++----- trnsysGUI/proforma/convertXmlTmfToDdck.py | 62 +++++++++++++------ trnsysGUI/proforma/createModelConnections.py | 2 +- .../dialogs/editHydraulicConnectionsDialog.py | 46 +++++++------- trnsysGUI/proforma/models.py | 29 ++++++++- 7 files changed, 121 insertions(+), 65 deletions(-) diff --git a/tests/trnsysGUI/proforma/testConvertXmlTmfToDdck.py b/tests/trnsysGUI/proforma/testConvertXmlTmfToDdck.py index ab855c74..14332f3f 100644 --- a/tests/trnsysGUI/proforma/testConvertXmlTmfToDdck.py +++ b/tests/trnsysGUI/proforma/testConvertXmlTmfToDdck.py @@ -31,6 +31,7 @@ def testDecodeXmlTmf() -> None: def _getSchema() -> _xml.XMLSchema11: data = _pu.get_data(_pc.__name__, "xmltmf.xsd") + assert data string = data.decode("UTF8") schema = _xml.XMLSchema11(string) return schema @@ -64,7 +65,7 @@ def _getTestCases() -> _tp.Iterable[TestCase]: @_pt.mark.parametrize("testCase", [_pt.param(tc, id=tc.fileStem) for tc in _getTestCases()]) -def testConvertXmlTmfStringToDdck(testCase: TestCase, qtbot, monkeypatch) -> None: +def testConvertXmlTmfStringToDdck(testCase: TestCase, monkeypatch) -> None: xmlFileContent = testCase.inputFilePath.read_text(encoding="utf8") def returnConnectionsUnmodified(connections, _): @@ -77,6 +78,8 @@ def returnConnectionsUnmodified(connections, _): ) actualDdckContent = _pc.convertXmlTmfStringToDdck(xmlFileContent) + assert isinstance(actualDdckContent, str) + testCase.actualOutputFilePath.write_text(actualDdckContent) expectedDdckContent = testCase.expectedOutputFilePath.read_text(encoding="utf8") assert actualDdckContent == expectedDdckContent diff --git a/trnsysGUI/common/cancelled.py b/trnsysGUI/common/cancelled.py index a3fcc24e..3ee83ba0 100644 --- a/trnsysGUI/common/cancelled.py +++ b/trnsysGUI/common/cancelled.py @@ -10,7 +10,7 @@ class Cancelled: MaybeCancelled: _tp.TypeAlias = _T | Cancelled -def isCancelled(maybeCancelled: MaybeCancelled[_T]) -> bool: +def isCancelled(maybeCancelled: MaybeCancelled[_T]) -> _tp.TypeGuard[Cancelled]: if maybeCancelled == CANCELLED: return True diff --git a/trnsysGUI/diagram/fileSystemTreeView.py b/trnsysGUI/diagram/fileSystemTreeView.py index 81c7754d..d2398e1d 100644 --- a/trnsysGUI/diagram/fileSystemTreeView.py +++ b/trnsysGUI/diagram/fileSystemTreeView.py @@ -6,6 +6,8 @@ import PyQt5.QtGui as _qtg import PyQt5.QtWidgets as _qtw +import pytrnsys.utils.result as _res + import trnsysGUI.common.cancelled as _cancel import trnsysGUI.proforma.convertXmlTmfToDdck as _pro import trnsysGUI.proforma.models as _models @@ -81,23 +83,31 @@ def _loadFileIntoFolder(self) -> None: if standardButton != _qtw.QMessageBox.StandardButton.Yes: # pylint: disable=no-member return - if sourceSuffix != ".xmltmf": - _su.copy(sourceFilePath, targetFilePath) - else: - sourceFileContent = sourceFilePath.read_text() - suggestedHydraulicConnections = { - _models.Connection(None, _models.InputPort("In"), _models.OutputPort("Out")) - } - - maybeCancelled = _pro.convertXmlTmfStringToDdck( - sourceFileContent, - suggestedHydraulicConnections, - ) - if _cancel.isCancelled(maybeCancelled): + if sourceSuffix != ".xmltmf": + _su.copy(sourceFilePath, targetFilePath) return - targetFileContent = maybeCancelled - targetFilePath.write_text(targetFileContent) + self._convertAndLoadProformaFileIntoFolder(sourceFilePath, targetFilePath) + + @staticmethod + def _convertAndLoadProformaFileIntoFolder(sourceFilePath: _pl.Path, targetFilePath: _pl.Path) -> None: + sourceFileContent = sourceFilePath.read_text() + suggestedHydraulicConnections = [_models.Connection(None, _models.InputPort("In"), _models.OutputPort("Out"))] + + maybeCancelled = _pro.convertXmlTmfStringToDdck( + sourceFileContent, + suggestedHydraulicConnections, + ) + if _cancel.isCancelled(maybeCancelled): + return + result = _cancel.value(maybeCancelled) + if _res.isError(result): + _warn.showMessageBox(_res.error(result).message) + return + targetFileContent = _res.value(result) + + assert isinstance(targetFileContent, str) + targetFilePath.write_text(targetFileContent) def _deleteCurrentFile(self) -> None: path = self._getCurrentPath() diff --git a/trnsysGUI/proforma/convertXmlTmfToDdck.py b/trnsysGUI/proforma/convertXmlTmfToDdck.py index cf0f21c9..a2549f2e 100644 --- a/trnsysGUI/proforma/convertXmlTmfToDdck.py +++ b/trnsysGUI/proforma/convertXmlTmfToDdck.py @@ -6,6 +6,8 @@ import jinja2 as _jj import xmlschema as _xml +import pytrnsys.utils.result as _res + import trnsysGUI.common.cancelled as _cancel import trnsysGUI.proforma.dialogs.editHydraulicConnectionsDialog as _ehcd @@ -41,8 +43,11 @@ def convertXmltmfToDdck(xmlTmfFilePath: _pl.Path, ddckFilePath: _pl.Path) -> _ca except ValueError as exception: raise ValueError(f"Error parsing {xmlTmfFilePath}") from exception + assert isinstance(ddckContent, str) ddckFilePath.write_text(ddckContent, encoding="utf8") + return None + _StringMapping = _tp.Mapping[str, _tp.Any] @@ -136,11 +141,17 @@ def _createVariablesForRole( sortedSerializedVariablesForRole = _getSerializedVariablesWithRole(role, sortedSerializedVariables) variables = [] - for roleOrder, variable in enumerate(sortedSerializedVariablesForRole, start=1): - order = variable["order"] - bounds = _getBounds(variable) + for roleOrder, serializedVariable in enumerate(sortedSerializedVariablesForRole, start=1): + order = serializedVariable["order"] + bounds = _getBounds(serializedVariable) variable = _models.Variable( - variable["name"], order, role, roleOrder, variable["unit"], bounds, variable["default"] + serializedVariable["name"], + order, + role, + roleOrder, + serializedVariable["unit"], + bounds, + serializedVariable["default"], ) variables.append(variable) @@ -149,7 +160,7 @@ def _createVariablesForRole( def convertXmlTmfStringToDdck( xmlTmfContent: str, suggestedHydraulicConnections: _cabc.Sequence[_models.Connection] | None = None -) -> _cancel.MaybeCancelled[str]: +) -> _cancel.MaybeCancelled[_res.Result[str]]: schema = _xml.XMLSchema11(_SCHEMA_FILE_PATH) try: @@ -164,9 +175,10 @@ def convertXmlTmfStringToDdck( if "hydraulicConnections" in proforma: serializedHydraulicConnections = proforma["hydraulicConnections"]["connection"] - hydraulicConnections = _cmcs.createModelConnectionsFromProforma( - serializedHydraulicConnections, variablesByRole.allVariables - ) + result = _cmcs.createModelConnectionsFromProforma(serializedHydraulicConnections, variablesByRole.allVariables) + if _res.isError(result): + return _res.error(result) + hydraulicConnections = _res.value(result) elif suggestedHydraulicConnections is not None: hydraulicConnections = suggestedHydraulicConnections else: @@ -178,16 +190,21 @@ def convertXmlTmfStringToDdck( ) if _cancel.isCancelled(maybeCancelled): return _cancel.CANCELLED - hydraulicConnections = maybeCancelled + hydraulicConnections = _cancel.value(maybeCancelled) - connectionsDataByOrder = _createHydraulicConnectionDataByOrder(hydraulicConnections) + trnsysType = proforma["type"] + return _convertXmlTmfStringToDdck(trnsysType, hydraulicConnections, variablesByRole) + + +def _convertXmlTmfStringToDdck( + trnsysType: int, hydraulicConnections: _cabc.Sequence[_models.Connection], variablesByRole: _models.VariablesByRole +) -> str: + connectionsDataByOrder = _createHydraulicConnectionDataByOrder(hydraulicConnections) parameters = _createProcessedVariables(variablesByRole.parameters, connectionsDataByOrder) inputs = _createProcessedVariables(variablesByRole.inputs, connectionsDataByOrder) outputs = _createProcessedVariables(variablesByRole.outputs, connectionsDataByOrder) - - ddckContent = _JINJA_TEMPLATE.render(type=proforma["type"], parameters=parameters, inputs=inputs, outputs=outputs) - + ddckContent = _JINJA_TEMPLATE.render(type=trnsysType, parameters=parameters, inputs=inputs, outputs=outputs) return ddckContent @@ -209,7 +226,9 @@ def _createHydraulicConnectionDataByOrder( return hydraulicConnectionDataByOrder -def _getHydraulicConnectionDataByOrderForFluid(connectionName, hydraulicConnection, inputPort): +def _getHydraulicConnectionDataByOrderForFluid( + connectionName: str | None, hydraulicConnection: _models.Connection, inputPort: _models.InputPort +) -> dict[int, _HydraulicConnectionsData]: dataByOrderForFluid = {} heatCapacityVariable = hydraulicConnection.fluid.heatCapacity if heatCapacityVariable: @@ -225,11 +244,12 @@ def _getHydraulicConnectionDataByOrderForFluid(connectionName, hydraulicConnecti def _getHydraulicConnectionDataByOrderForInput( - connectionName: str, inputPort: _models.InputPort + connectionName: str | None, inputPort: _models.InputPort ) -> dict[int, _HydraulicConnectionsData]: portName = inputPort.name - mfrOrder = inputPort.massFlowRate.order - tempOrder = inputPort.temperature.order + + mfrOrder = inputPort.massFlowRateSet.order + tempOrder = inputPort.temperatureSet.order hydraulicConnectionData = { mfrOrder: _HydraulicConnectionsData.createForMassFlowRate(connectionName, portName), @@ -240,11 +260,13 @@ def _getHydraulicConnectionDataByOrderForInput( def _getHydraulicConnectionDataByOrderForOutput( - connectionName: str, + connectionName: str | None, outputPort: _models.OutputPort, -) -> _tp.Mapping[int, _HydraulicConnectionsData]: +) -> dict[int, _HydraulicConnectionsData]: + outputPort.ensureAllRequiredVariablesAreSet() + portName = outputPort.name - tempOrder = outputPort.temperature.order + tempOrder = outputPort.temperatureSet.order revTempOrder = outputPort.reverseTemperature.order if outputPort.reverseTemperature else None hydraulicConnectionData = { diff --git a/trnsysGUI/proforma/createModelConnections.py b/trnsysGUI/proforma/createModelConnections.py index 3eabd0fe..a5e36742 100644 --- a/trnsysGUI/proforma/createModelConnections.py +++ b/trnsysGUI/proforma/createModelConnections.py @@ -15,7 +15,7 @@ def createModelConnectionsFromInternalPiping( connections = set() for node in internalPiping.nodes: if not isinstance(node, _mfn.Pipe): - return _res.Error(f"`{node.name}` is a `{node.__name__}`, but only direct pipes are supported.") + return _res.Error(f"`{node.name}` is a `{node.__class__.__name__}`, but only direct pipes are supported.") pipe = node inputPort = _models.InputPort(pipe.fromPort.name) diff --git a/trnsysGUI/proforma/dialogs/editHydraulicConnectionsDialog.py b/trnsysGUI/proforma/dialogs/editHydraulicConnectionsDialog.py index cbdd9e3a..8602b985 100644 --- a/trnsysGUI/proforma/dialogs/editHydraulicConnectionsDialog.py +++ b/trnsysGUI/proforma/dialogs/editHydraulicConnectionsDialog.py @@ -41,9 +41,7 @@ def __init__( self.setupUi(self) self._variablesByRole = variablesByRole - self.hydraulicConnections: _cancel.MaybeCancelled[_cabc.Sequence[_models.Connection]] = ( - self._getDeepCopiesSortedByName(suggestedHydraulicConnections) - ) + self.hydraulicConnections = self._getDeepCopiesSortedByName(suggestedHydraulicConnections) self._onActivatedCallbacks = list[_OnActivatedCallBack]() @@ -56,28 +54,21 @@ def __init__( self._reloadConnections() def _configureButtonBox(self) -> None: - okButton = self.okCancelButtonBox.button(_qtw.QDialogButtonBox.StandardButton.Ok) - okButton.clicked.connect(self.close) - - cancelButton = self.okCancelButtonBox.button(_qtw.QDialogButtonBox.StandardButton.Cancel) - cancelButton.clicked.connect(self._onCancelClicked) - - def _onCancelClicked(self) -> None: - self.hydraulicConnections = _cancel.CANCELLED - self.close() + self.okCancelButtonBox.accepted.connect(self.accept) + self.okCancelButtonBox.rejected.connect(self.reject) @staticmethod def _getDeepCopiesSortedByName(suggestedHydraulicConnections): hydraulicConnections = [_copy.deepcopy(c) for c in suggestedHydraulicConnections] def getConnectionName(connection: _models.Connection) -> str: - return connection.name + return connection.name or "" sortedHydraulicConnections = sorted(hydraulicConnections, key=getConnectionName) return sortedHydraulicConnections def _configureHydraulicConnections(self): - self.connectionsListWidget.setSelectionMode(_qtw.QAbstractItemView.SelectionMode.SingleSelection) + self.connectionsListWidget.setSelectionMode(_qtw.QAbstractItemView.SingleSelection) for hydraulicConnection in self.hydraulicConnections: name = hydraulicConnection.name if hydraulicConnection.name else self._DEFAULT_CONNECTION_LABEL @@ -130,7 +121,10 @@ def _reconfigureOutputs(self) -> None: @staticmethod def _addOptions( - comboBox: _qtw.QComboBox, variables: _cabc.Sequence[_models.Variable], withUnset: bool = True, clear: bool = True + comboBox: _qtw.QComboBox, + variables: _cabc.Sequence[_models.Variable], + withUnset: bool = True, + clear: bool = True, ) -> None: if clear: comboBox.clear() @@ -170,18 +164,18 @@ def _getVariableCorrespondingToComboBox(self, comboBox: _qtw.QComboBox) -> _mode if comboBox == self.fluidDensityComboBox: return selectedConnection.fluid.density or _models.UNSET - elif comboBox == self.fluidHeatCapacityComboBox: + if comboBox == self.fluidHeatCapacityComboBox: return selectedConnection.fluid.heatCapacity or _models.UNSET - elif comboBox == self.massFlowRateComboBox: + if comboBox == self.massFlowRateComboBox: return selectedConnection.inputPort.massFlowRate - elif comboBox == self.inputTempComboBox: + if comboBox == self.inputTempComboBox: return selectedConnection.inputPort.temperature - elif comboBox == self.outputTempComboBox: + if comboBox == self.outputTempComboBox: return selectedConnection.outputPort.temperature - elif comboBox == self.outputRevTempComboBox: + if comboBox == self.outputRevTempComboBox: return selectedConnection.outputPort.reverseTemperature or _models.UNSET - else: - raise AssertionError("Unknown combo box.") + + raise AssertionError("Unknown combo box.") def _getSelectedConnection(self) -> _models.Connection: selectedItems = self.connectionsListWidget.selectedItems() @@ -210,7 +204,7 @@ def _reloadSummaryText(self) -> None: self.summaryTextEdit.setPlainText(overallSummary) def _resetOkButton(self) -> None: - okButton = self.okCancelButtonBox.button(_qtw.QDialogButtonBox.StandardButton.Ok) + okButton = self.okCancelButtonBox.button(_qtw.QDialogButtonBox.Ok) areAnyRequiredVariablesUnset = self._areAnyRequiredVariablesUnset() isEnabled = not areAnyRequiredVariablesUnset okButton.setEnabled(isEnabled) @@ -271,7 +265,11 @@ def showDialogAndGetResults( variablesByRole: _models.VariablesByRole, ) -> _cancel.MaybeCancelled[_cabc.Sequence[_models.Connection]]: dialog = EditHydraulicConnectionsDialog(suggestedHydraulicConnections, variablesByRole) - dialog.exec() + returnValue = dialog.exec() + + if returnValue == _qtw.QDialog.Rejected: + return _cancel.CANCELLED + return dialog.hydraulicConnections diff --git a/trnsysGUI/proforma/models.py b/trnsysGUI/proforma/models.py index 9e29747b..72701b92 100644 --- a/trnsysGUI/proforma/models.py +++ b/trnsysGUI/proforma/models.py @@ -55,7 +55,7 @@ def _getSummaryLine( variable: Variable | None | Unset, direction: _tp.Literal["input", "output"], ) -> str: - if not variable or variable == UNSET: + if not isinstance(variable, Variable): return "" computedVariable = f"@{variableStringConstants.propertyName}({qualifiedPortName})" @@ -142,19 +142,34 @@ def _getQualifiedPortName(connectionName: str | None, portName: str) -> str: return qualifiedPortName +def _getSetVariable(variable: Variable | Unset) -> Variable: + if not isinstance(variable, Variable): + raise ValueError("Required variable not set.") + + return variable + + @_dc.dataclass class InputPort: name: str temperature: "RequiredVariable" = UNSET massFlowRate: "RequiredVariable" = UNSET + @property + def temperatureSet(self) -> Variable: + return _getSetVariable(self.temperature) + + @property + def massFlowRateSet(self) -> "Variable": + return _getSetVariable(self.massFlowRate) + @property def allVariables(self) -> _cabc.Sequence[Variable]: return _removeUnsetAndNone(self.temperature, self.massFlowRate) @property def areAnyRequiredVariablesUnset(self) -> bool: - return self.temperature == UNSET or self.massFlowRate == UNSET + return UNSET in (self.temperature, self.massFlowRate) def getSummary(self, connectionName: str | None) -> str: qualifiedPortName = _getQualifiedPortName(connectionName, self.name) @@ -172,6 +187,10 @@ class OutputPort: temperature: "RequiredVariable" = UNSET reverseTemperature: Variable | None = None + @property + def temperatureSet(self) -> Variable: + return _getSetVariable(self.temperature) + @property def allVariables(self) -> _cabc.Sequence[Variable]: return _removeUnsetAndNone(self.temperature, self.reverseTemperature) @@ -180,6 +199,10 @@ def allVariables(self) -> _cabc.Sequence[Variable]: def areAnyRequiredVariablesUnset(self): return self.temperature == UNSET + def ensureAllRequiredVariablesAreSet(self) -> None: + if not self.areAnyRequiredVariablesUnset: + raise ValueError("Not all required variables are set", self) + def getSummary(self, connectionName: str | None) -> str: qualifiedPortName = _getQualifiedPortName(connectionName, self.name) summary = _joinNonEmptyStringsWithNewLines( @@ -192,7 +215,7 @@ def getSummary(self, connectionName: str | None) -> str: return summary -def _removeUnsetAndNone(*variables: Variable) -> _cabc.Sequence[Variable]: +def _removeUnsetAndNone(*variables: Variable | Unset | None) -> _cabc.Sequence[Variable]: return [v for v in variables if isinstance(v, Variable)] From 4ef6393065d0511833eb3f64dd15541d95a62fce Mon Sep 17 00:00:00 2001 From: Damian Birchler Date: Mon, 29 Jul 2024 15:46:31 +0200 Subject: [PATCH 20/33] Add check for unique node names. --- trnsysGUI/internalPiping.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/trnsysGUI/internalPiping.py b/trnsysGUI/internalPiping.py index 2cf09f1c..64644f73 100644 --- a/trnsysGUI/internalPiping.py +++ b/trnsysGUI/internalPiping.py @@ -17,6 +17,11 @@ class InternalPiping: def __post_init__(self): if not all(mpi in self.modelPortItemsToGraphicalPortItem for n in self.nodes for mpi in n.getPortItems()): raise ValueError("Error a port item of a node was not contained in `modelPortItemsToGraphicalPortItem`.") + + uniqueNodeNames = {n.name for n in self.nodes} + if not len(uniqueNodeNames) == len(self.nodes): + raise ValueError("Node names must be unique within one component.") + def getModelPortItem( self, graphicalPortItem: _pi.PortItemBase, portItemType: _mfn.PortItemType # type: ignore[name-defined] From 40cb748fc3a8fb468d00fbe929839a686c3fe422 Mon Sep 17 00:00:00 2001 From: Damian Birchler Date: Mon, 29 Jul 2024 16:19:41 +0200 Subject: [PATCH 21/33] Miscellaneous cleanups plus extra test. --- .../Type71-missing-closing-bracket.xmltmf | 484 ++++++++++++++++++ .../proforma/testConvertXmlTmfToDdck.py | 19 +- trnsysGUI/diagram/fileSystemTreeView.py | 18 +- trnsysGUI/internalPiping.py | 9 +- trnsysGUI/proforma/convertXmlTmfToDdck.py | 34 +- trnsysGUI/proforma/models.py | 4 - 6 files changed, 527 insertions(+), 41 deletions(-) create mode 100644 tests/trnsysGUI/proforma/data/input/Type71-missing-closing-bracket.xmltmf diff --git a/tests/trnsysGUI/proforma/data/input/Type71-missing-closing-bracket.xmltmf b/tests/trnsysGUI/proforma/data/input/Type71-missing-closing-bracket.xmltmf new file mode 100644 index 00000000..d9615781 --- /dev/null +++ b/tests/trnsysGUI/proforma/data/input/Type71-missing-closing-bracket.xmltmf @@ -0,0 +1,484 @@ + + + Solar Collector; Evacuated Tube + Contributors are listed in manuals + Solar Energy Laboratory, University of Wisconsin - Madison + TESS + TRNSYS v7.5 + May 2011 + 1 + 16 + C:\Users\damian.birchler\dev\pytrnsys\issues\gui\proforma-to-ddck\Type71.bmp + 71 + 20 + + + + + 1 + Inlet temperature + input + Temperature + C + real + -Inf + +Inf + [ ; ] + 20.0 + SN + The temperature of the fluid entering the the solar collector. + + + 2 + Inlet flowrate + input + Flow Rate + kg/hr + real + 0.0 + +Inf + [ ; ] + 100.0 + SN + The flow rate of the fluid entering the solar collector. + + + 3 + Ambient temperature + input + Temperature + C + real + -Inf + +Inf + [ ; ] + 10.0 + SN + The temperature of the environment in which the solar collector is located. + This temperature will be used for loss calculations. + + + 4 + Incident radiation + input + Flux + kJ/hr.m^2 + real + 0.0 + +Inf + [ ; ] + 0. + SN + The total (beam + diffuse) radiation incident on the plane of the solar + collector per unit area. + This input is commonly hooked up to the TYPE 16 "total radiation on surface 1" + output. + + + 5 + Incident diffuse radiation + input + Flux + kJ/hr.m^2 + real + 0.0 + +Inf + [ ; ] + 0.0 + SN + The incident diffuse solar radiation in the plane of the collector, per unit + area + + + 6 + Solar incidence angle + input + Direction (Angle) + degrees + real + -360 + +360 + [ ; ] + 0.0 + SN + Incidence angle of beam radiation on the collector's surface + + + 7 + Solar zenith angle + input + Direction (Angle) + degrees + real + -360 + +360 + [ ; ] + 0.0 + SN + The solar zenith angle is the angle between the vertical and the line of + sight of the sun + + + 8 + Solar azimuth angle + input + Direction (Angle) + degrees + real + -360 + +360 + [ ; ] + 0.0 + SN + The solar azimuth angle is the angle between the local meridian and the + projection + of the line of sight of the sun onto the horizontal plane + + + 9 + Collector slope + input + Direction (Angle) + degrees + real + -360 + +360 + [ ; ] + 45 + SN + The slope of the collector is the angle between the collector surface and + the horizontal + 0= horizontal, 90= vertical + The angle is positive when facing towards the collector surface azimuth + As a general rule, the performance of the collector is somewhat optimiszed when the + sollector slope is set to the latitude + + + 10 + Collector azimuth + input + Direction (Angle) + degrees + real + -360 + +360 + [ ; ] + 0.0 + SN + The collector surface azimuth is the angle between the local meridian and + the projection of the normal to the surface onto the horizontal plane + 0 = facing the equator + 90 = facing West + 180 = facing North in northern hemisphere, South in Southern hemisphere + 270 = facing East + + + 11 + Outlet temperature + output + Temperature + C + real + -Inf + +Inf + [ ; ] + 0 + SN + The temperature of the fluid exiting the solar collector array. + + + 12 + Outlet flowrate + output + Flow Rate + kg/hr + real + 0.0 + +Inf + [ ; ] + 0 + SN + The flowrate of the fluid exiting the solar collecor array. In this model: + mdot,in = mdot,out + + + 13 + Useful energy gain + output + Power + kJ/hr + real + 0.0 + +Inf + [ ; ] + 0 + SN + The rate of useful energy gain by the solar collector fluid: + Qu = mdot * Cp * (Tout - Tin) + + + 14 + Number in series + parameter + Dimensionless + - + integer + 1 + +Inf + [ ; ] + 1 + SN + The solar collector model can simulate an array of identical solar + collectors hooked up in series. This parameter is used to specify how many + collectors are hooked up in a series arrangement where the output of the first + collector is the inlet to the second collector. NOTE: increasing this value does not + change the array area. Total array area is set by parameter 2 + + + 15 + Collector area + parameter + Area + m^2 + real + 0.0 + +Inf + [ ; ] + 2.0 + SN + The total area of the solar collector array consistent with the supplied + efficiency parameters (typically gross area and not net area). + + + 16 + Fluid specific heat + parameter + Specific Heat + kJ/kg.K + real + 0.0 + +Inf + [ ; ] + 4.190 + SN + The specific heat of the fluid flowing through the solar collector array. + + + 17 + Efficiency mode + parameter + Dimensionless + - + integer + 1 + 3 + [ ; ] + 1 + SN + The collector efficiency equation can be written as a function of the inlet, + average or outlet temperature. + Specify 1 if the collector efficiency parameters are given as a function of the + inlet temperature + Specify 2 for a function of the collector average temperature + Specify 3 for a function of the collector outlet temperature + + + 18 + Flow rate at test conditions + parameter + Flow/area + kg/hr.m^2 + real + 0.0 + +Inf + [ ; ] + 50.0 + SN + Collector Flow rate per unit area for efficiency test conditions + + + 19 + Intercept efficiency + parameter + Dimensionless + - + real + 0.0 + 1.0 + [ ; ] + 0.7 + SN + This parameter is the y intercept of the collector efficiency curve versus + the temperature difference / radiation ratio + In equation form, this parameter is a0 in the following eq: + Eff = a0 - a1 * (Tc-Tamb)/Rad. - a2 * (Tc-Tamb)^2 / Rad. + where Tc is the collector inlet, average or outlet temperature according to + parameter 4 + + + 20 + Negative of first order efficiency coeficient + parameter + Heat Transfer Coeff. + kJ/hr.m^2.K + real + 0.0 + +Inf + [ ; ] + 10 + SN + This parameter is the slope of the collector efficiency curve versus the + temperature difference / radiation ratio + In equation form, this parameter is a1 in the following eq: + Eff. = a0 - a1 * (Tc-Tamb)/Rad. - a2 * (Tc-Tamb)^2 /Rad. + Where Tc is the collector inlet, average or outlet temperature according to + parameter 4 + + + 21 + Negative of second order efficiency coeficient + parameter + Temp. Dependent Loss Coeff. + kJ/hr.m^2.K^2 + real + 0.0 + +Inf + [ ; ] + 0.03 + SN + This parameter is the curvature of the efficiency curve versus the + temperature difference / radiation ratio + In equation form, this parameter is a2 in the following eq: + Eff. = a0 - a1 * (Tc-Tamb) /Rad.- a2 * (Tc-Tamb)^2/Rad + where Tc is the collector inlet, average or outlet temperature according to + parameter 4 + + + 22 + Logical unit of file containing biaxial IAM data + parameter + Dimensionless + - + integer + 10 + 100 + [ ; ] + 13 + SN + FORTRAN Logical unit for file containing biaxial IAM data Make sure that + each logical unit specified in an assembly is unique + + + 23 + Number of longitudinal angles for which IAMs are provided + parameter + Dimensionless + - + integer + 1 + +Inf + [ ; ] + 7 + SN + Number of data points for the IAM (longitudinal direction) + + + 24 + Number of transverse angles for which IAMs are provided + parameter + Dimensionless + - + integer + 1 + +Inf + [ ; ] + 7 + SN + Number of data points for the IAM (transverse direction) + + + 25 + Collector efficiency + output + dimensionless + - + real + -Inf + +Inf + [ ; ] + 0 + SN + + + 26 + Incidence angle modifier - overall + output + dimensionless + - + real + -Inf + +Inf + [ ; ] + 0 + SN + + + + + + + + 1 + + + + + 2 + + + + 16 + + + + + + 11 + + + + + + + + + What file contains the 2D IAM data? + .\Examples\Data Files\Type71-EvacuatedTubeSolarCollector-IAMData.dat + + .\Examples\Data Files\Type71-EvacuatedTubeSolarCollector-IAMData.dat + + Logical unit of file containing biaxial IAM data + no + + + df /c + .\SourceCode\Types\Type71.f90 + \ No newline at end of file diff --git a/tests/trnsysGUI/proforma/testConvertXmlTmfToDdck.py b/tests/trnsysGUI/proforma/testConvertXmlTmfToDdck.py index 14332f3f..ebe96f40 100644 --- a/tests/trnsysGUI/proforma/testConvertXmlTmfToDdck.py +++ b/tests/trnsysGUI/proforma/testConvertXmlTmfToDdck.py @@ -7,6 +7,8 @@ import pytest as _pt import xmlschema as _xml +import pytrnsys.utils.result as _res + import trnsysGUI.proforma.convertXmlTmfToDdck as _pc import trnsysGUI.proforma.dialogs.editHydraulicConnectionsDialog as _ehcd @@ -77,9 +79,16 @@ def returnConnectionsUnmodified(connections, _): returnConnectionsUnmodified, ) - actualDdckContent = _pc.convertXmlTmfStringToDdck(xmlFileContent) - assert isinstance(actualDdckContent, str) + result = _pc.convertXmlTmfStringToDdck(xmlFileContent, suggestedHydraulicConnections=None) + + if testCase.expectedOutputFilePath.is_file(): + assert isinstance(result, str) + actualDdckContent = _res.value(result) - testCase.actualOutputFilePath.write_text(actualDdckContent) - expectedDdckContent = testCase.expectedOutputFilePath.read_text(encoding="utf8") - assert actualDdckContent == expectedDdckContent + testCase.actualOutputFilePath.write_text(result) + expectedDdckContent = testCase.expectedOutputFilePath.read_text(encoding="utf8") + assert actualDdckContent == expectedDdckContent + else: + assert isinstance(result, _res.Error) + print(result.message) + assert not testCase.actualOutputFilePath.is_file() diff --git a/trnsysGUI/diagram/fileSystemTreeView.py b/trnsysGUI/diagram/fileSystemTreeView.py index d2398e1d..c7200de7 100644 --- a/trnsysGUI/diagram/fileSystemTreeView.py +++ b/trnsysGUI/diagram/fileSystemTreeView.py @@ -83,20 +83,20 @@ def _loadFileIntoFolder(self) -> None: if standardButton != _qtw.QMessageBox.StandardButton.Yes: # pylint: disable=no-member return - if sourceSuffix != ".xmltmf": - _su.copy(sourceFilePath, targetFilePath) - return + if sourceSuffix != ".xmltmf": + _su.copy(sourceFilePath, targetFilePath) + return - self._convertAndLoadProformaFileIntoFolder(sourceFilePath, targetFilePath) + self._convertAndLoadProformaFileIntoFolder(sourceFilePath, targetFilePath) @staticmethod def _convertAndLoadProformaFileIntoFolder(sourceFilePath: _pl.Path, targetFilePath: _pl.Path) -> None: - sourceFileContent = sourceFilePath.read_text() suggestedHydraulicConnections = [_models.Connection(None, _models.InputPort("In"), _models.OutputPort("Out"))] - maybeCancelled = _pro.convertXmlTmfStringToDdck( - sourceFileContent, + maybeCancelled = _pro.convertXmltmfToDdck( + sourceFilePath, suggestedHydraulicConnections, + targetFilePath, ) if _cancel.isCancelled(maybeCancelled): return @@ -104,10 +104,6 @@ def _convertAndLoadProformaFileIntoFolder(sourceFilePath: _pl.Path, targetFilePa if _res.isError(result): _warn.showMessageBox(_res.error(result).message) return - targetFileContent = _res.value(result) - - assert isinstance(targetFileContent, str) - targetFilePath.write_text(targetFileContent) def _deleteCurrentFile(self) -> None: path = self._getCurrentPath() diff --git a/trnsysGUI/internalPiping.py b/trnsysGUI/internalPiping.py index 64644f73..3feaa3e6 100644 --- a/trnsysGUI/internalPiping.py +++ b/trnsysGUI/internalPiping.py @@ -14,14 +14,13 @@ class InternalPiping: nodes: _tp.Sequence[_mfn.Node] modelPortItemsToGraphicalPortItem: _tp.Mapping[_mfn.PortItem, _pi.PortItemBase] # type: ignore[name-defined] - def __post_init__(self): + def __post_init__(self) -> None: if not all(mpi in self.modelPortItemsToGraphicalPortItem for n in self.nodes for mpi in n.getPortItems()): raise ValueError("Error a port item of a node was not contained in `modelPortItemsToGraphicalPortItem`.") - + uniqueNodeNames = {n.name for n in self.nodes} - if not len(uniqueNodeNames) == len(self.nodes): + if len(uniqueNodeNames) != len(self.nodes): raise ValueError("Node names must be unique within one component.") - def getModelPortItem( self, graphicalPortItem: _pi.PortItemBase, portItemType: _mfn.PortItemType # type: ignore[name-defined] @@ -79,7 +78,7 @@ def hasDdckPlaceHolders(cls) -> bool: return True @classmethod - def shallRenameOutputTemperaturesInHydraulicFile(cls): + def shallRenameOutputTemperaturesInHydraulicFile(cls) -> bool: return True def getInternalPiping(self) -> InternalPiping: diff --git a/trnsysGUI/proforma/convertXmlTmfToDdck.py b/trnsysGUI/proforma/convertXmlTmfToDdck.py index a2549f2e..b7478cc9 100644 --- a/trnsysGUI/proforma/convertXmlTmfToDdck.py +++ b/trnsysGUI/proforma/convertXmlTmfToDdck.py @@ -4,7 +4,8 @@ import typing as _tp import jinja2 as _jj -import xmlschema as _xml +import xmlschema as _xs +import xml.etree.ElementTree as _etree import pytrnsys.utils.result as _res @@ -32,16 +33,19 @@ def _createDdckJinjaTemplate() -> _jj.Template: _JINJA_TEMPLATE = _createDdckJinjaTemplate() -def convertXmltmfToDdck(xmlTmfFilePath: _pl.Path, ddckFilePath: _pl.Path) -> _cancel.MaybeCancelled[None]: +def convertXmltmfToDdck( + xmlTmfFilePath: _pl.Path, + suggestedHydraulicConnections: _cabc.Sequence[_models.Connection] | None, + ddckFilePath: _pl.Path, +) -> _cancel.MaybeCancelled[_res.Result[None]]: xmlTmfContent = xmlTmfFilePath.read_text(encoding="utf8") - try: - maybeCancelled = convertXmlTmfStringToDdck(xmlTmfContent) - if _cancel.isCancelled(maybeCancelled): - return _cancel.CANCELLED - - ddckContent = maybeCancelled - except ValueError as exception: - raise ValueError(f"Error parsing {xmlTmfFilePath}") from exception + maybeCancelled = convertXmlTmfStringToDdck(xmlTmfContent, suggestedHydraulicConnections) + if _cancel.isCancelled(maybeCancelled): + return _cancel.CANCELLED + result = _cancel.value(maybeCancelled) + if _res.isError(result): + return _res.error(result).withContext(f"Converting proforma `{xmlTmfFilePath}`") + ddckContent = _res.value(result) assert isinstance(ddckContent, str) ddckFilePath.write_text(ddckContent, encoding="utf8") @@ -159,14 +163,14 @@ def _createVariablesForRole( def convertXmlTmfStringToDdck( - xmlTmfContent: str, suggestedHydraulicConnections: _cabc.Sequence[_models.Connection] | None = None + xmlTmfContent: str, suggestedHydraulicConnections: _cabc.Sequence[_models.Connection] | None ) -> _cancel.MaybeCancelled[_res.Result[str]]: - schema = _xml.XMLSchema11(_SCHEMA_FILE_PATH) + schema = _xs.XMLSchema11(_SCHEMA_FILE_PATH) try: schema.validate(xmlTmfContent) - except _xml.XMLSchemaValidationError as validationError: - raise ValueError("Failed to validate schema.") from validationError + except (_etree.ParseError, _xs.XMLSchemaException) as error: + return _res.Error(f"Error reading or validating the proforma file: {error}") proforma: _tp.Any = schema.to_dict(xmlTmfContent) @@ -263,8 +267,6 @@ def _getHydraulicConnectionDataByOrderForOutput( connectionName: str | None, outputPort: _models.OutputPort, ) -> dict[int, _HydraulicConnectionsData]: - outputPort.ensureAllRequiredVariablesAreSet() - portName = outputPort.name tempOrder = outputPort.temperatureSet.order revTempOrder = outputPort.reverseTemperature.order if outputPort.reverseTemperature else None diff --git a/trnsysGUI/proforma/models.py b/trnsysGUI/proforma/models.py index 72701b92..1fbd2552 100644 --- a/trnsysGUI/proforma/models.py +++ b/trnsysGUI/proforma/models.py @@ -199,10 +199,6 @@ def allVariables(self) -> _cabc.Sequence[Variable]: def areAnyRequiredVariablesUnset(self): return self.temperature == UNSET - def ensureAllRequiredVariablesAreSet(self) -> None: - if not self.areAnyRequiredVariablesUnset: - raise ValueError("Not all required variables are set", self) - def getSummary(self, connectionName: str | None) -> str: qualifiedPortName = _getQualifiedPortName(connectionName, self.name) summary = _joinNonEmptyStringsWithNewLines( From c388c77402729613f46940cee58938186b313017 Mon Sep 17 00:00:00 2001 From: Damian Birchler Date: Mon, 29 Jul 2024 17:34:29 +0200 Subject: [PATCH 22/33] Start working on convert dialog. --- trnsysGUI/diagram/Editor.py | 13 ++-- trnsysGUI/diagram/fileSystemTreeView.py | 26 +++++-- trnsysGUI/internalPiping.py | 10 ++- trnsysGUI/proforma/createModelConnections.py | 6 +- trnsysGUI/proforma/dialogs/_convert.ui | 10 +-- .../proforma/dialogs/_hydraulicConnections.ui | 2 +- trnsysGUI/proforma/dialogs/convertDialog.py | 72 +++++++++++++++++++ .../dialogs/editHydraulicConnectionsDialog.py | 2 +- trnsysGUI/proforma/models.py | 1 - trnsysGUI/pytrnsys-gui.pyproject | 2 +- 10 files changed, 118 insertions(+), 26 deletions(-) create mode 100644 trnsysGUI/proforma/dialogs/convertDialog.py diff --git a/trnsysGUI/diagram/Editor.py b/trnsysGUI/diagram/Editor.py index 7e7ea0ca..4fa7c163 100644 --- a/trnsysGUI/diagram/Editor.py +++ b/trnsysGUI/diagram/Editor.py @@ -1,6 +1,7 @@ # pylint: skip-file # type: ignore +import collections.abc as _cabc import json import math as _math import os @@ -60,7 +61,7 @@ from . import fileSystemTreeView as _fst -class Editor(_qtw.QWidget): +class Editor(_qtw.QWidget, _ip.HasInternalPipingsProvider): def __init__(self, parent, projectFolder, jsonPath, loadValue, logger): super().__init__(parent) @@ -100,7 +101,7 @@ def __init__(self, parent, projectFolder, jsonPath, loadValue, logger): self.pathLayout.addWidget(self.projectPathLabel) self.pathLayout.addWidget(self.PPL) - treeView = _fst.FileSystemTreeView(_pl.Path(self.projectFolder)) + treeView = _fst.FileSystemTreeView(_pl.Path(self.projectFolder), self) self.fileBrowserLayout.addLayout(self.pathLayout) self.fileBrowserLayout.addWidget(treeView) @@ -178,9 +179,7 @@ def _setupWidgetHierarchy(self): libraryBrowserAndContextInfoSplitter = _qtw.QSplitter(_qtc.Qt.Orientation.Vertical) libraryBrowserAndContextInfoSplitter.addWidget(libraryBrowserView) libraryBrowserAndContextInfoSplitter.addWidget(self.contextInfoList) - _sizes.setRelativeSizes( - libraryBrowserAndContextInfoSplitter, [libraryBrowserView, self.contextInfoList], [3, 1] - ) + _sizes.setRelativeSizes(libraryBrowserAndContextInfoSplitter, [libraryBrowserView, self.contextInfoList], [3, 1]) self._consoleWidget = _con.QtConsoleWidget() @@ -1008,3 +1007,7 @@ def editHydraulicLoop(self, singlePipeConnection: SinglePipeConnection): def _updateGradientsInHydraulicLoop(hydraulicLoop: _hlm.HydraulicLoop) -> None: for connection in hydraulicLoop.connections: connection.updateSegmentGradients() + + @_tp.override + def getInternalPipings(self) -> _cabc.Sequence[_ip.HasInternalPiping]: + return [o for o in self.trnsysObj if isinstance(o, _ip.HasInternalPiping)] diff --git a/trnsysGUI/diagram/fileSystemTreeView.py b/trnsysGUI/diagram/fileSystemTreeView.py index c7200de7..d4fc16c2 100644 --- a/trnsysGUI/diagram/fileSystemTreeView.py +++ b/trnsysGUI/diagram/fileSystemTreeView.py @@ -8,16 +8,20 @@ import pytrnsys.utils.result as _res +import trnsysGUI.internalPiping as _ip import trnsysGUI.common.cancelled as _cancel import trnsysGUI.proforma.convertXmlTmfToDdck as _pro -import trnsysGUI.proforma.models as _models +import trnsysGUI.proforma.createModelConnections as _pcmc +import trnsysGUI.proforma.dialogs.convertDialog as _pcd import trnsysGUI.warningsAndErrors as _warn class FileSystemTreeView(_qtw.QTreeView): - def __init__(self, rootDirPath: _pl.Path) -> None: + def __init__(self, rootDirPath: _pl.Path, hasInternalPipingsProvider: _ip.HasInternalPipingsProvider) -> None: super().__init__(parent=None) + self._hasInternalPipingsProvider = hasInternalPipingsProvider + rootFolder = str(rootDirPath) self._model = _qtw.QFileSystemModel() @@ -89,14 +93,24 @@ def _loadFileIntoFolder(self) -> None: self._convertAndLoadProformaFileIntoFolder(sourceFilePath, targetFilePath) - @staticmethod - def _convertAndLoadProformaFileIntoFolder(sourceFilePath: _pl.Path, targetFilePath: _pl.Path) -> None: - suggestedHydraulicConnections = [_models.Connection(None, _models.InputPort("In"), _models.OutputPort("Out"))] + def _convertAndLoadProformaFileIntoFolder(self, sourceFilePath: _pl.Path, targetFilePath: _pl.Path) -> None: + hasInternalPipings = self._hasInternalPipingsProvider.getInternalPipings() + + maybeCancelled = _pcd.ConvertDialog.showDialogAndGetResults(hasInternalPipings, targetFilePath) + if _cancel.isCancelled(maybeCancelled): + return + dialogResult = _cancel.value(maybeCancelled) + + result = _pcmc.createModelConnectionsFromInternalPiping(dialogResult.internalPiping) + if _res.isError(result): + _warn.showMessageBox(_res.error(result).message) + return + suggestedHydraulicConnections = _res.value(result) maybeCancelled = _pro.convertXmltmfToDdck( sourceFilePath, suggestedHydraulicConnections, - targetFilePath, + dialogResult.outputFilePath, ) if _cancel.isCancelled(maybeCancelled): return diff --git a/trnsysGUI/internalPiping.py b/trnsysGUI/internalPiping.py index 3feaa3e6..a0719c7a 100644 --- a/trnsysGUI/internalPiping.py +++ b/trnsysGUI/internalPiping.py @@ -2,6 +2,7 @@ import dataclasses as _dc import typing as _tp +import collections.abc as _cabc import trnsysGUI.massFlowSolver.networkModel as _mfn @@ -39,9 +40,7 @@ def getModelPortItem( return modelPortItems[0] def getModelPortItems(self, graphicalPortItem: _pi.PortItemBase) -> _tp.Sequence[_mfn.PortItem]: - modelPortItems = [ - mpi for mpi, gpi in self.modelPortItemsToGraphicalPortItem.items() if gpi == graphicalPortItem - ] + modelPortItems = [mpi for mpi, gpi in self.modelPortItemsToGraphicalPortItem.items() if gpi == graphicalPortItem] return modelPortItems def getNode( @@ -83,3 +82,8 @@ def shallRenameOutputTemperaturesInHydraulicFile(cls) -> bool: def getInternalPiping(self) -> InternalPiping: raise NotImplementedError() + + +class HasInternalPipingsProvider: + def getInternalPipings(self) -> _cabc.Sequence[HasInternalPiping]: + raise NotImplementedError() diff --git a/trnsysGUI/proforma/createModelConnections.py b/trnsysGUI/proforma/createModelConnections.py index a5e36742..0b7b35b4 100644 --- a/trnsysGUI/proforma/createModelConnections.py +++ b/trnsysGUI/proforma/createModelConnections.py @@ -11,8 +11,8 @@ def createModelConnectionsFromInternalPiping( internalPiping: _ip.InternalPiping, -) -> _res.Result[_cabc.Set[_models.Connection]]: - connections = set() +) -> _res.Result[_cabc.Sequence[_models.Connection]]: + connections = [] for node in internalPiping.nodes: if not isinstance(node, _mfn.Pipe): return _res.Error(f"`{node.name}` is a `{node.__class__.__name__}`, but only direct pipes are supported.") @@ -23,7 +23,7 @@ def createModelConnectionsFromInternalPiping( connection = _models.Connection(pipe.name, inputPort, outputPort) - connections.add(connection) + connections.append(connection) return connections diff --git a/trnsysGUI/proforma/dialogs/_convert.ui b/trnsysGUI/proforma/dialogs/_convert.ui index b287894c..af803455 100644 --- a/trnsysGUI/proforma/dialogs/_convert.ui +++ b/trnsysGUI/proforma/dialogs/_convert.ui @@ -1,7 +1,7 @@ - HydraulicConnections - + Convert + 0 @@ -19,7 +19,7 @@ - <html><head/><body><p>Note: You can hover overd disabled buttons to get more information.</p></body></html> + <html><head/><body><p>Note: You can hover over disabled buttons to get more information.</p></body></html> Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop @@ -56,7 +56,7 @@ - + @@ -92,7 +92,7 @@ 0 - + diff --git a/trnsysGUI/proforma/dialogs/_hydraulicConnections.ui b/trnsysGUI/proforma/dialogs/_hydraulicConnections.ui index cc82c8b6..a1a4c634 100644 --- a/trnsysGUI/proforma/dialogs/_hydraulicConnections.ui +++ b/trnsysGUI/proforma/dialogs/_hydraulicConnections.ui @@ -182,7 +182,7 @@
- <html><head/><body><p>The inlet temperature at the nominal output when the direction of the mass flow is the reverse of the nominal direction. Not all types support reversing the mass flow. If your type doens't support it, you can leave this field empty.</p></body></html> + <html><head/><body><p>The inlet temperature at the nominal output when the direction of the mass flow is the reverse of the nominal direction. Not all types support reversing the mass flow. If your type doesn't support it, you can leave this field empty.</p></body></html> image: url(:/tango-icons/status/tango-icon-theme-0.8.90/scalable/status/dialog-information.svg); diff --git a/trnsysGUI/proforma/dialogs/convertDialog.py b/trnsysGUI/proforma/dialogs/convertDialog.py new file mode 100644 index 00000000..229dbab3 --- /dev/null +++ b/trnsysGUI/proforma/dialogs/convertDialog.py @@ -0,0 +1,72 @@ +import dataclasses as _dc +import pathlib as _pl +import collections.abc as _cabc + +import PyQt5.QtWidgets as _qtw +import PyQt5.QtCore as _qtc + +import trnsysGUI.common.cancelled as _cancel +import trnsysGUI.dialogs as _dlgs +import trnsysGUI.internalPiping as _ip + +_dlgs.assertThatLocalGeneratedUIModuleAndResourcesExist(__name__, moduleName="_UI_convert_generated") + +from . import _UI_convert_generated as _uigen # type: ignore[import] # pylint: disable=wrong-import-position + + +@_dc.dataclass +class DialogResult: + internalPiping: _ip.InternalPiping + outputFilePath: _pl.Path + + +class ConvertDialog(_qtw.QDialog, _uigen.Ui_Convert): + def __init__(self, hasInternalPipings: _cabc.Sequence[_ip.HasInternalPiping], outputFilePath: _pl.Path) -> None: + super().__init__() + self.setupUi(self) + + if not hasInternalPipings: + raise ValueError("Must have at least one internal piping.") + + self.outputFilePathLineEdit.setText(str(outputFilePath)) + + self._configureComponentcomponentComboBox(hasInternalPipings) + + componentName = outputFilePath.parent.name + currentIndex = self.componentComboBox.findText(componentName, _qtc.Qt.MatchContains) + currentIndex = max(0, currentIndex) + self.componentComboBox.setCurrentIndex(currentIndex) + currentInternalPiping = self.componentComboBox.currentData() + + self.dialogResult = DialogResult(currentInternalPiping, outputFilePath) + + self.okCancelButtonBox.accepted.connect(self.accept) + self.okCancelButtonBox.rejected.connect(self.reject) + + def _configureComponentcomponentComboBox(self, hasInternalPipings: _cabc.Sequence[_ip.HasInternalPiping]) -> None: + def getDisplayName(hip: _ip.HasInternalPiping) -> str: + return hip.getDisplayName() + + sortedHasInternalPiping = sorted(hasInternalPipings, key=getDisplayName) + for hasInternalPiping in sortedHasInternalPiping: + displayName = hasInternalPiping.getDisplayName() + internalPiping = hasInternalPiping.getInternalPiping() + + self.componentComboBox.addItem(displayName, internalPiping) + self.componentComboBox.activated.connect(self._onActivated) + + def _onActivated(self, newIndex: int) -> None: + newData = self.componentComboBox.itemData(newIndex) + self.dialogResult.internalPiping = newData + + @staticmethod + def showDialogAndGetResults( + hasInternalPipings: _cabc.Sequence[_ip.HasInternalPiping], outputFilePath: _pl.Path + ) -> _cancel.MaybeCancelled[DialogResult]: + dialog = ConvertDialog(hasInternalPipings, outputFilePath) + returnValue = dialog.exec() + + if returnValue == _qtw.QDialog.Rejected: + return _cancel.CANCELLED + + return dialog.dialogResult diff --git a/trnsysGUI/proforma/dialogs/editHydraulicConnectionsDialog.py b/trnsysGUI/proforma/dialogs/editHydraulicConnectionsDialog.py index 8602b985..19aa55b5 100644 --- a/trnsysGUI/proforma/dialogs/editHydraulicConnectionsDialog.py +++ b/trnsysGUI/proforma/dialogs/editHydraulicConnectionsDialog.py @@ -200,7 +200,7 @@ def _reloadConnections(self) -> None: self._reloadSummaryText() def _reloadSummaryText(self) -> None: - overallSummary = "\n\n".join(c.getSummary() for c in self.hydraulicConnections) + overallSummary = "\n".join(s for c in self.hydraulicConnections if (s := c.getSummary())) self.summaryTextEdit.setPlainText(overallSummary) def _resetOkButton(self) -> None: diff --git a/trnsysGUI/proforma/models.py b/trnsysGUI/proforma/models.py index 1fbd2552..78f15504 100644 --- a/trnsysGUI/proforma/models.py +++ b/trnsysGUI/proforma/models.py @@ -129,7 +129,6 @@ def getSummary(self) -> str: summary = f"""\ ** {connectionName} {subSummaries} - """ return summary diff --git a/trnsysGUI/pytrnsys-gui.pyproject b/trnsysGUI/pytrnsys-gui.pyproject index 8a18ade4..f8d22c32 100644 --- a/trnsysGUI/pytrnsys-gui.pyproject +++ b/trnsysGUI/pytrnsys-gui.pyproject @@ -1,3 +1,3 @@ { - "files": ["hydraulicLoops/_dialogs/split/_dialog.ui","pumpsAndTaps/_dialog.ui","resources/resources.qrc","hydraulicLoops/_dialogs/merge/_dialog.ui","dialogs/connections/_doublePipe.ui","hydraulicLoops/_dialogs/edit/_dialog.ui", "proforma/_dialogs/_convert.ui", "proforma/_dialogs/_hydraulicConnections.ui"] + "files": ["hydraulicLoops/_dialogs/split/_dialog.ui","pumpsAndTaps/_dialog.ui","resources/resources.qrc","hydraulicLoops/_dialogs/merge/_dialog.ui","dialogs/connections/_doublePipe.ui","hydraulicLoops/_dialogs/edit/_dialog.ui", "proforma/dialogs/_convert.ui", "proforma/dialogs/_hydraulicConnections.ui"] } From b67b1cb12886b0aba59e467e20a792bc05e49737 Mon Sep 17 00:00:00 2001 From: Damian Birchler Date: Mon, 29 Jul 2024 17:39:33 +0200 Subject: [PATCH 23/33] Satisfy static checkers. --- trnsysGUI/diagram/fileSystemTreeView.py | 14 +++++++------- trnsysGUI/proforma/convertXmlTmfToDdck.py | 4 +--- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/trnsysGUI/diagram/fileSystemTreeView.py b/trnsysGUI/diagram/fileSystemTreeView.py index d4fc16c2..f5c649fb 100644 --- a/trnsysGUI/diagram/fileSystemTreeView.py +++ b/trnsysGUI/diagram/fileSystemTreeView.py @@ -96,16 +96,16 @@ def _loadFileIntoFolder(self) -> None: def _convertAndLoadProformaFileIntoFolder(self, sourceFilePath: _pl.Path, targetFilePath: _pl.Path) -> None: hasInternalPipings = self._hasInternalPipingsProvider.getInternalPipings() - maybeCancelled = _pcd.ConvertDialog.showDialogAndGetResults(hasInternalPipings, targetFilePath) - if _cancel.isCancelled(maybeCancelled): + dialogMaybeCancelled = _pcd.ConvertDialog.showDialogAndGetResults(hasInternalPipings, targetFilePath) + if _cancel.isCancelled(dialogMaybeCancelled): return - dialogResult = _cancel.value(maybeCancelled) + dialogResult = _cancel.value(dialogMaybeCancelled) - result = _pcmc.createModelConnectionsFromInternalPiping(dialogResult.internalPiping) - if _res.isError(result): - _warn.showMessageBox(_res.error(result).message) + createConnectionsResult = _pcmc.createModelConnectionsFromInternalPiping(dialogResult.internalPiping) + if _res.isError(createConnectionsResult): + _warn.showMessageBox(_res.error(createConnectionsResult).message) return - suggestedHydraulicConnections = _res.value(result) + suggestedHydraulicConnections = _res.value(createConnectionsResult) maybeCancelled = _pro.convertXmltmfToDdck( sourceFilePath, diff --git a/trnsysGUI/proforma/convertXmlTmfToDdck.py b/trnsysGUI/proforma/convertXmlTmfToDdck.py index b7478cc9..8c05a519 100644 --- a/trnsysGUI/proforma/convertXmlTmfToDdck.py +++ b/trnsysGUI/proforma/convertXmlTmfToDdck.py @@ -2,16 +2,14 @@ import dataclasses as _dc import pathlib as _pl import typing as _tp +import xml.etree.ElementTree as _etree import jinja2 as _jj import xmlschema as _xs -import xml.etree.ElementTree as _etree import pytrnsys.utils.result as _res - import trnsysGUI.common.cancelled as _cancel import trnsysGUI.proforma.dialogs.editHydraulicConnectionsDialog as _ehcd - from . import createModelConnections as _cmcs from . import models as _models From 6aaa8f1aef4c968add2b990684067324af088cbe Mon Sep 17 00:00:00 2001 From: Damian Birchler Date: Tue, 30 Jul 2024 13:53:45 +0200 Subject: [PATCH 24/33] Allow loading ddcks on components. --- trnsysGUI/BlockItem.py | 35 +++++--- trnsysGUI/diagram/fileSystemTreeView.py | 70 +--------------- trnsysGUI/loadDdckFile.py | 90 +++++++++++++++++++++ trnsysGUI/proforma/dialogs/_convert.ui | 46 +---------- trnsysGUI/proforma/dialogs/convertDialog.py | 37 ++++++--- trnsysGUI/storageTank/widget.py | 24 +----- 6 files changed, 150 insertions(+), 152 deletions(-) create mode 100644 trnsysGUI/loadDdckFile.py diff --git a/trnsysGUI/BlockItem.py b/trnsysGUI/BlockItem.py index c06748ae..ff76c870 100644 --- a/trnsysGUI/BlockItem.py +++ b/trnsysGUI/BlockItem.py @@ -1,5 +1,6 @@ # pylint: disable=invalid-name +import pathlib as _pl import typing as _tp import PyQt5.QtCore as _qtc @@ -11,6 +12,7 @@ import trnsysGUI.idGenerator as _id import trnsysGUI.images as _img import trnsysGUI.internalPiping as _ip +import trnsysGUI.loadDdckFile as _ld import trnsysGUI.moveCommand as _mc @@ -31,6 +33,9 @@ def __init__(self, trnsysType: str, editor, displayName: str) -> None: self.displayName = displayName + self._ddckFileLoader = _ld.DdckFileLoader(editor) + self._projectDirPath = _pl.Path(editor.projectFolder) + self.inputs: list[_pib.PortItemBase] = [] self.outputs: list[_pib.PortItemBase] = [] @@ -103,22 +108,28 @@ def setDisplayName(self, newName: str) -> None: def contextMenuEvent(self, event): menu = _qtw.QMenu() - rr = _img.ROTATE_TO_RIGHT_PNG.icon() - a2 = menu.addAction(rr, "Rotate Block clockwise") - a2.triggered.connect(self.rotateBlockCW) + rotateCwAction = menu.addAction(_img.ROTATE_TO_RIGHT_PNG.icon(), "Rotate Block clockwise") + rotateCwAction.triggered.connect(self.rotateBlockCW) + + rotateCcwAction = menu.addAction(_img.ROTATE_LEFT_PNG.icon(), "Rotate Block counter-clockwise") + rotateCcwAction.triggered.connect(self.rotateBlockCCW) + + resetRotationAction = menu.addAction("Reset Rotation") + resetRotationAction.triggered.connect(self.resetRotation) - ll = _img.ROTATE_LEFT_PNG.icon() - a3 = menu.addAction(ll, "Rotate Block counter-clockwise") - a3.triggered.connect(self.rotateBlockCCW) + deleteBlockAction = menu.addAction("Delete this Block") + deleteBlockAction.triggered.connect(self.deleteBlockCom) - a4 = menu.addAction("Reset Rotation") - a4.triggered.connect(self.resetRotation) + loadDdckAction = menu.addAction("Load ddck file...") + loadDdckAction.triggered.connect(self._onLoadDdckActionTriggered) - c1 = menu.addAction("Delete this Block") - c1.triggered.connect(self.deleteBlockCom) + self._addChildContextMenuActions(menu) menu.exec_(event.screenPos()) + def _addChildContextMenuActions(self, contextMenu: _qtw.QMenu) -> None: + pass + def mouseDoubleClickEvent(self, event: _qtw.QGraphicsSceneMouseEvent) -> None: # pylint: disable=unused-argument self.editor.showBlockDlg(self) @@ -143,6 +154,10 @@ def mouseReleaseEvent(self, event): def changeSize(self): self._positionLabel() + def _onLoadDdckActionTriggered(self) -> None: + targetDirPath = self._projectDirPath / "ddck" / self.displayName + self._ddckFileLoader.loadDdckFile(targetDirPath) + def _positionLabel(self): width, _ = self._getCappedWidthAndHeight() rect = self.label.boundingRect() diff --git a/trnsysGUI/diagram/fileSystemTreeView.py b/trnsysGUI/diagram/fileSystemTreeView.py index f5c649fb..beb83fd4 100644 --- a/trnsysGUI/diagram/fileSystemTreeView.py +++ b/trnsysGUI/diagram/fileSystemTreeView.py @@ -1,18 +1,12 @@ import os as _os import pathlib as _pl -import shutil as _su import PyQt5.QtCore as _qtc import PyQt5.QtGui as _qtg import PyQt5.QtWidgets as _qtw -import pytrnsys.utils.result as _res - import trnsysGUI.internalPiping as _ip -import trnsysGUI.common.cancelled as _cancel -import trnsysGUI.proforma.convertXmlTmfToDdck as _pro -import trnsysGUI.proforma.createModelConnections as _pcmc -import trnsysGUI.proforma.dialogs.convertDialog as _pcd +import trnsysGUI.loadDdckFile as _ld import trnsysGUI.warningsAndErrors as _warn @@ -20,7 +14,7 @@ class FileSystemTreeView(_qtw.QTreeView): def __init__(self, rootDirPath: _pl.Path, hasInternalPipingsProvider: _ip.HasInternalPipingsProvider) -> None: super().__init__(parent=None) - self._hasInternalPipingsProvider = hasInternalPipingsProvider + self._ddckLoader = _ld.DdckFileLoader(hasInternalPipingsProvider) rootFolder = str(rootDirPath) @@ -59,65 +53,7 @@ def _openCurrentFileOrNoOp(self) -> None: def _loadFileIntoFolder(self) -> None: currentPath = self._getCurrentPath() targetDirPath = currentPath if currentPath.is_dir() else currentPath.parent - - sourceFilePathString = _qtw.QFileDialog.getOpenFileName(self, "Load file")[0] - if not sourceFilePathString: - return - - sourceFilePath = _pl.Path(sourceFilePathString) - - sourceSuffix = sourceFilePath.suffix - targetSuffix = ".ddck" if sourceSuffix == ".xmltml" else sourceSuffix - - targetFilePathStem = targetDirPath / sourceFilePath.stem - targetFilePath = targetFilePathStem.with_suffix(targetSuffix) - - if targetFilePath.is_dir(): - message = f"""\ -A directory of the name `{targetFilePath.name}` already exists. Please change the name of the file before -importing or remove the directory.""" - _warn.showMessageBox(message, _warn.Title.WARNING) - return - - if targetFilePath.is_file(): - message = f"""\ - A file of the name `{targetFilePath.name}` already exists. Do you want to overwrite it?""" - - standardButton = _qtw.QMessageBox.question(None, "Overwrite file?", message) - if standardButton != _qtw.QMessageBox.StandardButton.Yes: # pylint: disable=no-member - return - - if sourceSuffix != ".xmltmf": - _su.copy(sourceFilePath, targetFilePath) - return - - self._convertAndLoadProformaFileIntoFolder(sourceFilePath, targetFilePath) - - def _convertAndLoadProformaFileIntoFolder(self, sourceFilePath: _pl.Path, targetFilePath: _pl.Path) -> None: - hasInternalPipings = self._hasInternalPipingsProvider.getInternalPipings() - - dialogMaybeCancelled = _pcd.ConvertDialog.showDialogAndGetResults(hasInternalPipings, targetFilePath) - if _cancel.isCancelled(dialogMaybeCancelled): - return - dialogResult = _cancel.value(dialogMaybeCancelled) - - createConnectionsResult = _pcmc.createModelConnectionsFromInternalPiping(dialogResult.internalPiping) - if _res.isError(createConnectionsResult): - _warn.showMessageBox(_res.error(createConnectionsResult).message) - return - suggestedHydraulicConnections = _res.value(createConnectionsResult) - - maybeCancelled = _pro.convertXmltmfToDdck( - sourceFilePath, - suggestedHydraulicConnections, - dialogResult.outputFilePath, - ) - if _cancel.isCancelled(maybeCancelled): - return - result = _cancel.value(maybeCancelled) - if _res.isError(result): - _warn.showMessageBox(_res.error(result).message) - return + self._ddckLoader.loadDdckFile(targetDirPath) def _deleteCurrentFile(self) -> None: path = self._getCurrentPath() diff --git a/trnsysGUI/loadDdckFile.py b/trnsysGUI/loadDdckFile.py new file mode 100644 index 00000000..e40ca7d8 --- /dev/null +++ b/trnsysGUI/loadDdckFile.py @@ -0,0 +1,90 @@ +import dataclasses as _dc +import pathlib as _pl +import shutil as _su + +import PyQt5.QtWidgets as _qtw + +import pytrnsys.utils.result as _res + +import trnsysGUI.common.cancelled as _cancel +import trnsysGUI.internalPiping as _ip +import trnsysGUI.proforma.convertXmlTmfToDdck as _pro +import trnsysGUI.proforma.createModelConnections as _pcmc +import trnsysGUI.proforma.dialogs.convertDialog as _pcd +import trnsysGUI.warningsAndErrors as _warn + + +@_dc.dataclass +class DdckFileLoader: + hasInternalPipingsProvider: _ip.HasInternalPipingsProvider + + def loadDdckFile(self, targetDirPath: _pl.Path) -> None: + if not targetDirPath.is_dir(): + raise ValueError("Not a directory.", targetDirPath) + + sourceFilePathString, _ = _qtw.QFileDialog.getOpenFileName(None, "Load file") + if not sourceFilePathString: + return + + sourceFilePath = _pl.Path(sourceFilePathString) + + isSourceProformaFile = self._isProformaFilePath(sourceFilePath) + targetSuffix = ".ddck" if isSourceProformaFile else sourceFilePath.suffix + + targetFilePathStem = targetDirPath / sourceFilePath.stem + targetFilePath = targetFilePathStem.with_suffix(targetSuffix) + + if targetFilePath.is_dir(): + message = f"""\ + A directory of the name `{targetFilePath.name}` already exists. Please change the name of the file before + importing or remove the directory.""" + _warn.showMessageBox(message, _warn.Title.WARNING) + return + + if targetFilePath.is_file(): + message = f"""\ + A file of the name `{targetFilePath.name}` already exists. Do you want to overwrite it?""" + + standardButton = _qtw.QMessageBox.question(None, "Overwrite file?", message) + if standardButton != _qtw.QMessageBox.StandardButton.Yes: # pylint: disable=no-member + return + + if isSourceProformaFile: + _su.copy(sourceFilePath, targetFilePath) + return + + self._convertAndLoadProformaFileIntoFolder(sourceFilePath, targetFilePath) + + def _convertAndLoadProformaFileIntoFolder(self, sourceFilePath: _pl.Path, targetFilePath: _pl.Path) -> None: + hasInternalPipingsWithDdckPlaceholders = [ + hip for hip in self.hasInternalPipingsProvider.getInternalPipings() if hip.hasDdckPlaceHolders() + ] + + dialogMaybeCancelled = _pcd.ConvertDialog.showDialogAndGetResults( + hasInternalPipingsWithDdckPlaceholders, targetFilePath + ) + if _cancel.isCancelled(dialogMaybeCancelled): + return + dialogResult = _cancel.value(dialogMaybeCancelled) + + createConnectionsResult = _pcmc.createModelConnectionsFromInternalPiping(dialogResult.internalPiping) + if _res.isError(createConnectionsResult): + _warn.showMessageBox(_res.error(createConnectionsResult).message) + return + suggestedHydraulicConnections = _res.value(createConnectionsResult) + + maybeCancelled = _pro.convertXmltmfToDdck( + sourceFilePath, + suggestedHydraulicConnections, + dialogResult.outputFilePath, + ) + if _cancel.isCancelled(maybeCancelled): + return + result = _cancel.value(maybeCancelled) + if _res.isError(result): + _warn.showMessageBox(_res.error(result).message) + return + + @staticmethod + def _isProformaFilePath(filePath: _pl.Path) -> bool: + return filePath.suffix == ".xmltmf" diff --git a/trnsysGUI/proforma/dialogs/_convert.ui b/trnsysGUI/proforma/dialogs/_convert.ui index af803455..dbcb228a 100644 --- a/trnsysGUI/proforma/dialogs/_convert.ui +++ b/trnsysGUI/proforma/dialogs/_convert.ui @@ -7,40 +7,13 @@ 0 0 644 - 123 + 83 Convert Proforma to ddck file... - - - - - - - <html><head/><body><p>Note: You can hover over disabled buttons to get more information.</p></body></html> - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - + @@ -95,7 +68,7 @@ - + 0 @@ -123,19 +96,6 @@ - - - - Qt::Vertical - - - - 20 - 0 - - - - diff --git a/trnsysGUI/proforma/dialogs/convertDialog.py b/trnsysGUI/proforma/dialogs/convertDialog.py index 229dbab3..e1be7d21 100644 --- a/trnsysGUI/proforma/dialogs/convertDialog.py +++ b/trnsysGUI/proforma/dialogs/convertDialog.py @@ -29,21 +29,21 @@ def __init__(self, hasInternalPipings: _cabc.Sequence[_ip.HasInternalPiping], ou raise ValueError("Must have at least one internal piping.") self.outputFilePathLineEdit.setText(str(outputFilePath)) + self.outputFilePathLineEdit.textChanged.connect(self._onOutputFilePathLineEditTextChanged) - self._configureComponentcomponentComboBox(hasInternalPipings) + self._configureComponentComboBox(hasInternalPipings) componentName = outputFilePath.parent.name - currentIndex = self.componentComboBox.findText(componentName, _qtc.Qt.MatchContains) - currentIndex = max(0, currentIndex) - self.componentComboBox.setCurrentIndex(currentIndex) - currentInternalPiping = self.componentComboBox.currentData() + currentInternalPiping = self._setAndGetCurrentInternalPiping(componentName) self.dialogResult = DialogResult(currentInternalPiping, outputFilePath) + self.chooseOutputFilePathPushButton.pressed.connect(self._onChooseOutputFilePathPushButtonPressed) + self.okCancelButtonBox.accepted.connect(self.accept) self.okCancelButtonBox.rejected.connect(self.reject) - def _configureComponentcomponentComboBox(self, hasInternalPipings: _cabc.Sequence[_ip.HasInternalPiping]) -> None: + def _configureComponentComboBox(self, hasInternalPipings: _cabc.Sequence[_ip.HasInternalPiping]) -> None: def getDisplayName(hip: _ip.HasInternalPiping) -> str: return hip.getDisplayName() @@ -53,11 +53,28 @@ def getDisplayName(hip: _ip.HasInternalPiping) -> str: internalPiping = hasInternalPiping.getInternalPiping() self.componentComboBox.addItem(displayName, internalPiping) - self.componentComboBox.activated.connect(self._onActivated) - def _onActivated(self, newIndex: int) -> None: - newData = self.componentComboBox.itemData(newIndex) - self.dialogResult.internalPiping = newData + def _setAndGetCurrentInternalPiping(self, componentName: str) -> _ip.InternalPiping: + currentIndex = self.componentComboBox.findText(componentName, _qtc.Qt.MatchContains) + currentIndex = max(0, currentIndex) + self.componentComboBox.setCurrentIndex(currentIndex) + currentInternalPiping = self.componentComboBox.currentData() + return currentInternalPiping + + def _onOutputFilePathLineEditTextChanged(self, newText: str) -> None: + self.dialogResult.outputFilePath = _pl.Path(newText) + + def _onChooseOutputFilePathPushButtonPressed(self) -> None: + currentOutputFilePathString = self.outputFilePathLineEdit.text() + + newOutputFilePathString, _ = _qtw.QFileDialog.getSaveFileName( + None, "Select ouput file path...", currentOutputFilePathString + ) + + if not newOutputFilePathString: + return + + self.outputFilePathLineEdit.setText(newOutputFilePathString) @staticmethod def showDialogAndGetResults( diff --git a/trnsysGUI/storageTank/widget.py b/trnsysGUI/storageTank/widget.py index a82b2c61..03628d5a 100644 --- a/trnsysGUI/storageTank/widget.py +++ b/trnsysGUI/storageTank/widget.py @@ -118,9 +118,7 @@ def addDirectPortPair( # pylint: disable=too-many-arguments inputPort.id = portIds.inputId outputPort.id = portIds.outputId - directPortPair = DirectPortPair( - trnsysId, inputPort, outputPort, relativeInputHeight, relativeOutputHeight, side - ) + directPortPair = DirectPortPair(trnsysId, inputPort, outputPort, relativeInputHeight, relativeOutputHeight, side) self.directPortPairs.append(directPortPair) @@ -339,28 +337,10 @@ def _getSingleOrNone(iterable: _tp.Iterable[_T_co]) -> _tp.Optional[_T_co]: return sequence[0] # Misc - def contextMenuEvent(self, event): - menu = _qtw.QMenu() - - rotateRightIcon = _img.ROTATE_TO_RIGHT_PNG.icon() - rotateRightAction = menu.addAction(rotateRightIcon, "Rotate Block clockwise") - rotateRightAction.triggered.connect(self.rotateBlockCW) - - rotateLeftIcon = _img.ROTATE_LEFT_PNG.icon() - rotateLeftIcon = menu.addAction(rotateLeftIcon, "Rotate Block counter-clockwise") - rotateLeftIcon.triggered.connect(self.rotateBlockCCW) - - resetRotationAction = menu.addAction("Reset Rotation") - resetRotationAction.triggered.connect(self.resetRotation) - - deleteBlockAction = menu.addAction("Delete this Block") - deleteBlockAction.triggered.connect(self.deleteBlockCom) - + def _addChildContextMenuActions(self, contextMenu: _qtw.QMenu) -> None: exportDdckAction = menu.addAction("Export ddck") exportDdckAction.triggered.connect(self.exportDck) - menu.exec(event.screenPos()) - def mouseDoubleClickEvent(self, event: _qtw.QGraphicsSceneMouseEvent) -> None: renameHelper = _rename.RenameHelper(self.editor.namesManager) dialog = ConfigureStorageDialog(self, self.editor, renameHelper, self.editor.projectFolder) From 9cbc50e1b45d3d67f3b6167ae9d9cd725a51768b Mon Sep 17 00:00:00 2001 From: Damian Birchler Date: Tue, 30 Jul 2024 14:28:27 +0200 Subject: [PATCH 25/33] Distinguish between components with a (just?) a ddck folder and those with placeholders. --- trnsysGUI/BlockItem.py | 12 ------------ trnsysGUI/blockItemHasInternalPiping.py | 18 ++++++++++++++++++ trnsysGUI/blockItems/getBlockItem.py | 2 +- trnsysGUI/components/ddckFolderHelpers.py | 2 +- trnsysGUI/deleteBlockCommand.py | 2 +- trnsysGUI/diagram/Editor.py | 4 +++- trnsysGUI/internalPiping.py | 8 +++++++- trnsysGUI/names/dialog.py | 5 +---- trnsysGUI/storageTank/widget.py | 20 ++++++++++++++++++-- 9 files changed, 50 insertions(+), 23 deletions(-) diff --git a/trnsysGUI/BlockItem.py b/trnsysGUI/BlockItem.py index ff76c870..3e65a03e 100644 --- a/trnsysGUI/BlockItem.py +++ b/trnsysGUI/BlockItem.py @@ -1,6 +1,5 @@ # pylint: disable=invalid-name -import pathlib as _pl import typing as _tp import PyQt5.QtCore as _qtc @@ -12,7 +11,6 @@ import trnsysGUI.idGenerator as _id import trnsysGUI.images as _img import trnsysGUI.internalPiping as _ip -import trnsysGUI.loadDdckFile as _ld import trnsysGUI.moveCommand as _mc @@ -33,9 +31,6 @@ def __init__(self, trnsysType: str, editor, displayName: str) -> None: self.displayName = displayName - self._ddckFileLoader = _ld.DdckFileLoader(editor) - self._projectDirPath = _pl.Path(editor.projectFolder) - self.inputs: list[_pib.PortItemBase] = [] self.outputs: list[_pib.PortItemBase] = [] @@ -120,9 +115,6 @@ def contextMenuEvent(self, event): deleteBlockAction = menu.addAction("Delete this Block") deleteBlockAction.triggered.connect(self.deleteBlockCom) - loadDdckAction = menu.addAction("Load ddck file...") - loadDdckAction.triggered.connect(self._onLoadDdckActionTriggered) - self._addChildContextMenuActions(menu) menu.exec_(event.screenPos()) @@ -154,10 +146,6 @@ def mouseReleaseEvent(self, event): def changeSize(self): self._positionLabel() - def _onLoadDdckActionTriggered(self) -> None: - targetDirPath = self._projectDirPath / "ddck" / self.displayName - self._ddckFileLoader.loadDdckFile(targetDirPath) - def _positionLabel(self): width, _ = self._getCappedWidthAndHeight() rect = self.label.boundingRect() diff --git a/trnsysGUI/blockItemHasInternalPiping.py b/trnsysGUI/blockItemHasInternalPiping.py index 0359f118..970d28ce 100644 --- a/trnsysGUI/blockItemHasInternalPiping.py +++ b/trnsysGUI/blockItemHasInternalPiping.py @@ -1,7 +1,11 @@ +import pathlib as _pl import typing as _tp +from PyQt5 import QtWidgets as _qtw + import trnsysGUI.BlockItem as _bi import trnsysGUI.internalPiping as _ip +import trnsysGUI.loadDdckFile as _ld class BlockItemHasInternalPiping(_bi.BlockItem, _ip.HasInternalPiping): @@ -11,3 +15,17 @@ def getDisplayName(self) -> str: def getInternalPiping(self) -> _ip.InternalPiping: raise NotImplementedError() + + @_tp.override + def _addChildContextMenuActions(self, contextMenu: _qtw.QMenu) -> None: + if self.hasDdckDirectory(): + loadDdckAction = contextMenu.addAction("Load ddck file...") + loadDdckAction.triggered.connect(self._onLoadDdckActionTriggered) + + def _onLoadDdckActionTriggered(self) -> None: + ddckFileLoader = _ld.DdckFileLoader(self.editor) + + projectDirPath = _pl.Path(self.editor.projectFolder) + targetDirPath = projectDirPath / "ddck" / self.displayName + + ddckFileLoader.loadDdckFile(targetDirPath) diff --git a/trnsysGUI/blockItems/getBlockItem.py b/trnsysGUI/blockItems/getBlockItem.py index 65da71f7..a4f25aa6 100644 --- a/trnsysGUI/blockItems/getBlockItem.py +++ b/trnsysGUI/blockItems/getBlockItem.py @@ -141,7 +141,7 @@ def _addOrCreateAndAddDisplayName( if not displayName: createNamingHelper = _cname.CreateNamingHelper(namesManager) checkDdckFolder = ( - blockItemClass.hasDdckPlaceHolders() if issubclass(blockItemClass, _ip.HasInternalPiping) else False + blockItemClass.hasDdckDirectory() if issubclass(blockItemClass, _ip.HasInternalPiping) else False ) displayName = createNamingHelper.generateName( baseDisplayName, checkDdckFolder, firstGeneratedNameHasNumber=False diff --git a/trnsysGUI/components/ddckFolderHelpers.py b/trnsysGUI/components/ddckFolderHelpers.py index a45f25c3..a5db61c1 100644 --- a/trnsysGUI/components/ddckFolderHelpers.py +++ b/trnsysGUI/components/ddckFolderHelpers.py @@ -33,7 +33,7 @@ def moveComponentDdckFolderIfNecessary( def hasComponentDdckFolder(blockItem: _bi.BlockItem) -> bool: - hasDdckFolder = blockItem.hasDdckPlaceHolders() if isinstance(blockItem, _ip.HasInternalPiping) else False + hasDdckFolder = blockItem.hasDdckDirectory() if isinstance(blockItem, _ip.HasInternalPiping) else False return hasDdckFolder diff --git a/trnsysGUI/deleteBlockCommand.py b/trnsysGUI/deleteBlockCommand.py index 69eb1252..fb7cdc6d 100644 --- a/trnsysGUI/deleteBlockCommand.py +++ b/trnsysGUI/deleteBlockCommand.py @@ -52,7 +52,7 @@ def redo(self): _dfh.maybeDeleteNonEmptyComponentDdckFolder(self._blockItem, _pl.Path(self._editor.projectFolder)) def undo(self): - checkDdckFolder = self._blockItem.hasDdckPlaceHolders() + checkDdckFolder = self._blockItem.hasDdckDirectory() oldName = self._blockItem.displayName generatedNamePrefix = self._blockItem.name diff --git a/trnsysGUI/diagram/Editor.py b/trnsysGUI/diagram/Editor.py index 4fa7c163..15612b1c 100644 --- a/trnsysGUI/diagram/Editor.py +++ b/trnsysGUI/diagram/Editor.py @@ -179,7 +179,9 @@ def _setupWidgetHierarchy(self): libraryBrowserAndContextInfoSplitter = _qtw.QSplitter(_qtc.Qt.Orientation.Vertical) libraryBrowserAndContextInfoSplitter.addWidget(libraryBrowserView) libraryBrowserAndContextInfoSplitter.addWidget(self.contextInfoList) - _sizes.setRelativeSizes(libraryBrowserAndContextInfoSplitter, [libraryBrowserView, self.contextInfoList], [3, 1]) + _sizes.setRelativeSizes( + libraryBrowserAndContextInfoSplitter, [libraryBrowserView, self.contextInfoList], [3, 1] + ) self._consoleWidget = _con.QtConsoleWidget() diff --git a/trnsysGUI/internalPiping.py b/trnsysGUI/internalPiping.py index a0719c7a..e8feb25d 100644 --- a/trnsysGUI/internalPiping.py +++ b/trnsysGUI/internalPiping.py @@ -40,7 +40,9 @@ def getModelPortItem( return modelPortItems[0] def getModelPortItems(self, graphicalPortItem: _pi.PortItemBase) -> _tp.Sequence[_mfn.PortItem]: - modelPortItems = [mpi for mpi, gpi in self.modelPortItemsToGraphicalPortItem.items() if gpi == graphicalPortItem] + modelPortItems = [ + mpi for mpi, gpi in self.modelPortItemsToGraphicalPortItem.items() if gpi == graphicalPortItem + ] return modelPortItems def getNode( @@ -76,6 +78,10 @@ def getDisplayName(self) -> str: def hasDdckPlaceHolders(cls) -> bool: return True + @classmethod + def hasDdckDirectory(cls) -> bool: + return cls.hasDdckPlaceHolders() + @classmethod def shallRenameOutputTemperaturesInHydraulicFile(cls) -> bool: return True diff --git a/trnsysGUI/names/dialog.py b/trnsysGUI/names/dialog.py index 914a2cb2..b363ee59 100644 --- a/trnsysGUI/names/dialog.py +++ b/trnsysGUI/names/dialog.py @@ -5,7 +5,6 @@ import pytrnsys.utils.result as _res import trnsysGUI.BlockItem as _bi import trnsysGUI.components.ddckFolderHelpers as _dfh -import trnsysGUI.internalPiping as _ip import trnsysGUI.names.rename as _rename import trnsysGUI.warningsAndErrors as _werrors @@ -26,9 +25,7 @@ def acceptedEdit(self) -> None: newName = self._displayNameLineEdit.text() oldName = self._blockItem.displayName - hasDdckFolder = ( - self._blockItem.hasDdckPlaceHolders() if isinstance(self._blockItem, _ip.HasInternalPiping) else False - ) + hasDdckFolder = _dfh.hasComponentDdckFolder(self._blockItem) result = self._renameHelper.canRename(oldName, newName, hasDdckFolder) if _res.isError(result): diff --git a/trnsysGUI/storageTank/widget.py b/trnsysGUI/storageTank/widget.py index 03628d5a..6a8257af 100644 --- a/trnsysGUI/storageTank/widget.py +++ b/trnsysGUI/storageTank/widget.py @@ -73,6 +73,11 @@ def getDisplayName(self) -> str: def hasDdckPlaceHolders(cls) -> bool: return False + @classmethod + @_tp.override + def hasDdckDirectory(cls) -> bool: + return True + @property def leftDirectPortPairsPortItems(self): return self._getDirectPortPairPortItems(_sd.Side.LEFT) @@ -118,7 +123,9 @@ def addDirectPortPair( # pylint: disable=too-many-arguments inputPort.id = portIds.inputId outputPort.id = portIds.outputId - directPortPair = DirectPortPair(trnsysId, inputPort, outputPort, relativeInputHeight, relativeOutputHeight, side) + directPortPair = DirectPortPair( + trnsysId, inputPort, outputPort, relativeInputHeight, relativeOutputHeight, side + ) self.directPortPairs.append(directPortPair) @@ -338,7 +345,7 @@ def _getSingleOrNone(iterable: _tp.Iterable[_T_co]) -> _tp.Optional[_T_co]: # Misc def _addChildContextMenuActions(self, contextMenu: _qtw.QMenu) -> None: - exportDdckAction = menu.addAction("Export ddck") + exportDdckAction = contextMenu.addAction("Export ddck") exportDdckAction.triggered.connect(self.exportDck) def mouseDoubleClickEvent(self, event: _qtw.QGraphicsSceneMouseEvent) -> None: @@ -411,6 +418,15 @@ def exportDck(self) -> None: # pylint: disable=too-many-locals,too-many-stateme projectDirPath = _pl.Path(self.editor.projectFolder) ddckDirPath = _dfh.getComponentDdckDirPath(self.displayName, projectDirPath) + if not ddckDirPath.is_dir(): + _qtw.QMessageBox.information( + None, + "Component ddck directory doesn't exist", + f"The component ddck directory `{ddckDirPath}` does not exist. The ddck file will not be exported. " + f"Please create the directory and try again.", + ) + return + tool.createDDck(str(ddckDirPath), self.displayName, typeFile="ddck") def _getDirectPairPortsForExport(self): From 7b9306692ca76b36a7ad6865cb7212e351599683 Mon Sep 17 00:00:00 2001 From: Damian Birchler Date: Tue, 30 Jul 2024 14:28:51 +0200 Subject: [PATCH 26/33] Convert Proformas, not all the other files. --- trnsysGUI/loadDdckFile.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/trnsysGUI/loadDdckFile.py b/trnsysGUI/loadDdckFile.py index e40ca7d8..39732596 100644 --- a/trnsysGUI/loadDdckFile.py +++ b/trnsysGUI/loadDdckFile.py @@ -5,7 +5,6 @@ import PyQt5.QtWidgets as _qtw import pytrnsys.utils.result as _res - import trnsysGUI.common.cancelled as _cancel import trnsysGUI.internalPiping as _ip import trnsysGUI.proforma.convertXmlTmfToDdck as _pro @@ -49,7 +48,7 @@ def loadDdckFile(self, targetDirPath: _pl.Path) -> None: if standardButton != _qtw.QMessageBox.StandardButton.Yes: # pylint: disable=no-member return - if isSourceProformaFile: + if not isSourceProformaFile: _su.copy(sourceFilePath, targetFilePath) return From af57d7387d73cc36b1d55178934ed0fdd6707d81 Mon Sep 17 00:00:00 2001 From: Damian Birchler Date: Tue, 30 Jul 2024 14:36:52 +0200 Subject: [PATCH 27/33] Revert inadvertently modified file. --- .../TRIHP_dualSource/TRIHP_dualSource.json | 152 ++++++------------ 1 file changed, 52 insertions(+), 100 deletions(-) diff --git a/data/examplesToBeCompleted/TRIHP_dualSource/TRIHP_dualSource.json b/data/examplesToBeCompleted/TRIHP_dualSource/TRIHP_dualSource.json index c8c056b3..7da3d43c 100644 --- a/data/examplesToBeCompleted/TRIHP_dualSource/TRIHP_dualSource.json +++ b/data/examplesToBeCompleted/TRIHP_dualSource/TRIHP_dualSource.json @@ -826,7 +826,7 @@ }, "Connection-3830": { ".__ConnectionDict__": true, - "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", + "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", "connectionId": 857, "diameterInCm": 2.0, "fromPortId": 1619, @@ -851,14 +851,13 @@ -269.0 ] ], - "shallBeSimulated": true, "toPortId": 2027, "trnsysId": 1240, "uValueInWPerM2K": 0.8333 }, "Connection-5976": { ".__ConnectionDict__": true, - "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", + "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", "connectionId": 1353, "diameterInCm": 2.0, "fromPortId": 3264, @@ -879,14 +878,13 @@ -411.0 ] ], - "shallBeSimulated": true, "toPortId": 309, "trnsysId": 1938, "uValueInWPerM2K": 0.8333 }, "Connection-6007": { ".__ConnectionDict__": true, - "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", + "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", "connectionId": 1361, "diameterInCm": 2.0, "fromPortId": 5982, @@ -907,14 +905,13 @@ -420.0 ] ], - "shallBeSimulated": true, "toPortId": 5990, "trnsysId": 1948, "uValueInWPerM2K": 0.8333 }, "Connection-6010": { ".__ConnectionDict__": true, - "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", + "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", "connectionId": 1362, "diameterInCm": 2.0, "fromPortId": 5991, @@ -935,14 +932,13 @@ -351.0 ] ], - "shallBeSimulated": true, "toPortId": 3084, "trnsysId": 1949, "uValueInWPerM2K": 0.8333 }, "Connection-6013": { ".__ConnectionDict__": true, - "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", + "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", "connectionId": 1363, "diameterInCm": 2.0, "fromPortId": 5980, @@ -967,14 +963,13 @@ -480.0 ] ], - "shallBeSimulated": true, "toPortId": 78, "trnsysId": 1950, "uValueInWPerM2K": 0.8333 }, "Connection-6039": { ".__ConnectionDict__": true, - "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", + "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", "connectionId": 1371, "diameterInCm": 2.0, "fromPortId": 6035, @@ -995,14 +990,13 @@ -91.0 ] ], - "shallBeSimulated": true, "toPortId": 62, "trnsysId": 1959, "uValueInWPerM2K": 0.8333 }, "Connection-6049": { ".__ConnectionDict__": true, - "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", + "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", "connectionId": 1375, "diameterInCm": 2.0, "fromPortId": 56, @@ -1027,14 +1021,13 @@ -151.0 ] ], - "shallBeSimulated": true, "toPortId": 52, "trnsysId": 1963, "uValueInWPerM2K": 0.8333 }, "Connection-6081": { ".__ConnectionDict__": true, - "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", + "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", "connectionId": 1385, "diameterInCm": 2.0, "fromPortId": 6074, @@ -1055,14 +1048,13 @@ -289.0 ] ], - "shallBeSimulated": true, "toPortId": 5318, "trnsysId": 1974, "uValueInWPerM2K": 0.8333 }, "Connection-6085": { ".__ConnectionDict__": true, - "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", + "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", "connectionId": 1387, "diameterInCm": 2.0, "fromPortId": 3804, @@ -1087,14 +1079,13 @@ -529.0 ] ], - "shallBeSimulated": true, "toPortId": 1982, "trnsysId": 1976, "uValueInWPerM2K": 0.8333 }, "Connection-6098": { ".__ConnectionDict__": true, - "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", + "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", "connectionId": 1390, "diameterInCm": 2.0, "fromPortId": 6091, @@ -1115,14 +1106,13 @@ -290.0 ] ], - "shallBeSimulated": true, "toPortId": 173, "trnsysId": 1980, "uValueInWPerM2K": 0.8333 }, "Connection-6103": { ".__ConnectionDict__": true, - "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", + "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", "connectionId": 1393, "diameterInCm": 2.0, "fromPortId": 178, @@ -1147,14 +1137,13 @@ 60.0 ] ], - "shallBeSimulated": true, "toPortId": 3772, "trnsysId": 1983, "uValueInWPerM2K": 0.8333 }, "Connection-6110": { ".__ConnectionDict__": true, - "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", + "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", "connectionId": 1394, "diameterInCm": 2.0, "fromPortId": 6107, @@ -1179,14 +1168,13 @@ -20.0 ] ], - "shallBeSimulated": true, "toPortId": 176, "trnsysId": 1985, "uValueInWPerM2K": 0.8333 }, "Connection-6113": { ".__ConnectionDict__": true, - "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", + "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", "connectionId": 1395, "diameterInCm": 2.0, "fromPortId": 6109, @@ -1207,14 +1195,13 @@ 39.667 ] ], - "shallBeSimulated": true, "toPortId": 3773, "trnsysId": 1986, "uValueInWPerM2K": 0.8333 }, "Connection-6119": { ".__ConnectionDict__": true, - "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", + "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", "connectionId": 1397, "diameterInCm": 2.0, "fromPortId": 3774, @@ -1239,14 +1226,13 @@ 60.0 ] ], - "shallBeSimulated": true, "toPortId": 3776, "trnsysId": 1988, "uValueInWPerM2K": 0.8333 }, "Connection-6134": { ".__ConnectionDict__": true, - "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", + "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", "connectionId": 1402, "diameterInCm": 2.0, "fromPortId": 3777, @@ -1267,14 +1253,13 @@ 60.0 ] ], - "shallBeSimulated": true, "toPortId": 365, "trnsysId": 1993, "uValueInWPerM2K": 0.8333 }, "Connection-6195": { ".__ConnectionDict__": true, - "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", + "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", "connectionId": 1422, "diameterInCm": 2.0, "fromPortId": 3066, @@ -1299,14 +1284,13 @@ -331.0 ] ], - "shallBeSimulated": true, "toPortId": 6190, "trnsysId": 2017, "uValueInWPerM2K": 0.8333 }, "Connection-6206": { ".__ConnectionDict__": true, - "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", + "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", "connectionId": 1425, "diameterInCm": 2.0, "fromPortId": 3067, @@ -1327,14 +1311,13 @@ -474.333 ] ], - "shallBeSimulated": true, "toPortId": 6201, "trnsysId": 2021, "uValueInWPerM2K": 0.8333 }, "Connection-6209": { ".__ConnectionDict__": true, - "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", + "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", "connectionId": 1426, "diameterInCm": 2.0, "fromPortId": 6202, @@ -1359,14 +1342,13 @@ -509.0 ] ], - "shallBeSimulated": true, "toPortId": 3263, "trnsysId": 2022, "uValueInWPerM2K": 0.8333 }, "Connection-6230": { ".__ConnectionDict__": true, - "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", + "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", "connectionId": 1433, "diameterInCm": 2.0, "fromPortId": 6089, @@ -1391,14 +1373,13 @@ -529.0 ] ], - "shallBeSimulated": true, "toPortId": 3803, "trnsysId": 2030, "uValueInWPerM2K": 0.8333 }, "Connection-6234": { ".__ConnectionDict__": true, - "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", + "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", "connectionId": 1435, "diameterInCm": 2.0, "fromPortId": 174, @@ -1423,14 +1404,13 @@ -269.0 ] ], - "shallBeSimulated": true, "toPortId": 1618, "trnsysId": 2032, "uValueInWPerM2K": 0.8333 }, "Connection-6237": { ".__ConnectionDict__": true, - "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", + "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", "connectionId": 1436, "diameterInCm": 2.0, "fromPortId": 5317, @@ -1455,14 +1435,13 @@ -270.0 ] ], - "shallBeSimulated": true, "toPortId": 172, "trnsysId": 2033, "uValueInWPerM2K": 0.8333 }, "Connection-6245": { ".__ConnectionDict__": true, - "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", + "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", "connectionId": 1440, "diameterInCm": 2.0, "fromPortId": 2028, @@ -1487,14 +1466,13 @@ -71.0 ] ], - "shallBeSimulated": true, "toPortId": 4284, "trnsysId": 2037, "uValueInWPerM2K": 0.8333 }, "Connection-6255": { ".__ConnectionDict__": true, - "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", + "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", "connectionId": 1444, "diameterInCm": 2.0, "fromPortId": 80, @@ -1519,14 +1497,13 @@ -331.0 ] ], - "shallBeSimulated": true, "toPortId": 3083, "trnsysId": 2041, "uValueInWPerM2K": 0.8333 }, "Connection-6261": { ".__ConnectionDict__": true, - "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", + "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", "connectionId": 1446, "diameterInCm": 2.0, "fromPortId": 311, @@ -1551,14 +1528,13 @@ -331.0 ] ], - "shallBeSimulated": true, "toPortId": 5956, "trnsysId": 2043, "uValueInWPerM2K": 0.8333 }, "Connection-6264": { ".__ConnectionDict__": true, - "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", + "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", "connectionId": 1447, "diameterInCm": 2.0, "fromPortId": 5957, @@ -1583,14 +1559,13 @@ -331.0 ] ], - "shallBeSimulated": true, "toPortId": 3068, "trnsysId": 2044, "uValueInWPerM2K": 0.8333 }, "Connection-6271": { ".__ConnectionDict__": true, - "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", + "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", "connectionId": 1450, "diameterInCm": 2.0, "fromPortId": 6191, @@ -1615,14 +1590,13 @@ -529.0 ] ], - "shallBeSimulated": true, "toPortId": 3262, "trnsysId": 2047, "uValueInWPerM2K": 0.8333 }, "Connection-6275": { ".__ConnectionDict__": true, - "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", + "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", "connectionId": 1452, "diameterInCm": 2.0, "fromPortId": 6213, @@ -1647,14 +1621,13 @@ -269.0 ] ], - "shallBeSimulated": true, "toPortId": 189, "trnsysId": 2049, "uValueInWPerM2K": 0.8333 }, "Connection-6278": { ".__ConnectionDict__": true, - "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", + "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", "connectionId": 1453, "diameterInCm": 2.0, "fromPortId": 188, @@ -1679,14 +1652,13 @@ -269.0 ] ], - "shallBeSimulated": true, "toPortId": 6034, "trnsysId": 2050, "uValueInWPerM2K": 0.8333 }, "Connection-6281": { ".__ConnectionDict__": true, - "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", + "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", "connectionId": 1454, "diameterInCm": 2.0, "fromPortId": 6033, @@ -1711,14 +1683,13 @@ -269.0 ] ], - "shallBeSimulated": true, "toPortId": 55, "trnsysId": 2051, "uValueInWPerM2K": 0.8333 }, "Connection-6293": { ".__ConnectionDict__": true, - "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", + "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", "connectionId": 1458, "diameterInCm": 2.0, "fromPortId": 184, @@ -1743,14 +1714,13 @@ -71.0 ] ], - "shallBeSimulated": true, "toPortId": 6212, "trnsysId": 2055, "uValueInWPerM2K": 0.8333 }, "Connection-6297": { ".__ConnectionDict__": true, - "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", + "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", "connectionId": 1460, "diameterInCm": 2.0, "fromPortId": 53, @@ -1775,14 +1745,13 @@ -71.0 ] ], - "shallBeSimulated": true, "toPortId": 63, "trnsysId": 2057, "uValueInWPerM2K": 0.8333 }, "Connection-6300": { ".__ConnectionDict__": true, - "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", + "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", "connectionId": 1461, "diameterInCm": 2.0, "fromPortId": 61, @@ -1807,14 +1776,13 @@ -71.0 ] ], - "shallBeSimulated": true, "toPortId": 186, "trnsysId": 2058, "uValueInWPerM2K": 0.8333 }, "Connection-6705": { ".__ConnectionDict__": true, - "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", + "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", "connectionId": 1552, "diameterInCm": 2.0, "fromPortId": 4285, @@ -1835,14 +1803,13 @@ -379.0 ] ], - "shallBeSimulated": true, "toPortId": 4288, "trnsysId": 2189, "uValueInWPerM2K": 0.8333 }, "Connection-6708": { ".__ConnectionDict__": true, - "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", + "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", "connectionId": 1553, "diameterInCm": 2.0, "fromPortId": 1983, @@ -1867,14 +1834,13 @@ -399.0 ] ], - "shallBeSimulated": true, "toPortId": 4287, "trnsysId": 2190, "uValueInWPerM2K": 0.8333 }, "Connection-6711": { ".__ConnectionDict__": true, - "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", + "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", "connectionId": 1554, "diameterInCm": 2.0, "fromPortId": 4289, @@ -1899,14 +1865,13 @@ -398.0 ] ], - "shallBeSimulated": true, "toPortId": 6072, "trnsysId": 2191, "uValueInWPerM2K": 0.8333 }, "Connection-7414": { ".__ConnectionDict__": true, - "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", + "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", "connectionId": 1709, "diameterInCm": 2.0, "fromPortId": 366, @@ -1931,14 +1896,13 @@ 60.0 ] ], - "shallBeSimulated": true, "toPortId": 137, "trnsysId": 2416, "uValueInWPerM2K": 0.8333 }, "Connection-7417": { ".__ConnectionDict__": true, - "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", + "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", "connectionId": 1710, "diameterInCm": 2.0, "fromPortId": 6162, @@ -1963,14 +1927,13 @@ -20.0 ] ], - "shallBeSimulated": true, "toPortId": 140, "trnsysId": 2417, "uValueInWPerM2K": 0.8333 }, "Connection-7420": { ".__ConnectionDict__": true, - "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", + "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", "connectionId": 1711, "diameterInCm": 2.0, "fromPortId": 139, @@ -1995,14 +1958,13 @@ -20.0 ] ], - "shallBeSimulated": true, "toPortId": 6108, "trnsysId": 2418, "uValueInWPerM2K": 0.8333 }, "Connection-7423": { ".__ConnectionDict__": true, - "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", + "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", "connectionId": 1712, "diameterInCm": 2.0, "fromPortId": 136, @@ -2023,14 +1985,13 @@ -0.333 ] ], - "shallBeSimulated": true, "toPortId": 141, "trnsysId": 2419, "uValueInWPerM2K": 0.8333 }, "Connection-7426": { ".__ConnectionDict__": true, - "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", + "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", "connectionId": 1713, "diameterInCm": 2.0, "fromPortId": 135, @@ -2055,14 +2016,13 @@ -91.0 ] ], - "shallBeSimulated": true, "toPortId": 6159, "trnsysId": 2420, "uValueInWPerM2K": 0.8333 }, "Connection-7534": { ".__ConnectionDict__": true, - "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", + "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", "connectionId": 1741, "diameterInCm": 2.0, "fromPortId": 312, @@ -2087,14 +2047,13 @@ -480.0 ] ], - "shallBeSimulated": true, "toPortId": 5981, "trnsysId": 2463, "uValueInWPerM2K": 0.8333 }, "Connection-7535": { ".__ConnectionDict__": true, - "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", + "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", "connectionId": 1742, "diameterInCm": 2.0, "fromPortId": 3082, @@ -2119,14 +2078,13 @@ -331.0 ] ], - "shallBeSimulated": true, "toPortId": 310, "trnsysId": 2464, "uValueInWPerM2K": 0.8333 }, "Connection-7536": { ".__ConnectionDict__": true, - "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", + "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", "connectionId": 1743, "diameterInCm": 2.0, "fromPortId": 179, @@ -2151,14 +2109,13 @@ -51.0 ] ], - "shallBeSimulated": true, "toPortId": 185, "trnsysId": 2465, "uValueInWPerM2K": 0.8333 }, "Connection-7537": { ".__ConnectionDict__": true, - "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", + "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", "connectionId": 1744, "diameterInCm": 2.0, "fromPortId": 190, @@ -2179,14 +2136,13 @@ 60.0 ] ], - "shallBeSimulated": true, "toPortId": 177, "trnsysId": 2466, "uValueInWPerM2K": 0.8333 }, "Connection-7547": { ".__ConnectionDict__": true, - "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", + "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", "connectionId": 1754, "diameterInCm": 2.0, "fromPortId": 6164, @@ -2211,14 +2167,13 @@ -529.0 ] ], - "shallBeSimulated": true, "toPortId": 6090, "trnsysId": 2476, "uValueInWPerM2K": 0.8333 }, "Connection-7548": { ".__ConnectionDict__": true, - "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", + "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", "connectionId": 1755, "diameterInCm": 2.0, "fromPortId": 6073, @@ -2243,14 +2198,13 @@ -131.0 ] ], - "shallBeSimulated": true, "toPortId": 6161, "trnsysId": 2477, "uValueInWPerM2K": 0.8333 }, "Connection-7549": { ".__ConnectionDict__": true, - "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", + "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", "connectionId": 1756, "diameterInCm": 2.0, "fromPortId": 6163, @@ -2275,14 +2229,13 @@ -269.0 ] ], - "shallBeSimulated": true, "toPortId": 5319, "trnsysId": 2478, "uValueInWPerM2K": 0.8333 }, "Connection-7550": { ".__ConnectionDict__": true, - "__version__": "74032e79-2abc-4fcf-9328-5d697ef31023", + "__version__": "f03faf46-4d0e-4407-a604-90925d83d43a", "connectionId": 1757, "diameterInCm": 2.0, "fromPortId": 4283, @@ -2307,20 +2260,19 @@ -71.0 ] ], - "shallBeSimulated": true, "toPortId": 6160, "trnsysId": 2479, "uValueInWPerM2K": 0.8333 }, "IDs": { - "GlobalId": 7560, + "GlobalId": 7559, "__idDct__": true, - "globalConnID": 1767, - "trnsysID": 2489 + "globalConnID": 1766, + "trnsysID": 2488 }, "Strings": { "DiagramName": "TRIHP_dualSource.json", - "ProjectFolder": "C:\\Users\\damian.birchler\\src\\pytrnsys\\wd1\\pytrnsys_gui\\data\\examplesToBeCompleted\\TRIHP_dualSource", + "ProjectFolder": "C:\\GIT\\pytrnsys_gui\\data\\examples\\TRIHP_dualSource", "__nameDct__": true } }, From 16203015a4bc07c695335cc946c1543fdbe5fb4c Mon Sep 17 00:00:00 2001 From: Damian Birchler Date: Tue, 30 Jul 2024 14:45:23 +0200 Subject: [PATCH 28/33] Satisfy SONAR. --- trnsysGUI/proforma/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/trnsysGUI/proforma/models.py b/trnsysGUI/proforma/models.py index 78f15504..87fdf587 100644 --- a/trnsysGUI/proforma/models.py +++ b/trnsysGUI/proforma/models.py @@ -13,7 +13,7 @@ class Variable: roleOrder: int unit: str bounds: str - defaultValue: _tp.Union[float, int] + defaultValue: float | int def getInfo(self, withRole: bool) -> str: roleOrEmpty = f"{self.role.capitalize()} " if withRole else "" @@ -32,7 +32,7 @@ class Unset: UNSET = Unset() -RequiredVariable = _tp.Union[Variable, Unset] +RequiredVariable = Variable | Unset @_dc.dataclass From 75e36b9bb61c2bad02c20996071606283b971de9 Mon Sep 17 00:00:00 2001 From: Damian Birchler Date: Tue, 30 Jul 2024 15:53:46 +0200 Subject: [PATCH 29/33] Add nice sections to generated ddcks. --- .../proforma/data/expected/Type137.ddck | 97 ++++++++++--- .../Type71-no-hydraulic-connections.ddck | 93 ++++++++++-- .../proforma/data/expected/Type71.ddck | 92 ++++++++++-- .../proforma/testConvertXmlTmfToDdck.py | 3 +- trnsysGUI/proforma/convertXmlTmfToDdck.py | 32 ++++- trnsysGUI/proforma/templates/ddck.jinja | 135 ++++++++++++------ 6 files changed, 365 insertions(+), 87 deletions(-) diff --git a/tests/trnsysGUI/proforma/data/expected/Type137.ddck b/tests/trnsysGUI/proforma/data/expected/Type137.ddck index 655a2a64..8ce159b1 100644 --- a/tests/trnsysGUI/proforma/data/expected/Type137.ddck +++ b/tests/trnsysGUI/proforma/data/expected/Type137.ddck @@ -1,3 +1,51 @@ +******************************* +** BEGIN Type137.ddck +******************************* + +*************************************************************************** +** Description: +** 4-Pipe Fan Coil: Heating and Cooling +*************************************************************************** + +*************************************************************************** +** Details: +** This component models a fan coil where the air is heated or cooled as it passes across coils containing hot and cold +** liquid flow streams. This model relies on user-provided external data files which contain the performance of the +** coils as a function of the entering air and fluid conditions. Refer to the sample data files which accompany this +** model for the format of these external files. +*************************************************************************** + +*********************************** +** inputs from hydraulic solver +*********************************** + +*********************************** +** outputs to hydraulic solver +*********************************** + +*********************************** +** outputs to other ddck +*********************************** + + +****************************************************************************************** +** outputs to energy balance in kWh and ABSOLUTE value +****************************************************************************************** + + +*********************************** +** Dependencies with other ddck +*********************************** + + +*********************************** +** Begin CONSTANTS +*********************************** + + +*********************************** +** Begin TYPE +*********************************** UNIT 1 TYPE 137 PARAMETERS 20 2.0 ! 1: Humidity Mode [-] ([2,2]) @@ -39,23 +87,23 @@ INPUTS 17 0,0 ! 16: Fan Control Signal [-] ([0.,1.]) 0,0 ! 17: Fraction of Outside Air [-] ([0.,1.]) ** initial values -10.0 ! 1: Cooling Fluid Inlet Temperature - initial value -0.0 ! 2: Cooling Fluid Flow Rate - initial value -10.0 ! 3: Heating Fluid Inlet Temperature - initial value -0.0 ! 4: Heating Fluid Flow Rate - initial value -20.0 ! 5: Return Air Temperature - initial value -0.002 ! 6: Return Air Humidity Ratio - initial value -50.0 ! 7: Return Air % Relative Humidity - initial value -1.0 ! 8: Return Air Pressure - initial value -0.0 ! 9: Air-Side Pressure Rise: Fan - initial value -0.0 ! 10: Air-Side Pressure Drop: Coils - initial value -20.0 ! 11: Fresh Air Temperature - initial value -0.002 ! 12: Fresh Air Humidity Ratio - initial value -50.0 ! 13: Fresh Air % Relative Humidity - initial value -0.0 ! 14: Heating Control Signal - initial value -0.0 ! 15: Cooling Control Signal - initial value -0.0 ! 16: Fan Control Signal - initial value -0.0 ! 17: Fraction of Outside Air - initial value +10.0 ! 1: Cooling Fluid Inlet Temperature initial value +0.0 ! 2: Cooling Fluid Flow Rate initial value +10.0 ! 3: Heating Fluid Inlet Temperature initial value +0.0 ! 4: Heating Fluid Flow Rate initial value +20.0 ! 5: Return Air Temperature initial value +0.002 ! 6: Return Air Humidity Ratio initial value +50.0 ! 7: Return Air % Relative Humidity initial value +1.0 ! 8: Return Air Pressure initial value +0.0 ! 9: Air-Side Pressure Rise: Fan initial value +0.0 ! 10: Air-Side Pressure Drop: Coils initial value +20.0 ! 11: Fresh Air Temperature initial value +0.002 ! 12: Fresh Air Humidity Ratio initial value +50.0 ! 13: Fresh Air % Relative Humidity initial value +0.0 ! 14: Heating Control Signal initial value +0.0 ! 15: Cooling Control Signal initial value +0.0 ! 16: Fan Control Signal initial value +0.0 ! 17: Fraction of Outside Air initial value ! EQUATIONS 18 ! XXX = [1, 1] ! Cooling Fluid Outlet Temperature [C] ([-Inf,+Inf]) @@ -76,3 +124,18 @@ INPUTS 17 ! XXX = [1, 16] ! Condensate Temperature [C] ([-Inf,+Inf]) ! XXX = [1, 17] ! Condensate Flow Rate [kg/hr] ([0.0,+Inf]) ! XXX = [1, 18] ! Conditioning Energy Rate [kJ/hr] ([-Inf,+Inf]) + +*********************************** +** Monthly printer +*********************************** + + +*********************************** +** Hourly printer +*********************************** + + +*********************************** +** Online Plotter +*********************************** + diff --git a/tests/trnsysGUI/proforma/data/expected/Type71-no-hydraulic-connections.ddck b/tests/trnsysGUI/proforma/data/expected/Type71-no-hydraulic-connections.ddck index e415decb..93c25e2a 100644 --- a/tests/trnsysGUI/proforma/data/expected/Type71-no-hydraulic-connections.ddck +++ b/tests/trnsysGUI/proforma/data/expected/Type71-no-hydraulic-connections.ddck @@ -1,3 +1,61 @@ +******************************* +** BEGIN Type71-no-hydraulic-connections.ddck +******************************* + +*************************************************************************** +** Description: +** Solar Collector; Evacuated Tube +*************************************************************************** + +*************************************************************************** +** Details: +** Because the Solar Ratings and Certification Commission (SRCC) defines the efficiency of an evacuated tube collector +** bank using the same equations as those for a flat plat, the main difference (from a modeling point of view) between +** an evacuated tube collector and a flat plate collector is in the treatment of incidence angle modifiers (IAMs). Type +** 71 is therefore based on the Type 1 code with the major difference being that Type 71 reads a text file containing a +** list of transverse and longitudinal IAMs. This component models the thermal performance of a variety of an evacuated +** tube collector types using theory. The total collector array may consist of collectors connected in series and in +** parallel. The thermal performance of the total collector array is determined by the number of modules in series and +** the characteristics of each module. The user must provide results from standard tests of efficiency versus a ratio of +** fluid temperature minus ambient temperature to radiation (DT/IT). The fluid temperature may be an inlet, average, or +** outlet temperature. The model assumes that the efficiency vs. DT/IT curve can be modeled as a quadratic equation. +** (Changed from version 13 where efficiency vs. DT/IT was assumed linear.) Corrections are applied to the slope, +** intercept, and curvature parameters to account for identical collectors in series, and flow rates other than those at +** test conditions. The effects of off-normal solar incidence are modeled by the provision of a bi-axial incidence angle +** modifier data file. +*************************************************************************** + +*********************************** +** inputs from hydraulic solver +*********************************** + +*********************************** +** outputs to hydraulic solver +*********************************** + +*********************************** +** outputs to other ddck +*********************************** + + +****************************************************************************************** +** outputs to energy balance in kWh and ABSOLUTE value +****************************************************************************************** + + +*********************************** +** Dependencies with other ddck +*********************************** + + +*********************************** +** Begin CONSTANTS +*********************************** + + +*********************************** +** Begin TYPE +*********************************** UNIT 1 TYPE 71 PARAMETERS 11 1.0 ! 1: Number in series [-] ([1,+Inf]) @@ -23,16 +81,16 @@ INPUTS 10 0,0 ! 9: Collector slope [degrees] ([-360,+360]) 0,0 ! 10: Collector azimuth [degrees] ([-360,+360]) ** initial values -20.0 ! 1: Inlet temperature - initial value -100.0 ! 2: Inlet flowrate - initial value -10.0 ! 3: Ambient temperature - initial value -0.0 ! 4: Incident radiation - initial value -0.0 ! 5: Incident diffuse radiation - initial value -0.0 ! 6: Solar incidence angle - initial value -0.0 ! 7: Solar zenith angle - initial value -0.0 ! 8: Solar azimuth angle - initial value -45.0 ! 9: Collector slope - initial value -0.0 ! 10: Collector azimuth - initial value +20.0 ! 1: Inlet temperature initial value +100.0 ! 2: Inlet flowrate initial value +10.0 ! 3: Ambient temperature initial value +0.0 ! 4: Incident radiation initial value +0.0 ! 5: Incident diffuse radiation initial value +0.0 ! 6: Solar incidence angle initial value +0.0 ! 7: Solar zenith angle initial value +0.0 ! 8: Solar azimuth angle initial value +45.0 ! 9: Collector slope initial value +0.0 ! 10: Collector azimuth initial value ! EQUATIONS 5 ! XXX = [1, 1] ! Outlet temperature [C] ([-Inf,+Inf]) @@ -40,3 +98,18 @@ INPUTS 10 ! XXX = [1, 3] ! Useful energy gain [kJ/hr] ([0.0,+Inf]) ! XXX = [1, 4] ! Collector efficiency [-] ([-Inf,+Inf]) ! XXX = [1, 5] ! Incidence angle modifier - overall [-] ([-Inf,+Inf]) + +*********************************** +** Monthly printer +*********************************** + + +*********************************** +** Hourly printer +*********************************** + + +*********************************** +** Online Plotter +*********************************** + diff --git a/tests/trnsysGUI/proforma/data/expected/Type71.ddck b/tests/trnsysGUI/proforma/data/expected/Type71.ddck index 9c5b313f..a169ebb1 100644 --- a/tests/trnsysGUI/proforma/data/expected/Type71.ddck +++ b/tests/trnsysGUI/proforma/data/expected/Type71.ddck @@ -1,11 +1,67 @@ +******************************* +** BEGIN Type71.ddck +******************************* + +*************************************************************************** +** Description: +** Solar Collector; Evacuated Tube +*************************************************************************** + +*************************************************************************** +** Details: +** Because the Solar Ratings and Certification Commission (SRCC) defines the efficiency of an evacuated tube collector +** bank using the same equations as those for a flat plat, the main difference (from a modeling point of view) between +** an evacuated tube collector and a flat plate collector is in the treatment of incidence angle modifiers (IAMs). Type +** 71 is therefore based on the Type 1 code with the major difference being that Type 71 reads a text file containing a +** list of transverse and longitudinal IAMs. This component models the thermal performance of a variety of an evacuated +** tube collector types using theory. The total collector array may consist of collectors connected in series and in +** parallel. The thermal performance of the total collector array is determined by the number of modules in series and +** the characteristics of each module. The user must provide results from standard tests of efficiency versus a ratio of +** fluid temperature minus ambient temperature to radiation (DT/IT). The fluid temperature may be an inlet, average, or +** outlet temperature. The model assumes that the efficiency vs. DT/IT curve can be modeled as a quadratic equation. +** (Changed from version 13 where efficiency vs. DT/IT was assumed linear.) Corrections are applied to the slope, +** intercept, and curvature parameters to account for identical collectors in series, and flow rates other than those at +** test conditions. The effects of off-normal solar incidence are modeled by the provision of a bi-axial incidence angle +** modifier data file. +*************************************************************************** + *********************************** -** Inputs from hydraulic solver +** inputs from hydraulic solver *********************************** EQUATIONS 3 :CpIn = @cp(In) :TIn = @temp(In) :MIn = @mfr(In) +*********************************** +** outputs to hydraulic solver +*********************************** +EQUATIONS 1 +@temp(Out) = :TOut + +*********************************** +** outputs to other ddck +*********************************** + + +****************************************************************************************** +** outputs to energy balance in kWh and ABSOLUTE value +****************************************************************************************** + + +*********************************** +** Dependencies with other ddck +*********************************** + + +*********************************** +** Begin CONSTANTS +*********************************** + + +*********************************** +** Begin TYPE +*********************************** UNIT 1 TYPE 71 PARAMETERS 11 1.0 ! 1: Number in series [-] ([1,+Inf]) @@ -31,16 +87,16 @@ INPUTS 10 0,0 ! 9: Collector slope [degrees] ([-360,+360]) 0,0 ! 10: Collector azimuth [degrees] ([-360,+360]) ** initial values -20.0 ! 1: Inlet temperature - initial value -100.0 ! 2: Inlet flowrate - initial value -10.0 ! 3: Ambient temperature - initial value -0.0 ! 4: Incident radiation - initial value -0.0 ! 5: Incident diffuse radiation - initial value -0.0 ! 6: Solar incidence angle - initial value -0.0 ! 7: Solar zenith angle - initial value -0.0 ! 8: Solar azimuth angle - initial value -45.0 ! 9: Collector slope - initial value -0.0 ! 10: Collector azimuth - initial value +20.0 ! 1: Inlet temperature initial value +100.0 ! 2: Inlet flowrate initial value +10.0 ! 3: Ambient temperature initial value +0.0 ! 4: Incident radiation initial value +0.0 ! 5: Incident diffuse radiation initial value +0.0 ! 6: Solar incidence angle initial value +0.0 ! 7: Solar zenith angle initial value +0.0 ! 8: Solar azimuth angle initial value +45.0 ! 9: Collector slope initial value +0.0 ! 10: Collector azimuth initial value EQUATIONS 1 ! 5 :TOut = [1, 1] ! Outlet temperature [C] ([-Inf,+Inf]) @@ -50,6 +106,16 @@ EQUATIONS 1 ! 5 ! XXX = [1, 5] ! Incidence angle modifier - overall [-] ([-Inf,+Inf]) *********************************** -** Outputs to hydraulic solver +** Monthly printer *********************************** -@temp(Out) = :TOut + + +*********************************** +** Hourly printer +*********************************** + + +*********************************** +** Online Plotter +*********************************** + diff --git a/tests/trnsysGUI/proforma/testConvertXmlTmfToDdck.py b/tests/trnsysGUI/proforma/testConvertXmlTmfToDdck.py index ebe96f40..eacf1113 100644 --- a/tests/trnsysGUI/proforma/testConvertXmlTmfToDdck.py +++ b/tests/trnsysGUI/proforma/testConvertXmlTmfToDdck.py @@ -79,7 +79,8 @@ def returnConnectionsUnmodified(connections, _): returnConnectionsUnmodified, ) - result = _pc.convertXmlTmfStringToDdck(xmlFileContent, suggestedHydraulicConnections=None) + fileName = testCase.inputFilePath.with_suffix(".ddck").name + result = _pc.convertXmlTmfStringToDdck(xmlFileContent, suggestedHydraulicConnections=None, fileName=fileName) if testCase.expectedOutputFilePath.is_file(): assert isinstance(result, str) diff --git a/trnsysGUI/proforma/convertXmlTmfToDdck.py b/trnsysGUI/proforma/convertXmlTmfToDdck.py index 8c05a519..6d4e3121 100644 --- a/trnsysGUI/proforma/convertXmlTmfToDdck.py +++ b/trnsysGUI/proforma/convertXmlTmfToDdck.py @@ -3,6 +3,8 @@ import pathlib as _pl import typing as _tp import xml.etree.ElementTree as _etree +import re as _re +import textwrap as _tw import jinja2 as _jj import xmlschema as _xs @@ -22,6 +24,8 @@ def _createDdckJinjaTemplate() -> _jj.Template: loader=_jj.PackageLoader(__name__), autoescape=_jj.select_autoescape(), lstrip_blocks=True, + trim_blocks=True, + keep_trailing_newline=True, undefined=_jj.StrictUndefined, ) template = environment.get_template("ddck.jinja") @@ -37,7 +41,7 @@ def convertXmltmfToDdck( ddckFilePath: _pl.Path, ) -> _cancel.MaybeCancelled[_res.Result[None]]: xmlTmfContent = xmlTmfFilePath.read_text(encoding="utf8") - maybeCancelled = convertXmlTmfStringToDdck(xmlTmfContent, suggestedHydraulicConnections) + maybeCancelled = convertXmlTmfStringToDdck(xmlTmfContent, suggestedHydraulicConnections, ddckFilePath.name) if _cancel.isCancelled(maybeCancelled): return _cancel.CANCELLED result = _cancel.value(maybeCancelled) @@ -161,7 +165,7 @@ def _createVariablesForRole( def convertXmlTmfStringToDdck( - xmlTmfContent: str, suggestedHydraulicConnections: _cabc.Sequence[_models.Connection] | None + xmlTmfContent: str, suggestedHydraulicConnections: _cabc.Sequence[_models.Connection] | None, fileName: str ) -> _cancel.MaybeCancelled[_res.Result[str]]: schema = _xs.XMLSchema11(_SCHEMA_FILE_PATH) @@ -194,19 +198,35 @@ def convertXmlTmfStringToDdck( return _cancel.CANCELLED hydraulicConnections = _cancel.value(maybeCancelled) - trnsysType = proforma["type"] + otherJinjaVariables = { + "fileName": fileName, + "type": proforma["type"], + "description": _makeMultilineComment(proforma["object"]), + "details": _makeMultilineComment(proforma["details"]), + } + + return _convertXmlTmfStringToDdck(hydraulicConnections, variablesByRole, otherJinjaVariables) - return _convertXmlTmfStringToDdck(trnsysType, hydraulicConnections, variablesByRole) + +def _makeMultilineComment(text: str) -> str: + textWithCollapsedWhitespace = _re.sub(r"\s+", " ", text.strip(), count=0, flags=_re.MULTILINE) + linePrefix = "** " + maxWidth = 120 - len(linePrefix) + wrappedLines = _tw.wrap(textWithCollapsedWhitespace, maxWidth) + newText = "\n".join(f"** {l}" for l in wrappedLines) + return newText def _convertXmlTmfStringToDdck( - trnsysType: int, hydraulicConnections: _cabc.Sequence[_models.Connection], variablesByRole: _models.VariablesByRole + hydraulicConnections: _cabc.Sequence[_models.Connection], + variablesByRole: _models.VariablesByRole, + otherJinjaVariables: _cabc.Mapping[str, _tp.Any], ) -> str: connectionsDataByOrder = _createHydraulicConnectionDataByOrder(hydraulicConnections) parameters = _createProcessedVariables(variablesByRole.parameters, connectionsDataByOrder) inputs = _createProcessedVariables(variablesByRole.inputs, connectionsDataByOrder) outputs = _createProcessedVariables(variablesByRole.outputs, connectionsDataByOrder) - ddckContent = _JINJA_TEMPLATE.render(type=trnsysType, parameters=parameters, inputs=inputs, outputs=outputs) + ddckContent = _JINJA_TEMPLATE.render(parameters=parameters, inputs=inputs, outputs=outputs, **otherJinjaVariables) return ddckContent diff --git a/trnsysGUI/proforma/templates/ddck.jinja b/trnsysGUI/proforma/templates/ddck.jinja index 664bec89..2c967d33 100644 --- a/trnsysGUI/proforma/templates/ddck.jinja +++ b/trnsysGUI/proforma/templates/ddck.jinja @@ -1,53 +1,108 @@ -{% set inputsWithHydraulicData = (parameters + inputs)|rejectattr("hydraulicConnectionsData", "none")|list -%} -{% if inputsWithHydraulicData|length -%} +******************************* +** BEGIN {{fileName}} +******************************* + +*************************************************************************** +** Description: +{{description}} +*************************************************************************** + +*************************************************************************** +** Details: +{{details}} +*************************************************************************** + *********************************** -** Inputs from hydraulic solver +** inputs from hydraulic solver *********************************** +{% set inputsWithHydraulicData = (parameters + inputs)|rejectattr("hydraulicConnectionsData", "none")|list %} +{% if inputsWithHydraulicData|length %} EQUATIONS {{inputsWithHydraulicData|length}} - {% for input in inputsWithHydraulicData -%} - {{input.hydraulicConnectionsData.variableName}} = {{input.hydraulicConnectionsData.rhs}} + {% for input in inputsWithHydraulicData %} +{{input.hydraulicConnectionsData.variableName}} = {{input.hydraulicConnectionsData.rhs}} + {% endfor %} +{% endif %} + +*********************************** +** outputs to hydraulic solver +*********************************** +{% set outputsWithHydraulicData = outputs|rejectattr("hydraulicConnectionsData", "none")|list %} +{% if outputsWithHydraulicData|length %} +EQUATIONS {{outputsWithHydraulicData|length}} + {% for outputWithHydraulicData in outputsWithHydraulicData %} +{{outputWithHydraulicData.hydraulicConnectionsData.rhs}} = {{outputWithHydraulicData.hydraulicConnectionsData.variableName}} {% endfor %} -{% endif -%} +{% endif %} + +*********************************** +** outputs to other ddck +*********************************** + + +****************************************************************************************** +** outputs to energy balance in kWh and ABSOLUTE value +****************************************************************************************** + + +*********************************** +** Dependencies with other ddck +*********************************** + + +*********************************** +** Begin CONSTANTS +*********************************** + + +*********************************** +** Begin TYPE +*********************************** UNIT 1 TYPE {{type}} PARAMETERS {{parameters|length}} -{% for parameter in parameters -%} - {% if parameter.hydraulicConnectionsData -%} - {{parameter.hydraulicConnectionsData.variableName}} ! {{parameter.info}} - {% else -%} - {{parameter.defaultValue}} ! {{parameter.info}} - {% endif -%} -{% endfor -%} +{% for parameter in parameters %} + {% if parameter.hydraulicConnectionsData %} +{{parameter.hydraulicConnectionsData.variableName}} ! {{parameter.info}} + {% else %} +{{parameter.defaultValue}} ! {{parameter.info}} + {% endif %} +{% endfor %} INPUTS {{inputs|length}} -{% for input in inputs -%} - {% if input.hydraulicConnectionsData -%} - {{input.hydraulicConnectionsData.variableName}} ! {{input.info}} - {% else -%} - 0,0 ! {{input.roleOrder}}: {{input.tmfName}} [{{input.unit}}] ({{input.bounds}}) - {% endif -%} -{% endfor -%} +{% for input in inputs %} + {% if input.hydraulicConnectionsData %} +{{input.hydraulicConnectionsData.variableName}} ! {{input.info}} + {% else %} +0,0 ! {{input.roleOrder}}: {{input.tmfName}} [{{input.unit}}] ({{input.bounds}}) + {% endif %} +{% endfor %} ** initial values -{% for input in inputs -%} - {{input.defaultValue}} ! {{input.roleOrder}}: {{input.tmfName}} - initial value +{% for input in inputs %} +{{input.defaultValue}} ! {{input.roleOrder}}: {{input.tmfName}} initial value {% endfor %} -{% set outputsWithHydraulicData = outputs|rejectattr("hydraulicConnectionsData", "none")|list -%} -{% if outputsWithHydraulicData|length -%} - EQUATIONS {{outputsWithHydraulicData|length}} ! {{outputs|length}} -{% else -%} - ! EQUATIONS {{outputs|length}} -{% endif -%} -{% for output in outputs -%} - {% if output.hydraulicConnectionsData -%} - {{output.hydraulicConnectionsData.variableName}} = [1, {{output.roleOrder}}] ! {{output.tmfName}} [{{output.unit}}] ({{output.bounds}}) - {% else -%} - ! XXX = [1, {{output.roleOrder}}] ! {{output.tmfName}} [{{output.unit}}] ({{output.bounds}}) - {% endif -%} -{% endfor -%} + {% if outputsWithHydraulicData|length %} +EQUATIONS {{outputsWithHydraulicData|length}} ! {{outputs|length}} +{% else %} +! EQUATIONS {{outputs|length}} +{% endif %} +{% for output in outputs %} + {% if output.hydraulicConnectionsData %} +{{output.hydraulicConnectionsData.variableName}} = [1, {{output.roleOrder}}] ! {{output.tmfName}} [{{output.unit}}] ({{output.bounds}}) + {% else %} +! XXX = [1, {{output.roleOrder}}] ! {{output.tmfName}} [{{output.unit}}] ({{output.bounds}}) + {% endif %} +{% endfor %} + +*********************************** +** Monthly printer +*********************************** + + +*********************************** +** Hourly printer +*********************************** + + *********************************** -** Outputs to hydraulic solver +** Online Plotter *********************************** - {% for outputWithHydraulicData in outputsWithHydraulicData -%} - {{outputWithHydraulicData.hydraulicConnectionsData.rhs}} = {{outputWithHydraulicData.hydraulicConnectionsData.variableName}} - {% endfor -%} -{% endif -%} From 72ad607ac3573c80c47b8de6c2fc32d77b195593 Mon Sep 17 00:00:00 2001 From: Damian Birchler Date: Tue, 30 Jul 2024 16:30:18 +0200 Subject: [PATCH 30/33] Add possibility to export for local and global default visibility. --- .../proforma/data/expected/Type71.ddck | 8 ++-- .../proforma/testConvertXmlTmfToDdck.py | 9 ++-- trnsysGUI/proforma/convertXmlTmfToDdck.py | 25 +++++++++-- .../proforma/dialogs/_hydraulicConnections.ui | 4 +- .../dialogs/editHydraulicConnectionsDialog.py | 42 ++++++++++++++----- trnsysGUI/proforma/templates/ddck.jinja | 12 +++--- 6 files changed, 70 insertions(+), 30 deletions(-) diff --git a/tests/trnsysGUI/proforma/data/expected/Type71.ddck b/tests/trnsysGUI/proforma/data/expected/Type71.ddck index a169ebb1..d6614385 100644 --- a/tests/trnsysGUI/proforma/data/expected/Type71.ddck +++ b/tests/trnsysGUI/proforma/data/expected/Type71.ddck @@ -100,10 +100,10 @@ INPUTS 10 EQUATIONS 1 ! 5 :TOut = [1, 1] ! Outlet temperature [C] ([-Inf,+Inf]) -! XXX = [1, 2] ! Outlet flowrate [kg/hr] ([0.0,+Inf]) -! XXX = [1, 3] ! Useful energy gain [kJ/hr] ([0.0,+Inf]) -! XXX = [1, 4] ! Collector efficiency [-] ([-Inf,+Inf]) -! XXX = [1, 5] ! Incidence angle modifier - overall [-] ([-Inf,+Inf]) +! :XXX = [1, 2] ! Outlet flowrate [kg/hr] ([0.0,+Inf]) +! :XXX = [1, 3] ! Useful energy gain [kJ/hr] ([0.0,+Inf]) +! :XXX = [1, 4] ! Collector efficiency [-] ([-Inf,+Inf]) +! :XXX = [1, 5] ! Incidence angle modifier - overall [-] ([-Inf,+Inf]) *********************************** ** Monthly printer diff --git a/tests/trnsysGUI/proforma/testConvertXmlTmfToDdck.py b/tests/trnsysGUI/proforma/testConvertXmlTmfToDdck.py index eacf1113..c4d3d572 100644 --- a/tests/trnsysGUI/proforma/testConvertXmlTmfToDdck.py +++ b/tests/trnsysGUI/proforma/testConvertXmlTmfToDdck.py @@ -7,8 +7,8 @@ import pytest as _pt import xmlschema as _xml +import pytrnsys.ddck.replaceTokens.defaultVisibility as _dv import pytrnsys.utils.result as _res - import trnsysGUI.proforma.convertXmlTmfToDdck as _pc import trnsysGUI.proforma.dialogs.editHydraulicConnectionsDialog as _ehcd @@ -70,13 +70,14 @@ def _getTestCases() -> _tp.Iterable[TestCase]: def testConvertXmlTmfStringToDdck(testCase: TestCase, monkeypatch) -> None: xmlFileContent = testCase.inputFilePath.read_text(encoding="utf8") - def returnConnectionsUnmodified(connections, _): - return connections + def returnConnectionsUnmodifiedWithGlobalDefaultVisibility(connections, _): + dialogResult = _ehcd.DialogResult(connections, _dv.DefaultVisibility.GLOBAL) + return dialogResult monkeypatch.setattr( _ehcd.EditHydraulicConnectionsDialog, _ehcd.EditHydraulicConnectionsDialog.showDialogAndGetResults.__name__, - returnConnectionsUnmodified, + returnConnectionsUnmodifiedWithGlobalDefaultVisibility, ) fileName = testCase.inputFilePath.with_suffix(".ddck").name diff --git a/trnsysGUI/proforma/convertXmlTmfToDdck.py b/trnsysGUI/proforma/convertXmlTmfToDdck.py index 6d4e3121..d8ec1ef0 100644 --- a/trnsysGUI/proforma/convertXmlTmfToDdck.py +++ b/trnsysGUI/proforma/convertXmlTmfToDdck.py @@ -1,17 +1,19 @@ import collections.abc as _cabc import dataclasses as _dc import pathlib as _pl -import typing as _tp -import xml.etree.ElementTree as _etree import re as _re import textwrap as _tw +import typing as _tp +import xml.etree.ElementTree as _etree import jinja2 as _jj import xmlschema as _xs +import pytrnsys.ddck.replaceTokens.defaultVisibility as _dv import pytrnsys.utils.result as _res import trnsysGUI.common.cancelled as _cancel import trnsysGUI.proforma.dialogs.editHydraulicConnectionsDialog as _ehcd + from . import createModelConnections as _cmcs from . import models as _models @@ -67,7 +69,7 @@ class _HydraulicConnectionsData: @property def variableName(self) -> str: capitalizedPortName = self.portName.capitalize() - return f":{self.stringConstants.variableNamePrefix}{capitalizedPortName}" + return f"{self.stringConstants.variableNamePrefix}{capitalizedPortName}" @property def rhs(self) -> str: @@ -190,24 +192,39 @@ def convertXmlTmfStringToDdck( else: hydraulicConnections = [] + defaultVisibility = _dv.DefaultVisibility.LOCAL + if hydraulicConnections: maybeCancelled = _ehcd.EditHydraulicConnectionsDialog.showDialogAndGetResults( hydraulicConnections, variablesByRole ) if _cancel.isCancelled(maybeCancelled): return _cancel.CANCELLED - hydraulicConnections = _cancel.value(maybeCancelled) + dialogResult = _cancel.value(maybeCancelled) + + hydraulicConnections = dialogResult.hydraulicConnections + defaultVisibility = dialogResult.defaultVisibility otherJinjaVariables = { "fileName": fileName, "type": proforma["type"], "description": _makeMultilineComment(proforma["object"]), "details": _makeMultilineComment(proforma["details"]), + "visibilityModifier": _getVisibilityModifier(defaultVisibility), } return _convertXmlTmfStringToDdck(hydraulicConnections, variablesByRole, otherJinjaVariables) +def _getVisibilityModifier(defaultVisibility: _dv.DefaultVisibility) -> str: + if defaultVisibility == _dv.DefaultVisibility.LOCAL: + return "" + if defaultVisibility == _dv.DefaultVisibility.GLOBAL: + return ":" + + _tp.assert_never(defaultVisibility) + + def _makeMultilineComment(text: str) -> str: textWithCollapsedWhitespace = _re.sub(r"\s+", " ", text.strip(), count=0, flags=_re.MULTILINE) linePrefix = "** " diff --git a/trnsysGUI/proforma/dialogs/_hydraulicConnections.ui b/trnsysGUI/proforma/dialogs/_hydraulicConnections.ui index a1a4c634..ef89cc0c 100644 --- a/trnsysGUI/proforma/dialogs/_hydraulicConnections.ui +++ b/trnsysGUI/proforma/dialogs/_hydraulicConnections.ui @@ -211,7 +211,7 @@ - + Advanced @@ -233,7 +233,7 @@ - + Local diff --git a/trnsysGUI/proforma/dialogs/editHydraulicConnectionsDialog.py b/trnsysGUI/proforma/dialogs/editHydraulicConnectionsDialog.py index 19aa55b5..90465a84 100644 --- a/trnsysGUI/proforma/dialogs/editHydraulicConnectionsDialog.py +++ b/trnsysGUI/proforma/dialogs/editHydraulicConnectionsDialog.py @@ -1,9 +1,11 @@ import collections.abc as _cabc import copy as _copy +import dataclasses as _dc import PyQt5.QtCore as _qtc import PyQt5.QtWidgets as _qtw +import pytrnsys.ddck.replaceTokens.defaultVisibility as _dv import trnsysGUI.common as _com import trnsysGUI.common.cancelled as _cancel import trnsysGUI.dialogs as _dlgs @@ -26,7 +28,13 @@ def createAndConnect(dialog: "EditHydraulicConnectionsDialog", comboBox: _qtw.QC return callback def onActivated(self, newIndex: int) -> None: - self._dialog.onComboBoxActivated(self._comboBox, newIndex) + self._dialog.onVariableComboBoxActivated(self._comboBox, newIndex) + + +@_dc.dataclass +class DialogResult: + hydraulicConnections: _cabc.Sequence[_models.Connection] + defaultVisibility: _dv.DefaultVisibility class EditHydraulicConnectionsDialog(_qtw.QDialog, _uigen.Ui_HydraulicConnections): @@ -49,7 +57,9 @@ def __init__( self._configureButtonBox() self._configureHydraulicConnections() - self._configureComboBoxes() + self._configureVariableComboBoxes() + + self._configureDefaultVisibilityGroupBox() self._reloadConnections() @@ -184,16 +194,20 @@ def _getSelectedConnection(self) -> _models.Connection: assert isinstance(selectedConnection, _models.Connection) return selectedConnection - def _configureComboBoxes(self) -> None: - for comboBox in self._comboBoxes: - onActivatedCallback = _OnActivatedCallBack.createAndConnect(self, comboBox) + def _configureVariableComboBoxes(self) -> None: + for variableComboBox in self._variableComboBoxes: + onActivatedCallback = _OnActivatedCallBack.createAndConnect(self, variableComboBox) self._onActivatedCallbacks.append(onActivatedCallback) - def onComboBoxActivated(self, comboBox: _qtw.QComboBox, newIndex: int) -> None: + def onVariableComboBoxActivated(self, comboBox: _qtw.QComboBox, newIndex: int) -> None: data = comboBox.itemData(newIndex) self._setVariableCorrespondingToComboBox(comboBox, data) self._reloadConnections() + def _configureDefaultVisibilityGroupBox(self) -> None: + for defaultVisibility in _dv.DefaultVisibility: + self.defaultVisibilityComboBox.addItem(defaultVisibility.name.capitalize(), defaultVisibility) + def _reloadConnections(self) -> None: self._resetOkButton() self._reloadSelectedComboBoxItems() @@ -222,12 +236,12 @@ def _areAnyRequiredVariablesUnset(self) -> bool: def _reloadSelectedComboBoxItems(self) -> None: self._reconfigureComboBoxOptions() - for comboBox in self._comboBoxes: + for comboBox in self._variableComboBoxes: data = self._getVariableCorrespondingToComboBox(comboBox) _setSelected(comboBox, data) @property - def _comboBoxes(self) -> _cabc.Sequence[_qtw.QComboBox]: + def _variableComboBoxes(self) -> _cabc.Sequence[_qtw.QComboBox]: return [ self.fluidDensityComboBox, self.fluidHeatCapacityComboBox, @@ -263,14 +277,22 @@ def _setVariableCorrespondingToComboBox( def showDialogAndGetResults( suggestedHydraulicConnections: _cabc.Sequence[_models.Connection], variablesByRole: _models.VariablesByRole, - ) -> _cancel.MaybeCancelled[_cabc.Sequence[_models.Connection]]: + ) -> _cancel.MaybeCancelled[DialogResult]: dialog = EditHydraulicConnectionsDialog(suggestedHydraulicConnections, variablesByRole) returnValue = dialog.exec() if returnValue == _qtw.QDialog.Rejected: return _cancel.CANCELLED - return dialog.hydraulicConnections + defaultVisibility = ( + dialog.defaultVisibilityComboBox.currentData() + if dialog.advancedOptionsGroupBox.isChecked() + else _dv.DefaultVisibility.LOCAL + ) + + dialogResult = DialogResult(dialog.hydraulicConnections, defaultVisibility) + + return dialogResult def _setSelected(comboBox: _qtw.QComboBox, data: _models.Variable | _models.Unset) -> None: diff --git a/trnsysGUI/proforma/templates/ddck.jinja b/trnsysGUI/proforma/templates/ddck.jinja index 2c967d33..92857a66 100644 --- a/trnsysGUI/proforma/templates/ddck.jinja +++ b/trnsysGUI/proforma/templates/ddck.jinja @@ -19,7 +19,7 @@ {% if inputsWithHydraulicData|length %} EQUATIONS {{inputsWithHydraulicData|length}} {% for input in inputsWithHydraulicData %} -{{input.hydraulicConnectionsData.variableName}} = {{input.hydraulicConnectionsData.rhs}} +{{visibilityModifier}}{{input.hydraulicConnectionsData.variableName}} = {{input.hydraulicConnectionsData.rhs}} {% endfor %} {% endif %} @@ -30,7 +30,7 @@ EQUATIONS {{inputsWithHydraulicData|length}} {% if outputsWithHydraulicData|length %} EQUATIONS {{outputsWithHydraulicData|length}} {% for outputWithHydraulicData in outputsWithHydraulicData %} -{{outputWithHydraulicData.hydraulicConnectionsData.rhs}} = {{outputWithHydraulicData.hydraulicConnectionsData.variableName}} +{{outputWithHydraulicData.hydraulicConnectionsData.rhs}} = {{visibilityModifier}}{{outputWithHydraulicData.hydraulicConnectionsData.variableName}} {% endfor %} {% endif %} @@ -61,7 +61,7 @@ UNIT 1 TYPE {{type}} PARAMETERS {{parameters|length}} {% for parameter in parameters %} {% if parameter.hydraulicConnectionsData %} -{{parameter.hydraulicConnectionsData.variableName}} ! {{parameter.info}} +{{visibilityModifier}}{{parameter.hydraulicConnectionsData.variableName}} ! {{parameter.info}} {% else %} {{parameter.defaultValue}} ! {{parameter.info}} {% endif %} @@ -69,7 +69,7 @@ PARAMETERS {{parameters|length}} INPUTS {{inputs|length}} {% for input in inputs %} {% if input.hydraulicConnectionsData %} -{{input.hydraulicConnectionsData.variableName}} ! {{input.info}} +{{visibilityModifier}}{{input.hydraulicConnectionsData.variableName}} ! {{input.info}} {% else %} 0,0 ! {{input.roleOrder}}: {{input.tmfName}} [{{input.unit}}] ({{input.bounds}}) {% endif %} @@ -86,9 +86,9 @@ EQUATIONS {{outputsWithHydraulicData|length}} ! {{outputs|length}} {% endif %} {% for output in outputs %} {% if output.hydraulicConnectionsData %} -{{output.hydraulicConnectionsData.variableName}} = [1, {{output.roleOrder}}] ! {{output.tmfName}} [{{output.unit}}] ({{output.bounds}}) +{{visibilityModifier}}{{output.hydraulicConnectionsData.variableName}} = [1, {{output.roleOrder}}] ! {{output.tmfName}} [{{output.unit}}] ({{output.bounds}}) {% else %} -! XXX = [1, {{output.roleOrder}}] ! {{output.tmfName}} [{{output.unit}}] ({{output.bounds}}) +! {{visibilityModifier}}XXX = [1, {{output.roleOrder}}] ! {{output.tmfName}} [{{output.unit}}] ({{output.bounds}}) {% endif %} {% endfor %} From 3609d8f4e353749e01f5a3f1d3d52984cb263732 Mon Sep 17 00:00:00 2001 From: Damian Birchler Date: Tue, 30 Jul 2024 16:36:37 +0200 Subject: [PATCH 31/33] Satisfy `pylint`. --- trnsysGUI/proforma/convertXmlTmfToDdck.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/trnsysGUI/proforma/convertXmlTmfToDdck.py b/trnsysGUI/proforma/convertXmlTmfToDdck.py index d8ec1ef0..c781ffd7 100644 --- a/trnsysGUI/proforma/convertXmlTmfToDdck.py +++ b/trnsysGUI/proforma/convertXmlTmfToDdck.py @@ -216,7 +216,9 @@ def convertXmlTmfStringToDdck( return _convertXmlTmfStringToDdck(hydraulicConnections, variablesByRole, otherJinjaVariables) -def _getVisibilityModifier(defaultVisibility: _dv.DefaultVisibility) -> str: +def _getVisibilityModifier( # pylint: disable=inconsistent-return-statements + defaultVisibility: _dv.DefaultVisibility, +) -> str: if defaultVisibility == _dv.DefaultVisibility.LOCAL: return "" if defaultVisibility == _dv.DefaultVisibility.GLOBAL: From a481bf5a131939242c54c3438d3d510bcb9fc7da Mon Sep 17 00:00:00 2001 From: Damian Birchler Date: Tue, 30 Jul 2024 16:41:56 +0200 Subject: [PATCH 32/33] Remove group box items hard-coded in .ui file. --- trnsysGUI/proforma/dialogs/_hydraulicConnections.ui | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/trnsysGUI/proforma/dialogs/_hydraulicConnections.ui b/trnsysGUI/proforma/dialogs/_hydraulicConnections.ui index ef89cc0c..78e1333d 100644 --- a/trnsysGUI/proforma/dialogs/_hydraulicConnections.ui +++ b/trnsysGUI/proforma/dialogs/_hydraulicConnections.ui @@ -233,18 +233,7 @@ - - - - Local - - - - - Global - - - + From 87aa6fbf9ca3f902a83fc6c17c772163939adcd0 Mon Sep 17 00:00:00 2001 From: Damian Birchler Date: Tue, 30 Jul 2024 16:55:40 +0200 Subject: [PATCH 33/33] Remove some dead code to pass coveralls. :D --- trnsysGUI/DeepInspector.py | 95 ----------------- trnsysGUI/TestDlg.py | 37 ------- trnsysGUI/Test_Export.py | 207 ------------------------------------ trnsysGUI/closeDlg.py | 37 ------- trnsysGUI/diagram/Editor.py | 39 ------- 5 files changed, 415 deletions(-) delete mode 100644 trnsysGUI/DeepInspector.py delete mode 100644 trnsysGUI/TestDlg.py delete mode 100644 trnsysGUI/Test_Export.py delete mode 100644 trnsysGUI/closeDlg.py diff --git a/trnsysGUI/DeepInspector.py b/trnsysGUI/DeepInspector.py deleted file mode 100644 index 9512afd4..00000000 --- a/trnsysGUI/DeepInspector.py +++ /dev/null @@ -1,95 +0,0 @@ -# pylint: skip-file -# type: ignore - -import sys -import re - -from PyQt5.QtWidgets import QDialog, QLabel, QHBoxLayout, QPushButton, QLineEdit, QGridLayout, QListWidget, QRadioButton - - -class DeepInspector(QDialog): - def __init__(self, parent): - super(DeepInspector, self).__init__(parent) - - self.stdout = sys.stdout - self.messages = [] - - self.editor = parent - self.currentObj = parent - - funcLabel = QLabel("Enter function call:") - self.le = QLineEdit() - - self.okButton = QPushButton("Execute") - self.cancelButton = QPushButton("Cancel") - - self.listW = QListWidget() - - radioButtonLayout = QHBoxLayout() - self.rMeth = QRadioButton("Method") - self.rAttr = QRadioButton("Attr") - radioButtonLayout.addWidget(self.rMeth) - radioButtonLayout.addWidget(self.rAttr) - - buttonLayout = QHBoxLayout() - buttonLayout.addStretch() - buttonLayout.addWidget(self.okButton) - buttonLayout.addWidget(self.cancelButton) - layout = QGridLayout() - layout.addWidget(funcLabel, 0, 0) - layout.addWidget(self.le, 1, 0) - layout.addWidget(self.listW, 2, 0) - layout.addLayout(radioButtonLayout, 3, 0, 2, 0) - layout.addLayout(buttonLayout, 5, 0, 2, 0) - self.setLayout(layout) - - self.okButton.clicked.connect(self.acceptedEdit) - self.cancelButton.clicked.connect(self.cancel) - self.setWindowTitle("Diagram Properties") - self.show() - - def acceptedEdit(self): - if not (self.rMeth.isChecked() or self.rAttr.isChecked()): - print("No button checked") - else: - t = self.le.text() - # b = False - # if "(" in t: - # b = True - if self.rMeth.isChecked(): - funcStr = re.findall(r".{0,}\(", t)[0][:-1] - argStr = re.findall(r"\(.{0,}\)", t)[0][1:-1] - print(funcStr + ", " + argStr) - if argStr == "": - self.executeMeth((funcStr)) - else: - self.executeMeth(funcStr, argStr) - else: - funcStr = t - argStr = None - self.currentObj = self.getAtr(funcStr) - - # self.stopLog() - print("now in console should appear: " + str(self.messages)) - self.listW.addItem(funcStr) - - def cancel(self): - self.close() - - def executeMeth(self, meth, *args): - res = getattr(self.currentObj, meth)(*args) - # if type(res) is not int and type(res) is not list: - print(type(res)) - self.currentObj = res - - def getAtr(self, name): - return getattr(self.currentObj, name) - - def startLog(self): - sys.stdout = self - - def stopLog(self): - sys.stdout = self.stdout - - def write(self, text): - self.messages.append(text) diff --git a/trnsysGUI/TestDlg.py b/trnsysGUI/TestDlg.py deleted file mode 100644 index d71deb19..00000000 --- a/trnsysGUI/TestDlg.py +++ /dev/null @@ -1,37 +0,0 @@ -# pylint: skip-file -# type: ignore - -from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QVBoxLayout, QLabel - - -class TestDlg(QDialog): - """ - This is the dialog box for testing. It will be called when an exported file cannot be found inside the reference - folder. (file of same name doesnt exist inside reference folder) - """ - - def __init__(self, exportedFile, *args, **kwargs): - super(TestDlg, self).__init__(*args, **kwargs) - - self.exportBool = False - - self.setWindowTitle("Error!") - self.dlgMessage = QLabel() - self.strMessage = "%s not found inside Reference folder, add the file into Reference folder?" % exportedFile - self.dlgMessage.setText(self.strMessage) - - QBtn = QDialogButtonBox.Ok | QDialogButtonBox.Cancel - - self.buttonBox = QDialogButtonBox(QBtn) - self.buttonBox.accepted.connect(self.acceptExport) - self.buttonBox.rejected.connect(self.reject) - - self.layout = QVBoxLayout() - self.layout.addWidget(self.dlgMessage) - self.layout.addWidget(self.buttonBox) - self.setLayout(self.layout) - self.exec_() - - def acceptExport(self): - self.exportBool = True - self.accept() diff --git a/trnsysGUI/Test_Export.py b/trnsysGUI/Test_Export.py deleted file mode 100644 index 15a7eec9..00000000 --- a/trnsysGUI/Test_Export.py +++ /dev/null @@ -1,207 +0,0 @@ -# pylint: skip-file -# type: ignore - -import filecmp -import os - - -class Test_Export(object): - """ - Just a class that contains the method for testing. - No need to declare or pass anything. - Parameters should be passed into the individual functions to avoid unnecessary instantiotion - of class. - """ - - testPassed = True - - def retrieveFiles(self, exportedFilepath, originalFilePath, exportedFileList, originalFileList): - """ - - Parameters - ---------- - exportedFilepath : Folder where files are exported to - originalFilePath : Folder containing the reference files - exportedFileList : List of files inside the exported folder - originalFileList : List of files inside the reference folder - - 1.Access the exported folder - 2.Retrieve all exported files and add into a list - 3.Access the reference folder - 4.Retrieve all reference files and add into a list - 5.Sort the two lists - - Returns - ------- - - """ - for efiles in os.listdir(exportedFilepath): - if efiles != ".keepMe": - # exportedFile = exportedFilepath + efiles - exportedFile = os.path.join(exportedFilepath, efiles) - if exportedFile not in exportedFileList: - exportedFileList.append(exportedFile) - - for ofiles in os.listdir(originalFilePath): - # originalFile = originalFilePath + ofiles - originalFile = os.path.join(originalFilePath, ofiles) - if originalFile not in originalFileList: - originalFileList.append(originalFile) - - self.sortList(exportedFileList, originalFileList) - - def checkFileExists(self, exportedFile, originalFileList): - """ - - Parameters - ---------- - exportedFile : ONE FILE from the export_test folder - originalFileList : list of files inside the reference folder - - 1.Check if the individual files inside the export_test folder can be found - inside the reference folder as well. - 2.If found, return true. - 3.Else, return false. - - Returns - ------- - - """ - i = 0 - exportedFile = exportedFile.split("\\") - while i < len(originalFileList): - referenceFile = originalFileList[i].split("\\") - if exportedFile[-1] == referenceFile[-1]: - return True - i += 1 - return False - - def checkFiles(self, exportedFileList, originalFileList): - """ - - Parameters - ---------- - exportedFileList : All files inside the export_test folder - originalFileList : All files inside the reference folder - - 1.Check each exported file against the reference file of the same name - 2.If two files of the same name from both folders are found, check their contents - 3.If the contents are not the same, append the file index(order inside the folder) into fileErrorList - and set testPassed to False - 4.Return fileErrorList and testPassed attribute - - Returns - ------- - - """ - - i = 0 - fileErrorList = [] - found = False - while i < len(exportedFileList): - j = 0 - while j < len(originalFileList): - exportedFile = exportedFileList[i].split("\\")[-1] - referencefile = originalFileList[j].split("\\")[-1] - # print('this is exported :' + exportedFile + '\n') - # print('this is reference :' + referencefile + '\n') - if exportedFile != referencefile: - j += 1 - else: - found = True - break - if found: - if not filecmp.cmp(exportedFileList[i], originalFileList[j], shallow=False): - # todo : can maybe simplify this part is not using random number for pump power - fileOne = open(exportedFileList[i]) - fileTwo = open(originalFileList[j]) - - fileOneLine = fileOne.readline() - fileTwoLine = fileTwo.readline() - - while fileOneLine: - if fileOneLine != fileTwoLine: - if fileOneLine.split(" = ")[0][:5] != "MfrPu": - self.testPassed = False - fileErrorList.append(i) - break - - fileOneLine = fileOne.readline() - fileTwoLine = fileTwo.readline() - # break - i += 1 - return fileErrorList, self.testPassed - # return i, self.testPassed - - def deleteFiles(self, fileDirectory): - """ - - Parameters - ---------- - fileDirectory : A specified file directory - - 1.Delete every files inside the specified file directory - - Returns - ------- - - """ - for dfiles in os.listdir(fileDirectory): - # deleteFiles = fileDirectory + dfiles - deleteFiles = os.path.join(fileDirectory, dfiles) - if dfiles != ".keepMe": - os.remove(deleteFiles) - - def showDifference(self, file1, file2): - """ - - Parameters - ---------- - file1 : exported file - file2 : reference file - - 1.Read a line from exported file and reference file - 2.Compare the two lines, if they are identical, go to next line. - 3.Else, Append the lines with their line number into two separate list. - 4.Close the files and return the two lists. - - Returns - ------- - - """ - line = 1 - fileOne = open(file1) - fileTwo = open(file2) - - file1List = [] - file2List = [] - - fileOneLine = fileOne.readline() - fileTwoLine = fileTwo.readline() - - while fileOneLine: - if fileOneLine != fileTwoLine: - print("Error found at line %d\n" % line) - print("Exported file: %s \nReference file: %s \n" % (fileOneLine, fileTwoLine)) - if fileOneLine.split(" = ")[0][:5] != "MfrPu": - file1Str = str(line) + ":" + fileOneLine - file2Str = str(line) + ":" + fileTwoLine - file1List.append(file1Str) - file2List.append(file2Str) - fileOneLine = fileOne.readline() - fileTwoLine = fileTwo.readline() - line += 1 - - # print("File 1 list:") - # print(file1List) - # print("\nFile 2 List:") - # print(file2List) - - fileOne.close() - fileTwo.close() - - return file1List, file2List - - def sortList(self, list1, list2): - list1.sort() - list2.sort() diff --git a/trnsysGUI/closeDlg.py b/trnsysGUI/closeDlg.py deleted file mode 100644 index 0658ddb7..00000000 --- a/trnsysGUI/closeDlg.py +++ /dev/null @@ -1,37 +0,0 @@ -# pylint: skip-file -# type: ignore - -from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QVBoxLayout, QLabel - - -class closeDlg(QDialog): - """ - Not used currently. Keep for future sake. - Used to prompt user whether to save their current template - """ - - def __init__(self, *args, **kwargs): - super(closeDlg, self).__init__(*args, **kwargs) - - self.closeBool = False - - self.setWindowTitle("Closing!") - self.dlgMessage = QLabel() - self.strMessage = "Do you want to retain the current template for when you next open the application?" - self.dlgMessage.setText(self.strMessage) - - QBtn = QDialogButtonBox.Ok | QDialogButtonBox.Cancel - - self.buttonBox = QDialogButtonBox(QBtn) - self.buttonBox.accepted.connect(self.acceptExport) - self.buttonBox.rejected.connect(self.reject) - - self.layout = QVBoxLayout() - self.layout.addWidget(self.dlgMessage) - self.layout.addWidget(self.buttonBox) - self.setLayout(self.layout) - self.exec_() - - def acceptExport(self): - self.closeBool = True - self.accept() diff --git a/trnsysGUI/diagram/Editor.py b/trnsysGUI/diagram/Editor.py index 15612b1c..6f93031c 100644 --- a/trnsysGUI/diagram/Editor.py +++ b/trnsysGUI/diagram/Editor.py @@ -858,45 +858,6 @@ def renameDiagram(self, newName): self.diagramName = newName self.parent().currentFile = newName - # fromPath = self.projectFolder - # destPath = os.path.dirname(__file__) - # destPath = os.path.join(destPath, 'default') - # destPath = os.path.join(destPath, newName) - # os.rename(fromPath, destPath) - - # print("Path is now: " + str(self.saveAsPath)) - # print("Diagram name is: " + self.diagramName) - - def saveAtClose(self): - self.logger.info("saveaspath is " + str(self.saveAsPath)) - - # closeDialog = closeDlg() - # if closeDialog.closeBool: - filepath = _pl.Path(_pl.Path(__file__).resolve().parent.joinpath("recent")) - self.encodeDiagram(str(filepath.joinpath(self.diagramName + ".json"))) - - # Mode related - def setAlignMode(self, b): - self.alignMode = True - - def setMoveDirectPorts(self, b): - """ - Sets the bool moveDirectPorts. When mouse released in diagramScene, moveDirectPorts is set to False again - Parameters - ---------- - b : bool - - Returns - ------- - - """ - self.moveDirectPorts = b - - def setSnapGrid(self, b): - self.snapGrid = b - - def setSnapSize(self, s): - self.snapSize = s def setConnLabelVis(self, isVisible: bool) -> None: for c in self.trnsysObj: