Scengraf
En scene graph är den grundläggande datamodellen för Aspose.3D FOSS för Python. Varje 3D‑fil, oavsett om den laddas från disk eller konstrueras i minnet, representeras som ett träd av Node‑objekt med rot i Scene.root_node. Varje nod kan innehålla undernoder och ett eller flera Entity‑objekt (meshes, kameror, ljus). Att förstå scene graph ger dig direkt åtkomst till geometri, material och rumsliga transformationer för varje objekt i en scen.
Installation och konfiguration
Installera biblioteket från PyPI:
pip install aspose-3d-fossInga inbyggda tillägg, kompilatorer eller ytterligare systempaket krävs. För fullständiga installationsinstruktioner, se Installationsguide.
Översikt: Scene Graph Concepts
Scengrafen i Aspose.3D FOSS följer en enkel innehållshierarki:
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 | Behållare på toppnivå. Innehåller root_node, asset_info, animation_clips och sub_scenes. |
Node | Namngiven trädnod. Har en förälder, noll eller fler barn, noll eller fler enheter och en lokal Transform. |
Entity | Geometri eller scenobjekt kopplat till en nod. Vanliga entitetstyper: Mesh, Camera, Light. |
Transform | Lokalrymdens position, rotation och skala för en nod. Resultatet i världsrymden läses från global_transform. |
Steg-för-steg: Bygga en scengraf programatiskt
Steg 1: Skapa en scen
En ny Scene börjar 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 är ingångspunkten för allt: läsa in filer, spara filer, skapa animationsklipp och komma åt nodträdet.
Steg 2: Skapa undernoder
Använd create_child_node(name) för att lägga till namngivna noder i trädet:
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 skapa en fristående Node och bifoga den explicit:
from aspose.threed import Scene, Node
scene = Scene()
node = Node("standalone")
scene.root_node.add_child_node(node)Båda tillvägagångssätten ger samma resultat. create_child_node är mer koncis för inline‑konstruktion.
Steg 3: Skapa en Mesh Entity och fäst den
En Mesh lagrar vertexdata (control_points) och ansikts‑topologi (polygons). Skapa en, lägg till geometri och fäst den sedan på en nod:
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) representerar en homogen koordinat. Använd w=1 för vanliga punktpositioner.
create_polygon(*indices) accepterar vertex indices och registrerar ett face i polygon listan. Skicka tre index för en triangel, fyra för en quad.
Steg 4: Ställ in nodtransformer
Varje nod har en Transform som styr dess position, orientering och storlek i lokalt utrymme:
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)Transformationer är kumulativa: ett barns nods världsrymdsposition är sammansättningen av dess egen transformation med alla förfäders transformationer. Läs det utvärderade världsrymdsresultatet från node.global_transform (oföränderligt, skrivskyddat).
Steg 5: Traversera scengrafen rekursivt
Traversera hela trädet genom att rekursivt gå igenom 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)Exempelutdata för scenen som byggdes ovan (rotnodens namn är en tom sträng):
[None]
parent [None]
child [Mesh]För scener med flera enheter per nod, iterera node.entities istället för 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)Steg 6: Spara scenen
Skicka en filsökväg till scene.save(). Formatet härleds från filändelsen:
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") # STLFör format‑specifika alternativ, skicka ett save‑options‑objekt som det andra argumentet:
from aspose.threed.formats import GltfSaveOptions
opts = GltfSaveOptions()
scene.save("scene.gltf", opts)Tips och bästa praxis
- Namnge varje nod. Att ge noder meningsfulla namn gör felsökning av traverseringar mycket enklare och säkerställer att namn bevaras i den exporterade filen.
- En mesh per nod. Att hålla entiteter 1:1 med noder förenklar transformationer och kollisionförfrågningar.
- Använd
create_child_nodeistället för manuell fästning. Den sätter föräldrareferensen automatiskt och är mindre felbenägen. - Läs
global_transformefter att hierarkin har byggts. Resultatet i världsrummet är bara stabilt när alla föräldratransformeringar är satta. - Mutera inte trädet under traversering. Att lägga till eller ta bort barnnoder medan du itererar
child_nodeskommer att ge oförutsägbart beteende. Samla noder först, modifiera sedan. - Kontrollpunkter använder
Vector4, inteVector3. Skicka alltidw=1för vanliga vertexpositioner;w=0representerar en riktningsvektor (inte en punkt). mesh.control_pointsreturnerar en kopia. Egenskapencontrol_pointsreturnerarlist(self._control_points)— att lägga till i den returnerade listan ändrar inte meshen. Lägg alltid till imesh._control_pointsdirekt när du bygger geometri programatiskt. Detta är en känd biblioteksbegränsning; ett offentligt mutations‑API finns ännu inte.
Vanliga problem
| Issue | Resolution |
|---|---|
AttributeError: 'NoneType' object has no attribute 'polygons' | Skydda med if node.entity is not None innan du får åtkomst till enhetsegenskaper. En nod utan enheter har entity = None. |
Mesh appears at the origin despite setting translation | transform.translation tillämpar en lokal förskjutning. Om föräldranoden själv har en icke‑identitets‑transform kan världens position skilja sig. Kontrollera global_transform. |
Child nodes missing after scene.save() / reload | Vissa format (OBJ) plattar till hierarkin. Använd glTF eller COLLADA för att bevara hela nodträdet. |
polygon_count is 0 after mesh.create_polygon(...) | Verifiera att vertexindexen som skickas till create_polygon ligger inom intervallet (0 till len(control_points) - 1). |
Node.get_child(name) returns None | Namnet är skiftlägeskänsligt. Bekräfta den exakta namnsträngen som användes vid skapandet. |
| Traversal visits nodes in unexpected order | child_nodes returnerar barn i insättningsordning (den ordning add_child_node / create_child_node anropades). |