# -*- coding: utf-8 -*- import json import os import copy import traceback import re from collections import defaultdict # --- Constantes y Configuración --- SCL_SUFFIX = "_scl" GROUPED_COMMENT = "// Logic included in grouped IF" # Global data variable data = {} # --- Helper Functions --- # Updated get_scl_representation def get_scl_representation(source_info, network_id, scl_map, access_map): """ Obtiene la representación SCL para una fuente de datos (variable, constante, conexión). Handles S5Time constants by converting them to TIME literals. """ if not source_info: return None if isinstance(source_info, list): # Handle OR merges scl_parts = [] all_resolved = True for sub_source in source_info: sub_scl = get_scl_representation( sub_source, network_id, scl_map, access_map ) if sub_scl is None: all_resolved = False break if ( sub_scl in ["TRUE", "FALSE"] or (sub_scl.startswith('"') and sub_scl.endswith('"')) or (sub_scl.startswith("'") and sub_scl.endswith("'")) or ( sub_scl.upper().startswith("T#") ) # Check T# prefix case-insensitive or (sub_scl.upper().startswith("W#")) or (sub_scl.upper().startswith("B#")) or sub_scl.isdigit() or (sub_scl.startswith("-") and sub_scl[1:].isdigit()) or (sub_scl.startswith("(") and sub_scl.endswith(")")) ): scl_parts.append(sub_scl) else: scl_parts.append(f"({sub_scl})") # Parenthesize complex terms in OR 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") return ( format_variable_name(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") uid = source_info.get("uid", "UnknownUID") try: # S5Time conversion (Example: S5T#2S -> T#2s) if ( dtype == "S5TIME" and value and isinstance(value, str) and value.upper().startswith("S5T#") ): time_str = value.upper().split("#")[-1] time_str = time_str.replace("_", "") match = re.match(r"(\d+)(MS|S|M|H)?", time_str, re.IGNORECASE) if match: num_val = match.group(1) unit = (match.group(2) or "S").lower() # Default to seconds return f"T#{num_val}{unit}" else: print( f"Advertencia: Formato S5Time no reconocido '{value}' UID={uid}. Usando T#0s." ) return "T#0s" # Standard constants elif dtype == "BOOL": return str(value).upper() elif dtype in [ "INT", "DINT", "SINT", "USINT", "UINT", "UDINT", "LINT", "ULINT", "WORD", "DWORD", "LWORD", "BYTE", ]: return str(int(value)) elif dtype in ["REAL", "LREAL"]: s_val = str(float(value)) return s_val if "." in s_val or "e" in s_val.lower() else s_val + ".0" elif dtype == "STRING": # Escapar comillas simples dentro del string si es necesario str_val = str(value).replace("'", "''") return f"'{str_val}'" elif dtype == "TIME": return f"T#{str(value)}" elif dtype == "DATE": return f"D#{str(value)}" # Add other types like TOD, DT, DTL if needed else: # Fallback: treat as potentially numeric or string try: return str(int(value)) except ValueError: try: return str(float(value)) except ValueError: escaped_value = str(value).replace("'", "''") return f"'{escaped_value}'" # Treat as string if not numeric except Exception as e: print(f"Advertencia: Error formateando constante {source_info}: {e}") return f"#_ERR_CONST_FORMAT_{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": return f"#_ERR_UNKNOWN_SRC_{source_info.get('uid')}_" else: return f"#_ERR_INVALID_SRC_TYPE_" # format_variable_name - no change needed def format_variable_name(name): """Formats variable names for SCL, preserving quotes for structured names.""" if not name: return "_INVALID_NAME_" if name.startswith('"') and name.endswith('"'): return name prefix = "" if name.startswith("#"): prefix = "#" name = name[1:] if not name: return "_INVALID_NAME_" # Handle case "#" if not re.match(r"^[a-zA-Z_]", name[0]): name = "_" + name name = re.sub(r"[^a-zA-Z0-9_]", "_", name) return prefix + name # generate_temp_var_name - returns base name WITHOUT # def generate_temp_var_name(network_id, instr_uid, pin_name): """Generates a base name for temporary variables (without the leading #).""" net_id_clean = str(network_id).replace("-", "_") instr_uid_clean = str(instr_uid).replace("-", "_") pin_name_clean = str(pin_name).replace("-", "_").lower() return f"_temp_{net_id_clean}_{instr_uid_clean}_{pin_name_clean}" # get_target_scl_name - adds # for temps when needed for SCL usage def get_target_scl_name(instruction, output_pin_name, network_id, default_to_temp=True): """Gets the SCL name for an output target, using temps if necessary. Adds # prefix for temp usage.""" instr_uid = instruction["instruction_uid"] output_pin_data = instruction["outputs"].get(output_pin_name) target_scl_base = 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_name = dest_access.get("name") if target_name: target_scl_base = format_variable_name(target_name) elif default_to_temp: target_scl_base = generate_temp_var_name( network_id, instr_uid, output_pin_name ) elif dest_access.get("type") == "constant": print( f"Advertencia: Instr {instr_uid} escribe en const UID {dest_access.get('uid')}. Usando temp." ) if default_to_temp: target_scl_base = generate_temp_var_name( network_id, instr_uid, output_pin_name ) elif default_to_temp: # Connection or other types target_scl_base = generate_temp_var_name( network_id, instr_uid, output_pin_name ) elif default_to_temp: # No explicit target, use temp if allowed target_scl_base = generate_temp_var_name(network_id, instr_uid, output_pin_name) if target_scl_base is None: return None # Add # prefix for temps if target_scl_base.startswith("_temp_"): return f"#{target_scl_base}" else: return target_scl_base # Standard variable # Timer/Edge name helpers def get_s5_timer_instance_name(network_id, instr_uid, timer_type="Se"): type_prefix = ( "TON" if timer_type.upper() == "TON_S5" else "TONR" if timer_type.upper() == "TONR_S5" else "IEC_TIMER" ) base_name = f"stat_{type_prefix}_{network_id}_{instr_uid}" return format_variable_name(f'"{base_name}"') def get_edge_mem_bit_name(network_id, instr_uid, edge_type="P_TRIG"): type_prefix = ( "ptrig" if edge_type.upper() == "P_TRIG" else "ntrig" if edge_type.upper() == "N_TRIG" else "edge" ) base_name = f"stat_{type_prefix}_mem_{network_id}_{instr_uid}" return format_variable_name(f'"{base_name}"') # --- Procesadores de Instrucciones --- # process_contact, process_eq, process_o, process_convert, process_mod, process_add, process_move - Minor refinements if needed, but largely okay 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 = instruction.get("negated_pins", {}).get("operand", False) in_rlo_scl = ( get_scl_representation( instruction["inputs"].get("in"), network_id, scl_map, access_map ) or "TRUE" ) operand_scl = get_scl_representation( instruction["inputs"].get("operand"), network_id, scl_map, access_map ) if operand_scl is None: return False term = f"(NOT {operand_scl})" if is_negated else operand_scl if ( " " in operand_scl or "AND" in operand_scl or "OR" in operand_scl ) and not is_negated: term = f"({operand_scl})" # Parenthesize operand if complex if is_negated and not operand_scl.startswith("("): term = f"NOT ({operand_scl})" # Ensure NOT applies correctly if in_rlo_scl == "TRUE": new_rlo_scl = term else: in_rlo_formatted = ( f"({in_rlo_scl})" if (" " in in_rlo_scl or "AND" in in_rlo_scl or "OR" in in_rlo_scl) and not (in_rlo_scl.startswith("(") and in_rlo_scl.endswith(")")) else in_rlo_scl ) new_rlo_scl = f"{in_rlo_formatted} AND {term}" scl_map[(network_id, instr_uid, "out")] = new_rlo_scl instruction["scl"] = f"// RLO: {new_rlo_scl}" instruction["type"] = instr_type + SCL_SUFFIX return True # --- Add this function definition back into x2_process.py --- 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_scl = get_scl_representation(instruction["inputs"].get("en"), network_id, scl_map, access_map) or "TRUE" 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 # Wait for operands target_scl = get_target_scl_name(instruction, "out", network_id, default_to_temp=True) # Add usually outputs to 'out' if target_scl is None: instruction["scl"] = f"// ERROR: ADD {instr_uid} sin destino" instruction["type"] += "_error" return True # Parenthesize inputs if they are complex 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};" instruction["scl"] = f"IF {en_scl} THEN\n {scl_core}\nEND_IF;" if en_scl != "TRUE" else scl_core instruction["type"] = instr_type + SCL_SUFFIX scl_map[(network_id, instr_uid, "out")] = target_scl # The result is the value in the target scl_map[(network_id, instr_uid, "eno")] = en_scl return True # --- End of function to add --- 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 = ( get_scl_representation(en_input, network_id, scl_map, access_map) if en_input else "TRUE" ) in1_info = instruction["inputs"].get("in1") in2_info = instruction["inputs"].get("in2") in1_scl = get_scl_representation(in1_info, network_id, scl_map, access_map) in2_scl = get_scl_representation(in2_info, 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: Sin destino MOD {instr_uid}") instruction["scl"] = f"// ERROR: Mod {instr_uid} sin destino" instruction["type"] += "_error" return True # Formatear operandos si son variables op1 = ( format_variable_name(in1_scl) if in1_info and in1_info.get("type") == "variable" else in1_scl ) op2 = ( format_variable_name(in2_scl) if in2_info and in2_info.get("type") == "variable" else in2_scl ) # Añadir paréntesis si es necesario (poco probable tras formatear) op1 = f"({op1})" if " " in op1 and not op1.startswith("(") else op1 op2 = f"({op2})" if " " in op2 and not op2.startswith("(") else op2 scl_core = f"{target_scl} := {op1} MOD {op2};" scl_final = ( f"IF {en_scl} THEN\n {scl_core}\nEND_IF;" if en_scl != "TRUE" else 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 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 = ( get_scl_representation(en_input, network_id, scl_map, access_map) if en_input else "TRUE" ) in_info = instruction["inputs"].get("in") in_scl = get_scl_representation(in_info, network_id, scl_map, access_map) if en_scl is None or in_scl is None: return False # Esperar si dependencias no listas target_scl = get_target_scl_name( instruction, "out", network_id, default_to_temp=True ) if target_scl is None: print(f"Error: Sin destino claro para CONVERT {instr_uid}") instruction["scl"] = f"// ERROR: Convert {instr_uid} sin destino" instruction["type"] += "_error" return True # Procesado con error # Formatear entrada si es variable in_scl_formatted = ( format_variable_name(in_scl) if in_info and in_info.get("type") == "variable" else in_scl ) # Determinar el tipo de destino (simplificado, necesitaría info del XML original) # Asumimos que el tipo está en TemplateValues o inferirlo del nombre/contexto target_type = instruction.get("template_values", {}).get( "destType", "VARIANT" ) # Ejemplo, ajustar según XML real conversion_func = f"{target_type}_TO_" # Necesita el tipo de origen también # Esta parte es compleja sin saber los tipos exactos. Usaremos una conversión genérica o MOVE. # Para una conversión real, necesitaríamos algo como: # conversion_expr = f"CONVERT(IN := {in_scl_formatted}, OUT => {target_scl})" # Sintaxis inventada # O usar funciones específicas: INT_TO_REAL, etc. # Simplificación: Usar asignación directa (MOVE implícito) o función genérica si existe # Asumiremos asignación directa por ahora. conversion_expr = in_scl_formatted scl_core = f"{target_scl} := {conversion_expr};" # Añadir IF EN si es necesario scl_final = ( f"IF {en_scl} THEN\n {scl_core}\nEND_IF;" if en_scl != "TRUE" else 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 # El valor de salida es el contenido del destino map_key_eno = (network_id, instr_uid, "eno") scl_map[map_key_eno] = en_scl # ENO sigue a EN return True def process_eq(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 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 ) pre_scl = ( get_scl_representation( instruction["inputs"].get("pre"), network_id, scl_map, access_map ) or "TRUE" ) 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}" if pre_scl == "TRUE": result_rlo = f"({comparison_scl})" else: pre_formatted = ( f"({pre_scl})" if (" " in pre_scl or "AND" in pre_scl or "OR" in pre_scl) and not (pre_scl.startswith("(") and pre_scl.endswith(")")) else pre_scl ) result_rlo = f"{pre_formatted} AND ({comparison_scl})" scl_map[(network_id, instr_uid, "out")] = result_rlo scl_map[(network_id, instr_uid, "eno")] = pre_scl instruction["scl"] = f"// Comparison Eq {instr_uid}: {comparison_scl}" instruction["type"] = instr_type + SCL_SUFFIX return True def process_o(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 input_pins = sorted([pin for pin in instruction["inputs"] if pin.startswith("in")]) if not input_pins: instruction["scl"] = f"// ERROR: O {instr_uid} sin pines inX" instruction["type"] += "_error" return True scl_parts = [] all_resolved = True for pin in input_pins: in_scl = get_scl_representation( instruction["inputs"][pin], network_id, scl_map, access_map ) if in_scl is None: all_resolved = False break term = ( f"({in_scl})" if (" " in in_scl or "AND" in in_scl or "OR" 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") ) scl_map[(network_id, instr_uid, "out")] = result_scl instruction["scl"] = f"// Logic O {instr_uid}: {result_scl}" instruction["type"] = instr_type + SCL_SUFFIX 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 en_scl = ( get_scl_representation( instruction["inputs"].get("en"), network_id, scl_map, access_map ) or "TRUE" ) in_scl = get_scl_representation( instruction["inputs"].get("in"), network_id, scl_map, access_map ) if 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: target_scl = get_target_scl_name( instruction, "out", network_id, default_to_temp=True ) if target_scl is None: instruction["scl"] = f"// ERROR: MOVE {instr_uid} sin destino" instruction["type"] += "_error" return True scl_core = f"{target_scl} := {in_scl};" instruction["scl"] = ( f"IF {en_scl} THEN\n {scl_core}\nEND_IF;" if en_scl != "TRUE" else scl_core ) instruction["type"] = instr_type + SCL_SUFFIX scl_map[(network_id, instr_uid, "out")] = target_scl scl_map[(network_id, instr_uid, "out1")] = target_scl scl_map[(network_id, instr_uid, "eno")] = en_scl return True # --- Processors for Coils (Coil, R, S, SR) --- def process_coil(instruction, network_id, scl_map, access_map): # Standard Coil (=) 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: return False if not (operand_info and operand_info.get("type") == "variable"): instruction["scl"] = f"// ERROR: Coil {instr_uid} operando no valido" instruction["type"] += "_error" return True instruction["scl"] = ( f"{operand_scl} := {in_rlo_scl};" # Direct assignment based on RLO ) instruction["type"] = instr_type + SCL_SUFFIX return True def process_r(instruction, network_id, scl_map, access_map): # Reset Coil (R) 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: return False if not (operand_info and operand_info.get("type") == "variable"): instruction["scl"] = f"// ERROR: R Coil {instr_uid} operando no valido" instruction["type"] += "_error" return True scl_core = f"{operand_scl} := FALSE;" instruction["scl"] = ( f"IF {in_rlo_scl} THEN\n {scl_core}\nEND_IF;" if in_rlo_scl != "FALSE" else f"// R Coil {instr_uid} disabled" ) instruction["type"] = instr_type + SCL_SUFFIX return True def process_s(instruction, network_id, scl_map, access_map): # Set Coil (S) 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: return False if not (operand_info and operand_info.get("type") == "variable"): instruction["scl"] = f"// ERROR: S Coil {instr_uid} operando no valido" instruction["type"] += "_error" return True scl_core = f"{operand_scl} := TRUE;" instruction["scl"] = ( f"IF {in_rlo_scl} THEN\n {scl_core}\nEND_IF;" if in_rlo_scl != "FALSE" else f"// S Coil {instr_uid} disabled" ) instruction["type"] = instr_type + SCL_SUFFIX return True def process_sr( instruction, network_id, scl_map, access_map ): # Set-Dominant SR FlipFlop (mapped from SdCoil) # Assumes separate S and R coils target the same operand in the original LAD. # This processor handles the SET part. process_r handles the RESET part. return process_s( instruction, network_id, scl_map, access_map ) # Treat SR essentially as S coil # --- Processors for Edges (P_TRIG, N_TRIG) --- def process_p_trig( instruction, network_id, scl_map, access_map, network_logic_list=None ): instr_uid = instruction["instruction_uid"] instr_type = instruction["type"] if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type: return False clk_scl = ( get_scl_representation( instruction["inputs"].get("in"), network_id, scl_map, access_map ) or "FALSE" ) # Default clk to FALSE if not connected mem_bit_info = instruction["inputs"].get("bit") if clk_scl is None: return False if not (mem_bit_info and mem_bit_info.get("type") == "variable"): stat_mem_bit_scl = get_edge_mem_bit_name(network_id, instr_uid, "P_TRIG") print( f"Advertencia: P_TRIG {instr_uid} sin 'bit' variable conectado. Usando STAT {stat_mem_bit_scl}." ) else: stat_mem_bit_scl_raw = get_scl_representation( mem_bit_info, network_id, scl_map, access_map ) if stat_mem_bit_scl_raw is None: return False stat_mem_bit_scl = format_variable_name(stat_mem_bit_scl_raw) result_scl = f"({clk_scl}) AND (NOT {stat_mem_bit_scl})" scl_update_mem = f"{stat_mem_bit_scl} := {clk_scl};" scl_map[(network_id, instr_uid, "out")] = result_scl instruction["scl"] = ( f"{scl_update_mem} // P_TRIG Edge Memory Update for UID {instr_uid}" ) # Output logic isn't placed here, it's propagated via scl_map instruction["type"] = instr_type + SCL_SUFFIX return True def process_n_trig( instruction, network_id, scl_map, access_map, network_logic_list=None ): instr_uid = instruction["instruction_uid"] instr_type = instruction["type"] if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type: return False clk_scl = ( get_scl_representation( instruction["inputs"].get("in"), network_id, scl_map, access_map ) or "FALSE" ) mem_bit_info = instruction["inputs"].get("bit") if clk_scl is None: return False if not (mem_bit_info and mem_bit_info.get("type") == "variable"): stat_mem_bit_scl = get_edge_mem_bit_name(network_id, instr_uid, "N_TRIG") print( f"Advertencia: N_TRIG {instr_uid} sin 'bit' variable conectado. Usando STAT {stat_mem_bit_scl}." ) else: stat_mem_bit_scl_raw = get_scl_representation( mem_bit_info, network_id, scl_map, access_map ) if stat_mem_bit_scl_raw is None: return False stat_mem_bit_scl = format_variable_name(stat_mem_bit_scl_raw) result_scl = f"(NOT ({clk_scl})) AND {stat_mem_bit_scl}" scl_update_mem = f"{stat_mem_bit_scl} := {clk_scl};" scl_map[(network_id, instr_uid, "out")] = result_scl instruction["scl"] = ( f"{scl_update_mem} // N_TRIG Edge Memory Update for UID {instr_uid}" ) instruction["type"] = instr_type + SCL_SUFFIX return True # --- Processors for Timers (TON_S5 -> TON, TONR_S5 -> TONR) --- def process_ton_s5(instruction, network_id, scl_map, access_map): # Mapped from Se instr_uid = instruction["instruction_uid"] instr_type = instruction["type"] # Type is TON_S5 if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type: return False start_scl = get_scl_representation( instruction["inputs"].get("s"), network_id, scl_map, access_map ) pt_scl = get_scl_representation( instruction["inputs"].get("tv"), network_id, scl_map, access_map ) reset_scl = ( get_scl_representation( instruction["inputs"].get("r"), network_id, scl_map, access_map ) or "FALSE" ) # Assume FALSE if R not connected if start_scl is None or pt_scl is None: return False timer_instance_scl = get_s5_timer_instance_name(network_id, instr_uid, "TON_S5") scl_call = f"{timer_instance_scl}(IN := {start_scl}, PT := {pt_scl}, RESET := {reset_scl}); // TON from S5 Se" scl_map[(network_id, instr_uid, "q")] = ( f"{timer_instance_scl}.Q" # Map S5 'q' to TON 'Q' ) scl_map[(network_id, instr_uid, "et")] = ( f"{timer_instance_scl}.ET" # Map ET if needed ) instruction["scl"] = scl_call instruction["type"] = instr_type + SCL_SUFFIX return True def process_tonr_s5(instruction, network_id, scl_map, access_map): # Mapped from Sd instr_uid = instruction["instruction_uid"] instr_type = instruction["type"] # Type is TONR_S5 if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type: return False start_scl = get_scl_representation( instruction["inputs"].get("s"), network_id, scl_map, access_map ) pt_scl = get_scl_representation( instruction["inputs"].get("tv"), network_id, scl_map, access_map ) reset_scl = ( get_scl_representation( instruction["inputs"].get("r"), network_id, scl_map, access_map ) or "FALSE" ) # Reset essential for TONR if start_scl is None or pt_scl is None: return False timer_instance_scl = get_s5_timer_instance_name(network_id, instr_uid, "TONR_S5") scl_call = f"{timer_instance_scl}(IN := {start_scl}, PT := {pt_scl}, RESET := {reset_scl}); // TONR from S5 Sd" scl_map[(network_id, instr_uid, "q")] = f"{timer_instance_scl}.Q" scl_map[(network_id, instr_uid, "et")] = f"{timer_instance_scl}.ET" instruction["scl"] = scl_call instruction["type"] = instr_type + SCL_SUFFIX return True # --- Processor for BLKMOV --- def process_blkmov(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_scl = ( get_scl_representation( instruction["inputs"].get("en"), network_id, scl_map, access_map ) or "TRUE" ) src_scl = get_scl_representation( instruction["inputs"].get("SRCBLK"), network_id, scl_map, access_map ) # DSTBLK is parsed as input by corrected x1 dst_scl = get_scl_representation( instruction["inputs"].get("DSTBLK"), network_id, scl_map, access_map ) ret_val_target = get_target_scl_name( instruction, "RET_VAL", network_id, default_to_temp=True ) # Name includes # if temp if src_scl is None or dst_scl is None: return False print( f"Advertencia: BLKMOV {instr_uid} traducido como asignación simple (:=). Verificar compatibilidad de tipos/tamaños entre {src_scl} y {dst_scl}." ) scl_core = f"{dst_scl} := {src_scl};" if ret_val_target: scl_core += ( f"\n {ret_val_target} := 0; // Assuming success" # Assign 0=OK to RET_VAL ) instruction["scl"] = ( f"IF {en_scl} THEN\n {scl_core}\nEND_IF;" if en_scl != "TRUE" else scl_core ) instruction["type"] = instr_type + SCL_SUFFIX scl_map[(network_id, instr_uid, "eno")] = en_scl if ret_val_target: scl_map[(network_id, instr_uid, "RET_VAL")] = ret_val_target return True # --- Processor for Call --- def process_call(instruction, network_id, scl_map, access_map): instr_uid = instruction["instruction_uid"] instr_type = instruction.get("type", "") if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type: return False block_name = instruction.get("block_name", f"UnknownCall_{instr_uid}") block_type = instruction.get("block_type") instance_db_name = instruction.get("instance_db") # Provided by x1 en_scl = ( get_scl_representation( instruction["inputs"].get("en"), network_id, scl_map, access_map ) or "TRUE" ) block_name_scl = format_variable_name(block_name) instance_db_scl = ( format_variable_name(instance_db_name) if instance_db_name else None ) # DEBUG PRINT for FB calls: # if block_type == "FB": print(f"DEBUG process_call FB: UID={instr_uid}, Instance DB from JSON: {instance_db_name}, Formatted: {instance_db_scl}") scl_call_params = [] output_assignments = [] inout_assignments = [] all_params_resolved = True processed_inputs = {"en"} # Inputs for pin_name, source_info in instruction.get("inputs", {}).items(): if pin_name not in processed_inputs: param_scl = get_scl_representation( source_info, network_id, scl_map, access_map ) if param_scl is None: all_params_resolved = False break param_name_scl = format_variable_name(pin_name) scl_call_params.append(f"{param_name_scl} := {param_scl}") processed_inputs.add(pin_name) if not all_params_resolved: return False # Outputs (=> assignments) for pin_name, dest_list in instruction.get("outputs", {}).items(): if pin_name == "eno": continue if isinstance(dest_list, list): for dest_info in dest_list: if dest_info.get("type") == "variable": dest_var_name = dest_info.get("name") if dest_var_name: dest_scl = format_variable_name(dest_var_name) param_name_scl = format_variable_name(pin_name) # Assume Output (=>). Could be InOut (:=) - needs interface info to be sure. output_assignments.append(f"{param_name_scl} => {dest_scl}") # else: print(f"Warn: Call {instr_uid} output '{pin_name}' to unnamed var UID {dest_info.get('uid')}") all_params_str = ", ".join(scl_call_params + output_assignments + inout_assignments) scl_call_body = "" if block_type == "FB": if not instance_db_scl: # Check the formatted name error_msg = f"// ERROR: FB Call '{block_name_scl}' UID {instr_uid} sin DB de instancia válido." print(error_msg.replace("// ", "")) instruction["scl"] = error_msg instruction["type"] = "Call_FB_error" return True scl_call_body = f"{instance_db_scl}({all_params_str});" elif block_type == "FC": if output_assignments: print( f"Advertencia: FC Call {instr_uid} '{block_name_scl}' usa sintaxis '=>' para salidas. Verificar." ) scl_call_body = f"{block_name_scl}({all_params_str});" else: error_msg = ( f"// ERROR: Tipo bloque no soportado Call UID {instr_uid}: {block_type}" ) print(error_msg.replace("// ", "")) instruction["scl"] = error_msg instruction["type"] = f"Call_{block_type}_error" return True instruction["scl"] = ( f"IF {en_scl} THEN\n {scl_call_body}\nEND_IF;" if en_scl != "TRUE" else scl_call_body ) instruction["type"] = ( f"Call_{block_type}_scl" if "_error" not in instruction["type"] else instruction["type"] ) scl_map[(network_id, instr_uid, "eno")] = en_scl if block_type == "FB" and instance_db_scl: for pin_name in instruction.get("outputs", {}).keys(): if pin_name != "eno": scl_map[(network_id, instr_uid, pin_name)] = ( f"{instance_db_scl}.{format_variable_name(pin_name)}" ) return True # process_group_ifs - No significant changes needed from previous version def process_group_ifs(instruction, network_id, scl_map, access_map): """ Groups instructions enabled by the same condition into a single IF block. """ instr_uid = instruction["instruction_uid"] instr_type = instruction["type"] instr_type_original = instr_type.replace("_scl", "").replace("_error", "") made_change = False if ( not instr_type.endswith("_scl") or "_error" in instr_type or instruction.get("grouped", False) ): return False is_condition_generator = instr_type_original in [ "Contact", "O", "Eq", "Ne", "Gt", "Lt", "Ge", "Le", "P_TRIG", "N_TRIG", "And", "Xor", ] if not is_condition_generator: return False current_scl = instruction.get("scl", "").strip() if ( current_scl.startswith("IF") and "END_IF;" in current_scl and "\n" in current_scl ) or current_scl.startswith("//"): return False map_key_out = (network_id, instr_uid, "out") condition_scl = scl_map.get(map_key_out) if condition_scl is None or condition_scl in ["TRUE", "FALSE"]: return False grouped_instructions_cores = [] consumer_instr_list = [] network_logic = next( (net["logic"] for net in data["networks"] if net["id"] == network_id), [] ) if not network_logic: return False groupable_types_original = [ "Move", "Add", "Sub", "Mul", "Div", "Mod", "Convert", "Call_FC", "Call_FB", "Coil", "R", "S", "SR", "TON_S5", "TONR_S5", "BLKMOV", "P_TRIG", "N_TRIG", ] for consumer_instr in network_logic: consumer_uid = consumer_instr["instruction_uid"] if consumer_instr.get("grouped", False) or consumer_uid == instr_uid: continue consumer_type = consumer_instr.get("type", "") consumer_type_original = consumer_type.replace("_scl", "").replace("_error", "") consumer_scl = consumer_instr.get("scl", "") is_enabled_by_us = False # Determine enabling pin based on consumer type enabling_pin = "en" # Default for functional blocks if consumer_type_original in ["Coil", "R", "S", "SR"]: enabling_pin = "in" elif consumer_type_original in ["TON_S5", "TONR_S5"]: enabling_pin = "s" elif consumer_type_original in ["P_TRIG", "N_TRIG"]: enabling_pin = "in" # Clock input consumer_input = consumer_instr.get("inputs", {}).get(enabling_pin) if ( isinstance(consumer_input, dict) and consumer_input.get("type") == "connection" and consumer_input.get("source_instruction_uid") == instr_uid and consumer_input.get("source_pin") == "out" ): is_enabled_by_us = True if ( is_enabled_by_us and consumer_type.endswith("_scl") and consumer_type_original in groupable_types_original and consumer_scl ): core_scl = None if consumer_scl.strip().startswith("IF"): match = re.search( r"IF\s+.*\s+THEN\s*(.*?)\s*END_IF;", consumer_scl, re.DOTALL | re.IGNORECASE, ) if match: core_scl = match.group(1).strip() elif not consumer_scl.strip().startswith("//"): core_scl = consumer_scl.strip() if core_scl: grouped_instructions_cores.append(core_scl) consumer_instr_list.append(consumer_instr) if len(grouped_instructions_cores) > 1: # print(f"INFO: Agrupando {len(grouped_instructions_cores)} instrucciones bajo condición de {instr_type_original} UID {instr_uid}") scl_grouped = [f"IF {condition_scl} THEN"] for core_line in grouped_instructions_cores: indented_core = "\n".join( [f" {line.strip()}" for line in core_line.splitlines() if line.strip()] ) scl_grouped.append(indented_core) scl_grouped.append("END_IF;") instruction["scl"] = "\n".join(scl_grouped) for consumer_instr in consumer_instr_list: consumer_instr["scl"] = f"{GROUPED_COMMENT} (by UID {instr_uid})" consumer_instr["grouped"] = True made_change = True return made_change # --- Bucle Principal de Procesamiento --- def process_json_to_scl(json_filepath): if not os.path.exists(json_filepath): print(f"Error: JSON no encontrado: {json_filepath}") return print(f"Cargando JSON desde: {json_filepath}") try: with open(json_filepath, "r", encoding="utf-8") as f: global data data = json.load(f) except Exception as e: print(f"Error al cargar JSON: {e}") traceback.print_exc() return network_access_maps = {} for network in data.get("networks", []): net_id = network["id"] current_access_map = {} network_logic = network.get("logic", []) for instr in network_logic: for source in list(instr.get("inputs", {}).values()) + [ item for sublist in instr.get("outputs", {}).values() for item in sublist ]: 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("type") in ["variable", "constant"] ): current_access_map[src["uid"]] = src network_access_maps[net_id] = current_access_map scl_map = {} max_passes = 50 passes = 0 processing_complete = False # Define processor functions and map base_processors_list = [ process_contact, process_eq, process_o, process_p_trig, process_n_trig, # Edges before coils process_ton_s5, process_tonr_s5, # Timers before coils process_move, process_add, process_mod, process_convert, process_blkmov, process_coil, process_r, process_s, process_sr, # Coils process_call, # Calls last ] grouping_processor = process_group_ifs processor_map = {} for func in base_processors_list: match = re.match(r"process_(\w+)", func.__name__) if match: type_name = match.group(1).lower() processor_map[type_name] = func if type_name == "call": processor_map["call_fc"] = func processor_map["call_fb"] = func # Map mapped types directly if type_name == "ton_s5": processor_map["ton_s5"] = func if type_name == "tonr_s5": processor_map["tonr_s5"] = func if type_name == "p_trig": processor_map["p_trig"] = func if type_name == "n_trig": processor_map["n_trig"] = func if type_name == "r": processor_map["r"] = func if type_name == "s": processor_map["s"] = func if type_name == "sr": processor_map["sr"] = func print("\n--- Iniciando Bucle de Procesamiento Iterativo ---") while passes < max_passes and not processing_complete: passes += 1 made_change_in_base_pass = False made_change_in_group_pass = False print(f"\n--- Pase {passes} ---") num_processed_this_pass = 0 num_grouped_this_pass = 0 # --- FASE 1: Base Processors --- 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: instr_uid = instruction.get("instruction_uid") instr_type = instruction.get("type", "Unknown") if ( instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type or instruction.get("grouped", False) ): continue lookup_key = ( instr_type.lower() ) # Use the mapped type from x1 (e.g., ton_s5) if instr_type == "Call": # Still need to handle FC/FB for Call type block_type = instruction.get("block_type", "").upper() if block_type == "FC": lookup_key = "call_fc" elif block_type == "FB": lookup_key = "call_fb" func_to_call = processor_map.get(lookup_key) if func_to_call: try: # Pass network_logic only if needed (currently not used by edge detectors) changed = func_to_call( instruction, network_id, scl_map, access_map ) if changed: made_change_in_base_pass = True num_processed_this_pass += 1 except Exception as e: print( f"ERROR(Base) al procesar {instr_type} UID {instr_uid}: {e}" ) traceback.print_exc() instruction["scl"] = f"// ERROR en procesador base: {e}" instruction["type"] = instr_type + "_error" made_change_in_base_pass = True # else: # Debug missing # if not lookup_key.endswith(SCL_SUFFIX) and lookup_key not in ['unknown_access', 'unknown', 'contact']: # Example ignore list # print(f"DEBUG: No processor for type '{instr_type}' (lookup: '{lookup_key}') UID {instr_uid}") # --- FASE 2: Grouping Processor --- if made_change_in_base_pass or passes == 1: 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: if instruction["type"].endswith("_scl") and not instruction.get( "grouped", False ): try: if grouping_processor( instruction, network_id, scl_map, access_map ): made_change_in_group_pass = True num_grouped_this_pass += 1 except Exception as e: print( f"ERROR(Group) UID {instruction.get('instruction_uid')}: {e}" ) traceback.print_exc() # --- Check Completion --- if not made_change_in_base_pass and not made_change_in_group_pass: print(f"\n--- No cambios en pase {passes}. Proceso iterativo COMPLETO. ---") processing_complete = True else: print( f"--- Fin Pase {passes}: {num_processed_this_pass} procesados, {num_grouped_this_pass} agrupados. Continuando..." ) if passes == max_passes and not processing_complete: print(f"\n--- ADVERTENCIA: Límite {max_passes} pases alcanzado. ---") # --- Final Verification --- (No changes needed) print("\n--- Verificación Final de Instrucciones No Procesadas ---") unprocessed_count = 0 unprocessed_details = [] for network in data.get("networks", []): network_id = network.get("id", "N/A") network_title = network.get("title", f"Red {network_id}") for instruction in network.get("logic", []): instr_uid = instruction.get("instruction_uid", "N/A") instr_type = instruction.get("type", "N/A") is_grouped = instruction.get("grouped", False) if ( not instr_type.endswith(SCL_SUFFIX) and "_error" not in instr_type and not is_grouped ): unprocessed_count += 1 orig_type = instruction.get( "original_type", instr_type ) # Show original type if available unprocessed_details.append( f" - Red '{network_title}' (ID: {network_id}), UID: {instr_uid}, Tipo: '{orig_type}' (Mapped: '{instr_type}')" ) if unprocessed_count > 0: print(f"ADVERTENCIA: {unprocessed_count} instrucciones no procesadas:") [print(d) for d in unprocessed_details] print(">>> Verificar dependencias o procesadores faltantes.") else: print( "INFO: Todas las instrucciones fueron procesadas, marcadas como error o agrupadas." ) # --- Guardar JSON Final --- (No changes needed) output_filename = json_filepath.replace( "_simplified.json", "_simplified_processed.json" ) print(f"\nGuardando JSON procesado en: {output_filename}") try: 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 Crítico al guardar JSON: {e}") traceback.print_exc() # --- Ejecución --- if __name__ == "__main__": xml_filename_base = "BlenderCtrl__Main" input_json_file = f"{xml_filename_base}_simplified.json" if not os.path.exists(input_json_file): print(f"Error Fatal: Archivo JSON '{input_json_file}' no existe.") print(f"Ejecuta 'x1_to_json.py' sobre '{xml_filename_base}.xml' primero.") else: process_json_to_scl(input_json_file)