424 lines
16 KiB
Python
424 lines
16 KiB
Python
"""
|
|
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_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
|
|
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 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.")
|