# -*- 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__": xml_file = "BlenderCtrl__Main.xml" # CAMBIAR AL NUEVO ARCHIVO XML input_json_file = xml_file.replace( ".xml", "_simplified_processed.json" ) # Nombre de salida dinámico output_scl_file = input_json_file.replace( ".json", ".scl" ) # Nombre de salida dinámico generate_scl(input_json_file, output_scl_file)