Come caricare modelli 3D in Python

Come caricare modelli 3D in Python

Aspose.3D FOSS per Python fornisce un’API semplice per aprire file 3D senza dipendenze native. Dopo aver caricato un file in un Scene oggetto, è 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-foss

Versioni Python supportate: 3.7, 3.8, 3.9, 3.10, 3.11, 3.12.


Passo 2: Importa la classe Scene

Il Scene classe è il contenitore di livello superiore per tutti i dati 3D. Importala insieme a qualsiasi classe di opzioni di caricamento di cui hai bisogno.

from aspose.threed import Scene
from aspose.threed.formats import ObjLoadOptions

Tutte 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

Usa 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 Scene istanza e chiama open(); utile quando vuoi passare opzioni di caricamento o gestire gli errori esplicitamente:

scene = Scene()
scene.open("model.obj")

Entrambi i metodi supportano i file OBJ, STL (binario e ASCII), glTF 2.0 / GLB, COLLADA (DAE) e 3MF.


Passo 4: Attraversa i nodi della scena

Una scena caricata è un albero di Node oggetti radicati in 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ù Entity oggetti (mesh, telecamere, luci). Controlla 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 di 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 Vector4 oggetti; x, y, z contengono la posizione e w è la coordinata omogenea (normalmente 1.0).

mesh.polygons è un elenco di elenchi di interi, dove 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 dettagliato su come viene interpretato un file OBJ, passa un ObjLoadOptions istanza 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. Vedi la riferimento API per un elenco completo.


Problemi comuni e soluzioni

FileNotFoundError durante la chiamata a Scene.from_file()

Il percorso deve essere assoluto o corretto relativo alla directory di lavoro a 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 oppure applicare una rotazione al nodo radice’s Transform 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 il parsing completo non è ancora terminato.

Il caricamento di un file OBJ carica anche il .mtl materiale?

Sì, quando ObjLoadOptions.enable_materials = True (predefinito). La libreria cerca il .mtl file nella stessa directory del .obj file. Se il .mtl è mancante, la geometria viene comunque caricata e viene emesso un avviso.

Posso caricare un file da uno stream di byte invece che da un percorso?

Sì. scene.open() accetta qualsiasi oggetto simile a un file con un .read() metodo oltre a una stringa di percorso file. Passa un flusso binario aperto (ad es., io.BytesIO) direttamente. Scene.from_file() accetta solo una stringa di percorso file.

Come ottengo le normali di superficie?

Dopo il caricamento, verifica mesh.get_element(VertexElementType.NORMAL). Questo restituisce un VertexElementNormal il cui data l’elenco 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 vector

La libreria è thread-safe per il caricamento di più file contemporaneamente?

Ogni Scene l’oggetto è indipendente. Caricare file separati in separati Scene le istanze da thread separati sono sicure finché non condividi un singolo Scene tra i thread senza blocco esterno.

 Italiano