- Backend: Express + TypeORM + PostgreSQL, JWT + OTP auth - Frontend: React + Vite + Tailwind, 4 pages (Home/Login/Orders/404) - Docker Compose: PostgreSQL 15 on port 5434 - Tests: 16 tests passing (API + Frontend) - ai_state: rules.md, INSTRUCTIONS.md, session tasks - Examples: brand book PDF, logos, page mockups
107 lines
3.5 KiB
Python
107 lines
3.5 KiB
Python
"""
|
|
3D Model Validation Module
|
|
Validates STL, STEP, and other 3D model formats
|
|
"""
|
|
|
|
import trimesh
|
|
import numpy as np
|
|
from typing import Dict, Any
|
|
import os
|
|
|
|
|
|
class ModelValidator:
|
|
"""Validates 3D models for manufacturing"""
|
|
|
|
def __init__(self):
|
|
self.supported_formats = ['.stl', '.obj', '.ply', '.gltf', '.glb']
|
|
|
|
def validate_model(self, file_path: str) -> Dict[str, Any]:
|
|
"""
|
|
Validate 3D model file
|
|
Returns validation report with mesh integrity, wall thickness, etc.
|
|
"""
|
|
try:
|
|
# Load mesh
|
|
mesh = trimesh.load(file_path, force='mesh')
|
|
|
|
# Validate mesh
|
|
report = {
|
|
'file_path': file_path,
|
|
'file_type': os.path.splitext(file_path)[1].lower(),
|
|
'is_valid': True,
|
|
'file_size': os.path.getsize(file_path),
|
|
'mesh_integrity': 'PASS',
|
|
'warnings': [],
|
|
'errors': [],
|
|
}
|
|
|
|
# Check if mesh is valid
|
|
if not mesh.is_valid:
|
|
report['mesh_integrity'] = 'FAIL'
|
|
report['is_valid'] = False
|
|
report['errors'].append('Mesh is not valid (non-manifold geometry detected)')
|
|
|
|
# Check for empty mesh
|
|
if len(mesh.vertices) == 0:
|
|
report['is_valid'] = False
|
|
report['errors'].append('Mesh is empty')
|
|
return report
|
|
|
|
# Calculate mesh properties
|
|
report['volume_mm3'] = mesh.volume
|
|
report['bounding_box'] = {
|
|
'x': float(mesh.bounds[1][0] - mesh.bounds[0][0]),
|
|
'y': float(mesh.bounds[1][1] - mesh.bounds[0][1]),
|
|
'z': float(mesh.bounds[1][2] - mesh.bounds[0][2]),
|
|
}
|
|
|
|
# Check wall thickness (simple check)
|
|
# For better results, would need voxelization or distance field
|
|
wall_thickness = self._estimate_wall_thickness(mesh)
|
|
report['min_wall_thickness_mm'] = wall_thickness
|
|
|
|
if wall_thickness < 0.5:
|
|
report['warnings'].append(f'Wall thickness is very thin ({wall_thickness}mm)')
|
|
|
|
# Check for non-manifold edges
|
|
if not mesh.is_watertight:
|
|
report['warnings'].append('Mesh is not watertight (may have gaps)')
|
|
|
|
# Generate slicing preview data (simplified)
|
|
report['slicing_preview'] = {
|
|
'layer_count': int(report['bounding_box']['z'] / 0.2),
|
|
'suggested_support': not mesh.is_watertight,
|
|
}
|
|
|
|
return report
|
|
|
|
except Exception as e:
|
|
return {
|
|
'file_path': file_path,
|
|
'is_valid': False,
|
|
'errors': [str(e)],
|
|
'mesh_integrity': 'FAIL',
|
|
}
|
|
|
|
def _estimate_wall_thickness(self, mesh: trimesh.Trimesh) -> float:
|
|
"""Estimate minimum wall thickness"""
|
|
# Simplified estimation
|
|
# In production, use voxel-based thickness analysis
|
|
vertices = mesh.vertices
|
|
if len(vertices) < 3:
|
|
return 0.0
|
|
|
|
# Calculate approximate minimum distance
|
|
distances = []
|
|
for v in vertices[:100]: # Sample vertices
|
|
closest_distance = np.min(np.linalg.norm(vertices - v, axis=1)[1:])
|
|
distances.append(closest_distance)
|
|
|
|
return float(np.min(distances)) if distances else 0.1
|
|
|
|
|
|
if __name__ == '__main__':
|
|
# Test example
|
|
validator = ModelValidator()
|
|
print("Model Validator initialized and ready for use")
|