From c62ce459877c26352f6cd7817906d7345c432e76 Mon Sep 17 00:00:00 2001 From: Miguel Date: Fri, 18 Apr 2025 17:16:25 +0200 Subject: [PATCH] Modificado x1 para mantener los enlaces de EN con rails multiples --- BlenderRun_ProdTime.scl | 14 +- BlenderRun_ProdTime_simplified.json | 270 ++-- ...Run_ProdTime_simplified_scl_processed.json | 320 +++-- x1_to_json.py | 546 ++++--- x2_process.py | 1270 ++++++++++------- 5 files changed, 1325 insertions(+), 1095 deletions(-) diff --git a/BlenderRun_ProdTime.scl b/BlenderRun_ProdTime.scl index 4725e26..2f933ed 100644 --- a/BlenderRun_ProdTime.scl +++ b/BlenderRun_ProdTime.scl @@ -52,9 +52,7 @@ BEGIN // Network 5: Minute Counter - IF "m1MinONS" THEN - "Blender_Variables_Pers"."gProdSec" := 0; - END_IF; + "Blender_Variables_Pers"."gProdSec" := 0; "Blender_Variables_Pers"."gProdMin" := "Blender_Variables_Pers"."gProdMin" + 1; // Network 6: Hour @@ -63,17 +61,13 @@ BEGIN // Network 7: Hour Counter - IF "m1HourONS" THEN - "Blender_Variables_Pers"."gProdMin" := 0; - END_IF; + "Blender_Variables_Pers"."gProdMin" := 0; "Blender_Variables_Pers"."gProdHour" := "Blender_Variables_Pers"."gProdHour" + 1; "Blender_Variables_Pers"."gBlendingMaintHour" := "Blender_Variables_Pers"."gBlendingMaintHour" + 1; // Network 8: Counter reset - IF "gBlenderCIPMode" OR "gBlenderRinseMode" THEN - "Blender_Variables_Pers"."gProdSec" := 0; - END_IF; + "Blender_Variables_Pers"."gProdSec" := 0; "Blender_Variables_Pers"."gProdMin" := 0; "Blender_Variables_Pers"."gProdHour" := 0; @@ -90,7 +84,7 @@ BEGIN IF ("MOD60" = DINT#0 AND "Procedure_Variables"."Blender_Run"."Running") AND "CLK_1.0S" THEN "Blender_Variables_Pers"."gRunningMinutes" := "Blender_Variables_Pers"."gRunningMinutes" + 1; END_IF; - // Edge detection PBox 41 -> P_TRIG_FUNC(CLK := (("MOD60" = DINT#0 AND "Procedure_Variables"."Blender_Run"."Running") AND "CLK_1.0S"), M := "M19012") (CLK source inferred) + // Edge detection PBox 41 -> P_TRIG_FUNC(CLK := (("MOD60" = DINT#0 AND "Procedure_Variables"."Blender_Run"."Running") AND "CLK_1.0S"), M := "M19012") (CLK inferred) "mRunMin" := P_TRIG_FUNC(CLK := (("MOD60" = DINT#0 AND "Procedure_Variables"."Blender_Run"."Running") AND "CLK_1.0S"), M := "M19012"); // Network 11: Running Hours for Maintenance diff --git a/BlenderRun_ProdTime_simplified.json b/BlenderRun_ProdTime_simplified.json index 7bac087..dd19691 100644 --- a/BlenderRun_ProdTime_simplified.json +++ b/BlenderRun_ProdTime_simplified.json @@ -55,14 +55,14 @@ "instruction_uid": "26", "type": "Contact", "inputs": { + "in": { + "type": "powerrail" + }, "operand": { "uid": "21", "scope": "GlobalVariable", "type": "variable", "name": "\"Procedure_Variables\".\"Blender_Run\".\"Running\"" - }, - "in": { - "type": "powerrail" } }, "outputs": {} @@ -71,17 +71,17 @@ "instruction_uid": "27", "type": "Contact", "inputs": { - "operand": { - "uid": "22", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"CLK_1.0S\"" - }, "in": { "type": "connection", "source_instruction_uid": "26", "source_instruction_type": "Contact", "source_pin": "out" + }, + "operand": { + "uid": "22", + "scope": "GlobalVariable", + "type": "variable", + "name": "\"CLK_1.0S\"" } }, "outputs": {} @@ -90,6 +90,12 @@ "instruction_uid": "28", "type": "Add", "inputs": { + "en": { + "type": "connection", + "source_instruction_uid": "27", + "source_instruction_type": "Contact", + "source_pin": "out" + }, "in1": { "uid": "23", "scope": "GlobalVariable", @@ -102,12 +108,6 @@ "type": "constant", "datatype": "Int", "value": 1 - }, - "en": { - "type": "connection", - "source_instruction_uid": "27", - "source_instruction_type": "Contact", - "source_pin": "out" } }, "outputs": { @@ -132,14 +132,14 @@ "instruction_uid": "24", "type": "Contact", "inputs": { + "in": { + "type": "powerrail" + }, "operand": { "uid": "21", "scope": "GlobalVariable", "type": "variable", "name": "\"SLIM_Variables\".\"ResetHour\"" - }, - "in": { - "type": "powerrail" } }, "outputs": {} @@ -148,18 +148,18 @@ "instruction_uid": "25", "type": "Move", "inputs": { + "en": { + "type": "connection", + "source_instruction_uid": "24", + "source_instruction_type": "Contact", + "source_pin": "out" + }, "in": { "uid": "22", "scope": "LiteralConstant", "type": "constant", "datatype": "Int", "value": 0 - }, - "en": { - "type": "connection", - "source_instruction_uid": "24", - "source_instruction_type": "Contact", - "source_pin": "out" } }, "outputs": { @@ -184,14 +184,14 @@ "instruction_uid": "26", "type": "Contact", "inputs": { + "in": { + "type": "powerrail" + }, "operand": { "uid": "21", "scope": "GlobalVariable", "type": "variable", "name": "\"gBlenderBlending\"" - }, - "in": { - "type": "powerrail" } }, "outputs": {} @@ -200,17 +200,17 @@ "instruction_uid": "27", "type": "Contact", "inputs": { - "operand": { - "uid": "22", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"CLK_1.0S\"" - }, "in": { "type": "connection", "source_instruction_uid": "26", "source_instruction_type": "Contact", "source_pin": "out" + }, + "operand": { + "uid": "22", + "scope": "GlobalVariable", + "type": "variable", + "name": "\"CLK_1.0S\"" } }, "outputs": {} @@ -219,6 +219,12 @@ "instruction_uid": "28", "type": "Add", "inputs": { + "en": { + "type": "connection", + "source_instruction_uid": "27", + "source_instruction_type": "Contact", + "source_pin": "out" + }, "in1": { "uid": "23", "scope": "GlobalVariable", @@ -231,12 +237,6 @@ "type": "constant", "datatype": "Int", "value": 1 - }, - "en": { - "type": "connection", - "source_instruction_uid": "27", - "source_instruction_type": "Contact", - "source_pin": "out" } }, "outputs": { @@ -310,14 +310,14 @@ "instruction_uid": "27", "type": "Contact", "inputs": { + "in": { + "type": "powerrail" + }, "operand": { "uid": "21", "scope": "LocalVariable", "type": "variable", "name": "\"m1MinONS\"" - }, - "in": { - "type": "powerrail" } }, "outputs": {} @@ -326,18 +326,18 @@ "instruction_uid": "28", "type": "Move", "inputs": { + "en": { + "type": "connection", + "source_instruction_uid": "27", + "source_instruction_type": "Contact", + "source_pin": "out" + }, "in": { "uid": "22", "scope": "LiteralConstant", "type": "constant", "datatype": "Int", "value": 0 - }, - "en": { - "type": "connection", - "source_instruction_uid": "27", - "source_instruction_type": "Contact", - "source_pin": "out" } }, "outputs": { @@ -367,6 +367,12 @@ "type": "constant", "datatype": "Int", "value": 1 + }, + "en": { + "type": "connection", + "source_instruction_uid": "27", + "source_instruction_type": "Contact", + "source_pin": "out" } }, "outputs": { @@ -440,14 +446,14 @@ "instruction_uid": "30", "type": "Contact", "inputs": { + "in": { + "type": "powerrail" + }, "operand": { "uid": "21", "scope": "LocalVariable", "type": "variable", "name": "\"m1HourONS\"" - }, - "in": { - "type": "powerrail" } }, "outputs": {} @@ -456,18 +462,18 @@ "instruction_uid": "31", "type": "Move", "inputs": { + "en": { + "type": "connection", + "source_instruction_uid": "30", + "source_instruction_type": "Contact", + "source_pin": "out" + }, "in": { "uid": "22", "scope": "LiteralConstant", "type": "constant", "datatype": "Int", "value": 0 - }, - "en": { - "type": "connection", - "source_instruction_uid": "30", - "source_instruction_type": "Contact", - "source_pin": "out" } }, "outputs": { @@ -497,6 +503,12 @@ "type": "constant", "datatype": "Int", "value": 1 + }, + "en": { + "type": "connection", + "source_instruction_uid": "30", + "source_instruction_type": "Contact", + "source_pin": "out" } }, "outputs": { @@ -526,6 +538,12 @@ "type": "constant", "datatype": "Int", "value": 1 + }, + "en": { + "type": "connection", + "source_instruction_uid": "30", + "source_instruction_type": "Contact", + "source_pin": "out" } }, "outputs": { @@ -550,14 +568,14 @@ "instruction_uid": "29", "type": "Contact", "inputs": { + "in": { + "type": "powerrail" + }, "operand": { "uid": "21", "scope": "GlobalVariable", "type": "variable", "name": "\"gBlenderCIPMode\"" - }, - "in": { - "type": "powerrail" } }, "outputs": {} @@ -598,18 +616,18 @@ "instruction_uid": "32", "type": "Move", "inputs": { + "en": { + "type": "connection", + "source_instruction_uid": "31", + "source_instruction_type": "O", + "source_pin": "out" + }, "in": { "uid": "23", "scope": "LiteralConstant", "type": "constant", "datatype": "Int", "value": 0 - }, - "en": { - "type": "connection", - "source_instruction_uid": "31", - "source_instruction_type": "O", - "source_pin": "out" } }, "outputs": { @@ -633,6 +651,12 @@ "type": "constant", "datatype": "Int", "value": 0 + }, + "en": { + "type": "connection", + "source_instruction_uid": "31", + "source_instruction_type": "O", + "source_pin": "out" } }, "outputs": { @@ -656,6 +680,12 @@ "type": "constant", "datatype": "Int", "value": 0 + }, + "en": { + "type": "connection", + "source_instruction_uid": "31", + "source_instruction_type": "O", + "source_pin": "out" } }, "outputs": { @@ -680,14 +710,14 @@ "instruction_uid": "26", "type": "Contact", "inputs": { + "in": { + "type": "powerrail" + }, "operand": { "uid": "21", "scope": "GlobalVariable", "type": "variable", "name": "\"Procedure_Variables\".\"Blender_Run\".\"Running\"" - }, - "in": { - "type": "powerrail" } }, "outputs": {} @@ -696,17 +726,17 @@ "instruction_uid": "27", "type": "Contact", "inputs": { - "operand": { - "uid": "22", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"CLK_1.0S\"" - }, "in": { "type": "connection", "source_instruction_uid": "26", "source_instruction_type": "Contact", "source_pin": "out" + }, + "operand": { + "uid": "22", + "scope": "GlobalVariable", + "type": "variable", + "name": "\"CLK_1.0S\"" } }, "outputs": {} @@ -715,6 +745,12 @@ "instruction_uid": "28", "type": "Add", "inputs": { + "en": { + "type": "connection", + "source_instruction_uid": "27", + "source_instruction_type": "Contact", + "source_pin": "out" + }, "in1": { "uid": "23", "scope": "GlobalVariable", @@ -727,12 +763,6 @@ "type": "constant", "datatype": "Int", "value": 1 - }, - "en": { - "type": "connection", - "source_instruction_uid": "27", - "source_instruction_type": "Contact", - "source_pin": "out" } }, "outputs": { @@ -757,14 +787,14 @@ "instruction_uid": "35", "type": "Convert", "inputs": { + "en": { + "type": "powerrail" + }, "in": { "uid": "21", "scope": "GlobalVariable", "type": "variable", "name": "\"Blender_Variables_Pers\".\"gRunningSeconds\"" - }, - "en": { - "type": "powerrail" } }, "outputs": { @@ -825,12 +855,6 @@ "instruction_uid": "37", "type": "Eq", "inputs": { - "pre": { - "type": "connection", - "source_instruction_uid": "36", - "source_instruction_type": "Mod", - "source_pin": "eno" - }, "in1": { "uid": "26", "scope": "LocalVariable", @@ -843,6 +867,12 @@ "type": "constant", "datatype": "TypedConstant", "value": "DINT#0" + }, + "pre": { + "type": "connection", + "source_instruction_uid": "36", + "source_instruction_type": "Mod", + "source_pin": "eno" } }, "outputs": {} @@ -870,17 +900,17 @@ "instruction_uid": "39", "type": "Contact", "inputs": { - "operand": { - "uid": "29", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"CLK_1.0S\"" - }, "in": { "type": "connection", "source_instruction_uid": "38", "source_instruction_type": "Contact", "source_pin": "out" + }, + "operand": { + "uid": "29", + "scope": "GlobalVariable", + "type": "variable", + "name": "\"CLK_1.0S\"" } }, "outputs": {} @@ -889,6 +919,12 @@ "instruction_uid": "40", "type": "Add", "inputs": { + "en": { + "type": "connection", + "source_instruction_uid": "39", + "source_instruction_type": "Contact", + "source_pin": "out" + }, "in1": { "uid": "30", "scope": "GlobalVariable", @@ -901,12 +937,6 @@ "type": "constant", "datatype": "Int", "value": 1 - }, - "en": { - "type": "connection", - "source_instruction_uid": "39", - "source_instruction_type": "Contact", - "source_pin": "out" } }, "outputs": { @@ -963,14 +993,14 @@ "instruction_uid": "32", "type": "Contact", "inputs": { + "in": { + "type": "powerrail" + }, "operand": { "uid": "21", "scope": "LocalVariable", "type": "variable", "name": "\"mRunMin\"" - }, - "in": { - "type": "powerrail" } }, "outputs": {} @@ -979,17 +1009,17 @@ "instruction_uid": "33", "type": "Convert", "inputs": { - "in": { - "uid": "22", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"Blender_Variables_Pers\".\"gRunningMinutes\"" - }, "en": { "type": "connection", "source_instruction_uid": "32", "source_instruction_type": "Contact", "source_pin": "out" + }, + "in": { + "uid": "22", + "scope": "GlobalVariable", + "type": "variable", + "name": "\"Blender_Variables_Pers\".\"gRunningMinutes\"" } }, "outputs": { @@ -1050,12 +1080,6 @@ "instruction_uid": "35", "type": "Eq", "inputs": { - "pre": { - "type": "connection", - "source_instruction_uid": "34", - "source_instruction_type": "Mod", - "source_pin": "eno" - }, "in1": { "uid": "27", "scope": "LocalVariable", @@ -1068,6 +1092,12 @@ "type": "constant", "datatype": "TypedConstant", "value": "DINT#0" + }, + "pre": { + "type": "connection", + "source_instruction_uid": "34", + "source_instruction_type": "Mod", + "source_pin": "eno" } }, "outputs": {} @@ -1118,14 +1148,14 @@ "instruction_uid": "23", "type": "Move", "inputs": { + "en": { + "type": "powerrail" + }, "in": { "uid": "21", "scope": "GlobalVariable", "type": "variable", "name": "\"Blender_Variables_Pers\".\"gRunningMaintHour\"" - }, - "en": { - "type": "powerrail" } }, "outputs": { diff --git a/BlenderRun_ProdTime_simplified_scl_processed.json b/BlenderRun_ProdTime_simplified_scl_processed.json index f14f84b..9b202c4 100644 --- a/BlenderRun_ProdTime_simplified_scl_processed.json +++ b/BlenderRun_ProdTime_simplified_scl_processed.json @@ -55,14 +55,14 @@ "instruction_uid": "26", "type": "Contact_scl", "inputs": { + "in": { + "type": "powerrail" + }, "operand": { "uid": "21", "scope": "GlobalVariable", "type": "variable", "name": "\"Procedure_Variables\".\"Blender_Run\".\"Running\"" - }, - "in": { - "type": "powerrail" } }, "outputs": {}, @@ -72,17 +72,17 @@ "instruction_uid": "27", "type": "Contact_scl", "inputs": { - "operand": { - "uid": "22", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"CLK_1.0S\"" - }, "in": { "type": "connection", "source_instruction_uid": "26", "source_instruction_type": "Contact", "source_pin": "out" + }, + "operand": { + "uid": "22", + "scope": "GlobalVariable", + "type": "variable", + "name": "\"CLK_1.0S\"" } }, "outputs": {}, @@ -92,6 +92,12 @@ "instruction_uid": "28", "type": "Add_scl", "inputs": { + "en": { + "type": "connection", + "source_instruction_uid": "27", + "source_instruction_type": "Contact", + "source_pin": "out" + }, "in1": { "uid": "23", "scope": "GlobalVariable", @@ -104,12 +110,6 @@ "type": "constant", "datatype": "Int", "value": 1 - }, - "en": { - "type": "connection", - "source_instruction_uid": "27", - "source_instruction_type": "Contact", - "source_pin": "out" } }, "outputs": { @@ -135,14 +135,14 @@ "instruction_uid": "24", "type": "Contact_scl", "inputs": { + "in": { + "type": "powerrail" + }, "operand": { "uid": "21", "scope": "GlobalVariable", "type": "variable", "name": "\"SLIM_Variables\".\"ResetHour\"" - }, - "in": { - "type": "powerrail" } }, "outputs": {}, @@ -152,18 +152,18 @@ "instruction_uid": "25", "type": "Move_scl", "inputs": { + "en": { + "type": "connection", + "source_instruction_uid": "24", + "source_instruction_type": "Contact", + "source_pin": "out" + }, "in": { "uid": "22", "scope": "LiteralConstant", "type": "constant", "datatype": "Int", "value": 0 - }, - "en": { - "type": "connection", - "source_instruction_uid": "24", - "source_instruction_type": "Contact", - "source_pin": "out" } }, "outputs": { @@ -189,14 +189,14 @@ "instruction_uid": "26", "type": "Contact_scl", "inputs": { + "in": { + "type": "powerrail" + }, "operand": { "uid": "21", "scope": "GlobalVariable", "type": "variable", "name": "\"gBlenderBlending\"" - }, - "in": { - "type": "powerrail" } }, "outputs": {}, @@ -206,17 +206,17 @@ "instruction_uid": "27", "type": "Contact_scl", "inputs": { - "operand": { - "uid": "22", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"CLK_1.0S\"" - }, "in": { "type": "connection", "source_instruction_uid": "26", "source_instruction_type": "Contact", "source_pin": "out" + }, + "operand": { + "uid": "22", + "scope": "GlobalVariable", + "type": "variable", + "name": "\"CLK_1.0S\"" } }, "outputs": {}, @@ -226,6 +226,12 @@ "instruction_uid": "28", "type": "Add_scl", "inputs": { + "en": { + "type": "connection", + "source_instruction_uid": "27", + "source_instruction_type": "Contact", + "source_pin": "out" + }, "in1": { "uid": "23", "scope": "GlobalVariable", @@ -238,12 +244,6 @@ "type": "constant", "datatype": "Int", "value": 1 - }, - "en": { - "type": "connection", - "source_instruction_uid": "27", - "source_instruction_type": "Contact", - "source_pin": "out" } }, "outputs": { @@ -320,14 +320,14 @@ "instruction_uid": "27", "type": "Contact_scl", "inputs": { + "in": { + "type": "powerrail" + }, "operand": { "uid": "21", "scope": "LocalVariable", "type": "variable", "name": "\"m1MinONS\"" - }, - "in": { - "type": "powerrail" } }, "outputs": {}, @@ -337,18 +337,18 @@ "instruction_uid": "28", "type": "Move_scl", "inputs": { + "en": { + "type": "connection", + "source_instruction_uid": "27", + "source_instruction_type": "Contact", + "source_pin": "out" + }, "in": { "uid": "22", "scope": "LiteralConstant", "type": "constant", "datatype": "Int", "value": 0 - }, - "en": { - "type": "connection", - "source_instruction_uid": "27", - "source_instruction_type": "Contact", - "source_pin": "out" } }, "outputs": { @@ -361,7 +361,8 @@ } ] }, - "scl": "IF \"m1MinONS\" THEN\n \"Blender_Variables_Pers\".\"gProdSec\" := 0;\nEND_IF;" + "grouped": true, + "scl": "\"Blender_Variables_Pers\".\"gProdSec\" := 0;" }, { "instruction_uid": "29", @@ -379,6 +380,12 @@ "type": "constant", "datatype": "Int", "value": 1 + }, + "en": { + "type": "connection", + "source_instruction_uid": "27", + "source_instruction_type": "Contact", + "source_pin": "out" } }, "outputs": { @@ -391,6 +398,7 @@ } ] }, + "grouped": true, "scl": "\"Blender_Variables_Pers\".\"gProdMin\" := \"Blender_Variables_Pers\".\"gProdMin\" + 1;" } ] @@ -455,14 +463,14 @@ "instruction_uid": "30", "type": "Contact_scl", "inputs": { + "in": { + "type": "powerrail" + }, "operand": { "uid": "21", "scope": "LocalVariable", "type": "variable", "name": "\"m1HourONS\"" - }, - "in": { - "type": "powerrail" } }, "outputs": {}, @@ -472,18 +480,18 @@ "instruction_uid": "31", "type": "Move_scl", "inputs": { + "en": { + "type": "connection", + "source_instruction_uid": "30", + "source_instruction_type": "Contact", + "source_pin": "out" + }, "in": { "uid": "22", "scope": "LiteralConstant", "type": "constant", "datatype": "Int", "value": 0 - }, - "en": { - "type": "connection", - "source_instruction_uid": "30", - "source_instruction_type": "Contact", - "source_pin": "out" } }, "outputs": { @@ -496,7 +504,8 @@ } ] }, - "scl": "IF \"m1HourONS\" THEN\n \"Blender_Variables_Pers\".\"gProdMin\" := 0;\nEND_IF;" + "grouped": true, + "scl": "\"Blender_Variables_Pers\".\"gProdMin\" := 0;" }, { "instruction_uid": "32", @@ -514,6 +523,12 @@ "type": "constant", "datatype": "Int", "value": 1 + }, + "en": { + "type": "connection", + "source_instruction_uid": "30", + "source_instruction_type": "Contact", + "source_pin": "out" } }, "outputs": { @@ -526,6 +541,7 @@ } ] }, + "grouped": true, "scl": "\"Blender_Variables_Pers\".\"gProdHour\" := \"Blender_Variables_Pers\".\"gProdHour\" + 1;" }, { @@ -544,6 +560,12 @@ "type": "constant", "datatype": "Int", "value": 1 + }, + "en": { + "type": "connection", + "source_instruction_uid": "30", + "source_instruction_type": "Contact", + "source_pin": "out" } }, "outputs": { @@ -556,6 +578,7 @@ } ] }, + "grouped": true, "scl": "\"Blender_Variables_Pers\".\"gBlendingMaintHour\" := \"Blender_Variables_Pers\".\"gBlendingMaintHour\" + 1;" } ] @@ -569,14 +592,14 @@ "instruction_uid": "29", "type": "Contact_scl", "inputs": { + "in": { + "type": "powerrail" + }, "operand": { "uid": "21", "scope": "GlobalVariable", "type": "variable", "name": "\"gBlenderCIPMode\"" - }, - "in": { - "type": "powerrail" } }, "outputs": {}, @@ -620,12 +643,43 @@ "instruction_uid": "32", "type": "Move_scl", "inputs": { + "en": { + "type": "connection", + "source_instruction_uid": "31", + "source_instruction_type": "O", + "source_pin": "out" + }, "in": { "uid": "23", "scope": "LiteralConstant", "type": "constant", "datatype": "Int", "value": 0 + } + }, + "outputs": { + "out1": [ + { + "uid": "24", + "scope": "GlobalVariable", + "type": "variable", + "name": "\"Blender_Variables_Pers\".\"gProdSec\"" + } + ] + }, + "grouped": true, + "scl": "\"Blender_Variables_Pers\".\"gProdSec\" := 0;" + }, + { + "instruction_uid": "33", + "type": "Move_scl", + "inputs": { + "in": { + "uid": "25", + "scope": "LiteralConstant", + "type": "constant", + "datatype": "Int", + "value": 0 }, "en": { "type": "connection", @@ -634,30 +688,6 @@ "source_pin": "out" } }, - "outputs": { - "out1": [ - { - "uid": "24", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"Blender_Variables_Pers\".\"gProdSec\"" - } - ] - }, - "scl": "IF \"gBlenderCIPMode\" OR \"gBlenderRinseMode\" THEN\n \"Blender_Variables_Pers\".\"gProdSec\" := 0;\nEND_IF;" - }, - { - "instruction_uid": "33", - "type": "Move_scl", - "inputs": { - "in": { - "uid": "25", - "scope": "LiteralConstant", - "type": "constant", - "datatype": "Int", - "value": 0 - } - }, "outputs": { "out1": [ { @@ -668,6 +698,7 @@ } ] }, + "grouped": true, "scl": "\"Blender_Variables_Pers\".\"gProdMin\" := 0;" }, { @@ -680,6 +711,12 @@ "type": "constant", "datatype": "Int", "value": 0 + }, + "en": { + "type": "connection", + "source_instruction_uid": "31", + "source_instruction_type": "O", + "source_pin": "out" } }, "outputs": { @@ -692,6 +729,7 @@ } ] }, + "grouped": true, "scl": "\"Blender_Variables_Pers\".\"gProdHour\" := 0;" } ] @@ -705,14 +743,14 @@ "instruction_uid": "26", "type": "Contact_scl", "inputs": { + "in": { + "type": "powerrail" + }, "operand": { "uid": "21", "scope": "GlobalVariable", "type": "variable", "name": "\"Procedure_Variables\".\"Blender_Run\".\"Running\"" - }, - "in": { - "type": "powerrail" } }, "outputs": {}, @@ -722,17 +760,17 @@ "instruction_uid": "27", "type": "Contact_scl", "inputs": { - "operand": { - "uid": "22", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"CLK_1.0S\"" - }, "in": { "type": "connection", "source_instruction_uid": "26", "source_instruction_type": "Contact", "source_pin": "out" + }, + "operand": { + "uid": "22", + "scope": "GlobalVariable", + "type": "variable", + "name": "\"CLK_1.0S\"" } }, "outputs": {}, @@ -742,6 +780,12 @@ "instruction_uid": "28", "type": "Add_scl", "inputs": { + "en": { + "type": "connection", + "source_instruction_uid": "27", + "source_instruction_type": "Contact", + "source_pin": "out" + }, "in1": { "uid": "23", "scope": "GlobalVariable", @@ -754,12 +798,6 @@ "type": "constant", "datatype": "Int", "value": 1 - }, - "en": { - "type": "connection", - "source_instruction_uid": "27", - "source_instruction_type": "Contact", - "source_pin": "out" } }, "outputs": { @@ -785,14 +823,14 @@ "instruction_uid": "35", "type": "Convert_scl", "inputs": { + "en": { + "type": "powerrail" + }, "in": { "uid": "21", "scope": "GlobalVariable", "type": "variable", "name": "\"Blender_Variables_Pers\".\"gRunningSeconds\"" - }, - "en": { - "type": "powerrail" } }, "outputs": { @@ -855,12 +893,6 @@ "instruction_uid": "37", "type": "Eq_scl", "inputs": { - "pre": { - "type": "connection", - "source_instruction_uid": "36", - "source_instruction_type": "Mod", - "source_pin": "eno" - }, "in1": { "uid": "26", "scope": "LocalVariable", @@ -873,6 +905,12 @@ "type": "constant", "datatype": "TypedConstant", "value": "DINT#0" + }, + "pre": { + "type": "connection", + "source_instruction_uid": "36", + "source_instruction_type": "Mod", + "source_pin": "eno" } }, "outputs": {}, @@ -902,17 +940,17 @@ "instruction_uid": "39", "type": "Contact_scl", "inputs": { - "operand": { - "uid": "29", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"CLK_1.0S\"" - }, "in": { "type": "connection", "source_instruction_uid": "38", "source_instruction_type": "Contact", "source_pin": "out" + }, + "operand": { + "uid": "29", + "scope": "GlobalVariable", + "type": "variable", + "name": "\"CLK_1.0S\"" } }, "outputs": {}, @@ -922,6 +960,12 @@ "instruction_uid": "40", "type": "Add_scl", "inputs": { + "en": { + "type": "connection", + "source_instruction_uid": "39", + "source_instruction_type": "Contact", + "source_pin": "out" + }, "in1": { "uid": "30", "scope": "GlobalVariable", @@ -934,12 +978,6 @@ "type": "constant", "datatype": "Int", "value": 1 - }, - "en": { - "type": "connection", - "source_instruction_uid": "39", - "source_instruction_type": "Contact", - "source_pin": "out" } }, "outputs": { @@ -966,7 +1004,7 @@ } }, "outputs": {}, - "scl": "// Edge detection PBox 41 -> P_TRIG_FUNC(CLK := ((\"MOD60\" = DINT#0 AND \"Procedure_Variables\".\"Blender_Run\".\"Running\") AND \"CLK_1.0S\"), M := \"M19012\") (CLK source inferred)" + "scl": "// Edge detection PBox 41 -> P_TRIG_FUNC(CLK := ((\"MOD60\" = DINT#0 AND \"Procedure_Variables\".\"Blender_Run\".\"Running\") AND \"CLK_1.0S\"), M := \"M19012\") (CLK inferred)" }, { "instruction_uid": "42", @@ -999,14 +1037,14 @@ "instruction_uid": "32", "type": "Contact_scl", "inputs": { + "in": { + "type": "powerrail" + }, "operand": { "uid": "21", "scope": "LocalVariable", "type": "variable", "name": "\"mRunMin\"" - }, - "in": { - "type": "powerrail" } }, "outputs": {}, @@ -1016,17 +1054,17 @@ "instruction_uid": "33", "type": "Convert_scl", "inputs": { - "in": { - "uid": "22", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"Blender_Variables_Pers\".\"gRunningMinutes\"" - }, "en": { "type": "connection", "source_instruction_uid": "32", "source_instruction_type": "Contact", "source_pin": "out" + }, + "in": { + "uid": "22", + "scope": "GlobalVariable", + "type": "variable", + "name": "\"Blender_Variables_Pers\".\"gRunningMinutes\"" } }, "outputs": { @@ -1089,12 +1127,6 @@ "instruction_uid": "35", "type": "Eq_scl", "inputs": { - "pre": { - "type": "connection", - "source_instruction_uid": "34", - "source_instruction_type": "Mod", - "source_pin": "eno" - }, "in1": { "uid": "27", "scope": "LocalVariable", @@ -1107,6 +1139,12 @@ "type": "constant", "datatype": "TypedConstant", "value": "DINT#0" + }, + "pre": { + "type": "connection", + "source_instruction_uid": "34", + "source_instruction_type": "Mod", + "source_pin": "eno" } }, "outputs": {}, @@ -1159,14 +1197,14 @@ "instruction_uid": "23", "type": "Move_scl", "inputs": { + "en": { + "type": "powerrail" + }, "in": { "uid": "21", "scope": "GlobalVariable", "type": "variable", "name": "\"Blender_Variables_Pers\".\"gRunningMaintHour\"" - }, - "en": { - "type": "powerrail" } }, "outputs": { diff --git a/x1_to_json.py b/x1_to_json.py index 039a8c3..0a68b7d 100644 --- a/x1_to_json.py +++ b/x1_to_json.py @@ -3,201 +3,193 @@ import json import os from lxml import etree import traceback +from collections import defaultdict # Para facilitar el manejo de conexiones # --- Namespaces --- ns = { - 'iface': 'http://www.siemens.com/automation/Openness/SW/Interface/v5', - 'flg': 'http://www.siemens.com/automation/Openness/SW/NetworkSource/FlgNet/v4' + "iface": "http://www.siemens.com/automation/Openness/SW/Interface/v5", + "flg": "http://www.siemens.com/automation/Openness/SW/NetworkSource/FlgNet/v4", } + # --- Helper Functions --- -# (get_multilingual_text, get_symbol_name, parse_access, parse_part sin cambios) -def get_multilingual_text(element, default_lang='en-US', fallback_lang='it-IT'): - """ - Intenta extraer texto de un elemento MultilingualText, priorizando idiomas. - Busca directamente los Text dentro de Items/AttributeList/Text bajo las Culture especificadas. - """ +# (get_multilingual_text, get_symbol_name, parse_access, parse_part - sin cambios) +def get_multilingual_text(element, default_lang="en-US", fallback_lang="it-IT"): if element is None: return "" try: - # Intenta encontrar el idioma por defecto - xpath_expr = f".//*[local-name()='MultilingualTextItem'][*[local-name()='AttributeList']/*[local-name()='Culture' and text()='{default_lang}']]" \ - f"/*[local-name()='AttributeList']/*[local-name()='Text']" + xpath_expr = ( + f".//*[local-name()='MultilingualTextItem'][*[local-name()='AttributeList']/*[local-name()='Culture' and text()='{default_lang}']]" + f"/*[local-name()='AttributeList']/*[local-name()='Text']" + ) text_items = element.xpath(xpath_expr) if text_items and text_items[0].text is not None: return text_items[0].text.strip() - - # Si no, intenta encontrar el idioma de fallback - xpath_expr = f".//*[local-name()='MultilingualTextItem'][*[local-name()='AttributeList']/*[local-name()='Culture' and text()='{fallback_lang}']]" \ - f"/*[local-name()='AttributeList']/*[local-name()='Text']" + xpath_expr = ( + f".//*[local-name()='MultilingualTextItem'][*[local-name()='AttributeList']/*[local-name()='Culture' and text()='{fallback_lang}']]" + f"/*[local-name()='AttributeList']/*[local-name()='Text']" + ) text_items = element.xpath(xpath_expr) if text_items and text_items[0].text is not None: return text_items[0].text.strip() - - # Si no, toma el primer texto que encuentre xpath_expr = f".//*[local-name()='MultilingualTextItem']/*[local-name()='AttributeList']/*[local-name()='Text']" text_items = element.xpath(xpath_expr) if text_items and text_items[0].text is not None: return text_items[0].text.strip() - - return "" # Devuelve cadena vacía si no se encuentra nada + return "" except Exception as e: - print(f"Advertencia: Error extrayendo MultilingualText desde {etree.tostring(element, encoding='unicode')[:100]}...: {e}") + print(f"Advertencia: Error extrayendo MultilingualText: {e}") return "" + def get_symbol_name(symbol_element): - """ - Construye el nombre completo del símbolo (variable) a partir de sus elementos Component. - Encapsula cada componente entre comillas dobles y los une con puntos. - """ if symbol_element is None: return None try: - # Selecciona el atributo 'Name' de cada elemento 'Component' hijo directo del Symbol components = symbol_element.xpath("./*[local-name()='Component']/@Name") - if components: - # Une los componentes, asegurándose de que cada uno esté entre comillas dobles - full_name = ".".join(f'"{c}"' for c in components) - return full_name - else: - return None # Indica que no se pudo formar un nombre + return ".".join(f'"{c}"' for c in components) if components else None except Exception as e: - print(f"Advertencia: Excepción en get_symbol_name para {etree.tostring(symbol_element, encoding='unicode')[:100]}...: {e}") + print(f"Advertencia: Excepción en get_symbol_name: {e}") return None + def parse_access(access_element): - """ - Parsea un elemento Access (acceso a operando) para obtener información - detallada sobre si es una variable (Symbol) o una constante (Constant). - Devuelve un diccionario con la información o None si hay un error crítico. - """ if access_element is None: return None - - uid = access_element.get('UId') - scope = access_element.get('Scope') - - info = {'uid': uid, 'scope': scope, 'type': 'unknown'} # Inicializa info - + uid = access_element.get("UId") + scope = access_element.get("Scope") + info = {"uid": uid, "scope": scope, "type": "unknown"} symbol = access_element.xpath("./*[local-name()='Symbol']") constant = access_element.xpath("./*[local-name()='Constant']") - if symbol: - info['type'] = 'variable' - info['name'] = get_symbol_name(symbol[0]) - if info['name'] is None: - info['type'] = 'error_parsing_symbol' - print(f"Error: No se pudo parsear el nombre del símbolo para Access UID={uid}") + info["type"] = "variable" + info["name"] = get_symbol_name(symbol[0]) + if info["name"] is None: + info["type"] = "error_parsing_symbol" + print(f"Error: No se pudo parsear nombre símbolo Access UID={uid}") return info - elif constant: - info['type'] = 'constant' + info["type"] = "constant" const_type_elem = constant[0].xpath("./*[local-name()='ConstantType']") const_val_elem = constant[0].xpath("./*[local-name()='ConstantValue']") - info['datatype'] = const_type_elem[0].text if const_type_elem and const_type_elem[0].text is not None else 'Unknown' - value_str = const_val_elem[0].text if const_val_elem and const_val_elem[0].text is not None else None - + info["datatype"] = ( + const_type_elem[0].text + if const_type_elem and const_type_elem[0].text is not None + else "Unknown" + ) + value_str = ( + const_val_elem[0].text + if const_val_elem and const_val_elem[0].text is not None + else None + ) if value_str is None: - info['type'] = 'error_parsing_constant' - info['value'] = None - print(f"Error: Constante sin valor encontrada para Access UID={uid}") + info["type"] = "error_parsing_constant" + info["value"] = None + print(f"Error: Constante sin valor Access UID={uid}") return info - - if info['datatype'] == 'Unknown': + if info["datatype"] == "Unknown": val_lower = value_str.lower() - if val_lower in ['true', 'false']: info['datatype'] = 'Bool' - elif value_str.isdigit() or (value_str.startswith('-') and value_str[1:].isdigit()): info['datatype'] = 'Int' - elif '.' in value_str: - try: float(value_str); info['datatype'] = 'Real' - except ValueError: pass - elif '#' in value_str: info['datatype'] = 'TypedConstant' - - info['value'] = value_str - dtype_lower = info['datatype'].lower() - val_str_processed = value_str.split('#')[-1] if '#' in value_str else value_str + if val_lower in ["true", "false"]: + info["datatype"] = "Bool" + elif value_str.isdigit() or ( + value_str.startswith("-") and value_str[1:].isdigit() + ): + info["datatype"] = "Int" + elif "." in value_str: + try: + float(value_str) + info["datatype"] = "Real" + except ValueError: + pass # Si no es float, sigue siendo Unknown (o lo que fuera antes) + elif "#" in value_str: + info["datatype"] = "TypedConstant" + info["value"] = value_str + dtype_lower = info["datatype"].lower() + val_str_processed = value_str.split("#")[-1] if "#" in value_str else value_str try: - if dtype_lower in ['int', 'dint', 'udint', 'sint', 'usint', 'lint', 'ulint', 'word', 'dword', 'lword', 'byte']: - info['value'] = int(val_str_processed) - elif dtype_lower == 'bool': - info['value'] = val_str_processed.lower() == 'true' or val_str_processed == '1' - elif dtype_lower in ['real', 'lreal']: - info['value'] = float(val_str_processed) - elif dtype_lower == 'typedconstant': - info['value'] = value_str + if dtype_lower in [ + "int", + "dint", + "udint", + "sint", + "usint", + "lint", + "ulint", + "word", + "dword", + "lword", + "byte", + ]: + info["value"] = int(val_str_processed) + elif dtype_lower == "bool": + info["value"] = ( + val_str_processed.lower() == "true" or val_str_processed == "1" + ) + elif dtype_lower in ["real", "lreal"]: + info["value"] = float(val_str_processed) + elif dtype_lower == "typedconstant": + info["value"] = value_str except (ValueError, TypeError) as e: - print(f"Advertencia: No se pudo convertir el valor '{val_str_processed}' a tipo {dtype_lower} para UID={uid}. Se mantiene como string. Error: {e}") - info['value'] = value_str - + print( + f"Advertencia: No se pudo convertir valor '{val_str_processed}' a {dtype_lower} UID={uid}. Error: {e}" + ) + info["value"] = value_str else: - info['type'] = 'unknown_structure' - print(f"Advertencia: Access UID={uid} no es ni Symbol ni Constant.") + info["type"] = "unknown_structure" + print(f"Advertencia: Access UID={uid} no es Symbol ni Constant.") + return info + if info["type"] == "variable" and info.get("name") is None: + print(f"Error Interno: parse_access var sin nombre UID {uid}.") + info["type"] = "error_no_name" return info - - if info['type'] == 'variable' and info.get('name') is None: - print(f"Error Interno: parse_access terminó con tipo 'variable' pero sin nombre para UID {uid}.") - info['type'] = 'error_no_name' - return info - return info + def parse_part(part_element): - """ - Parsea un elemento Part (representa una instrucción como Add, Move, Contact, Coil) - y extrae su UID, nombre y valores de plantilla (TemplateValue). - Devuelve un diccionario con la información o None si el elemento es inválido. - """ if part_element is None: return None - - uid = part_element.get('UId') - name = part_element.get('Name') - + uid = part_element.get("UId") + name = part_element.get("Name") if not uid or not name: - print(f"Error: Part encontrado sin UID o Name: {etree.tostring(part_element, encoding='unicode')}") + print( + f"Error: Part sin UID o Name: {etree.tostring(part_element, encoding='unicode')}" + ) return None - template_values = {} try: for tv in part_element.xpath("./*[local-name()='TemplateValue']"): - tv_name = tv.get('Name') - tv_type = tv.get('Type') + tv_name = tv.get("Name") + tv_type = tv.get("Type") if tv_name and tv_type: template_values[tv_name] = tv_type except Exception as e: - print(f"Advertencia: Error extrayendo TemplateValues para Part UID={uid}: {e}") + print(f"Advertencia: Error extrayendo TemplateValues Part UID={uid}: {e}") + return {"uid": uid, "name": name, "template_values": template_values} - return { - 'uid': uid, - 'name': name, - 'template_values': template_values # Mantenemos esto por si acaso, aunque no se use prominentemente - } - -# --- Main Parsing Logic --- def parse_network(network_element): """ - Parsea un elemento SW.Blocks.CompileUnit (representa una red de lógica) - y extrae su ID, título, comentario y la lógica interna simplificada en formato JSON, - incluyendo la lógica conectada a ENO si no es un simple EN->ENO. + Parsea una red (CompileUnit), extrae lógica, infiere conexiones EN implícitas, + y añade lógica ENO interesante. """ if network_element is None: print("Error: parse_network llamado con network_element=None") return {'id': 'ERROR', 'title': 'Invalid Network Element', 'comment': '', 'logic': [], 'error': 'Input element was None'} network_id = network_element.get('ID') + # print(f"--- Parseando Red ID={network_id} ---") # Descomentar para depuración detallada # Extrae el título de la red title_element = network_element.xpath(".//*[local-name()='MultilingualText'][@CompositionName='Title']") network_title = get_multilingual_text(title_element[0]) if title_element else f"Network {network_id}" - # *** NUEVO: Extrae el comentario de la red *** + # Extrae el comentario de la red network_comment = "" comment_title_element = network_element.xpath("./*[local-name()='ObjectList']/*[local-name()='MultilingualText'][@CompositionName='Comment']") if comment_title_element: network_comment = get_multilingual_text(comment_title_element[0]) - # print(f"DEBUG: Comentario Red {network_id}: '{network_comment[:50]}...'") - + # Encuentra FlgNet flgnet_list = network_element.xpath(".//flg:FlgNet", namespaces=ns) if not flgnet_list: print(f"Error: No se encontró FlgNet en la red ID={network_id}") @@ -205,28 +197,20 @@ def parse_network(network_element): flgnet = flgnet_list[0] # 1. Parsear Access y Parts - access_map = {} - for acc in flgnet.xpath(".//flg:Access", namespaces=ns): - acc_info = parse_access(acc) - if acc_info and 'uid' in acc_info: - access_map[acc_info['uid']] = acc_info + access_map = {acc_info['uid']: acc_info for acc in flgnet.xpath(".//flg:Access", namespaces=ns) if (acc_info := parse_access(acc))} + parts_map = {part_info['uid']: part_info for part in flgnet.xpath(".//flg:Part", namespaces=ns) if (part_info := parse_part(part))} + # print(f"DEBUG: Red {network_id} - Access={len(access_map)}, Parts={len(parts_map)}") # Debug - parts_map = {} - for part in flgnet.xpath(".//flg:Part", namespaces=ns): - part_info = parse_part(part) - if part_info and 'uid' in part_info: - parts_map[part_info['uid']] = part_info - - # 2. Parsear Wires y construir mapa de conexiones de entrada y *salida ENO* - wire_connections = {} # Clave=(dest_uid, dest_pin), Valor=lista de (source_uid, source_pin) - eno_outputs = {} # Clave=source_part_uid, Valor=lista de (dest_uid, dest_pin) + # 2. Parsear Wires y construir mapas de conexiones + wire_connections = defaultdict(list) # key=(dest_uid, dest_pin), value=list of (src_uid, src_pin) + source_connections = defaultdict(list) # key=(src_uid, src_pin), value=list of (dest_uid, dest_pin) + eno_outputs = defaultdict(list) # key=src_uid, value=list of (dest_uid, dest_pin) from ENO flg_ns_uri = ns['flg'] for wire in flgnet.xpath(".//flg:Wire", namespaces=ns): source_uid, source_pin, dest_uid, dest_pin = None, None, None, None children = wire.getchildren() if len(children) < 2: continue - source_elem, dest_elem = children[0], children[1] # Determina la fuente @@ -238,181 +222,184 @@ def parse_network(network_element): if dest_elem.tag == etree.QName(flg_ns_uri, 'IdentCon'): dest_uid, dest_pin = dest_elem.get('UId'), 'value' elif dest_elem.tag == etree.QName(flg_ns_uri, 'NameCon'): dest_uid, dest_pin = dest_elem.get('UId'), dest_elem.get('Name') - # Registrar conexión de entrada normal + # Registrar conexiones si son válidas if dest_uid and dest_pin and source_uid is not None: - dest_key = (dest_uid, dest_pin) - source_info = (source_uid, source_pin) - if dest_key not in wire_connections: wire_connections[dest_key] = [] + dest_key = (dest_uid, dest_pin); source_key = (source_uid, source_pin) + source_info = (source_uid, source_pin); dest_info = (dest_uid, dest_pin) + if source_info not in wire_connections[dest_key]: wire_connections[dest_key].append(source_info) + if dest_info not in source_connections[source_key]: source_connections[source_key].append(dest_info) - # *** NUEVO: Registrar conexiones que SALEN de un pin ENO *** + # Registrar conexiones que SALEN de un pin ENO if source_pin == 'eno' and source_uid in parts_map: - if source_uid not in eno_outputs: eno_outputs[source_uid] = [] - eno_dest_info = (dest_uid, dest_pin) - if eno_dest_info not in eno_outputs[source_uid]: - eno_outputs[source_uid].append(eno_dest_info) - # print(f"DEBUG: Red {network_id} - ENO de {source_uid} conectado a ({dest_uid}, {dest_pin})") + if dest_info not in eno_outputs[source_uid]: + eno_outputs[source_uid].append(dest_info) - - # 3. Construir la representación lógica principal + # 3. Construir la representación lógica INICIAL all_logic_steps = {} + functional_block_types = ['Move', 'Add', 'Sub', 'Mul', 'Div', 'Mod', 'Convert'] # Ampliar si es necesario + rlo_generators = ['Contact', 'O', 'Eq', 'Ne', 'Gt', 'Lt', 'Ge', 'Le', 'And', 'Xor', 'PBox'] # Ampliar si es necesario + for part_uid, part_info in parts_map.items(): instruction_repr = {'instruction_uid': part_uid, 'type': part_info['name'], 'inputs': {}, 'outputs': {}} + # Rellenar inputs explícitos + for dest_pin_name in ['en', 'in', 'in1', 'in2', 'in3', 'in4', 'bit', 'operand', 'pre', 'clk']: # Añadir pines comunes + dest_key = (part_uid, dest_pin_name) + if dest_key in wire_connections: + sources_list = wire_connections[dest_key] + input_sources_repr = [] + for source_uid, source_pin in sources_list: + if source_uid == 'POWERRAIL': input_sources_repr.append({'type': 'powerrail'}) + elif source_uid in access_map: input_sources_repr.append(access_map[source_uid]) + elif source_uid in parts_map: + input_sources_repr.append({'type': 'connection', 'source_instruction_uid': source_uid, + 'source_instruction_type': parts_map[source_uid]['name'], 'source_pin': source_pin}) + else: input_sources_repr.append({'type': 'unknown_source', 'uid': source_uid}) + if len(input_sources_repr) == 1: instruction_repr['inputs'][dest_pin_name] = input_sources_repr[0] + elif len(input_sources_repr) > 1: instruction_repr['inputs'][dest_pin_name] = input_sources_repr # Rama OR/Multiple - # Procesar Entradas (igual que antes) - for (conn_dest_uid, conn_dest_pin), sources_list in wire_connections.items(): - if conn_dest_uid == part_uid: - input_sources_repr = [] - for source_uid, source_pin in sources_list: - if source_uid == 'POWERRAIL': input_sources_repr.append({'type': 'powerrail'}) - elif source_uid in access_map: input_sources_repr.append(access_map[source_uid]) - elif source_uid in parts_map: - input_sources_repr.append({'type': 'connection', 'source_instruction_uid': source_uid, - 'source_instruction_type': parts_map[source_uid]['name'], 'source_pin': source_pin}) - else: input_sources_repr.append({'type': 'unknown_source', 'uid': source_uid}) - - if len(input_sources_repr) == 1: instruction_repr['inputs'][conn_dest_pin] = input_sources_repr[0] - elif len(input_sources_repr) > 1: instruction_repr['inputs'][conn_dest_pin] = input_sources_repr - - # Procesar Salidas (igual que antes) - for (conn_dest_uid, conn_dest_pin), sources_list in wire_connections.items(): - for source_uid, source_pin in sources_list: - if source_uid == part_uid and conn_dest_uid in access_map: - if source_pin not in instruction_repr['outputs']: instruction_repr['outputs'][source_pin] = [] - if access_map[conn_dest_uid] not in instruction_repr['outputs'][source_pin]: - instruction_repr['outputs'][source_pin].append(access_map[conn_dest_uid]) + # Rellenar outputs explícitos (hacia Access) + for source_pin_name in ['out', 'out1', 'Q', 'eno']: # Añadir pines comunes + source_key = (part_uid, source_pin_name) + if source_key in source_connections: + for dest_uid, dest_pin in source_connections[source_key]: + if dest_uid in access_map: + if source_pin_name not in instruction_repr['outputs']: instruction_repr['outputs'][source_pin_name] = [] + if access_map[dest_uid] not in instruction_repr['outputs'][source_pin_name]: + instruction_repr['outputs'][source_pin_name].append(access_map[dest_uid]) all_logic_steps[part_uid] = instruction_repr + # --- 4. INFERENCIA Y PROPAGACIÓN DE CONEXIONES 'EN' IMPLÍCITAS --- + # print(f"DEBUG: Iniciando inferencia EN para Red {network_id}...") # Debug + processed_blocks_en_inference = set() # Evitar procesar el mismo bloque múltiples veces en inferencia EN + something_changed = True + inference_passes = 0 + max_inference_passes = len(all_logic_steps) + 5 - # *** NUEVO: Procesar y añadir lógica ENO "interesante" *** + while something_changed and inference_passes < max_inference_passes: + something_changed = False + inference_passes += 1 + # print(f"DEBUG: Pase de inferencia EN {inference_passes}...") # Debug + + # Ordenar por UID para intentar procesar en orden lógico + try: + sorted_uids_for_pass = sorted(all_logic_steps.keys(), key=lambda x: int(x) if x.isdigit() else float('inf')) + except ValueError: + sorted_uids_for_pass = sorted(all_logic_steps.keys()) + + for part_uid in sorted_uids_for_pass: + if part_uid not in all_logic_steps: continue # Seguridad + instruction = all_logic_steps[part_uid] + part_type = instruction['type'] + + if part_type in functional_block_types and 'en' not in instruction['inputs'] and part_uid not in processed_blocks_en_inference: + # print(f"DEBUG: Intentando inferir EN para {part_type} UID {part_uid}") # Debug + inferred_en_source = None + # Usar la lista ordenada por UID para buscar atrás + my_index = -1 + current_logic_list = [all_logic_steps[uid] for uid in sorted_uids_for_pass if uid in all_logic_steps] # Lista actual ordenada + for i, instr in enumerate(current_logic_list): + if instr['instruction_uid'] == part_uid: + my_index = i + break + + if my_index > 0: + for i in range(my_index - 1, -1, -1): + prev_instr = current_logic_list[i] + prev_uid = prev_instr['instruction_uid'] + prev_type = prev_instr['type'] + if prev_type in rlo_generators: + inferred_en_source = {'type': 'connection', 'source_instruction_uid': prev_uid, 'source_instruction_type': prev_type, 'source_pin': 'out'} + # print(f"DEBUG: Inferido EN para {part_uid} desde RLO de {prev_type} {prev_uid}.out") # Debug + break + elif prev_type in functional_block_types: + source_key_eno = (prev_uid, 'eno') + # Verificar si el ENO del bloque anterior está conectado a *algo* + if source_key_eno in source_connections: + inferred_en_source = {'type': 'connection', 'source_instruction_uid': prev_uid, 'source_instruction_type': prev_type, 'source_pin': 'eno'} + # print(f"DEBUG: Inferido EN para {part_uid} desde ENO de {prev_type} {prev_uid}.eno") # Debug + break + # Si no hay conexión ENO explícita, podríamos asumir que sigue el RLO del EN de ese bloque? Más complejo. + # Por ahora, solo usamos ENO si está conectado. + + if inferred_en_source: + instruction['inputs']['en'] = inferred_en_source + # print(f"INFO: Conexión EN inferida añadida a {part_type} UID {part_uid}") # Info + processed_blocks_en_inference.add(part_uid) + something_changed = True + # else: + # print(f"DEBUG: No se pudo inferir EN para {part_type} UID {part_uid}") # Debug + + + # --- 5. Añadir lógica ENO interesante --- for source_instr_uid, eno_destinations in eno_outputs.items(): - if source_instr_uid not in all_logic_steps: continue # Seguridad - + if source_instr_uid not in all_logic_steps: continue interesting_eno_logic = [] for dest_uid, dest_pin in eno_destinations: - # Determinar si es una conexión directa a EN de otra instrucción is_direct_en_connection = (dest_uid in parts_map and dest_pin == 'en') - if not is_direct_en_connection: - # Si NO es directa a EN, es "interesante" target_info = {'target_pin': dest_pin} - if dest_uid in parts_map: - target_info['target_type'] = 'instruction' - target_info['target_uid'] = dest_uid - target_info['target_name'] = parts_map[dest_uid]['name'] - elif dest_uid in access_map: - # El destino es una variable o constante - target_info['target_type'] = 'operand' - target_info['target_details'] = access_map[dest_uid] # Incluye toda la info del Access - else: - target_info['target_type'] = 'unknown' - target_info['target_uid'] = dest_uid - + if dest_uid in parts_map: target_info.update({'target_type': 'instruction', 'target_uid': dest_uid, 'target_name': parts_map[dest_uid]['name']}) + elif dest_uid in access_map: target_info.update({'target_type': 'operand', 'target_details': access_map[dest_uid]}) + else: target_info.update({'target_type': 'unknown', 'target_uid': dest_uid}) interesting_eno_logic.append(target_info) - # print(f"DEBUG: Red {network_id} - ENO de {source_instr_uid}: Lógica interesante -> {target_info}") - - # Añadir la lista de lógica ENO interesante a la instrucción fuente, si existe if interesting_eno_logic: all_logic_steps[source_instr_uid]['eno_logic'] = interesting_eno_logic + # print(f"DEBUG: Red {network_id} - Añadida lógica ENO interesante para {source_instr_uid}") # Debug - - # 4. Ordenar y finalizar - try: - sorted_uids = sorted(all_logic_steps.keys(), key=lambda x: int(x) if x.isdigit() else float('inf')) - except ValueError: - print(f"Advertencia: UIDs no puramente numéricos en red {network_id}. Ordenando alfabéticamente.") - sorted_uids = sorted(all_logic_steps.keys()) - + # --- 6. Ordenar Lógica Final y Devolver --- + try: sorted_uids = sorted(all_logic_steps.keys(), key=lambda x: int(x) if x.isdigit() else float('inf')) + except ValueError: print(f"Advertencia: UIDs no numéricos red {network_id}. Orden alfabético."); sorted_uids = sorted(all_logic_steps.keys()) network_logic = [all_logic_steps[uid] for uid in sorted_uids if uid in all_logic_steps] - # Devolver estructura de red con ID, título, comentario y lógica + # print(f"--- Fin Parseo Red ID={network_id} ---") # Debug return {'id': network_id, 'title': network_title, 'comment': network_comment, 'logic': network_logic} +# --- Función Principal convert_xml_to_json (sin cambios respecto a la versión anterior) --- def convert_xml_to_json(xml_filepath, json_filepath): """ Función principal que orquesta la conversión del archivo XML de Openness a un archivo JSON simplificado que representa la estructura del bloque FC, - incluyendo comentarios y lógica ENO no trivial. + incluyendo comentarios, inferencia EN y lógica ENO no trivial. """ print(f"Iniciando conversión de '{xml_filepath}' a '{json_filepath}'...") - - if not os.path.exists(xml_filepath): - print(f"Error Crítico: Archivo XML no encontrado en '{xml_filepath}'") - return + if not os.path.exists(xml_filepath): print(f"Error Crítico: Archivo XML no encontrado: '{xml_filepath}'"); return try: - print("Paso 1: Parseando archivo XML...") - parser = etree.XMLParser(remove_blank_text=True) - tree = etree.parse(xml_filepath, parser) - root = tree.getroot() - print("Paso 1: Parseo XML completado.") - - print("Paso 2: Buscando el bloque SW.Blocks.FC...") - fc_block_list = root.xpath("//*[local-name()='SW.Blocks.FC']") - if not fc_block_list: - print("Error Crítico: No se encontró el elemento en el archivo.") - return - fc_block = fc_block_list[0] - print(f"Paso 2: Bloque SW.Blocks.FC encontrado (ID={fc_block.get('ID')}).") - - print("Paso 3: Extrayendo atributos del bloque...") - attribute_list_node = fc_block.xpath("./*[local-name()='AttributeList']") - block_name_val = "Unknown" - block_number_val = None - block_lang_val = "Unknown" + print("Paso 1: Parseando archivo XML..."); parser = etree.XMLParser(remove_blank_text=True); tree = etree.parse(xml_filepath, parser); root = tree.getroot(); print("Paso 1: Parseo XML completado.") + print("Paso 2: Buscando el bloque SW.Blocks.FC..."); fc_block_list = root.xpath("//*[local-name()='SW.Blocks.FC']"); + if not fc_block_list: print("Error Crítico: No se encontró ."); return + fc_block = fc_block_list[0]; print(f"Paso 2: Bloque SW.Blocks.FC encontrado (ID={fc_block.get('ID')}).") + print("Paso 3: Extrayendo atributos del bloque..."); attribute_list_node = fc_block.xpath("./*[local-name()='AttributeList']") + block_name_val, block_number_val, block_lang_val = "Unknown", None, "Unknown" if attribute_list_node: attr_list = attribute_list_node[0] - name_node = attr_list.xpath("./*[local-name()='Name']/text()") - if name_node: block_name_val = name_node[0].strip() - num_node = attr_list.xpath("./*[local-name()='Number']/text()") - if num_node and num_node[0].isdigit(): block_number_val = int(num_node[0]) - lang_node = attr_list.xpath("./*[local-name()='ProgrammingLanguage']/text()") - if lang_node: block_lang_val = lang_node[0].strip() - print(f"Paso 3: Atributos extraídos: Nombre='{block_name_val}', Número={block_number_val}, Lenguaje='{block_lang_val}'") + name_node = attr_list.xpath("./*[local-name()='Name']/text()"); block_name_val = name_node[0].strip() if name_node else block_name_val + num_node = attr_list.xpath("./*[local-name()='Number']/text()"); block_number_val = int(num_node[0]) if num_node and num_node[0].isdigit() else block_number_val + lang_node = attr_list.xpath("./*[local-name()='ProgrammingLanguage']/text()"); block_lang_val = lang_node[0].strip() if lang_node else block_lang_val + print(f"Paso 3: Atributos: Nombre='{block_name_val}', Número={block_number_val}, Lenguaje='{block_lang_val}'") else: print("Advertencia: No se encontró AttributeList para el bloque FC.") - - # *** NUEVO: Extraer comentario del bloque *** - block_comment_val = "" - # El comentario del bloque suele estar en ObjectList > MultilingualText[@CompositionName='Comment'] - comment_node_list = fc_block.xpath("./*[local-name()='ObjectList']/*[local-name()='MultilingualText'][@CompositionName='Comment']") - if comment_node_list: - block_comment_val = get_multilingual_text(comment_node_list[0]) - print(f"Paso 3b: Comentario del bloque extraído: '{block_comment_val[:50]}...'") - - - result = { - "block_name": block_name_val, - "block_number": block_number_val, - "language": block_lang_val, - "block_comment": block_comment_val, # Añadido comentario del bloque - "interface": {}, - "networks": [] - } - + block_comment_val = ""; comment_node_list = fc_block.xpath("./*[local-name()='ObjectList']/*[local-name()='MultilingualText'][@CompositionName='Comment']") + if comment_node_list: block_comment_val = get_multilingual_text(comment_node_list[0]); print(f"Paso 3b: Comentario bloque: '{block_comment_val[:50]}...'") + result = {"block_name": block_name_val, "block_number": block_number_val, "language": block_lang_val, "block_comment": block_comment_val, "interface": {}, "networks": []} print("Paso 4: Extrayendo la interfaz del bloque...") - interface_found = False if attribute_list_node: interface_node_list = attribute_list_node[0].xpath("./*[local-name()='Interface']") if interface_node_list: - interface_node = interface_node_list[0] - interface_found = True - print("Paso 4: Nodo Interface encontrado dentro de AttributeList.") + interface_node = interface_node_list[0]; print("Paso 4: Nodo Interface encontrado.") for section in interface_node.xpath(".//iface:Section", namespaces=ns): - section_name = section.get('Name') - members = [] + section_name = section.get('Name'); members = [] for member in section.xpath("./iface:Member", namespaces=ns): - member_name = member.get('Name') - member_dtype = member.get('Datatype') + member_name = member.get('Name'); member_dtype = member.get('Datatype') if member_name and member_dtype: members.append({"name": member_name, "datatype": member_dtype}) if members: result["interface"][section_name] = members - if not result["interface"]: print("Advertencia: Nodo Interface encontrado, pero no contenía secciones iface:Section válidas.") - else: print("Advertencia: No se encontró el nodo DENTRO de .") + if not result["interface"]: print("Advertencia: Interface sin secciones iface:Section válidas.") + else: print("Advertencia: No se encontró DENTRO de .") + if not result["interface"]: print("Advertencia: No se pudo extraer información de la interfaz.") - if not interface_found and not result["interface"]: print("Advertencia: No se pudo extraer ninguna información de la interfaz.") - - print("Paso 5: Extrayendo la lógica de las redes (CompileUnits)...") + print("Paso 5: Extrayendo y PROCESANDO lógica de redes (CompileUnits)...") networks_processed_count = 0 object_list_node = fc_block.xpath("./*[local-name()='ObjectList']") if object_list_node: @@ -420,39 +407,30 @@ def convert_xml_to_json(xml_filepath, json_filepath): print(f"Paso 5: Se encontraron {len(compile_units)} elementos SW.Blocks.CompileUnit.") for network_elem in compile_units: networks_processed_count += 1 - print(f"DEBUG: Procesando red #{networks_processed_count} (ID={network_elem.get('ID')})...") - parsed_network = parse_network(network_elem) # Ahora parse_network incluye comentario + # print(f"DEBUG: Procesando red #{networks_processed_count} (ID={network_elem.get('ID')})...") + parsed_network = parse_network(network_elem) # Llamada a la función modificada if parsed_network and parsed_network.get('error') is None: result["networks"].append(parsed_network) - elif parsed_network: - print(f"Error: Falló el parseo de la red ID={parsed_network.get('id')}: {parsed_network.get('error')}") - result["networks"].append(parsed_network) - else: print(f"Error: parse_network devolvió None para un CompileUnit (ID={network_elem.get('ID')}).") - if networks_processed_count == 0: print("Advertencia: ObjectList encontrado, pero no contenía SW.Blocks.CompileUnit.") - else: print("Advertencia: No se encontró ObjectList para el bloque FC.") + elif parsed_network: print(f"Error: Falló parseo red ID={parsed_network.get('id')}: {parsed_network.get('error')}"); result["networks"].append(parsed_network) + else: print(f"Error: parse_network devolvió None para CompileUnit (ID={network_elem.get('ID')}).") + if networks_processed_count == 0: print("Advertencia: ObjectList sin SW.Blocks.CompileUnit.") + else: print("Advertencia: No se encontró ObjectList.") print("Paso 6: Escribiendo el resultado en el archivo JSON...") - # Chequeos finales - if not result["interface"]: print("ADVERTENCIA FINAL: La sección 'interface' está vacía.") - if not result["networks"]: print("ADVERTENCIA FINAL: La sección 'networks' está vacía.") - else: - # Chequea si alguna instrucción tiene lógica ENO interesante - eno_logic_found = any(instr.get('eno_logic') for net in result.get('networks', []) if net.get('error') is None for instr in net.get('logic', [])) - if eno_logic_found: print("INFO FINAL: Se detectó lógica ENO interesante en al menos una instrucción.") - else: print("INFO FINAL: No se detectó lógica ENO interesante (solo conexiones directas ENO->EN o ENO no conectado).") + if not result["interface"]: print("ADVERTENCIA FINAL: 'interface' está vacía.") + if not result["networks"]: print("ADVERTENCIA FINAL: 'networks' está vacía.") + # else: # Chequeo ENO logic + # eno_logic_found = any(instr.get('eno_logic') for net in result.get('networks', []) if net.get('error') is None for instr in net.get('logic', [])) + # if eno_logic_found: print("INFO FINAL: Lógica ENO interesante detectada.") + # else: print("INFO FINAL: No se detectó lógica ENO interesante.") try: - with open(json_filepath, 'w', encoding='utf-8') as f: - json.dump(result, f, indent=4, ensure_ascii=False) - print(f"Paso 6: Escritura completada.") - print(f"Conversión finalizada con éxito. Archivo JSON guardado en: '{json_filepath}'") - except IOError as e: print(f"Error Crítico: No se pudo escribir el archivo JSON en '{json_filepath}'. Error: {e}") - except TypeError as e: print(f"Error Crítico: Problema al serializar datos a JSON. Error: {e}") + with open(json_filepath, 'w', encoding='utf-8') as f: json.dump(result, f, indent=4, ensure_ascii=False) + print(f"Paso 6: Escritura completada."); print(f"Conversión finalizada. JSON guardado en: '{json_filepath}'") + except IOError as e: print(f"Error Crítico: No se pudo escribir JSON en '{json_filepath}'. Error: {e}") + except TypeError as e: print(f"Error Crítico: Problema al serializar a JSON. Error: {e}") - except etree.XMLSyntaxError as e: - print(f"Error Crítico: Error de sintaxis en el archivo XML '{xml_filepath}'. Detalles: {e}") - except Exception as e: - print(f"Error Crítico: Ocurrió un error inesperado durante el procesamiento: {e}") - print("--- Traceback ---"); traceback.print_exc(); print("--- Fin Traceback ---") + except etree.XMLSyntaxError as e: print(f"Error Crítico: Sintaxis XML en '{xml_filepath}'. Detalles: {e}") + except Exception as e: print(f"Error Crítico: Error inesperado: {e}"); print("--- Traceback ---"); traceback.print_exc(); print("--- Fin Traceback ---") # --- Punto de Entrada Principal --- if __name__ == "__main__": diff --git a/x2_process.py b/x2_process.py index 8a925b7..429e7b0 100644 --- a/x2_process.py +++ b/x2_process.py @@ -3,616 +3,751 @@ import json import os import copy import traceback -import re # Import regex for PBox SCL parsing if needed later +import re # --- Constantes y Configuración --- SCL_SUFFIX = "_scl" +GROUPED_COMMENT = "// Logic included in grouped IF" -# Global data variable to be accessible by processors needing network context +# Global data variable data = {} + # --- Helper Functions --- - +# (get_scl_representation, generate_temp_var_name, get_target_scl_name - sin cambios) def get_scl_representation(source_info, network_id, scl_map, access_map): - """ - Busca la representación SCL de una entrada. - source_info: Puede ser {'type': 'powerrail'}, un Access dict, o un Connection dict, o una lista (OR). - """ if not source_info: - return None # Entrada no conectada o dependencia no lista - - # Si es una lista (rama OR), procesarla recursivamente + return None if isinstance(source_info, list): scl_parts = [] all_resolved = True for sub_source in source_info: - sub_scl = get_scl_representation(sub_source, network_id, scl_map, access_map) + sub_scl = get_scl_representation( + sub_source, network_id, scl_map, access_map + ) if sub_scl is None: all_resolved = False - # print(f"DEBUG: Dependencia no resuelta DENTRO de rama OR: {sub_source}") break - # Evitar paréntesis innecesarios si ya es una expresión simple o contenida - if sub_scl in ["TRUE", "FALSE"] or (sub_scl.startswith('"') and sub_scl.endswith('"')) or sub_scl.isdigit() or (sub_scl.startswith('(') and sub_scl.endswith(')')): - scl_parts.append(sub_scl) + if ( + sub_scl in ["TRUE", "FALSE"] + or (sub_scl.startswith('"') and sub_scl.endswith('"')) + or sub_scl.isdigit() + or (sub_scl.startswith("(") and sub_scl.endswith(")")) + ): + scl_parts.append(sub_scl) else: - scl_parts.append(f"({sub_scl})") # Añadir paréntesis por precaución de precedencia - if all_resolved: - or_expr = " OR ".join(scl_parts) if len(scl_parts) > 1 else (scl_parts[0] if scl_parts else "FALSE") # Default a FALSE si lista vacía? - # print(f"DEBUG: Rama OR resuelta a: {or_expr}") - return or_expr - else: - return None # Dependencia en la rama no resuelta - - # Si no es lista, procesar como fuente única - source_type = source_info.get('type') - - if source_type == 'powerrail': + scl_parts.append(f"({sub_scl})") + return ( + " OR ".join(scl_parts) + if len(scl_parts) > 1 + else (scl_parts[0] if scl_parts else "FALSE") if all_resolved else None + ) + source_type = source_info.get("type") + if source_type == "powerrail": return "TRUE" - - elif source_type == 'variable': - name = source_info.get('name') + elif source_type == "variable": + name = source_info.get("name") return name if name else f"_ERR_VAR_NO_NAME_{source_info.get('uid')}_" - - - elif source_type == 'constant': - dtype = str(source_info.get('datatype', '')).upper() - value = source_info.get('value') + elif source_type == "constant": + dtype = str(source_info.get("datatype", "")).upper() + value = source_info.get("value") try: - if dtype == 'BOOL': return str(value).upper() - elif dtype in ['INT', 'DINT', 'SINT', 'USINT', 'UINT', 'UDINT', 'LINT', 'ULINT', 'WORD', 'DWORD', 'LWORD', 'BYTE']: return str(value) - elif dtype in ['REAL', 'LREAL']: + if dtype == "BOOL": + return str(value).upper() + elif dtype in [ + "INT", + "DINT", + "SINT", + "USINT", + "UINT", + "UDINT", + "LINT", + "ULINT", + "WORD", + "DWORD", + "LWORD", + "BYTE", + ]: + return str(value) + elif dtype in ["REAL", "LREAL"]: s_val = str(value) - # Asegurar formato decimal para SCL, incluso para enteros como 1.0 - if '.' not in s_val and 'e' not in s_val.lower(): - s_val += ".0" - return s_val - elif dtype == 'STRING': return f"'{str(value)}'" - elif dtype == 'TYPEDCONSTANT': return str(value) # Ej: DINT#60 - else: return f"'{str(value)}'" # Otros tipos como string + return s_val if "." in s_val or "e" in s_val.lower() else s_val + ".0" + elif dtype == "STRING": + return f"'{str(value)}'" + elif dtype == "TYPEDCONSTANT": + return str(value) + else: + return f"'{str(value)}'" except Exception as e: print(f"Advertencia: Error formateando constante {source_info}: {e}") return f"_ERR_CONST_FORMAT_{source_info.get('uid')}_" - - elif source_type == 'connection': - map_key = (network_id, source_info.get('source_instruction_uid'), source_info.get('source_pin')) - # print(f"DEBUG: Buscando en scl_map por {map_key}") - result = scl_map.get(map_key) - # if result is not None: print(f"DEBUG: Encontrado: {result}") - # else: print(f"DEBUG: No encontrado.") - return result # Devuelve valor o None si no existe - - elif source_type == 'unknown_source': - print(f"Advertencia: Refiriendo a fuente desconocida UID: {source_info.get('uid')}") - return f"_ERR_UNKNOWN_SRC_{source_info.get('uid')}_" - + elif source_type == "connection": + map_key = ( + network_id, + source_info.get("source_instruction_uid"), + source_info.get("source_pin"), + ) + return scl_map.get(map_key) + elif source_type == "unknown_source": + print( + f"Advertencia: Refiriendo a fuente desconocida UID: {source_info.get('uid')}" + ) + return f"_ERR_UNKNOWN_SRC_{source_info.get('uid')}_" else: print(f"Advertencia: Tipo de fuente desconocido o inválido: {source_info}") return f"_ERR_INVALID_SRC_TYPE_" + def generate_temp_var_name(network_id, instr_uid, pin_name): - """Genera un nombre único para una variable temporal SCL.""" - net_id_clean = str(network_id).replace('-', '_') - instr_uid_clean = str(instr_uid).replace('-', '_') - pin_name_clean = str(pin_name).replace('-', '_').lower() + net_id_clean = str(network_id).replace("-", "_") + instr_uid_clean = str(instr_uid).replace("-", "_") + pin_name_clean = str(pin_name).replace("-", "_").lower() prefix = "_" if str(net_id_clean)[0].isdigit() else "" return f"{prefix}temp_{net_id_clean}_{instr_uid_clean}_{pin_name_clean}" + def get_target_scl_name(instruction, output_pin_name, network_id, default_to_temp=True): - """Determina el nombre SCL del destino (variable o temporal).""" - instr_uid = instruction['instruction_uid'] - output_pin_data = instruction['outputs'].get(output_pin_name) - target_scl = None + instr_uid = instruction["instruction_uid"] + output_pin_data = instruction["outputs"].get(output_pin_name) + target_scl = None + if ( + output_pin_data + and isinstance(output_pin_data, list) + and len(output_pin_data) == 1 + ): + dest_access = output_pin_data[0] + if dest_access.get("type") == "variable": + target_scl = dest_access.get("name") + if not target_scl: + print( + f"Error: Variable destino para {instr_uid}.{output_pin_name} sin nombre (UID: {dest_access.get('uid')}). {'Usando temp.' if default_to_temp else 'Ignorando.'}" + ) + target_scl = ( + generate_temp_var_name(network_id, instr_uid, output_pin_name) + if default_to_temp + else None + ) + elif dest_access.get("type") == "constant": + print( + f"Advertencia: Instr {instr_uid} intenta escribir en constante UID {dest_access.get('uid')}. {'Usando temp.' if default_to_temp else 'Ignorando.'}" + ) + target_scl = ( + generate_temp_var_name(network_id, instr_uid, output_pin_name) + if default_to_temp + else None + ) + else: + print( + f"Advertencia: Destino de {instr_uid}.{output_pin_name} no es var/const: {dest_access.get('type')}. {'Usando temp.' if default_to_temp else 'Ignorando.'}" + ) + target_scl = ( + generate_temp_var_name(network_id, instr_uid, output_pin_name) + if default_to_temp + else None + ) + elif default_to_temp: + target_scl = generate_temp_var_name(network_id, instr_uid, output_pin_name) + return target_scl - if output_pin_data and isinstance(output_pin_data, list) and len(output_pin_data) == 1: - dest_access = output_pin_data[0] - if dest_access.get('type') == 'variable': - target_scl = dest_access.get('name') - if not target_scl: # Si el nombre es None o vacío - print(f"Error: Variable de destino para {instr_uid}.{output_pin_name} no tiene nombre (UID: {dest_access.get('uid')}). {'Usando temporal.' if default_to_temp else 'Ignorando.'}") - target_scl = generate_temp_var_name(network_id, instr_uid, output_pin_name) if default_to_temp else None - elif dest_access.get('type') == 'constant': - print(f"Advertencia: Instrucción {instr_uid} intenta escribir en constante UID {dest_access.get('uid')}. {'Usando temporal.' if default_to_temp else 'Ignorando.'}") - target_scl = generate_temp_var_name(network_id, instr_uid, output_pin_name) if default_to_temp else None - else: - print(f"Advertencia: Destino de {instr_uid}.{output_pin_name} no es variable ni constante: {dest_access.get('type')}. {'Usando temporal.' if default_to_temp else 'Ignorando.'}") - target_scl = generate_temp_var_name(network_id, instr_uid, output_pin_name) if default_to_temp else None - elif default_to_temp: - # print(f"DEBUG: Usando temporal para {instr_uid}.{output_pin_name} (no hay destino único o no se requiere destino directo)") - target_scl = generate_temp_var_name(network_id, instr_uid, output_pin_name) - return target_scl +# --- Procesadores de Instrucciones (MODIFICADOS para Agrupación) --- -# --- Procesadores de Instrucciones --- -def process_contact(instruction, network_id, scl_map, access_map): - """Traduce Contact a una expresión booleana SCL y actualiza scl_map.""" - instr_uid = instruction['instruction_uid'] - instr_type = instruction['type'] - if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type: return False +def check_if_grouped(instruction, network_id): + """Verifica si la entrada EN de esta instrucción viene de una fuente + que alimenta a más de un bloque funcional en la misma red.""" + en_input = instruction.get("inputs", {}).get("en") + if not isinstance(en_input, dict) or en_input.get("type") != "connection": + return False, None # No está conectado a otra instrucción o no tiene EN - is_negated = False # TODO: Determinar si es negado (ej. por 'Name') + source_uid = en_input.get("source_instruction_uid") + source_pin = en_input.get("source_pin") + if not source_uid or not source_pin: + return False, None # Información de fuente inválida - # print(f"DEBUG: Intentando procesar CONTACT{' (N)' if is_negated else ''} - UID: {instr_uid} en Red: {network_id}") + consumer_count = 0 + network_logic = next( + (net["logic"] for net in data.get("networks", []) if net["id"] == network_id), + [], + ) - # --- CORRECCIÓN: Manejar 'in' implícito --- - in_input = instruction['inputs'].get('in') - in_rlo_scl = None - if in_input is None: - # Si no hay pin 'in' conectado explícitamente, asumir TRUE - # print(f"DEBUG: Asumiendo IN=TRUE para CONTACT UID {instr_uid} (pin 'in' no conectado)") - in_rlo_scl = "TRUE" - else: - in_rlo_scl = get_scl_representation(in_input, network_id, scl_map, access_map) - # --- FIN CORRECCIÓN --- + for instr in network_logic: + other_en = instr.get("inputs", {}).get("en") + if ( + isinstance(other_en, dict) + and other_en.get("type") == "connection" + and other_en.get("source_instruction_uid") == source_uid + and other_en.get("source_pin") == source_pin + ): + # Contar solo bloques funcionales como consumidores + instr_type_orig = ( + instr.get("type", "").replace(SCL_SUFFIX, "").replace("_error", "") + ) + if instr_type_orig in [ + "Move", + "Add", + "Sub", + "Mul", + "Div", + "Mod", + "Convert", + ]: # Ampliar si es necesario + consumer_count += 1 - operand_scl = get_scl_representation(instruction['inputs'].get('operand'), network_id, scl_map, access_map) + is_grouped = consumer_count > 1 + # print(f"DEBUG: Check group for {instruction['instruction_uid']}: source={source_uid}.{source_pin}, consumers={consumer_count}, is_grouped={is_grouped}") + return is_grouped, (source_uid, source_pin) - if in_rlo_scl is None or operand_scl is None: - # print(f"DEBUG: Dependencia no resuelta para CONTACT UID: {instr_uid} (in={in_rlo_scl}, op={operand_scl})") + +def process_move(instruction, network_id, scl_map, access_map): + instr_uid = instruction["instruction_uid"] + instr_type = instruction["type"] + if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type: return False - term = f"NOT {operand_scl}" if is_negated else operand_scl - # Envolver término en paréntesis si es necesario - if not (term.startswith('"') and term.endswith('"')): - if ' ' in term and not (term.startswith('(') and term.endswith(')')): - term = f"({term})" + en_input = instruction["inputs"].get("en") + en_scl = ( + "TRUE" + if en_input is None + else get_scl_representation(en_input, network_id, scl_map, access_map) + ) + in_scl = get_scl_representation( + instruction["inputs"].get("in"), network_id, scl_map, access_map + ) + if en_scl is None or in_scl is None: + return False + + target_scl = get_target_scl_name( + instruction, "out1", network_id, default_to_temp=False + ) + if target_scl is None: + print(f"Advertencia: MOVE UID: {instr_uid} no tiene destino claro.") + return False + + scl_core = f"{target_scl} := {in_scl};" + + # --- Lógica de Agrupación --- + is_grouped, _ = check_if_grouped(instruction, network_id) + if is_grouped: + scl_final = scl_core # Solo el core si es parte de grupo + instruction["grouped"] = True # Marcar como agrupado + elif en_scl != "TRUE": + scl_final = f"IF {en_scl} THEN\n {scl_core}\nEND_IF;" + else: + scl_final = scl_core + # --- Fin Lógica de Agrupación --- + + instruction["scl"] = scl_final + instruction["type"] = instr_type + SCL_SUFFIX + return True + + +def process_add(instruction, network_id, scl_map, access_map): + instr_uid = instruction["instruction_uid"] + instr_type = instruction["type"] + if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type: + return False + + en_input = instruction["inputs"].get("en") + en_scl = ( + "TRUE" + if en_input is None + else get_scl_representation(en_input, network_id, scl_map, access_map) + ) + in1_scl = get_scl_representation( + instruction["inputs"].get("in1"), network_id, scl_map, access_map + ) + in2_scl = get_scl_representation( + instruction["inputs"].get("in2"), network_id, scl_map, access_map + ) + + if en_scl is None or in1_scl is None or in2_scl is None: + return False + + target_scl = get_target_scl_name( + instruction, "out", network_id, default_to_temp=True + ) + if target_scl is None: + print(f"Error Interno: No se pudo det. destino ADD {instr_uid}") + instruction["type"] += "_error" + return True + + op1 = f"({in1_scl})" if " " in in1_scl else in1_scl + op2 = f"({in2_scl})" if " " in in2_scl else in2_scl + scl_core = f"{target_scl} := {op1} + {op2};" + + is_grouped, _ = check_if_grouped(instruction, network_id) + if is_grouped: + scl_final = scl_core + instruction["grouped"] = True + elif en_scl != "TRUE": + scl_final = f"IF {en_scl} THEN\n {scl_core}\nEND_IF;" + else: + scl_final = scl_core + + instruction["scl"] = scl_final + instruction["type"] = instr_type + SCL_SUFFIX + map_key_out = (network_id, instr_uid, "out") + scl_map[map_key_out] = target_scl + map_key_eno = (network_id, instr_uid, "eno") + scl_map[map_key_eno] = en_scl + return True + + +# --- (Aplica lógica similar de 'check_if_grouped' a process_convert, process_mod, etc.) --- +# Ejemplo para process_convert: +def process_convert(instruction, network_id, scl_map, access_map): + instr_uid = instruction["instruction_uid"] + instr_type = instruction["type"] + if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type: + return False + + en_input = instruction["inputs"].get("en") + en_scl = ( + "TRUE" + if en_input is None + else get_scl_representation(en_input, network_id, scl_map, access_map) + ) + in_scl = get_scl_representation( + instruction["inputs"].get("in"), network_id, scl_map, access_map + ) + + if en_scl is None or in_scl is None: + return False + + target_scl = get_target_scl_name( + instruction, "out", network_id, default_to_temp=True + ) + if target_scl is None: + print(f"Error Interno: No se pudo det. destino CONVERT {instr_uid}") + instruction["type"] += "_error" + return True + + conversion_expr = in_scl + scl_core = f"{target_scl} := {conversion_expr};" + + is_grouped, _ = check_if_grouped(instruction, network_id) + if is_grouped: + scl_final = scl_core + instruction["grouped"] = True + elif en_scl != "TRUE": + scl_final = f"IF {en_scl} THEN\n {scl_core}\nEND_IF;" + else: + scl_final = scl_core + + instruction["scl"] = scl_final + instruction["type"] = instr_type + SCL_SUFFIX + map_key_out = (network_id, instr_uid, "out") + scl_map[map_key_out] = target_scl + map_key_eno = (network_id, instr_uid, "eno") + scl_map[map_key_eno] = en_scl + return True + + +# Ejemplo para process_mod: +def process_mod(instruction, network_id, scl_map, access_map): + instr_uid = instruction["instruction_uid"] + instr_type = instruction["type"] + if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type: + return False + + en_input = instruction["inputs"].get("en") + en_scl = ( + "TRUE" + if en_input is None + else get_scl_representation(en_input, network_id, scl_map, access_map) + ) + in1_scl = get_scl_representation( + instruction["inputs"].get("in1"), network_id, scl_map, access_map + ) + in2_scl = get_scl_representation( + instruction["inputs"].get("in2"), network_id, scl_map, access_map + ) + + if en_scl is None or in1_scl is None or in2_scl is None: + return False + + target_scl = get_target_scl_name( + instruction, "out", network_id, default_to_temp=True + ) + if target_scl is None: + print(f"Error Interno: No se pudo det. destino MOD {instr_uid}") + instruction["type"] += "_error" + return True + + op1 = f"({in1_scl})" if " " in in1_scl else in1_scl + op2 = f"({in2_scl})" if " " in in2_scl else in2_scl + scl_core = f"{target_scl} := {op1} MOD {op2};" + + is_grouped, _ = check_if_grouped(instruction, network_id) + if is_grouped: + scl_final = scl_core + instruction["grouped"] = True + elif en_scl != "TRUE": + scl_final = f"IF {en_scl} THEN\n {scl_core}\nEND_IF;" + else: + scl_final = scl_core + + instruction["scl"] = scl_final + instruction["type"] = instr_type + SCL_SUFFIX + map_key_out = (network_id, instr_uid, "out") + scl_map[map_key_out] = target_scl + map_key_eno = (network_id, instr_uid, "eno") + scl_map[map_key_eno] = en_scl + return True + + +# --- (process_contact, process_eq, process_coil, process_o, process_pbox - sin cambios respecto a la versión anterior) --- +# ... (Asegúrate de tener las versiones funcionales de estos aquí) ... +def process_contact(instruction, network_id, scl_map, access_map): + instr_uid = instruction["instruction_uid"] + instr_type = instruction["type"] + if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type: + return False + is_negated = False + in_input = instruction["inputs"].get("in") + in_rlo_scl = ( + "TRUE" + if in_input is None + else get_scl_representation(in_input, network_id, scl_map, access_map) + ) + operand_scl = get_scl_representation( + instruction["inputs"].get("operand"), network_id, scl_map, access_map + ) + if in_rlo_scl is None or operand_scl is None: + return False + term = f"NOT {operand_scl}" if is_negated else operand_scl + if not (term.startswith('"') and term.endswith('"')): + if " " in term and not (term.startswith("(") and term.endswith(")")): + term = f"({term})" new_rlo_scl = "" if in_rlo_scl == "TRUE": new_rlo_scl = term else: - # Envolver RLO anterior en paréntesis si es necesario - if ('AND' in in_rlo_scl or 'OR' in in_rlo_scl) and not (in_rlo_scl.startswith('(') and in_rlo_scl.endswith(')')): + if ("AND" in in_rlo_scl or "OR" in in_rlo_scl) and not ( + in_rlo_scl.startswith("(") and in_rlo_scl.endswith(")") + ): in_rlo_processed = f"({in_rlo_scl})" else: in_rlo_processed = in_rlo_scl new_rlo_scl = f"{in_rlo_processed} AND {term}" - - map_key = (network_id, instr_uid, 'out') + map_key = (network_id, instr_uid, "out") scl_map[map_key] = new_rlo_scl - - instruction['scl'] = f"// RLO updated by Contact {instr_uid}: {new_rlo_scl}" - instruction['type'] = instr_type + SCL_SUFFIX + instruction["scl"] = f"// RLO updated by Contact {instr_uid}: {new_rlo_scl}" + instruction["type"] = instr_type + SCL_SUFFIX return True + def process_eq(instruction, network_id, scl_map, access_map): - """Traduce Eq (comparación) a una expresión booleana SCL y actualiza scl_map.""" - instr_uid = instruction['instruction_uid'] - instr_type = instruction['type'] - if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type: return False - - # print(f"DEBUG: Intentando procesar EQ - UID: {instr_uid} en Red: {network_id}") - - in1_scl = get_scl_representation(instruction['inputs'].get('in1'), network_id, scl_map, access_map) - in2_scl = get_scl_representation(instruction['inputs'].get('in2'), network_id, scl_map, access_map) - - if in1_scl is None or in2_scl is None: - # print(f"DEBUG: Dependencia no resuelta para EQ UID: {instr_uid} (in1={in1_scl}, in2={in2_scl})") + instr_uid = instruction["instruction_uid"] + instr_type = instruction["type"] + if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type: return False - - op1 = f"({in1_scl})" if ' ' in in1_scl else in1_scl - op2 = f"({in2_scl})" if ' ' in in2_scl else in2_scl + in1_scl = get_scl_representation( + instruction["inputs"].get("in1"), network_id, scl_map, access_map + ) + in2_scl = get_scl_representation( + instruction["inputs"].get("in2"), network_id, scl_map, access_map + ) + if in1_scl is None or in2_scl is None: + return False + op1 = f"({in1_scl})" if " " in in1_scl else in1_scl + op2 = f"({in2_scl})" if " " in in2_scl else in2_scl comparison_scl = f"{op1} = {op2}" - - map_key_out = (network_id, instr_uid, 'out') + map_key_out = (network_id, instr_uid, "out") scl_map[map_key_out] = comparison_scl - - # --- Manejo de ENO (CORREGIDO) --- - pre_input = instruction['inputs'].get('pre') - # Asumir TRUE si 'pre' no está conectado explícitamente - pre_scl = "TRUE" if pre_input is None else get_scl_representation(pre_input, network_id, scl_map, access_map) - - if pre_scl is None: # Si está conectado pero no resuelto - # print(f"DEBUG: Dependencia PRE no resuelta para EQ UID {instr_uid}") - return False # No se puede determinar ENO si PRE no está listo - - # El estado ENO de una comparación es TRUE si se ejecuta (EN/PRE=TRUE) y FALSE si no. - map_key_eno = (network_id, instr_uid, 'eno') - scl_map[map_key_eno] = pre_scl # ENO sigue a la habilitación PRE - # --- Fin Manejo de ENO --- - - instruction['scl'] = f"// Comparison Eq {instr_uid}: {comparison_scl}" - instruction['type'] = instr_type + SCL_SUFFIX + pre_input = instruction["inputs"].get("pre") + pre_scl = ( + "TRUE" + if pre_input is None + else get_scl_representation(pre_input, network_id, scl_map, access_map) + ) + if pre_scl is None: + return False + map_key_eno = (network_id, instr_uid, "eno") + scl_map[map_key_eno] = pre_scl + instruction["scl"] = f"// Comparison Eq {instr_uid}: {comparison_scl}" + instruction["type"] = instr_type + SCL_SUFFIX return True + def process_coil(instruction, network_id, scl_map, access_map): - """Traduce Coil a una asignación SCL.""" - instr_uid = instruction['instruction_uid'] - instr_type = instruction['type'] - if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type: return False - - # print(f"DEBUG: Intentando procesar COIL - UID: {instr_uid} en Red: {network_id}") - - in_rlo_scl = get_scl_representation(instruction['inputs'].get('in'), network_id, scl_map, access_map) - operand_info = instruction['inputs'].get('operand') # Obtener info completa del operando + instr_uid = instruction["instruction_uid"] + instr_type = instruction["type"] + if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type: + return False + in_rlo_scl = get_scl_representation( + instruction["inputs"].get("in"), network_id, scl_map, access_map + ) + operand_info = instruction["inputs"].get("operand") operand_scl = get_scl_representation(operand_info, network_id, scl_map, access_map) - - if in_rlo_scl is None or operand_scl is None: - # print(f"DEBUG: Dependencia no resuelta para COIL UID: {instr_uid} (in={in_rlo_scl}, op={operand_scl})") return False - - if not (operand_info and operand_info.get('type') == 'variable'): - print(f"Error: Operando de COIL UID {instr_uid} no es una variable: {operand_info}") - instruction['scl'] = f"// ERROR: Coil {instr_uid} operando no es variable" - instruction['type'] = instr_type + "_error" + if not (operand_info and operand_info.get("type") == "variable"): + print(f"Error: Operando COIL {instr_uid} no es var") + instruction["scl"] = f"// ERROR: Coil {instr_uid} operando no es variable" + instruction["type"] = instr_type + "_error" return True - - # Simplificar RLO si es posible antes de asignar - if in_rlo_scl == "(TRUE)": in_rlo_scl = "TRUE" - elif in_rlo_scl == "(FALSE)": in_rlo_scl = "FALSE" - + if in_rlo_scl == "(TRUE)": + in_rlo_scl = "TRUE" + elif in_rlo_scl == "(FALSE)": + in_rlo_scl = "FALSE" scl_final = f"{operand_scl} := {in_rlo_scl};" - - instruction['scl'] = scl_final - instruction['type'] = instr_type + SCL_SUFFIX - - # print(f"INFO: COIL UID: {instr_uid} procesado. SCL: {scl_final}") + instruction["scl"] = scl_final + instruction["type"] = instr_type + SCL_SUFFIX return True -def process_convert(instruction, network_id, scl_map, access_map): - """Traduce Convert a SCL, usando temporal si es necesario.""" - instr_uid = instruction['instruction_uid'] - instr_type = instruction['type'] - if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type: return False - - # print(f"DEBUG: Intentando procesar CONVERT - UID: {instr_uid} en Red: {network_id}") - - # --- CORRECCIÓN: Manejo de EN --- - en_input = instruction['inputs'].get('en') - en_scl = None - if en_input is None: - # print(f"DEBUG: Asumiendo EN=TRUE para CONVERT UID {instr_uid} (pin 'en' no conectado)") - en_scl = "TRUE" - else: - en_scl = get_scl_representation(en_input, network_id, scl_map, access_map) - - if en_scl is None: # Bloquear si EN está conectado pero no resuelto - # print(f"DEBUG: Dependencia EN no resuelta para CONVERT UID: {instr_uid}") - return False - # --- FIN CORRECCIÓN --- - - in_scl = get_scl_representation(instruction['inputs'].get('in'), network_id, scl_map, access_map) - - if in_scl is None: - # print(f"DEBUG: Dependencia IN no resuelta para CONVERT UID: {instr_uid}") - return False - - target_scl = get_target_scl_name(instruction, 'out', network_id, default_to_temp=True) - if target_scl is None: - print(f"Error Interno: No se pudo determinar destino para CONVERT UID {instr_uid}") - instruction['scl'] = f"// ERROR: No se pudo determinar destino para Convert {instr_uid}" - instruction['type'] += "_error" - return True - - conversion_expr = in_scl # Asume conversión implícita por ahora - - scl_core = f"{target_scl} := {conversion_expr};" - # --- CORRECCIÓN: Generación de IF --- - if en_scl != "TRUE": - scl_final = f"IF {en_scl} THEN\n {scl_core}\nEND_IF;" - else: - scl_final = scl_core - # --- FIN CORRECCIÓN --- - - instruction['scl'] = scl_final - instruction['type'] = instr_type + SCL_SUFFIX - - map_key_out = (network_id, instr_uid, 'out') - scl_map[map_key_out] = target_scl - - # --- CORRECCIÓN: Añadir ENO a scl_map --- - map_key_eno = (network_id, instr_uid, 'eno') - scl_map[map_key_eno] = en_scl # ENO sigue a EN - # --- FIN CORRECCIÓN --- - - # print(f"INFO: CONVERT UID: {instr_uid} procesado. SCL: {scl_final.splitlines()[0]}...") - return True - -def process_mod(instruction, network_id, scl_map, access_map): - """Traduce Mod (módulo) a SCL, usando temporal si es necesario.""" - instr_uid = instruction['instruction_uid'] - instr_type = instruction['type'] - if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type: return False - - # print(f"DEBUG: Intentando procesar MOD - UID: {instr_uid} en Red: {network_id}") - - # --- CORRECCIÓN: Manejo de EN --- - en_input = instruction['inputs'].get('en') - en_scl = None - if en_input is None: - # print(f"DEBUG: Asumiendo EN=TRUE para MOD UID {instr_uid} (pin 'en' no conectado)") - en_scl = "TRUE" - else: - en_scl = get_scl_representation(en_input, network_id, scl_map, access_map) - - if en_scl is None: # Bloquear si EN está conectado pero no resuelto - # print(f"DEBUG: Dependencia EN no resuelta para MOD UID: {instr_uid}") - return False - # --- FIN CORRECCIÓN --- - - in1_scl = get_scl_representation(instruction['inputs'].get('in1'), network_id, scl_map, access_map) - in2_scl = get_scl_representation(instruction['inputs'].get('in2'), network_id, scl_map, access_map) - - if in1_scl is None or in2_scl is None: - # print(f"DEBUG: Dependencia no resuelta para MOD UID: {instr_uid} (in1={in1_scl}, in2={in2_scl})") - return False - - target_scl = get_target_scl_name(instruction, 'out', network_id, default_to_temp=True) - if target_scl is None: - print(f"Error Interno: No se pudo determinar destino para MOD UID {instr_uid}") - instruction['scl'] = f"// ERROR: No se pudo determinar destino para Mod {instr_uid}" - instruction['type'] += "_error" - return True - - op1 = f"({in1_scl})" if ' ' in in1_scl else in1_scl - op2 = f"({in2_scl})" if ' ' in in2_scl else in2_scl - scl_core = f"{target_scl} := {op1} MOD {op2};" - # --- CORRECCIÓN: Generación de IF --- - if en_scl != "TRUE": - scl_final = f"IF {en_scl} THEN\n {scl_core}\nEND_IF;" - else: - scl_final = scl_core - # --- FIN CORRECCIÓN --- - - instruction['scl'] = scl_final - instruction['type'] = instr_type + SCL_SUFFIX - - map_key_out = (network_id, instr_uid, 'out') - scl_map[map_key_out] = target_scl - - # --- CORRECCIÓN: Añadir ENO a scl_map --- - map_key_eno = (network_id, instr_uid, 'eno') - scl_map[map_key_eno] = en_scl # ENO sigue a EN - # --- FIN CORRECCIÓN --- - - # print(f"INFO: MOD UID: {instr_uid} procesado. SCL: {scl_final.splitlines()[0]}...") - return True - -def process_add(instruction, network_id, scl_map, access_map): - instr_uid = instruction['instruction_uid'] - instr_type = instruction['type'] - if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type: return False - - # print(f"DEBUG: Intentando procesar ADD - UID: {instr_uid} en Red: {network_id}") - - # --- CORRECCIÓN: Manejo de EN --- - en_input = instruction['inputs'].get('en') - en_scl = None - if en_input is None: - # print(f"DEBUG: Asumiendo EN=TRUE para ADD UID {instr_uid} (pin 'en' no conectado)") - en_scl = "TRUE" - else: - en_scl = get_scl_representation(en_input, network_id, scl_map, access_map) - - if en_scl is None: # Bloquear si EN está conectado pero no resuelto - # print(f"DEBUG: Dependencia EN no resuelta para ADD UID: {instr_uid}") - return False - # --- FIN CORRECCIÓN --- - - in1_scl = get_scl_representation(instruction['inputs'].get('in1'), network_id, scl_map, access_map) - in2_scl = get_scl_representation(instruction['inputs'].get('in2'), network_id, scl_map, access_map) - - if in1_scl is None or in2_scl is None: - # print(f"DEBUG: Dependencia no resuelta para ADD UID: {instr_uid} (in1={in1_scl}, in2={in2_scl})") - return False - - target_scl = get_target_scl_name(instruction, 'out', network_id, default_to_temp=True) - if target_scl is None: - print(f"Error Interno: No se pudo determinar destino para ADD UID {instr_uid}") - instruction['scl'] = f"// ERROR: No se pudo determinar destino para Add {instr_uid}" - instruction['type'] += "_error" - return True - - op1 = f"({in1_scl})" if ' ' in in1_scl else in1_scl - op2 = f"({in2_scl})" if ' ' in in2_scl else in2_scl - scl_core = f"{target_scl} := {op1} + {op2};" - # --- CORRECCIÓN: Generación de IF --- - if en_scl != "TRUE": - scl_final = f"IF {en_scl} THEN\n {scl_core}\nEND_IF;" - else: - scl_final = scl_core - # --- FIN CORRECCIÓN --- - - instruction['scl'] = scl_final - instruction['type'] = instr_type + SCL_SUFFIX - - map_key_out = (network_id, instr_uid, 'out') - scl_map[map_key_out] = target_scl - - # --- CORRECCIÓN: Añadir ENO a scl_map --- - map_key_eno = (network_id, instr_uid, 'eno') - scl_map[map_key_eno] = en_scl # ENO sigue a EN - # --- FIN CORRECCIÓN --- - - # print(f"INFO: ADD UID: {instr_uid} procesado. SCL: {scl_final.splitlines()[0]}...") - return True - -def process_move(instruction, network_id, scl_map, access_map): - instr_uid = instruction['instruction_uid'] - instr_type = instruction['type'] - if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type: return False - - # print(f"DEBUG: Intentando procesar MOVE - UID: {instr_uid} en Red: {network_id}") - - # --- CORRECCIÓN: Manejo de EN --- - en_input = instruction['inputs'].get('en') - en_scl = None - if en_input is None: - # print(f"DEBUG: Asumiendo EN=TRUE para MOVE UID {instr_uid} (pin 'en' no conectado)") - en_scl = "TRUE" - else: - en_scl = get_scl_representation(en_input, network_id, scl_map, access_map) - - if en_scl is None: # Bloquear si EN está conectado pero no resuelto - # print(f"DEBUG: Dependencia EN no resuelta para MOVE UID: {instr_uid}") - return False - # --- FIN CORRECCIÓN --- - - in_scl = get_scl_representation(instruction['inputs'].get('in'), network_id, scl_map, access_map) - - if in_scl is None: - # print(f"DEBUG: Dependencia IN no resuelta para MOVE UID: {instr_uid}") - return False - - target_scl = get_target_scl_name(instruction, 'out1', network_id, default_to_temp=False) # No usar temp por defecto - - if target_scl is None: - print(f"Advertencia: MOVE UID: {instr_uid} no tiene un destino variable único claro en out1. No se procesa.") - return False - - scl_core = f"{target_scl} := {in_scl};" - # --- CORRECCIÓN: Generación de IF --- - if en_scl != "TRUE": - scl_final = f"IF {en_scl} THEN\n {scl_core}\nEND_IF;" - else: - scl_final = scl_core - # --- FIN CORRECCIÓN --- - - instruction['scl'] = scl_final - instruction['type'] = instr_type + SCL_SUFFIX - - # print(f"INFO: MOVE UID: {instr_uid} procesado. SCL: {scl_final.splitlines()[0]}...") - # Move no tiene ENO explícito en la mayoría de implementaciones LAD/SCL, - # por lo que no añadimos map_key_eno aquí normalmente. - return True def process_pbox(instruction, network_id, scl_map, access_map, network_logic_list): - """ - Traduce PBox a SCL. Asume P_TRIG (flanco positivo) si detecta uso típico, - si no, pasa el valor del bit directamente. - """ - instr_uid = instruction['instruction_uid'] - instr_type = instruction['type'] - if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type: return False - - # print(f"DEBUG: Intentando procesar PBOX - UID: {instr_uid} en Red: {network_id}") - - mem_bit_input = instruction['inputs'].get('bit') + instr_uid = instruction["instruction_uid"] + instr_type = instruction["type"] + if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type: + return False + mem_bit_input = instruction["inputs"].get("bit") mem_bit_scl = get_scl_representation(mem_bit_input, network_id, scl_map, access_map) if mem_bit_scl is None: - # print(f"DEBUG: Dependencia no resuelta para PBOX UID: {instr_uid} (bit={mem_bit_scl})") return False - if not (mem_bit_input and mem_bit_input.get('type') == 'variable'): - print(f"Error: Entrada 'bit' de PBOX UID {instr_uid} no es una variable: {mem_bit_input}") - instruction['scl'] = f"// ERROR: PBox {instr_uid} entrada 'bit' no es variable" - instruction['type'] += "_error" - return True - + if not (mem_bit_input and mem_bit_input.get("type") == "variable"): + print(f"Error: PBOX {instr_uid} bit no es var") + instruction["scl"] = f"// ERROR: PBox {instr_uid} bit no es var" + instruction["type"] += "_error" + return True is_likely_p_trig = False consuming_coil_uid = None for potential_consumer in network_logic_list: - consumer_inputs = potential_consumer.get('inputs', {}) - coil_input_signal = consumer_inputs.get('in') - if isinstance(coil_input_signal, dict) and \ - coil_input_signal.get('type') == 'connection' and \ - coil_input_signal.get('source_instruction_uid') == instr_uid and \ - coil_input_signal.get('source_pin') == 'out': - consumer_type = potential_consumer.get('type', '').replace('_scl','').replace('_error','') - if consumer_type == 'Coil': - consuming_coil_uid = potential_consumer['instruction_uid'] - is_likely_p_trig = True - break - + coil_input_signal = potential_consumer.get("inputs", {}).get("in") + if ( + isinstance(coil_input_signal, dict) + and coil_input_signal.get("type") == "connection" + and coil_input_signal.get("source_instruction_uid") == instr_uid + and coil_input_signal.get("source_pin") == "out" + ): + if ( + potential_consumer.get("type", "") + .replace("_scl", "") + .replace("_error", "") + == "Coil" + ): + is_likely_p_trig = True + break rlo_scl = None if is_likely_p_trig: clk_source_found = False current_instr_index = -1 for i, instr in enumerate(network_logic_list): - if instr['instruction_uid'] == instr_uid: + if instr["instruction_uid"] == instr_uid: current_instr_index = i break - if current_instr_index != -1: for i in range(current_instr_index - 1, -1, -1): prev_instr = network_logic_list[i] - prev_instr_uid = prev_instr['instruction_uid'] - prev_instr_type = prev_instr.get('type', '').replace(SCL_SUFFIX, '').replace('_error', '') - - if prev_instr_type in ['Contact', 'Eq', 'O', 'PBox', 'And', 'Xor', 'Ne', 'Gt', 'Lt', 'Ge', 'Le']: - map_key_prev_out = (network_id, prev_instr_uid, 'out') + prev_instr_uid = prev_instr["instruction_uid"] + prev_instr_type = ( + prev_instr.get("type", "") + .replace(SCL_SUFFIX, "") + .replace("_error", "") + ) + if prev_instr_type in [ + "Contact", + "Eq", + "O", + "PBox", + "And", + "Xor", + "Ne", + "Gt", + "Lt", + "Ge", + "Le", + ]: + map_key_prev_out = (network_id, prev_instr_uid, "out") potential_clk_scl = scl_map.get(map_key_prev_out) if potential_clk_scl is not None: rlo_scl = potential_clk_scl clk_source_found = True break - elif prev_instr_type in ['Move', 'Add', 'Convert', 'Mod']: - map_key_prev_eno = (network_id, prev_instr_uid, 'eno') - potential_clk_scl = scl_map.get(map_key_prev_eno) - if potential_clk_scl is not None: - rlo_scl = potential_clk_scl - clk_source_found = True - break - + elif prev_instr_type in ["Move", "Add", "Convert", "Mod"]: + map_key_prev_eno = (network_id, prev_instr_uid, "eno") + potential_clk_scl = scl_map.get(map_key_prev_eno) + if potential_clk_scl is not None: + rlo_scl = potential_clk_scl + clk_source_found = True + break if not clk_source_found: - print(f"Error: No se pudo inferir la fuente CLK para PBOX UID {instr_uid} (probable P_TRIG).") - instruction['scl'] = f"// ERROR: PBox {instr_uid} sin fuente CLK implícita clara" - instruction['type'] += "_error" + print(f"Error: No se pudo inferir CLK para PBOX {instr_uid}") + instruction["scl"] = f"// ERROR: PBox {instr_uid} sin CLK" + instruction["type"] += "_error" return True - if rlo_scl is None: - # print(f"DEBUG: Dependencia CLK (inferida) no resuelta para PBOX UID: {instr_uid}") - return False - + return False scl_comment = "" if is_likely_p_trig: - clk_signal_formatted = f"({rlo_scl})" if ' ' in rlo_scl else rlo_scl + clk_signal_formatted = f"({rlo_scl})" if " " in rlo_scl else rlo_scl result_scl = f"P_TRIG_FUNC(CLK := {clk_signal_formatted}, M := {mem_bit_scl})" - scl_comment = f"// Edge detection PBox {instr_uid} -> {result_scl} (CLK source inferred)" + scl_comment = ( + f"// Edge detection PBox {instr_uid} -> {result_scl} (CLK inferred)" + ) else: - print(f"Advertencia: PBox UID {instr_uid} no coincide con patrón P_TRIG->Coil. Pasando valor de {mem_bit_scl} directamente.") + print(f"Advertencia: PBox {instr_uid} no como P_TRIG. Pasando bit.") result_scl = mem_bit_scl - scl_comment = f"// PBox {instr_uid} - Passing value from bit: {result_scl}" - - map_key_out = (network_id, instr_uid, 'out') + scl_comment = f"// PBox {instr_uid} - Passing bit: {result_scl}" + map_key_out = (network_id, instr_uid, "out") scl_map[map_key_out] = result_scl - - instruction['scl'] = scl_comment - instruction['type'] = instr_type + SCL_SUFFIX + instruction["scl"] = scl_comment + instruction["type"] = instr_type + SCL_SUFFIX return True + def process_o(instruction, network_id, scl_map, access_map): - """Traduce O (OR lógico) a una expresión booleana SCL.""" - instr_uid = instruction['instruction_uid'] - instr_type = instruction['type'] - if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type: return False - - # print(f"DEBUG: Intentando procesar O - UID: {instr_uid} en Red: {network_id}") - - input_pins = [pin for pin in instruction['inputs'] if pin.startswith('in')] + instr_uid = instruction["instruction_uid"] + instr_type = instruction["type"] + if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type: + return False + input_pins = [pin for pin in instruction["inputs"] if pin.startswith("in")] if not input_pins: - print(f"Error: Instrucción O UID {instr_uid} no tiene pines de entrada 'inX'.") - instruction['scl'] = f"// ERROR: O {instr_uid} sin pines de entrada" - instruction['type'] += "_error" - return True - + print(f"Error: O {instr_uid} sin pines inX") + instruction["scl"] = f"// ERROR: O {instr_uid} sin pines in" + instruction["type"] += "_error" + return True scl_parts = [] all_resolved = True for pin in sorted(input_pins): - in_scl = get_scl_representation(instruction['inputs'][pin], network_id, scl_map, access_map) + in_scl = get_scl_representation( + instruction["inputs"][pin], network_id, scl_map, access_map + ) if in_scl is None: all_resolved = False - # print(f"DEBUG: Dependencia no resuelta para O UID: {instr_uid} (pin {pin})") break - term = f"({in_scl})" if (' ' in in_scl or 'AND' in in_scl) and not (in_scl.startswith('(') and in_scl.endswith(')')) else in_scl + term = ( + f"({in_scl})" + if (" " in in_scl or "AND" in in_scl) + and not (in_scl.startswith("(") and in_scl.endswith(")")) + else in_scl + ) scl_parts.append(term) - if not all_resolved: return False - - result_scl = " OR ".join(scl_parts) if len(scl_parts) > 1 else (scl_parts[0] if scl_parts else "FALSE") - - map_key_out = (network_id, instr_uid, 'out') + result_scl = ( + " OR ".join(scl_parts) + if len(scl_parts) > 1 + else (scl_parts[0] if scl_parts else "FALSE") + ) + map_key_out = (network_id, instr_uid, "out") scl_map[map_key_out] = result_scl - - instruction['scl'] = f"// Logic O {instr_uid}: {result_scl}" - instruction['type'] = instr_type + SCL_SUFFIX + instruction["scl"] = f"// Logic O {instr_uid}: {result_scl}" + instruction["type"] = instr_type + SCL_SUFFIX return True +# --- NUEVO: Procesador de Agrupación --- +def process_group_ifs(instruction, network_id, scl_map, access_map): + """ + Busca instrucciones que generan condiciones (Contact, O, Eq, PBox) + y, si habilitan un grupo de bloques funcionales, construye el bloque IF agrupado. + Modifica el campo 'scl' de la instrucción generadora de condición. + """ + instr_uid = instruction["instruction_uid"] + instr_type = instruction["type"] + instr_type_original = instr_type.replace("_scl", "").replace("_error", "") + made_change = False + + # Solo actuar sobre generadores de condición ya procesados + if not instr_type.endswith("_scl") or instr_type_original not in [ + "Contact", + "O", + "Eq", + "Ne", + "Gt", + "Lt", + "Ge", + "Le", + "PBox", + ]: + return False # No es un generador de condición procesado relevante + + # Si ya contiene un IF agrupado (de un pase anterior o error), no hacer nada + if instruction.get("scl", "").strip().startswith("IF"): + return False + + # Obtener la condición generada por esta instrucción + map_key_out = (network_id, instr_uid, "out") + condition_scl = scl_map.get(map_key_out) + + if condition_scl is None or condition_scl == "TRUE" or condition_scl == "FALSE": + return False # No agrupar para condiciones triviales + + # Encontrar todos los bloques funcionales habilitados DIRECTAMENTE por esta condición + grouped_instructions_core_scl = [] + consumer_uids_processed_in_group = set() # Para marcar consumidores agrupados + network_logic = next( + (net["logic"] for net in data["networks"] if net["id"] == network_id), [] + ) + if not network_logic: + return False + + for consumer_instr in network_logic: + consumer_uid = consumer_instr["instruction_uid"] + # Saltar si el consumidor ya fue marcado como agrupado por otra condición + if consumer_instr.get("grouped", False): + continue + + consumer_en = consumer_instr.get("inputs", {}).get("en") + consumer_type = consumer_instr.get("type", "") + consumer_type_original = consumer_type.replace("_scl", "").replace("_error", "") + + # Verificar si está conectado a nuestra salida 'out' + if ( + isinstance(consumer_en, dict) + and consumer_en.get("type") == "connection" + and consumer_en.get("source_instruction_uid") == instr_uid + and consumer_en.get("source_pin") == "out" + ): + + # Verificar si es un bloque funcional procesado y si su SCL es solo el 'core' + if ( + consumer_type.endswith("_scl") + and consumer_type_original + in ["Move", "Add", "Sub", "Mul", "Div", "Mod", "Convert"] + and consumer_instr.get("scl") + and not consumer_instr["scl"].strip().startswith("IF") + ): # Asegurarse de que NO tenga IF + + core_scl = consumer_instr["scl"].strip() + grouped_instructions_core_scl.append(core_scl) + consumer_uids_processed_in_group.add(consumer_uid) + + # Si encontramos más de un consumidor agrupado + if len(grouped_instructions_core_scl) > 1: + print( + f"INFO: Agrupando {len(grouped_instructions_core_scl)} instrucciones para condición de {instr_type_original} UID {instr_uid}" + ) + + # Construir el bloque IF agrupado + scl_grouped = [f"IF {condition_scl} THEN"] + for core_line in grouped_instructions_core_scl: + scl_grouped.append(f" {core_line}") # Añadir indentación + scl_grouped.append("END_IF;") + final_grouped_scl = "\n".join(scl_grouped) + + # Sobrescribir el campo 'scl' de la instrucción generadora de condición + instruction["scl"] = final_grouped_scl + # Marcar los consumidores para que generate_scl los ignore + for consumer_uid_to_mark in consumer_uids_processed_in_group: + for instr_to_mark in network_logic: + if instr_to_mark["instruction_uid"] == consumer_uid_to_mark: + # Añadir comentario y flag + instr_to_mark["scl"] = f"{GROUPED_COMMENT} (by UID {instr_uid})" + instr_to_mark["grouped"] = True + break + made_change = True + + return made_change + # --- Bucle Principal de Procesamiento --- + def process_json_to_scl(json_filepath): """Lee el JSON, aplica los procesadores iterativamente y guarda el resultado.""" if not os.path.exists(json_filepath): @@ -621,7 +756,7 @@ def process_json_to_scl(json_filepath): print(f"Cargando JSON desde: {json_filepath}") try: - with open(json_filepath, 'r', encoding='utf-8') as f: + with open(json_filepath, "r", encoding="utf-8") as f: global data data = json.load(f) except Exception as e: @@ -631,28 +766,42 @@ def process_json_to_scl(json_filepath): # Reconstruir access_map dinámicamente network_access_maps = {} # print("Creando mapas de acceso por red...") - for network in data.get('networks', []): - net_id = network['id'] + for network in data.get("networks", []): + net_id = network["id"] current_access_map = {} - for instr in network.get('logic', []): - for pin, source in instr.get('inputs', {}).items(): - sources_to_check = source if isinstance(source, list) else ([source] if isinstance(source, dict) else []) + for instr in network.get("logic", []): + for _, source in instr.get("inputs", {}).items(): + sources_to_check = ( + source + if isinstance(source, list) + else ([source] if isinstance(source, dict) else []) + ) for src in sources_to_check: - if isinstance(src, dict) and src.get('uid') and src.get('scope') and src.get('type') in ['variable', 'constant']: - current_access_map[src['uid']] = src - for pin, dest_list in instr.get('outputs', {}).items(): - if isinstance(dest_list, list): + if ( + isinstance(src, dict) + and src.get("uid") + and src.get("scope") + and src.get("type") in ["variable", "constant"] + ): + current_access_map[src["uid"]] = src + for _, dest_list in instr.get("outputs", {}).items(): + if isinstance(dest_list, list): for dest in dest_list: - if isinstance(dest, dict) and dest.get('uid') and dest.get('scope') and dest.get('type') in ['variable', 'constant']: - current_access_map[dest['uid']] = dest + if ( + isinstance(dest, dict) + and dest.get("uid") + and dest.get("scope") + and dest.get("type") in ["variable", "constant"] + ): + current_access_map[dest["uid"]] = dest network_access_maps[net_id] = current_access_map scl_map = {} - max_passes = 25 # Aumentado ligeramente por si acaso + max_passes = 25 passes = 0 - # Lista de procesadores con orden corregido - processors = [ + # Lista de procesadores base + procesador de agrupación + base_processors = [ process_convert, process_mod, process_eq, @@ -662,77 +811,118 @@ def process_json_to_scl(json_filepath): process_add, process_move, process_coil, - # process_group_ifs, # Aplazado por ahora para simplificar ] - # Mapa de procesadores usando lower case para la clave - processor_map = {func.__name__.split('_')[1].lower(): func for func in processors} + processor_map = { + func.__name__.split("_")[1].lower(): func for func in base_processors + } print("\n--- Iniciando Bucle de Procesamiento Iterativo ---") while passes < max_passes: passes += 1 - made_change_in_pass = False + made_change_in_base_pass = False + made_change_in_group_pass = False print(f"\n--- Pase {passes} ---") - for network in data.get('networks', []): - network_id = network['id'] + # --- FASE 1: Procesadores Base --- + print(f"DEBUG: Iniciando Fase 1 (Procesadores Base) - Pase {passes}") + for network in data.get("networks", []): + network_id = network["id"] access_map = network_access_maps.get(network_id, {}) - network_logic = network.get('logic', []) + network_logic = network.get("logic", []) for instruction in network_logic: - instr_type_original = instruction['type'] - if instr_type_original.endswith(SCL_SUFFIX) or "_error" in instr_type_original: - continue + instr_type_original = instruction["type"] + if ( + instr_type_original.endswith(SCL_SUFFIX) + or "_error" in instr_type_original + or instruction.get("grouped", False) + ): + continue # Saltar ya procesados, erróneos o agrupados - # Búsqueda de procesador (case-insensitive) instr_type_lower_lookup = instr_type_original.lower() func_to_call = processor_map.get(instr_type_lower_lookup) if func_to_call: - # print(f"DEBUG: Intentando {func_to_call.__name__} para {instr_type_original} UID {instruction['instruction_uid']}") try: - # Pasar lista lógica si es PBox + changed = False if func_to_call == process_pbox: - changed = func_to_call(instruction, network_id, scl_map, access_map, network_logic) + changed = func_to_call( + instruction, + network_id, + scl_map, + access_map, + network_logic, + ) else: - changed = func_to_call(instruction, network_id, scl_map, access_map) + changed = func_to_call( + instruction, network_id, scl_map, access_map + ) if changed: - # print(f"DEBUG: Cambio realizado por {func_to_call.__name__} en UID {instruction['instruction_uid']}") - made_change_in_pass = True - # No hacer break, permite que el procesador de grupo actúe si se añade después + # print(f"DEBUG: Cambio BASE detectado por {func_to_call.__name__} en UID {instruction['instruction_uid']}") + made_change_in_base_pass = True except Exception as e: - print(f"ERROR al ejecutar {func_to_call.__name__} en UID {instruction.get('instruction_uid')} Red {network_id}: {e}") + print( + f"ERROR(Base) al ejecutar {func_to_call.__name__} en UID {instruction.get('instruction_uid')} Red {network_id}: {e}" + ) traceback.print_exc() - instruction['scl'] = f"// ERROR during processing: {e}" - instruction['type'] += "_error" - made_change_in_pass = True + instruction["scl"] = f"// ERROR during base processing: {e}" + instruction["type"] += "_error" + made_change_in_base_pass = True # Considerar error como cambio - # --- Llamada Opcional al Procesador de Agrupación (APLAZADO) --- - # if made_change_in_pass: # Solo intentar agrupar si hubo otros cambios? - # for network in data.get('networks', []): - # # ... (código para llamar a process_group_ifs) ... - # pass + # --- FASE 2: Procesador de Agrupación --- + print(f"DEBUG: Iniciando Fase 2 (Agrupación IF) - Pase {passes}") + for network in data.get("networks", []): + network_id = network["id"] + access_map = network_access_maps.get(network_id, {}) + network_logic = network.get("logic", []) + for instruction in network_logic: + # Solo intentar agrupar en generadores de condición ya procesados + if instruction["type"].endswith("_scl") and not instruction.get( + "grouped", False + ): + try: + group_changed = process_group_ifs( + instruction, network_id, scl_map, access_map + ) + if group_changed: + # print(f"DEBUG: Cambio GROUP detectado por process_group_ifs en UID {instruction['instruction_uid']}") + made_change_in_group_pass = True + except Exception as e: + print( + f"ERROR(Group) al ejecutar process_group_ifs en UID {instruction.get('instruction_uid')}: {e}" + ) + traceback.print_exc() + # No marcar la instrucción como error por fallo en agrupación - - if not made_change_in_pass: - print(f"\n--- No se hicieron cambios en el pase {passes}. Proceso completado. ---") + # Decidir si continuar: Hubo algún cambio en CUALQUIERA de las fases? + if not made_change_in_base_pass and not made_change_in_group_pass: + print( + f"\n--- No se hicieron cambios en el pase {passes}. Proceso completado. ---" + ) break - # else: print(f"DEBUG: Se hicieron cambios en el pase {passes}. Continuando...") - + else: + print( + f"DEBUG: Cambios en Pase {passes}: Base={made_change_in_base_pass}, Grupo={made_change_in_group_pass}. Continuando..." + ) if passes == max_passes: - print(f"\n--- Límite de {max_passes} pases alcanzado. Puede haber dependencias circulares o lógica no procesada. ---") + print( + f"\n--- Límite de {max_passes} pases alcanzado. Puede haber dependencias circulares o lógica no procesada. ---" + ) - output_filename = json_filepath.replace('.json', '_scl_processed.json') + # --- Guardar JSON Final --- + output_filename = json_filepath.replace(".json", "_scl_processed.json") print(f"\nGuardando JSON procesado en: {output_filename}") try: - with open(output_filename, 'w', encoding='utf-8') as f: + with open(output_filename, "w", encoding="utf-8") as f: json.dump(data, f, indent=4, ensure_ascii=False) print("Guardado completado.") except Exception as e: print(f"Error al guardar el JSON procesado: {e}") + # --- Ejecución --- if __name__ == "__main__": - input_json_file = 'BlenderRun_ProdTime_simplified.json' - process_json_to_scl(input_json_file) \ No newline at end of file + input_json_file = "BlenderRun_ProdTime_simplified.json" + process_json_to_scl(input_json_file)