From 94163baee0720acfcef6b288d44606596f4460a1 Mon Sep 17 00:00:00 2001 From: Miguel Date: Fri, 18 Apr 2025 16:11:13 +0200 Subject: [PATCH] Con script para la generacion del SCL --- BlenderRun_ProdTime.scl | 112 ++++++++ ...Run_ProdTime_simplified_scl_processed.json | 15 +- json_to_scl.py | 259 ------------------ to_json.py => x1_to_json.py | 0 process.py => x2_process.py | 42 ++- x3_generate_scl.py | 202 ++++++++++++++ 6 files changed, 356 insertions(+), 274 deletions(-) create mode 100644 BlenderRun_ProdTime.scl delete mode 100644 json_to_scl.py rename to_json.py => x1_to_json.py (100%) rename process.py => x2_process.py (96%) create mode 100644 x3_generate_scl.py diff --git a/BlenderRun_ProdTime.scl b/BlenderRun_ProdTime.scl new file mode 100644 index 0000000..4725e26 --- /dev/null +++ b/BlenderRun_ProdTime.scl @@ -0,0 +1,112 @@ +// Block Name (Original): BlenderRun_ProdTime +// Block Number: 2040 +// Original Language: LAD + +FUNCTION_BLOCK "BlenderRun_ProdTime" +{ S7_Optimized_Access := 'TRUE' } +VERSION : 0.1 + +VAR_INPUT +END_VAR + +VAR_OUTPUT +END_VAR + +VAR_IN_OUT +END_VAR + +VAR_TEMP + m1MinONS : Bool; + m1HourONS : Bool; + Buffer : Bool; + mRunMin : Bool; + mRunHr : Bool; + I_DIRunning_sec : DInt; + I_DIRunning_min : DInt; + MOD60 : DInt; +END_VAR + +BEGIN + + // Network 1: Seconds + + IF "Procedure_Variables"."Blender_Run"."Running" AND "CLK_1.0S" THEN + "Blender_Variables_Pers"."gSLIM_Sec" := "Blender_Variables_Pers"."gSLIM_Sec" + 1; + END_IF; + + // Network 2: Reset Hours + + IF "SLIM_Variables"."ResetHour" THEN + "Blender_Variables_Pers"."gSLIM_Sec" := 0; + END_IF; + + // Network 3: Seconds Counter + + IF "gBlenderBlending" AND "CLK_1.0S" THEN + "Blender_Variables_Pers"."gProdSec" := "Blender_Variables_Pers"."gProdSec" + 1; + END_IF; + + // Network 4: Minute + + "m1MinONS" := "Blender_Variables_Pers"."gProdSec" = 60; + + // Network 5: Minute Counter + + IF "m1MinONS" THEN + "Blender_Variables_Pers"."gProdSec" := 0; + END_IF; + "Blender_Variables_Pers"."gProdMin" := "Blender_Variables_Pers"."gProdMin" + 1; + + // Network 6: Hour + + "m1HourONS" := "Blender_Variables_Pers"."gProdMin" = 60; + + // Network 7: Hour Counter + + IF "m1HourONS" THEN + "Blender_Variables_Pers"."gProdMin" := 0; + END_IF; + "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"."gProdMin" := 0; + "Blender_Variables_Pers"."gProdHour" := 0; + + // Network 9: Running Seconds + + IF "Procedure_Variables"."Blender_Run"."Running" AND "CLK_1.0S" THEN + "Blender_Variables_Pers"."gRunningSeconds" := "Blender_Variables_Pers"."gRunningSeconds" + 1; + END_IF; + + // Network 10: Running Minutes + + "I_DIRunning_sec" := "Blender_Variables_Pers"."gRunningSeconds"; + "MOD60" := "I_DIRunning_sec" MOD DINT#60; + 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) + "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 + + IF "mRunMin" THEN + "I_DIRunning_min" := "Blender_Variables_Pers"."gRunningMinutes"; + END_IF; + IF "mRunMin" THEN + "MOD60" := "I_DIRunning_min" MOD DINT#60; + END_IF; + IF "MOD60" = DINT#0 THEN + "Blender_Variables_Pers"."gRunningMaintHour" := "Blender_Variables_Pers"."gRunningMaintHour" + 1; + END_IF; + + // Network 12: Running Hours for Maintenance + + "HMI_Variables_Status"."System"."BlendingMaintHour" := "Blender_Variables_Pers"."gRunningMaintHour"; + +END_FUNCTION_BLOCK diff --git a/BlenderRun_ProdTime_simplified_scl_processed.json b/BlenderRun_ProdTime_simplified_scl_processed.json index 7457c46..f14f84b 100644 --- a/BlenderRun_ProdTime_simplified_scl_processed.json +++ b/BlenderRun_ProdTime_simplified_scl_processed.json @@ -584,7 +584,7 @@ }, { "instruction_uid": "30", - "type": "Contact", + "type": "Contact_scl", "inputs": { "operand": { "uid": "22", @@ -593,11 +593,12 @@ "name": "\"gBlenderRinseMode\"" } }, - "outputs": {} + "outputs": {}, + "scl": "// RLO updated by Contact 30: \"gBlenderRinseMode\"" }, { "instruction_uid": "31", - "type": "O", + "type": "O_scl", "inputs": { "in1": { "type": "connection", @@ -612,11 +613,12 @@ "source_pin": "out" } }, - "outputs": {} + "outputs": {}, + "scl": "// Logic O 31: \"gBlenderCIPMode\" OR \"gBlenderRinseMode\"" }, { "instruction_uid": "32", - "type": "Move", + "type": "Move_scl", "inputs": { "in": { "uid": "23", @@ -641,7 +643,8 @@ "name": "\"Blender_Variables_Pers\".\"gProdSec\"" } ] - } + }, + "scl": "IF \"gBlenderCIPMode\" OR \"gBlenderRinseMode\" THEN\n \"Blender_Variables_Pers\".\"gProdSec\" := 0;\nEND_IF;" }, { "instruction_uid": "33", diff --git a/json_to_scl.py b/json_to_scl.py deleted file mode 100644 index c0db9e9..0000000 --- a/json_to_scl.py +++ /dev/null @@ -1,259 +0,0 @@ -import json -import os - -# --- Funciones Procesadoras por Tipo de Instrucción --- -# Cada función recibe el diccionario de la instrucción del JSON. -# Por ahora, solo imprimen información. - -def process_contact(instruction): - """Procesa una instrucción 'Contact'.""" - print(f" [Contact] UID: {instruction['instruction_uid']}") - operand = instruction['inputs'].get('operand', {}) - print(f" - Checks: {operand.get('scope', '?')} UID: {operand.get('uid', '?')}") # Adaptar si 'unknown_access' se resuelve - in_source = instruction['inputs'].get('in', {}) - if in_source.get('type') == 'powerrail': - print(" - Input: Power Rail") - elif in_source.get('type') == 'connection': - print(f" - Input: From instruction {in_source.get('source_instruction_uid', '?')} (Pin: {in_source.get('source_pin', '?')})") - else: - print(f" - Input: {in_source}") - -def process_coil(instruction): - """Procesa una instrucción 'Coil'.""" - print(f" [Coil] UID: {instruction['instruction_uid']}") - operand = instruction['inputs'].get('operand', {}) - print(f" - Assigns to: {operand.get('scope', '?')} UID: {operand.get('uid', '?')}") - in_source = instruction['inputs'].get('in', {}) - if in_source.get('type') == 'connection': - print(f" - Condition from: instruction {in_source.get('source_instruction_uid', '?')} (Pin: {in_source.get('source_pin', '?')})") - else: - print(f" - Condition: {in_source}") - -def process_add(instruction): - """Procesa una instrucción 'Add'.""" - print(f" [Add] UID: {instruction['instruction_uid']}") - in1 = instruction['inputs'].get('in1', {}) - in2 = instruction['inputs'].get('in2', {}) - en = instruction['inputs'].get('en', {}) - outputs = instruction['outputs'].get('out', []) - - print(f" - Input 1: {in1.get('scope', '?')} UID: {in1.get('uid', '?')}") - print(f" - Input 2: {in2.get('scope', '?')} UID: {in2.get('uid', '?')}") - if en.get('type') == 'powerrail': - print(" - Enabled by: Power Rail (Direct)") # Si Add pudiera conectarse directo - elif en.get('type') == 'connection': - print(f" - Enabled by: instruction {en.get('source_instruction_uid', '?')} (Pin: {en.get('source_pin', '?')})") - elif en: # Si 'en' no está presente o no es conexión/powerrail (poco común en Add) - print(f" - Enabled by: {en}") - else: - print(" - Enabled by: Power Rail (Implícito, sin EN)") # Asumir si no hay pin 'en' - - for output in outputs: - print(f" - Output to: {output.get('scope', '?')} UID: {output.get('uid', '?')}") - -def process_move(instruction): - """Procesa una instrucción 'Move'.""" - print(f" [Move] UID: {instruction['instruction_uid']}") - in_val = instruction['inputs'].get('in', {}) - en = instruction['inputs'].get('en', {}) - outputs = instruction['outputs'].get('out1', []) # Asumiendo pin 'out1' para Move - - print(f" - Input Value: {in_val.get('scope', '?')} UID: {in_val.get('uid', '?')}") - if en.get('type') == 'powerrail': - print(" - Enabled by: Power Rail") - elif en.get('type') == 'connection': - print(f" - Enabled by: instruction {en.get('source_instruction_uid', '?')} (Pin: {en.get('source_pin', '?')})") - elif en: - print(f" - Enabled by: {en}") - else: - print(" - Enabled by: Power Rail (Implícito, sin EN)") - - for output in outputs: - print(f" - Output to: {output.get('scope', '?')} UID: {output.get('uid', '?')}") - -def process_eq(instruction): - """Procesa una instrucción 'Eq' (Equal).""" - print(f" [Compare EQ] UID: {instruction['instruction_uid']}") - in1 = instruction['inputs'].get('in1', {}) - in2 = instruction['inputs'].get('in2', {}) - pre = instruction['inputs'].get('pre', {}) # Condición previa (usualmente PowerRail o conexión) - - print(f" - Input 1: {in1.get('scope', '?')} UID: {in1.get('uid', '?')}") - print(f" - Input 2: {in2.get('scope', '?')} UID: {in2.get('uid', '?')}") - if pre.get('type') == 'powerrail': - print(" - Pre-condition: Power Rail") - elif pre.get('type') == 'connection': - print(f" - Pre-condition: instruction {pre.get('source_instruction_uid', '?')} (Pin: {pre.get('source_pin', '?')})") - else: - print(f" - Pre-condition: {pre}") - - # La salida 'out' de Eq usualmente va a otra instrucción (Contact, Coil, Enable pin) - # Lo veremos cuando procesemos la instrucción destino - -def process_mod(instruction): - """Procesa una instrucción 'Mod' (Modulo).""" - print(f" [Modulo] UID: {instruction['instruction_uid']}") - in1 = instruction['inputs'].get('in1', {}) - in2 = instruction['inputs'].get('in2', {}) - en = instruction['inputs'].get('en', {}) - outputs = instruction['outputs'].get('out', []) - eno_outputs = instruction['outputs'].get('eno', []) # Mod también puede tener ENO - - print(f" - Input 1 (Dividend): {in1.get('scope', '?')} UID: {in1.get('uid', '?')}") - print(f" - Input 2 (Divisor): {in2.get('scope', '?')} UID: {in2.get('uid', '?')}") - if en.get('type') == 'powerrail': - print(" - Enabled by: Power Rail") - elif en.get('type') == 'connection': - print(f" - Enabled by: instruction {en.get('source_instruction_uid', '?')} (Pin: {en.get('source_pin', '?')})") - elif en: - print(f" - Enabled by: {en}") - else: - print(" - Enabled by: Power Rail (Implícito, sin EN)") - - for output in outputs: - print(f" - Output (Remainder) to: {output.get('scope', '?')} UID: {output.get('uid', '?')}") - # ENO normalmente se conecta a pines 'en' o 'pre' de la siguiente instrucción - -def process_convert(instruction): - """Procesa una instrucción 'Convert'.""" - print(f" [Convert] UID: {instruction['instruction_uid']}") - in_val = instruction['inputs'].get('in', {}) - en = instruction['inputs'].get('en', {}) - outputs = instruction['outputs'].get('out', []) - # Podríamos extraer los tipos de datos de TemplateValue si estuvieran en el JSON - # template_vals = instruction.get('template_values', {}) - - print(f" - Input Value: {in_val.get('scope', '?')} UID: {in_val.get('uid', '?')}") - if en.get('type') == 'powerrail': - print(" - Enabled by: Power Rail") - elif en.get('type') == 'connection': - print(f" - Enabled by: instruction {en.get('source_instruction_uid', '?')} (Pin: {en.get('source_pin', '?')})") - elif en: - print(f" - Enabled by: {en}") - else: - print(" - Enabled by: Power Rail (Implícito, sin EN)") - - for output in outputs: - print(f" - Output to: {output.get('scope', '?')} UID: {output.get('uid', '?')}") - # print(f" (Expected DestType: {template_vals.get('DestType', '?')})") - - -def process_or(instruction): - """Procesa una instrucción 'O' (OR).""" - # Las instrucciones 'O' en LAD suelen representar la unión de ramas paralelas. - # Este parser simple solo muestra las entradas directas. Reconstruir la lógica OR completa requeriría más análisis. - print(f" [OR Logic] UID: {instruction['instruction_uid']}") - in1 = instruction['inputs'].get('in1', {}) - in2 = instruction['inputs'].get('in2', {}) - # Podría haber in3, in4... si Cardinality > 2 - - if in1.get('type') == 'connection': - print(f" - Input 1 from: instruction {in1.get('source_instruction_uid', '?')} (Pin: {in1.get('source_pin', '?')})") - else: - print(f" - Input 1: {in1}") - if in2.get('type') == 'connection': - print(f" - Input 2 from: instruction {in2.get('source_instruction_uid', '?')} (Pin: {in2.get('source_pin', '?')})") - else: - print(f" - Input 2: {in2}") - - # La salida 'out' de O usualmente va a otra instrucción (Contact, Coil, Enable pin) - -def process_pbox(instruction): - """Procesa una instrucción 'PBox'.""" - # PBox puede ser muchas cosas (Rising Edge, Falling Edge, Set, Reset, etc.) - # Necesitaríamos más información o convenciones para saber qué hace exactamente. - print(f" [PBox - Special?] UID: {instruction['instruction_uid']}") - inputs = instruction.get('inputs', {}) - outputs = instruction.get('outputs', {}) - for pin, source in inputs.items(): - if source.get('type') == 'connection': - print(f" - Input Pin '{pin}' from: instruction {source.get('source_instruction_uid', '?')} (Pin: {source.get('source_pin', '?')})") - elif source.get('type') == 'powerrail': - print(f" - Input Pin '{pin}': Power Rail") - else: - print(f" - Input Pin '{pin}': {source.get('scope', '?')} UID: {source.get('uid', '?')}") - - # La salida de PBox la veremos en el destino - -def process_unknown(instruction): - """Procesa una instrucción de tipo desconocido.""" - print(f" [Unknown Type: {instruction.get('type', 'N/A')}] UID: {instruction['instruction_uid']}") - print(f" - Inputs: {instruction.get('inputs')}") - print(f" - Outputs: {instruction.get('outputs')}") - -# --- Mapeo de Tipos a Funciones --- -instruction_handlers = { - "Contact": process_contact, - "Coil": process_coil, - "Add": process_add, - "Move": process_move, - "Eq": process_eq, - "Mod": process_mod, - "Convert": process_convert, - "O": process_or, # 'O' representa un OR lógico en FlgNet - "PBox": process_pbox, # Tipo genérico, tratar como desconocido por ahora - # Añade más tipos aquí si aparecen -} - -# --- Función Principal de Procesamiento --- - -def process_logic_data(data): - """Itera sobre el JSON cargado y procesa cada instrucción.""" - print("=" * 40) - print(f"Processing Block: {data.get('block_name')} ({data.get('block_number')})") - print(f"Language: {data.get('language')}") - print("-" * 40) - - # Opcional: Imprimir interfaz - print("Interface:") - for section, members in data.get('interface', {}).items(): - if members: - print(f" {section}:") - for member in members: - print(f" - {member['name']} ({member['datatype']})") - print("-" * 40) - - # Procesar Redes - print("Networks:") - for network in data.get('networks', []): - print(f"\nNetwork ID: {network.get('id')} - Title: '{network.get('title', '')}'") - if 'error' in network: - print(f" ERROR en esta red: {network['error']}") - continue - if not network.get('logic'): - print(" (No logic instructions found in JSON for this network)") - continue - - for instruction in network.get('logic', []): - instruction_type = instruction.get('type') - # Obtener el handler adecuado, o el default si no se encuentra - handler = instruction_handlers.get(instruction_type, process_unknown) - try: - handler(instruction) - except Exception as e: - print(f" ERROR procesando instrucción UID {instruction.get('instruction_uid')} (Tipo: {instruction_type}): {e}") - # Considerar imprimir más detalles del error o de la instrucción - # import traceback - # traceback.print_exc() - - -# --- Ejecución --- -if __name__ == "__main__": - json_file = 'BlenderRun_ProdTime_simplified.json' # El archivo generado por el script anterior - - if not os.path.exists(json_file): - print(f"Error: Archivo JSON no encontrado en {json_file}") - print("Asegúrate de haber ejecutado el script de conversión XML a JSON primero.") - else: - try: - with open(json_file, 'r', encoding='utf-8') as f: - logic_data = json.load(f) - - process_logic_data(logic_data) - - except json.JSONDecodeError as e: - print(f"Error: El archivo JSON ({json_file}) no es válido: {e}") - except Exception as e: - print(f"Ocurrió un error inesperado al cargar o procesar el JSON: {e}") - import traceback - traceback.print_exc() \ No newline at end of file diff --git a/to_json.py b/x1_to_json.py similarity index 100% rename from to_json.py rename to x1_to_json.py diff --git a/process.py b/x2_process.py similarity index 96% rename from process.py rename to x2_process.py index 57cec70..2a3e855 100644 --- a/process.py +++ b/x2_process.py @@ -130,32 +130,40 @@ def process_contact(instruction, network_id, scl_map, access_map): # print(f"DEBUG: Intentando procesar CONTACT{' (N)' if is_negated else ''} - UID: {instr_uid} en Red: {network_id}") - in_rlo_scl = get_scl_representation(instruction['inputs'].get('in'), network_id, scl_map, access_map) + # --- INICIO CORRECCIÓN --- + 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 (conectado a powerrail/riel vertical) + # print(f"DEBUG: Asumiendo IN=TRUE para CONTACT UID {instr_uid} (pin 'in' no conectado)") + in_rlo_scl = "TRUE" + else: + # Si hay pin 'in', intentar resolverlo + in_rlo_scl = get_scl_representation(in_input, network_id, scl_map, access_map) + # --- FIN CORRECCIÓN --- + 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: # print(f"DEBUG: Dependencia no resuelta para CONTACT UID: {instr_uid} (in={in_rlo_scl}, op={operand_scl})") return False + # ... (resto de process_contact sin cambios: construir RLO, actualizar scl_map y JSON, retornar True) ... term = f"NOT {operand_scl}" if is_negated else operand_scl - # Asegurarse de que el operando esté entre paréntesis si no es una variable simple if not (term.startswith('"') and term.endswith('"')): if not (term.startswith('(') and term.endswith(')')): term = f"({term})" - new_rlo_scl = "" if in_rlo_scl == "TRUE": new_rlo_scl = term else: - # Poner el RLO anterior entre paréntesis si es necesario if not (in_rlo_scl.startswith('(') and in_rlo_scl.endswith(')')) and 'AND' in in_rlo_scl or 'OR' in in_rlo_scl: 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') scl_map[map_key] = new_rlo_scl @@ -266,7 +274,11 @@ def process_convert(instruction, network_id, scl_map, access_map): # TODO: Añadir lógica de conversión explícita si se extraen tipos de TemplateValue scl_core = f"{target_scl} := {conversion_expr};" - scl_final = scl_core if en_scl == "TRUE" else f"IF {en_scl} THEN\n {scl_core}\nEND_IF;" + if en_scl != "TRUE": + scl_final = f"IF {en_scl} THEN\n {scl_core}\nEND_IF;" + else: + # Si en_scl ES "TRUE", no se necesita IF + scl_final = scl_core instruction['scl'] = scl_final instruction['type'] = instr_type + SCL_SUFFIX @@ -316,7 +328,11 @@ def process_mod(instruction, network_id, scl_map, access_map): 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};" - scl_final = scl_core if en_scl == "TRUE" else f"IF {en_scl} THEN\n {scl_core}\nEND_IF;" + if en_scl != "TRUE": + scl_final = f"IF {en_scl} THEN\n {scl_core}\nEND_IF;" + else: + # Si en_scl ES "TRUE", no se necesita IF + scl_final = scl_core instruction['scl'] = scl_final instruction['type'] = instr_type + SCL_SUFFIX @@ -365,7 +381,11 @@ def process_add(instruction, network_id, scl_map, access_map): 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};" - scl_final = scl_core if en_scl == "TRUE" else f"IF {en_scl} THEN\n {scl_core}\nEND_IF;" + if en_scl != "TRUE": + scl_final = f"IF {en_scl} THEN\n {scl_core}\nEND_IF;" + else: + # Si en_scl ES "TRUE", no se necesita IF + scl_final = scl_core instruction['scl'] = scl_final instruction['type'] = instr_type + SCL_SUFFIX @@ -410,7 +430,11 @@ def process_move(instruction, network_id, scl_map, access_map): return False scl_core = f"{target_scl} := {in_scl};" - scl_final = scl_core if en_scl == "TRUE" else f"IF {en_scl} THEN\n {scl_core}\nEND_IF;" + if en_scl != "TRUE": + scl_final = f"IF {en_scl} THEN\n {scl_core}\nEND_IF;" + else: + # Si en_scl ES "TRUE", no se necesita IF + scl_final = scl_core instruction['scl'] = scl_final instruction['type'] = instr_type + SCL_SUFFIX diff --git a/x3_generate_scl.py b/x3_generate_scl.py new file mode 100644 index 0000000..6b49738 --- /dev/null +++ b/x3_generate_scl.py @@ -0,0 +1,202 @@ +# -*- coding: utf-8 -*- +import json +import os +import re + +# --- Helper Functions --- + +def format_variable_name(name): + """Limpia el nombre de la variable quitando comillas y espacios.""" + if not name: + return "_INVALID_NAME_" + # Quita comillas dobles iniciales/finales + name = name.strip('"') + # Reemplaza comillas dobles internas y puntos por guión bajo + name = name.replace('"."', '_').replace('.', '_') + # Quita comillas restantes (si las hubiera) + name = name.replace('"', '') + # Asegurarse de que no empiece con número (aunque raro con comillas iniciales) + if name and name[0].isdigit(): + name = "_" + name + return name + +def generate_scl(processed_json_filepath, output_scl_filepath): + """Genera un archivo SCL a partir del JSON procesado.""" + + if not os.path.exists(processed_json_filepath): + print(f"Error: Archivo JSON procesado no encontrado en '{processed_json_filepath}'") + return + + print(f"Cargando JSON procesado desde: {processed_json_filepath}") + try: + with open(processed_json_filepath, 'r', encoding='utf-8') as f: + data = json.load(f) + except Exception as e: + print(f"Error al cargar o parsear JSON: {e}") + return + + # --- Extracción de Información del Bloque --- + block_name = data.get('block_name', 'UnknownBlock') + block_number = data.get('block_number') + block_lang = data.get('language', 'LAD') # Lenguaje original + block_comment = data.get('block_comment', '') + + # Limpiar nombre del bloque para usarlo en SCL + scl_block_name = format_variable_name(block_name) + print(f"Generando SCL para el bloque: {scl_block_name} (Original: {block_name})") + + # --- Identificación de Variables Temporales y Estáticas --- + temp_vars = set() + stat_vars = set() # Para flancos, si se implementan completamente + # Usar regex para encontrar variables _temp_... y stat_... + temp_pattern = re.compile(r'"?(_temp_[a-zA-Z0-9_]+)"?') + stat_pattern = re.compile(r'"?(stat_[a-zA-Z0-9_]+)"?') + + for network in data.get('networks', []): + for instruction in network.get('logic', []): + scl_code = instruction.get('scl', '') + if scl_code: + # Buscar temporales en el código SCL generado + found_temps = temp_pattern.findall(scl_code) + for temp_name in found_temps: + temp_vars.add(temp_name) # Añadir al set (evita duplicados) + # Buscar estáticas (para flancos) + found_stats = stat_pattern.findall(scl_code) + for stat_name in found_stats: + stat_vars.add(stat_name) + + print(f"Variables temporales detectadas: {len(temp_vars)}") + # print(f"Variables estáticas detectadas (para flancos): {len(stat_vars)}") + + # --- Construcción del String SCL --- + scl_output = [] + + # Cabecera del Bloque + scl_output.append(f"// Block Name (Original): {block_name}") + if block_number: + scl_output.append(f"// Block Number: {block_number}") + scl_output.append(f"// Original Language: {block_lang}") + if block_comment: + scl_output.append(f"// Block Comment: {block_comment}") + scl_output.append("") + scl_output.append(f"FUNCTION_BLOCK \"{scl_block_name}\"") # Asumir FB por variables Temp/Stat + scl_output.append("{ S7_Optimized_Access := 'TRUE' }") # Opcional, común + scl_output.append("VERSION : 0.1") # Opcional + scl_output.append("") + + # Declaraciones de Interfaz + scl_output.append("VAR_INPUT") + # Iterar sobre data['interface']['Input'] si existe + # for var in data.get('interface', {}).get('Input', []): + # scl_output.append(f" {format_variable_name(var['name'])} : {var['datatype']};") + scl_output.append("END_VAR") + scl_output.append("") + + scl_output.append("VAR_OUTPUT") + # Iterar sobre data['interface']['Output'] si existe + # for var in data.get('interface', {}).get('Output', []): + # scl_output.append(f" {format_variable_name(var['name'])} : {var['datatype']};") + scl_output.append("END_VAR") + scl_output.append("") + + scl_output.append("VAR_IN_OUT") + # Iterar sobre data['interface']['InOut'] si existe + # for var in data.get('interface', {}).get('InOut', []): + # scl_output.append(f" {format_variable_name(var['name'])} : {var['datatype']};") + scl_output.append("END_VAR") + scl_output.append("") + + # Declaraciones Estáticas (para flancos) + if stat_vars: + scl_output.append("VAR_STAT") + # Asumir Bool para flancos, se podría inferir mejor si PBox lo indicara + for var_name in sorted(list(stat_vars)): + scl_output.append(f" \"{var_name}\" : Bool; // Memory for edge detection") + scl_output.append("END_VAR") + scl_output.append("") + + # Declaraciones Temporales + # Incluir las variables de la sección Temp del JSON original + # y las generadas automáticamente (_temp_...) + scl_output.append("VAR_TEMP") + declared_temps = set() + interface_temps = data.get('interface', {}).get('Temp', []) + if interface_temps: + for var in interface_temps: + formatted_name = format_variable_name(var['name']) + # Añadir comillas si el nombre original las tenía o si contiene caracteres especiales + scl_name = f'"{formatted_name}"' if '"' in var['name'] or '.' in var['name'] else formatted_name + scl_output.append(f" {scl_name} : {var['datatype']};") + declared_temps.add(scl_name) # Marcar como declarada + + # Declarar las _temp_ generadas si no estaban ya en la interfaz Temp + if temp_vars: + # Intentar inferir tipo (difícil sin más info), por ahora usar Variant o Bool/DInt + for var_name in sorted(list(temp_vars)): + scl_name = f'"{var_name}"' # Asegurar comillas para _temp_ + if scl_name not in declared_temps: + # Inferencia básica de tipo por nombre de pin (muy heurístico) + inferred_type = "Variant" # Tipo seguro por defecto + if var_name.endswith("_out"): # Salida de bloque lógico/comparación? + inferred_type = "Bool" + elif var_name.endswith("_out_num"): # Salida de bloque numérico? + inferred_type = "DInt" # O Real? O Int? Difícil saber + # Se podría mejorar si los procesadores añadieran info de tipo + scl_output.append(f" {scl_name} : {inferred_type}; // Auto-generated temporary") + declared_temps.add(scl_name) # Marcar como declarada + scl_output.append("END_VAR") + scl_output.append("") + + # Cuerpo del Bloque + scl_output.append("BEGIN") + scl_output.append("") + + # Iterar por redes y lógica + for i, network in enumerate(data.get('networks', [])): + network_title = network.get('title', f'Network {network.get("id")}') + network_comment = network.get('comment', '') + scl_output.append(f" // Network {i+1}: {network_title}") + if network_comment: + # Añadir comentario de red indentado + for line in network_comment.splitlines(): + scl_output.append(f" // {line}") + scl_output.append("") # Línea en blanco antes del código de la red + + network_has_code = False + for instruction in network.get('logic', []): + scl_code = instruction.get('scl') + if scl_code: + network_has_code = True + # Indentar y añadir el código SCL de la instrucción + # Quitar comentarios "// RLO updated..." o "// Comparison..." + # ya que el código SCL resultante es la representación final. + # Mantener comentarios de PBox por claridad. + is_rlo_update_comment = scl_code.strip().startswith("// RLO updated") + is_comparison_comment = scl_code.strip().startswith("// Comparison") + is_logic_o_comment = scl_code.strip().startswith("// Logic O") + + if not (is_rlo_update_comment or is_comparison_comment or is_logic_o_comment) or "PBox" in scl_code : + for line in scl_code.splitlines(): + scl_output.append(f" {line}") # Indentación de 2 espacios + + if network_has_code: + scl_output.append("") # Añadir línea en blanco después del código de la red + + scl_output.append("END_FUNCTION_BLOCK") + + # --- Escritura del Archivo SCL --- + print(f"Escribiendo archivo SCL en: {output_scl_filepath}") + try: + with open(output_scl_filepath, 'w', encoding='utf-8') as f: + for line in scl_output: + f.write(line + '\n') + print("Generación de SCL completada.") + except Exception as e: + print(f"Error al escribir el archivo SCL: {e}") + +# --- Ejecución --- +if __name__ == "__main__": + input_json_file = 'BlenderRun_ProdTime_simplified_scl_processed.json' # Usar el JSON procesado + output_scl_file = input_json_file.replace('_simplified_scl_processed.json', '.scl') # Nombre de salida + + generate_scl(input_json_file, output_scl_file) \ No newline at end of file