Cómo cargar modelos 3D en Python
Aspose.3D FOSS para Python ofrece una API sencilla para abrir archivos 3D sin dependencias nativas. Después de cargar un archivo en un Scene objeto, puedes recorrer la jerarquía de nodos y leer los datos de geometría cruda de cada malla en la escena.
Guía paso a paso
Paso 1: Instalar el paquete
Instala Aspose.3D FOSS desde PyPI. No se requieren bibliotecas del sistema adicionales.
pip install aspose-3d-fossVersiones compatibles de Python: 3.7, 3.8, 3.9, 3.10, 3.11, 3.12.
Paso 2: Importar la clase Scene
El Scene class es el contenedor de nivel superior para todos los datos 3D. Impórtala junto con cualquier clase de opciones de carga que necesites.
from aspose.threed import Scene
from aspose.threed.formats import ObjLoadOptionsTodas las clases públicas se encuentran bajo aspose.threed o sus subpaquetes (aspose.threed.entities, aspose.threed.formats, aspose.threed.utilities).
Paso 3: Cargar un archivo
Utiliza el estático Scene.from_file() método para abrir cualquier formato compatible. La biblioteca detecta el formato automáticamente a partir de la extensión del archivo.
##Automatic format detection
scene = Scene.from_file("model.obj")Alternativamente, crea un Scene instancia y llama a open(); útil cuando deseas pasar opciones de carga o manejar errores explícitamente:
scene = Scene()
scene.open("model.obj")Ambos métodos admiten archivos OBJ, STL (binario y ASCII), glTF 2.0 / GLB, COLLADA (DAE) y 3MF.
Paso 4: Recorrer los nodos de la escena
Una escena cargada es un árbol de Node objetos con raíz en scene.root_node. Itera recursivamente para encontrar todos los nodos:
from aspose.threed import Scene, Node
scene = Scene.from_file("model.obj")
def walk(node: Node, depth: int = 0) -> None:
indent = " " * depth
print(f"{indent}Node: {node.name!r}")
for child in node.child_nodes:
walk(child, depth + 1)
walk(scene.root_node)Cada Node puede contener cero o más Entity objetos (mallas, cámaras, luces). Verifica node.entities para ver qué está adjunto.
Paso 5: Acceder a los datos de vértices y polígonos
Convierte la entidad de un nodo a Mesh y lee sus puntos de control (posiciones de vértices) y polígonos (listas de índices de caras):
from aspose.threed import Scene
from aspose.threed.entities import Mesh
scene = Scene.from_file("model.obj")
for node in scene.root_node.child_nodes:
for entity in node.entities:
if isinstance(entity, Mesh):
mesh: Mesh = entity
print(f"Mesh '{node.name}': "
f"{len(mesh.control_points)} vertices, "
f"{len(mesh.polygons)} polygons")
# First vertex position
if mesh.control_points:
v = mesh.control_points[0]
print(f" First vertex: ({v.x:.4f}, {v.y:.4f}, {v.z:.4f})")
# First polygon face (list of control-point indices)
if mesh.polygons:
print(f" First polygon: {mesh.polygons[0]}")mesh.control_points es una lista de Vector4 objetos; x, y, z llevar la posición y w es la coordenada homogénea (normalmente 1.0).
mesh.polygons es una lista de listas de enteros, donde cada lista interna es el conjunto ordenado de índices de puntos de control para una cara.
Paso 6: Aplicar opciones de carga específicas del formato
Para un control detallado sobre cómo se interpreta un archivo OBJ, pasa un ObjLoadOptions instancia a scene.open():
from aspose.threed import Scene
from aspose.threed.formats import ObjLoadOptions
options = ObjLoadOptions()
options.flip_coordinate_system = True # Convert right-hand Y-up to Z-up
options.scale = 0.01 # Convert centimetres to metres
options.enable_materials = True # Load .mtl material file
options.normalize_normal = True # Normalize all normals to unit length
scene = Scene()
scene.open("model.obj", options)Para archivos STL, la clase equivalente es StlLoadOptions. Para glTF, usa GltfLoadOptions. Ver el referencia de la API para una lista completa.
Problemas comunes y soluciones
FileNotFoundError al llamar Scene.from_file()
La ruta debe ser absoluta o correcta relativa al directorio de trabajo en tiempo de ejecución. Use pathlib.Path para construir rutas fiables:
from pathlib import Path
from aspose.threed import Scene
path = Path(__file__).parent / "assets" / "model.obj"
scene = Scene.from_file(str(path))mesh.polygons está vacío después de cargar un archivo STL
Los archivos STL almacenan triángulos como facetas crudas, no como una malla indexada. Después de cargar, los polígonos se sintetizan a partir de esas facetas. Si polygons aparece vacío, comprueba len(mesh.control_points); si el recuento es múltiplo de 3, la geometría se almacena en forma no indexada y cada triple consecutivo de vértices forma un triángulo.
Desajuste del sistema de coordenadas (el modelo parece rotado o reflejado)
Diferentes herramientas usan convenciones distintas (Y-arriba vs Z-arriba, mano izquierda vs mano derecha). Establece ObjLoadOptions.flip_coordinate_system = True o aplica una rotación al nodo raíz’s Transform después de cargar.
AttributeError: 'NoneType' object has no attribute 'polygons'
La lista de entidades de un nodo puede contener entidades que no son mallas (cámaras, luces). Siempre protege con isinstance(entity, Mesh) antes de casting.
Preguntas frecuentes (FAQ)
¿Qué formatos 3D puedo cargar?
OBJ (Wavefront), STL (binario y ASCII), glTF 2.0 / GLB, COLLADA (DAE) y 3MF. La tokenización de archivos FBX está parcialmente soportada, pero el análisis completo aún no está terminado.
¿Cargar un archivo OBJ también carga el .mtl ¿material?
Sí, cuando ObjLoadOptions.enable_materials = True (el predeterminado). La biblioteca busca el .mtl archivo en el mismo directorio que el .obj archivo. Si el .mtl falta, la geometría aún se carga y se emite una advertencia.
¿Puedo cargar un archivo desde un flujo de bytes en lugar de una ruta?
Sí. scene.open() acepta cualquier objeto similar a un archivo con un .read() método además de una cadena de ruta de archivo. Pase un flujo binario abierto (p. ej., io.BytesIO) directamente. Scene.from_file() solo acepta una cadena de ruta de archivo.
¿Cómo obtengo las normales de superficie?
Después de cargar, verifica mesh.get_element(VertexElementType.NORMAL). Esto devuelve un VertexElementNormal cuyo data la lista contiene un vector normal por referencia, mapeado según mapping_mode y reference_mode.
from aspose.threed.entities import Mesh, VertexElementType
normals = mesh.get_element(VertexElementType.NORMAL)
if normals:
print(normals.data[0]) # First normal vector¿Es la biblioteca thread-safe para cargar varios archivos simultáneamente?
Cada Scene el objeto es independiente. Cargar archivos separados en separate Scene instancias desde hilos separados es seguro siempre que no compartas una única Scene a través de hilos sin bloqueo externo.