Simatic_XML_Parser_to_SCL/x3_generate_scl.py

208 lines
9.0 KiB
Python

# -*- 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)