ParamManagerScripts/backend/script_groups/ObtainIOFromProjectTia/x2.py

790 lines
32 KiB
Python

"""
export_simatic_sd_tia20 : Script para exportar bloques de PLC desde TIA Portal 20 en formato SIMATIC SD.
Basado en la nueva funcionalidad de exportación SIMATIC SD disponible en TIA Portal V20.
"""
import tkinter as tk
from tkinter import filedialog
import os
import sys
import traceback
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 = "20.0" # Target TIA Portal version for SIMATIC SD support
EXPORT_OPTIONS = None # Use default export options
KEEP_FOLDER_STRUCTURE = (
True # Replicate TIA project folder structure in export directory
)
# --- TIA Scripting Import Handling ---
# Check if the TIA_SCRIPTING environment variable is set
if os.getenv("TIA_SCRIPTING"):
sys.path.append(os.getenv("TIA_SCRIPTING"))
else:
# Optional: Define a fallback path if the environment variable isn't set
# fallback_path = "C:\\path\\to\\your\\TIA_Scripting_binaries"
# if os.path.exists(fallback_path):
# sys.path.append(fallback_path)
pass # Allow import to fail if not found
try:
import siemens_tia_scripting as ts
EXPORT_OPTIONS = (
ts.Enums.ExportOptions.WithDefaults
) # Set default options now that 'ts' is imported
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 verify_export_format(export_path, expected_format="SIMATIC_SD"):
"""
Verifies what format was actually exported by examining file extensions.
Returns (actual_format, file_extensions, file_count)
"""
if not os.path.exists(export_path):
return "NO_FILES", [], 0
files = [
f
for f in os.listdir(export_path)
if os.path.isfile(os.path.join(export_path, f))
]
if not files:
return "EMPTY_FOLDER", [], 0
extensions = [os.path.splitext(f)[1].lower() for f in files]
extension_counts = {}
for ext in extensions:
extension_counts[ext] = extension_counts.get(ext, 0) + 1
# Determine actual format based on file extensions
if all(ext == ".xml" for ext in extensions):
actual_format = "XML_ONLY"
elif any(ext in [".sd", ".simatic"] for ext in extensions):
actual_format = "SIMATIC_SD"
elif ".xml" in extensions and len(set(extensions)) > 1:
actual_format = "MIXED"
else:
actual_format = "UNKNOWN"
return actual_format, extension_counts, len(files)
def check_simatic_sd_block_compatibility(block):
"""
Checks if a block is compatible with SIMATIC SD export format.
Returns (is_compatible, reason)
"""
try:
# Check if block is consistent
if not block.is_consistent():
return False, "Block is not consistent/compiled"
# Check programming language - SIMATIC SD only supports LAD
try:
prog_lang = block.get_programming_language()
if prog_lang != ts.Enums.ProgrammingLanguage.LAD:
return (
False,
f"Language {prog_lang} not supported (SIMATIC SD requires LAD)",
)
except Exception:
return False, "Could not determine programming language"
# Check block type - SIMATIC SD typically supports FB, FC, OB
try:
block_type = block.get_block_type()
supported_types = [
ts.Enums.BlockType.FB, # Function Block
ts.Enums.BlockType.FC, # Function
ts.Enums.BlockType.OB, # Organization Block
]
if block_type not in supported_types:
return False, f"Block type {block_type} may not be supported"
except Exception:
# If we can't determine type, assume it might work
pass
return True, "Block appears compatible with SIMATIC SD"
except Exception as e:
return False, f"Error checking compatibility: {e}"
def verify_export_format(export_path, expected_format="SimaticSD"):
"""
Verifies if the exported files are actually in the expected format.
For SIMATIC SD, looks for specific keywords like RUNG, END_RUNG, wire#
Returns (is_correct_format, file_count, sample_files, format_details)
"""
if not os.path.exists(export_path):
return False, 0, [], "Directory does not exist"
files = [
f
for f in os.listdir(export_path)
if os.path.isfile(os.path.join(export_path, f))
]
if not files:
return False, 0, [], "No files found"
# Check first few files for format
sample_files = files[:3]
format_details = []
for file_name in sample_files:
file_path = os.path.join(export_path, file_name)
try:
with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
content = f.read(2000) # Read first 2KB
file_info = {"file": file_name, "size": len(content)}
if expected_format == "SimaticSD":
# SIMATIC SD specific keywords and structure
sd_keywords = ["RUNG", "END_RUNG", "wire#", "NETWORK", "TITLE", "LAD"]
xml_indicators = ["<?xml", "<Document", "<SW.Blocks"]
found_sd_keywords = [kw for kw in sd_keywords if kw in content]
found_xml_indicators = [xi for xi in xml_indicators if xi in content]
file_info["sd_keywords"] = found_sd_keywords
file_info["xml_indicators"] = found_xml_indicators
file_info["is_xml"] = len(found_xml_indicators) > 0
file_info["is_simatic_sd"] = (
len(found_sd_keywords) > 0 and not file_info["is_xml"]
)
file_info["first_100_chars"] = (
content[:100].replace("\n", " ").replace("\r", "")
)
else: # XML format
file_info["is_xml"] = (
content.strip().startswith("<?xml") or "<Document" in content
)
file_info["first_100_chars"] = (
content[:100].replace("\n", " ").replace("\r", "")
)
format_details.append(file_info)
except Exception as e:
format_details.append({"file": file_name, "error": str(e)})
# Determine overall result for SIMATIC SD
if expected_format == "SimaticSD":
is_correct_format = any(
f.get("is_simatic_sd", False) for f in format_details if "error" not in f
)
else:
is_correct_format = any(
f.get("is_xml", False) for f in format_details if "error" not in f
)
return is_correct_format, len(files), sample_files, format_details
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. *.ap20
)
root.destroy()
if not file_path:
print("No project file selected. Exiting.")
sys.exit(0)
return file_path
def select_export_directory():
"""Opens a dialog to select the export directory."""
root = tk.Tk()
root.withdraw() # Hide the main tkinter window
dir_path = filedialog.askdirectory(title="Select Export Directory")
root.destroy()
if not dir_path:
print("No export directory selected. Exiting.")
sys.exit(0)
return dir_path
def check_simatic_sd_support():
"""Verifies if SIMATIC SD format is available in the current TIA Scripting version."""
try:
# Check if SimaticSD is available in ExportFormats enum
simatic_sd_format = ts.Enums.ExportFormats.SimaticSD
print(f"✓ SIMATIC SD format enum found (value: {simatic_sd_format})")
# Try to get more information about available formats
try:
all_formats = [
attr for attr in dir(ts.Enums.ExportFormats) if not attr.startswith("_")
]
print(f" Available export formats: {all_formats}")
except Exception:
pass
return True
except AttributeError:
print("✗ ERROR: SIMATIC SD format not available in this TIA Scripting version.")
print(
"Please ensure you are using TIA Portal V20 or later with compatible TIA Scripting."
)
return False
def check_tia_portal_version():
"""Check TIA Portal version and compatibility."""
print("\n=== TIA PORTAL VERSION CHECK ===")
try:
# This will be filled when we connect to TIA Portal
print("TIA Portal version check will be performed after connection...")
return True
except Exception as e:
print(f"Could not check TIA Portal version: {e}")
return False
def export_plc_data_simatic_sd(plc, export_base_dir):
"""Exports Blocks, UDTs, and Tag Tables from a given PLC in SIMATIC SD format."""
plc_name = plc.get_name()
print(f"\n--- Processing PLC: {plc_name} (SIMATIC SD Export) ---")
# Define base export path for this PLC with timestamp to avoid conflicts
import datetime
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
plc_export_dir = os.path.join(export_base_dir, f"{plc_name}_SimaticSD_{timestamp}")
os.makedirs(plc_export_dir, exist_ok=True)
# --- Export Program Blocks in SIMATIC SD Format ---
blocks_exported_sd = 0
blocks_exported_xml = 0
blocks_skipped = 0
blocks_not_lad = 0
print(f"\n[PLC: {plc_name}] Exporting Program Blocks (SIMATIC SD)...")
print(" NOTE: SIMATIC SD format only supports LAD (Ladder) programming language!")
sd_blocks_path = os.path.join(plc_export_dir, "01_ProgramBlocks_SD")
os.makedirs(sd_blocks_path, exist_ok=True)
print(f" SIMATIC SD Target: {sd_blocks_path}")
# Also create a separate SimaticML export for comparison
xml_blocks_path = os.path.join(plc_export_dir, "02_ProgramBlocks_XML_Compare")
os.makedirs(xml_blocks_path, exist_ok=True)
try:
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:
if not block.is_consistent():
print(f" Compiling block {block_name}...")
block.compile()
if not block.is_consistent():
print(
f" WARNING: Block {block_name} inconsistent after compile. Skipping."
)
blocks_skipped += 1
continue
# Check programming language - CRITICAL for SIMATIC SD
is_compatible, compatibility_reason = (
check_simatic_sd_block_compatibility(block)
)
print(f" Compatibility check: {compatibility_reason}")
if not is_compatible:
print(f" Block {block_name} not compatible with SIMATIC SD.")
print(f" Exporting XML only: {compatibility_reason}")
blocks_not_lad += 1
# Export only in XML for incompatible blocks
block.export(
target_directory_path=xml_blocks_path,
export_options=EXPORT_OPTIONS,
export_format=ts.Enums.ExportFormats.SimaticML,
keep_folder_structure=KEEP_FOLDER_STRUCTURE,
)
blocks_exported_xml += 1
print(
f" ✓ Exported {block_name} in XML (incompatible with SIMATIC SD)"
)
continue
# Try SIMATIC SD export for LAD blocks
print(f" Exporting LAD block {block_name} as SIMATIC SD...")
try:
block.export(
target_directory_path=sd_blocks_path,
export_options=EXPORT_OPTIONS,
export_format=ts.Enums.ExportFormats.SimaticSD, # SIMATIC SD format
keep_folder_structure=KEEP_FOLDER_STRUCTURE,
)
# Verify if the export was actually in SIMATIC SD format
print(f" Verifying SIMATIC SD format for {block_name}...")
is_sd, file_count, sample_files, format_details = (
verify_export_format(sd_blocks_path, "SimaticSD")
)
if is_sd:
blocks_exported_sd += 1
print(
f" ✓ Successfully exported {block_name} in REAL SIMATIC SD format"
)
# Show sample of SD content
for detail in format_details:
if detail.get("is_simatic_sd") and detail.get(
"sd_keywords"
):
print(
f" 🎯 SD Keywords found: {', '.join(detail['sd_keywords'])}"
)
break
else:
print(
f" ❌ FAILED: Export claimed SD but files are actually XML!"
)
print(f" 📋 Format analysis:")
for detail in format_details:
if "error" not in detail:
print(f" File: {detail['file']}")
print(
f" SIMATIC SD: {detail.get('is_simatic_sd', False)}"
)
print(
f" XML format: {detail.get('is_xml', False)}"
)
if detail.get("sd_keywords"):
print(
f" SD keywords: {detail['sd_keywords']}"
)
if detail.get("xml_indicators"):
print(
f" XML indicators: {detail['xml_indicators']}"
)
print(
f" Content start: {detail.get('first_100_chars', '')[:50]}..."
)
# Also export same block in XML for comparison
print(f" Exporting {block_name} as XML for comparison...")
block.export(
target_directory_path=xml_blocks_path,
export_options=EXPORT_OPTIONS,
export_format=ts.Enums.ExportFormats.SimaticML, # Traditional XML format
keep_folder_structure=KEEP_FOLDER_STRUCTURE,
)
blocks_exported_xml += 1
print(f" + Also exported {block_name} in XML for comparison")
except Exception as export_ex:
print(f" ERROR during SIMATIC SD export: {export_ex}")
print(
f" This is likely because SIMATIC SD has specific requirements:"
)
print(f" - Block must be in LAD (Ladder) format")
print(
f" - Block must be compatible with SIMATIC SD specification"
)
# Try to export only in XML if SD fails
try:
print(
f" Attempting fallback XML export for {block_name}..."
)
block.export(
target_directory_path=xml_blocks_path,
export_options=EXPORT_OPTIONS,
export_format=ts.Enums.ExportFormats.SimaticML,
keep_folder_structure=KEEP_FOLDER_STRUCTURE,
)
print(
f" ✓ Fallback XML export successful for {block_name}"
)
blocks_exported_xml += 1
except Exception as fallback_ex:
print(
f" ERROR: Both SD and XML export failed: {fallback_ex}"
)
blocks_skipped += 1
except Exception as block_ex:
print(f" ERROR exporting block {block_name}: {block_ex}")
blocks_skipped += 1
print(
f" Program Blocks Export Summary: SIMATIC SD={blocks_exported_sd}, XML={blocks_exported_xml}, Non-LAD={blocks_not_lad}, Skipped/Errors={blocks_skipped}"
)
except Exception as e:
print(f" ERROR processing Program Blocks: {e}")
traceback.print_exc()
# --- Export PLC Data Types (UDTs) in SIMATIC SD Format ---
udts_exported = 0
udts_skipped = 0
print(f"\n[PLC: {plc_name}] Exporting PLC Data Types - UDTs (SIMATIC SD)...")
udt_sd_export_path = os.path.join(plc_export_dir, "03_PlcDataTypes_SD")
udt_xml_export_path = os.path.join(plc_export_dir, "04_PlcDataTypes_XML_Compare")
os.makedirs(udt_sd_export_path, exist_ok=True)
os.makedirs(udt_xml_export_path, exist_ok=True)
print(f" SIMATIC SD Target: {udt_sd_export_path}")
print(f" XML Compare Target: {udt_xml_export_path}")
try:
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:
if not udt.is_consistent():
print(f" Compiling UDT {udt_name}...")
udt.compile()
if not udt.is_consistent():
print(
f" WARNING: UDT {udt_name} inconsistent after compile. Skipping."
)
udts_skipped += 1
continue
print(f" Exporting {udt_name} as SIMATIC SD...")
try:
udt.export(
target_directory_path=udt_sd_export_path,
export_options=EXPORT_OPTIONS,
export_format=ts.Enums.ExportFormats.SimaticSD, # SIMATIC SD format for UDTs
keep_folder_structure=KEEP_FOLDER_STRUCTURE,
)
# Also export in XML for comparison
udt.export(
target_directory_path=udt_xml_export_path,
export_options=EXPORT_OPTIONS,
export_format=ts.Enums.ExportFormats.SimaticML,
keep_folder_structure=KEEP_FOLDER_STRUCTURE,
)
udts_exported += 1
print(f" ✓ Successfully exported {udt_name} (SD + XML)")
except Exception as udt_export_ex:
print(f" ERROR during UDT export: {udt_export_ex}")
udts_skipped += 1
except Exception as udt_ex:
print(f" ERROR exporting UDT {udt_name}: {udt_ex}")
udts_skipped += 1
print(
f" UDT Export Summary: Exported={udts_exported}, Skipped/Errors={udts_skipped}"
)
except Exception as e:
print(f" ERROR processing UDTs: {e}")
traceback.print_exc()
# --- Export PLC Tag Tables in SIMATIC SD Format ---
tags_exported = 0
tags_skipped = 0
print(f"\n[PLC: {plc_name}] Exporting PLC Tag Tables (SIMATIC SD)...")
tags_sd_export_path = os.path.join(plc_export_dir, "05_PlcTags_SD")
tags_xml_export_path = os.path.join(plc_export_dir, "06_PlcTags_XML_Compare")
os.makedirs(tags_sd_export_path, exist_ok=True)
os.makedirs(tags_xml_export_path, exist_ok=True)
print(f" SIMATIC SD Target: {tags_sd_export_path}")
print(f" XML Compare Target: {tags_xml_export_path}")
try:
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 {table_name} as SIMATIC SD...")
try:
table.export(
target_directory_path=tags_sd_export_path,
export_options=EXPORT_OPTIONS,
export_format=ts.Enums.ExportFormats.SimaticSD, # SIMATIC SD format for Tag Tables
keep_folder_structure=KEEP_FOLDER_STRUCTURE,
)
# Also export in XML for comparison
table.export(
target_directory_path=tags_xml_export_path,
export_options=EXPORT_OPTIONS,
export_format=ts.Enums.ExportFormats.SimaticML,
keep_folder_structure=KEEP_FOLDER_STRUCTURE,
)
tags_exported += 1
print(f" ✓ Successfully exported {table_name} (SD + XML)")
except Exception as tag_export_ex:
print(f" ERROR during Tag Table export: {tag_export_ex}")
tags_skipped += 1
except Exception as table_ex:
print(f" ERROR exporting Tag Table {table_name}: {table_ex}")
tags_skipped += 1
print(
f" Tag Table Export Summary: Exported={tags_exported}, Skipped/Errors={tags_skipped}"
)
except Exception as e:
print(f" ERROR processing Tag Tables: {e}")
traceback.print_exc()
print(f"\n--- Finished processing PLC: {plc_name} ---")
def export_additional_formats(plc, export_base_dir):
"""Optional: Export in traditional formats alongside SIMATIC SD for comparison."""
plc_name = plc.get_name()
print(
f"\n[Optional] Exporting traditional formats for comparison - PLC: {plc_name}"
)
# Create comparison directory
comparison_dir = os.path.join(export_base_dir, plc_name, "Comparison_Formats")
os.makedirs(comparison_dir, exist_ok=True)
# Export a few blocks in SimaticML for comparison
xml_comparison_path = os.path.join(comparison_dir, "SimaticML_Sample")
os.makedirs(xml_comparison_path, exist_ok=True)
try:
program_blocks = plc.get_program_blocks()
# Export first 3 blocks in SimaticML for comparison
for i, block in enumerate(program_blocks[:3]):
if block.is_consistent():
block_name = block.get_name()
print(f" Exporting {block_name} in SimaticML for comparison...")
block.export(
target_directory_path=xml_comparison_path,
export_options=EXPORT_OPTIONS,
export_format=ts.Enums.ExportFormats.SimaticML,
keep_folder_structure=KEEP_FOLDER_STRUCTURE,
)
except Exception as e:
print(f" Warning: Could not export comparison formats: {e}")
# --- Main Script ---
if __name__ == "__main__":
configs = load_configuration()
working_directory = configs.get("working_directory")
print("--- TIA Portal 20 SIMATIC SD Exporter ---")
print("Exporting Blocks, UDTs, and Tags in SIMATIC SD Format")
# Check SIMATIC SD support first
if not check_simatic_sd_support():
sys.exit(1)
# 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()
export_dir = working_directory # Use working directory from config
print(f"\nSelected Project: {project_file}")
print(f"Using Export Directory (Working Directory): {export_dir}")
portal_instance = None
project_object = None
try:
# 2. Connect to TIA Portal V20
print(f"\nConnecting to TIA Portal V{TIA_PORTAL_VERSION}...")
portal_instance = ts.open_portal(
version=TIA_PORTAL_VERSION,
portal_mode=ts.Enums.PortalMode.WithGraphicalUserInterface,
)
print("Connected to TIA Portal V20.")
print(f"Portal Process ID: {portal_instance.get_process_id()}")
# Get TIA Portal version information
try:
portal_version = portal_instance.get_version()
print(f"TIA Portal Version: {portal_version}")
# Check if this version really supports SIMATIC SD
version_parts = portal_version.split(".")
major_version = int(version_parts[0]) if version_parts else 0
if major_version < 20:
print(
f"⚠️ WARNING: TIA Portal V{major_version} may not fully support SIMATIC SD (requires V20+)"
)
else:
print(f"✓ TIA Portal V{major_version} should support SIMATIC SD")
except Exception as ver_ex:
print(f"Could not get TIA Portal version: {ver_ex}")
# 3. Open Project
print(f"Opening project: {os.path.basename(project_file)}...")
project_object = portal_instance.open_project(project_file_path=project_file)
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.")
# 4. 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 SIMATIC SD export process...")
# 5. Iterate and Export Data for each PLC in SIMATIC SD format
import datetime # Add this import for timestamp
for plc_device in plcs:
export_plc_data_simatic_sd(plc=plc_device, export_base_dir=export_dir)
print("\n🎉 SIMATIC SD Export process completed successfully!")
print("\nExported files structure:")
print("├── [PLC_Name]_SimaticSD_[timestamp]/")
print(
"│ ├── 01_ProgramBlocks_SD/ # SIMATIC SD format (LAD blocks only)"
)
print("│ ├── 02_ProgramBlocks_XML_Compare/ # Traditional XML for comparison")
print("│ ├── 03_PlcDataTypes_SD/")
print("│ ├── 04_PlcDataTypes_XML_Compare/")
print("│ ├── 05_PlcTags_SD/")
print("│ └── 06_PlcTags_XML_Compare/")
print("\n📋 IMPORTANT SIMATIC SD LIMITATIONS:")
print(" • SIMATIC SD format ONLY supports LAD (Ladder) programming language")
print(" • SCL, STL, FBD blocks are exported as XML only")
print(" • Only FB, FC, OB block types are typically supported")
print(" • Complex LAD elements may still fall back to XML")
print(
"\nNow you can compare the differences between SIMATIC SD and traditional XML formats!"
)
# Add file analysis
print("\n=== FILE FORMAT ANALYSIS ===")
for plc_device in plcs:
plc_name = plc_device.get_name()
import datetime
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
plc_export_dir = os.path.join(
export_dir, f"{plc_name}_SimaticSD_{timestamp}"
)
print(f"\nAnalyzing exported files for PLC: {plc_name}")
folders_to_check = [
("01_ProgramBlocks_SD", "SIMATIC SD Blocks"),
("02_ProgramBlocks_XML_Compare", "XML Blocks"),
("03_PlcDataTypes_SD", "SIMATIC SD UDTs"),
("04_PlcDataTypes_XML_Compare", "XML UDTs"),
("05_PlcTags_SD", "SIMATIC SD Tags"),
("06_PlcTags_XML_Compare", "XML Tags"),
]
simatic_sd_working = False
total_sd_files = 0
total_xml_fallbacks = 0
for folder_name, description in folders_to_check:
folder_path = os.path.join(plc_export_dir, folder_name)
if os.path.exists(folder_path):
actual_format, extensions, file_count = verify_export_format(
folder_path
)
print(f" {description}: {file_count} files")
print(f" Format detected: {actual_format}")
print(f" File extensions: {extensions}")
if "SD" in folder_name: # This should be SIMATIC SD folder
if actual_format == "XML_ONLY":
print(
f" ⚠️ WARNING: Expected SIMATIC SD but got XML only!"
)
total_xml_fallbacks += file_count
elif actual_format == "SIMATIC_SD":
simatic_sd_working = True
total_sd_files += file_count
elif actual_format == "UNKNOWN" and file_count > 0:
print(f" 🔍 UNKNOWN format - needs manual inspection")
else:
print(f" {description}: Folder not found")
print(f"\n🔍 SIMATIC SD DIAGNOSIS FOR {plc_name}:")
if simatic_sd_working:
print(
f" ✅ SIMATIC SD is working: {total_sd_files} files in true SD format"
)
else:
print(f" ❌ SIMATIC SD NOT working: All 'SD' exports are actually XML")
print(f" 📊 Total XML fallbacks: {total_xml_fallbacks}")
if total_xml_fallbacks > 0:
print(f"\n 💡 POSSIBLE CAUSES:")
print(f" • TIA Portal version doesn't fully support SIMATIC SD")
print(f" • TIA Scripting version incompatible with SIMATIC SD")
print(f" • Project blocks contain unsupported LAD elements")
print(
f" • SIMATIC SD enum exists but falls back to XML silently"
)
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:
# 6. 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.")