ObjDocument3D is a document delegate class for generating 3D model preview icons. It loads 3D mesh files using trimesh and renders visual previews using matplotlib's 3D projection capabilities, supporting multiple 3D file formats including STL, OBJ, PLY, and OFF.
Module: factory.core/ObjDocument3D.py
Inherits from: ObjDocumentDelegate.ObjDocumentDelegate
Test file: resource.test/pytests/factory.core/test_ObjDocument3D.py
This module enables developers to:
| Format | Extension | Common Use | Support |
|---|---|---|---|
| STL | .stl |
3D printing, CAD | Full |
| OBJ | .obj |
3D modeling, games | Full |
| PLY | .ply |
3D scanning, point clouds | Full |
| OFF | .off |
Research, geometry | Full |
__init__(DB: int = 0) -> NoneInitializes the ObjDocument3D instance.
Parameters:
DB (int, optional): Database connection identifier (default: 0)Initialization:
_IsA to "ObjDocument3D"load_mesh(file_path: str) -> trimesh.TrimeshLoads a 3D mesh file and returns a trimesh.Trimesh object.
Parameters:
file_path (str): Path to the 3D model fileReturns:
trimesh.Trimesh: Trimesh mesh objectRaises:
ValueError: If Scene contains no geometryProcess Flow:
trimesh.load() to load fileExample:
doc3d = ObjDocument3D()
# Load simple mesh
mesh = doc3d.load_mesh("/models/cube.stl")
print(f"Vertices: {len(mesh.vertices)}")
print(f"Faces: {len(mesh.faces)}")
# Load complex scene (auto-concatenated)
mesh = doc3d.load_mesh("/models/assembly.obj")
print(f"Scale: {mesh.scale}")
Scene Handling:
# When trimesh.load returns a Scene with multiple parts:
# Before: Scene with multiple geometries
# After: Single concatenated Trimesh
doc3d = ObjDocument3D()
mesh = doc3d.load_mesh("/models/car.obj") # May contain wheels, body, etc.
# Result: Single unified mesh with all parts
generate_preview(mesh: trimesh.Trimesh, output_path: str, figure_size: int = 10, face_alpha: float = 0.8, edge_color: str = "k", face_color: list | None = None, show_axes: bool = True) -> boolGenerates a 3D preview image from a mesh object.
Parameters:
mesh (trimesh.Trimesh): Mesh object to renderoutput_path (str): Path where preview image will be savedfigure_size (int, optional): Figure size in inches (default: 10)face_alpha (float, optional): Face transparency 0.0-1.0 (default: 0.8)edge_color (str, optional): Edge color (default: "k" for black)face_color (list, optional): RGB face color (default: [0.5, 0.5, 1.0])show_axes (bool, optional): Show axis labels and title (default: True)Returns:
True if preview generation succeededFalse if an exception occurredVisualization Details:
Example:
doc3d = ObjDocument3D()
# Load mesh
mesh = doc3d.load_mesh("/models/bunny.stl")
# Generate preview with defaults
success = doc3d.generate_preview(
mesh,
"/previews/bunny.png"
)
# Custom colors and transparency
success = doc3d.generate_preview(
mesh,
"/previews/bunny_red.png",
figure_size=12,
face_alpha=0.6,
edge_color="gray",
face_color=[1.0, 0.0, 0.0] # Red
)
# Clean preview without axes (for thumbnails)
success = doc3d.generate_preview(
mesh,
"/previews/bunny_clean.png",
show_axes=False
)
Color Specifications:
# Face colors (RGB, each 0.0-1.0)
face_color=[1.0, 0.0, 0.0] # Red
face_color=[0.0, 1.0, 0.0] # Green
face_color=[0.0, 0.0, 1.0] # Blue
face_color=[0.5, 0.5, 0.5] # Gray
face_color=[1.0, 1.0, 0.0] # Yellow
# Edge colors (matplotlib color codes)
edge_color="k" # Black
edge_color="gray" # Gray
edge_color="none" # No edges
edge_color="#FF0000" # Hex color
_do_generate_icon(input_path: str, output_path: str, icon_size: int) -> NoneInternal method that loads a 3D file and generates a square thumbnail icon.
Parameters:
input_path (str): Path to the input 3D model fileoutput_path (str): Path where the icon will be savedicon_size (int): Desired size of the square icon in pixelsProcess Flow:
load_mesh() to load 3D modelgenerate_preview() with:
DocumentTools.resize_to_square() to resize to square thumbnailExample:
doc3d = ObjDocument3D()
# Generate 128x128 thumbnail
doc3d._do_generate_icon(
"/models/gear.stl",
"/thumbnails/gear_thumb.png",
128
)
# Generate 256x256 thumbnail
doc3d._do_generate_icon(
"/models/building.obj",
"/thumbnails/building_thumb.png",
256
)
from ObjDocument3D import ObjDocument3D
# Create instance
doc3d = ObjDocument3D()
# Load and preview STL file
mesh = doc3d.load_mesh("/models/part.stl")
doc3d.generate_preview(mesh, "/previews/part.png")
from ObjDocument3D import ObjDocument3D
doc3d = ObjDocument3D()
# Generate 200x200 thumbnail
doc3d._do_generate_icon(
"/models/sculpture.obj",
"/thumbnails/sculpture.png",
200
)
from ObjDocument3D import ObjDocument3D
import os
doc3d = ObjDocument3D()
models_dir = "/3d_models"
output_dir = "/thumbnails"
model_formats = (".stl", ".obj", ".ply", ".off")
for filename in os.listdir(models_dir):
if filename.lower().endswith(model_formats):
input_path = os.path.join(models_dir, filename)
output_name = os.path.splitext(filename)[0] + "_thumb.png"
output_path = os.path.join(output_dir, output_name)
try:
doc3d._do_generate_icon(input_path, output_path, 128)
print(f"Generated thumbnail for {filename}")
except Exception as e:
print(f"Failed for {filename}: {e}")
from ObjDocument3D import ObjDocument3D
doc3d = ObjDocument3D()
mesh = doc3d.load_mesh("/models/car.obj")
# Wireframe style (transparent faces)
doc3d.generate_preview(
mesh, "/previews/car_wireframe.png",
face_alpha=0.1,
edge_color="blue"
)
# Solid style (no edges)
doc3d.generate_preview(
mesh, "/previews/car_solid.png",
face_alpha=1.0,
edge_color="none",
face_color=[0.8, 0.8, 0.8]
)
# Colorful style
doc3d.generate_preview(
mesh, "/previews/car_colorful.png",
face_color=[1.0, 0.5, 0.0], # Orange
edge_color="yellow"
)
from ObjDocument3D import ObjDocument3D
doc3d = ObjDocument3D()
# Load mesh
mesh = doc3d.load_mesh("/models/object.stl")
# Analyze mesh
print(f"Vertices: {len(mesh.vertices)}")
print(f"Faces: {len(mesh.faces)}")
print(f"Bounds: {mesh.bounds}")
print(f"Volume: {mesh.volume:.2f}")
print(f"Surface area: {mesh.area:.2f}")
print(f"Is watertight: {mesh.is_watertight}")
The module has comprehensive test coverage including:
| Test Case | Description | Status |
|---|---|---|
test_isa_set |
Validates _IsA attribute initialization | ✓ |
test_load_simple_mesh |
Tests loading single mesh | ✓ |
test_load_scene_with_geometries |
Tests Scene concatenation | ✓ |
test_load_scene_empty_raises |
Tests empty Scene validation | ✓ |
test_generates_image_file |
Tests preview generation | ✓ |
test_show_axes_true |
Tests axes display | ✓ |
test_show_axes_false |
Tests axes hiding | ✓ |
test_custom_face_color |
Tests custom color application | ✓ |
test_default_face_color |
Tests default color | ✓ |
test_returns_false_on_error |
Tests error handling | ✓ |
test_delegates_to_load_and_preview |
Tests icon generation pipeline | ✓ |
test_handles_load_error |
Tests error logging | ✓ |
Test file location: resource.test/pytests/factory.core/test_ObjDocument3D.py
Run tests:
pytest resource.test/pytests/factory.core/test_ObjDocument3D.py -v
trimesh - 3D mesh loading and processingmatplotlib - Visualization and renderingmpl_toolkits.mplot3d - 3D projection toolkitnumpy - Numerical operations (trimesh dependency)ObjDocumentDelegate - Base delegate classObjDocumentTools.DocumentTools - Thumbnail resizing utilitiesInstallation:
pip install trimesh matplotlib numpy
Trimesh supports many formats through various backends:
Trimesh provides extensive mesh analysis:
mesh.vertices - Vertex array (Nx3)mesh.faces - Face array (Mx3)mesh.bounds - Bounding boxmesh.scale - Maximum dimensionmesh.volume - Volume (if watertight)mesh.area - Surface areamesh.is_watertight - Manifold checkOptimization Tips:
# Simplify mesh for faster preview
mesh = doc3d.load_mesh("/models/complex.stl")
simplified = mesh.simplify_quadric_decimation(face_count=5000)
doc3d.generate_preview(simplified, "/preview.png")
# Reduce figure size for thumbnails
doc3d.generate_preview(mesh, "/preview.png", figure_size=5)
from ObjDocument3D import ObjDocument3D
import trimesh
class Model3DLibrary:
def __init__(self):
self.handler = ObjDocument3D()
self.supported_formats = {".stl", ".obj", ".ply", ".off"}
def is_supported(self, file_path):
"""Check if file format is supported."""
ext = os.path.splitext(file_path)[1].lower()
return ext in self.supported_formats
def get_mesh_info(self, model_path):
"""Get mesh statistics."""
mesh = self.handler.load_mesh(model_path)
return {
"vertices": len(mesh.vertices),
"faces": len(mesh.faces),
"volume": mesh.volume if mesh.is_watertight else None,
"surface_area": mesh.area,
"bounds": mesh.bounds.tolist(),
"is_watertight": mesh.is_watertight
}
def generate_previews(self, model_path, output_dir):
"""Generate multiple preview styles."""
mesh = self.handler.load_mesh(model_path)
base = os.path.splitext(os.path.basename(model_path))[0]
# Standard preview
self.handler.generate_preview(
mesh,
os.path.join(output_dir, f"{base}_standard.png")
)
# Wireframe
self.handler.generate_preview(
mesh,
os.path.join(output_dir, f"{base}_wireframe.png"),
face_alpha=0.2,
edge_color="blue"
)
# Thumbnail
self.handler._do_generate_icon(
model_path,
os.path.join(output_dir, f"{base}_thumb.png"),
150
)
def export_mesh(self, model_path, output_format):
"""Convert between 3D formats."""
mesh = self.handler.load_mesh(model_path)
output_path = os.path.splitext(model_path)[0] + output_format
mesh.export(output_path)
return output_path
from ObjDocument3D import ObjDocument3D
def generate_print_preview(stl_path, output_dir):
"""Generate preview images for 3D print inspection."""
doc3d = ObjDocument3D()
mesh = doc3d.load_mesh(stl_path)
views = {
"front": (0, 0),
"side": (0, 90),
"top": (90, 0),
}
for view_name, (elev, azim) in views.items():
# Note: This example shows the concept; actual view
# angle setting would require matplotlib ax.view_init()
output = os.path.join(output_dir, f"print_{view_name}.png")
doc3d.generate_preview(mesh, output, show_axes=False)
# Print statistics
print(f"Model: {os.path.basename(stl_path)}")
print(f"Volume: {mesh.volume:.2f} mm³")
print(f"Surface area: {mesh.area:.2f} mm²")
print(f"Watertight: {mesh.is_watertight}")
Error: ModuleNotFoundError: No module named 'trimesh'
Solution:
pip install trimesh
Error: ValueError: Scene contains no geometry
Cause: OBJ/other file loaded as Scene with no geometries
Solution:
Check file contents or try different loader:
import trimesh
mesh = trimesh.load("/model.obj", force="mesh")
Issue: 3D preview is blank or incorrect
Possible Causes:
Solution:
doc3d = ObjDocument3D()
mesh = doc3d.load_mesh("/model.stl")
# Fix face orientation
mesh.fix_normals()
# Check and fix mesh
mesh.fill_holes()
mesh.remove_degenerate_faces()
# Generate preview
doc3d.generate_preview(mesh, "/output.png")
Issue: MemoryError when loading very large STL/OBJ
Solution:
doc3d = ObjDocument3D()
# Load with simplification
mesh = doc3d.load_mesh("/huge_model.stl")
mesh = mesh.simplify_quadric_decimation(face_count=10000)
doc3d.generate_preview(mesh, "/preview.png")
Issue: Slow rendering for complex meshes
Solution:
# Reduce mesh complexity before rendering
mesh = doc3d.load_mesh("/complex.obj")
# Option 1: Decimation
simplified = mesh.simplify_quadric_decimation(face_count=5000)
# Option 2: Convex hull (very fast, but less detailed)
hull = mesh.convex_hull
doc3d.generate_preview(simplified, "/preview.png")
ObjDocumentDelegate.py - Base class for document delegatesObjDocumentTools.py - Shared thumbnail utilitiesObjDocumentVideo.py - Video file thumbnail generationObjDocument.py - Main document handler that delegates to format-specific handlers