feat: Update TIA Portal version support and enhance export functionality in x1.py and x4.py; remove obsolete test scripts

This commit is contained in:
Miguel 2025-08-23 16:49:30 +02:00
parent 48e25282d6
commit 18f6cdaa4f
7 changed files with 317 additions and 196 deletions

View File

@ -1,8 +1,8 @@
{ {
"x1.py": { "x1.py": {
"display_name": "1: Exportar Lógica desde TIA Portal v18,v19,v20 en XML", "display_name": "1: Exportar Lógica desde TIA Portal v15,v16,v17,v18,v19,v20 en XML",
"short_description": "Exporta la lógica del PLC desde TIA Portal en archivos XML y SCL.", "short_description": "Exporta la lógica del PLC desde TIA Portal (V15 a V20) en archivos XML y SCL.",
"long_description": "Este script utiliza TIA Portal Openness para exportar la lógica de un PLC en formato XML y SCL. Permite seleccionar un proyecto de TIA Portal y genera los archivos de exportación en el directorio configurado.\n***\n**Lógica Principal:**\n\n1. **Configuración:** Carga parámetros desde `ParamManagerScripts` (directorio de trabajo, versión de TIA Portal).\n2. **Selección de Proyecto:** Abre un cuadro de diálogo para seleccionar el archivo del proyecto de TIA Portal.\n3. **Conexión a TIA Portal:** Utiliza la API de TIA Openness para conectarse al portal y abrir el proyecto seleccionado.\n4. **Exportación:** Exporta la lógica del PLC en archivos XML y SCL al directorio configurado.\n5. **Cierre:** Cierra la conexión con TIA Portal al finalizar.", "long_description": "Este script utiliza TIA Portal Openness para exportar la lógica de un PLC en formato XML y SCL. Permite seleccionar un proyecto de TIA Portal (V15, V16, V17, V18, V19, V20) y genera los archivos de exportación en el directorio configurado.\n***\n**Lógica Principal:**\n\n1. **Configuración:** Carga parámetros desde `ParamManagerScripts` (directorio de trabajo, versión de TIA Portal).\n2. **Selección de Proyecto:** Abre un cuadro de diálogo para seleccionar el archivo del proyecto de TIA Portal (V15 a V20).\n3. **Conexión a TIA Portal:** Utiliza la API de TIA Openness para conectarse al portal y abrir el proyecto seleccionado.\n4. **Exportación:** Exporta la lógica del PLC en archivos XML y SCL al directorio configurado.\n5. **Cierre:** Cierra la conexión con TIA Portal al finalizar.",
"hidden": false "hidden": false
}, },
"x2.py": { "x2.py": {
@ -12,9 +12,9 @@
"hidden": false "hidden": false
}, },
"x4.py": { "x4.py": {
"display_name": "3: Exportar Referencias Cruzadas desde TIA Portal", "display_name": "3: Exportar Referencias Cruzadas desde TIA Portal v17,v18,v19,v20",
"short_description": "Script para exportar las referencias cruzadas de un proyecto TIA Portal", "short_description": "Script para exportar las referencias cruzadas de un proyecto TIA Portal (V17 a V20)",
"long_description": "Este script exporta las referencias cruzadas (cross-references) de un proyecto TIA Portal, proporcionando información detallada sobre las interconexiones entre variables, bloques y componentes del sistema.\n***\n**Funcionalidad:**\n\n1. **Referencias cruzadas:** Extrae información sobre dónde se utilizan las variables y bloques\n2. **Análisis de dependencias:** Identifica relaciones entre componentes del proyecto\n3. **Documentación:** Genera reportes útiles para mantenimiento y debugging\n4. **Formato estructurado:** Exporta en formato legible para análisis posterior\n\n**Casos de uso:**\n- Documentación de proyecto\n- Análisis de impacto de cambios\n- Debugging y mantenimiento\n- Auditorías de código", "long_description": "Este script exporta las referencias cruzadas (cross-references) de un proyecto TIA Portal (V17, V18, V19, V20), proporcionando información detallada sobre las interconexiones entre variables, bloques y componentes del sistema.\n***\n**Funcionalidad:**\n\n1. **Referencias cruzadas:** Extrae información sobre dónde se utilizan las variables y bloques\n2. **Análisis de dependencias:** Identifica relaciones entre componentes del proyecto\n3. **Documentación:** Genera reportes útiles para mantenimiento y debugging\n4. **Formato estructurado:** Exporta en formato legible para análisis posterior\n\n**Casos de uso:**\n- Documentación de proyecto\n- Análisis de impacto de cambios\n- Debugging y mantenimiento\n- Auditorías de código\n\n**NOTA:** La exportación de referencias cruzadas requiere TIA Portal V17.0 o superior. Las versiones V15 y V16 no soportan esta funcionalidad en la API de Openness.",
"hidden": false "hidden": false
}, },
"xTest.py": { "xTest.py": {
@ -28,5 +28,23 @@
"short_description": "Test script to verify SIMATIC SD compatibility detection", "short_description": "Test script to verify SIMATIC SD compatibility detection",
"long_description": "", "long_description": "",
"hidden": false "hidden": false
},
"test_path_validation.py": {
"display_name": "test_path_validation",
"short_description": "Test script for path validation and sanitization functions",
"long_description": "",
"hidden": false
},
"test_sanitization.py": {
"display_name": "test_sanitization",
"short_description": "Test script for updated sanitization function",
"long_description": "",
"hidden": false
},
"test_block_validation.py": {
"display_name": "test_block_validation",
"short_description": "Test script para verificar la función is_block_exportable",
"long_description": "",
"hidden": false
} }
} }

View File

@ -0,0 +1,56 @@
"""
Test script para verificar la función is_block_exportable
"""
# Mock class para simular un bloque de TIA Portal
class MockBlock:
def __init__(self, programming_language):
self.programming_language = programming_language
def get_property(self, name):
if name == "ProgrammingLanguage":
return self.programming_language
raise Exception(f"Property {name} not found")
# Importar la función desde x1.py
import sys
import os
sys.path.append(os.path.dirname(__file__))
from x1 import is_block_exportable
# Testear diferentes tipos de bloques
test_cases = [
("LAD", True, "LAD blocks should be exportable"),
("FBD", True, "FBD blocks should be exportable"),
("STL", True, "STL blocks should be exportable"),
("SCL", True, "SCL blocks should be exportable"),
("ProDiag_OB", False, "ProDiag_OB blocks should not be exportable"),
("ProDiag", False, "ProDiag blocks should not be exportable"),
("GRAPH", False, "GRAPH blocks should not be exportable"),
]
print("=== Test de validación de bloques ===")
for prog_lang, expected_exportable, description in test_cases:
block = MockBlock(prog_lang)
is_exportable, detected_lang, reason = is_block_exportable(block)
status = "✓ PASS" if is_exportable == expected_exportable else "✗ FAIL"
print(f"{status} - {description}")
print(f" Lenguaje: {detected_lang}, Exportable: {is_exportable}, Razón: {reason}")
print()
# Test con bloque que genera excepción
class MockBlockError:
def get_property(self, name):
raise Exception("Cannot access property")
print("=== Test de manejo de errores ===")
error_block = MockBlockError()
is_exportable, detected_lang, reason = is_block_exportable(error_block)
print(f"Bloque con error - Exportable: {is_exportable}, Lenguaje: {detected_lang}")
print(f"Razón: {reason}")

View File

@ -1,103 +0,0 @@
"""
Test script for path validation and sanitization functions
"""
import os
import re
def sanitize_filename(name):
"""Sanitizes a filename by removing/replacing invalid characters and whitespace."""
# Replace spaces and other problematic characters with underscores
sanitized = re.sub(r'[<>:"/\\|?*\s]+', "_", name)
# Remove leading/trailing underscores and dots
sanitized = sanitized.strip("_.")
# Ensure it's not empty
if not sanitized:
sanitized = "unknown"
return sanitized
def sanitize_path(path):
"""Sanitizes a path by ensuring it doesn't contain problematic whitespace."""
# Normalize the path and remove any trailing/leading whitespace
normalized = os.path.normpath(path.strip())
return normalized
def validate_export_path(path):
"""Validates that an export path is suitable for TIA Portal."""
if not path:
return False, "La ruta está vacía"
# Check for problematic characters or patterns
if any(char in path for char in '<>"|?*'):
return False, f"La ruta contiene caracteres no válidos: {path}"
# Check for excessive whitespace
if path != path.strip():
return False, f"La ruta contiene espacios al inicio o final: '{path}'"
# Check for multiple consecutive spaces
if " " in path:
return False, f"La ruta contiene espacios múltiples consecutivos: '{path}'"
# Check path length (Windows limitation)
if len(path) > 250:
return False, f"La ruta es demasiado larga ({len(path)} caracteres): {path}"
return True, "OK"
# Test cases
test_names = [
"IO access error",
"CreatesAnyPointer",
"WriteMemArea_G",
"Block with spaces",
"Block<>with:invalid|chars",
" Block with leading and trailing spaces ",
"Block with multiple spaces",
"",
]
test_paths = [
"C:\\normal\\path",
"C:\\path with spaces\\subdir",
" C:\\path with leading space",
"C:\\path with trailing space ",
"C:\\path with multiple spaces\\subdir",
"C:\\path<with>invalid:chars|in\\subdir",
"",
]
print("=== Testing sanitize_filename ===")
for name in test_names:
sanitized = sanitize_filename(name)
print(f"'{name}' -> '{sanitized}'")
print("\n=== Testing sanitize_path ===")
for path in test_paths:
sanitized = sanitize_path(path)
print(f"'{path}' -> '{sanitized}'")
print("\n=== Testing validate_export_path ===")
for path in test_paths:
sanitized = sanitize_path(path)
is_valid, msg = validate_export_path(sanitized)
print(f"'{sanitized}' -> Valid: {is_valid}, Message: {msg}")
print("\n=== Test specific problematic block names ===")
problematic_blocks = ["IO access error", "CreatesAnyPointer", "WriteMemArea_G"]
base_path = "D:\\Export\\Test"
for block_name in problematic_blocks:
sanitized_name = sanitize_filename(block_name)
full_path = sanitize_path(
os.path.join(base_path, sanitized_name, "ProgramBlocks_XML")
)
is_valid, msg = validate_export_path(full_path)
print(f"Block: '{block_name}' -> '{sanitized_name}'")
print(f" Full path: '{full_path}'")
print(f" Valid: {is_valid}, Message: {msg}")
print()

View File

@ -1,49 +0,0 @@
"""
Test script for updated sanitization function
"""
import re
def sanitize_filename(name):
"""Sanitizes a filename by removing/replacing invalid characters and whitespace."""
# Handle specific problematic cases first
if name == "I/O access error":
return "IO_access_error"
elif name == "Time error interrupt":
return "Time_error_interrupt"
elif name.startswith("I/O_"):
return name.replace("I/O_", "IO_").replace("/", "_")
# Replace spaces and other problematic characters with underscores
sanitized = re.sub(r'[<>:"/\\|?*\s]+', "_", name)
# Remove leading/trailing underscores and dots
sanitized = sanitized.strip("_.")
# Ensure it's not empty
if not sanitized:
sanitized = "unknown"
return sanitized
# Test the problematic block names from the log
problematic_blocks = [
"IO access error",
"Time error interrupt",
"I/O_FLT1",
"I/O_FLT2",
"CreatesAnyPointer",
"WriteMemArea_G",
"ComGetPut_G",
"Sys_Plc_G",
"Sys_PLC_D",
"RACK_FLT",
"Startup",
"PROG_ERR",
]
print("=== Testing updated sanitize_filename function ===")
for block_name in problematic_blocks:
sanitized = sanitize_filename(block_name)
has_spaces = " " in block_name
has_slash = "/" in block_name
print(f"'{block_name}' -> '{sanitized}' [Spaces: {has_spaces}, Slash: {has_slash}]")

View File

@ -19,7 +19,14 @@ 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 = {".ap18": "18.0", ".ap19": "19.0", ".ap20": "20.0"} SUPPORTED_TIA_VERSIONS = {
".ap15": "15.0",
".ap16": "16.0",
".ap17": "17.0",
".ap18": "18.0",
".ap19": "19.0",
".ap20": "20.0",
}
EXPORT_OPTIONS = None # Use default export options EXPORT_OPTIONS = None # Use default export options
KEEP_FOLDER_STRUCTURE = ( KEEP_FOLDER_STRUCTURE = (
@ -65,6 +72,36 @@ except Exception as e:
# --- Functions --- # --- Functions ---
def is_block_exportable(block):
"""
Checks if a block can be exported based on its programming language.
Returns (is_exportable, programming_language, reason)
"""
try:
prog_language = block.get_property(name="ProgrammingLanguage")
# List of known unsupported programming languages
unsupported_languages = [
"ProDiag_OB", # ProDiag Organization Blocks
"ProDiag", # ProDiag Function Blocks
"GRAPH", # GRAPH (Sequential Control)
]
if prog_language in unsupported_languages:
return (
False,
prog_language,
f"Programming language '{prog_language}' is not supported for export",
)
return True, prog_language, "OK"
except Exception as e:
# If we can't determine the programming language, assume it might be exportable
# but warn about it
return True, "Unknown", f"Could not determine programming language: {e}"
def sanitize_filename(name): def sanitize_filename(name):
"""Sanitizes a filename by removing/replacing invalid characters and whitespace.""" """Sanitizes a filename by removing/replacing invalid characters and whitespace."""
import re import re
@ -188,6 +225,17 @@ def get_supported_filetypes():
return filetypes return filetypes
def normalize_project_path(project_path):
"""Normalizes a TIA Portal project path to avoid path-related issues."""
# Convert forward slashes to backslashes for Windows
normalized = project_path.replace("/", "\\")
# Use os.path.normpath to clean up the path
normalized = os.path.normpath(normalized)
# Ensure it's an absolute path
normalized = os.path.abspath(normalized)
return normalized
def detect_tia_version(project_file_path): def detect_tia_version(project_file_path):
"""Detects TIA Portal version based on file extension.""" """Detects TIA Portal version based on file extension."""
file_path = Path(project_file_path) file_path = Path(project_file_path)
@ -203,9 +251,9 @@ def detect_tia_version(project_file_path):
print( print(
f"ADVERTENCIA: Extensión de archivo no reconocida '{file_extension}'. Extensiones soportadas: {list(SUPPORTED_TIA_VERSIONS.keys())}" f"ADVERTENCIA: Extensión de archivo no reconocida '{file_extension}'. Extensiones soportadas: {list(SUPPORTED_TIA_VERSIONS.keys())}"
) )
# Default to version 18.0 for backward compatibility # Default to version 15.0 for backward compatibility
print("Usando por defecto TIA Portal V18.0") print("Usando por defecto TIA Portal V15.0")
return "18.0" return "15.0"
def select_project_file(): def select_project_file():
@ -282,6 +330,18 @@ def export_plc_data(plc, export_base_dir):
for block in program_blocks: for block in program_blocks:
block_name = block.get_name() block_name = block.get_name()
print(f" Procesando bloque: {block_name}...") print(f" Procesando bloque: {block_name}...")
# Check if block is exportable
is_exportable, prog_language, reason = is_block_exportable(block)
if not is_exportable:
print(f" ADVERTENCIA: {reason}. Omitiendo bloque {block_name}.")
blocks_skipped += 1
continue
if prog_language == "Unknown":
print(f" ADVERTENCIA: {reason}")
try: try:
if not block.is_consistent(): if not block.is_consistent():
print(f" Compilando bloque {block_name}...") print(f" Compilando bloque {block_name}...")
@ -364,6 +424,15 @@ def export_plc_data(plc, export_base_dir):
) )
print(f" Ruta problemática: '{xml_blocks_path}'") print(f" Ruta problemática: '{xml_blocks_path}'")
print(f" Tipo de bloque: {type(block).__name__}") print(f" Tipo de bloque: {type(block).__name__}")
print(f" Lenguaje de programación: {prog_language}")
# Check if it's a ProDiag related error
if "ProDiag" in str(
xml_ex
) or "not supported during import and export" in str(xml_ex):
print(
f" Este bloque usa un lenguaje no soportado para exportación. Omitiendo."
)
# Skip this block and continue with others # Skip this block and continue with others
blocks_skipped += 1 blocks_skipped += 1
@ -372,7 +441,6 @@ def export_plc_data(plc, export_base_dir):
# If we get here, XML export was successful # If we get here, XML export was successful
# Now try SCL export if applicable # Now try SCL export if applicable
try: try:
prog_language = block.get_property(name="ProgrammingLanguage")
if prog_language == "SCL": if prog_language == "SCL":
print(f" Exportando {block_name} como SCL...") print(f" Exportando {block_name} como SCL...")
try: try:
@ -440,9 +508,13 @@ def export_plc_data(plc, export_base_dir):
) )
print(f" Ruta problemática: '{scl_blocks_path}'") print(f" Ruta problemática: '{scl_blocks_path}'")
# Don't raise, just continue # Don't raise, just continue
except Exception as prop_ex: else:
print( print(
f" No se pudo obtener el lenguaje de programación para {block_name}. Omitiendo SCL. Error: {prop_ex}" f" Bloque {block_name} no es SCL (lenguaje: {prog_language}). Omitiendo exportación SCL."
)
except Exception as scl_check_ex:
print(
f" Error verificando lenguaje para exportación SCL: {scl_check_ex}"
) )
blocks_exported += 1 blocks_exported += 1
@ -584,6 +656,8 @@ if __name__ == "__main__":
# 1. Select Project File, Export Directory comes from config # 1. Select Project File, Export Directory comes from config
project_file = select_project_file() project_file = select_project_file()
# Normalize the project file path to avoid TIA Portal path issues
project_file = normalize_project_path(project_file)
export_dir = sanitize_path( export_dir = sanitize_path(
working_directory working_directory
) # Use working directory from config with sanitization ) # Use working directory from config with sanitization
@ -615,7 +689,17 @@ if __name__ == "__main__":
# 4. Open Project # 4. Open Project
print(f"Abriendo proyecto: {os.path.basename(project_file)}...") print(f"Abriendo proyecto: {os.path.basename(project_file)}...")
project_object = portal_instance.open_project(project_file_path=project_file) print(f"Ruta completa del proyecto: {project_file}")
try:
project_object = portal_instance.open_project(
project_file_path=project_file
)
except Exception as open_ex:
print(f"Error al abrir el proyecto: {open_ex}")
print("Intentando obtener proyecto ya abierto...")
project_object = None
if project_object is None: if project_object is None:
print( print(
"El proyecto podría estar ya abierto, intentando obtener el manejador..." "El proyecto podría estar ya abierto, intentando obtener el manejador..."
@ -640,8 +724,21 @@ if __name__ == "__main__":
print("\nProceso de exportación completado.") print("\nProceso de exportación completado.")
except ts.TiaException as tia_ex: except ValueError as val_ex:
print(f"\nError de TIA Portal Openness: {tia_ex}") # Handle TIA Portal Openness exceptions (they come as ValueError)
if "OpennessAccessException" in str(val_ex):
print(f"\nError de TIA Portal Openness: {val_ex}")
print("Posibles causas:")
print("- El proyecto puede estar corrupto o en un formato incompatible")
print(
"- El proyecto puede requerir actualización a una versión más reciente"
)
print(
"- Verificar que la ruta del proyecto no contenga caracteres especiales"
)
print("- Asegurarse de que TIA Portal esté instalado correctamente")
else:
print(f"\nError de valor: {val_ex}")
traceback.print_exc() traceback.print_exc()
except FileNotFoundError: except FileNotFoundError:
print(f"\nERROR: Archivo de proyecto no encontrado en {project_file}") print(f"\nERROR: Archivo de proyecto no encontrado en {project_file}")

View File

@ -19,7 +19,18 @@ 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 = {".ap18": "18.0", ".ap19": "19.0", ".ap20": "20.0"} SUPPORTED_TIA_VERSIONS = {
".ap15": "15.0",
".ap16": "16.0",
".ap17": "17.0",
".ap18": "18.0",
".ap19": "19.0",
".ap20": "20.0",
}
# Cross-references export support was introduced in TIA Portal V17+
# Earlier versions don't support the export_cross_references() method
CROSS_REFERENCES_SUPPORTED_VERSIONS = ["17.0", "18.0", "19.0", "20.0"]
# Filter for cross-references. Based on documentation: # Filter for cross-references. Based on documentation:
# 1: 'AllObjects', 2: 'ObjectsWithReferences', 3: 'ObjectsWithoutReferences', 4: 'UnusedObjects' # 1: 'AllObjects', 2: 'ObjectsWithReferences', 3: 'ObjectsWithoutReferences', 4: 'UnusedObjects'
@ -83,6 +94,34 @@ except Exception as e:
# --- Functions --- # --- Functions ---
def normalize_project_path(project_path):
"""Normalizes a project path to ensure it's compatible with TIA Portal."""
if not project_path:
return project_path
# Convert to Path object for easier manipulation
path_obj = Path(project_path)
# Resolve to absolute path and normalize
try:
normalized = path_obj.resolve()
# Convert back to string with Windows-style separators
normalized_str = str(normalized).replace("/", "\\")
print(f" Ruta original: {project_path}")
print(f" Ruta normalizada: {normalized_str}")
return normalized_str
except Exception as e:
print(f" ADVERTENCIA: Error al normalizar ruta: {e}")
return str(project_path)
def is_cross_references_supported(tia_version):
"""Check if cross-references export is supported in the given TIA Portal version."""
return tia_version in CROSS_REFERENCES_SUPPORTED_VERSIONS
def get_supported_filetypes(): def get_supported_filetypes():
"""Returns the supported file types for TIA Portal projects.""" """Returns the supported file types for TIA Portal projects."""
filetypes = [] filetypes = []
@ -112,9 +151,9 @@ def detect_tia_version(project_file_path):
print( print(
f"ADVERTENCIA: Extensión de archivo no reconocida '{file_extension}'. Extensiones soportadas: {list(SUPPORTED_TIA_VERSIONS.keys())}" f"ADVERTENCIA: Extensión de archivo no reconocida '{file_extension}'. Extensiones soportadas: {list(SUPPORTED_TIA_VERSIONS.keys())}"
) )
# Default to version 18.0 for backward compatibility # Default to version 15.0 for backward compatibility
print("Usando por defecto TIA Portal V18.0") print("Usando por defecto TIA Portal V15.0")
return "18.0" return "15.0"
def select_project_file(): def select_project_file():
@ -249,19 +288,31 @@ def export_plc_cross_references(
print(f" Exportación completada en {elapsed_time:.2f} segundos") print(f" Exportación completada en {elapsed_time:.2f} segundos")
blocks_cr_exported += 1 blocks_cr_exported += 1
exported_blocks.add(norm_block) exported_blocks.add(norm_block)
except RuntimeError as block_ex: except Exception as block_ex:
error_msg = str(block_ex)
if (
"NotSupportedException" in error_msg
and "not supported in this API version" in error_msg
):
print(
f" ERROR: Método export_cross_references() no soportado en esta versión de TIA Portal para el bloque {block_name}"
)
print(
f" Esta funcionalidad requiere TIA Portal V17.0 o superior."
)
elif "RuntimeError" in str(type(block_ex)):
print( print(
f" ERROR TIA al exportar referencias cruzadas para el bloque {block_name}: {block_ex}" f" ERROR TIA al exportar referencias cruzadas para el bloque {block_name}: {block_ex}"
) )
problematic_blocks.add(norm_block) else:
blocks_cr_skipped += 1
except Exception as block_ex:
print( print(
f" ERROR GENERAL al exportar referencias cruzadas para el bloque {block_name}: {block_ex}" f" ERROR GENERAL al exportar referencias cruzadas para el bloque {block_name}: {block_ex}"
) )
traceback.print_exc() traceback.print_exc()
problematic_blocks.add(norm_block) # Always mark as problematic
problematic_blocks.add(norm_block)
blocks_cr_skipped += 1 blocks_cr_skipped += 1
if _is_disposed_exception(block_ex): if _is_disposed_exception(block_ex):
# Escalamos para que el script pueda re-abrir el Portal y omitir el bloque # Escalamos para que el script pueda re-abrir el Portal y omitir el bloque
raise PortalDisposedException(block_ex, failed_block=block_name) raise PortalDisposedException(block_ex, failed_block=block_name)
@ -496,13 +547,39 @@ def open_portal_and_project(tia_version: str, project_file_path: str):
print("Conectado a TIA Portal.") print("Conectado a TIA Portal.")
print(f"ID del proceso del Portal: {portal.get_process_id()}") print(f"ID del proceso del Portal: {portal.get_process_id()}")
project_obj = portal.open_project(project_file_path=str(project_file_path)) # Normalize the project path
normalized_path = normalize_project_path(project_file_path)
print(f"Abriendo proyecto: {Path(normalized_path).name}...")
try:
project_obj = portal.open_project(project_file_path=normalized_path)
if project_obj is None: if project_obj is None:
print(
"El proyecto podría estar ya abierto, intentando obtener el manejador..."
)
project_obj = portal.get_project() project_obj = portal.get_project()
if project_obj is None: if project_obj is None:
raise Exception( raise Exception(
"No se pudo abrir u obtener el proyecto especificado tras la reapertura." "No se pudo abrir u obtener el proyecto especificado tras la reapertura."
) )
print("Proyecto abierto exitosamente.")
return portal, project_obj
except Exception as e:
error_msg = str(e)
print(f"ERROR al abrir proyecto: {error_msg}")
if "path" in error_msg.lower() and "cannot be" in error_msg.lower():
print(
f" Problema con formato de ruta. Ruta utilizada: '{normalized_path}'"
)
print(f" Ruta original: '{project_file_path}'")
# Re-raise with more context
raise Exception(f"Error al abrir proyecto TIA Portal: {error_msg}")
return portal, project_obj return portal, project_obj
@ -535,6 +612,23 @@ if __name__ == "__main__":
# 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)
# Check if cross-references export is supported in this version
if not is_cross_references_supported(tia_version):
print(
f"\nADVERTENCIA: La exportación de referencias cruzadas no está soportada en TIA Portal V{tia_version}"
)
print(
f"Las referencias cruzadas están soportadas desde TIA Portal V17.0 en adelante."
)
print(
"Versiones soportadas para referencias cruzadas:",
", ".join([f"V{v}" for v in CROSS_REFERENCES_SUPPORTED_VERSIONS]),
)
print(
"\nEl script se cerrará. Por favor use TIA Portal V17.0 o superior para exportar referencias cruzadas."
)
sys.exit(1)
# 3. Define Export Directory using working_directory and subfolder # 3. Define Export Directory using working_directory and subfolder
export_base_dir = Path(working_directory) export_base_dir = Path(working_directory)
try: try:
@ -640,7 +734,7 @@ if __name__ == "__main__":
print("\nProceso de exportación de referencias cruzadas completado.") print("\nProceso de exportación de referencias cruzadas completado.")
except RuntimeError as tia_ex: except Exception as tia_ex:
print(f"\nError de TIA Portal Openness: {tia_ex}") print(f"\nError de TIA Portal Openness: {tia_ex}")
traceback.print_exc() traceback.print_exc()
except FileNotFoundError: except FileNotFoundError:

View File

@ -1,5 +1,13 @@
[16:23:41] Iniciando ejecución de x1.py en D:\Trabajo\VM\45 - HENKEL - VM Auto Changeover\ExportTia... [16:48:57] Iniciando ejecución de x4.py en D:\Trabajo\VM\45 - HENKEL - VM Auto Changeover\ExportTia...
[16:23:41] --- Exportador de datos TIA Portal (Bloques, UDTs, Variables) --- [16:48:57] --- Exportador de Referencias Cruzadas de TIA Portal ---
[16:24:03] No se seleccionó ningún archivo de proyecto. Saliendo. [16:48:57] Configuración:
[16:24:04] Ejecución de x1.py finalizada (success). Duración: 0:00:22.833498. [16:48:57] - Tiempo esperado por bloque: 120 segundos (para logging)
[16:24:04] Log completo guardado en: D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\ObtainIOFromProjectTia\.log\log_x1.txt [16:48:57] - Máximo intentos de reapertura: 5
[16:48:57] - Filtro de referencias cruzadas: 1
[16:49:01] Versión de TIA Portal detectada: 16.0 (de la extensión .ap16)
[16:49:01] ADVERTENCIA: La exportación de referencias cruzadas no está soportada en TIA Portal V16.0
[16:49:01] Las referencias cruzadas están soportadas desde TIA Portal V17.0 en adelante.
[16:49:01] Versiones soportadas para referencias cruzadas: V17.0, V18.0, V19.0, V20.0
[16:49:01] El script se cerrará. Por favor use TIA Portal V17.0 o superior para exportar referencias cruzadas.
[16:49:02] Ejecución de x4.py finalizada (error). Duración: 0:00:05.217693.
[16:49:02] Log completo guardado en: D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\ObtainIOFromProjectTia\.log\log_x4.txt