AnsysLink/scripts/blade_mesh_cli.py

435 lines
16 KiB
Python

#!/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 <step_file_path> [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()