Scene Graph
En scene graph er den grunnleggende datamodellen i Aspose.3D FOSS for Python. Hver 3D‑fil, enten den er lastet fra disk eller konstruert i minnet, representeres som et tre av Node‑objekter med roten Scene.root_node. Hvert node kan inneholde undernoder og ett eller flere Entity‑objekter (mesher, kameraer, lys). Å forstå scene graph gir deg direkte tilgang til geometrien, materialene og romlige transformasjoner for hvert objekt i en scene.
Installasjon og oppsett
Installer biblioteket fra PyPI:
pip install aspose-3d-fossIngen native‑utvidelser, kompilatorer eller ekstra systempakker er påkrevd. For fullstendige installasjonsinstruksjoner, se Installation Guide.
Oversikt: Scene Graph-konsepter
Scenegrafen i Aspose.3D FOSS følger et enkelt innholdshierarki:
Scene
└── root_node (Node)
├── child_node_A (Node)
│ ├── entity: Mesh
│ └── transform: translation, rotation, scale
├── child_node_B (Node)
│ └── child_node_C (Node)
│ └── entity: Mesh
└── ...| Object | Role |
|---|---|
Scene | Toppnivåbeholder. Inneholder root_node, asset_info, animation_clips og sub_scenes. |
Node | Navngitt tre-node. Har en forelder, null eller flere barn, null eller flere enheter, og en lokal Transform. |
Entity | Geometri eller scenobjekt festet til en node. Vanlige entitetstyper: Mesh, Camera, Light. |
Transform | Lokalrom-posisjon, rotasjon og skalering for en node. Verdien i verdensrom leses fra global_transform. |
Trinn for trinn: Bygge en Scene Graph programmatisk
Trinn 1: Opprett en scene
En ny Scene starter alltid med en tom root_node:
from aspose.threed import Scene
scene = Scene()
print(scene.root_node.name) # "" (empty string — root node has no name by default)
print(len(scene.root_node.child_nodes)) # 0Scene er inngangspunktet for alt: lasting av filer, lagring av filer, opprettelse av animasjonsklipp og tilgang til nodetreet.
Trinn 2: Opprett undernoder
Bruk create_child_node(name) for å legge til navngitte noder i treet:
from aspose.threed import Scene
scene = Scene()
parent = scene.root_node.create_child_node("parent")
child = parent.create_child_node("child")
print(parent.name) # "parent"
print(child.parent_node.name) # "parent"
print(len(scene.root_node.child_nodes)) # 1Alternativt kan du opprette en frittstående Node og legge den ved eksplisitt:
from aspose.threed import Scene, Node
scene = Scene()
node = Node("standalone")
scene.root_node.add_child_node(node)Begge tilnærmingene gir samme resultat. create_child_node er mer konsis for inline‑konstruksjon.
Trinn 3: Opprett en Mesh Entity og fest den
En Mesh lagrer vertex data (control_points) og face topology (polygons). Opprett en, legg til geometri, og fest den til en node:
from aspose.threed import Scene, Node
from aspose.threed.entities import Mesh
from aspose.threed.utilities import Vector3, Vector4
scene = Scene()
parent = scene.root_node.create_child_node("parent")
child = parent.create_child_node("child")
##Create a quad mesh (four vertices, one polygon)
# Note: control_points returns a copy of the internal list; append to
# _control_points directly to actually add vertices. This is a known
# library limitation — a public add_control_point() API is not yet available.
mesh = Mesh("cube")
mesh._control_points.append(Vector4(0, 0, 0, 1))
mesh._control_points.append(Vector4(1, 0, 0, 1))
mesh._control_points.append(Vector4(1, 1, 0, 1))
mesh._control_points.append(Vector4(0, 1, 0, 1))
mesh.create_polygon(0, 1, 2, 3)
child.add_entity(mesh)
print(f"Mesh name: {mesh.name}")
print(f"Vertex count: {len(mesh.control_points)}")
print(f"Polygon count: {mesh.polygon_count}")Vector4(x, y, z, w) representerer en homogen koordinat. Bruk w=1 for vanlige punktposisjoner.
create_polygon(*indices) godtar vertex‑indekser og registrerer en flate i polygonlisten. Oppgi tre indekser for en trekant, fire for en firkant.
Trinn 4: Angi node‑transformasjoner
Hver node har en Transform som styrer posisjonen, orienteringen og størrelsen i lokalt rom:
from aspose.threed.utilities import Vector3, Quaternion
##Translate the node 2 units along the X axis
child.transform.translation = Vector3(2.0, 0.0, 0.0)
##Scale the node to half its natural size
child.transform.scaling = Vector3(0.5, 0.5, 0.5)
##Rotate 45 degrees around the Y axis using Euler angles
child.transform.euler_angles = Vector3(0.0, 45.0, 0.0)Transformasjoner er kumulative: et underordnet nodes world-space‑posisjon er sammensetningen av sin egen transform med alle foreldertransformasjoner. Les det evaluerte world-space‑resultatet fra node.global_transform (uforanderlig, skrivebeskyttet).
Steg 5: Traverser scenegrafen rekursivt
Gå gjennom hele treet ved å rekursivt gå gjennom node.child_nodes:
def traverse(node, depth=0):
indent = " " * depth
entity_type = type(node.entity).__name__ if node.entity else "None"
print(f"{indent}{node.name} [{entity_type}]")
for child in node.child_nodes:
traverse(child, depth + 1)
traverse(scene.root_node)Eksempel på output for scenen bygget ovenfor (rotnodenavnet er en tom streng):
[None]
parent [None]
child [Mesh]For scener med flere enheter per node, iterer node.entities i stedet for node.entity:
def traverse_full(node, depth=0):
indent = " " * depth
entity_names = [type(e).__name__ for e in node.entities] or ["None"]
print(f"{indent}{node.name} [{', '.join(entity_names)}]")
for child in node.child_nodes:
traverse_full(child, depth + 1)
traverse_full(scene.root_node)Trinn 6: Lagre scenen
Oppgi en filsti til scene.save(). Formatet blir utledet fra filendelsen:
scene.save("scene.gltf") # JSON glTF 2.0
scene.save("scene.glb") # Binary GLB container
scene.save("scene.obj") # Wavefront OBJ
scene.save("scene.stl") # STLFor formatspesifikke alternativer, send inn et save-options-objekt som det andre argumentet:
from aspose.threed.formats import GltfSaveOptions
opts = GltfSaveOptions()
scene.save("scene.gltf", opts)Tips og beste praksis
- Navngi hver node. Å gi noder meningsfulle navn gjør feilsøking av traverseringer mye enklere og sikrer at navn beholdes i den eksporterte filen.
- Én mesh per node. Å holde entiteter 1:1 med noder forenkler transformasjoner og kollisjonsforespørsler.
- Bruk
create_child_nodei stedet for manuell tilknytning. Den setter foreldre‑referansen automatisk og er mindre feilutsatt. - Les
global_transformetter at hierarkiet er bygget. Verdensrom‑resultatet er kun stabilt når alle overordnede transformasjoner er satt. - Ikke endre treet under traversering. Å legge til eller fjerne barnenoder mens du itererer
child_nodesvil gi uforutsigbar oppførsel. Samle noder først, deretter endre. - Kontrollpunkter bruker
Vector4, ikkeVector3. Send alltidw=1for vanlige vertex‑posisjoner;w=0representerer en retningsvektor (ikke et punkt). mesh.control_pointsreturnerer en kopi. Egenskapencontrol_pointsreturnererlist(self._control_points)— å legge til i den returnerte listen endrer ikke meshen. Legg alltid til imesh._control_pointsdirekte når du bygger geometri programmatisk. Dette er en kjent biblioteksbegrensning; et offentlig mutasjons‑API finnes ennå ikke.
Vanlige problemer
| Issue | Resolution |
|---|---|
AttributeError: 'NoneType' object has no attribute 'polygons' | Beskytt med if node.entity is not None før du får tilgang til entitetsegenskaper. En node uten entiteter har entity = None. |
Mesh appears at the origin despite setting translation | transform.translation bruker en lokal forskyvning. Hvis overordnet node selv har en transformasjon som ikke er identitet, kan verdensposisjonen avvike. Sjekk global_transform. |
Child nodes missing after scene.save() / reload | Noen formater (OBJ) flater ut hierarkiet. Bruk glTF eller COLLADA for å bevare hele nodetreet. |
polygon_count is 0 after mesh.create_polygon(...) | Bekreft at vertex-indeksene som sendes til create_polygon er innenfor området (0 til len(control_points) - 1). |
Node.get_child(name) returns None | Navnet er store- og småbokstavfølsomt. Bekreft den eksakte navnestrengen som ble brukt ved opprettelse. |
| Traversal visits nodes in unexpected order | child_nodes returnerer barn i innsettingsrekkefølge (den rekkefølgen add_child_node / create_child_node ble kalt). |