1310 lines
50 KiB
Python
1310 lines
50 KiB
Python
# -*- 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)
|