From 8fcb4410039833852828a52c4959496b376fe7a7 Mon Sep 17 00:00:00 2001 From: Miguel Date: Mon, 5 May 2025 12:32:17 +0200 Subject: [PATCH] Agregar script para exportar referencias cruzadas de proyectos TIA Portal a archivos --- .../ObtainIOFromProjectTia/x4.py | 423 ++++++++++++++++++ 1 file changed, 423 insertions(+) create mode 100644 backend/script_groups/ObtainIOFromProjectTia/x4.py diff --git a/backend/script_groups/ObtainIOFromProjectTia/x4.py b/backend/script_groups/ObtainIOFromProjectTia/x4.py new file mode 100644 index 0000000..8b21ee5 --- /dev/null +++ b/backend/script_groups/ObtainIOFromProjectTia/x4.py @@ -0,0 +1,423 @@ +""" +export_cross_references_from_tia : Script para exportar las referencias cruzadas +de un proyecto TIA Portal a archivos (probablemente XML). +""" + +import tkinter as tk +from tkinter import filedialog +import os +import sys +import traceback +from pathlib import Path # Import Path for easier path manipulation + +script_root = os.path.dirname( + os.path.dirname(os.path.dirname(os.path.dirname(__file__))) +) +sys.path.append(script_root) +from backend.script_utils import load_configuration + +# --- Configuration --- +TIA_PORTAL_VERSION = "18.0" # Target TIA Portal version (e.g., "18.0") +CROSS_REF_SUBFOLDER = "cross_ref" # Subfolder name within working_directory +# Filter for cross-references. Based on documentation: +# 1: 'AllObjects', 2: 'ObjectsWithReferences', 3: 'ObjectsWithoutReferences', 4: 'UnusedObjects' +# Using 1 to export all. 0 might also work as a default in some API versions. +CROSS_REF_FILTER = 1 + +# --- TIA Scripting Import Handling --- +# (Same import handling as x1.py) +if os.getenv("TIA_SCRIPTING"): + sys.path.append(os.getenv("TIA_SCRIPTING")) +else: + pass + +try: + import siemens_tia_scripting as ts +except ImportError: + print("ERROR: Failed to import 'siemens_tia_scripting'.") + print("Ensure:") + print(f"1. TIA Portal Openness for V{TIA_PORTAL_VERSION} is installed.") + print( + "2. The 'siemens_tia_scripting' Python module is installed (pip install ...) or" + ) + print( + " the path to its binaries is set in the 'TIA_SCRIPTING' environment variable." + ) + print( + "3. You are using a compatible Python version (e.g., 3.12.X as per documentation)." + ) + sys.exit(1) +except Exception as e: + print(f"An unexpected error occurred during import: {e}") + traceback.print_exc() + sys.exit(1) + +# --- Functions --- + + +def select_project_file(): + """Opens a dialog to select a TIA Portal project file.""" + root = tk.Tk() + root.withdraw() # Hide the main tkinter window + file_path = filedialog.askopenfilename( + title="Select TIA Portal Project File", + filetypes=[ + ( + f"TIA Portal V{TIA_PORTAL_VERSION} Projects", + f"*.ap{TIA_PORTAL_VERSION.split('.')[0]}", + ) + ], # e.g. *.ap18 + ) + root.destroy() + if not file_path: + print("No project file selected. Exiting.") + sys.exit(0) + return file_path + + +def export_plc_cross_references(plc, export_base_dir): + """Exports cross-references for various elements from a given PLC.""" + plc_name = plc.get_name() + print(f"\n--- Processing PLC: {plc_name} ---") + + # Define base export path for this PLC's cross-references + plc_export_dir = export_base_dir / plc_name + plc_export_dir.mkdir(parents=True, exist_ok=True) # Use pathlib's mkdir + + # --- Export Program Block Cross-References --- + blocks_cr_exported = 0 + blocks_cr_skipped = 0 + print(f"\n[PLC: {plc_name}] Exporting Program Block Cross-References...") + blocks_cr_path = plc_export_dir / "ProgramBlocks_CR" + blocks_cr_path.mkdir(exist_ok=True) + print(f" Target: {blocks_cr_path}") + + try: + # Assuming get_program_blocks() doesn't need folder_path to get all blocks + program_blocks = plc.get_program_blocks() + print(f" Found {len(program_blocks)} program blocks.") + for block in program_blocks: + block_name = block.get_name() + print(f" Processing block: {block_name}...") + try: + # Note: Consistency check might not be needed/available before cross-ref export + print(f" Exporting cross-references for {block_name}...") + block.export_cross_references( + target_directory_path=str( + blocks_cr_path + ), # API likely needs string path + filter=CROSS_REF_FILTER, + ) + blocks_cr_exported += 1 + except ts.TiaException as block_ex: + print( + f" TIA ERROR exporting cross-references for block {block_name}: {block_ex}" + ) + blocks_cr_skipped += 1 + except Exception as block_ex: + print( + f" GENERAL ERROR exporting cross-references for block {block_name}: {block_ex}" + ) + traceback.print_exc() # Print stack trace for general errors + blocks_cr_skipped += 1 + print( + f" Program Block CR Export Summary: Exported={blocks_cr_exported}, Skipped/Errors={blocks_cr_skipped}" + ) + except AttributeError: + print( + " AttributeError: Could not find 'get_program_blocks' on PLC object. Skipping Program Blocks." + ) + except Exception as e: + print(f" ERROR accessing Program Blocks for cross-reference export: {e}") + traceback.print_exc() + + # --- Export PLC Tag Table Cross-References --- + tags_cr_exported = 0 + tags_cr_skipped = 0 + print(f"\n[PLC: {plc_name}] Exporting PLC Tag Table Cross-References...") + tags_cr_path = plc_export_dir / "PlcTags_CR" + tags_cr_path.mkdir(exist_ok=True) + print(f" Target: {tags_cr_path}") + + try: + # Assuming get_plc_tag_tables() doesn't need folder_path to get all tables + tag_tables = plc.get_plc_tag_tables() + print(f" Found {len(tag_tables)} Tag Tables.") + for table in tag_tables: + table_name = table.get_name() + print(f" Processing Tag Table: {table_name}...") + try: + print(f" Exporting cross-references for {table_name}...") + table.export_cross_references( + target_directory_path=str(tags_cr_path), filter=CROSS_REF_FILTER + ) + tags_cr_exported += 1 + except ts.TiaException as table_ex: + print( + f" TIA ERROR exporting cross-references for Tag Table {table_name}: {table_ex}" + ) + tags_cr_skipped += 1 + except Exception as table_ex: + print( + f" GENERAL ERROR exporting cross-references for Tag Table {table_name}: {table_ex}" + ) + traceback.print_exc() + tags_cr_skipped += 1 + print( + f" Tag Table CR Export Summary: Exported={tags_cr_exported}, Skipped/Errors={tags_cr_skipped}" + ) + except AttributeError: + print( + " AttributeError: Could not find 'get_plc_tag_tables' on PLC object. Skipping Tag Tables." + ) + except Exception as e: + print(f" ERROR accessing Tag Tables for cross-reference export: {e}") + traceback.print_exc() + + # --- Export PLC Data Type (UDT) Cross-References --- + udts_cr_exported = 0 + udts_cr_skipped = 0 + print(f"\n[PLC: {plc_name}] Exporting PLC Data Type (UDT) Cross-References...") + udts_cr_path = plc_export_dir / "PlcDataTypes_CR" + udts_cr_path.mkdir(exist_ok=True) + print(f" Target: {udts_cr_path}") + + try: + # Assuming get_user_data_types() doesn't need folder_path to get all UDTs + udts = plc.get_user_data_types() + print(f" Found {len(udts)} UDTs.") + for udt in udts: + udt_name = udt.get_name() + print(f" Processing UDT: {udt_name}...") + try: + print(f" Exporting cross-references for {udt_name}...") + udt.export_cross_references( + target_directory_path=str(udts_cr_path), filter=CROSS_REF_FILTER + ) + udts_cr_exported += 1 + except ts.TiaException as udt_ex: + print( + f" TIA ERROR exporting cross-references for UDT {udt_name}: {udt_ex}" + ) + udts_cr_skipped += 1 + except Exception as udt_ex: + print( + f" GENERAL ERROR exporting cross-references for UDT {udt_name}: {udt_ex}" + ) + traceback.print_exc() + udts_cr_skipped += 1 + print( + f" UDT CR Export Summary: Exported={udts_cr_exported}, Skipped/Errors={udts_cr_skipped}" + ) + except AttributeError: + print( + " AttributeError: Could not find 'get_user_data_types' on PLC object. Skipping UDTs." + ) + except Exception as e: + print(f" ERROR accessing UDTs for cross-reference export: {e}") + traceback.print_exc() + + # --- Export System Block Cross-References --- + sys_blocks_cr_exported = 0 + sys_blocks_cr_skipped = 0 + print(f"\n[PLC: {plc_name}] Attempting to Export System Block Cross-References...") + sys_blocks_cr_path = plc_export_dir / "SystemBlocks_CR" + sys_blocks_cr_path.mkdir(exist_ok=True) + print(f" Target: {sys_blocks_cr_path}") + + try: + # Check if method exists before calling + if hasattr(plc, "get_system_blocks"): + system_blocks = plc.get_system_blocks() + print( + f" Found {len(system_blocks)} system blocks (using get_system_blocks)." + ) + for sys_block in system_blocks: + sys_block_name = sys_block.get_name() + print(f" Processing System Block: {sys_block_name}...") + try: + print(f" Exporting cross-references for {sys_block_name}...") + sys_block.export_cross_references( + target_directory_path=str(sys_blocks_cr_path), + filter=CROSS_REF_FILTER, + ) + sys_blocks_cr_exported += 1 + except ts.TiaException as sys_ex: + print( + f" TIA ERROR exporting cross-references for System Block {sys_block_name}: {sys_ex}" + ) + sys_blocks_cr_skipped += 1 + except Exception as sys_ex: + print( + f" GENERAL ERROR exporting cross-references for System Block {sys_block_name}: {sys_ex}" + ) + traceback.print_exc() + sys_blocks_cr_skipped += 1 + else: + print( + " Method 'get_system_blocks' not found on PLC object. Skipping System Blocks." + ) + # Alternative: Try navigating DeviceItems if needed, but that's more complex. + + print( + f" System Block CR Export Summary: Exported={sys_blocks_cr_exported}, Skipped/Errors={sys_blocks_cr_skipped}" + ) + except AttributeError: # Catch if get_name() or other methods fail on sys_block + print( + " AttributeError during System Block processing. Skipping remaining System Blocks." + ) + traceback.print_exc() + except Exception as e: + print( + f" ERROR accessing/processing System Blocks for cross-reference export: {e}" + ) + traceback.print_exc() + + # --- Export Software Unit Cross-References --- + sw_units_cr_exported = 0 + sw_units_cr_skipped = 0 + print(f"\n[PLC: {plc_name}] Attempting to Export Software Unit Cross-References...") + sw_units_cr_path = plc_export_dir / "SoftwareUnits_CR" + sw_units_cr_path.mkdir(exist_ok=True) + print(f" Target: {sw_units_cr_path}") + + try: + # Check if method exists before calling + if hasattr(plc, "get_software_units"): + software_units = plc.get_software_units() + print(f" Found {len(software_units)} Software Units.") + for unit in software_units: + unit_name = unit.get_name() + print(f" Processing Software Unit: {unit_name}...") + try: + print(f" Exporting cross-references for {unit_name}...") + unit.export_cross_references( + target_directory_path=str(sw_units_cr_path), + filter=CROSS_REF_FILTER, + ) + sw_units_cr_exported += 1 + except ts.TiaException as unit_ex: + print( + f" TIA ERROR exporting cross-references for Software Unit {unit_name}: {unit_ex}" + ) + sw_units_cr_skipped += 1 + except Exception as unit_ex: + print( + f" GENERAL ERROR exporting cross-references for Software Unit {unit_name}: {unit_ex}" + ) + traceback.print_exc() + sw_units_cr_skipped += 1 + print( + f" Software Unit CR Export Summary: Exported={sw_units_cr_exported}, Skipped/Errors={sw_units_cr_skipped}" + ) + else: + print( + " Method 'get_software_units' not found on PLC object. Skipping Software Units." + ) + except AttributeError: # Catch if get_name() or other methods fail on unit + print( + " AttributeError during Software Unit processing. Skipping remaining Software Units." + ) + traceback.print_exc() + except Exception as e: + print( + f" ERROR accessing/processing Software Units for cross-reference export: {e}" + ) + traceback.print_exc() + + print(f"\n--- Finished processing PLC: {plc_name} ---") + + +# --- Main Script --- + +if __name__ == "__main__": + configs = load_configuration() + working_directory = configs.get("working_directory") + + print("--- TIA Portal Cross-Reference Exporter ---") + + # Validate working directory + if not working_directory or not os.path.isdir(working_directory): + print("ERROR: Working directory not set or invalid in configuration.") + print("Please configure the working directory using the main application.") + sys.exit(1) + + # 1. Select Project File + project_file = select_project_file() + + # 2. Define Export Directory using working_directory and subfolder + export_dir = Path(working_directory) / CROSS_REF_SUBFOLDER + try: + export_dir.mkdir(parents=True, exist_ok=True) + print(f"\nSelected Project: {project_file}") + print( + f"Using Export Directory: {export_dir.resolve()}" + ) # Use resolve() for absolute path + except Exception as e: + print(f"ERROR: Could not create export directory '{export_dir}'. Error: {e}") + sys.exit(1) + + portal_instance = None + project_object = None + + try: + # 3. Connect to TIA Portal + print(f"\nConnecting to TIA Portal V{TIA_PORTAL_VERSION}...") + # Connect using WithGraphicalUserInterface mode for visibility + portal_instance = ts.open_portal( + version=TIA_PORTAL_VERSION, + portal_mode=ts.Enums.PortalMode.WithGraphicalUserInterface, + ) + print("Connected to TIA Portal.") + print(f"Portal Process ID: {portal_instance.get_process_id()}") + + # 4. Open Project + print(f"Opening project: {os.path.basename(project_file)}...") + project_path_obj = Path(project_file) # Use Path object + project_object = portal_instance.open_project( + project_file_path=str(project_path_obj) + ) + if project_object is None: + print("Project might already be open, attempting to get handle...") + project_object = portal_instance.get_project() + if project_object is None: + raise Exception("Failed to open or get the specified project.") + print("Project opened successfully.") + + # 5. Get PLCs + plcs = project_object.get_plcs() + if not plcs: + print("No PLC devices found in the project.") + else: + print( + f"Found {len(plcs)} PLC(s). Starting cross-reference export process..." + ) + + # 6. Iterate and Export Cross-References for each PLC + for plc_device in plcs: + export_plc_cross_references( + plc=plc_device, + export_base_dir=export_dir, # Pass the specific cross-ref dir + ) + + print("\nCross-reference export process completed.") + + except ts.TiaException as tia_ex: + print(f"\nTIA Portal Openness Error: {tia_ex}") + traceback.print_exc() + except FileNotFoundError: + print(f"\nERROR: Project file not found at {project_file}") + except Exception as e: + print(f"\nAn unexpected error occurred: {e}") + traceback.print_exc() + finally: + # 7. Cleanup + if portal_instance: + try: + print("\nClosing TIA Portal...") + portal_instance.close_portal() + print("TIA Portal closed.") + except Exception as close_ex: + print(f"Error during TIA Portal cleanup: {close_ex}") + + print("\nScript finished.")