Come caricare modelli 3D in Python
Aspose.3D FOSS for Python fornisce un’API semplice per aprire file 3D senza dipendenze native. Dopo aver caricato un file in un oggetto Scene, è possibile attraversare la gerarchia dei nodi e leggere i dati di geometria grezzi per ogni mesh nella scena.
Guida passo-passo
Passo 1: Installa il pacchetto
Installa Aspose.3D FOSS da PyPI. Non sono richieste librerie di sistema aggiuntive.
pip install aspose-3d-fossVersioni Python supportate: 3.7, 3.8, 3.9, 3.10, 3.11, 3.12.
Passo 2: Importa la classe Scene
La classe Scene è il contenitore di livello superiore per tutti i dati 3D. Importala insieme a tutte le classi di opzioni di caricamento di cui hai bisogno.
from aspose.threed import Scene
from aspose.threed.formats import ObjLoadOptionsTutte le classi pubbliche si trovano sotto aspose.threed o nei suoi sotto‑pacchetti (aspose.threed.entities, aspose.threed.formats, aspose.threed.utilities).
Passo 3: Carica un file
Utilizza il metodo statico Scene.from_file() per aprire qualsiasi formato supportato. La libreria rileva automaticamente il formato dall’estensione del file.
##Automatic format detection
scene = Scene.from_file("model.obj")In alternativa, crea un’istanza Scene e chiama open(); utile quando desideri passare le opzioni di caricamento o gestire gli errori esplicitamente:
scene = Scene()
scene.open("model.obj")Entrambi i metodi supportano i file OBJ, STL (binari e ASCII), glTF 2.0 / GLB, COLLADA (DAE) e 3MF.
Passo 4: Attraversa i nodi della scena
Una scena caricata è un albero di oggetti Node con radice scene.root_node. Itera ricorsivamente per trovare tutti i nodi:
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)Ogni Node può contenere zero o più oggetti Entity (mesh, telecamere, luci). Verifica node.entities per vedere cosa è collegato.
Passo 5: Accedi ai dati dei vertici e dei poligoni
Esegui il cast dell’entità di un nodo a Mesh e leggi i suoi punti di controllo (posizioni dei vertici) e i poligoni (elenchi degli indici delle facce):
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 è un elenco di oggetti Vector4; x, y, z rappresentano la posizione e w è la coordinata omogenea (normalmente 1.0).
mesh.polygons è un elenco di elenchi di interi, in cui ogni elenco interno è l’insieme ordinato di indici dei punti di controllo per una faccia.
Passo 6: Applica le opzioni di caricamento specifiche per il formato
Per un controllo granulare su come viene interpretato un file OBJ, passa un’istanza 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)Per i file STL, la classe equivalente è StlLoadOptions. Per glTF, usa GltfLoadOptions. Consulta il riferimento API per un elenco completo.
Problemi comuni e soluzioni
FileNotFoundError quando si chiama Scene.from_file()
Il percorso deve essere assoluto o corretto relativo alla directory di lavoro al runtime. Usa pathlib.Path per costruire percorsi affidabili:
from pathlib import Path
from aspose.threed import Scene
path = Path(__file__).parent / "assets" / "model.obj"
scene = Scene.from_file(str(path))mesh.polygons è vuoto dopo il caricamento di un file STL
I file STL memorizzano i triangoli come faccette grezze, non come una mesh indicizzata. Dopo il caricamento, i poligoni vengono sintetizzati da quelle faccette. Se polygons appare vuoto, controlla len(mesh.control_points); se il conteggio è un multiplo di 3 la geometria è memorizzata in forma non indicizzata e ogni tripla consecutiva di vertici forma un triangolo.
Discrepanza del sistema di coordinate (il modello appare ruotato o specchiato)
Strumenti diversi usano convenzioni diverse (Y‑up vs Z‑up, mano sinistra vs mano destra). Imposta ObjLoadOptions.flip_coordinate_system = True o applica una rotazione al Transform del nodo radice dopo il caricamento.
AttributeError: 'NoneType' object has no attribute 'polygons'
L’elenco delle entità di un nodo può contenere entità non mesh (telecamere, luci). Proteggi sempre con isinstance(entity, Mesh) prima del cast.
Domande Frequenti (FAQ)
Quali formati 3D posso caricare?
OBJ (Wavefront), STL (binario e ASCII), glTF 2.0 / GLB, COLLADA (DAE) e 3MF. La tokenizzazione dei file FBX è parzialmente supportata, ma l’analisi completa non è ancora terminata.
Caricare un file OBJ carica anche il materiale .mtl?
Sì, quando ObjLoadOptions.enable_materials = True (il valore predefinito). La libreria cerca il file .mtl nella stessa directory del file .obj. Se il .mtl è mancante, la geometria viene comunque caricata e viene emesso un avviso.
Posso caricare un file da un flusso di byte invece di un percorso?
Sì. scene.open() accetta qualsiasi oggetto simile a un file con un metodo .read() oltre a una stringa di percorso file. Passa direttamente un flusso binario aperto (ad es., io.BytesIO). Scene.from_file() accetta solo una stringa di percorso file.
Come ottengo le normali di superficie?
Dopo il caricamento, controlla mesh.get_element(VertexElementType.NORMAL). Questo restituisce un VertexElementNormal la cui lista data contiene un vettore normale per riferimento, mappato secondo mapping_mode e reference_mode.
from aspose.threed.entities import Mesh, VertexElementType
normals = mesh.get_element(VertexElementType.NORMAL)
if normals:
print(normals.data[0]) # First normal vectorLa libreria è thread‑safe per il caricamento di più file contemporaneamente?
Ogni oggetto Scene è indipendente. Caricare file separati in istanze separate di Scene da thread separati è sicuro purché non si condivida un singolo Scene tra i thread senza un blocco esterno.