Come caricare modelli 3D in Python

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-foss

Versioni 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 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

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 vector

La 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.

 Italiano