Как да обходим 3D графа на сцената в Python
Сценичният граф в Aspose.3D FOSS е дърво от Node обекти, чиито корен е scene.root_node. Всеки 3D файл, независимо дали е зареден от OBJ, glTF, STL, COLLADA или 3MF, създава една и съща дървовидна структура. Познаването на начина за обхождане ви позволява да инспектирате геометрията, броите полигони, филтрирате обекти по тип и обработвате конкретни части от сложна сцена.
Ръководство стъпка по стъпка
Стъпка 1: Инсталиране и импортиране
Инсталирайте Aspose.3D FOSS от PyPI:
pip install aspose-3d-fossИмпортирайте необходимите класове:
from aspose.threed import Scene
from aspose.threed.entities import MeshВсички публични класове се намират под aspose.threed или неговите подпакети (aspose.threed.entities, aspose.threed.utilities).
Стъпка 2: Зареждане на сцена от файл
Използвайте статичния Scene.from_file() метод, за да отворите всеки поддържан формат. Форматът се открива автоматично от разширението на файла:
scene = Scene.from_file("model.gltf")Можете също да отворите с явни опции за зареждане:
from aspose.threed import Scene
from aspose.threed.formats import ObjLoadOptions
options = ObjLoadOptions()
options.enable_materials = True
scene = Scene()
scene.open("model.obj", options)След зареждане, scene.root_node е коренът на дървото. Всички импортирани възли са деца или наследници на този възел.
Стъпка 3: Напишете рекурсивна функция за обхождане
Най‑опростеното обхождане посещава всеки възел в дълбочинно‑първо ред:
from aspose.threed import Scene
from aspose.threed.entities import Mesh
def traverse(node, depth=0):
prefix = " " * depth
entity_name = type(node.entity).__name__ if node.entity else "-"
print(f"{prefix}[{entity_name}] {node.name}")
for child in node.child_nodes:
traverse(child, depth + 1)
scene = Scene.from_file("model.gltf")
traverse(scene.root_node)Примерен изход:
[-]
[-] Armature
[Mesh] Body
[Mesh] Eyes
[-] Ground
[Mesh] Plane<button class=“hextra-code-copy-btn hx-group/copybtn hx-transition-all active:hx-opacity-50 hx-bg-primary-700/5 hx-border hx-border-black/5 hx-text-gray-600 hover:hx-text-gray-900 hx-rounded-md hx-p-1.5 dark:hx-bg-primary-300/10 dark:hx-border-white/10 dark:hx-text-gray-400 dark:hover:hx-text-gray-50” title=“Copy code”
<div class="copy-icon group-[.copied]/copybtn:hx-hidden hx-pointer-events-none hx-h-4 hx-w-4"></div>
<div class="success-icon hx-hidden group-[.copied]/copybtn:hx-block hx-pointer-events-none hx-h-4 hx-w-4"></div>
Бележка: Коренният възел има празно име (""), така че първият ред показва [-] без последващо име.
node.child_nodes връща децата в реда на вмъкване (редът, в който импортерът или потребителят ги е добавил).
Стъпка 4: Достъп до свойствата на обекта за всеки възел
Използвайте node.entity за да получите първото обект, прикрепен към възел, или node.entities за да итерирате всички тях. Проверете типа преди да достъпвате свойства, специфични за формата:
from aspose.threed import Scene
from aspose.threed.entities import Mesh
def print_entity_details(node, depth=0):
indent = " " * depth
for entity in node.entities:
if isinstance(entity, Mesh):
mesh = entity
print(f"{indent}Mesh '{node.name}':")
print(f"{indent} vertices : {len(mesh.control_points)}")
print(f"{indent} polygons : {mesh.polygon_count}")
print(f"{indent} cast_shadows : {mesh.cast_shadows}")
print(f"{indent} receive_shadows: {mesh.receive_shadows}")
for child in node.child_nodes:
print_entity_details(child, depth + 1)
scene = Scene.from_file("model.gltf")
print_entity_details(scene.root_node)node.transform.translation, node.transform.rotation, и node.transform.scaling дават трансформацията в локалното пространство. node.global_transform дава изчисления резултат в световното пространство (с global_transform.scale за скалиране в световното пространство).
Стъпка 5: Филтриране на възлите по тип на обекта
За да работите само с конкретни типове обекти, добавете isinstance guard вътре в обхода:
from aspose.threed import Scene
from aspose.threed.entities import Mesh
def find_mesh_nodes(node, results=None):
if results is None:
results = []
for entity in node.entities:
if isinstance(entity, Mesh):
results.append(node)
break # One match per node is enough
for child in node.child_nodes:
find_mesh_nodes(child, results)
return results
scene = Scene.from_file("model.gltf")
mesh_nodes = find_mesh_nodes(scene.root_node)
print(f"Found {len(mesh_nodes)} mesh node(s)")
for n in mesh_nodes:
print(f" {n.name}")Този шаблон е полезен за масови операции: прилагане на трансформация към всеки възел с мрежа, замяна на материали или експортиране на поддървета.
Стъпка 6: Събиране на всички мрежи и отпечатване на броя върхове
Агрегиране на статистиката за мрежите в един проход:
from aspose.threed import Scene
from aspose.threed.entities import Mesh
def collect_meshes(node, results=None):
if results is None:
results = []
if isinstance(node.entity, Mesh):
results.append((node.name, node.entity))
for child in node.child_nodes:
collect_meshes(child, results)
return results
scene = Scene.from_file("model.gltf")
meshes = collect_meshes(scene.root_node)
print(f"Total meshes: {len(meshes)}")
total_verts = 0
total_polys = 0
for name, mesh in meshes:
verts = len(mesh.control_points)
polys = mesh.polygon_count
total_verts += verts
total_polys += polys
print(f" {name}: {verts} vertices, {polys} polygons")
print(f"Scene totals: {total_verts} vertices, {total_polys} polygons")Чести проблеми
| Проблем | Резолюция |
|---|---|
AttributeError: 'NoneType' object has no attribute 'polygons' | Защита с if node.entity is not None или isinstance(node.entity, Mesh) преди достъп до свойствата на мрежата. Възли без обекти връщат None. |
| Обхождането спира рано | Уверете се, че рекурсията достига до node.child_nodes. Ако итерирате само scene.root_node.child_nodes (не рекурсивно), пропускате всички наследници. |
| Mesh липсва в събраните резултати | Проверете дали файловият формат запазва йерархията. OBJ изравнява цялата геометрия в един ниво на възел. Използвайте glTF или COLLADA за дълбоки йерархии. |
node.entity връща само първия елемент | Използвайте node.entities (пълният списък), когато възелът съдържа множество ентитети. node.entity е съкратено за node.entities[0] когато entities не е празно. |
collect_meshes връща 0 резултата за зареден STL | STL файловете обикновено създават един плосък възел с една mesh entity директно под root_node. Проверете root_node.child_nodes[0].entity директно. |
| Имената на възлите са празни низове | Някои формати (binary STL, някои OBJ файлове) не съхраняват имена на обекти. Възлите ще имат празни name низове; използвайте индекса за идентификация вместо това. |
Често задавани въпроси
Колко дълбок може да бъде графът на сцената?
Няма твърдо ограничение. Стандартният лимит за рекурсия на Python (1000 кадъра) се прилага за рекурсивните функции за обход. За много дълбоки йерархии, преобразувайте рекурсията в явен стек:
from collections import deque
from aspose.threed import Scene
scene = Scene.from_file("deep.gltf")
queue = deque([(scene.root_node, 0)])
while queue:
node, depth = queue.popleft()
print(" " * depth + node.name)
for child in node.child_nodes:
queue.append((child, depth + 1))Мога ли да модифицирам дървото, докато го обхождам?
Не добавяйте и не премахвайте възли от child_nodes по време на итериране. Съберете възлите за модифициране в първия проход, след което приложете промените във втория проход.
Как да намеря конкретен възел по име?
Използвайте node.get_child(name) за да намерите директно дете по име. За дълбоко търсене, обходете дървото с филтър за име:
def find_by_name(root, name):
if root.name == name:
return root
for child in root.child_nodes:
result = find_by_name(child, name)
if result:
return result
return None
target = find_by_name(scene.root_node, "Wheel_FL")Връща ли node.entity винаги връща Mesh?
Не. Възелът може да съдържа всеки тип обект: Mesh, Camera, Light, или персонализирани обекти. Винаги проверявайте с isinstance(node.entity, Mesh) преди да използвате свойства, специфични за mesh.
Как да получа позицията в световно пространство на възел?
Четене node.global_transform.translation. Това е изчислената позиция в световното пространство, като се вземат предвид всички трансформации на предците. Тя е само за четене; променете node.transform.translation за да преместите възела.
Мога ли да преброя общия брой полигони в сцена без да пиша обход?
Не директно чрез API; няма scene.total_polygon_count свойство. Използвайте collect_meshes и сумирайте mesh.polygon_count по резултатите, както е показано в Стъпка 6.