Cómo cargar modelos 3D en Python
Aspose.3D FOSS para Python proporciona una API sencilla para abrir archivos 3D sin dependencias nativas. Después de cargar un archivo en un objeto Scene, puedes recorrer la jerarquía de nodos y leer los datos de geometría sin procesar de cada malla en la escena.
Guía paso a paso
Paso 1: Instalar el paquete
Instale Aspose.3D FOSS desde PyPI. No se requieren bibliotecas del sistema adicionales.
pip install aspose-3d-fossVersiones de Python compatibles: 3.7, 3.8, 3.9, 3.10, 3.11, 3.12.
Paso 2: Importar la clase Scene
La clase Scene es el contenedor de nivel superior para todos los datos 3D. Impórtala junto con cualquier clase de opción 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
Utilice el método estático Scene.from_file() 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, cree una instancia Scene y llame a open(); útil cuando desea 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 nodos de escena
Una escena cargada es un árbol de objetos Node 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 objetos Entity (mallas, cámaras, luces). Consulte node.entities para ver qué está adjunto.
Paso 5: Acceder a los datos de vértices y polígonos
Convierta la entidad de un nodo a Mesh y lea 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 objetos Vector4; x, y, z llevan 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 fino sobre cómo se interpreta un archivo OBJ, pase una instancia ObjLoadOptions 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 los archivos STL, la clase equivalente es StlLoadOptions. Para glTF, use GltfLoadOptions. Consulte la referencia de API para obtener 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. Utilice pathlib.Path para crear 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, verifique 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 diferentes convenciones (Y‑up vs Z‑up, mano izquierda vs mano derecha). Establezca ObjLoadOptions.flip_coordinate_system = True o aplique una rotación al Transform del nodo raíz 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 hacer el 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 es compatible parcialmente, pero el análisis completo aún no está completo.
¿Cargar un archivo OBJ también carga el material .mtl?
Sí, cuando ObjLoadOptions.enable_materials = True (el predeterminado). La biblioteca busca el archivo .mtl en el mismo directorio que el archivo .obj. 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 método .read() 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, verifique mesh.get_element(VertexElementType.NORMAL). Esto devuelve un VertexElementNormal cuya lista de data 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 segura para hilos al cargar varios archivos simultáneamente?
Cada objeto Scene es independiente. Cargar archivos separados en instancias separadas de Scene desde hilos separados es seguro siempre que no comparta un único Scene entre hilos sin un bloqueo externo.