#!/usr/bin/env python3 """ Blade Mesh Generator - Command Line Interface A professional command-line tool for generating ANSYS mesh from STEP files. Designed for customer demonstrations of automated blade mesh generation. Usage: python blade_mesh_cli.py [options] Example: python blade_mesh_cli.py resource/blade.step python blade_mesh_cli.py C:/models/turbine_blade.step --output-dir C:/results """ import sys import os import argparse import time import glob import shutil from pathlib import Path from datetime import datetime # Add project root to path sys.path.append(os.path.dirname(os.path.abspath(__file__))) from backend.utils.mesh_processor import process_blade_mesh from backend.utils.state_manager import state_manager import logging # Configure logging for CLI logging.basicConfig( level=logging.WARNING, # Reduce noise for CLI format='%(message)s' ) class BladeCliColors: """ANSI color codes for terminal output""" HEADER = '\033[95m' BLUE = '\033[94m' CYAN = '\033[96m' GREEN = '\033[92m' YELLOW = '\033[93m' RED = '\033[91m' BOLD = '\033[1m' UNDERLINE = '\033[4m' END = '\033[0m' def print_header(): """Print application header""" print(f"{BladeCliColors.CYAN}{BladeCliColors.BOLD}") print("="*70) print(" 网格生成器 - Ansys版") print("="*70) print(f"{BladeCliColors.END}") def print_step(step_num, title, color=BladeCliColors.BLUE): """Print a processing step""" print(f"\n{color}{BladeCliColors.BOLD}[Step {step_num}] {title}{BladeCliColors.END}") print("-" * (len(title) + 10)) def print_success(message): """Print success message""" print(f"{BladeCliColors.GREEN}✓ {message}{BladeCliColors.END}") def print_warning(message): """Print warning message""" print(f"{BladeCliColors.YELLOW}⚠ {message}{BladeCliColors.END}") def print_error(message): """Print error message""" print(f"{BladeCliColors.RED}✗ {message}{BladeCliColors.END}") def print_info(message, indent=2): """Print info message with indentation""" print(" " * indent + message) def validate_step_file(file_path): """Validate STEP file exists and is readable""" if not os.path.exists(file_path): print_error(f"STEP file not found: {file_path}") return False if not file_path.lower().endswith(('.step', '.stp')): print_warning(f"File extension is not .step or .stp: {file_path}") print_info("Continuing anyway...") try: file_size = os.path.getsize(file_path) if file_size == 0: print_error("STEP file is empty") return False print_success(f"STEP file validated: {file_size:,} bytes") return True except Exception as e: print_error(f"Error reading STEP file: {e}") return False def create_progress_callback(): """Create a progress callback for mesh processing""" last_step = None def progress_callback(percentage, message, step=None): nonlocal last_step # Print step header if step changed if step and step != last_step: step_names = { 'INITIALIZING': 'Initializing ANSYS Session', 'IMPORTING_GEOMETRY': 'Importing Geometry', 'VALIDATING_GEOMETRY': 'Validating Geometry', 'CREATING_NAMED_SELECTIONS': 'Creating Named Selections', 'APPLYING_MESH_CONTROLS': 'Applying Mesh Controls', 'GENERATING_MESH': 'Generating Mesh', 'CHECKING_QUALITY': 'Checking Mesh Quality', 'FINALIZING': 'Finalizing Results' } if hasattr(step, 'value'): step_name = step_names.get(step.value, str(step.value)) else: step_name = step_names.get(str(step), str(step)) print(f"\n{BladeCliColors.CYAN}► {step_name}...{BladeCliColors.END}") last_step = step # Print progress if percentage >= 0: bar_length = 30 filled_length = int(bar_length * percentage / 100) bar = '█' * filled_length + '░' * (bar_length - filled_length) print(f"\r [{bar}] {percentage:5.1f}% - {message}", end='', flush=True) else: print(f"\r {message}", end='', flush=True) return progress_callback def save_mesh_database(result, step_file_path, output_dir): """Save the ANSYS mesh database file by copying from ANSYS temp directory""" import shutil import glob try: print_step("Final", "Saving Mesh Database", BladeCliColors.GREEN) # Create output filename step_file = Path(step_file_path) timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") output_filename = f"{step_file.stem}_mesh_{timestamp}.mechdb" output_path = Path(output_dir) / output_filename # Try to find the actual ANSYS .mechdb file mechdb_copied = False # Common ANSYS temp directories to search temp_patterns = [ os.path.expanduser("~/AppData/Local/Temp/ANSYS.*/AnsysMech*/Project_Mech_Files/*.mechdb"), "C:/Users/*/AppData/Local/Temp/ANSYS.*/AnsysMech*/Project_Mech_Files/*.mechdb", os.path.expanduser("~/AppData/Local/Temp/ANSYS.*/AnsysMech*/*.mechdb"), "C:/temp/ANSYS*/*.mechdb", "./temp/*.mechdb" ] print_info("Searching for ANSYS mesh database file...") for pattern in temp_patterns: try: mechdb_files = glob.glob(pattern, recursive=True) if mechdb_files: # Sort by modification time, get the most recent mechdb_files.sort(key=lambda x: os.path.getmtime(x), reverse=True) source_file = mechdb_files[0] # Check if file is recent (within last 24 hours) file_age = time.time() - os.path.getmtime(source_file) if file_age < 86400: # 24 hours print_info(f"Found recent ANSYS database: {os.path.basename(source_file)}") # Copy the file shutil.copy2(source_file, output_path) mechdb_copied = True file_size = os.path.getsize(output_path) print_success(f"Mesh database copied: {output_filename} ({file_size:,} bytes)") break except Exception as search_error: continue if not mechdb_copied: print_warning("Could not find recent ANSYS .mechdb file, creating placeholder") # Create a placeholder file to indicate where the real file would be with open(output_path, 'wb') as f: f.write(b"ANSYS Mechanical Database Placeholder\n") f.write(f"Generated: {datetime.now()}\n".encode()) f.write(f"Elements: {result.element_count}\n".encode()) f.write(f"Nodes: {result.node_count}\n".encode()) print_info(f"Placeholder created: {output_filename}") # Always create summary report summary_content = f"""BLADE MESH GENERATION REPORT {'='*50} Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} Source File: {step_file_path} Output File: {output_filename} MESH STATISTICS: - Elements: {result.element_count:,} - Nodes: {result.node_count:,} - Generation Time: {result.total_time:.1f} seconds QUALITY ASSESSMENT: - Quality Score: {result.quality_score:.2f} - Quality Status: {result.quality_status} PROCESSING DETAILS: - Named Selections: {len(result.named_selections_result.get('selections_created', []))} - Mesh Controls Applied: {len(result.mesh_controls_result.get('controls_applied', []))} - Warnings: {len(result.warnings)} DATABASE FILE: - File copied: {'Yes' if mechdb_copied else 'No (placeholder created)'} - File size: {os.path.getsize(output_path):,} bytes STATUS: {'SUCCESS' if result.success else 'FAILED'} {'='*50} Generated by Blade Mesh Generator Professional Edition """ # Save summary file summary_path = output_path.with_suffix('.txt') with open(summary_path, 'w', encoding='utf-8') as f: f.write(summary_content) print_success(f"Summary report saved: {summary_path}") print_info(f"Output directory: {output_dir}") return str(output_path) except Exception as e: print_error(f"Error saving mesh database: {e}") return None def print_final_results(result, mesh_file_path, processing_time): """Print final results summary""" print(f"\n{BladeCliColors.CYAN}{BladeCliColors.BOLD}") print("="*70) print(" 网格生成结果") print("="*70) print(f"{BladeCliColors.END}") if result.success: print_success("MESH GENERATION COMPLETED SUCCESSFULLY") print(f"\n{BladeCliColors.BOLD}Mesh Statistics:{BladeCliColors.END}") # Check if counts are estimated counts_estimated = any("counts unavailable" in str(w).lower() for w in result.warnings) if counts_estimated: print_info(f"Elements: ~{result.element_count:,} (estimated)") print_info(f"Nodes: ~{result.node_count:,} (estimated)") else: print_info(f"Elements: {result.element_count:,}") print_info(f"Nodes: {result.node_count:,}") print_info(f"Generation Time: {result.total_time:.1f} seconds") print(f"\n{BladeCliColors.BOLD}Quality Assessment:{BladeCliColors.END}") print_info(f"Quality Score: {result.quality_score:.2f}/10.0") print_info(f"Quality Status: {result.quality_status}") if result.quality_score >= 7.0: print_success("Excellent mesh quality achieved") elif result.quality_score >= 5.0: print_success("Good mesh quality achieved") else: print_warning("Mesh quality could be improved") print(f"\n{BladeCliColors.BOLD}Processing Summary:{BladeCliColors.END}") print_info(f"Named Selections: {len(result.named_selections_result.get('selections_created', []))}") print_info(f"Mesh Controls: {len(result.mesh_controls_result.get('controls_applied', []))}") print_info(f"Total Processing Time: {processing_time:.1f} seconds") if mesh_file_path: print(f"\n{BladeCliColors.BOLD}Output Files:{BladeCliColors.END}") print_info(f"Mesh Database: {mesh_file_path}") if result.warnings: print(f"\n{BladeCliColors.YELLOW}{BladeCliColors.BOLD}Warnings:{BladeCliColors.END}") for warning in result.warnings: print_warning(warning) # Display detailed quality issues if available if result.quality_check_result and result.quality_check_result.get('critical_issues'): critical_issues = result.quality_check_result.get('critical_issues', []) if critical_issues: print(f"\n{BladeCliColors.RED}{BladeCliColors.BOLD}Quality Issues Found:{BladeCliColors.END}") # Check if these are estimated values has_warnings = any("estimated" in str(w).lower() for w in result.warnings) if has_warnings: print_info("(Note: Values below are estimates due to limited ANSYS output)", indent=2) for i, issue in enumerate(critical_issues, 1): if isinstance(issue, dict): issue_type = issue.get('type', 'Unknown') issue_desc = issue.get('description', 'No description') issue_count = issue.get('count', 'N/A') print_info(f"{i}. {issue_type}: {issue_desc} (Count: {issue_count})", indent=4) else: print_info(f"{i}. {issue}", indent=4) # Display quality metrics if available if result.quality_check_result and result.quality_check_result.get('quality_metrics'): quality_metrics = result.quality_check_result.get('quality_metrics', {}) if quality_metrics: print(f"\n{BladeCliColors.BOLD}Quality Metrics:{BladeCliColors.END}") for metric_name, metric_value in quality_metrics.items(): if isinstance(metric_value, dict): min_val = metric_value.get('min', 'N/A') max_val = metric_value.get('max', 'N/A') avg_val = metric_value.get('average', 'N/A') print_info(f"{metric_name}: Min={min_val}, Max={max_val}, Avg={avg_val}") else: print_info(f"{metric_name}: {metric_value}") else: print_error("MESH GENERATION FAILED") if hasattr(result, 'error') and result.error: print_info(f"Error: {result.error}") if result.warnings: print(f"\n{BladeCliColors.YELLOW}{BladeCliColors.BOLD}Warnings:{BladeCliColors.END}") for warning in result.warnings: print_warning(warning) print(f"\n{BladeCliColors.CYAN}{'='*70}{BladeCliColors.END}") def main(): """Main CLI function""" parser = argparse.ArgumentParser( description='Professional Blade Mesh Generator - Generate ANSYS mesh from STEP files', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: python blade_mesh_cli.py resource/blade.step python blade_mesh_cli.py C:/models/blade.step --output-dir C:/results python blade_mesh_cli.py blade.step --simulation For technical support, contact the development team. """ ) parser.add_argument('step_file', help='Path to the STEP file to process') parser.add_argument('--output-dir', '-o', help='Output directory for mesh files (default: same as STEP file directory)') parser.add_argument('--simulation', '-s', action='store_true', help='Run in simulation mode (faster, for testing)') parser.add_argument('--verbose', '-v', action='store_true', help='Enable verbose output') args = parser.parse_args() # Configure logging based on verbose flag if args.verbose: logging.getLogger().setLevel(logging.INFO) # Print header print_header() # Validate inputs print_step(1, "Validating Input File") if not validate_step_file(args.step_file): sys.exit(1) # Determine output directory if args.output_dir: output_dir = Path(args.output_dir) else: output_dir = Path(args.step_file).parent # Create output directory if it doesn't exist output_dir.mkdir(parents=True, exist_ok=True) print_success(f"Output directory: {output_dir}") #if args.simulation: # print_warning("Running in SIMULATION mode (may still use ANSYS if available)") # Start processing print_step(2, "Starting Mesh Generation Process") start_time = time.time() try: # Create progress callback progress_callback = create_progress_callback() # Process the mesh result = process_blade_mesh( file_path=args.step_file, simulation_mode=args.simulation, progress_callback=progress_callback ) print() # New line after progress bar processing_time = time.time() - start_time # Save mesh database mesh_file_path = None if result.success: mesh_file_path = save_mesh_database(result, args.step_file, output_dir) # Print final results print_final_results(result, mesh_file_path, processing_time) # Exit with appropriate code sys.exit(0 if result.success else 1) except KeyboardInterrupt: print(f"\n\n{BladeCliColors.YELLOW}Process interrupted by user{BladeCliColors.END}") sys.exit(130) except Exception as e: print(f"\n\n{BladeCliColors.RED}Unexpected error: {e}{BladeCliColors.END}") if args.verbose: import traceback traceback.print_exc() sys.exit(1) if __name__ == '__main__': main()