Πώς να διασχίσετε ένα 3D Scene Graph στο Python
Το γράφημα σκηνής στο Aspose.3D FOSS είναι ένα δέντρο από Node αντικείμενα με ρίζα στο scene.root_node. Κάθε αρχείο 3D, είτε φορτώνεται από OBJ, glTF, STL, COLLADA ή 3MF, παράγει την ίδια δομή δέντρου. Η γνώση του πώς να το διασχίσετε σας επιτρέπει να επιθεωρήσετε τη γεωμετρία, να μετρήσετε πολύγωνα, να φιλτράρετε αντικείμενα ανά τύπο και να επεξεργαστείτε συγκεκριμένα τμήματα μιας σύνθετης σκηνής.
Οδηγός βήμα προς βήμα
Βήμα 1: Εγκατάσταση και Εισαγωγή
Εγκαταστήστε το Aspose.3D FOSS από το PyPI:
pip install aspose-3d-fossΕισάγετε τις κλάσεις που χρειάζεστε:
from aspose.threed import Scene
from aspose.threed.entities import MeshΌλες οι δημόσιες κλάσεις βρίσκονται κάτω από aspose.threed ή στα υπο-πακέτα της (aspose.threed.entities, aspose.threed.utilities).
Βήμα 2: Φόρτωση σκηνής από αρχείο
Χρησιμοποιήστε τη στατική Scene.from_file() μέθοδο για να ανοίξετε οποιαδήποτε υποστηριζόμενη μορφή. Η μορφή ανιχνεύεται αυτόματα από την επέκταση του αρχείου:
scene = Scene.from_file("model.gltf")Μπορείτε επίσης να ανοίξετε με ρητές επιλογές φόρτωσης:
from aspose.threed import Scene
from aspose.threed.formats import ObjLoadOptions
options = ObjLoadOptions()
options.enable_materials = True
scene = Scene()
scene.open("model.obj", options)Μετά τη φόρτωση, scene.root_node είναι η ρίζα του δέντρου. Όλοι οι εισαγόμενοι κόμβοι είναι παιδιά ή απόγονοι αυτού του κόμβου.
Βήμα 3: Γράψτε μια αναδρομική συνάρτηση διάσχισης
Η πιο απλή διάσχιση επισκέπτεται κάθε κόμβο με σειρά βάθους-πρώτα:
from aspose.threed import Scene
from aspose.threed.entities import Mesh
def traverse(node, depth=0):
prefix = " " * depth
entity_name = type(node.entity).__name__ if node.entity else "-"
print(f"{prefix}[{entity_name}] {node.name}")
for child in node.child_nodes:
traverse(child, depth + 1)
scene = Scene.from_file("model.gltf")
traverse(scene.root_node)Παράδειγμα εξόδου:
[-]
[-] Armature
[Mesh] Body
[Mesh] Eyes
[-] Ground
[Mesh] Plane<button class=“hextra-code-copy-btn hx-group/copybtn hx-transition-all active:hx-opacity-50 hx-bg-primary-700/5 hx-border hx-border-black/5 hx-text-gray-600 hover:hx-text-gray-900 hx-rounded-md hx-p-1.5 dark:hx-bg-primary-300/10 dark:hx-border-white/10 dark:hx-text-gray-400 dark:hover:hx-text-gray-50” title=“Copy code”
<div class="copy-icon group-[.copied]/copybtn:hx-hidden hx-pointer-events-none hx-h-4 hx-w-4"></div>
<div class="success-icon hx-hidden group-[.copied]/copybtn:hx-block hx-pointer-events-none hx-h-4 hx-w-4"></div>
Σημείωση: Ο ριζικός κόμβος έχει κενό όνομα (""), οπότε η πρώτη γραμμή εμφανίζει [-] χωρίς όνομα μετά.
node.child_nodes επιστρέφει τα παιδιά με τη σειρά εισαγωγής (τη σειρά με την οποία ο εισαγωγέας ή ο χρήστης τα πρόσθεσε).
Βήμα 4: Πρόσβαση στις ιδιότητες οντοτήτων σε κάθε κόμβο
Χρησιμοποιήστε node.entity για να λάβετε την πρώτη οντότητα που είναι συνδεδεμένη σε έναν κόμβο, ή node.entities για να επαναλάβετε όλα αυτά. Ελέγξτε τον τύπο πριν αποκτήσετε πρόσβαση σε ιδιότητες ειδικές για τη μορφή:
from aspose.threed import Scene
from aspose.threed.entities import Mesh
def print_entity_details(node, depth=0):
indent = " " * depth
for entity in node.entities:
if isinstance(entity, Mesh):
mesh = entity
print(f"{indent}Mesh '{node.name}':")
print(f"{indent} vertices : {len(mesh.control_points)}")
print(f"{indent} polygons : {mesh.polygon_count}")
print(f"{indent} cast_shadows : {mesh.cast_shadows}")
print(f"{indent} receive_shadows: {mesh.receive_shadows}")
for child in node.child_nodes:
print_entity_details(child, depth + 1)
scene = Scene.from_file("model.gltf")
print_entity_details(scene.root_node)node.transform.translation, node.transform.rotation, και node.transform.scaling δίνουν τη μετασχηματισμό τοπικού χώρου. node.global_transform δίνει το αξιολογημένο αποτέλεσμα του παγκόσμιου χώρου (με global_transform.scale για κλίμακα σε παγκόσμιο χώρο).
Βήμα 5: Φιλτράρισμα Κόμβων ανά Τύπο Οντότητας
Για να λειτουργήσετε μόνο σε συγκεκριμένους τύπους οντοτήτων, προσθέστε ένα isinstance προστασία μέσα στην διέλευση:
from aspose.threed import Scene
from aspose.threed.entities import Mesh
def find_mesh_nodes(node, results=None):
if results is None:
results = []
for entity in node.entities:
if isinstance(entity, Mesh):
results.append(node)
break # One match per node is enough
for child in node.child_nodes:
find_mesh_nodes(child, results)
return results
scene = Scene.from_file("model.gltf")
mesh_nodes = find_mesh_nodes(scene.root_node)
print(f"Found {len(mesh_nodes)} mesh node(s)")
for n in mesh_nodes:
print(f" {n.name}")Αυτό το μοτίβο είναι χρήσιμο για μαζικές λειτουργίες: εφαρμογή μετασχηματισμού σε κάθε κόμβο πλέγματος, αντικατάσταση υλικών ή εξαγωγή υποδέντρων.
Βήμα 6: Συλλογή Όλων των Πλέγματων και Εκτύπωση Αριθμού Κορυφών
Συγκεντρώστε στατιστικά πλέγματος σε μία μόνο διέλευση:
from aspose.threed import Scene
from aspose.threed.entities import Mesh
def collect_meshes(node, results=None):
if results is None:
results = []
if isinstance(node.entity, Mesh):
results.append((node.name, node.entity))
for child in node.child_nodes:
collect_meshes(child, results)
return results
scene = Scene.from_file("model.gltf")
meshes = collect_meshes(scene.root_node)
print(f"Total meshes: {len(meshes)}")
total_verts = 0
total_polys = 0
for name, mesh in meshes:
verts = len(mesh.control_points)
polys = mesh.polygon_count
total_verts += verts
total_polys += polys
print(f" {name}: {verts} vertices, {polys} polygons")
print(f"Scene totals: {total_verts} vertices, {total_polys} polygons")Συνηθισμένα Προβλήματα
| Πρόβλημα | Επίλυση |
|---|---|
AttributeError: 'NoneType' object has no attribute 'polygons' | Προστασία με if node.entity is not None ή isinstance(node.entity, Mesh) πριν την πρόσβαση στις ιδιότητες του πλέγματος. Οι κόμβοι χωρίς οντότητες επιστρέφουν None. |
| Η διέλευση σταματά νωρίς | Βεβαιωθείτε ότι η αναδρομή φτάνει στο node.child_nodes. Εάν επαναλαμβάνετε μόνο scene.root_node.child_nodes (μη αναδρομικά), χάνετε όλους τους απογόνους. |
| Το πλέγμα λείπει από τα συλλεγμένα αποτελέσματα | Ελέγξτε ότι η μορφή αρχείου διατηρεί την ιεραρχία. Το OBJ ισοπεδώνει όλη τη γεωμετρία σε ένα μόνο επίπεδο κόμβων. Χρησιμοποιήστε glTF ή COLLADA για βαθιές ιεραρχίες. |
node.entity επιστρέφει μόνο την πρώτη οντότητα | Χρησιμοποιήστε node.entities (η πλήρης λίστα) όταν ένας κόμβος μεταφέρει πολλαπλές οντότητες. node.entity είναι συντομογραφία για node.entities[0] όταν entities δεν είναι κενό. |
collect_meshes επιστρέφει 0 αποτελέσματα για ένα φορτωμένο STL | Τα αρχεία STL συνήθως παράγουν έναν ενιαίο επίπεδο κόμβο με μία οντότητα mesh απευθείας κάτω root_node. Ελέγξτε root_node.child_nodes[0].entity απευθείας. |
| Τα ονόματα των κόμβων είναι κενές συμβολοσειρές | Ορισμένες μορφές (binary STL, κάποια αρχεία OBJ) δεν αποθηκεύουν ονόματα αντικειμένων. Οι κόμβοι θα έχουν κενές name συμβολοσειρές· χρησιμοποιήστε το ευρετήριο για την ταυτοποίηση αντ’ αυτού. |
Συχνές Ερωτήσεις
Πόσο βαθύ μπορεί να είναι ένα γράφημα σκηνής;?
Δεν υπάρχει σκληρό όριο. Το προεπιλεγμένο όριο αναδρομής του Python (1000 καρέ) ισχύει για τις συναρτήσεις αναδρομικής διαδρομής. Για πολύ βαθιές ιεραρχίες, μετατρέψτε την αναδρομή σε ρητό στοίβα:
from collections import deque
from aspose.threed import Scene
scene = Scene.from_file("deep.gltf")
queue = deque([(scene.root_node, 0)])
while queue:
node, depth = queue.popleft()
print(" " * depth + node.name)
for child in node.child_nodes:
queue.append((child, depth + 1))Μπορώ να τροποποιήσω το δέντρο ενώ το διασχίζω;?
Μην προσθέτετε ή αφαιρείτε κόμβους από child_nodes κατά τη διάρκεια της επανάληψης. Συλλέξτε τους κόμβους προς τροποποίηση σε ένα πρώτο πέρασμα, έπειτα εφαρμόστε τις αλλαγές σε ένα δεύτερο πέρασμα.
Πώς μπορώ να βρω έναν συγκεκριμένο κόμβο με βάση το όνομα;?
Χρησιμοποιήστε node.get_child(name) για να βρείτε ένα άμεσο παιδί με όνομα. Για μια βαθιά αναζήτηση, διασχίστε το δέντρο με ένα φίλτρο ονόματος:
def find_by_name(root, name):
if root.name == name:
return root
for child in root.child_nodes:
result = find_by_name(child, name)
if result:
return result
return None
target = find_by_name(scene.root_node, "Wheel_FL")Επιστρέφει node.entity πάντα ένα Mesh;?
Όχι. Ένας κόμβος μπορεί να περιέχει οποιονδήποτε τύπο οντότητας: Mesh, Camera, Light, ή προσαρμοσμένες οντότητες. Πάντα ελέγξτε με isinstance(node.entity, Mesh) πριν χρησιμοποιήσετε ιδιότητες ειδικές για mesh.
Πώς μπορώ να λάβω τη θέση σε παγκόσμιο χώρο ενός κόμβου;?
Ανάγνωση node.global_transform.translation. Αυτή είναι η υπολογισμένη θέση στον παγκόσμιο χώρο, λαμβάνοντας υπόψη όλες τις μετασχηματισμούς των προγόνων. Είναι μόνο για ανάγνωση· τροποποιήστε node.transform.translation για να μετατοπίσετε τον κόμβο.
Μπορώ να μετρήσω το συνολικό πλήθος πολυγώνων σε μια σκηνή χωρίς να γράψω ένα traversal;?
Δεν γίνεται άμεσα μέσω του API· δεν υπάρχει scene.total_polygon_count ιδιότητα. Χρησιμοποιήστε collect_meshes και άθροισε mesh.polygon_count στα αποτελέσματα, όπως φαίνεται στο Βήμα 6.