PLC_Generation/Routines Generator/complete_workflow.py
2025-08-05 14:38:54 +04:00

344 lines
13 KiB
Python

#!/usr/bin/env python3
"""
Complete PLC Generation Workflow
Runs the entire pipeline from raw Excel to compiled ACD:
1. PLC Data Generator (raw Excel → DESC_IP_MERGED.xlsx)
2. Routines Generator (DESC_IP_MERGED.xlsx → L5X files)
3. L5X2ACD Compiler (L5X files → ACD files)
"""
from __future__ import annotations
import sys
import argparse
import subprocess
from pathlib import Path
def get_project_paths():
"""Get standardized paths for all project components."""
project_root = Path(__file__).parent.parent.resolve()
return {
'project_root': project_root,
'data_generator': project_root / "PLC Data Generator",
'routines_generator': project_root / "Routines Generator",
'l5x2acd_compiler': project_root / "L5X2ACD Compiler",
'io_tree_generator': project_root / "IO Tree Configuration Generator"
}
def run_plc_data_generator(raw_excel_file: Path, paths: dict) -> bool:
"""Run the PLC Data Generator to create DESC_IP_MERGED.xlsx."""
data_gen_dir = paths['data_generator']
data_gen_script = data_gen_dir / "main.py"
if not data_gen_script.exists():
print(f"ERROR: PLC Data Generator not found at {data_gen_script}")
return False
if not raw_excel_file.exists():
print(f"ERROR: Raw Excel file not found at {raw_excel_file}")
return False
print(f"=== Step 1: Processing Raw Excel Data ===")
print(f"Input: {raw_excel_file}")
print(f"Processing with PLC Data Generator...")
try:
# Run the PLC Data Generator with the Excel file path as argument
result = subprocess.run([
sys.executable,
str(data_gen_script),
str(raw_excel_file.resolve()) # Pass the Excel file path as argument
], cwd=data_gen_dir, capture_output=True, text=True)
# Check if core processing succeeded by looking for output files
# Even if there's a permission error at the end, the processing might have completed
source = data_gen_dir / "DESC_IP_MERGED.xlsx"
success_indicators = [
"Processing complete!" in result.stdout,
"New Excel file created:" in result.stdout,
source.exists()
]
# Consider it successful if the essential files were created, even with permission errors
if result.returncode == 0 or (any(success_indicators) and "[Errno 1] Operation not permitted" in result.stdout):
if result.returncode != 0:
print("WARNING: Permission error at end of processing, but core processing completed successfully")
else:
print("SUCCESS: PLC Data Generator completed successfully")
print(result.stdout)
# Copy DESC_IP_MERGED.xlsx from data generator output (it already has safety sheets)
dest = paths['routines_generator'] / "DESC_IP_MERGED.xlsx"
if source.exists():
import shutil
shutil.copy2(source, dest)
print(f"SUCCESS: DESC_IP_MERGED.xlsx (with safety sheets) copied to {dest}")
return True
else:
print("ERROR: DESC_IP_MERGED.xlsx not found after data generation")
return False
else:
print("ERROR: PLC Data Generator failed")
print("STDOUT:", result.stdout)
print("STDERR:", result.stderr)
return False
except Exception as e:
print(f"ERROR: Error running PLC Data Generator: {e}")
return False
def run_routines_generator(paths: dict, project_name: str = None, ignore_estop1ok: bool = False) -> bool:
"""Run the Routines Generator using generate_main_with_dpm.py script."""
print(f"\n=== Step 2: Generating L5X Programs ===")
routines_dir = paths['routines_generator']
main_dpm_script = routines_dir / "generate_main_with_dpm.py"
if not main_dpm_script.exists():
print(f"ERROR: generate_main_with_dpm.py script not found at {main_dpm_script}")
return False
try:
# Build command arguments for generate_main_with_dpm.py script
cmd_args = [
sys.executable,
str(main_dpm_script),
"--desc-ip-mode" # Use DESC_IP data extraction mode
]
# Add project name if provided
if project_name:
cmd_args.extend(["--project-name", project_name])
print(f"INFO: Running with project name: {project_name}")
# Add ignore-estop1ok flag if set
if ignore_estop1ok:
cmd_args.append("--ignore-estop1ok")
print("INFO: Running with --ignore-estop1ok flag")
# Run the main program generator script with DPM routines
result = subprocess.run(cmd_args, cwd=routines_dir, capture_output=True, text=True)
print(result.stdout)
if result.stderr:
print("STDERR:", result.stderr)
if result.returncode == 0:
print("SUCCESS: Routines generation completed successfully")
return True
else:
print("ERROR: Routines generation failed")
return False
except Exception as e:
print(f"ERROR: Error running Routines Generator: {e}")
return False
def run_io_tree_generator(paths: dict, project_name: str) -> bool:
"""Run the IO Tree Configuration Generator."""
print(f"\n=== Step 3: Generating Complete Project L5X ===")
io_tree_dir = paths['io_tree_generator']
enhanced_mcm_script = io_tree_dir / "enhanced_mcm_generator.py"
# Use the file directly from PLC Data Generator since we're skipping Routines Generator
desc_ip_file = paths['data_generator'] / "DESC_IP_MERGED.xlsx"
if not enhanced_mcm_script.exists():
print(f"ERROR: enhanced_mcm_generator.py not found at {enhanced_mcm_script}")
return False
# Load zones configuration for the project
zones_json = None
if project_name:
try:
import sys
sys.path.append(str(paths['project_root']))
from zones_config import ZONES_CONFIGS, DEFAULT_ZONES
# Determine project type from project name (MCM01, MCM04, MCM05, etc.)
if 'MCM01' in project_name.upper():
zones_to_use = ZONES_CONFIGS.get("MCM01", DEFAULT_ZONES)
elif 'MCM04' in project_name.upper():
zones_to_use = ZONES_CONFIGS.get("MCM04", DEFAULT_ZONES)
elif 'MCM05' in project_name.upper():
zones_to_use = ZONES_CONFIGS.get("MCM05", DEFAULT_ZONES)
else:
zones_to_use = DEFAULT_ZONES
import json
zones_json = json.dumps(zones_to_use)
print(f"Using zones configuration for IO Tree Generator: {project_name}")
except Exception as e:
print(f"WARNING: Could not load zones configuration for IO Tree: {e}")
print("Proceeding without zones...")
try:
# Build command arguments
cmd_args = [
sys.executable,
str(enhanced_mcm_script),
str(desc_ip_file),
project_name
]
# Add zones if available
if zones_json:
cmd_args.extend(["--zones", zones_json])
# Run the IO Tree Configuration Generator
result = subprocess.run(cmd_args, cwd=io_tree_dir, capture_output=True, text=True)
print(result.stdout)
if result.stderr:
print("STDERR:", result.stderr)
if result.returncode == 0:
print("SUCCESS: Complete project L5X generated successfully")
return True
else:
print("ERROR: IO Tree generation failed")
return False
except Exception as e:
print(f"ERROR: Error running IO Tree Generator: {e}")
return False
def run_l5x_to_acd_compiler(paths: dict, project_name: str) -> bool:
"""Prepare for L5X2ACD Compilation using dynamic compilation manager."""
print(f"\n=== Step 4: Preparing for L5X to ACD Compilation ===")
# Find the generated complete project L5X file
io_tree_dir = paths['io_tree_generator']
generated_projects_dir = io_tree_dir / "generated_projects"
if not generated_projects_dir.exists():
print(f"ERROR: Generated projects directory not found at {generated_projects_dir}")
return False
# Look for L5X files that start with the project name
l5x_files = list(generated_projects_dir.glob(f"{project_name}*.L5X"))
if not l5x_files:
print(f"ERROR: No L5X files found starting with '{project_name}' in {generated_projects_dir}")
available_files = list(generated_projects_dir.glob("*.L5X"))
if available_files:
print(f"Available L5X files: {[f.name for f in available_files]}")
return False
if len(l5x_files) > 1:
print(f"WARNING: Multiple L5X files found: {[f.name for f in l5x_files]}")
print(f"Using the first one: {l5x_files[0].name}")
complete_l5x = l5x_files[0]
print(f"Found generated L5X file: {complete_l5x.name}")
# Use the dynamic compilation manager
l5x2acd_dir = paths['project_root'] / "L5X2ACD Compiler"
try:
# Import and use the compilation manager
import sys
sys.path.append(str(l5x2acd_dir))
from compilation_manager import CompilationManager
# Create compilation manager
manager = CompilationManager(l5x2acd_dir)
# Determine project-specific options
project_type = "UNKNOWN"
options = {}
if project_name:
if "MCM01" in project_name.upper():
project_type = "MCM01"
options['enable_safety_validation'] = True
elif "MCM04" in project_name.upper():
project_type = "MCM04"
options['enable_feeder_optimization'] = True
print(f"Using dynamic compilation setup for project type: {project_type}")
# Setup compilation with wipe and dynamic generation
result = manager.setup_compilation(
source_l5x=complete_l5x,
project_name=project_name or complete_l5x.stem,
compilation_options=options,
wipe_existing=True
)
print("SUCCESS: Dynamic compilation setup completed")
print("=" * 60)
print("⚠️ COMPILATION STEP REQUIRES WINDOWS:")
print(f" L5X File: {result['l5x_file']}")
print(f" Batch File: {result['batch_file']}")
print()
print("🪟 To compile on Windows:")
print(f" 1. Run: {result['batch_file']}")
print(" 2. Or double-click: {result['batch_file'].name}")
print(" 3. Or manually run:")
l5x2acd_windows_path = str(l5x2acd_dir).replace('/mnt/c/', 'C:\\').replace('/', '\\')
l5x_windows_path = str(result['l5x_file']).replace('/mnt/c/', 'C:\\').replace('/', '\\')
print(f" cd \"{l5x2acd_windows_path}\"")
print(f" python l5x_to_acd.py \"{l5x_windows_path}\"")
print("=" * 60)
return True
except Exception as e:
print(f"ERROR: Failed to setup dynamic compilation: {e}")
import traceback
traceback.print_exc()
return False
def main() -> None:
"""Main entry point for complete workflow."""
parser = argparse.ArgumentParser(description="Complete PLC generation workflow from raw Excel to ACD")
parser.add_argument('--excel-file', type=Path, required=True, help='Raw Excel file to process')
parser.add_argument('--project-name', help='Project name (for compatibility)')
parser.add_argument('--ignore-estop1ok', action='store_true', help='Ignore ESTOP1OK tags in safety routines generation')
args = parser.parse_args()
# Get project paths
paths = get_project_paths()
print("Complete PLC Generation Workflow")
print("=" * 60)
print(f"Project Root: {paths['project_root']}")
print(f"Input Excel: {args.excel_file}")
print(f"Project: {args.project_name}")
print()
# Step 1: Process raw Excel data
if not run_plc_data_generator(args.excel_file, paths):
print("ERROR: Workflow failed at Step 1 (Data Processing)")
sys.exit(1)
# Step 2: Generate L5X programs (Routines Generator)
if not run_routines_generator(paths, args.project_name, args.ignore_estop1ok):
print("ERROR: Workflow failed at Step 2 (Routines Generation)")
sys.exit(1)
# Step 3: Generate complete project L5X (IO Tree Generator)
if not run_io_tree_generator(paths, args.project_name):
print("ERROR: Workflow failed at Step 3 (IO Tree Generation)")
sys.exit(1)
# Step 4: Compile L5X to ACD
if not run_l5x_to_acd_compiler(paths, args.project_name):
print("ERROR: Workflow failed at Step 4 (L5X2ACD Compilation)")
sys.exit(1)
print("\n" + "="*60)
print("SUCCESS: Complete PLC Generation Workflow completed!")
print(f"Generated L5X file: IO Tree Configuration Generator/generated_projects/{args.project_name}.L5X")
print("🪟 Run the generated batch file on Windows to compile to ACD")
print("="*60)
if __name__ == '__main__':
main()