285 lines
11 KiB
Python
285 lines
11 KiB
Python
"""
|
|
Error handling utilities for CAE Mesh Generator
|
|
"""
|
|
import logging
|
|
import traceback
|
|
from datetime import datetime
|
|
from functools import wraps
|
|
from flask import jsonify
|
|
from typing import Dict, Any, Optional
|
|
|
|
# Configure logging
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
|
handlers=[
|
|
logging.FileHandler('app.log'),
|
|
logging.StreamHandler()
|
|
]
|
|
)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
class MeshGeneratorError(Exception):
|
|
"""Base exception class for mesh generator errors"""
|
|
def __init__(self, message: str, error_code: str = None, details: Dict = None):
|
|
self.message = message
|
|
self.error_code = error_code or 'GENERAL_ERROR'
|
|
self.details = details or {}
|
|
super().__init__(self.message)
|
|
|
|
class FileUploadError(MeshGeneratorError):
|
|
"""Exception for file upload related errors"""
|
|
def __init__(self, message: str, details: Dict = None):
|
|
super().__init__(message, 'FILE_UPLOAD_ERROR', details)
|
|
|
|
class ANSYSError(MeshGeneratorError):
|
|
"""Exception for ANSYS related errors"""
|
|
def __init__(self, message: str, details: Dict = None, diagnosis=None):
|
|
super().__init__(message, 'ANSYS_ERROR', details)
|
|
self.diagnosis = diagnosis # ErrorDiagnosis object from ANSYSErrorHandler
|
|
|
|
class MeshGenerationError(MeshGeneratorError):
|
|
"""Exception for mesh generation related errors"""
|
|
def __init__(self, message: str, details: Dict = None):
|
|
super().__init__(message, 'MESH_GENERATION_ERROR', details)
|
|
|
|
class ValidationError(MeshGeneratorError):
|
|
"""Exception for validation related errors"""
|
|
def __init__(self, message: str, details: Dict = None):
|
|
super().__init__(message, 'VALIDATION_ERROR', details)
|
|
|
|
def handle_api_error(func):
|
|
"""Decorator for handling API errors"""
|
|
@wraps(func)
|
|
def wrapper(*args, **kwargs):
|
|
try:
|
|
return func(*args, **kwargs)
|
|
except MeshGeneratorError as e:
|
|
logger.error(f"API Error in {func.__name__}: {e.message}", extra={
|
|
'error_code': e.error_code,
|
|
'details': e.details
|
|
})
|
|
return jsonify({
|
|
'success': False,
|
|
'error': e.message,
|
|
'error_code': e.error_code,
|
|
'details': e.details
|
|
}), 400
|
|
except Exception as e:
|
|
logger.error(f"Unexpected error in {func.__name__}: {str(e)}", extra={
|
|
'traceback': traceback.format_exc()
|
|
})
|
|
return jsonify({
|
|
'success': False,
|
|
'error': 'An unexpected error occurred. Please try again.',
|
|
'error_code': 'INTERNAL_ERROR'
|
|
}), 500
|
|
return wrapper
|
|
|
|
def handle_ansys_error(func):
|
|
"""Decorator for handling ANSYS-specific errors with intelligent diagnosis"""
|
|
@wraps(func)
|
|
def wrapper(*args, **kwargs):
|
|
try:
|
|
return func(*args, **kwargs)
|
|
except ImportError as e:
|
|
logger.warning(f"ANSYS not available: {str(e)}")
|
|
raise ANSYSError(
|
|
"ANSYS Mechanical is not available. Please check your installation.",
|
|
details={'import_error': str(e)}
|
|
)
|
|
except Exception as e:
|
|
error_msg = str(e)
|
|
|
|
# Use intelligent ANSYS error analysis
|
|
try:
|
|
from backend.pymechanical.ansys_error_handler import ANSYSErrorHandler, ErrorContext
|
|
|
|
# Initialize error handler (could be cached for performance)
|
|
error_handler = ANSYSErrorHandler()
|
|
|
|
# Create error context from function context
|
|
context = ErrorContext(
|
|
operation_type=func.__name__,
|
|
timestamp=datetime.now()
|
|
)
|
|
|
|
# Analyze the error
|
|
diagnosis = error_handler.analyze_error(error_msg, context)
|
|
|
|
# Create enhanced error with diagnosis
|
|
user_message = f"{diagnosis.title}: {diagnosis.description}"
|
|
details = {
|
|
'original_error': error_msg,
|
|
'error_category': diagnosis.category.value,
|
|
'severity': diagnosis.severity.value,
|
|
'solutions': diagnosis.immediate_solutions,
|
|
'estimated_fix_time': diagnosis.estimated_fix_time,
|
|
'recovery_possible': diagnosis.recovery_possible,
|
|
'confidence_level': diagnosis.confidence_level
|
|
}
|
|
|
|
logger.error(f"ANSYS error in {func.__name__}: {error_msg} (Category: {diagnosis.category.value}, Severity: {diagnosis.severity.value})")
|
|
raise ANSYSError(user_message, details=details, diagnosis=diagnosis)
|
|
|
|
except ImportError:
|
|
# Fallback to basic error handling if error handler not available
|
|
logger.warning("ANSYS error handler not available, using basic error handling")
|
|
|
|
# Basic ANSYS error patterns and user-friendly messages
|
|
if 'license' in error_msg.lower():
|
|
user_message = "ANSYS license error. Please check your license status."
|
|
elif 'connection' in error_msg.lower():
|
|
user_message = "Cannot connect to ANSYS. Please ensure ANSYS is properly installed."
|
|
elif 'geometry' in error_msg.lower():
|
|
user_message = "Geometry processing error. Please check your STEP file."
|
|
elif 'mesh' in error_msg.lower():
|
|
user_message = "Mesh generation failed. The geometry may be too complex or contain errors."
|
|
else:
|
|
user_message = f"ANSYS processing error: {error_msg}"
|
|
|
|
logger.error(f"ANSYS error in {func.__name__}: {error_msg}")
|
|
raise ANSYSError(user_message, details={'original_error': error_msg})
|
|
|
|
except Exception as analysis_error:
|
|
# Fallback if error analysis fails
|
|
logger.warning(f"Error analysis failed: {str(analysis_error)}, using basic error handling")
|
|
user_message = f"ANSYS processing error: {error_msg}"
|
|
logger.error(f"ANSYS error in {func.__name__}: {error_msg}")
|
|
raise ANSYSError(user_message, details={'original_error': error_msg, 'analysis_error': str(analysis_error)})
|
|
return wrapper
|
|
|
|
def validate_file_upload(file) -> None:
|
|
"""Validate uploaded file"""
|
|
if not file:
|
|
raise FileUploadError("No file provided")
|
|
|
|
if file.filename == '':
|
|
raise FileUploadError("No file selected")
|
|
|
|
# Check file extension
|
|
allowed_extensions = {'.step', '.stp'}
|
|
file_ext = '.' + file.filename.rsplit('.', 1)[-1].lower()
|
|
if file_ext not in allowed_extensions:
|
|
raise FileUploadError(
|
|
f"Invalid file format. Only STEP files (.step, .stp) are supported.",
|
|
details={'provided_extension': file_ext, 'allowed_extensions': list(allowed_extensions)}
|
|
)
|
|
|
|
# Check file size (this is also handled by Flask, but we add explicit validation)
|
|
if hasattr(file, 'content_length') and file.content_length:
|
|
max_size = 100 * 1024 * 1024 # 100MB
|
|
if file.content_length > max_size:
|
|
raise FileUploadError(
|
|
f"File too large. Maximum size is 100MB.",
|
|
details={'file_size': file.content_length, 'max_size': max_size}
|
|
)
|
|
|
|
def validate_mesh_parameters(params: Dict[str, Any]) -> None:
|
|
"""Validate mesh generation parameters"""
|
|
if not isinstance(params, dict):
|
|
raise ValidationError("Parameters must be a dictionary")
|
|
|
|
# Validate element size if provided
|
|
if 'element_size' in params:
|
|
element_size = params['element_size']
|
|
if not isinstance(element_size, (int, float)) or element_size <= 0:
|
|
raise ValidationError(
|
|
"Element size must be a positive number",
|
|
details={'provided_value': element_size}
|
|
)
|
|
|
|
# Validate refinement settings if provided
|
|
if 'refinement_regions' in params:
|
|
regions = params['refinement_regions']
|
|
if not isinstance(regions, list):
|
|
raise ValidationError("Refinement regions must be a list")
|
|
|
|
for i, region in enumerate(regions):
|
|
if not isinstance(region, dict):
|
|
raise ValidationError(
|
|
f"Refinement region {i} must be a dictionary",
|
|
details={'region_index': i}
|
|
)
|
|
|
|
class ErrorReporter:
|
|
"""Class for collecting and reporting errors"""
|
|
|
|
def __init__(self):
|
|
self.errors = []
|
|
self.warnings = []
|
|
|
|
def add_error(self, message: str, error_code: str = None, details: Dict = None):
|
|
"""Add an error to the report"""
|
|
self.errors.append({
|
|
'message': message,
|
|
'error_code': error_code or 'GENERAL_ERROR',
|
|
'details': details or {},
|
|
'timestamp': self._get_timestamp()
|
|
})
|
|
logger.error(f"Error reported: {message}", extra={'error_code': error_code, 'details': details})
|
|
|
|
def add_warning(self, message: str, details: Dict = None):
|
|
"""Add a warning to the report"""
|
|
self.warnings.append({
|
|
'message': message,
|
|
'details': details or {},
|
|
'timestamp': self._get_timestamp()
|
|
})
|
|
logger.warning(f"Warning reported: {message}", extra={'details': details})
|
|
|
|
def has_errors(self) -> bool:
|
|
"""Check if there are any errors"""
|
|
return len(self.errors) > 0
|
|
|
|
def has_warnings(self) -> bool:
|
|
"""Check if there are any warnings"""
|
|
return len(self.warnings) > 0
|
|
|
|
def get_report(self) -> Dict[str, Any]:
|
|
"""Get the complete error report"""
|
|
return {
|
|
'errors': self.errors,
|
|
'warnings': self.warnings,
|
|
'error_count': len(self.errors),
|
|
'warning_count': len(self.warnings)
|
|
}
|
|
|
|
def clear(self):
|
|
"""Clear all errors and warnings"""
|
|
self.errors.clear()
|
|
self.warnings.clear()
|
|
|
|
def _get_timestamp(self) -> str:
|
|
"""Get current timestamp"""
|
|
from datetime import datetime
|
|
return datetime.now().isoformat()
|
|
|
|
def create_error_response(error: Exception, status_code: int = 500) -> tuple:
|
|
"""Create a standardized error response"""
|
|
if isinstance(error, MeshGeneratorError):
|
|
return jsonify({
|
|
'success': False,
|
|
'error': error.message,
|
|
'error_code': error.error_code,
|
|
'details': error.details
|
|
}), status_code
|
|
else:
|
|
logger.error(f"Unexpected error: {str(error)}", extra={'traceback': traceback.format_exc()})
|
|
return jsonify({
|
|
'success': False,
|
|
'error': 'An unexpected error occurred',
|
|
'error_code': 'INTERNAL_ERROR'
|
|
}), status_code
|
|
|
|
def log_processing_step(step_name: str, status: str, details: Dict = None):
|
|
"""Log processing step with standardized format"""
|
|
logger.info(f"Processing step: {step_name} - {status}", extra={
|
|
'step': step_name,
|
|
'status': status,
|
|
'details': details or {}
|
|
})
|
|
|
|
# Global error reporter instance
|
|
error_reporter = ErrorReporter() |