435 lines
16 KiB
Python
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() |