Scenegraf

En scenegraf er den grundlæggende datamodel for Aspose.3D FOSS til Python. Hver 3D-fil, uanset om den er indlæst fra disk eller konstrueret i hukommelsen, repræsenteres som et træ af Node objekter med rod i Scene.root_node. Hvert knudepunkt kan indeholde underknuder og en eller flere Entity objekter (meshes, kameraer, lys). At forstå scenegrafen giver dig direkte adgang til geometrien, materialerne og de rumlige transformationer for hvert objekt i en scene.

Installation og opsætning

Installer biblioteket fra PyPI:

pip install aspose-3d-foss

Ingen native udvidelser, compilere eller ekstra systempakker er påkrævet. For fulde installationsinstruktioner, se den Installationsvejledning.


Oversigt: Koncepter for scenegraf

Scenegrafen i Aspose.3D FOSS følger et enkelt indeholdshierarki:

Scene
└── root_node  (Node)
    ├── child_node_A  (Node)
    │   ├── entity: Mesh
    │   └── transform: translation, rotation, scale
    ├── child_node_B  (Node)
    │   └── child_node_C  (Node)
    │       └── entity: Mesh
    └── ...
ObjektRolle
SceneTopniveau-beholder. Indeholder root_node, asset_info, animation_clips, og sub_scenes.
NodeNavngivet træknude. Har en forælder, nul eller flere børn, nul eller flere enheder, og en lokal Transform.
EntityGeometri eller sceneobjekt knyttet til en knude. Almindelige enhedstyper: Mesh, Camera, Light.
TransformLokalrum position, rotation og skalering for en knude. Verdensrum-resultatet læses fra global_transform.

Trin for trin: Bygning af en scenegraf programmatisk

Trin 1: Opret en scene

En ny Scene begynder altid 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 indgangspunktet for alt: indlæsning af filer, gemning af filer, oprettelse af animationsklip og adgang til nodetræet.


Trin 2: Opret under‑noder

Brug create_child_node(name) til at tilføje navngivne noder til træet:

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 oprette en selvstændig Node og vedhæft den eksplicit:

from aspose.threed import Scene, Node

scene = Scene()
node = Node("standalone")
scene.root_node.add_child_node(node)

Begge tilgange giver det samme resultat. create_child_node er mere kortfattet til inline‑konstruktion.


Trin 3: Opret en Mesh‑entity og vedhæft den

En Mesh gemmer vertexdata (control_points) og ansigtstopologi (polygons). Opret en, tilføj geometri, og vedhæft den derefter 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) repræsenterer en homogen koordinat. Brug w=1 til almindelige punktpositioner.

create_polygon(*indices) accepterer vertexindekser og registrerer ét ansigt i polygonlisten. Angiv tre indekser for en trekant, fire for en firkant.


Trin 4: Indstil node‑transformationer

Hver node har en Transform som styrer dens position, orientering og størrelse i lokalt rum:

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 er kumulative: et underordneds nodes verdensrumposition er sammensætningen af dens egen transformation med alle forældrenodes transformationer. Læs det evaluerede verdensrumresultat fra node.global_transform (uforanderlig, skrivebeskyttet).


Trin 5: Gå igennem scenegrafen rekursivt

Gå gennem hele træet ved at rekursivt gennemløbe 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)

Eksempeloutput for scenen oprettet ovenfor (rotnodens navn er en tom streng):

 [None]
  parent [None]
    child [Mesh]

For scener med flere enheder pr. 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)

Trin 6: Gem scenen

Videregiv en filsti til scene.save(). Formatet udledes af 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 format‑specifikke indstillinger, send et save‑options‑objekt som det andet argument:

from aspose.threed.formats import GltfSaveOptions

opts = GltfSaveOptions()
scene.save("scene.gltf", opts)

Tips og bedste praksis

  • Navngiv hver node. At give noder meningsfulde navne gør fejlsøgning af traversal meget lettere og sikrer, at navne bevares i den eksporterede fil.
  • Én mesh pr. node. At holde entiteter 1:1 med noder forenkler transformationer og kollisionsforespørgsler.
  • Brug create_child_node i stedet for manuel tilknytning. Den sætter forældre‑referencen automatisk og er mindre fejlbehæftet.
  • Læs global_transform efter opbygning af hierarkiet. Resultatet i verdensrum er kun stabilt, når alle forældre‑transformationer er indstillet.
  • Mutér ikke træet under traversal. Tilføjelse eller fjernelse af undernoder under iteration child_nodes vil give uforudsigelig opførsel. Saml noder først, og modificér derefter.
  • Kontrolpunkter bruger Vector4, ikke Vector3. Giv altid pass w=1 for almindelige vertexpositioner; w=0 repræsenterer en retningsvektor (ikke et punkt).
  • mesh.control_points returnerer en kopi. Den control_points egenskab returnerer list(self._control_points) — at tilføje til den returnerede liste ændrer ikke mesh’en. Tilføj altid til mesh._control_points direkte når du bygger geometri programmatisk. Dette er en kendt biblioteksbegrænsning; en offentlig mutations-API findes endnu ikke.

Almindelige problemer

ProblemLøsning
AttributeError: 'NoneType' object has no attribute 'polygons'Beskyt med if node.entity is not None før du får adgang til entitetsegenskaber. En node uden entiteter har entity = None.
Mesh vises ved origo på trods af indstillingen translationtransform.translation anvender en lokal forskydning. Hvis forældrenoden selv har en ikke-identitets-transformation, kan verdenspositionen afvige. Tjek global_transform.
Underordnede noder mangler efter scene.save() / genindlæsningNogle formater (OBJ) flader hierarkiet ud. Brug glTF eller COLLADA for at bevare det fulde nodetræ.
polygon_count er 0 efter mesh.create_polygon(...)Bekræft at de vertex-indekser, der sendes til create_polygon er inden for intervallet (0 til len(control_points) - 1).
Node.get_child(name) returnerer NoneNavnet er case-sensitive. Bekræft den nøjagtige navnestreng, der blev brugt på oprettelsestidspunktet.
Traversal besøger noder i uventet rækkefølgechild_nodes returnerer børn i indsættelsesrækkefølge (rækkefølgen add_child_node / create_child_node blev kaldet).
 Dansk