AnsysLink/backend/utils/visualization_exporter.py
2025-08-11 13:58:59 +08:00

361 lines
13 KiB
Python

"""
Visualization Exporter for CAE Mesh Generator
This module handles mesh visualization export including image generation,
3D visualization, and mesh quality visualization for blade geometry.
"""
import logging
import os
import time
from typing import Dict, List, Any, Optional, Tuple
from datetime import datetime
from pathlib import Path
from dataclasses import dataclass
logger = logging.getLogger(__name__)
@dataclass
class VisualizationSettings:
"""Settings for mesh visualization export"""
width: int = 1280
height: int = 720
background_color: str = "white"
show_edges: bool = True
show_nodes: bool = False
camera_view: str = "isometric" # isometric, front, side, top
image_format: str = "PNG"
quality: int = 95
anti_aliasing: bool = True
@dataclass
class VisualizationResult:
"""Result container for visualization export"""
success: bool = False
image_path: Optional[str] = None
image_size: Tuple[int, int] = (0, 0)
file_size: int = 0
export_time: float = 0.0
error_message: Optional[str] = None
warnings: List[str] = None
def __post_init__(self):
if self.warnings is None:
self.warnings = []
class VisualizationExporter:
"""
Mesh visualization exporter for ANSYS Mechanical
This class provides functionality to export mesh visualizations,
generate images, and create visual reports for blade geometry.
"""
def __init__(self, mechanical_session=None, output_dir: str = "output"):
"""
Initialize visualization exporter
Args:
mechanical_session: Active PyMechanical session
output_dir: Directory for output files
"""
self.mechanical = mechanical_session
self.output_dir = Path(output_dir)
self.output_dir.mkdir(parents=True, exist_ok=True)
# Store mechanical session for real visualization export
self.mechanical = mechanical_session
logger.info("Visualization Exporter initialized")
def export_mesh_image(self,
filename: str = None,
settings: VisualizationSettings = None) -> VisualizationResult:
"""
Export mesh visualization as image
Args:
filename: Output filename (auto-generated if None)
settings: Visualization settings
Returns:
VisualizationResult with export results
"""
try:
logger.info("Starting mesh image export...")
start_time = time.time()
# Use default settings if not provided
if settings is None:
settings = VisualizationSettings()
# Generate filename if not provided
if filename is None:
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"mesh_visualization_{timestamp}.{settings.image_format.lower()}"
output_path = self.output_dir / filename
return self._export_real_image(output_path, settings, start_time)
except Exception as e:
logger.error(f"Mesh image export failed: {str(e)}")
result = VisualizationResult()
result.success = False
result.error_message = str(e)
return result
def _export_real_image(self,
output_path: Path,
settings: VisualizationSettings,
start_time: float) -> VisualizationResult:
"""
Export real mesh image using PyMechanical
Args:
output_path: Output file path
settings: Visualization settings
start_time: Export start time
Returns:
VisualizationResult with export results
"""
try:
logger.info("Exporting real mesh image using PyMechanical...")
# Prepare visualization script based on design document recommendations
visualization_script = f'''
# Mesh visualization export using PyMechanical API
import Ansys.Mechanical.Graphics as Graphics
try:
print("=== Starting Mesh Visualization Export ===")
# Set up graphics export settings
graphics_settings = Ansys.Mechanical.Graphics.GraphicsImageExportSettings()
graphics_settings.Width = {settings.width}
graphics_settings.Height = {settings.height}
graphics_settings.Background = Ansys.Mechanical.DataModel.Enums.GraphicsBackgroundType.{settings.background_color.title()}
graphics_settings.CurrentGraphicsDisplay = False
# Set image format
if "{settings.image_format.upper()}" == "PNG":
image_format = Ansys.Mechanical.DataModel.Enums.GraphicsImageExportFormat.PNG
elif "{settings.image_format.upper()}" == "JPG" or "{settings.image_format.upper()}" == "JPEG":
image_format = Ansys.Mechanical.DataModel.Enums.GraphicsImageExportFormat.JPG
else:
image_format = Ansys.Mechanical.DataModel.Enums.GraphicsImageExportFormat.PNG
print("Graphics settings configured")
# Set camera view
camera_view = "{settings.camera_view.lower()}"
if camera_view == "isometric":
ExtAPI.Graphics.Camera.SetSpecificViewOrientation(Ansys.Mechanical.DataModel.Enums.ViewOrientationType.Iso)
elif camera_view == "front":
ExtAPI.Graphics.Camera.SetSpecificViewOrientation(Ansys.Mechanical.DataModel.Enums.ViewOrientationType.Front)
elif camera_view == "side":
ExtAPI.Graphics.Camera.SetSpecificViewOrientation(Ansys.Mechanical.DataModel.Enums.ViewOrientationType.Right)
elif camera_view == "top":
ExtAPI.Graphics.Camera.SetSpecificViewOrientation(Ansys.Mechanical.DataModel.Enums.ViewOrientationType.Top)
print("Camera view set to: " + camera_view)
# Ensure mesh is visible
mesh = Model.Mesh
if mesh:
# Make sure mesh is displayed
mesh.Activate()
print("Mesh activated for visualization")
# Set mesh display options
if {str(settings.show_edges).lower()}:
print("Showing mesh edges")
if {str(settings.show_nodes).lower()}:
print("Showing mesh nodes")
# Fit view to show entire mesh
ExtAPI.Graphics.Camera.FitToScreen()
print("Camera fitted to screen")
# Export the image
export_path = r"{str(output_path).replace(chr(92), chr(92)+chr(92))}"
ExtAPI.Graphics.ExportImage(export_path, image_format, graphics_settings)
print("SUCCESS: Mesh visualization exported to: " + export_path)
except Exception as export_error:
print("ERROR: Mesh visualization export failed: " + str(export_error))
print("Common causes:")
print("- No mesh generated yet")
print("- Graphics system not initialized")
print("- Invalid file path or permissions")
print("- Unsupported image format")
raise export_error
'''
# Execute visualization script
result_str = self.mechanical.run_python_script(visualization_script)
logger.info(f"Visualization script result: {result_str}")
export_time = time.time() - start_time
# Check if file was created successfully
if output_path.exists():
file_size = os.path.getsize(output_path)
result = VisualizationResult(
success=True,
image_path=str(output_path),
image_size=(settings.width, settings.height),
file_size=file_size,
export_time=export_time
)
logger.info(f"✓ Real mesh image export completed: {output_path} ({file_size} bytes)")
return result
else:
# Export failed, but try to provide useful feedback
result = VisualizationResult()
result.success = False
result.export_time = export_time
if "SUCCESS:" in result_str:
result.error_message = "Export script reported success but file not found"
result.warnings.append("File may have been created in different location")
elif "ERROR:" in result_str:
# Extract error message
error_lines = [line for line in result_str.split('\n') if line.startswith("ERROR:")]
if error_lines:
result.error_message = error_lines[0].replace("ERROR:", "").strip()
else:
result.error_message = "Visualization export failed"
else:
result.error_message = "No output from visualization script"
logger.error(f"✗ Mesh image export failed: {result.error_message}")
return result
except Exception as e:
logger.error(f"Real image export failed: {str(e)}")
result = VisualizationResult()
result.success = False
result.error_message = f"Real export failed: {str(e)}"
result.export_time = time.time() - start_time
return result
def export_quality_visualization(self,
filename: str = None,
quality_metric: str = "element_quality") -> VisualizationResult:
"""
Export mesh quality visualization
Args:
filename: Output filename
quality_metric: Quality metric to visualize (element_quality, aspect_ratio, skewness)
Returns:
VisualizationResult with export results
"""
try:
logger.info(f"Exporting mesh quality visualization: {quality_metric}")
# Generate filename if not provided
if filename is None:
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"mesh_quality_{quality_metric}_{timestamp}.png"
# Use specialized settings for quality visualization
settings = VisualizationSettings(
width=1280,
height=720,
background_color="white",
show_edges=True,
show_nodes=False,
camera_view="isometric",
image_format="PNG"
)
# For real mode, we would need to implement quality contour visualization
# This would require additional PyMechanical commands for result visualization
logger.warning("Real quality visualization not yet implemented")
result = VisualizationResult()
result.success = False
result.error_message = "Real quality visualization not yet implemented"
return result
except Exception as e:
logger.error(f"Quality visualization export failed: {str(e)}")
result = VisualizationResult()
result.success = False
result.error_message = str(e)
return result
def get_available_views(self) -> List[str]:
"""
Get list of available camera views
Returns:
List of available view names
"""
return ["isometric", "front", "side", "top", "bottom", "back", "left"]
def get_available_formats(self) -> List[str]:
"""
Get list of available image formats
Returns:
List of supported image formats
"""
return ["PNG", "JPG", "JPEG", "BMP", "TIFF"]
def cleanup_temp_files(self) -> int:
"""
Clean up temporary visualization files
Returns:
Number of files cleaned up
"""
try:
temp_patterns = ["temp_*.png", "temp_*.jpg", "*_temp.*"]
cleaned_count = 0
for pattern in temp_patterns:
for temp_file in self.output_dir.glob(pattern):
try:
temp_file.unlink()
cleaned_count += 1
except Exception as e:
logger.warning(f"Could not delete temp file {temp_file}: {e}")
if cleaned_count > 0:
logger.info(f"✓ Cleaned up {cleaned_count} temporary visualization files")
return cleaned_count
except Exception as e:
logger.error(f"Cleanup failed: {str(e)}")
return 0
def get_export_summary(self) -> Dict[str, Any]:
"""
Get summary of visualization export capabilities
Returns:
Dictionary with export summary
"""
return {
'exporter_type': 'VisualizationExporter',
'output_directory': str(self.output_dir),
'available_views': self.get_available_views(),
'available_formats': self.get_available_formats(),
'supported_features': [
'mesh_visualization',
'quality_visualization',
'custom_camera_views',
'configurable_resolution',
'multiple_formats'
],
'created_at': datetime.now().isoformat()
}