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

Inga 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
    └── ...
ObjectRole
SceneBehållare på toppnivå. Innehåller root_node, asset_info, animation_clips och sub_scenes.
NodeNamngiven trädnod. Har en förälder, noll eller fler barn, noll eller fler enheter och en lokal Transform.
EntityGeometri eller scenobjekt kopplat till en nod. Vanliga entitetstyper: Mesh, Camera, Light.
TransformLokalrymdens 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))  # 0

Scene ä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))     # 1

Alternativt 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")    # STL

Fö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_node istället för manuell fästning. Den sätter föräldrareferensen automatiskt och är mindre felbenägen.
  • Läs global_transform efter 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_nodes kommer att ge oförutsägbart beteende. Samla noder först, modifiera sedan.
  • Kontrollpunkter använder Vector4, inte Vector3. Skicka alltid w=1 för vanliga vertexpositioner; w=0 representerar en riktningsvektor (inte en punkt).
  • mesh.control_points returnerar en kopia. Egenskapen control_points returnerar list(self._control_points) — att lägga till i den returnerade listan ändrar inte meshen. Lägg alltid till i mesh._control_points direkt när du bygger geometri programatiskt. Detta är en känd biblioteksbegränsning; ett offentligt mutations‑API finns ännu inte.

Vanliga problem

IssueResolution
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 translationtransform.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() / reloadVissa 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 NoneNamnet är skiftlägeskänsligt. Bekräfta den exakta namnsträngen som användes vid skapandet.
Traversal visits nodes in unexpected orderchild_nodes returnerar barn i insättningsordning (den ordning add_child_node / create_child_node anropades).
 Svenska