524 lines
19 KiB
Python
524 lines
19 KiB
Python
"""Scene manager conversion and tileset operations."""
|
||
|
||
import os
|
||
import shutil
|
||
import time
|
||
import json
|
||
import aiohttp
|
||
import asyncio
|
||
import inspect
|
||
from pathlib import Path
|
||
|
||
from panda3d.core import (
|
||
ModelPool, ModelRoot, Filename, NodePath, GeomNode, Material, Vec4, Vec3,
|
||
MaterialAttrib, ColorAttrib, Point3, CollisionNode, CollisionSphere, CollisionBox,
|
||
BitMask32, TransparencyAttrib, LColor, TransformState, RenderModeAttrib
|
||
)
|
||
from panda3d.egg import EggData, EggVertexPool
|
||
from direct.actor.Actor import Actor
|
||
from RenderPipelineFile.rpplugins.smaa.jitters import halton_seq
|
||
from scene import util
|
||
from core.editor_context import get_editor_context
|
||
|
||
class SceneManagerConvertTilesMixin:
|
||
def _get_tree_widget(self):
|
||
"""安全获取树形控件"""
|
||
return get_editor_context(self.world).get_tree_widget()
|
||
|
||
def _shouldConvertToGLB(self, filepath):
|
||
"""判断是否应该转换为GLB格式"""
|
||
ext = os.path.splitext(filepath)[1].lower()
|
||
# 需要转换的格式:FBX, OBJ, DAE等(但不转换已经是GLB/GLTF的)
|
||
convert_formats = ['.fbx', '.obj', '.dae', '.3ds', '.blend']
|
||
return ext in convert_formats
|
||
|
||
def _convertToGLBWithProgress(self, filepath):
|
||
"""带进度显示的GLB转换"""
|
||
class _ConsoleProgress:
|
||
def __init__(self, label):
|
||
self._value = 0
|
||
self._label = label
|
||
|
||
def setWindowTitle(self, title):
|
||
print(f"[GLB转换] {title}")
|
||
|
||
def setWindowModality(self, _):
|
||
return None
|
||
|
||
def show(self):
|
||
print(f"[GLB转换] {self._label}")
|
||
|
||
def hide(self):
|
||
print("[GLB转换] 完成")
|
||
|
||
def setValue(self, value):
|
||
value = int(value)
|
||
if value != self._value:
|
||
self._value = value
|
||
print(f"[GLB转换] 进度: {self._value}%")
|
||
|
||
def setLabelText(self, text):
|
||
if text != self._label:
|
||
self._label = text
|
||
print(f"[GLB转换] {self._label}")
|
||
|
||
progress = _ConsoleProgress("正在转换模型格式以获得更好的动画支持...")
|
||
progress.setWindowTitle("模型格式转换")
|
||
progress.show()
|
||
|
||
try:
|
||
result = self._convertToGLB(filepath, progress)
|
||
progress.hide()
|
||
return result
|
||
except Exception as e:
|
||
progress.hide()
|
||
print(f"转换过程出错: {e}")
|
||
return self._convertToGLB(filepath)
|
||
|
||
def _convertToGLB(self, filepath, progress=None):
|
||
"""将模型文件转换为GLB格式"""
|
||
try:
|
||
print(f"[GLB转换] 开始转换: {filepath}")
|
||
|
||
if progress:
|
||
progress.setValue(10)
|
||
progress.setLabelText("准备转换...")
|
||
|
||
# 准备输出路径
|
||
base_name = os.path.splitext(os.path.basename(filepath))[0]
|
||
output_dir = os.path.dirname(filepath)
|
||
glb_path = os.path.join(output_dir, f"{base_name}_auto_converted.glb")
|
||
|
||
# 如果已经存在转换后的文件,直接使用
|
||
if os.path.exists(glb_path):
|
||
# 检查文件时间,如果原文件更新则重新转换
|
||
original_time = os.path.getmtime(filepath)
|
||
converted_time = os.path.getmtime(glb_path)
|
||
if converted_time > original_time:
|
||
print(f"[GLB转换] 使用现有转换文件: {glb_path}")
|
||
if progress:
|
||
progress.setValue(100)
|
||
return glb_path
|
||
|
||
if progress:
|
||
progress.setValue(20)
|
||
progress.setLabelText("尝试 Blender 转换...")
|
||
|
||
# 方法1: 使用 Blender 进行转换
|
||
if self._convertWithBlender(filepath, glb_path, progress):
|
||
return glb_path
|
||
|
||
if progress:
|
||
progress.setValue(60)
|
||
progress.setLabelText("尝试 FBX2glTF 转换...")
|
||
|
||
# 方法2: 使用 FBX2glTF (如果可用)
|
||
if self._convertWithFBX2glTF(filepath, glb_path, progress):
|
||
return glb_path
|
||
|
||
if progress:
|
||
progress.setValue(80)
|
||
progress.setLabelText("尝试 Assimp 转换...")
|
||
|
||
# 方法3: 使用 Assimp
|
||
if self._convertWithAssimp(filepath, glb_path, progress):
|
||
return glb_path
|
||
|
||
#print(f"[GLB转换] 所有转换方法都失败,既然没有可以转换格式的工具和环境那么就用原始文件,不一定非要转换")
|
||
return None
|
||
|
||
except Exception as e:
|
||
print(f"[GLB转换] 转换过程出错: {e}")
|
||
return None
|
||
|
||
def _convertWithBlender(self, input_path, output_path, progress=None):
|
||
"""使用 Blender 进行转换"""
|
||
try:
|
||
import subprocess
|
||
import tempfile
|
||
|
||
print(f"[Blender转换] {input_path} → {output_path}")
|
||
|
||
# 创建 Blender 脚本
|
||
script_content = f'''
|
||
import bpy
|
||
import sys
|
||
import os
|
||
|
||
# 清理默认场景
|
||
bpy.ops.object.select_all(action='SELECT')
|
||
bpy.ops.object.delete(use_global=False)
|
||
|
||
print("开始导入文件...")
|
||
|
||
# 根据文件类型选择导入方法
|
||
input_file = "{input_path}"
|
||
output_file = "{output_path}"
|
||
|
||
try:
|
||
ext = os.path.splitext(input_file)[1].lower()
|
||
|
||
if ext == '.fbx':
|
||
bpy.ops.import_scene.fbx(filepath=input_file)
|
||
elif ext == '.obj':
|
||
bpy.ops.import_scene.obj(filepath=input_file)
|
||
elif ext == '.dae':
|
||
bpy.ops.wm.collada_import(filepath=input_file)
|
||
elif ext == '.blend':
|
||
bpy.ops.wm.open_mainfile(filepath=input_file)
|
||
else:
|
||
print(f"不支持的格式: {{ext}}")
|
||
sys.exit(1)
|
||
|
||
print("导入成功,开始导出GLB...")
|
||
|
||
# 导出为 GLB,保留动画
|
||
bpy.ops.export_scene.gltf(
|
||
filepath=output_file,
|
||
export_format='GLB',
|
||
export_animations=True,
|
||
export_force_sampling=True,
|
||
export_frame_range=True,
|
||
export_current_frame=False,
|
||
export_skins=True,
|
||
export_morph=True,
|
||
export_lights=True,
|
||
export_cameras=False
|
||
)
|
||
|
||
print("GLB导出成功!")
|
||
|
||
except Exception as e:
|
||
print(f"转换失败: {{e}}")
|
||
sys.exit(1)
|
||
'''
|
||
|
||
# 写入临时脚本文件
|
||
with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as temp_file:
|
||
temp_file.write(script_content)
|
||
script_path = temp_file.name
|
||
|
||
try:
|
||
# 执行 Blender 转换
|
||
result = subprocess.run([
|
||
'blender', '--background', '--python', script_path
|
||
], capture_output=True, text=True, timeout=180)
|
||
|
||
# 清理临时文件
|
||
os.unlink(script_path)
|
||
|
||
if result.returncode == 0 and os.path.exists(output_path):
|
||
print(f"[Blender转换] 转换成功")
|
||
return True
|
||
else:
|
||
print(f"[Blender转换] 转换失败: {result.stderr}")
|
||
return False
|
||
|
||
except subprocess.TimeoutExpired:
|
||
print(f"[Blender转换] 转换超时")
|
||
return False
|
||
except FileNotFoundError:
|
||
print(f"[Blender转换] Blender 未安装")
|
||
return False
|
||
|
||
except Exception as e:
|
||
print(f"[Blender转换] 转换过程出错: {e}")
|
||
return False
|
||
|
||
def _convertWithFBX2glTF(self, input_path, output_path, progress=None):
|
||
"""使用 FBX2glTF 进行转换(仅支持FBX)"""
|
||
try:
|
||
import subprocess
|
||
|
||
if not input_path.lower().endswith('.fbx'):
|
||
return False
|
||
|
||
print(f"[FBX2glTF转换] {input_path} → {output_path}")
|
||
|
||
# 使用 FBX2glTF 转换
|
||
result = subprocess.run([
|
||
'FBX2glTF', input_path, '--output', output_path, '--binary'
|
||
], capture_output=True, text=True, timeout=120)
|
||
|
||
if result.returncode == 0 and os.path.exists(output_path):
|
||
print(f"[FBX2glTF转换] 转换成功")
|
||
return True
|
||
else:
|
||
print(f"[FBX2glTF转换] 转换失败: {result.stderr}")
|
||
return False
|
||
|
||
except subprocess.TimeoutExpired:
|
||
print(f"[FBX2glTF转换] 转换超时")
|
||
return False
|
||
except FileNotFoundError:
|
||
print(f"[FBX2glTF转换] FBX2glTF 未安装")
|
||
return False
|
||
except Exception as e:
|
||
print(f"[FBX2glTF转换] 转换过程出错: {e}")
|
||
return False
|
||
|
||
def _convertWithAssimp(self, input_path, output_path, progress=None):
|
||
"""使用 PyAssimp 进行转换"""
|
||
try:
|
||
import pyassimp
|
||
|
||
print(f"[PyAssimp转换] {input_path} → {output_path}")
|
||
|
||
# 加载模型
|
||
scene = pyassimp.load(input_path)
|
||
if not scene:
|
||
print(f"[PyAssimp转换] 加载模型失败")
|
||
return False
|
||
|
||
if progress:
|
||
progress.setValue(30)
|
||
|
||
# 导出为GLB格式
|
||
pyassimp.export(scene, output_path, "glb2")
|
||
|
||
if progress:
|
||
progress.setValue(80)
|
||
|
||
# 释放资源
|
||
pyassimp.release(scene)
|
||
|
||
if os.path.exists(output_path):
|
||
print(f"[PyAssimp转换] 转换成功")
|
||
return True
|
||
else:
|
||
print(f"[PyAssimp转换] 转换失败: 输出文件未生成")
|
||
return False
|
||
|
||
except ImportError:
|
||
print(f"[PyAssimp转换] PyAssimp 未安装")
|
||
return False
|
||
except Exception as e:
|
||
print(f"[PyAssimp转换] 转换过程出错: {e}")
|
||
return False
|
||
|
||
def _check_async_task(self, panda3d_task):
|
||
# 检查 asyncio 任务是否完成
|
||
if hasattr(self, '_current_asyncio_task'):
|
||
if self._current_asyncio_task.done():
|
||
try:
|
||
self._current_asyncio_task.result()
|
||
except Exception as e:
|
||
print(f"异步任务出错:{e}")
|
||
# 返回 Panda3D 任务管理器的完成状态
|
||
return panda3d_task.done # 注意是 done 而不是 DONE
|
||
# 返回 Panda3D 任务管理器的继续状态
|
||
return panda3d_task.cont # 注意是 cont 而不是 CONTINUE
|
||
|
||
def _parse_tileset(self,tileset_data,tileset_info):
|
||
try:
|
||
root = tileset_data.get('root',{})
|
||
self._parse_tile(root,tileset_info['node'],tileset_info)
|
||
print("✓ Tileset 解析完成")
|
||
except Exception as e:
|
||
print(f"✗ Tileset 解析出错: {e}")
|
||
|
||
def _parse_tile(self, tile_data, parent_node, tileset_info):
|
||
try:
|
||
# 获取tileID
|
||
tile_id = f"tile_{len(tileset_info['tiles'])}"
|
||
print(f"创建tile节点: {tile_id}")
|
||
# 创建tile节点
|
||
tile_node = parent_node.attachNewNode(tile_id)
|
||
|
||
tileset_info['tiles'][tile_id] = {
|
||
'node': tile_node,
|
||
'data': tile_data,
|
||
'loaded': False
|
||
}
|
||
|
||
# 如果有内容,创建占位几何体
|
||
if 'content' in tile_data:
|
||
print(f"为tile {tile_id} 创建几何体")
|
||
self._create_tile_geometry(tile_node)
|
||
# 递归解析子tiles
|
||
children = tile_data.get('children', [])
|
||
print(f"Tile {tile_id} 有 {len(children)} 个子节点")
|
||
for child_data in children:
|
||
self._parse_tile(child_data, tile_node, tileset_info)
|
||
except Exception as e:
|
||
print(f"✗ Tile 解析出错: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
|
||
def _create_tile_geometry(self,parent_node):
|
||
"""为 tile 创建占位几何体"""
|
||
try:
|
||
# 创建一个简单的立方体作为占位符
|
||
from panda3d.core import GeomVertexFormat, GeomVertexData, GeomVertexWriter
|
||
from panda3d.core import Geom, GeomTriangles, GeomNode
|
||
|
||
format = GeomVertexFormat.getV3n3c4()
|
||
vdata = GeomVertexData('tile_cube', format, Geom.UHStatic)
|
||
|
||
vertex = GeomVertexWriter(vdata, 'vertex')
|
||
normal = GeomVertexWriter(vdata, 'normal')
|
||
color = GeomVertexWriter(vdata, 'color')
|
||
|
||
# 定义立方体顶点
|
||
vertices = [
|
||
(-0.5, -0.5, -0.5), (0.5, -0.5, -0.5), (0.5, 0.5, -0.5), (-0.5, 0.5, -0.5),
|
||
(-0.5, -0.5, 0.5), (0.5, -0.5, 0.5), (0.5, 0.5, 0.5), (-0.5, 0.5, 0.5)
|
||
]
|
||
|
||
for vert in vertices:
|
||
vertex.addData3f(*vert)
|
||
normal.addData3f(0, 0, 1)
|
||
color.addData4f(0.2, 0.6, 0.8, 1.0)
|
||
|
||
# 创建几何体
|
||
geom = Geom(vdata)
|
||
|
||
# 创建面
|
||
prim = GeomTriangles(Geom.UHStatic)
|
||
# 底面
|
||
prim.addVertices(0, 1, 2)
|
||
prim.addVertices(0, 2, 3)
|
||
# 顶面
|
||
prim.addVertices(4, 7, 6)
|
||
prim.addVertices(4, 6, 5)
|
||
# 前面
|
||
prim.addVertices(0, 4, 5)
|
||
prim.addVertices(0, 5, 1)
|
||
# 后面
|
||
prim.addVertices(2, 6, 7)
|
||
prim.addVertices(2, 7, 3)
|
||
# 左面
|
||
prim.addVertices(0, 3, 7)
|
||
prim.addVertices(0, 7, 4)
|
||
# 右面
|
||
prim.addVertices(1, 5, 6)
|
||
prim.addVertices(1, 6, 2)
|
||
|
||
prim.closePrimitive()
|
||
geom.addPrimitive(prim)
|
||
|
||
# 创建几何节点
|
||
geom_node = GeomNode('tile_geometry')
|
||
geom_node.addGeom(geom)
|
||
|
||
# 添加到场景
|
||
cube_node = parent_node.attachNewNode(geom_node)
|
||
cube_node.setScale(1000) # 放大以便观察
|
||
|
||
# 添加材质
|
||
material = Material()
|
||
material.setBaseColor((0.2, 0.6, 0.8, 1.0))
|
||
material.setSpecular((0.1, 0.1, 0.1, 1.0))
|
||
material.setShininess(10.0)
|
||
cube_node.setMaterial(material)
|
||
|
||
except Exception as e:
|
||
print(f"✗ 创建 tile 几何体出错: {e}")
|
||
|
||
def _create_placeholder_geometry(self, parent_node):
|
||
"""创建一个简单的占位符几何体,让用户能看到节点"""
|
||
try:
|
||
from panda3d.core import GeomVertexFormat, GeomVertexData, GeomVertexWriter
|
||
from panda3d.core import Geom, GeomTriangles, GeomNode
|
||
|
||
# 创建简单的立方体作为占位符
|
||
format = GeomVertexFormat.getV3n3c4()
|
||
vdata = GeomVertexData('placeholder_cube', format, Geom.UHStatic)
|
||
|
||
vertex = GeomVertexWriter(vdata, 'vertex')
|
||
normal = GeomVertexWriter(vdata, 'normal')
|
||
color = GeomVertexWriter(vdata, 'color')
|
||
|
||
# 定义立方体顶点
|
||
size = 1.0
|
||
vertices = [
|
||
# 前面 (Z+)
|
||
(-size, -size, size), (size, -size, size), (size, size, size), (-size, size, size),
|
||
# 后面 (Z-)
|
||
(-size, -size, -size), (-size, size, -size), (size, size, -size), (size, -size, -size),
|
||
# 左面 (X-)
|
||
(-size, -size, -size), (-size, -size, size), (-size, size, size), (-size, size, -size),
|
||
# 右面 (X+)
|
||
(size, -size, -size), (size, size, -size), (size, size, size), (size, -size, size),
|
||
# 上面 (Y+)
|
||
(-size, size, -size), (-size, size, size), (size, size, size), (size, size, -size),
|
||
# 下面 (Y-)
|
||
(-size, -size, -size), (size, -size, -size), (size, -size, size), (-size, -size, size)
|
||
]
|
||
|
||
normals = [
|
||
# 前面法线
|
||
(0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1),
|
||
# 后面法线
|
||
(0, 0, -1), (0, 0, -1), (0, 0, -1), (0, 0, -1),
|
||
# 左面法线
|
||
(-1, 0, 0), (-1, 0, 0), (-1, 0, 0), (-1, 0, 0),
|
||
# 右面法线
|
||
(1, 0, 0), (1, 0, 0), (1, 0, 0), (1, 0, 0),
|
||
# 上面法线
|
||
(0, 1, 0), (0, 1, 0), (0, 1, 0), (0, 1, 0),
|
||
# 下面法线
|
||
(0, -1, 0), (0, -1, 0), (0, -1, 0), (0, -1, 0)
|
||
]
|
||
|
||
# 青色
|
||
face_colors = [
|
||
(0.0, 1.0, 1.0, 1.0), (0.0, 1.0, 1.0, 1.0), (0.0, 1.0, 1.0, 1.0), (0.0, 1.0, 1.0, 1.0), # 前面 - 青色
|
||
(0.0, 0.8, 0.8, 1.0), (0.0, 0.8, 0.8, 1.0), (0.0, 0.8, 0.8, 1.0), (0.0, 0.8, 0.8, 1.0), # 后面 - 稍暗青色
|
||
(0.0, 0.9, 0.9, 1.0), (0.0, 0.9, 0.9, 1.0), (0.0, 0.9, 0.9, 1.0), (0.0, 0.9, 0.9, 1.0), # 左面 - 中等青色
|
||
(0.0, 0.7, 0.7, 1.0), (0.0, 0.7, 0.7, 1.0), (0.0, 0.7, 0.7, 1.0), (0.0, 0.7, 0.7, 1.0), # 右面 - 稍暗青色
|
||
(0.0, 1.0, 1.0, 1.0), (0.0, 1.0, 1.0, 1.0), (0.0, 1.0, 1.0, 1.0), (0.0, 1.0, 1.0, 1.0), # 上面 - 青色
|
||
(0.0, 0.6, 0.6, 1.0), (0.0, 0.6, 0.6, 1.0), (0.0, 0.6, 0.6, 1.0), (0.0, 0.6, 0.6, 1.0) # 下面 - 更暗青色
|
||
]
|
||
|
||
for i, vert in enumerate(vertices):
|
||
vertex.addData3f(*vert)
|
||
normal.addData3f(*normals[i])
|
||
color.addData4f(*face_colors[i])
|
||
|
||
# 创建几何体
|
||
geom = Geom(vdata)
|
||
|
||
# 创建面(每个面两个三角形)
|
||
prim = GeomTriangles(Geom.UHStatic)
|
||
|
||
# 每个面4个顶点,生成2个三角形
|
||
for face in range(6): # 6个面
|
||
base_index = face * 4
|
||
# 第一个三角形
|
||
prim.addVertices(base_index, base_index + 1, base_index + 2)
|
||
# 第二个三角形
|
||
prim.addVertices(base_index, base_index + 2, base_index + 3)
|
||
|
||
prim.closePrimitive()
|
||
geom.addPrimitive(prim)
|
||
|
||
# 创建几何节点
|
||
geom_node = GeomNode('tileset_placeholder')
|
||
geom_node.addGeom(geom)
|
||
|
||
# 添加到场景
|
||
cube_node = parent_node.attachNewNode(geom_node)
|
||
cube_node.setScale(5) # 设置合适的大小
|
||
|
||
# 设置双面渲染
|
||
cube_node.setTwoSided(True)
|
||
|
||
# 添加材质
|
||
material = Material()
|
||
material.setBaseColor((0.0, 1.0, 1.0, 1.0)) # 青色
|
||
material.setSpecular((0.5, 0.5, 0.5, 1.0))
|
||
material.setShininess(32.0)
|
||
cube_node.setMaterial(material)
|
||
|
||
# 添加标识标签
|
||
cube_node.setTag("element_type", "cesium_placeholder")
|
||
|
||
print("✓ 占位符几何体创建完成")
|
||
return cube_node
|
||
except Exception as e:
|
||
print(f"✗ 创建占位符几何体出错: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return None
|