Se actualizaron los archivos de configuración JSON para reflejar nuevos parámetros de directorios de exportación, incluyendo "aml_exp_directory" y "resultados_exp_directory". Además, se realizaron mejoras en el script `x1_export_CAx.py`, optimizando la gestión de directorios de salida y la detección de archivos de proyecto TIA. Se ajustaron los mensajes de depuración y se mejoró la estructura del código para mayor claridad.

This commit is contained in:
Miguel 2025-07-14 10:29:47 +02:00
parent 2297e217c7
commit c8141deb63
14 changed files with 1391 additions and 905 deletions

View File

@ -0,0 +1,4 @@
---
alwaysApply: true
---
Puedes usar .doc\MemoriaDeEvolucion.md para obtener un contexto de las ultimas modificaciones y conceptos sobre este proyecto. Quisiera que con los conocimientos importantes y desiciones importantes adquiridas en cada modificacion los agregues a MemoriaDeEvolucion.md manteniendo el estilo que ya tenemos de texto simple sin demasiado codigo y una semantica resumida.

View File

@ -0,0 +1,69 @@
Definiciones de Frontend en .doc\backend_setup.md
### Flujo de trabajo:
Se usa [x1] para exportar los datos de configuración desde el proyecto de Tia Portal, incluidos los IO configurados en el hardware. Esto genera un archivo AML.
Con [x2] se procesa el archivo AML y se convierte en markdown. Si hay un solo PLC se genera el archivo [Hardware.md]. **Además genera un archivo Excel con los IOs por nodos del PLC.**
3. Se deben procesar los IO **desde el esquema eléctrico y agregarlos** a [Hardware.md].
Se debe exportar todos los tags como un archivo excel desde el Tia Portal.
Con [x3] se convierte y se filtran los tags según los path definidos en [io_paths_config] y se genera un archivo "Master IO Tags.md".
[x4] Genera el prompt a usar con Claude usando MCP para que pueda acceder a los archivos originales y evitar tener que hacer uploads.
Una vez que se genera el archivo procesado por el LLM se puede usar
[x5] que convierte las adaptaciones a un archivo que luego se puede importar en Tia Portal
8. Importar en Tia Portal el archivo [PLCTags_Updated.xlsx]
**Nuevo flujo alternativo con Excel:**
Después del paso 2, se puede usar :
[x7] para: Modificar manualmente el archivo Excel de IOs generado- Aplicar los cambios de vuelta al archivo AML original Generar un AML actualizado con las modificaciones
### Cambios recientes:
**Automatización de selección de proyecto TIA Portal (x1_export_CAx.py):**
- Eliminada la interfaz gráfica tkinter para selección manual de archivos
- El script ahora lee automáticamente la ruta del proyecto desde la configuración `siemens_tia_project` en work_dir.json
- Nueva función `find_project_file_in_dir()` que busca automáticamente archivos .ap18/.ap19/.ap20 en el directorio configurado
- Validación automática que verifica que exista exactamente un archivo de proyecto en el directorio especificado
- Mejora en la experiencia de usuario al eliminar pasos manuales de selección de archivos
**Directorio de exportación CAx configurable (x1_export_CAx.py):**
- La ruta de exportación de archivos CAx ahora es configurable.
- El script utiliza el valor `aml_exp_directory` de la configuración `level2` en `work_dir.json`.
- La nueva ruta de salida se construye como `working_directory` / `aml_exp_directory`.
- Todos los archivos generados (AML, Markdown y log) se guardan en este directorio, manteniendo los artefactos de exportación organizados.
**Directorio de E/S configurable para el procesamiento CAx (x2_process_CAx.py):**
- El script ahora utiliza el directorio `aml_exp_directory` de `work_dir.json` para la entrada y salida de archivos.
- Se ha unificado el comportamiento con `x1_export_CAx.py`, esperando el archivo AML de entrada en `working_directory` / `aml_exp_directory`.
- Todos los archivos generados (JSON, Markdown, Excel) se guardan en este mismo directorio, manteniendo la consistencia del flujo de trabajo.
- Se mantiene el fallback al directorio `.debug` para la ejecución independiente del script.
**Selección automática de archivo AML (x2_process_CAx.py):**
- El script ahora busca automáticamente un único archivo `.aml` en el directorio `aml_exp_directory`.
- Si se encuentra un solo archivo, se utiliza directamente, eliminando la necesidad del diálogo de selección manual.
- Si se encuentran cero o múltiples archivos `.aml`, el script recurre al diálogo de selección manual para que el usuario resuelva la ambigüedad.
- Esta mejora agiliza el flujo de trabajo al automatizar la selección de archivos en el caso más común.
**Directorio de resultados configurable (x1 y x2):**
- Se ha agregado el parámetro `resultados_exp_directory` a la configuración `level2` en `work_dir.json`.
- `x1_export_CAx.py` ahora guarda la tabla de resumen (`*CAx_Summary.md`) en el subdirectorio `working_directory` / `resultados_exp_directory`.
- `x2_process_CAx.py` guarda todos los archivos de resultados finales (tablas Markdown y reportes Excel) en `working_directory` / `resultados_exp_directory`, anidados en carpetas por PLC.
- Los archivos intermedios (JSON, logs, debug files) permanecen en `aml_exp_directory`, separando los artefactos de compilación de los resultados finales.
**Corrección de errores de exportación en TIA Portal (x1_export_CAx.py):**
- Se ha solucionado un error que ocurría cuando el archivo AML a exportar ya existía. El script ahora elimina los archivos `.aml` y `.log` existentes antes de la exportación para permitir la sobrescritura.
- Se ha corregido el manejo de excepciones al eliminar un bloque `except` que intentaba capturar una excepción inexistente (`TiaException`), lo que causaba un error secundario y ocultaba el problema original.
**Flujo de trabajo de E/S con Excel mejorado (x3_excel_to_md.py):**
- **Directorios configurables**: El script ahora utiliza `tags_exp_directory` (de `level3`) para buscar archivos Excel y `resultados_exp_directory` (de `level2`) para guardar el archivo Markdown resultante. Ambos se leen desde `work_dir.json`.
- **Selección automática de archivo**: Si solo existe un archivo `.xlsx` en `tags_exp_directory`, el script lo utiliza automáticamente, eliminando la necesidad de selección manual.
- **Fallback a diálogo manual**: Si se encuentran cero o múltiples archivos Excel, el script muestra un diálogo para que el usuario seleccione el archivo correcto, manteniendo la flexibilidad.
- **Consistencia del proyecto**: Esta actualización alinea el comportamiento de `x3_excel_to_md.py` con los scripts `x1` y `x2`, creando un flujo de trabajo más coherente y automatizado.
**Configuración dinámica de rutas en el generador de prompts (x4_prompt_generator.py):**
- Las rutas de los archivos de entrada (`Master IO Tags.md` y `Hardware.md`) ahora se construyen dinámicamente utilizando `resultados_exp_directory` de la configuración `level2`, alineándose con el resto de los scripts.
- La configuración de las rutas base de Obsidian (`ObsideanDir`, `ObsideanProjectsBase`) se ha movido al `level3` para una mejor organización de la configuración.
- El prompt generado refleja automáticamente estas rutas configurables, asegurando que las herramientas de IA siempre utilicen las ubicaciones correctas de los archivos.

View File

@ -0,0 +1,410 @@
# Guía de Configuración para Scripts Backend
## Introducción
Esta guía explica cómo usar la función `load_configuration()` para cargar parámetros desde un archivo `script_config.json` en los scripts del backend.
## 1. Configuración del Script
Para que tus scripts puedan encontrar los módulos del proyecto, necesitas añadir el directorio raíz al path de Python.
### Path Setup e Importación
Coloca este código al inicio de tu script:
```python
import os
import sys
# Añadir el directorio raíz al sys.path
# El número de `os.path.dirname()` depende de la profundidad del script.
# Para /backend/script_groups/grupo/script.py, son 4.
script_root = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
)
sys.path.append(script_root)
# Importar la función
from backend.script_utils import load_configuration
```
## 2. Cargar la Configuración
La función `load_configuration()` busca y carga un archivo llamado `script_config.json` que debe estar en el **mismo directorio** que el script que la ejecuta. Devuelve un diccionario con todo el contenido del JSON.
### Ejemplo de Uso
```python
def main():
# Cargar configuraciones del archivo script_config.json
configs = load_configuration()
# Es buena práctica verificar si la configuración se cargó
if not configs:
print("Error: No se pudo cargar la configuración. Saliendo.")
return
# Acceder a los parámetros usando .get() para evitar errores
working_directory = configs.get("working_directory", "")
level1_config = configs.get("level1", {})
level2_config = configs.get("level2", {})
level3_config = configs.get("level3", {})
# Ejemplo de uso de un parámetro específico con valor por defecto
scl_output_dir = level2_config.get("scl_output_dir", "scl_output")
xref_output_dir = level2_config.get("xref_output_dir", "xref_output")
print(f"Directorio de trabajo: {working_directory}")
print(f"Directorio de salida SCL: {scl_output_dir}")
if __name__ == "__main__":
main()
```
## 3. Archivo `script_config.json`
Este archivo contiene los parámetros de tu script. La estructura interna del JSON, como el uso de `"level1"`, `"level2"`, etc., es una convención para organizar los parámetros. `load_configuration()` simplemente lee el archivo y devuelve su contenido.
**Ejemplo de `script_config.json`:**
```json
{
"working_directory": "/ruta/al/directorio/de/trabajo",
"level1": {
"parametro_global_1": "valor1"
},
"level2": {
"scl_output_dir": "scl_output",
"xref_output_dir": "xref_output"
},
"level3": {
"parametro_especifico_1": true
}
}
```
## 4. Manejo de Errores
`load_configuration()` está diseñada para ser robusta:
- Si `script_config.json` no se encuentra, retorna un diccionario vacío `{}`.
- Si el JSON es inválido, imprime un error y también retorna `{}`.
**Siempre** comprueba si el diccionario devuelto está vacío para manejar estos casos de forma segura en tu script.
## 5. Jerarquía de Archivos de Configuración
El sistema utiliza un modelo de configuración en cascada para gestionar los parámetros de los scripts. Esta jerarquía permite establecer configuraciones a nivel global, de grupo y de trabajo. Antes de la ejecución, el launcher lee estos archivos, los combina y genera un único `script_config.json` en la carpeta del script. La función `load_configuration()` es la que finalmente lee este archivo consolidado.
A continuación se describe la finalidad y ubicación de cada archivo clave.
### Archivos de Valores (Parámetros)
Contienen los datos y variables que utilizará el script. La configuración se superpone en el siguiente orden: `Nivel 1 < Nivel 2 < Nivel 3`.
- **`data.json` (Nivel 1 - Global)**
- **Ubicación:** `data/data.json`
- **Utilidad:** Almacena variables globales disponibles para todos los scripts. Ideal para parámetros generales.
- **Acceso:** Sus datos se cargan en la clave `"level1"` del diccionario `configs`.
- **`script_config.json` (Nivel 2 - Grupo)**
- **Ubicación:** En la raíz de cada directorio de grupo (ej: `backend/script_groups/MiGrupo/script_config.json`).
- **Utilidad:** Define parámetros compartidos por todos los scripts de un grupo.
- **Acceso:** Sus datos se cargan en la clave `"level2"`.
- **`work_dir.json` (Nivel 3 - Directorio de Trabajo)**
- **Ubicación:** Dentro del directorio de trabajo que el script va a procesar.
- **Utilidad:** Contiene parámetros para una ejecución específica. Es el nivel más específico y sobrescribe los anteriores.
- **Acceso:** Sus datos se cargan en la clave `"level3"`.
### Archivos de Esquema (Definiciones de Estructura)
No contienen valores, sino que describen la estructura y tipos de datos que los archivos de valores deben tener. Son usados por el launcher para validación y para generar interfaces de configuración.
- **`esquema_group.json`**
- **Ubicación:** Raíz del directorio del grupo.
- **Utilidad:** Define la estructura del `script_config.json` del grupo.
- **`esquema_work.json`**
- **Ubicación:** Raíz del directorio del grupo.
- **Utilidad:** Define la estructura del `work_dir.json`.
## 6. Documentación de Scripts para el Launcher
El sistema de launcher utiliza archivos JSON para mostrar información sobre los grupos de scripts y scripts individuales en la interfaz web.
### Archivo description.json (Descripción del Grupo)
Ubicación: En el directorio raíz del grupo de scripts.
```json
{
"name": "Nombre del Grupo",
"description": "Descripción del propósito y funcionalidad del grupo",
"version": "1.0",
"author": "Nombre del Autor"
}
```
### Archivo scripts_description.json (Descripción de Scripts)
Ubicación: En el directorio raíz del grupo de scripts.
```json
{
"nombre_script.py": {
"display_name": "Nombre para mostrar en la UI",
"short_description": "Descripción breve del script",
"long_description": "Descripción detallada con explicación completa de funcionalidad, pasos que ejecuta, y contexto de uso",
"hidden": false
},
"script_interno.py": {
"display_name": "Script Interno",
"short_description": "Script de uso interno",
"long_description": "",
"hidden": true
}
}
```
### Propiedades Importantes
- **hidden**: `true` oculta el script del launcher (útil para scripts auxiliares)
- **display_name**: Nombre amigable que aparece en la interfaz
- **short_description**: Se muestra en la lista de scripts
- **long_description**: Se muestra al expandir detalles del script
### Ejemplo Práctico
Para un grupo "XML Parser to SCL":
**description.json:**
```json
{
"name": "Siemens-Tia : 03 : Procesador de XML LAD-SCL-AWL",
"description": "Scripts que procesan archivos XML exportados de TIA, convirtiendo LAD a SCL",
"version": "1.0",
"author": "Miguel"
}
```
**scripts_description.json:**
```json
{
"x0_main.py": {
"display_name": "1: Procesar Exportación XML completa",
"short_description": "Conversor principal de LAD/FUP XML a SCL",
"long_description": "Script orquestador que procesa todos los archivos XML...",
"hidden": false
},
"x1_to_json.py": {
"display_name": "x1_to_json",
"short_description": "Converter XML interno",
"long_description": "",
"hidden": true
}
}
```
## 7. Requisitos de Codificación de Salida (stdout)
Toda la salida estándar (`stdout`) generada por los scripts (por ejemplo, mediante la función `print()`) es capturada en tiempo real y mostrada en el panel de logs del frontend.
Para garantizar que el texto se muestre correctamente y evitar caracteres corruptos (mojibake), **la salida de los scripts debe tener codificación UTF-8**.
### Configuración Automática
El sistema está diseñado para facilitar esto. Al ejecutar un script, el entorno se configura automáticamente para que la salida de Python sea UTF-8. Específicamente, se establece la variable de entorno `PYTHONIOENCODING=utf-8`.
Gracias a esto, en la mayoría de los casos, **no necesitas hacer nada especial**. Simplemente usa `print()` y la codificación será la correcta.
### Casos Especiales y Solución de Problemas
Pueden surgir problemas si tu script lee datos de fuentes externas (como archivos) que tienen una codificación diferente. Si imprimes contenido directamente sin decodificarlo primero, podrías enviar bytes no válidos a la salida.
La regla es: **decodifica los datos de entrada a un string de Python lo antes posible, y deja que `print()` se encargue de la codificación de salida.**
**Ejemplo de cómo manejar un archivo con codificación `latin-1`:**
```python
# INCORRECTO: Si el archivo no es UTF-8, esto puede enviar bytes inválidos.
# El launcher intentará decodificarlos como UTF-8 y podría fallar o mostrar basura.
try:
with open('mi_archivo_legacy.txt', 'rb') as f:
print(f.read())
except Exception as e:
print(f"Esto probablemente falle o muestre texto corrupto: {e}")
# CORRECTO: Leer el archivo especificando su codificación para decodificarlo a un string.
# Una vez que es un string de Python, `print` lo manejará correctamente.
try:
with open('mi_archivo_legacy.txt', 'r', encoding='latin-1') as f:
contenido = f.read()
# Ahora `contenido` es un string de Python.
# print() lo codificará a UTF-8 automáticamente gracias a la configuración del entorno.
print(contenido)
except FileNotFoundError:
print("El archivo 'mi_archivo_legacy.txt' no fue encontrado para el ejemplo.")
except Exception as e:
print(f"Error al procesar el archivo: {e}")
```
## 8. Uso de Servicios Compartidos
El proyecto ofrece una serie de servicios reutilizables en el directorio `services/` para tareas comunes como la manipulación de archivos Excel, detección de idioma o traducción.
Para utilizar estos servicios en tu script, asegúrate de que el directorio raíz del proyecto esté en el `sys.path`, como se explica en la sección 1 de esta guía.
### 8.1 Servicio de Excel (`ExcelService`)
El `ExcelService` (`services/excel/excel_service.py`) facilita la lectura y escritura de archivos Excel, con manejo de reintentos (por si el archivo está abierto) y opciones de formato.
**Ejemplo de importación y uso:**
```python
# Asegúrate de tener el path raíz configurado
# ... (código de configuración de sys.path)
from services.excel.excel_service import ExcelService
def main():
excel_service = ExcelService()
# Leer un archivo Excel
try:
df = excel_service.read_excel("mi_archivo_de_entrada.xlsx")
print("Datos cargados exitosamente.")
# ... procesar el DataFrame ...
# Guardar el DataFrame con formato personalizado
format_options = {
'freeze_row': 2,
'header_color': 'E6E6E6'
}
excel_service.save_excel(
df,
"mi_archivo_de_salida.xlsx",
sheet_name="Resultados",
format_options=format_options
)
print("Archivo guardado con éxito.")
except Exception as e:
print(f"Ocurrió un error al manejar el archivo Excel: {e}")
if __name__ == "__main__":
main()
```
### 8.2 Servicios de Lenguaje
Los servicios de lenguaje (`services/language/`) permiten detectar el idioma de un texto.
**Ejemplo de importación y uso:**
```python
# Asegúrate de tener el path raíz configurado
# ... (código de configuración de sys.path)
from services.language.language_factory import LanguageFactory
from services.language.language_utils import LanguageUtils
def main():
# Crear el servicio de detección de idioma
allowed_languages = LanguageUtils.get_available_languages()
detector = LanguageFactory.create_service("langid", allowed_languages=allowed_languages)
# Detectar idioma de un texto
text = "Este es un texto de ejemplo en español."
lang, confidence = detector.detect_language(text)
print(f"Texto: '{text}'")
print(f"Idioma detectado: {LanguageUtils.get_language_name(lang)} (código: {lang})")
print(f"Confianza: {confidence:.2f}")
if __name__ == "__main__":
main()
```
### 8.3 Servicios de LLM (Modelos de Lenguaje Grandes)
El proyecto integra una fábrica de servicios (`LLMFactory`) para interactuar con diferentes Modelos de Lenguaje Grandes (LLMs). Esto permite a los scripts aprovechar la IA generativa para tareas como el análisis de código, la generación de descripciones semánticas, etc.
#### 8.3.1 Configuración de API Keys
La mayoría de los servicios de LLM requieren una clave de API para funcionar. El sistema gestiona esto de forma centralizada a través de variables de entorno.
1. **Crear el archivo `.env`**: En el **directorio raíz del proyecto**, crea un archivo llamado `.env` (si aún no existe).
2. **Añadir las API Keys**: Abre el archivo `.env` y añade las claves para los servicios que planeas utilizar. El sistema cargará estas variables automáticamente al inicio.
```env
# Ejemplo de contenido para el archivo .env
# (Solo necesitas añadir las claves de los servicios que vayas a usar)
OPENAI_API_KEY="sk-..."
GROQ_API_KEY="gsk_..."
CLAUDE_API_KEY="sk-ant-..."
GEMINI_API_KEY="AIzaSy..."
GROK_API_KEY="TU_API_KEY_DE_GROK"
```
**Nota**: El servicio `ollama` se ejecuta localmente y no requiere una clave de API.
#### 8.3.2 Ejemplo de importación y uso
El siguiente ejemplo muestra cómo un script puede cargar su configuración, inicializar un servicio de LLM y usarlo para generar texto. Este patrón es similar al utilizado en `x3_generate_semantic_descriptions.py`.
```python
# Asegúrate de tener el path raíz configurado
# ... (código de configuración de sys.path)
from services.llm.llm_factory import LLMFactory
from backend.script_utils import load_configuration
def main():
# Cargar la configuración del script, que puede incluir qué LLM usar
configs = load_configuration()
llm_configs = configs.get("llm", {})
# Obtener el tipo de servicio y otros parámetros del config
# Por defecto, usamos 'groq' si no se especifica
service_type = llm_configs.get("service", "groq")
print(f"🤖 Inicializando servicio LLM: {service_type}")
# Crear una instancia del servicio usando la fábrica
# La fábrica se encarga de pasar las API keys desde las variables de entorno
llm_service = LLMFactory.create_service(service_type, **llm_configs)
if not llm_service:
print(f"❌ Error: No se pudo crear el servicio LLM '{service_type}'. Abortando.")
return
# Usar el servicio para generar texto
try:
prompt = "Explica la computación cuántica en una sola frase."
print(f"Enviando prompt: '{prompt}'")
description = llm_service.generate_text(prompt)
print("\nRespuesta del LLM:")
print(description)
except Exception as e:
print(f"Ocurrió un error al contactar al servicio LLM: {e}")
if __name__ == "__main__":
main()
```
#### 8.3.3 Servicios Disponibles
La `LLMFactory` soporta los siguientes tipos de servicio (`service_type`):
- `openai`
- `groq`
- `claude`
- `gemini`
- `grok`
- `ollama` (para ejecución local)

View File

@ -1,4 +1,4 @@
{ {
"ObsideanDir": "C:\\Users\\migue\\OneDrive\\Miguel\\Obsidean\\Trabajo\\VM", "aml_exp_directory": "aml",
"ObsideanProjectsBase": "\\04-SIDEL" "resultados_exp_directory": "Resultados"
} }

View File

@ -1,18 +1,17 @@
{ {
"type": "object", "type": "object",
"properties": { "properties": {
"ObsideanDir": { "aml_exp_directory": {
"type": "string", "type": "string",
"title": "Directorio de Vault de Obsidean", "title": "Directorio de exportación AML",
"description": "Directorio de Vault de Obsidean", "description": "",
"format": "directory", "default": "aml"
"default": "C:\\Users\\migue\\OneDrive\\Miguel\\Obsidean\\Trabajo\\VM"
}, },
"ObsideanProjectsBase": { "resultados_exp_directory": {
"type": "string", "type": "string",
"title": "Subdirectorio", "title": "Subdirectorio donde se colocan las tablas",
"description": "Subdirectorio de los proyectos actuales en el Vault de Obsidean", "description": "Subdirectorio donde se colocan las tablas",
"default": "\\04-SIDEL" "default": "Resultados"
} }
} }
} }

View File

@ -7,17 +7,30 @@
"description": "", "description": "",
"format": "directory" "format": "directory"
}, },
"siemens_tia_project": {
"type": "string",
"title": "Proyecto Tia Portal",
"description": "Donde esta el archivo *.ap18 - *.ap19 - *.ap20",
"format": "directory"
},
"tags_exp_directory": { "tags_exp_directory": {
"type": "string", "type": "string",
"title": "Directorio con el archivo Excel de Tags exportado de Tia", "title": "Directorio con el archivo Excel de Tags exportado de Tia",
"description": "", "description": "",
"format": "directory" "format": "directory"
}, },
"siemens_tia_project": { "ObsideanDir": {
"type": "string", "type": "string",
"title": "Proyecto Tia Portal", "title": "Directorio de Vault de Obsidean",
"description": "Donde esta el archivo *.ap18 - *.ap19 - *.ap20", "description": "Directorio de Vault de Obsidean",
"format": "directory" "format": "directory",
"default": "C:\\Users\\migue\\OneDrive\\Miguel\\Obsidean\\Trabajo\\VM"
},
"ObsideanProjectsBase": {
"type": "string",
"title": "Subdirectorio",
"description": "Subdirectorio de los proyectos actuales en el Vault de Obsidean",
"default": "\\04-SIDEL"
} }
} }
} }

View File

@ -4,9 +4,15 @@
"model": "gpt-3.5-turbo" "model": "gpt-3.5-turbo"
}, },
"level2": { "level2": {
"ObsideanDir": "C:\\Users\\migue\\OneDrive\\Miguel\\Obsidean\\Trabajo\\VM", "aml_exp_directory": "aml",
"ObsideanProjectsBase": "\\04-SIDEL" "resultados_exp_directory": "Resultados"
}, },
"level3": {}, "level3": {
"working_directory": "C:\\Trabajo\\SIDEL\\13 - E5.007560 - Modifica O&U - SAE235\\Reporte\\IOTags" "ObsideanDir": "C:\\Users\\migue\\OneDrive\\Miguel\\Obsidean\\Trabajo\\VM",
"ObsideanProjectsBase": "\\04-SIDEL",
"siemens_exp_directory": "C:\\Trabajo\\SIDEL\\13 - E5.007560 - Modifica O&U - SAE235\\Reporte\\ExportTia",
"siemens_tia_project": "C:/Trabajo/SIDEL/13 - E5.007560 - Modifica O&U - SAE235/InLavoro/PLC/SSAE0235/_NEW/SAE235_v0.4",
"tags_exp_directory": "C:\\Trabajo\\SIDEL\\13 - E5.007560 - Modifica O&U - SAE235\\Reporte\\IOTags"
},
"working_directory": "C:\\Trabajo\\SIDEL\\13 - E5.007560 - Modifica O&U - SAE235\\Reporte\\Analisis\\Siemens"
} }

View File

@ -2,8 +2,6 @@
export_CAx_from_tia : Script que exporta los datos CAx de un proyecto de TIA Portal y genera un resumen en Markdown. export_CAx_from_tia : Script que exporta los datos CAx de un proyecto de TIA Portal y genera un resumen en Markdown.
""" """
import tkinter as tk
from tkinter import filedialog
import os import os
import sys import sys
import traceback import traceback
@ -18,11 +16,7 @@ from backend.script_utils import load_configuration
# --- Configuration --- # --- Configuration ---
# Supported TIA Portal versions mapping (extension -> version) # Supported TIA Portal versions mapping (extension -> version)
SUPPORTED_TIA_VERSIONS = { SUPPORTED_TIA_VERSIONS = {".ap18": "18.0", ".ap19": "19.0", ".ap20": "20.0"}
".ap18": "18.0",
".ap19": "19.0",
".ap20": "20.0"
}
# --- TIA Scripting Import Handling --- # --- TIA Scripting Import Handling ---
# (Same import handling as the previous script) # (Same import handling as the previous script)
@ -45,18 +39,25 @@ except Exception as e:
# --- Functions --- # --- Functions ---
def get_supported_filetypes(): def find_project_file_in_dir(directory):
"""Returns the supported file types for TIA Portal projects.""" """Finds a TIA project file in the given directory."""
filetypes = [] project_files = []
for ext, version in SUPPORTED_TIA_VERSIONS.items(): supported_extensions = SUPPORTED_TIA_VERSIONS.keys()
version_major = version.split('.')[0] for ext in supported_extensions:
filetypes.append((f"TIA Portal V{version_major} Projects", f"*{ext}")) project_files.extend(list(Path(directory).glob(f"*{ext}")))
# Add option to show all supported files if len(project_files) == 1:
all_extensions = " ".join([f"*{ext}" for ext in SUPPORTED_TIA_VERSIONS.keys()]) return project_files[0]
filetypes.insert(0, ("All TIA Portal Projects", all_extensions)) elif len(project_files) == 0:
print(f"ERROR: No TIA Portal project file found in {directory}.")
return filetypes print(f"Supported extensions: {list(supported_extensions)}")
return None
else:
print(f"ERROR: Multiple TIA Portal project files found in {directory}:")
for f in project_files:
print(f" - {f}")
print("Please ensure only one project file exists in the directory.")
return None
def detect_tia_version(project_file_path): def detect_tia_version(project_file_path):
@ -66,44 +67,19 @@ def detect_tia_version(project_file_path):
if file_extension in SUPPORTED_TIA_VERSIONS: if file_extension in SUPPORTED_TIA_VERSIONS:
detected_version = SUPPORTED_TIA_VERSIONS[file_extension] detected_version = SUPPORTED_TIA_VERSIONS[file_extension]
print(f"Detected TIA Portal version: {detected_version} (from extension {file_extension})") print(
f"Detected TIA Portal version: {detected_version} (from extension {file_extension})"
)
return detected_version return detected_version
else: else:
print(f"WARNING: Unrecognized file extension '{file_extension}'. Supported extensions: {list(SUPPORTED_TIA_VERSIONS.keys())}") print(
f"WARNING: Unrecognized file extension '{file_extension}'. Supported extensions: {list(SUPPORTED_TIA_VERSIONS.keys())}"
)
# Default to version 18.0 for backward compatibility # Default to version 18.0 for backward compatibility
print("Defaulting to TIA Portal V18.0") print("Defaulting to TIA Portal V18.0")
return "18.0" return "18.0"
def select_project_file():
"""Opens a dialog to select a TIA Portal project file."""
root = tk.Tk()
root.withdraw()
file_path = filedialog.askopenfilename(
title="Select TIA Portal Project File",
filetypes=get_supported_filetypes(),
)
root.destroy()
if not file_path:
print("No project file selected. Exiting.")
sys.exit(0)
return file_path
def select_output_directory():
"""Opens a dialog to select the output directory."""
root = tk.Tk()
root.withdraw()
dir_path = filedialog.askdirectory(
title="Select Output Directory for AML and MD files"
)
root.destroy()
if not dir_path:
print("No output directory selected. Exiting.")
sys.exit(0)
return dir_path
def find_elements(element, path): def find_elements(element, path):
"""Helper to find elements using namespaces commonly found in AML.""" """Helper to find elements using namespaces commonly found in AML."""
# AutomationML namespaces often vary slightly or might be default # AutomationML namespaces often vary slightly or might be default
@ -253,7 +229,14 @@ def parse_aml_to_markdown(aml_file_path, md_file_path):
if __name__ == "__main__": if __name__ == "__main__":
configs = load_configuration() configs = load_configuration()
# Get parameters from configuration file
level3_configs = configs.get("level3", {})
level2_configs = configs.get("level2", {})
siemens_tia_project_dir = level3_configs.get("siemens_tia_project")
working_directory = configs.get("working_directory") working_directory = configs.get("working_directory")
aml_exp_directory = level2_configs.get("aml_exp_directory")
resultados_exp_directory = level2_configs.get("resultados_exp_directory")
print("--- TIA Portal Project CAx Exporter and Analyzer ---") print("--- TIA Portal Project CAx Exporter and Analyzer ---")
@ -263,14 +246,33 @@ if __name__ == "__main__":
print("Please configure the working directory using the main application.") print("Please configure the working directory using the main application.")
sys.exit(1) sys.exit(1)
# 1. Select Project File, Output Directory comes from config if not aml_exp_directory:
project_file = select_project_file() print("ERROR: aml_exp_directory not set in level2 configuration.")
output_dir = Path( sys.exit(1)
working_directory
) # Use working directory from config, ensure it's a Path object if not resultados_exp_directory:
print("ERROR: resultados_exp_directory not set in level2 configuration.")
sys.exit(1)
# Validate TIA project directory from config
if not siemens_tia_project_dir or not os.path.isdir(siemens_tia_project_dir):
print("ERROR: TIA project directory is not configured or invalid.")
print(
'Please set the "siemens_tia_project" path in your configuration (work_dir.json).'
)
sys.exit(1)
# 1. Find Project File in the configured directory
project_file = find_project_file_in_dir(siemens_tia_project_dir)
if not project_file:
sys.exit(1) # Error message is printed inside the function
aml_output_dir = Path(working_directory) / aml_exp_directory
md_output_dir = Path(working_directory) / resultados_exp_directory
print(f"\nSelected Project: {project_file}") print(f"\nSelected Project: {project_file}")
print(f"Using Output Directory (Working Directory): {output_dir}") print(f"Using AML Output Directory: {aml_output_dir}")
print(f"Using Results Output Directory: {md_output_dir}")
# 2. Detect TIA Portal version from project file # 2. Detect TIA Portal version from project file
tia_version = detect_tia_version(project_file) tia_version = detect_tia_version(project_file)
@ -278,10 +280,10 @@ if __name__ == "__main__":
# Define output file names using Path object # Define output file names using Path object
project_path = Path(project_file) project_path = Path(project_file)
project_base_name = project_path.stem # Get filename without extension project_base_name = project_path.stem # Get filename without extension
aml_file = output_dir / f"{project_base_name}_CAx_Export.aml" aml_file = aml_output_dir / f"{project_base_name}_CAx_Export.aml"
md_file = output_dir / f"{project_base_name}_CAx_Summary.md" md_file = md_output_dir / f"{project_base_name}_CAx_Summary.md"
log_file = ( log_file = (
output_dir / f"{project_base_name}_CAx_Export.log" aml_output_dir / f"{project_base_name}_CAx_Export.log"
) # Log file for the export process ) # Log file for the export process
print(f"Will export CAx data to: {aml_file}") print(f"Will export CAx data to: {aml_file}")
@ -317,8 +319,17 @@ if __name__ == "__main__":
# 5. Export CAx Data (Project Level) # 5. Export CAx Data (Project Level)
print(f"Exporting CAx data for the project to {aml_file}...") print(f"Exporting CAx data for the project to {aml_file}...")
# Ensure output directory exists (Path.mkdir handles this implicitly if needed later, but good practice) # Ensure output directories exist
output_dir.mkdir(parents=True, exist_ok=True) aml_output_dir.mkdir(parents=True, exist_ok=True)
md_output_dir.mkdir(parents=True, exist_ok=True)
# Delete existing files to allow overwrite by TIA Portal
if aml_file.exists():
print(f"Deleting existing AML file to allow overwrite: {aml_file}")
aml_file.unlink()
if log_file.exists():
print(f"Deleting existing log file to allow overwrite: {log_file}")
log_file.unlink()
# Pass paths as strings to the TIA function # Pass paths as strings to the TIA function
export_result = project_object.export_cax_data( export_result = project_object.export_cax_data(
@ -337,9 +348,6 @@ if __name__ == "__main__":
f"# Error\n\nCAx data export failed. Check log file: {log_file}" f"# Error\n\nCAx data export failed. Check log file: {log_file}"
) )
except ts.TiaException as tia_ex:
print(f"\nTIA Portal Openness Error: {tia_ex}")
traceback.print_exc()
except FileNotFoundError: except FileNotFoundError:
print(f"\nERROR: Project file not found at {project_file}") print(f"\nERROR: Project file not found at {project_file}")
except Exception as e: except Exception as e:

File diff suppressed because it is too large Load Diff

View File

@ -67,7 +67,7 @@ def load_path_config():
# Si el archivo existe, cargarlo # Si el archivo existe, cargarlo
if os.path.exists(json_config_path): if os.path.exists(json_config_path):
try: try:
with open(json_config_path, 'r', encoding='utf-8') as f: with open(json_config_path, "r", encoding="utf-8") as f:
config = json.load(f) config = json.load(f)
print(f"Configuración de paths cargada desde: {json_config_path}") print(f"Configuración de paths cargada desde: {json_config_path}")
return config return config
@ -81,33 +81,33 @@ def load_path_config():
{ {
"path": "Inputs", "path": "Inputs",
"type": "Input", "type": "Input",
"no_used_path": "IO Not in Hardware\\InputsMaster" "no_used_path": "IO Not in Hardware\\InputsMaster",
}, },
{ {
"path": "Outputs", "path": "Outputs",
"type": "Output", "type": "Output",
"no_used_path": "IO Not in Hardware\\OutputsMaster" "no_used_path": "IO Not in Hardware\\OutputsMaster",
}, },
{ {
"path": "OutputsFesto", "path": "OutputsFesto",
"type": "Output", "type": "Output",
"no_used_path": "IO Not in Hardware\\OutputsMaster" "no_used_path": "IO Not in Hardware\\OutputsMaster",
}, },
{ {
"path": "IO Not in Hardware\\InputsMaster", "path": "IO Not in Hardware\\InputsMaster",
"type": "Input", "type": "Input",
"no_used_path": "IO Not in Hardware\\InputsMaster" "no_used_path": "IO Not in Hardware\\InputsMaster",
}, },
{ {
"path": "IO Not in Hardware\\OutputsMaster", "path": "IO Not in Hardware\\OutputsMaster",
"type": "Output", "type": "Output",
"no_used_path": "IO Not in Hardware\\OutputsMaster" "no_used_path": "IO Not in Hardware\\OutputsMaster",
} },
] ]
} }
try: try:
with open(json_config_path, 'w', encoding='utf-8') as f: with open(json_config_path, "w", encoding="utf-8") as f:
json.dump(default_config, f, indent=2) json.dump(default_config, f, indent=2)
print(f"Archivo de configuración creado: {json_config_path}") print(f"Archivo de configuración creado: {json_config_path}")
return default_config return default_config
@ -118,19 +118,20 @@ def load_path_config():
def convert_excel_to_markdown_tables(): def convert_excel_to_markdown_tables():
""" """
Busca un archivo Excel en el working_directory o solicita al usuario seleccionarlo, Busca un archivo Excel en el directorio configurado, lo procesa y genera
filtra las entradas según los paths configurados en JSON, un archivo Markdown con tablas filtradas en el directorio de resultados.
y genera un archivo Markdown con tablas.
""" """
try: try:
configs = load_configuration() configs = load_configuration()
working_directory = configs.get("working_directory") working_directory = configs.get("working_directory")
if not working_directory: level2_configs = configs.get("level2", {})
print("Error: 'working_directory' no se encontró en la configuración.") level3_configs = configs.get("level3", {})
return tags_exp_directory = level3_configs.get("tags_exp_directory", ".")
if not os.path.isdir(working_directory): resultados_exp_directory = level2_configs.get("resultados_exp_directory", ".")
if not working_directory or not os.path.isdir(working_directory):
print( print(
f"Error: El directorio de trabajo '{working_directory}' no existe o no es un directorio." f"Error: El directorio de trabajo '{working_directory}' no es válido."
) )
return return
except Exception as e: except Exception as e:
@ -140,38 +141,48 @@ def convert_excel_to_markdown_tables():
working_directory_abs = os.path.abspath(working_directory) working_directory_abs = os.path.abspath(working_directory)
print(f"Usando directorio de trabajo: {working_directory_abs}") print(f"Usando directorio de trabajo: {working_directory_abs}")
# Cargar la configuración de paths
path_config = load_path_config() path_config = load_path_config()
if not path_config: if not path_config:
print("Error: No se pudo cargar la configuración de paths.") print("Error: No se pudo cargar la configuración de paths.")
return return
# Verificar si existe el archivo PLCTags.xlsx en el directorio de trabajo tags_exp_dir_abs = os.path.join(working_directory_abs, tags_exp_directory)
default_excel_path = os.path.join(working_directory_abs, "PLCTags.xlsx") os.makedirs(tags_exp_dir_abs, exist_ok=True)
print(f"Buscando archivos Excel en: {tags_exp_dir_abs}")
if os.path.exists(default_excel_path): excel_files = [
excel_file_path = default_excel_path f for f in os.listdir(tags_exp_dir_abs) if f.lower().endswith(".xlsx")
print(f"Usando archivo Excel predeterminado: {excel_file_path}") ]
excel_file_path = ""
if len(excel_files) == 1:
excel_file_path = os.path.join(tags_exp_dir_abs, excel_files[0])
print(f"Archivo Excel encontrado automáticamente: {excel_file_path}")
else: else:
# Solicitar al usuario que seleccione el archivo Excel if len(excel_files) == 0:
root = tk.Tk() print(f"No se encontraron archivos Excel en '{tags_exp_dir_abs}'.")
root.withdraw() # Ocultar ventana principal else:
print(
f"Se encontraron múltiples archivos Excel en '{tags_exp_dir_abs}'. Por favor seleccione uno."
)
print("Archivo PLCTags.xlsx no encontrado. Seleccione el archivo Excel exportado de TIA Portal:") root = tk.Tk()
root.withdraw()
excel_file_path = filedialog.askopenfilename( excel_file_path = filedialog.askopenfilename(
title="Seleccione el archivo Excel exportado de TIA Portal", title="Seleccione el archivo Excel exportado de TIA Portal",
filetypes=[("Excel files", "*.xlsx"), ("All files", "*.*")], filetypes=[("Excel files", "*.xlsx"), ("All files", "*.*")],
initialdir=working_directory_abs initialdir=tags_exp_dir_abs,
) )
if not excel_file_path: if not excel_file_path:
print("No se seleccionó ningún archivo Excel. Saliendo...") print("No se seleccionó ningún archivo Excel. Saliendo...")
return return
print(f"Procesando archivo Excel: {excel_file_path}...") print(f"Procesando archivo Excel: {excel_file_path}...")
output_dir_abs = os.path.join(working_directory_abs, resultados_exp_directory)
os.makedirs(output_dir_abs, exist_ok=True)
output_md_filename = "Master IO Tags.md" output_md_filename = "Master IO Tags.md"
output_md_filepath_abs = os.path.join(working_directory_abs, output_md_filename) output_md_filepath_abs = os.path.join(output_dir_abs, output_md_filename)
markdown_content = [] markdown_content = []
@ -207,9 +218,13 @@ def convert_excel_to_markdown_tables():
excel_col_comment, excel_col_comment,
] ]
missing_cols = [col for col in excel_required_cols if col not in excel_data.columns] missing_cols = [
col for col in excel_required_cols if col not in excel_data.columns
]
if missing_cols: if missing_cols:
print(f"Error: Columnas faltantes en el archivo Excel: {', '.join(missing_cols)}") print(
f"Error: Columnas faltantes en el archivo Excel: {', '.join(missing_cols)}"
)
return return
# Organizar entradas por path y crear tablas para cada tipo # Organizar entradas por path y crear tablas para cada tipo
@ -240,8 +255,12 @@ def convert_excel_to_markdown_tables():
tag_type_for_md = io_type tag_type_for_md = io_type
master_tag_cell = f"{master_tag:<{col_widths['Master Tag']}.{col_widths['Master Tag']}}" master_tag_cell = f"{master_tag:<{col_widths['Master Tag']}.{col_widths['Master Tag']}}"
type_cell = f"{tag_type_for_md:<{col_widths['Type']}.{col_widths['Type']}}" type_cell = (
data_type_cell = f"{data_type:<{col_widths['Data Type']}.{col_widths['Data Type']}}" f"{tag_type_for_md:<{col_widths['Type']}.{col_widths['Type']}}"
)
data_type_cell = (
f"{data_type:<{col_widths['Data Type']}.{col_widths['Data Type']}}"
)
description_cell = f"{description:<{col_widths['Description']}.{col_widths['Description']}}" description_cell = f"{description:<{col_widths['Description']}.{col_widths['Description']}}"
md_row = f"| {master_tag_cell} | {type_cell} | {data_type_cell} | {description_cell} |" md_row = f"| {master_tag_cell} | {type_cell} | {data_type_cell} | {description_cell} |"
@ -263,9 +282,7 @@ def convert_excel_to_markdown_tables():
try: try:
with open(output_md_filepath_abs, "w", encoding="utf-8") as f: with open(output_md_filepath_abs, "w", encoding="utf-8") as f:
f.write("\n".join(markdown_content)) f.write("\n".join(markdown_content))
print( print(f"¡Éxito! Archivo Markdown generado en: {output_md_filepath_abs}")
f"¡Éxito! Archivo Excel convertido a Markdown en: {output_md_filepath_abs}"
)
except IOError as e: except IOError as e:
print( print(
f"Error al escribir el archivo Markdown '{output_md_filepath_abs}': {e}" f"Error al escribir el archivo Markdown '{output_md_filepath_abs}': {e}"

View File

@ -71,6 +71,14 @@ def generate_prompt():
f"Error: El directorio de trabajo '{working_directory}' no existe o no es un directorio." f"Error: El directorio de trabajo '{working_directory}' no existe o no es un directorio."
) )
return False return False
group_config = configs.get("level2", {})
resultados_exp_directory = group_config.get("resultados_exp_directory")
if not resultados_exp_directory:
print(
"Error: 'resultados_exp_directory' no se encontró en la configuración de nivel 2."
)
return False
except Exception as e: except Exception as e:
print(f"Error al cargar la configuración: {e}") print(f"Error al cargar la configuración: {e}")
return False return False
@ -85,8 +93,8 @@ def generate_prompt():
# Intentar obtener la carpeta base de Obsidian desde la configuración # Intentar obtener la carpeta base de Obsidian desde la configuración
try: try:
# Obtener configuración del nivel 2 # Obtener configuración del nivel 3
group_config = configs.get("level2", {}) group_config = configs.get("level3", {})
obsidian_dir = group_config.get("ObsideanDir") obsidian_dir = group_config.get("ObsideanDir")
obsidian_projects_base = group_config.get("ObsideanProjectsBase") obsidian_projects_base = group_config.get("ObsideanProjectsBase")
@ -146,8 +154,9 @@ def generate_prompt():
print(f"Usando carpeta de equivalencias en Obsidian: {obsidian_mixer_path}") print(f"Usando carpeta de equivalencias en Obsidian: {obsidian_mixer_path}")
# Definir las rutas a los archivos # Definir las rutas a los archivos
master_table_path = os.path.join(working_directory_abs, "Master IO Tags.md") data_directory = os.path.join(working_directory_abs, resultados_exp_directory)
hardware_table_path = os.path.join(working_directory_abs, "Hardware.md") master_table_path = os.path.join(data_directory, "Master IO Tags.md")
hardware_table_path = os.path.join(data_directory, "Hardware.md")
adaptation_table_path = os.path.join(working_directory_abs, "IO Adapted.md") adaptation_table_path = os.path.join(working_directory_abs, "IO Adapted.md")
# Rutas a los archivos de datos semánticos # Rutas a los archivos de datos semánticos
@ -215,8 +224,8 @@ $Working_Directory = "{working_directory_abs}"
$Obsidean_Base_Folder = "{obsidian_mixer_path}" $Obsidean_Base_Folder = "{obsidian_mixer_path}"
# Archivos de entrada # Archivos de entrada
$Master_table = $Working_Directory + "/Master IO Tags.md" $Master_table = $Working_Directory + "/{resultados_exp_directory}/Master IO Tags.md"
$Hardware_table = $Working_Directory + "/Hardware.md" $Hardware_table = $Working_Directory + "/{resultados_exp_directory}/Hardware.md"
# Archivo de salida # Archivo de salida
$Adaptation_table = $Working_Directory + "/IO Adapted.md" $Adaptation_table = $Working_Directory + "/IO Adapted.md"
@ -370,7 +379,7 @@ def copy_adapted_file_to_obsidian():
return False return False
# Obtener la ruta de destino en Obsidian # Obtener la ruta de destino en Obsidian
group_config = configs.get("level2", {}) group_config = configs.get("level3", {})
obsidian_dir = group_config.get("ObsideanDir") obsidian_dir = group_config.get("ObsideanDir")
obsidian_projects_base = group_config.get("ObsideanProjectsBase") obsidian_projects_base = group_config.get("ObsideanProjectsBase")

View File

@ -1,5 +1,18 @@
{ {
"history": [ "history": [
{
"id": "8f721c30",
"group_id": "2",
"script_name": "main.py",
"executed_date": "2025-07-14T10:11:20.750196Z",
"arguments": [],
"working_directory": "D:/Proyectos/Scripts/RS485/MaselliSimulatorApp",
"python_env": "tia_scripting",
"executable_type": "pythonw.exe",
"status": "running",
"pid": 22396,
"execution_time": null
},
{ {
"id": "b321622a", "id": "b321622a",
"group_id": "4", "group_id": "4",

View File

@ -1,290 +1,21 @@
[16:38:15] Iniciando ejecución de x2_full_io_documentation.py en C:\Trabajo\SIDEL\13 - E5.007560 - Modifica O&U - SAE235\Reporte\Analisis\TwinCat... [18:10:38] Iniciando ejecución de x4_prompt_generator.py en C:\Trabajo\SIDEL\13 - E5.007560 - Modifica O&U - SAE235\Reporte\Analisis\Siemens...
[16:38:16] 🚀 Iniciando documentación completa de IOs de TwinCAT [18:10:38] Generador de prompt para adaptación de IO
[16:38:16] ================================================================================ [18:10:38] =========================================
[16:38:16] 📁 Directorio de trabajo: C:\Trabajo\SIDEL\13 - E5.007560 - Modifica O&U - SAE235\Reporte\Analisis\TwinCat [18:10:38] Usando directorio de trabajo: C:\Trabajo\SIDEL\13 - E5.007560 - Modifica O&U - SAE235\Reporte\Analisis\Siemens
[16:38:16] 📁 Directorio de resultados: C:\Trabajo\SIDEL\13 - E5.007560 - Modifica O&U - SAE235\Reporte\Analisis\TwinCat\result [18:10:38] Usando ruta de Obsidian desde configuración: C:\Users\migue\OneDrive\Miguel\Obsidean\Trabajo\VM\04-SIDEL\00 - MASTER\MIXER\IO
[16:38:16] 🔍 Escaneando definiciones TwinCAT activas en: C:\Trabajo\SIDEL\13 - E5.007560 - Modifica O&U - SAE235\Reporte\Analisis\TwinCat\scl [18:10:38] Usando carpeta de equivalencias en Obsidian: C:\Users\migue\OneDrive\Miguel\Obsidean\Trabajo\VM\04-SIDEL\00 - MASTER\MIXER\IO
[16:38:16] ✅ Encontradas 141 definiciones de IO activas. [18:10:38] ¡Prompt generado y copiado al portapapeles con éxito!
[16:38:16] 🔍 Buscando usos de variables definidas en: C:\Trabajo\SIDEL\13 - E5.007560 - Modifica O&U - SAE235\Reporte\Analisis\TwinCat\scl [18:10:38] Prompt guardado en: C:\Trabajo\SIDEL\13 - E5.007560 - Modifica O&U - SAE235\Reporte\Analisis\Siemens\IO_Adaptation_Prompt.txt
[16:38:16] 📄 Analizando uso en: ADSVARREAD.scl [18:11:10] Ejecución de x4_prompt_generator.py finalizada (success). Duración: 0:00:31.385093.
[16:38:16] 📄 Analizando uso en: ADSVARTRANSLATE.scl [18:11:10] Log completo guardado en: D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\IO_adaptation\.log\log_x4_prompt_generator.txt
[16:38:16] 📄 Analizando uso en: ADSVARWRITE.scl [10:11:20] Ejecutando script GUI: main.py
[16:38:16] 📄 Analizando uso en: AMMONIACTRL.scl [10:11:20] Entorno Python: tia_scripting
[16:38:16] 📄 Analizando uso en: ARRAYTOREAL.scl [10:11:20] Ejecutable: pythonw.exe (sin logging)
[16:38:16] 📄 Analizando uso en: BLENDERPROCEDURE_VARIABLES.scl [10:11:20] Comando: C:\Users\migue\miniconda3\envs\tia_scripting\pythonw.exe D:/Proyectos/Scripts/RS485/MaselliSimulatorApp\main.py
[16:38:16] 📄 Analizando uso en: BLENDERRINSE.scl [10:11:20] Directorio: D:/Proyectos/Scripts/RS485/MaselliSimulatorApp
[16:38:16] 📄 Analizando uso en: BLENDER_PID_CTRL_LOOP.scl [10:11:20] Encoding: UTF-8 (PYTHONUTF8=1, PYTHONIOENCODING=utf-8)
[16:38:17] 📄 Analizando uso en: BLENDER_PROCEDURECALL.scl [10:11:20] ==================================================
[16:38:17] 📄 Analizando uso en: BLENDER_RUNCONTROL.scl [10:11:20] Script GUI ejecutado sin logging (pythonw.exe)
[16:38:17] 📄 Analizando uso en: BLENDER_VARIABLES.scl [10:11:20] PID: 22396
[16:38:17] 📄 Analizando uso en: BLENDFILLRECSTRUCT.scl [10:11:20] ==================================================
[16:38:17] 📄 Analizando uso en: BLENDFILLSENDSTRUCT.scl [10:11:20] ID de ejecución: 8f721c30
[16:38:17] 📄 Analizando uso en: BLENDFILLSYSTEM_STARTUP.scl
[16:38:17] 📄 Analizando uso en: BRIXTRACKING.scl
[16:38:17] 📄 Analizando uso en: BYTES_TO_DWORD.scl
[16:38:17] 📄 Analizando uso en: BYTES_TO_WORD.scl
[16:38:17] 📄 Analizando uso en: CALC_INJPRESS.scl
[16:38:17] 📄 Analizando uso en: CARBOWATERLINE.scl
[16:38:17] 📄 Analizando uso en: CENTRALCIP_CTRL.scl
[16:38:17] 📄 Analizando uso en: CETRIFUGAL_HEAD.scl
[16:38:17] 📄 Analizando uso en: CIPRECEIVESTRUCT.scl
[16:38:17] 📄 Analizando uso en: CIPSENDSTRUCT.scl
[16:38:17] 📄 Analizando uso en: CIP_CVQ.scl
[16:38:17] 📄 Analizando uso en: CIP_LINK_TYPE.scl
[16:38:17] 📄 Analizando uso en: CIP_LIST_ELEMENT.scl
[16:38:17] 📄 Analizando uso en: CIP_MAIN.scl
[16:38:18] 📄 Analizando uso en: CIP_PROGRAM_VARIABLES.scl
[16:38:18] 📄 Analizando uso en: CIP_SIMPLE_TYPE.scl
[16:38:18] 📄 Analizando uso en: CIP_STEP_TYPE.scl
[16:38:18] 📄 Analizando uso en: CIP_WAITEVENT_TYPE.scl
[16:38:18] 📄 Analizando uso en: CLEANBOOLARRAY.scl
[16:38:18] 📄 Analizando uso en: CLOCK_SIGNAL.scl
[16:38:18] 📄 Analizando uso en: CLOCK_VARIABLES.scl
[16:38:18] 📄 Analizando uso en: CO2EQPRESS.scl
[16:38:18] 📄 Analizando uso en: CO2INJPRESSURE.scl
[16:38:18] 📄 Analizando uso en: CO2_SOLUBILITY.scl
[16:38:18] 📄 Analizando uso en: CONVERTREAL.scl
[16:38:18] 📄 Analizando uso en: CVQ_0_6_PERC.scl
[16:38:18] 📄 Analizando uso en: CVQ_1P7_8_PERC.scl
[16:38:18] 📄 Analizando uso en: DATA_FROM_CIP.scl
[16:38:18] 📄 Analizando uso en: DATA_TO_CIP.scl
[16:38:18] 📄 Analizando uso en: DEAIRCO2TEMPCOMP.scl
[16:38:18] 📄 Analizando uso en: DEAIREATIONVALVE.scl
[16:38:18] 📄 Analizando uso en: DEAIREATOR_STARTUP.scl
[16:38:18] 📄 Analizando uso en: DELAY.scl
[16:38:18] 📄 Analizando uso en: DELTAP.scl
[16:38:18] 📄 Analizando uso en: DENSIMETER_CALIBRATION.scl
[16:38:18] 📄 Analizando uso en: DERIVE.scl
[16:38:18] 📄 Analizando uso en: DEVICENET_VARIABLES.scl
[16:38:18] 📄 Analizando uso en: DWORD_TO_BYTES.scl
[16:38:18] 📄 Analizando uso en: EXEC_SIMPLE_CIP.scl
[16:38:18] 📄 Analizando uso en: FASTRINSE.scl
[16:38:18] 📄 Analizando uso en: FB41_PIDCONTROLLER.scl
[16:38:18] 📄 Analizando uso en: FC_CONTROL_WORD.scl
[16:38:18] 📄 Analizando uso en: FC_STATUS_WORD.scl
[16:38:18] 📄 Analizando uso en: FEEDFORWARD.scl
[16:38:18] 📄 Analizando uso en: FILLERHEAD.scl
[16:38:18] 📄 Analizando uso en: FILLERRECEIVESTRUCT.scl
[16:38:18] 📄 Analizando uso en: FILLERRINSE.scl
[16:38:18] 📄 Analizando uso en: FILLERRINSETANK_CTRL.scl
[16:38:18] 📄 Analizando uso en: FILLERSENDSTRUCT.scl
[16:38:18] 📄 Analizando uso en: FILLER_CONTROL.scl
[16:38:19] 📄 Analizando uso en: FILLINGTIME.scl
[16:38:19] 📄 Analizando uso en: FIRSTPRODUCTION.scl
[16:38:19] 📄 Analizando uso en: FLOW_TO_PRESS_LOSS.scl
[16:38:19] 📄 Analizando uso en: FREQ_TO_MMH2O.scl
[16:38:19] 📄 Analizando uso en: FRICTIONLOSS.scl
[16:38:19] 📄 Analizando uso en: GETPRODBRIXCO2_FROMANALOGINPUT.scl
[16:38:19] 📄 Analizando uso en: GETPRODO2_FROMANALOGINPUT.scl
[16:38:19] 📄 Analizando uso en: GLOBAL_ALARMS.scl
[16:38:19] 📄 Analizando uso en: GLOBAL_VARIABLES_IN_OUT.scl
[16:38:19] 📄 Analizando uso en: HMI_ALARMS.scl
[16:38:19] 📄 Analizando uso en: HMI_BLENDER_PARAMETERS.scl
[16:38:19] 📄 Analizando uso en: HMI_IO_SHOWING.scl
[16:38:19] 📄 Analizando uso en: HMI_LOCAL_CIP_VARIABLES.scl
[16:38:19] 📄 Analizando uso en: HMI_SERVICE.scl
[16:38:19] 📄 Analizando uso en: HMI_VARIABLES_CMD.scl
[16:38:19] 📄 Analizando uso en: HMI_VARIABLES_STATUS.scl
[16:38:19] 📄 Analizando uso en: INPUT.scl
[16:38:19] 📄 Analizando uso en: INPUT_CIP_SIGNALS.scl
[16:38:20] 📄 Analizando uso en: INPUT_SIGNAL.scl
[16:38:20] 📄 Analizando uso en: INTEGRAL.scl
[16:38:20] 📄 Analizando uso en: LOCALCIP_CTRL.scl
[16:38:20] 📄 Analizando uso en: LOWPASSFILTER.scl
[16:38:20] 📄 Analizando uso en: LOWPASSFILTEROPT.scl
[16:38:20] 📄 Analizando uso en: MASELLI.scl
[16:38:20] 📄 Analizando uso en: MASELLIOPTO_TYPE.scl
[16:38:20] 📄 Analizando uso en: MASELLIUC05_TYPE.scl
[16:38:20] 📄 Analizando uso en: MASELLIUR22_TYPE.scl
[16:38:20] 📄 Analizando uso en: MASELLI_CONTROL.scl
[16:38:20] 📄 Analizando uso en: MAXCARBOCO2_VOL.scl
[16:38:20] 📄 Analizando uso en: MESSAGESCROLL.scl
[16:38:20] 📄 Analizando uso en: MESSAGE_SCROLL.scl
[16:38:20] 📄 Analizando uso en: MFMANALOG_VALUES.scl
[16:38:20] 📄 Analizando uso en: MFM_REAL_STRUCT.scl
[16:38:20] 📄 Analizando uso en: MMH2O_TO_FREQ.scl
[16:38:20] 📄 Analizando uso en: MODVALVEFAULT.scl
[16:38:20] 📄 Analizando uso en: MOVEARRAY.scl
[16:38:20] 📄 Analizando uso en: MPDS1000.scl
[16:38:20] 📄 Analizando uso en: MPDS1000_CONTROL.scl
[16:38:20] 📄 Analizando uso en: MPDS1000_TYPE.scl
[16:38:20] 📄 Analizando uso en: MPDS2000.scl
[16:38:20] 📄 Analizando uso en: MPDS2000_CONTROL.scl
[16:38:20] 📄 Analizando uso en: MPDS2000_TYPE.scl
[16:38:20] 📄 Analizando uso en: MPDS_PA_CONTROL.scl
[16:38:20] 📄 Analizando uso en: MSE_SLOPE.scl
[16:38:20] 📄 Analizando uso en: MYVAR.scl
[16:38:20] 📄 Analizando uso en: OR_ARRAYBOOL.scl
[16:38:20] 📄 Analizando uso en: OUTPUT.scl
[16:38:20] 📄 Analizando uso en: PARAMETERNAMETYPE.scl
[16:38:20] 📄 Analizando uso en: PA_MPDS.scl
[16:38:20] 📄 Analizando uso en: PERIPHERIAL.scl
[16:38:20] 📄 Analizando uso en: PID_VARIABLES.scl
[16:38:20] 📄 Analizando uso en: PLC CONFIGURATION.scl
[16:38:21] 📄 Analizando uso en: PNEUMATIC_VALVE_CTRL.scl
[16:38:21] 📄 Analizando uso en: PPM_O2.scl
[16:38:21] 📄 Analizando uso en: PRODBRIXRECOVERY.scl
[16:38:21] 📄 Analizando uso en: PRODTANK_DRAIN.scl
[16:38:21] 📄 Analizando uso en: PRODTANK_RUNOUT.scl
[16:38:21] 📄 Analizando uso en: PRODUCTAVAILABLE.scl
[16:38:21] 📄 Analizando uso en: PRODUCTION_VARIABLES.scl
[16:38:21] 📄 Analizando uso en: PRODUCTLITERINTANK.scl
[16:38:21] 📄 Analizando uso en: PRODUCTPIPEDRAIN.scl
[16:38:21] 📄 Analizando uso en: PRODUCTPIPERUNOUT.scl
[16:38:21] 📄 Analizando uso en: PRODUCTQUALITY.scl
[16:38:21] 📄 Analizando uso en: PRODUCTTANKBRIX.scl
[16:38:21] 📄 Analizando uso en: PRODUCTTANK_PRESSCTRL.scl
[16:38:21] 📄 Analizando uso en: PROFIBUS_DATA.scl
[16:38:21] 📄 Analizando uso en: PROFIBUS_NETWORK.scl
[16:38:21] 📄 Analizando uso en: PROFIBUS_VARIABLES.scl
[16:38:21] 📄 Analizando uso en: PULSEPRESSURE.scl
[16:38:21] 📄 Analizando uso en: PUMPSCONTROL.scl
[16:38:21] 📄 Analizando uso en: READANALOGIN.scl
[16:38:21] 📄 Analizando uso en: READPERIPHERIAL.scl
[16:38:21] 📄 Analizando uso en: SAFETIES.scl
[16:38:22] 📄 Analizando uso en: SELCHECKBRIXSOURCE.scl
[16:38:22] 📄 Analizando uso en: SIGNALS_INTEFACE.scl
[16:38:22] 📄 Analizando uso en: SIGNAL_GEN.scl
[16:38:22] 📄 Analizando uso en: SINUSOIDAL_SIGNAL.scl
[16:38:22] 📄 Analizando uso en: SLEWLIMIT.scl
[16:38:22] 📄 Analizando uso en: SLIM_BLOCK.scl
[16:38:22] 📄 Analizando uso en: SLIM_VARIABLES.scl
[16:38:22] 📄 Analizando uso en: SOFTNET_VARIABLES.scl
[16:38:22] 📄 Analizando uso en: SPEEDADJUST.scl
[16:38:22] 📄 Analizando uso en: SP_AND_P_VARIABLES.scl
[16:38:22] 📄 Analizando uso en: STANDARD.LIB_5.6.98 09_39_02.scl
[16:38:22] 📄 Analizando uso en: STATISTICALANALISYS.scl
[16:38:22] 📄 Analizando uso en: SYRBRIX_AUTOCORRECTION.scl
[16:38:22] 📄 Analizando uso en: SYRUPDENSITY.scl
[16:38:22] 📄 Analizando uso en: SYRUPROOMCTRL.scl
[16:38:22] 📄 Analizando uso en: SYRUP_LINE_MFM_PREP.scl
[16:38:22] 📄 Analizando uso en: SYRUP_MFM_STARTUP.scl
[16:38:22] 📄 Analizando uso en: SYRUP_RUNOUT.scl
[16:38:22] 📄 Analizando uso en: SYSTEMRUNOUT_VARIABLES.scl
[16:38:22] 📄 Analizando uso en: SYSTEM_DATAS.scl
[16:38:22] 📄 Analizando uso en: SYSTEM_RUN_OUT.scl
[16:38:22] 📄 Analizando uso en: TANKLEVEL.scl
[16:38:22] 📄 Analizando uso en: TANKLEVELTOHEIGHT.scl
[16:38:22] 📄 Analizando uso en: TASK CONFIGURATION.scl
[16:38:22] 📄 Analizando uso en: TCPLCUTILITIES.LIB_11.12.01 09_39_02.scl
[16:38:22] 📄 Analizando uso en: TCSYSTEM.LIB_16.9.02 09_39_02.scl
[16:38:22] 📄 Analizando uso en: TESTFLOWMETERS.scl
[16:38:22] 📄 Analizando uso en: UDP_STRUCT.scl
[16:38:22] 📄 Analizando uso en: UV_LAMP.scl
[16:38:22] 📄 Analizando uso en: VACUUMCTRL.scl
[16:38:22] 📄 Analizando uso en: VALVEFAULT.scl
[16:38:22] 📄 Analizando uso en: VALVEFLOW.scl
[16:38:22] 📄 Analizando uso en: VARIABLE_CONFIGURATION.scl
[16:38:22] 📄 Analizando uso en: VOID.scl
[16:38:22] 📄 Analizando uso en: WATERDENSITY.scl
[16:38:22] 📄 Analizando uso en: WORD_TO_BYTES.scl
[16:38:22] 📄 Analizando uso en: WRITEPERIPHERIAL.scl
[16:38:22] 📄 Analizando uso en: _BLENDER_CTRL_MAIN.scl
[16:38:23] 📄 Analizando uso en: _BLENDER_PID_MAIN.scl
[16:38:23] 📄 Analizando uso en: _BOOLARRAY_TO_DWORD.scl
[16:38:23] 📄 Analizando uso en: _BOOLARRAY_TO_WORD.scl
[16:38:23] 📄 Analizando uso en: _DWORD_SWAP_BYTEARRAY.scl
[16:38:23] 📄 Analizando uso en: _DWORD_TO_BOOLARRAY.scl
[16:38:23] 📄 Analizando uso en: _FILLING_HEAD_PID_CTRL.scl
[16:38:23] 📄 Analizando uso en: _PUMPCONTROL.scl
[16:38:23] 📄 Analizando uso en: _STEPMOVE.scl
[16:38:23] 📄 Analizando uso en: _WORD_TO_BOOLARRAY.scl
[16:38:23] ✅ Encontrados 224 usos para 83 variables distintas.
[16:38:23] 📄 Generando tabla resumen: C:\Trabajo\SIDEL\13 - E5.007560 - Modifica O&U - SAE235\Reporte\Analisis\TwinCat\result\TwinCAT_Full_IO_List.md
[16:38:23] ✅ Tabla resumen generada exitosamente.
[16:38:23] 📄 Generando reporte de snippets: C:\Trabajo\SIDEL\13 - E5.007560 - Modifica O&U - SAE235\Reporte\Analisis\TwinCat\result\TwinCAT_IO_Usage_Snippets.md
[16:38:23] Generando snippets para 83 variables con uso...
[16:38:23] 📝 Procesando 1/83: AI_ProductTankLevel (1 usos)
[16:38:23] 📝 Procesando 2/83: AI_ProductTankPressure (1 usos)
[16:38:23] 📝 Procesando 3/83: AI_DeaireationValve_VEP4 (2 usos)
[16:38:23] 📝 Procesando 4/83: AI_ProdTankPressureValve_VEP1 (1 usos)
[16:38:23] 📝 Procesando 5/83: AI_ProductTemperature (1 usos)
[16:38:23] 📝 Procesando 6/83: AI_SyrupTankLevel (1 usos)
[16:38:23] 📝 Procesando 7/83: AI_DeairWaterTemperature (1 usos)
[16:38:23] 📝 Procesando 8/83: AI_InjectionPressure (2 usos)
[16:38:23] 📝 Procesando 9/83: gProduct_VFC_MainActualValue (1 usos)
[16:38:23] 📝 Procesando 10/83: DI_AuxVoltage_On (1 usos)
[16:38:23] 📝 Procesando 11/83: DI_Reset_Horn_Btn (2 usos)
[16:38:23] 📝 Procesando 12/83: DI_Reset_Btn (79 usos)
[16:38:23] 📝 Procesando 13/83: DI_Blender_Stop_Btn (3 usos)
[16:38:23] 📝 Procesando 14/83: DI_Blender_Start_Btn (1 usos)
[16:38:23] 📝 Procesando 15/83: DI_PowerSuppliesOk (3 usos)
[16:38:23] 📝 Procesando 16/83: DI_Min_Deair_Level (1 usos)
[16:38:23] 📝 Procesando 17/83: DI_ProdTankEmpty (1 usos)
[16:38:23] 📝 Procesando 18/83: DI_BatteryNotReady (1 usos)
[16:38:23] 📝 Procesando 19/83: DI_VM1_Water_Valve_Closed (1 usos)
[16:38:23] 📝 Procesando 20/83: DI_VM2_Syrup_Valve_Closed (1 usos)
[16:38:23] 📝 Procesando 21/83: DI_VM3_CO2_Valve_Closed (1 usos)
[16:38:23] 📝 Procesando 22/83: DI_Water_Pump_Contactor (1 usos)
[16:38:23] 📝 Procesando 23/83: DI_Syrup_Pump_Ovrld (1 usos)
[16:38:23] 📝 Procesando 24/83: DI_Syrup_Pump_Contactor (1 usos)
[16:38:23] 📝 Procesando 25/83: DI_Product_Pump_Contactor (1 usos)
[16:38:23] 📝 Procesando 26/83: DI_SyrRoom_Pump_Ready (1 usos)
[16:38:23] 📝 Procesando 27/83: DI_CIP_CIPMode (1 usos)
[16:38:23] 📝 Procesando 28/83: DI_CIP_RinseMode (1 usos)
[16:38:23] 📝 Procesando 29/83: DI_CIP_DrainRequest (1 usos)
[16:38:23] 📝 Procesando 30/83: DI_CIP_CIPCompleted (1 usos)
[16:38:23] 📝 Procesando 31/83: DI_Air_InletPress_OK (1 usos)
[16:38:23] 📝 Procesando 32/83: DI_Syrup_Line_Drain_Sensor (1 usos)
[16:38:23] 📝 Procesando 33/83: gWaterTotCtrl_Node20 (3 usos)
[16:38:23] 📝 Procesando 34/83: gSyrControl_Node21 (7 usos)
[16:38:23] 📝 Procesando 35/83: gCO2Control_Node22 (7 usos)
[16:38:23] 📝 Procesando 36/83: gProductTotCtrl_Node17 (3 usos)
[16:38:23] 📝 Procesando 37/83: AO_WaterCtrlValve_VM1 (1 usos)
[16:38:23] 📝 Procesando 38/83: AO_SyrupCtrlValve_VM2 (1 usos)
[16:38:23] 📝 Procesando 39/83: AO_CarboCO2CtrlValve_VM3 (1 usos)
[16:38:23] 📝 Procesando 40/83: AO_ProdTankPressureValve_VEP1 (1 usos)
[16:38:23] 📝 Procesando 41/83: AO_DeaireationValve_VEP4 (2 usos)
[16:38:23] 📝 Procesando 42/83: AO_ProdTempCtrlValve (1 usos)
[16:38:23] 📝 Procesando 43/83: AO_SyrupInletValve_VEP3 (1 usos)
[16:38:23] 📝 Procesando 44/83: AO_InjectionPressure (1 usos)
[16:38:23] 📝 Procesando 45/83: gProduct_VFC_MainRefValue (1 usos)
[16:38:23] 📝 Procesando 46/83: DO_SyrupInletValve_Enable (1 usos)
[16:38:23] 📝 Procesando 47/83: DO_HoldBrixMeter (2 usos)
[16:38:23] 📝 Procesando 48/83: DO_SyrupRoomPump_Run (2 usos)
[16:38:23] 📝 Procesando 49/83: DO_SyrupRoomWaterReq (2 usos)
[16:38:23] 📝 Procesando 50/83: DO_CIP_CIPRequest (2 usos)
[16:38:23] 📝 Procesando 51/83: DO_CIP_DrainCompleted (2 usos)
[16:38:23] 📝 Procesando 52/83: DO_Horn (2 usos)
[16:38:23] 📝 Procesando 53/83: DO_Blender_Run_Lamp (2 usos)
[16:38:23] 📝 Procesando 54/83: DO_Alarm_Lamp (2 usos)
[16:38:23] 📝 Procesando 55/83: DO_RotorAlarm_Lamp (2 usos)
[16:38:23] 📝 Procesando 56/83: DO_Water_Pump_Run (2 usos)
[16:38:23] 📝 Procesando 57/83: DO_Syrup_Pump_Run (2 usos)
[16:38:23] 📝 Procesando 58/83: DO_Product_Pump_Run (3 usos)
[16:38:23] 📝 Procesando 59/83: DO_EV11_BlowOff_Valve (2 usos)
[16:38:23] 📝 Procesando 60/83: DO_EV13_Prod_Recirc_Valve (2 usos)
[16:38:23] 📝 Procesando 61/83: DO_EV14_DeairDrain_Valve (2 usos)
[16:38:23] 📝 Procesando 62/83: DO_EV15_ProductTank_Drain_Valve (2 usos)
[16:38:23] 📝 Procesando 63/83: DO_EV16_SyrupTank_Drain_Valve (2 usos)
[16:38:23] 📝 Procesando 64/83: DO_EV17_BufferTankSprayBall_Valve (2 usos)
[16:38:23] 📝 Procesando 65/83: DO_EV18_DeairOverfill_Valve (2 usos)
[16:38:23] 📝 Procesando 66/83: DO_EV21_ProdTankOverfill_Valve (2 usos)
[16:38:23] 📝 Procesando 67/83: DO_EV22_WaterPumpPrime_Valve (2 usos)
[16:38:23] 📝 Procesando 68/83: DO_EV23_SerpentineDrain_valve (2 usos)
[16:38:23] 📝 Procesando 69/83: DO_EV24_SyrupRecirc_Valve (2 usos)
[16:38:23] 📝 Procesando 70/83: DO_EV26_CO2InjShutOff_Valve (2 usos)
[16:38:23] 📝 Procesando 71/83: DO_EV27_DeairSprayBall_Valve (2 usos)
[16:38:23] 📝 Procesando 72/83: DO_EV28_DeairStartCO2Inj_Valve (2 usos)
[16:38:23] 📝 Procesando 73/83: DO_EV44_SyrupLineDrain (2 usos)
[16:38:23] 📝 Procesando 74/83: DO_EV45_ProductChillerDrain (2 usos)
[16:38:23] 📝 Procesando 75/83: DO_EV61_SyrupTankSprayBall (2 usos)
[16:38:23] 📝 Procesando 76/83: DO_EV62_ProductOutlet (3 usos)
[16:38:23] 📝 Procesando 77/83: DO_EV69_Blender_ProductPipeDrain (2 usos)
[16:38:23] 📝 Procesando 78/83: DO_EV81_Prod_Recirc_Chiller_Valve (2 usos)
[16:38:23] 📝 Procesando 79/83: DO_EV01_Deair_Lvl_Ctrl_Valve (2 usos)
[16:38:23] 📝 Procesando 80/83: DO_EV02_Deair_FillUp_Valve (2 usos)
[16:38:23] 📝 Procesando 81/83: gPAmPDSFreeze (2 usos)
[16:38:23] 📝 Procesando 82/83: gPAmPDSCarboStop (2 usos)
[16:38:23] 📝 Procesando 83/83: gPAmPDSInlinePumpStop (2 usos)
[16:38:23] Generando tabla para 58 variables no usadas...
[16:38:23] ✅ Reporte de snippets generado exitosamente.
[16:38:23] 📄 Generando reporte JSON: C:\Trabajo\SIDEL\13 - E5.007560 - Modifica O&U - SAE235\Reporte\Analisis\TwinCat\result\TwinCAT_IO_Usage_Snippets.json
[16:38:23] ✅ Reporte JSON generado exitosamente.
[16:38:23] 🎉 Análisis completado exitosamente!
[16:38:23] 📁 Archivos generados en: C:\Trabajo\SIDEL\13 - E5.007560 - Modifica O&U - SAE235\Reporte\Analisis\TwinCat\result
[16:38:23] 📄 TwinCAT_Full_IO_List.md
[16:38:23] 📄 TwinCAT_IO_Usage_Snippets.md
[16:38:23] 📄 TwinCAT_IO_Usage_Snippets.json
[16:38:23] Ejecución de x2_full_io_documentation.py finalizada (success). Duración: 0:00:07.976669.
[16:38:23] Log completo guardado en: D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\TwinCat\log_x2_full_io_documentation.txt

View File

@ -227,7 +227,7 @@
<!-- Level 3 Configuration --> <!-- Level 3 Configuration -->
<div class="mb-8 bg-white p-6 rounded-lg shadow"> <div class="mb-8 bg-white p-6 rounded-lg shadow">
<div class="flex justify-between items-center mb-4"> <div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-bold">Configuración del Proyecto</h2> <h2 class="text-xl font-bold">Datos de Ingreso</h2>
<button class="bg-blue-500 text-white px-4 py-2 rounded" onclick="toggleConfig('level3-content')"> <button class="bg-blue-500 text-white px-4 py-2 rounded" onclick="toggleConfig('level3-content')">
Ocultar Configuración Ocultar Configuración
</button> </button>