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

Ingen 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
    └── ...
ObjectRole
SceneToppnivåbeholder. Inneholder root_node, asset_info, animation_clips og sub_scenes.
NodeNavngitt tre-node. Har en forelder, null eller flere barn, null eller flere enheter, og en lokal Transform.
EntityGeometri eller scenobjekt festet til en node. Vanlige entitetstyper: Mesh, Camera, Light.
TransformLokalrom-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))  # 0

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

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

For 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_node i stedet for manuell tilknytning. Den setter foreldre‑referansen automatisk og er mindre feilutsatt.
  • Les global_transform etter 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_nodes vil gi uforutsigbar oppførsel. Samle noder først, deretter endre.
  • Kontrollpunkter bruker Vector4, ikke Vector3. Send alltid w=1 for vanlige vertex‑posisjoner; w=0 representerer en retningsvektor (ikke et punkt).
  • mesh.control_points returnerer en kopi. Egenskapen control_points returnerer list(self._control_points) — å legge til i den returnerte listen endrer ikke meshen. Legg alltid til i mesh._control_points direkte når du bygger geometri programmatisk. Dette er en kjent biblioteksbegrensning; et offentlig mutasjons‑API finnes ennå ikke.

Vanlige problemer

IssueResolution
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 translationtransform.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() / reloadNoen 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 NoneNavnet er store- og småbokstavfølsomt. Bekreft den eksakte navnestrengen som ble brukt ved opprettelse.
Traversal visits nodes in unexpected orderchild_nodes returnerer barn i innsettingsrekkefølge (den rekkefølgen add_child_node / create_child_node ble kalt).
 Norsk