Adaptacion de los scripts de ObtainIOFromProjectTia a la logica de los directorios de trabajo

This commit is contained in:
Miguel 2025-05-02 23:27:36 +02:00
parent 239126bb96
commit b018e82848
9 changed files with 2267 additions and 162 deletions

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,9 @@
{
"level1": {
"api_key": "your-api-key-here",
"model": "gpt-3.5-turbo"
},
"level2": {},
"level3": {},
"working_directory": "C:\\Trabajo\\SIDEL\\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\\Reporte\\IOExport"
}

View File

@ -0,0 +1,6 @@
{
"path": "C:\\Trabajo\\SIDEL\\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\\Reporte\\IOExport",
"history": [
"C:\\Trabajo\\SIDEL\\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\\Reporte\\IOExport"
]
}

View File

@ -1,7 +1,8 @@
""" """
export_logic_from_tia : export_logic_from_tia :
Script para exportar el software de un PLC desde TIA Portal en archivos XML y SCL. Script para exportar el software de un PLC desde TIA Portal en archivos XML y SCL.
""" """
import tkinter as tk import tkinter as tk
from tkinter import filedialog from tkinter import filedialog
import os import os
@ -15,31 +16,42 @@ sys.path.append(script_root)
from backend.script_utils import load_configuration from backend.script_utils import load_configuration
# --- Configuration --- # --- Configuration ---
TIA_PORTAL_VERSION = "18.0" # Target TIA Portal version (e.g., "18.0") TIA_PORTAL_VERSION = "18.0" # Target TIA Portal version (e.g., "18.0")
EXPORT_OPTIONS = None # Use default export options EXPORT_OPTIONS = None # Use default export options
KEEP_FOLDER_STRUCTURE = True # Replicate TIA project folder structure in export directory KEEP_FOLDER_STRUCTURE = (
True # Replicate TIA project folder structure in export directory
)
# --- TIA Scripting Import Handling --- # --- TIA Scripting Import Handling ---
# Check if the TIA_SCRIPTING environment variable is set # Check if the TIA_SCRIPTING environment variable is set
if os.getenv('TIA_SCRIPTING'): if os.getenv("TIA_SCRIPTING"):
sys.path.append(os.getenv('TIA_SCRIPTING')) sys.path.append(os.getenv("TIA_SCRIPTING"))
else: else:
# Optional: Define a fallback path if the environment variable isn't set # Optional: Define a fallback path if the environment variable isn't set
# fallback_path = "C:\\path\\to\\your\\TIA_Scripting_binaries" # fallback_path = "C:\\path\\to\\your\\TIA_Scripting_binaries"
# if os.path.exists(fallback_path): # if os.path.exists(fallback_path):
# sys.path.append(fallback_path) # sys.path.append(fallback_path)
pass # Allow import to fail if not found pass # Allow import to fail if not found
try: try:
import siemens_tia_scripting as ts import siemens_tia_scripting as ts
EXPORT_OPTIONS = ts.Enums.ExportOptions.WithDefaults # Set default options now that 'ts' is imported
EXPORT_OPTIONS = (
ts.Enums.ExportOptions.WithDefaults
) # Set default options now that 'ts' is imported
except ImportError: except ImportError:
print("ERROR: Failed to import 'siemens_tia_scripting'.") print("ERROR: Failed to import 'siemens_tia_scripting'.")
print("Ensure:") print("Ensure:")
print(f"1. TIA Portal Openness for V{TIA_PORTAL_VERSION} is installed.") 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(
print(" the path to its binaries is set in the 'TIA_SCRIPTING' environment variable.") "2. The 'siemens_tia_scripting' Python module is installed (pip install ...) or"
print("3. You are using a compatible Python version (e.g., 3.12.X as per documentation).") )
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) sys.exit(1)
except Exception as e: except Exception as e:
print(f"An unexpected error occurred during import: {e}") print(f"An unexpected error occurred during import: {e}")
@ -48,13 +60,19 @@ except Exception as e:
# --- Functions --- # --- Functions ---
def select_project_file(): def select_project_file():
"""Opens a dialog to select a TIA Portal project file.""" """Opens a dialog to select a TIA Portal project file."""
root = tk.Tk() root = tk.Tk()
root.withdraw() # Hide the main tkinter window root.withdraw() # Hide the main tkinter window
file_path = filedialog.askopenfilename( file_path = filedialog.askopenfilename(
title="Select TIA Portal Project File", 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 filetypes=[
(
f"TIA Portal V{TIA_PORTAL_VERSION} Projects",
f"*.ap{TIA_PORTAL_VERSION.split('.')[0]}",
)
], # e.g. *.ap18
) )
root.destroy() root.destroy()
if not file_path: if not file_path:
@ -62,19 +80,19 @@ def select_project_file():
sys.exit(0) sys.exit(0)
return file_path return file_path
def select_export_directory(): def select_export_directory():
"""Opens a dialog to select the export directory.""" """Opens a dialog to select the export directory."""
root = tk.Tk() root = tk.Tk()
root.withdraw() # Hide the main tkinter window root.withdraw() # Hide the main tkinter window
dir_path = filedialog.askdirectory( dir_path = filedialog.askdirectory(title="Select Export Directory")
title="Select Export Directory"
)
root.destroy() root.destroy()
if not dir_path: if not dir_path:
print("No export directory selected. Exiting.") print("No export directory selected. Exiting.")
sys.exit(0) sys.exit(0)
return dir_path return dir_path
def export_plc_data(plc, export_base_dir): def export_plc_data(plc, export_base_dir):
"""Exports Blocks, UDTs, and Tag Tables from a given PLC.""" """Exports Blocks, UDTs, and Tag Tables from a given PLC."""
plc_name = plc.get_name() plc_name = plc.get_name()
@ -96,42 +114,52 @@ def export_plc_data(plc, export_base_dir):
print(f" SCL Target: {scl_blocks_path}") print(f" SCL Target: {scl_blocks_path}")
try: try:
program_blocks = plc.get_program_blocks() # program_blocks = plc.get_program_blocks() #
print(f" Found {len(program_blocks)} program blocks.") print(f" Found {len(program_blocks)} program blocks.")
for block in program_blocks: for block in program_blocks:
block_name = block.get_name() # Assuming get_name() exists block_name = block.get_name() # Assuming get_name() exists
print(f" Processing block: {block_name}...") print(f" Processing block: {block_name}...")
try: try:
if not block.is_consistent(): # if not block.is_consistent(): #
print(f" Compiling block {block_name}...") print(f" Compiling block {block_name}...")
block.compile() # block.compile() #
if not block.is_consistent(): if not block.is_consistent():
print(f" WARNING: Block {block_name} inconsistent after compile. Skipping.") print(
blocks_skipped += 1 f" WARNING: Block {block_name} inconsistent after compile. Skipping."
continue )
blocks_skipped += 1
continue
print(f" Exporting {block_name} as XML...") print(f" Exporting {block_name} as XML...")
block.export(target_directory_path=xml_blocks_path, # block.export(
export_options=EXPORT_OPTIONS, # target_directory_path=xml_blocks_path, #
export_format=ts.Enums.ExportFormats.SimaticML, # export_options=EXPORT_OPTIONS, #
keep_folder_structure=KEEP_FOLDER_STRUCTURE) # export_format=ts.Enums.ExportFormats.SimaticML, #
keep_folder_structure=KEEP_FOLDER_STRUCTURE,
) #
try: try:
prog_language = block.get_property(name="ProgrammingLanguage") prog_language = block.get_property(name="ProgrammingLanguage")
if prog_language == "SCL": if prog_language == "SCL":
print(f" Exporting {block_name} as SCL...") print(f" Exporting {block_name} as SCL...")
block.export(target_directory_path=scl_blocks_path, block.export(
export_options=EXPORT_OPTIONS, target_directory_path=scl_blocks_path,
export_format=ts.Enums.ExportFormats.ExternalSource, # export_options=EXPORT_OPTIONS,
keep_folder_structure=KEEP_FOLDER_STRUCTURE) export_format=ts.Enums.ExportFormats.ExternalSource, #
keep_folder_structure=KEEP_FOLDER_STRUCTURE,
)
except Exception as prop_ex: except Exception as prop_ex:
print(f" Could not get ProgrammingLanguage for {block_name}. Skipping SCL. Error: {prop_ex}") print(
f" Could not get ProgrammingLanguage for {block_name}. Skipping SCL. Error: {prop_ex}"
)
blocks_exported += 1 blocks_exported += 1
except Exception as block_ex: except Exception as block_ex:
print(f" ERROR exporting block {block_name}: {block_ex}") print(f" ERROR exporting block {block_name}: {block_ex}")
blocks_skipped += 1 blocks_skipped += 1
print(f" Program Blocks Export Summary: Exported={blocks_exported}, Skipped/Errors={blocks_skipped}") print(
f" Program Blocks Export Summary: Exported={blocks_exported}, Skipped/Errors={blocks_skipped}"
)
except Exception as e: except Exception as e:
print(f" ERROR processing Program Blocks: {e}") print(f" ERROR processing Program Blocks: {e}")
traceback.print_exc() traceback.print_exc()
@ -145,30 +173,36 @@ def export_plc_data(plc, export_base_dir):
print(f" Target: {udt_export_path}") print(f" Target: {udt_export_path}")
try: try:
udts = plc.get_user_data_types() # udts = plc.get_user_data_types() #
print(f" Found {len(udts)} UDTs.") print(f" Found {len(udts)} UDTs.")
for udt in udts: for udt in udts:
udt_name = udt.get_name() # udt_name = udt.get_name() #
print(f" Processing UDT: {udt_name}...") print(f" Processing UDT: {udt_name}...")
try: try:
if not udt.is_consistent(): # if not udt.is_consistent(): #
print(f" Compiling UDT {udt_name}...") print(f" Compiling UDT {udt_name}...")
udt.compile() # udt.compile() #
if not udt.is_consistent(): if not udt.is_consistent():
print(f" WARNING: UDT {udt_name} inconsistent after compile. Skipping.") print(
f" WARNING: UDT {udt_name} inconsistent after compile. Skipping."
)
udts_skipped += 1 udts_skipped += 1
continue continue
print(f" Exporting {udt_name}...") print(f" Exporting {udt_name}...")
udt.export(target_directory_path=udt_export_path, # udt.export(
export_options=EXPORT_OPTIONS, # target_directory_path=udt_export_path, #
# export_format defaults to SimaticML for UDTs export_options=EXPORT_OPTIONS, #
keep_folder_structure=KEEP_FOLDER_STRUCTURE) # # export_format defaults to SimaticML for UDTs
keep_folder_structure=KEEP_FOLDER_STRUCTURE,
) #
udts_exported += 1 udts_exported += 1
except Exception as udt_ex: except Exception as udt_ex:
print(f" ERROR exporting UDT {udt_name}: {udt_ex}") print(f" ERROR exporting UDT {udt_name}: {udt_ex}")
udts_skipped += 1 udts_skipped += 1
print(f" UDT Export Summary: Exported={udts_exported}, Skipped/Errors={udts_skipped}") print(
f" UDT Export Summary: Exported={udts_exported}, Skipped/Errors={udts_skipped}"
)
except Exception as e: except Exception as e:
print(f" ERROR processing UDTs: {e}") print(f" ERROR processing UDTs: {e}")
traceback.print_exc() traceback.print_exc()
@ -182,23 +216,27 @@ def export_plc_data(plc, export_base_dir):
print(f" Target: {tags_export_path}") print(f" Target: {tags_export_path}")
try: try:
tag_tables = plc.get_plc_tag_tables() # tag_tables = plc.get_plc_tag_tables() #
print(f" Found {len(tag_tables)} Tag Tables.") print(f" Found {len(tag_tables)} Tag Tables.")
for table in tag_tables: for table in tag_tables:
table_name = table.get_name() # table_name = table.get_name() #
print(f" Processing Tag Table: {table_name}...") print(f" Processing Tag Table: {table_name}...")
try: try:
# Note: Consistency check might not be available/needed for tag tables like blocks/UDTs # Note: Consistency check might not be available/needed for tag tables like blocks/UDTs
print(f" Exporting {table_name}...") print(f" Exporting {table_name}...")
table.export(target_directory_path=tags_export_path, # table.export(
export_options=EXPORT_OPTIONS, # target_directory_path=tags_export_path, #
# export_format defaults to SimaticML for Tag Tables export_options=EXPORT_OPTIONS, #
keep_folder_structure=KEEP_FOLDER_STRUCTURE) # # export_format defaults to SimaticML for Tag Tables
keep_folder_structure=KEEP_FOLDER_STRUCTURE,
) #
tags_exported += 1 tags_exported += 1
except Exception as table_ex: except Exception as table_ex:
print(f" ERROR exporting Tag Table {table_name}: {table_ex}") print(f" ERROR exporting Tag Table {table_name}: {table_ex}")
tags_skipped += 1 tags_skipped += 1
print(f" Tag Table Export Summary: Exported={tags_exported}, Skipped/Errors={tags_skipped}") print(
f" Tag Table Export Summary: Exported={tags_exported}, Skipped/Errors={tags_skipped}"
)
except Exception as e: except Exception as e:
print(f" ERROR processing Tag Tables: {e}") print(f" ERROR processing Tag Tables: {e}")
traceback.print_exc() traceback.print_exc()
@ -208,17 +246,25 @@ def export_plc_data(plc, export_base_dir):
# --- Main Script --- # --- Main Script ---
if __name__ == "__main__": if __name__ == "__main__":
configs = load_configuration() configs = load_configuration()
working_directory = configs.get("working_directory")
print("--- TIA Portal Data Exporter (Blocks, UDTs, Tags) ---") print("--- TIA Portal Data Exporter (Blocks, UDTs, Tags) ---")
# 1. Select Files/Folders # 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, Export Directory comes from config
project_file = select_project_file() project_file = select_project_file()
export_dir = select_export_directory() export_dir = working_directory # Use working directory from config
print(f"\nSelected Project: {project_file}") print(f"\nSelected Project: {project_file}")
print(f"Selected Export Directory: {export_dir}") print(f"Using Export Directory (Working Directory): {export_dir}")
portal_instance = None portal_instance = None
project_object = None project_object = None
@ -228,31 +274,33 @@ if __name__ == "__main__":
print(f"\nConnecting to TIA Portal V{TIA_PORTAL_VERSION}...") print(f"\nConnecting to TIA Portal V{TIA_PORTAL_VERSION}...")
portal_instance = ts.open_portal( portal_instance = ts.open_portal(
version=TIA_PORTAL_VERSION, version=TIA_PORTAL_VERSION,
portal_mode=ts.Enums.PortalMode.WithGraphicalUserInterface portal_mode=ts.Enums.PortalMode.WithGraphicalUserInterface,
) )
print("Connected to TIA Portal.") print("Connected to TIA Portal.")
print(f"Portal Process ID: {portal_instance.get_process_id()}") # print(f"Portal Process ID: {portal_instance.get_process_id()}") #
# 3. Open Project # 3. Open Project
print(f"Opening project: {os.path.basename(project_file)}...") print(f"Opening project: {os.path.basename(project_file)}...")
project_object = portal_instance.open_project(project_file_path=project_file) # project_object = portal_instance.open_project(project_file_path=project_file) #
if project_object is None: if project_object is None:
print("Project might already be open, attempting to get handle...") print("Project might already be open, attempting to get handle...")
project_object = portal_instance.get_project() # project_object = portal_instance.get_project() #
if project_object is None: if project_object is None:
raise Exception("Failed to open or get the specified project.") raise Exception("Failed to open or get the specified project.")
print("Project opened successfully.") print("Project opened successfully.")
# 4. Get PLCs # 4. Get PLCs
plcs = project_object.get_plcs() # plcs = project_object.get_plcs() #
if not plcs: if not plcs:
print("No PLC devices found in the project.") print("No PLC devices found in the project.")
else: else:
print(f"Found {len(plcs)} PLC(s). Starting export process...") print(f"Found {len(plcs)} PLC(s). Starting export process...")
# 5. Iterate and Export Data for each PLC # 5. Iterate and Export Data for each PLC
for plc_device in plcs: for plc_device in plcs:
export_plc_data(plc=plc_device, export_base_dir=export_dir) export_plc_data(
plc=plc_device, export_base_dir=export_dir
) # Pass export_dir
print("\nExport process completed.") print("\nExport process completed.")
@ -269,9 +317,9 @@ if __name__ == "__main__":
if portal_instance: if portal_instance:
try: try:
print("\nClosing TIA Portal...") print("\nClosing TIA Portal...")
portal_instance.close_portal() # portal_instance.close_portal() #
print("TIA Portal closed.") print("TIA Portal closed.")
except Exception as close_ex: except Exception as close_ex:
print(f"Error during TIA Portal cleanup: {close_ex}") print(f"Error during TIA Portal cleanup: {close_ex}")
print("\nScript finished.") print("\nScript finished.")

View File

@ -1,13 +1,15 @@
""" """
export_CAx_from_tia : export_CAx_from_tia :
Script que exporta los datos CAx de un proyecto de TIA Portal y genera un resumen en Markdown. Script que exporta los datos CAx de un proyecto de TIA Portal y genera un resumen en Markdown.
""" """
import tkinter as tk import tkinter as tk
from tkinter import filedialog from tkinter import filedialog
import os import os
import sys import sys
import traceback import traceback
import xml.etree.ElementTree as ET # Library to parse XML (AML) import xml.etree.ElementTree as ET # Library to parse XML (AML)
from pathlib import Path # Import Path
script_root = os.path.dirname( script_root = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.dirname(__file__))) os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
@ -16,12 +18,12 @@ sys.path.append(script_root)
from backend.script_utils import load_configuration from backend.script_utils import load_configuration
# --- Configuration --- # --- Configuration ---
TIA_PORTAL_VERSION = "18.0" # Target TIA Portal version TIA_PORTAL_VERSION = "18.0" # Target TIA Portal version
# --- TIA Scripting Import Handling --- # --- TIA Scripting Import Handling ---
# (Same import handling as the previous script) # (Same import handling as the previous script)
if os.getenv('TIA_SCRIPTING'): if os.getenv("TIA_SCRIPTING"):
sys.path.append(os.getenv('TIA_SCRIPTING')) sys.path.append(os.getenv("TIA_SCRIPTING"))
else: else:
pass pass
@ -38,13 +40,19 @@ except Exception as e:
# --- Functions --- # --- Functions ---
def select_project_file(): def select_project_file():
"""Opens a dialog to select a TIA Portal project file.""" """Opens a dialog to select a TIA Portal project file."""
root = tk.Tk() root = tk.Tk()
root.withdraw() root.withdraw()
file_path = filedialog.askopenfilename( file_path = filedialog.askopenfilename(
title="Select TIA Portal Project File", title="Select TIA Portal Project File",
filetypes=[(f"TIA Portal V{TIA_PORTAL_VERSION} Projects", f"*.ap{TIA_PORTAL_VERSION.split('.')[0]}")] filetypes=[
(
f"TIA Portal V{TIA_PORTAL_VERSION} Projects",
f"*.ap{TIA_PORTAL_VERSION.split('.')[0]}",
)
],
) )
root.destroy() root.destroy()
if not file_path: if not file_path:
@ -52,6 +60,7 @@ def select_project_file():
sys.exit(0) sys.exit(0)
return file_path return file_path
def select_output_directory(): def select_output_directory():
"""Opens a dialog to select the output directory.""" """Opens a dialog to select the output directory."""
root = tk.Tk() root = tk.Tk()
@ -65,35 +74,42 @@ def select_output_directory():
sys.exit(0) sys.exit(0)
return dir_path 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
# This basic approach tries common prefixes or no prefix # This basic approach tries common prefixes or no prefix
namespaces = { namespaces = {
'': element.tag.split('}')[0][1:] if '}' in element.tag else '', # Default namespace if present "": (
'caex': 'http://www.dke.de/CAEX', # Common CAEX namespace element.tag.split("}")[0][1:] if "}" in element.tag else ""
), # Default namespace if present
"caex": "http://www.dke.de/CAEX", # Common CAEX namespace
# Add other potential namespaces if needed based on file inspection # Add other potential namespaces if needed based on file inspection
} }
# Try finding with common prefixes or the default namespace # Try finding with common prefixes or the default namespace
for prefix, uri in namespaces.items(): for prefix, uri in namespaces.items():
# Construct path with namespace URI if prefix is defined # Construct path with namespace URI if prefix is defined
namespaced_path = path namespaced_path = path
if prefix: if prefix:
parts = path.split('/') parts = path.split("/")
namespaced_parts = [f"{{{uri}}}{part}" if part != '.' else part for part in parts] namespaced_parts = [
namespaced_path = '/'.join(namespaced_parts) f"{{{uri}}}{part}" if part != "." else part for part in parts
]
# Try findall with the constructed path namespaced_path = "/".join(namespaced_parts)
found = element.findall(namespaced_path)
if found: # Try findall with the constructed path
return found # Return first successful find found = element.findall(namespaced_path)
if found:
return found # Return first successful find
# Fallback: try finding without explicit namespace (might work if default ns is used throughout) # Fallback: try finding without explicit namespace (might work if default ns is used throughout)
# This might require adjusting the path string itself depending on the XML structure # This might require adjusting the path string itself depending on the XML structure
try: try:
# Simple attempt without namespace handling if the above fails # Simple attempt without namespace handling if the above fails
return element.findall(path) return element.findall(path)
except SyntaxError: # Handle potential errors if path isn't valid without namespaces except (
SyntaxError
): # Handle potential errors if path isn't valid without namespaces
return [] return []
@ -109,96 +125,137 @@ def parse_aml_to_markdown(aml_file_path, md_file_path):
# Find InstanceHierarchy - usually contains the project structure # Find InstanceHierarchy - usually contains the project structure
# Note: Namespace handling in ElementTree can be tricky. Adjust '{...}' part if needed. # Note: Namespace handling in ElementTree can be tricky. Adjust '{...}' part if needed.
# We will use a helper function 'find_elements' to try common patterns # We will use a helper function 'find_elements' to try common patterns
instance_hierarchies = find_elements(root, './/InstanceHierarchy') # Common CAEX tag instance_hierarchies = find_elements(
root, ".//InstanceHierarchy"
) # Common CAEX tag
if not instance_hierarchies: if not instance_hierarchies:
markdown_lines.append("Could not find InstanceHierarchy in the AML file.") markdown_lines.append("Could not find InstanceHierarchy in the AML file.")
print("Warning: Could not find InstanceHierarchy element.") print("Warning: Could not find InstanceHierarchy element.")
else: else:
# Assuming the first InstanceHierarchy is the main one # Assuming the first InstanceHierarchy is the main one
ih = instance_hierarchies[0] ih = instance_hierarchies[0]
markdown_lines.append(f"## Instance Hierarchy: {ih.get('Name', 'N/A')}") markdown_lines.append(f"## Instance Hierarchy: {ih.get('Name', 'N/A')}")
markdown_lines.append("") markdown_lines.append("")
# Look for InternalElements which represent devices/components # Look for InternalElements which represent devices/components
internal_elements = find_elements(ih, './/InternalElement') # Common CAEX tag internal_elements = find_elements(
ih, ".//InternalElement"
) # Common CAEX tag
if not internal_elements: if not internal_elements:
markdown_lines.append("No devices (InternalElement) found in InstanceHierarchy.") markdown_lines.append(
print("Info: No InternalElement tags found under InstanceHierarchy.") "No devices (InternalElement) found in InstanceHierarchy."
)
print("Info: No InternalElement tags found under InstanceHierarchy.")
else: else:
markdown_lines.append(f"Found {len(internal_elements)} device(s)/component(s):") markdown_lines.append(
f"Found {len(internal_elements)} device(s)/component(s):"
)
markdown_lines.append("") markdown_lines.append("")
markdown_lines.append("| Name | SystemUnitClass | RefBaseSystemUnitPath | Attributes |") markdown_lines.append(
"| Name | SystemUnitClass | RefBaseSystemUnitPath | Attributes |"
)
markdown_lines.append("|---|---|---|---|") markdown_lines.append("|---|---|---|---|")
for elem in internal_elements: for elem in internal_elements:
name = elem.get('Name', 'N/A') name = elem.get("Name", "N/A")
ref_path = elem.get('RefBaseSystemUnitPath', 'N/A') # Path to class definition ref_path = elem.get(
"RefBaseSystemUnitPath", "N/A"
) # Path to class definition
# Try to get the class name from the RefBaseSystemUnitPath or SystemUnitClassLib # Try to get the class name from the RefBaseSystemUnitPath or SystemUnitClassLib
su_class_path = find_elements(elem, './/SystemUnitClass') # Check direct child first su_class_path = find_elements(
su_class = su_class_path[0].get('Path', 'N/A') if su_class_path else ref_path.split('/')[-1] # Fallback to last part of path elem, ".//SystemUnitClass"
) # Check direct child first
su_class = (
su_class_path[0].get("Path", "N/A")
if su_class_path
else ref_path.split("/")[-1]
) # Fallback to last part of path
attributes_md = "" attributes_md = ""
attributes = find_elements(elem, './/Attribute') # Find attributes attributes = find_elements(elem, ".//Attribute") # Find attributes
attr_list = [] attr_list = []
for attr in attributes: for attr in attributes:
attr_name = attr.get('Name', '') attr_name = attr.get("Name", "")
attr_value_elem = find_elements(attr, './/Value') # Get Value element attr_value_elem = find_elements(
attr_value = attr_value_elem[0].text if attr_value_elem and attr_value_elem[0].text else 'N/A' attr, ".//Value"
) # Get Value element
attr_value = (
attr_value_elem[0].text
if attr_value_elem and attr_value_elem[0].text
else "N/A"
)
# Look for potential IP addresses (common attribute names) # Look for potential IP addresses (common attribute names)
if "Address" in attr_name or "IP" in attr_name: if "Address" in attr_name or "IP" in attr_name:
attr_list.append(f"**{attr_name}**: {attr_value}") attr_list.append(f"**{attr_name}**: {attr_value}")
else: else:
attr_list.append(f"{attr_name}: {attr_value}") attr_list.append(f"{attr_name}: {attr_value}")
attributes_md = "<br>".join(attr_list) if attr_list else "None" attributes_md = "<br>".join(attr_list) if attr_list else "None"
markdown_lines.append(
markdown_lines.append(f"| {name} | {su_class} | `{ref_path}` | {attributes_md} |") f"| {name} | {su_class} | `{ref_path}` | {attributes_md} |"
)
# Write to Markdown file # Write to Markdown file
with open(md_file_path, 'w', encoding='utf-8') as f: with open(md_file_path, "w", encoding="utf-8") as f:
f.write("\n".join(markdown_lines)) f.write("\n".join(markdown_lines))
print(f"Markdown summary written to: {md_file_path}") print(f"Markdown summary written to: {md_file_path}")
except ET.ParseError as xml_err: except ET.ParseError as xml_err:
print(f"ERROR parsing XML file {aml_file_path}: {xml_err}") print(f"ERROR parsing XML file {aml_file_path}: {xml_err}")
with open(md_file_path, 'w', encoding='utf-8') as f: with open(md_file_path, "w", encoding="utf-8") as f:
f.write(f"# Error\n\nFailed to parse AML file: {os.path.basename(aml_file_path)}\n\nError: {xml_err}") f.write(
f"# Error\n\nFailed to parse AML file: {os.path.basename(aml_file_path)}\n\nError: {xml_err}"
)
except Exception as e: except Exception as e:
print(f"ERROR processing AML file {aml_file_path}: {e}") print(f"ERROR processing AML file {aml_file_path}: {e}")
traceback.print_exc() traceback.print_exc()
with open(md_file_path, 'w', encoding='utf-8') as f: with open(md_file_path, "w", encoding="utf-8") as f:
f.write(f"# Error\n\nAn unexpected error occurred while processing AML file: {os.path.basename(aml_file_path)}\n\nError: {e}") f.write(
f"# Error\n\nAn unexpected error occurred while processing AML file: {os.path.basename(aml_file_path)}\n\nError: {e}"
)
# --- Main Script --- # --- Main Script ---
if __name__ == "__main__": if __name__ == "__main__":
configs = load_configuration() configs = load_configuration()
working_directory = configs.get("working_directory")
print("--- TIA Portal Project CAx Exporter and Analyzer ---") print("--- TIA Portal Project CAx Exporter and Analyzer ---")
# 1. Select Files/Folders # 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, Output Directory comes from config
project_file = select_project_file() project_file = select_project_file()
output_dir = select_output_directory() output_dir = Path(
working_directory
) # Use working directory from config, ensure it's a Path object
print(f"\nSelected Project: {project_file}") print(f"\nSelected Project: {project_file}")
print(f"Selected Output Directory: {output_dir}") print(f"Using Output Directory (Working Directory): {output_dir}")
# Define output file names # Define output file names using Path object
project_base_name = os.path.splitext(os.path.basename(project_file))[0] project_path = Path(project_file)
aml_file = os.path.join(output_dir, f"{project_base_name}_CAx_Export.aml") project_base_name = project_path.stem # Get filename without extension
md_file = os.path.join(output_dir, f"{project_base_name}_CAx_Summary.md") aml_file = output_dir / f"{project_base_name}_CAx_Export.aml"
log_file = os.path.join(output_dir, f"{project_base_name}_CAx_Export.log") # Log file for the export process md_file = output_dir / f"{project_base_name}_CAx_Summary.md"
log_file = (
output_dir / f"{project_base_name}_CAx_Export.log"
) # Log file for the export process
print(f"Will export CAx data to: {aml_file}") print(f"Will export CAx data to: {aml_file}")
print(f"Will generate summary to: {md_file}") print(f"Will generate summary to: {md_file}")
print(f"Export log file: {log_file}") print(f"Export log file: {log_file}")
portal_instance = None portal_instance = None
project_object = None project_object = None
cax_export_successful = False cax_export_successful = False
@ -208,25 +265,33 @@ if __name__ == "__main__":
print(f"\nConnecting to TIA Portal V{TIA_PORTAL_VERSION}...") print(f"\nConnecting to TIA Portal V{TIA_PORTAL_VERSION}...")
portal_instance = ts.open_portal( portal_instance = ts.open_portal(
version=TIA_PORTAL_VERSION, version=TIA_PORTAL_VERSION,
portal_mode=ts.Enums.PortalMode.WithGraphicalUserInterface portal_mode=ts.Enums.PortalMode.WithGraphicalUserInterface,
) )
print("Connected.") print("Connected.")
# 3. Open Project # 3. Open Project
print(f"Opening project: {os.path.basename(project_file)}...") print(
project_object = portal_instance.open_project(project_file_path=project_file) f"Opening project: {project_path.name}..."
) # Use Path object's name attribute
project_object = portal_instance.open_project(
project_file_path=str(project_path)
) # Pass path as string
if project_object is None: if project_object is None:
project_object = portal_instance.get_project() print("Project might already be open, attempting to get handle...")
if project_object is None: project_object = portal_instance.get_project()
raise Exception("Failed to open or get the specified project.") if project_object is None:
raise Exception("Failed to open or get the specified project.")
print("Project opened.") print("Project opened.")
# 4. Export CAx Data (Project Level) # 4. 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 for the log file as well # Ensure output directory exists (Path.mkdir handles this implicitly if needed later, but good practice)
os.makedirs(os.path.dirname(log_file), exist_ok=True) output_dir.mkdir(parents=True, exist_ok=True)
export_result = project_object.export_cax_data(export_file_path=aml_file, log_file_path=log_file) # [cite: 361] # Pass paths as strings to the TIA function
export_result = project_object.export_cax_data(
export_file_path=str(aml_file), log_file_path=str(log_file)
)
if export_result: if export_result:
print("CAx data exported successfully.") print("CAx data exported successfully.")
@ -235,9 +300,10 @@ if __name__ == "__main__":
print("CAx data export failed. Check the log file for details:") print("CAx data export failed. Check the log file for details:")
print(f" Log file: {log_file}") print(f" Log file: {log_file}")
# Write basic error message to MD file if export fails # Write basic error message to MD file if export fails
with open(md_file, 'w', encoding='utf-8') as f: with open(md_file, "w", encoding="utf-8") as f:
f.write(f"# Error\n\nCAx data export failed. Check log file: {log_file}") f.write(
f"# Error\n\nCAx data export failed. Check log file: {log_file}"
)
except ts.TiaException as tia_ex: except ts.TiaException as tia_ex:
print(f"\nTIA Portal Openness Error: {tia_ex}") print(f"\nTIA Portal Openness Error: {tia_ex}")
@ -259,11 +325,15 @@ if __name__ == "__main__":
# 5. Parse AML and Generate Markdown (only if export was successful) # 5. Parse AML and Generate Markdown (only if export was successful)
if cax_export_successful: if cax_export_successful:
if os.path.exists(aml_file): if aml_file.exists(): # Use Path object's exists() method
parse_aml_to_markdown(aml_file, md_file) parse_aml_to_markdown(aml_file, md_file)
else: else:
print(f"ERROR: Export was reported successful, but AML file not found at {aml_file}") print(
with open(md_file, 'w', encoding='utf-8') as f: f"ERROR: Export was reported successful, but AML file not found at {aml_file}"
f.write(f"# Error\n\nExport was reported successful, but AML file not found:\n{aml_file}") )
with open(md_file, "w", encoding="utf-8") as f:
f.write(
f"# Error\n\nExport was reported successful, but AML file not found:\n{aml_file}"
)
print("\nScript finished.") print("\nScript finished.")

View File

@ -1,8 +1,9 @@
""" """
export_io_from_CAx : export_io_from_CAx :
Script que sirve para exraer los IOs de un proyecto de TIA Portal y Script que sirve para exraer los IOs de un proyecto de TIA Portal y
generar un archivo Markdown con la información. generar un archivo Markdown con la información.
""" """
import os import os
import sys import sys
from tkinter import filedialog from tkinter import filedialog
@ -19,6 +20,7 @@ script_root = os.path.dirname(
sys.path.append(script_root) sys.path.append(script_root)
from backend.script_utils import load_configuration from backend.script_utils import load_configuration
# --- extract_aml_data function (Unchanged from v15) --- # --- extract_aml_data function (Unchanged from v15) ---
def extract_aml_data(root): def extract_aml_data(root):
"""(v15 logic - Unchanged) Extracts data, correcting PLC network association lookup.""" """(v15 logic - Unchanged) Extracts data, correcting PLC network association lookup."""
@ -955,13 +957,15 @@ def process_aml_file(
print(f"ERROR processing AML file {aml_file_path}: {e}") print(f"ERROR processing AML file {aml_file_path}: {e}")
traceback.print_exc() traceback.print_exc()
def select_cax_file():
"""Opens a dialog to select a CAx (XML) export file.""" def select_cax_file(initial_dir=None): # Add initial_dir parameter
"""Opens a dialog to select a CAx (XML) export file, starting in the specified directory."""
root = tk.Tk() root = tk.Tk()
root.withdraw() root.withdraw()
file_path = filedialog.askopenfilename( file_path = filedialog.askopenfilename(
title="Select CAx Export File (XML)", title="Select CAx Export File (XML)",
filetypes=[("XML Files", "*.xml"), ("All Files", "*.*")] # Changed filetypes filetypes=[("XML Files", "*.xml"), ("AML Files", "*.aml"), ("All Files", "*.*")], # Added AML
initialdir=initial_dir # Set the initial directory
) )
root.destroy() root.destroy()
if not file_path: if not file_path:
@ -969,12 +973,13 @@ def select_cax_file():
sys.exit(0) sys.exit(0)
return file_path return file_path
def select_output_directory(): def select_output_directory():
"""Opens a dialog to select the output directory.""" """Opens a dialog to select the output directory."""
root = tk.Tk() root = tk.Tk()
root.withdraw() root.withdraw()
dir_path = filedialog.askdirectory( dir_path = filedialog.askdirectory(
title="Select Output Directory for JSON and MD files" # Updated title slightly title="Select Output Directory for JSON and MD files" # Updated title slightly
) )
root.destroy() root.destroy()
if not dir_path: if not dir_path:
@ -982,35 +987,53 @@ def select_output_directory():
sys.exit(0) sys.exit(0)
return dir_path return dir_path
# --- Main Execution --- # --- Main Execution ---
if __name__ == "__main__": if __name__ == "__main__":
# configs = load_configuration() # Keep if needed, otherwise remove configs = load_configuration()
working_directory = configs.get("working_directory")
script_version = "v27 - User Input/Output Paths" # Updated version script_version = "v28 - Working Directory Integration" # Updated version
print( print(
f"--- AML (CAx Export) to Hierarchical JSON and Obsidian MD Converter ({script_version}) ---" f"--- AML (CAx Export) to Hierarchical JSON and Obsidian MD Converter ({script_version}) ---"
) )
# 1. Select Input CAx File and Output Directory # Validate working directory
cax_file_path = select_cax_file() if not working_directory or not os.path.isdir(working_directory):
output_dir = select_output_directory() print("ERROR: Working directory not set or invalid in configuration.")
print("Attempting to use script's directory as fallback.")
# Fallback to script's directory or current directory if needed
working_directory = os.path.dirname(os.path.abspath(__file__))
if not os.path.isdir(working_directory):
working_directory = os.getcwd()
print(f"Using fallback directory: {working_directory}")
# Optionally, prompt user to select a working directory here if critical
# output_dir = select_output_directory() # Keep this if you want user selection on failure
# Use working_directory as the output directory
output_dir = working_directory
print(f"Using Working Directory for Output: {output_dir}")
# 1. Select Input CAx File, starting in the working directory
# Pass working_directory to the selection function
cax_file_path = select_cax_file(initial_dir=working_directory)
# Convert paths to Path objects # Convert paths to Path objects
input_path = Path(cax_file_path) input_path = Path(cax_file_path)
output_path = Path(output_dir) output_path = Path(output_dir) # Output path is the working directory
# Check if input file exists # Check if input file exists
if not input_path.is_file(): if not input_path.is_file():
print(f"ERROR: Input file '{input_path}' not found or is not a file.") print(f"ERROR: Input file '{input_path}' not found or is not a file.")
sys.exit(1) sys.exit(1)
# Ensure output directory exists # Ensure output directory exists (redundant if working_directory is valid, but safe)
output_path.mkdir(parents=True, exist_ok=True) output_path.mkdir(parents=True, exist_ok=True)
# Construct output file paths within the selected output directory # Construct output file paths within the selected output directory (working_directory)
output_json_file = output_path / input_path.with_suffix(".hierarchical.json").name output_json_file = output_path / input_path.with_suffix(".hierarchical.json").name
output_md_file = output_path / input_path.with_name(f"{input_path.stem}_Hardware_Tree.md").name output_md_file = output_path / input_path.with_name(f"{input_path.stem}_Hardware_Tree.md") # Simplified name
output_md_upward_file = output_path / input_path.with_name(f"{input_path.stem}_IO_Upward_Debug.md").name output_md_upward_file = output_path / input_path.with_name(f"{input_path.stem}_IO_Upward_Debug.md") # Simplified name
print(f"Input AML: {input_path.resolve()}") print(f"Input AML: {input_path.resolve()}")
print(f"Output Directory: {output_path.resolve()}") print(f"Output Directory: {output_path.resolve()}")

File diff suppressed because it is too large Load Diff

15
requirements.txt Normal file
View File

@ -0,0 +1,15 @@
flask
flask-sock
lxml
pandas
google-cloud-translate
openai
ollama
langid
openpyxl
beautifulsoup4
requests
mammoth
html2text
pypandoc
# siemens-tia-scripting # Requiere instalación especial de TIA Portal Openness