""" 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") # 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_directorypath=str( blocks_cr_path ), # API likely needs string path filter=CROSS_REF_FILTER, ) blocks_cr_exported += 1 except RuntimeError 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_directorypath=str(tags_cr_path), filter=CROSS_REF_FILTER ) tags_cr_exported += 1 except RuntimeError 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_directorypath=str(udts_cr_path), filter=CROSS_REF_FILTER ) udts_cr_exported += 1 except RuntimeError 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_directorypath=str(sys_blocks_cr_path), filter=CROSS_REF_FILTER, ) sys_blocks_cr_exported += 1 except RuntimeError 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_directorypath=str(sw_units_cr_path), filter=CROSS_REF_FILTER, ) sw_units_cr_exported += 1 except RuntimeError 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 # The export base directory is the working directory. PLC-specific folders will be created inside. export_base_dir = Path(working_directory) try: # Ensure the base working directory exists (it should, but check doesn't hurt) export_base_dir.mkdir(parents=True, exist_ok=True) print(f"\nSelected Project: {project_file}") print(f"Using Base Export Directory: {export_base_dir.resolve()}") 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_base_dir, # Pass the base directory ) print("\nCross-reference export process completed.") except RuntimeError 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.")