stl_validator.py•4.73 kB
import os
import logging
import subprocess
import tempfile
from typing import Tuple, Optional
logger = logging.getLogger(__name__)
class STLValidator:
"""
Validates STL files to ensure they are manifold (watertight) and suitable for 3D printing.
"""
@staticmethod
def validate_stl(stl_file: str) -> Tuple[bool, Optional[str]]:
"""
Validate an STL file to ensure it is manifold and suitable for 3D printing.
Args:
stl_file: Path to the STL file to validate
Returns:
Tuple of (is_valid, error_message)
"""
if not os.path.exists(stl_file):
return False, f"STL file not found: {stl_file}"
# Check file size
file_size = os.path.getsize(stl_file)
if file_size == 0:
return False, "STL file is empty"
# Basic validation - check if the file starts with "solid" for ASCII STL
# or contains binary header for binary STL
try:
with open(stl_file, 'rb') as f:
header = f.read(5)
if header == b'solid':
# ASCII STL
is_valid, error = STLValidator._validate_ascii_stl(stl_file)
else:
# Binary STL
is_valid, error = STLValidator._validate_binary_stl(stl_file)
return is_valid, error
except Exception as e:
logger.error(f"Error validating STL file: {str(e)}")
return False, f"Error validating STL file: {str(e)}"
@staticmethod
def _validate_ascii_stl(stl_file: str) -> Tuple[bool, Optional[str]]:
"""Validate an ASCII STL file."""
try:
with open(stl_file, 'r') as f:
content = f.read()
# Check if the file has the correct structure
if not content.strip().startswith('solid'):
return False, "Invalid ASCII STL: Missing 'solid' header"
if not content.strip().endswith('endsolid'):
return False, "Invalid ASCII STL: Missing 'endsolid' footer"
# Count facets and vertices
facet_count = content.count('facet normal')
vertex_count = content.count('vertex')
if facet_count == 0:
return False, "Invalid ASCII STL: No facets found"
if vertex_count != facet_count * 3:
return False, f"Invalid ASCII STL: Expected {facet_count * 3} vertices, found {vertex_count}"
return True, None
except Exception as e:
logger.error(f"Error validating ASCII STL: {str(e)}")
return False, f"Error validating ASCII STL: {str(e)}"
@staticmethod
def _validate_binary_stl(stl_file: str) -> Tuple[bool, Optional[str]]:
"""Validate a binary STL file."""
try:
with open(stl_file, 'rb') as f:
# Skip 80-byte header
f.seek(80)
# Read number of triangles (4-byte unsigned int)
triangle_count_bytes = f.read(4)
if len(triangle_count_bytes) != 4:
return False, "Invalid binary STL: File too short"
# Convert bytes to integer (little-endian)
triangle_count = int.from_bytes(triangle_count_bytes, byteorder='little')
# Check file size
expected_size = 84 + (triangle_count * 50) # Header + count + triangles
actual_size = os.path.getsize(stl_file)
if actual_size != expected_size:
return False, f"Invalid binary STL: Expected size {expected_size}, actual size {actual_size}"
return True, None
except Exception as e:
logger.error(f"Error validating binary STL: {str(e)}")
return False, f"Error validating binary STL: {str(e)}"
@staticmethod
def repair_stl(stl_file: str) -> Tuple[bool, Optional[str]]:
"""
Attempt to repair a non-manifold STL file.
Args:
stl_file: Path to the STL file to repair
Returns:
Tuple of (success, error_message)
"""
# This is a placeholder for STL repair functionality
# In a real implementation, you would use a library like admesh or meshlab
# to repair the STL file
logger.warning(f"STL repair not implemented: {stl_file}")
return False, "STL repair not implemented"