1
0
forked from Rowland/EG

太阳方位角及高度角可自由调整

This commit is contained in:
Hector 2025-08-04 16:42:08 +08:00
parent d40cce3ece
commit 62aa0f6fbd
41 changed files with 2233 additions and 42 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 KiB

View File

@ -86,7 +86,8 @@ class RenderPipeline(RPObject):
self._applied_effects = [] self._applied_effects = []
self._pre_showbase_initialized = False self._pre_showbase_initialized = False
self._first_frame = None self._first_frame = None
self.set_loading_screen_image("/$$rp/data/gui/loading_screen_bg.txo") #self.set_loading_screen_image("/$$rp/data/gui/loading_screen_bg.txo")
self.set_loading_screen_image("/$$rp/data/gui/Default_Rough.png")
def load_settings(self, path): def load_settings(self, path):
""" Loads the pipeline configuration from a given filename. Usually """ Loads the pipeline configuration from a given filename. Usually

View File

@ -71,8 +71,8 @@ class DisplayShaderBuilder(object): # pylint: disable=too-few-public-methods
# Build actual shader # Build actual shader
built = """ built = """
#version 400 #version 400
#extension GL_ARB_shading_language_420pack : enable
#pragma include "render_pipeline_base.inc.glsl" #pragma include "render_pipeline_base.inc.glsl"
#extension GL_ARB_shading_language_420pack : enable
in vec2 texcoord; in vec2 texcoord;
out vec3 result; out vec3 result;
uniform int mipmap; uniform int mipmap;

View File

@ -127,9 +127,20 @@ class NetworkCommunication(RPObject):
be an update of a plugin setting """ be an update of a plugin setting """
if cmd.startswith("setval "): if cmd.startswith("setval "):
parts = cmd.split() parts = cmd.split()
if len(parts) < 3:
self.warn("Invalid setval command format. Expected: setval plugin.setting value")
return
setting_parts = parts[1].split(".") setting_parts = parts[1].split(".")
self._pipeline.plugin_mgr.on_setting_changed( if len(setting_parts) < 2:
setting_parts[0], setting_parts[1], parts[2]) self.warn("Invalid setting format. Expected: plugin.setting")
return
try:
self._pipeline.plugin_mgr.on_setting_changed(
setting_parts[0], setting_parts[1], parts[2])
except Exception as e:
self.warn("Error handling setting change:", str(e))
else: else:
self.warn("Recieved unkown plugin command:", cmd) self.warn("Recieved unkown plugin command:", cmd)

View File

@ -1,7 +1,5 @@
#pragma once #pragma once
#extension GL_ARB_shading_language_420pack : enable
// This file includes defines and functions used for the scattering // This file includes defines and functions used for the scattering
@ -9,6 +7,8 @@
#pragma include "render_pipeline_base.inc.glsl" #pragma include "render_pipeline_base.inc.glsl"
#extension GL_ARB_shading_language_420pack : enable
// These can be overridden depending on if the shader is a compute shader // These can be overridden depending on if the shader is a compute shader
// or a regular fragment shader // or a regular fragment shader

View File

@ -97,6 +97,11 @@ class PluginConfigurator(QMainWindow, Ui_MainWindow):
update_thread = Thread(target=self.update_thread, args=()) update_thread = Thread(target=self.update_thread, args=())
update_thread.start() update_thread.start()
# 启动通信文件监听线程
comm_thread = Thread(target=self.communication_thread, args=())
comm_thread.daemon = True # 设置为守护线程,主程序退出时自动结束
comm_thread.start()
def closeEvent(self, event): # noqa def closeEvent(self, event): # noqa
event.accept() event.accept()
import os import os
@ -486,6 +491,84 @@ class PluginConfigurator(QMainWindow, Ui_MainWindow):
self.lst_plugins.setCurrentRow(0) self.lst_plugins.setCurrentRow(0)
def communication_thread(self):
"""监听来自主程序的通信文件"""
import json
import tempfile
comm_dir = os.path.join(tempfile.gettempdir(), "daytime_editor_comm")
comm_file = os.path.join(comm_dir, "sun_azimuth_command.json")
print("🔄 Day Time Editor通信监听已启动")
print(f" 监听文件: {comm_file}")
last_timestamp = 0
while True:
try:
if os.path.exists(comm_file):
with open(comm_file, 'r') as f:
command = json.load(f)
# 检查是否是新命令(避免重复执行)
if command.get('timestamp', 0) > last_timestamp:
last_timestamp = command.get('timestamp', 0)
if command.get('command') == 'set_sun_azimuth':
azimuth_value = command.get('value', 0)
print(f"📨 收到Sun Azimuth命令: {azimuth_value}°")
# 在主线程中执行UI更新
QTimer.singleShot(0, partial(self.apply_sun_azimuth, azimuth_value))
time.sleep(0.1) # 100ms检查一次
except Exception as e:
print(f"⚠️ 通信监听错误: {e}")
time.sleep(1) # 出错时等待1秒再重试
def apply_sun_azimuth(self, azimuth_degrees):
"""应用Sun Azimuth值到Day Time Editor"""
try:
print(f"🌞 正在应用Sun Azimuth: {azimuth_degrees}°")
# 查找Sun Azimuth相关的控件
# 这里需要根据实际的UI结构来实现
# 先尝试找到可能的控件
# 方法1查找包含"azimuth"的控件
for widget_name in dir(self):
widget = getattr(self, widget_name, None)
if widget and hasattr(widget, 'setValue'):
if 'azimuth' in widget_name.lower() or 'sun' in widget_name.lower():
print(f" 尝试设置控件: {widget_name}")
try:
widget.setValue(azimuth_degrees)
print(f"✅ 成功设置 {widget_name} = {azimuth_degrees}°")
return
except Exception as e:
print(f" 设置失败: {e}")
# 方法2查找表格中的Sun Azimuth设置
if hasattr(self, 'table_plugin_settings'):
table = self.table_plugin_settings
for row in range(table.rowCount()):
item = table.item(row, 0) # 第一列通常是设置名称
if item and 'azimuth' in item.text().lower():
# 找到Sun Azimuth行设置第二列的值
value_item = table.item(row, 1)
if value_item:
value_item.setText(str(azimuth_degrees))
print(f"✅ 在表格中设置Sun Azimuth = {azimuth_degrees}°")
return
print("⚠️ 未找到Sun Azimuth控件请检查UI结构")
except Exception as e:
print(f"❌ 应用Sun Azimuth失败: {e}")
import traceback
traceback.print_exc()
# Start application # Start application
app = QApplication(sys.argv) app = QApplication(sys.argv)
qt_register_fonts() qt_register_fonts()

Binary file not shown.

View File

@ -1,13 +1,16 @@
import math import math
import warnings import warnings
from direct.actor.Actor import Actor
warnings.filterwarnings("ignore", category=DeprecationWarning) warnings.filterwarnings("ignore", category=DeprecationWarning)
from QPanda3D.Panda3DWorld import Panda3DWorld from QPanda3D.Panda3DWorld import Panda3DWorld
from panda3d.core import (CardMaker, Vec4, Vec3, AmbientLight, DirectionalLight, from panda3d.core import (CardMaker, Vec4, Vec3, AmbientLight, DirectionalLight,
Point3, WindowProperties,Material,LColor) Point3, WindowProperties,Material,LColor)
from direct.showbase.ShowBaseGlobal import globalClock from direct.showbase.ShowBaseGlobal import globalClock
from scene.scene_manager import SceneManager
class CoreWorld(Panda3DWorld): class CoreWorld(Panda3DWorld):
"""核心世界功能类 - 负责基础的3D世界设置和核心功能""" """核心世界功能类 - 负责基础的3D世界设置和核心功能"""
@ -49,6 +52,7 @@ class CoreWorld(Panda3DWorld):
# #
#self.createDirectionalLight() #self.createDirectionalLight()
#self._setYCModel() #self._setYCModel()
#self._setArm1()
# 创建示例材质 # 创建示例材质
#self.load_test_models_with_materials() #self.load_test_models_with_materials()
@ -70,6 +74,19 @@ class CoreWorld(Panda3DWorld):
model.setPos(-8, 42, 0) model.setPos(-8, 42, 0)
model.setHpr(0, 90, 0) model.setHpr(0, 90, 0)
def _setArm1(self):
model1 = self.loader.loadModel("/home/tiger/下载/JQB1.fbx")
model2 = self.loader.loadModel("/home/tiger/下载/JQB2.fbx")
model1.reparentTo(self.render)
model1.setPos(0, 5, 0)
model1.setHpr(0, 90, 0)
model1.setScale(0.01)
model2.reparentTo(self.render)
model2.setPos(0, -5, 0)
model2.setHpr(0, 90, 0)
model2.setScale(0.01)
def _setupCamera(self): def _setupCamera(self):
"""设置相机位置和朝向""" """设置相机位置和朝向"""
self.cam.setPos(0, -50, 20) self.cam.setPos(0, -50, 20)
@ -288,8 +305,19 @@ class CoreWorld(Panda3DWorld):
def set_daytime(self, time_str): def set_daytime(self, time_str):
self.render_pipeline.daytime_mgr.time = time_str """设置时间"""
print(f"当前时间设置为: {time_str}") try:
if hasattr(self, 'render_pipeline') and self.render_pipeline:
self.render_pipeline.daytime_mgr.time = time_str
print(f"当前时间设置为: {time_str}")
else:
print(f"⚠️ RenderPipeline 不可用,无法设置时间: {time_str}")
except Exception as e:
print(f"设置时间失败: {e}")
def get_render_pipeline(self):
"""获取 RenderPipeline 实例"""
return getattr(self, 'render_pipeline', None)
def _setupSkybox(self): def _setupSkybox(self):
# 加载天空盒模型 # 加载天空盒模型

View File

@ -59,7 +59,7 @@ class MyWorld(CoreWorld):
# 初始化脚本管理系统 # 初始化脚本管理系统
self.script_manager = ScriptManager(self) self.script_manager = ScriptManager(self)
# 初始化GUI管理系统 # 初始化GUI管理系统
self.gui_manager = GUIManager(self) self.gui_manager = GUIManager(self)
@ -509,7 +509,6 @@ class MyWorld(CoreWorld):
def listAllScripts(self): def listAllScripts(self):
"""列出所有脚本信息""" """列出所有脚本信息"""
return self.script_manager.list_all_scripts() return self.script_manager.list_all_scripts()
# ==================== VR系统功能代理 ==================== # ==================== VR系统功能代理 ====================
# VR系统控制方法 - 代理到vr_manager # VR系统控制方法 - 代理到vr_manager

View File

@ -15,13 +15,14 @@ from panda3d.core import (
from panda3d.egg import EggData, EggVertexPool from panda3d.egg import EggData, EggVertexPool
from direct.actor.Actor import Actor from direct.actor.Actor import Actor
from QPanda3D.Panda3DWorld import get_render_pipeline from QPanda3D.Panda3DWorld import get_render_pipeline
from scene.animation_manager import AnimationManager
class SceneManager: class SceneManager:
"""场景管理器 - 统一管理场景中的所有元素""" """场景管理器 - 统一管理场景中的所有元素"""
def __init__(self, world): def __init__(self, world):
"""初始化场景管理器 """初始化场景管理器
Args: Args:
world: 主程序world对象引用 world: 主程序world对象引用
""" """
@ -30,7 +31,10 @@ class SceneManager:
self.Spotlight = [] self.Spotlight = []
self.Pointlight = [] self.Pointlight = []
# 初始化动画管理器
self.animation_manager = AnimationManager(world)
print("✓ 场景管理系统初始化完成") print("✓ 场景管理系统初始化完成")
# ==================== 模型导入和处理 ==================== # ==================== 模型导入和处理 ====================
@ -105,6 +109,64 @@ class SceneManager:
except Exception as e: except Exception as e:
print(f"导入模型失败: {str(e)}") print(f"导入模型失败: {str(e)}")
return None return None
def importAnimatedFBX(self, filepath, scale=0.01, auto_play=True):
"""导入带动画的FBX模型使用assimp
Args:
filepath: FBX文件路径
scale: 缩放比例默认0.01从厘米转换到米
auto_play: 是否自动播放第一个动画
Returns:
Actor对象如果加载失败返回None
"""
try:
print(f"\n=== 导入动画FBX模型: {filepath} ===")
# 使用动画管理器加载FBX
actor = self.animation_manager.load_fbx_with_animations(filepath, scale)
if actor:
# 设置模型名称
model_name = os.path.basename(filepath)
actor.setName(model_name)
# 调整模型位置到地面
self._adjustModelToGround(actor)
# 设置碰撞检测
self.setupCollision(actor)
# 添加文件标签
actor.setTag("file", model_name)
actor.setTag("is_animated_model", "1")
# 添加到模型列表
self.models.append(actor)
# 自动播放第一个动画
if auto_play:
available_anims = self.animation_manager.get_available_animations(actor)
if available_anims:
first_anim = available_anims[0]
self.animation_manager.play_animation(actor, first_anim, loop=True)
print(f"🎬 自动播放动画: {first_anim}")
# 更新场景树
self.updateSceneTree()
print(f"=== 动画FBX模型导入成功: {model_name} ===\n")
return actor
else:
print("❌ 动画FBX模型导入失败")
return None
except Exception as e:
print(f"导入动画FBX模型失败: {str(e)}")
import traceback
traceback.print_exc()
return None
def _applyMaterialsToModel(self, model): def _applyMaterialsToModel(self, model):
"""递归应用材质到模型的所有GeomNode""" """递归应用材质到模型的所有GeomNode"""
@ -425,25 +487,59 @@ class SceneManager:
except Exception as e: except Exception as e:
print(f"异步加载模型失败: {str(e)}") print(f"异步加载模型失败: {str(e)}")
def loadAnimatedModel(self, model_path, anims=None): def loadAnimatedModel(self, model_path, anims=None, auto_play=True):
"""加载带动画的模型""" """加载带动画的模型
Args:
model_path: 模型文件路径
anims: 动画字典格式为 {"动画名": "动画文件路径"}
auto_play: 是否自动播放第一个动画
"""
try: try:
print(f"🎬 加载动画模型: {model_path}")
# 如果没有指定动画,尝试自动检测
if anims is None:
anims = self._detectAnimations(model_path)
# 创建Actor对象 # 创建Actor对象
actor = Actor(model_path, anims) actor = Actor(model_path, anims)
if actor: if actor:
actor.reparentTo(self.world.render) actor.reparentTo(self.world.render)
# 设置碰撞检测 # 设置碰撞检测
self.setupCollision(actor) self.setupCollision(actor)
# 获取可用的动画列表
available_anims = actor.getAnimNames()
print(f"📋 检测到动画: {available_anims}")
# 自动播放第一个动画
if auto_play and available_anims:
first_anim = available_anims[0]
actor.loop(first_anim)
print(f"▶️ 自动播放动画: {first_anim}")
# 添加动画控制标签
actor.setTag("animated", "true")
actor.setTag("current_anim", first_anim)
actor.setTag("available_anims", str(available_anims))
# 调整模型位置(让它站在地面上)
self._adjustModelPosition(actor)
self.models.append(actor) self.models.append(actor)
# 更新场景树 # 更新场景树
self.updateSceneTree() self.updateSceneTree()
print(f"✅ 动画模型加载完成: {actor.getName()}")
return actor return actor
except Exception as e: except Exception as e:
print(f"加载动画模型失败: {str(e)}") print(f"❌ 加载动画模型失败: {str(e)}")
import traceback
traceback.print_exc()
return None return None
# ==================== 材质和几何体处理 ==================== # ==================== 材质和几何体处理 ====================
def processMaterials(self, model): def processMaterials(self, model):

133
scripts/R_P.py Normal file
View File

@ -0,0 +1,133 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
旋转脚本 - 让对象持续旋转
"""
from core.script_system import ScriptBase
class RotatorScript(ScriptBase):
"""旋转脚本类"""
def __init__(self):
super().__init__()
self.rotation_speed_y = 30.0 # Y轴旋转速度 (度/秒)
self.max_angle = 30.0 # 最大旋转角度(相对于初始角度)
self.direction = 1
self.current_offset = 0.0 # 当前相对于初始角度的偏移
self.initial_angle = None # 模型的初始角度
self.is_rotating = True # 是否正在旋转
def start(self):
"""脚本开始时调用"""
self.log("旋转脚本启动!")
self.log(f"旋转速度: {self.rotation_speed_y}度/秒")
self.log(f"最大旋转角度: ±{self.max_angle}")
# 记录模型的初始角度
if self.gameObject:
initial_hpr = self.gameObject.getHpr()
self.initial_angle = initial_hpr.getZ() # 记录Z轴的初始角度
self.log(f"模型初始角度: {self.initial_angle}")
self.log(f"旋转范围: {self.initial_angle - self.max_angle}° 到 {self.initial_angle + self.max_angle}°")
else:
self.log("⚠️ 无法获取游戏对象使用默认初始角度0")
self.initial_angle = 0.0
def update(self, dt):
"""每帧更新"""
if not self.is_rotating or self.initial_angle is None:
return
# 计算角度变化量
delta_angle = self.rotation_speed_y * dt * self.direction
self.current_offset += delta_angle
# 如果超出角度范围,则反向并限制在边界
if self.current_offset > self.max_angle:
self.current_offset = self.max_angle
self.direction *= -1
elif self.current_offset < -self.max_angle:
self.current_offset = -self.max_angle
self.direction *= -1
# 计算最终角度(初始角度 + 偏移量)
final_angle = self.initial_angle + self.current_offset
# 设置新的旋转只改变Z轴保持其他不变
current_hpr = self.gameObject.getHpr()
self.gameObject.setHpr(current_hpr.getX(), final_angle, current_hpr.getZ())
# if not self.is_rotating:
# return
#
# # 获取当前旋转并应用增量
# current_hpr = self.gameObject.getHpr()
# new_r = current_hpr.getZ() + self.rotation_speed_y * dt
# self.gameObject.setHpr(current_hpr.getX(), current_hpr.getY(), new_r)
def on_destroy(self):
"""脚本销毁时调用"""
self.log("旋转脚本停止")
# ==================== 控制方法 ====================
def set_max_angle(self, new_max_angle):
"""
设置新的最大旋转角度
Args:
new_max_angle: 新的最大角度值
"""
self.max_angle = new_max_angle
self.log(f"最大旋转角度已设置为: ±{self.max_angle}")
if self.initial_angle is not None:
self.log(f"新的旋转范围: {self.initial_angle - self.max_angle}° 到 {self.initial_angle + self.max_angle}°")
def set_rotation_speed(self, new_speed):
"""
设置新的旋转速度
Args:
new_speed: 新的旋转速度/
"""
self.rotation_speed_y = new_speed
self.log(f"旋转速度已设置为: {self.rotation_speed_y}度/秒")
def pause_rotation(self):
"""暂停旋转"""
self.is_rotating = False
self.log("旋转已暂停")
def resume_rotation(self):
"""恢复旋转"""
self.is_rotating = True
self.log("旋转已恢复")
def reset_to_initial_angle(self):
"""重置到初始角度"""
if self.initial_angle is not None and self.gameObject:
current_hpr = self.gameObject.getHpr()
self.gameObject.setHpr(current_hpr.getX(), current_hpr.getY(), self.initial_angle)
self.current_offset = 0.0
self.direction = 1
self.log(f"已重置到初始角度: {self.initial_angle}")
def get_current_info(self):
"""获取当前旋转信息"""
if self.gameObject and self.initial_angle is not None:
current_hpr = self.gameObject.getHpr()
current_angle = current_hpr.getZ()
self.log("=== 当前旋转信息 ===")
self.log(f"初始角度: {self.initial_angle}")
self.log(f"当前角度: {current_angle}")
self.log(f"偏移量: {self.current_offset}")
self.log(f"最大角度: ±{self.max_angle}")
self.log(f"旋转速度: {self.rotation_speed_y}度/秒")
self.log(f"旋转方向: {'正向' if self.direction > 0 else '反向'}")
self.log(f"旋转状态: {'运行中' if self.is_rotating else '已暂停'}")
self.log("=== 信息结束 ===")
else:
self.log("无法获取旋转信息")

133
scripts/R_R.py Normal file
View File

@ -0,0 +1,133 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
旋转脚本 - 让对象持续旋转
"""
from core.script_system import ScriptBase
class RotatorScript(ScriptBase):
"""旋转脚本类"""
def __init__(self):
super().__init__()
self.rotation_speed_y = 30.0 # Y轴旋转速度 (度/秒)
self.max_angle = 30.0 # 最大旋转角度(相对于初始角度)
self.direction = 1
self.current_offset = 0.0 # 当前相对于初始角度的偏移
self.initial_angle = None # 模型的初始角度
self.is_rotating = True # 是否正在旋转
def start(self):
"""脚本开始时调用"""
self.log("旋转脚本启动!")
self.log(f"旋转速度: {self.rotation_speed_y}度/秒")
self.log(f"最大旋转角度: ±{self.max_angle}")
# 记录模型的初始角度
if self.gameObject:
initial_hpr = self.gameObject.getHpr()
self.initial_angle = initial_hpr.getZ() # 记录Z轴的初始角度
self.log(f"模型初始角度: {self.initial_angle}")
self.log(f"旋转范围: {self.initial_angle - self.max_angle}° 到 {self.initial_angle + self.max_angle}°")
else:
self.log("⚠️ 无法获取游戏对象使用默认初始角度0")
self.initial_angle = 0.0
def update(self, dt):
"""每帧更新"""
if not self.is_rotating or self.initial_angle is None:
return
# 计算角度变化量
delta_angle = self.rotation_speed_y * dt * self.direction
self.current_offset += delta_angle
# 如果超出角度范围,则反向并限制在边界
if self.current_offset > self.max_angle:
self.current_offset = self.max_angle
self.direction *= -1
elif self.current_offset < -self.max_angle:
self.current_offset = -self.max_angle
self.direction *= -1
# 计算最终角度(初始角度 + 偏移量)
final_angle = self.initial_angle + self.current_offset
# 设置新的旋转只改变Z轴保持其他不变
current_hpr = self.gameObject.getHpr()
self.gameObject.setHpr(current_hpr.getX(), current_hpr.getY(), final_angle)
# if not self.is_rotating:
# return
#
# # 获取当前旋转并应用增量
# current_hpr = self.gameObject.getHpr()
# new_r = current_hpr.getZ() + self.rotation_speed_y * dt
# self.gameObject.setHpr(current_hpr.getX(), current_hpr.getY(), new_r)
def on_destroy(self):
"""脚本销毁时调用"""
self.log("旋转脚本停止")
# ==================== 控制方法 ====================
def set_max_angle(self, new_max_angle):
"""
设置新的最大旋转角度
Args:
new_max_angle: 新的最大角度值
"""
self.max_angle = new_max_angle
self.log(f"最大旋转角度已设置为: ±{self.max_angle}")
if self.initial_angle is not None:
self.log(f"新的旋转范围: {self.initial_angle - self.max_angle}° 到 {self.initial_angle + self.max_angle}°")
def set_rotation_speed(self, new_speed):
"""
设置新的旋转速度
Args:
new_speed: 新的旋转速度/
"""
self.rotation_speed_y = new_speed
self.log(f"旋转速度已设置为: {self.rotation_speed_y}度/秒")
def pause_rotation(self):
"""暂停旋转"""
self.is_rotating = False
self.log("旋转已暂停")
def resume_rotation(self):
"""恢复旋转"""
self.is_rotating = True
self.log("旋转已恢复")
def reset_to_initial_angle(self):
"""重置到初始角度"""
if self.initial_angle is not None and self.gameObject:
current_hpr = self.gameObject.getHpr()
self.gameObject.setHpr(current_hpr.getX(), current_hpr.getY(), self.initial_angle)
self.current_offset = 0.0
self.direction = 1
self.log(f"已重置到初始角度: {self.initial_angle}")
def get_current_info(self):
"""获取当前旋转信息"""
if self.gameObject and self.initial_angle is not None:
current_hpr = self.gameObject.getHpr()
current_angle = current_hpr.getZ()
self.log("=== 当前旋转信息 ===")
self.log(f"初始角度: {self.initial_angle}")
self.log(f"当前角度: {current_angle}")
self.log(f"偏移量: {self.current_offset}")
self.log(f"最大角度: ±{self.max_angle}")
self.log(f"旋转速度: {self.rotation_speed_y}度/秒")
self.log(f"旋转方向: {'正向' if self.direction > 0 else '反向'}")
self.log(f"旋转状态: {'运行中' if self.is_rotating else '已暂停'}")
self.log("=== 信息结束 ===")
else:
self.log("无法获取旋转信息")

215
scripts/Rotate_H_Script.py Normal file
View File

@ -0,0 +1,215 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
旋转脚本 - 让对象持续旋转
"""
from core.script_system import ScriptBase
class RotatorScript(ScriptBase):
"""旋转脚本类"""
def __init__(self):
super().__init__()
self.rotation_speed_y = 30.0 # Y轴旋转速度 (度/秒)
self.max_angle = 30.0 # 最大旋转角度(相对于初始角度)
self.direction = 1
self.current_offset = 0.0 # 当前相对于初始角度的偏移
self.initial_angle = None # 模型的初始角度
self.is_rotating = True # 是否正在旋转
# 机器人式停顿参数
self.pause_duration = 0.5 # 停顿时间(秒)
self.current_pause_time = 0.0 # 当前停顿计时
self.is_paused = False # 是否正在停顿
self.robot_mode = True # 是否启用机器人模式
def start(self):
"""脚本开始时调用"""
self.log("机器人旋转脚本启动!")
self.log(f"旋转速度: {self.rotation_speed_y}度/秒")
self.log(f"最大旋转角度: ±{self.max_angle}")
self.log(f"机器人模式: {'开启' if self.robot_mode else '关闭'}")
if self.robot_mode:
self.log(f"停顿时间: {self.pause_duration}")
# 记录模型的初始角度
if self.gameObject:
initial_hpr = self.gameObject.getHpr()
self.initial_angle = initial_hpr.getY() # 记录Y轴的初始角度Pitch
self.log(f"模型初始角度: {self.initial_angle}")
self.log(f"旋转范围: {self.initial_angle - self.max_angle}° 到 {self.initial_angle + self.max_angle}°")
else:
self.log("⚠️ 无法获取游戏对象使用默认初始角度0")
self.initial_angle = 0.0
def update(self, dt):
"""每帧更新 - 机器人式旋转"""
if not self.is_rotating or self.initial_angle is None:
return
# 如果正在停顿中
if self.is_paused:
self.current_pause_time += dt
if self.current_pause_time >= self.pause_duration:
# 停顿结束,继续旋转
self.is_paused = False
self.current_pause_time = 0.0
self.log(f"停顿结束,继续旋转,方向: {'正向' if self.direction > 0 else '反向'}")
return
# 计算角度变化量
delta_angle = self.rotation_speed_y * dt * self.direction
self.current_offset += delta_angle
# 检查是否到达边界
reached_boundary = False
if self.current_offset > self.max_angle:
self.current_offset = self.max_angle
self.direction *= -1
reached_boundary = True
elif self.current_offset < -self.max_angle:
self.current_offset = -self.max_angle
self.direction *= -1
reached_boundary = True
# 如果到达边界且启用机器人模式,开始停顿
if reached_boundary and self.robot_mode:
self.is_paused = True
self.current_pause_time = 0.0
self.log(f"到达边界 ({self.current_offset}°),开始停顿 {self.pause_duration}")
# 计算最终角度(初始角度 + 偏移量)
final_angle = self.initial_angle + self.current_offset
# 设置新的旋转只改变Y轴保持其他不变
current_hpr = self.gameObject.getHpr()
self.gameObject.setHpr(final_angle, current_hpr.getY(), current_hpr.getZ())
# if not self.is_rotating:
# return
#
# # 获取当前旋转并应用增量
# current_hpr = self.gameObject.getHpr()
# new_r = current_hpr.getZ() + self.rotation_speed_y * dt
# self.gameObject.setHpr(current_hpr.getX(), current_hpr.getY(), new_r)
def on_destroy(self):
"""脚本销毁时调用"""
self.log("机器人旋转脚本停止")
# ==================== 机器人模式控制方法 ====================
def set_robot_mode(self, enabled=True):
"""
启用或禁用机器人模式
Args:
enabled: 是否启用机器人模式
"""
self.robot_mode = enabled
self.log(f"机器人模式: {'开启' if enabled else '关闭'}")
if not enabled:
self.is_paused = False # 如果禁用机器人模式,立即结束停顿
def set_pause_duration(self, duration):
"""
设置停顿时间
Args:
duration: 停顿时间
"""
self.pause_duration = max(0.1, duration) # 最小0.1秒
self.log(f"停顿时间已设置为: {self.pause_duration}")
def set_max_angle(self, new_max_angle):
"""
设置新的最大旋转角度
Args:
new_max_angle: 新的最大角度值
"""
self.max_angle = new_max_angle
self.log(f"最大旋转角度已设置为: ±{self.max_angle}")
if self.initial_angle is not None:
self.log(f"新的旋转范围: {self.initial_angle - self.max_angle}° 到 {self.initial_angle + self.max_angle}°")
def set_rotation_speed(self, new_speed):
"""
设置新的旋转速度
Args:
new_speed: 新的旋转速度/
"""
self.rotation_speed_y = new_speed
self.log(f"旋转速度已设置为: {self.rotation_speed_y}度/秒")
def pause_rotation(self):
"""暂停旋转"""
self.is_rotating = False
self.log("旋转已暂停")
def resume_rotation(self):
"""恢复旋转"""
self.is_rotating = True
self.is_paused = False # 同时结束停顿状态
self.log("旋转已恢复")
def reset_to_initial_angle(self):
"""重置到初始角度"""
if self.initial_angle is not None and self.gameObject:
current_hpr = self.gameObject.getHpr()
self.gameObject.setHpr(current_hpr.getX(), self.initial_angle, current_hpr.getZ())
self.current_offset = 0.0
self.direction = 1
self.is_paused = False
self.current_pause_time = 0.0
self.log(f"已重置到初始角度: {self.initial_angle}")
def get_current_info(self):
"""获取当前旋转信息"""
if self.gameObject and self.initial_angle is not None:
current_hpr = self.gameObject.getHpr()
current_angle = current_hpr.getY()
self.log("=== 机器人旋转信息 ===")
self.log(f"初始角度: {self.initial_angle}")
self.log(f"当前角度: {current_angle}")
self.log(f"偏移量: {self.current_offset}")
self.log(f"最大角度: ±{self.max_angle}")
self.log(f"旋转速度: {self.rotation_speed_y}度/秒")
self.log(f"旋转方向: {'正向' if self.direction > 0 else '反向'}")
self.log(f"旋转状态: {'运行中' if self.is_rotating else '已暂停'}")
self.log(f"机器人模式: {'开启' if self.robot_mode else '关闭'}")
if self.robot_mode:
self.log(f"停顿时间: {self.pause_duration}")
self.log(f"当前状态: {'停顿中' if self.is_paused else '旋转中'}")
if self.is_paused:
remaining_time = self.pause_duration - self.current_pause_time
self.log(f"剩余停顿时间: {remaining_time:.1f}")
self.log("=== 信息结束 ===")
else:
self.log("无法获取旋转信息")
# ==================== 预设配置方法 ====================
def set_slow_robot_mode(self):
"""预设:慢速机器人模式"""
self.set_rotation_speed(15.0)
self.set_pause_duration(1.0)
self.set_robot_mode(True)
self.log("已设置为慢速机器人模式")
def set_fast_robot_mode(self):
"""预设:快速机器人模式"""
self.set_rotation_speed(45.0)
self.set_pause_duration(0.3)
self.set_robot_mode(True)
self.log("已设置为快速机器人模式")
def set_smooth_mode(self):
"""预设:平滑模式(非机器人)"""
self.set_robot_mode(False)
self.set_rotation_speed(30.0)
self.log("已设置为平滑旋转模式")

740
scripts/Rotate_P.py Normal file
View File

@ -0,0 +1,740 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Rotate_P - 自定义脚本支持动态挂载其他脚本到模型或子物体
"""
from core.script_system import ScriptBase
class RotateP(ScriptBase):
"""自定义脚本类 - 支持脚本挂载功能"""
def __init__(self):
super().__init__()
# 在这里初始化您的变量
self.mounted_scripts = {} # 存储已挂载的脚本 {object_name: [script_names]}
self.world = None # 世界对象引用
def start(self):
"""脚本开始时调用 - 当此脚本被挂载时自动执行挂载其他脚本"""
self.log("Rotate_P 脚本开始运行!")
# 获取世界对象引用
if hasattr(self.script_manager, 'world'):
self.world = self.script_manager.world
self.log("✓ 获取世界对象引用成功")
# 当此脚本被挂载时,自动执行脚本挂载操作
self.auto_mount_scripts_on_start()
else:
self.log("⚠️ 无法获取世界对象引用")
def auto_mount_scripts_on_start(self):
"""
当Rotate_P脚本被挂载时自动执行的脚本挂载操作
在这里定义要自动挂载的脚本和目标对象
"""
self.log("=== 开始自动挂载脚本 ===")
# 获取当前脚本所挂载的对象
current_object = self.gameObject
current_object_name = current_object.getName() if current_object else "Unknown"
self.log(f"当前脚本挂载在对象: {current_object_name}")
# 显示当前对象信息
self.get_current_object_info()
# ==================== 配置区域 ====================
# 请根据您的需求选择以下配置之一,或自定义配置
# 配置1机器人式旋转演示有停顿
self.setup_rotation_demo(max_angle=45.0, rotation_speed=25.0, robot_mode=True, pause_duration=0.8)
# 配置2工业机器人演示取消注释以使用
# self.setup_industrial_robot_demo()
# 配置3伺服电机演示取消注释以使用
# self.setup_servo_motor_demo()
# 配置4机器人手臂演示取消注释以使用
# self.setup_robot_arm_demo()
# 配置5为指定对象挂载机器人旋转脚本取消注释并修改
# self.mount_robot_rotator("arm_joint", max_angle=60.0, rotation_speed=15.0, pause_duration=1.0)
# self.mount_robot_rotator("gripper", max_angle=30.0, rotation_speed=20.0, pause_duration=0.5)
# 配置6平滑旋转无停顿取消注释以使用
# self.mount_smooth_rotator("wheel", max_angle=180.0, rotation_speed=90.0)
# 配置5自定义挂载配置取消注释并修改
# auto_mount_configs = [
# ("your_model_name", "RotatorScript"), # 将RotatorScript挂载到指定模型
# ("child_object_name", "MoverScript"), # 将MoverScript挂载到子物体
# ("another_object", "ScalerScript"), # 将ScalerScript挂载到另一个对象
# ]
# if auto_mount_configs:
# success_count = self.batch_mount_scripts(auto_mount_configs)
# self.log(f"✓ 自定义挂载完成,成功挂载 {success_count} 个脚本")
# ==================== 配置区域结束 ====================
# 显示使用说明
self.show_usage_examples()
# 列出已挂载的脚本
self.list_mounted_scripts()
def mount_script_to_current_object(self, script_name):
"""
将脚本挂载到当前对象即Rotate_P脚本所在的对象
Args:
script_name: 要挂载的脚本名称
Returns:
bool: 挂载是否成功
"""
if not self.gameObject:
self.log("错误: 无法获取当前对象")
return False
return self.mount_script_to_object(self.gameObject, script_name)
def auto_mount_to_all_children(self, script_name):
"""
自动将脚本挂载到当前对象的所有子物体
Args:
script_name: 要挂载的脚本名称
Returns:
int: 成功挂载的子物体数量
"""
if not self.gameObject:
self.log("错误: 无法获取当前对象")
return 0
# 获取所有子对象
children = self.gameObject.getChildren()
success_count = 0
self.log(f"开始为当前对象的 {len(children)} 个子物体挂载脚本 '{script_name}'")
for child in children:
if self.mount_script_to_object(child, script_name):
success_count += 1
self.log(f"✓ 成功为 {success_count}/{len(children)} 个子物体挂载脚本 '{script_name}'")
return success_count
def update(self, dt):
"""每帧更新"""
# 在这里编写更新逻辑
pass
def on_destroy(self):
"""脚本销毁时调用"""
self.log("脚本被销毁")
# ==================== 脚本挂载功能 ====================
def mount_script_to_object(self, target_object, script_name, script_config=None):
"""
将脚本挂载到指定对象上
Args:
target_object: 目标对象Panda3D NodePath
script_name: 要挂载的脚本名称字符串
script_config: 脚本配置参数字典可选
Returns:
bool: 挂载是否成功
"""
if not self.world:
self.log("错误: 世界对象引用不可用")
return False
try:
# 使用世界对象的addScript方法挂载脚本
success = self.world.addScript(target_object, script_name)
if success:
# 记录挂载信息
obj_name = target_object.getName()
if obj_name not in self.mounted_scripts:
self.mounted_scripts[obj_name] = []
self.mounted_scripts[obj_name].append(script_name)
# 如果提供了配置参数,尝试配置脚本
if script_config and script_name == "RotatorScript":
self.configure_rotator_script(target_object, script_config)
self.log(f"✓ 成功将脚本 '{script_name}' 挂载到对象 '{obj_name}'")
return True
else:
self.log(f"✗ 挂载脚本 '{script_name}' 失败")
return False
except Exception as e:
self.log(f"✗ 挂载脚本时出错: {str(e)}")
return False
def configure_rotator_script(self, target_object, config):
"""
配置已挂载的RotatorScript脚本
Args:
target_object: 目标对象
config: 配置字典可包含 max_angle, rotation_speed, robot_mode, pause_duration
"""
try:
# 获取对象上的RotatorScript
scripts = self.world.getScripts(target_object)
for script_component in scripts:
if script_component.script_name == "RotatorScript":
script_instance = script_component.script_instance
# 配置最大角度
if 'max_angle' in config:
script_instance.set_max_angle(config['max_angle'])
# 配置旋转速度
if 'rotation_speed' in config:
script_instance.set_rotation_speed(config['rotation_speed'])
# 配置机器人模式
if 'robot_mode' in config:
script_instance.set_robot_mode(config['robot_mode'])
# 配置停顿时间
if 'pause_duration' in config:
script_instance.set_pause_duration(config['pause_duration'])
# 预设模式
if 'preset_mode' in config:
preset = config['preset_mode']
if preset == 'slow_robot':
script_instance.set_slow_robot_mode()
elif preset == 'fast_robot':
script_instance.set_fast_robot_mode()
elif preset == 'smooth':
script_instance.set_smooth_mode()
self.log(f"✓ 已配置 RotatorScript: {config}")
break
except Exception as e:
self.log(f"✗ 配置 RotatorScript 时出错: {str(e)}")
def unmount_script_from_object(self, target_object, script_name):
"""
从指定对象卸载脚本
Args:
target_object: 目标对象Panda3D NodePath
script_name: 要卸载的脚本名称字符串
Returns:
bool: 卸载是否成功
"""
if not self.world:
self.log("错误: 世界对象引用不可用")
return False
try:
# 使用世界对象的removeScript方法卸载脚本
success = self.world.removeScript(target_object, script_name)
if success:
# 更新挂载记录
obj_name = target_object.getName()
if obj_name in self.mounted_scripts and script_name in self.mounted_scripts[obj_name]:
self.mounted_scripts[obj_name].remove(script_name)
if not self.mounted_scripts[obj_name]: # 如果列表为空,删除键
del self.mounted_scripts[obj_name]
self.log(f"✓ 成功从对象 '{obj_name}' 卸载脚本 '{script_name}'")
return True
else:
self.log(f"✗ 卸载脚本 '{script_name}' 失败")
return False
except Exception as e:
self.log(f"✗ 卸载脚本时出错: {str(e)}")
return False
def mount_script_by_name(self, object_name, script_name):
"""
通过对象名称挂载脚本
Args:
object_name: 目标对象名称字符串
script_name: 要挂载的脚本名称字符串
Returns:
bool: 挂载是否成功
"""
if not self.world:
self.log("错误: 世界对象引用不可用")
return False
# 查找对象
target_object = self.find_object_by_name(object_name)
if not target_object:
self.log(f"✗ 未找到名为 '{object_name}' 的对象")
return False
return self.mount_script_to_object(target_object, script_name)
def find_object_by_name(self, object_name):
"""
通过名称查找对象
Args:
object_name: 对象名称字符串
Returns:
NodePath: 找到的对象如果未找到则返回None
"""
if not self.world:
return None
# 首先尝试使用世界的查找方法
if hasattr(self.world, 'findModelByName'):
obj = self.world.findModelByName(object_name)
if obj:
return obj
# 如果没有找到尝试在render下搜索
if hasattr(self.world, 'render'):
return self.world.render.find(f"**/{object_name}")
return None
def get_available_scripts(self):
"""
获取所有可用的脚本列表
Returns:
list: 可用脚本名称列表
"""
if not self.world:
self.log("错误: 世界对象引用不可用")
return []
try:
return self.world.getAvailableScripts()
except Exception as e:
self.log(f"✗ 获取可用脚本列表时出错: {str(e)}")
return []
def get_scripts_on_object(self, target_object):
"""
获取对象上已挂载的脚本
Args:
target_object: 目标对象Panda3D NodePath
Returns:
list: 脚本组件列表
"""
if not self.world:
self.log("错误: 世界对象引用不可用")
return []
try:
return self.world.getScripts(target_object)
except Exception as e:
self.log(f"✗ 获取对象脚本时出错: {str(e)}")
return []
def list_mounted_scripts(self):
"""
列出所有已挂载的脚本信息
"""
self.log("=== 已挂载脚本列表 ===")
if not self.mounted_scripts:
self.log("暂无已挂载的脚本")
return
for obj_name, script_names in self.mounted_scripts.items():
self.log(f"对象 '{obj_name}': {', '.join(script_names)}")
# ==================== 便捷方法 ====================
def mount_rotator_script(self, object_name):
"""
便捷方法挂载旋转脚本到指定对象
Args:
object_name: 目标对象名称
Returns:
bool: 挂载是否成功
"""
return self.mount_script_by_name(object_name, "RotatorScript")
def mount_mover_script(self, object_name):
"""
便捷方法挂载移动脚本到指定对象
Args:
object_name: 目标对象名称
Returns:
bool: 挂载是否成功
"""
return self.mount_script_by_name(object_name, "MoverScript")
def mount_scaler_script(self, object_name):
"""
便捷方法挂载缩放脚本到指定对象
Args:
object_name: 目标对象名称
Returns:
bool: 挂载是否成功
"""
return self.mount_script_by_name(object_name, "ScalerScript")
# ==================== 示例使用方法 ====================
def example_mount_scripts(self):
"""
示例如何使用脚本挂载功能
在start()方法中调用此方法来演示脚本挂载
"""
self.log("=== 脚本挂载示例 ===")
# 示例1挂载旋转脚本到特定对象
# self.mount_rotator_script("your_model_name")
# 示例2挂载多个脚本到同一个对象
# self.mount_script_by_name("your_model_name", "RotatorScript")
# self.mount_script_by_name("your_model_name", "MoverScript")
# 示例3查找并挂载脚本到子物体
# child_object = self.find_object_by_name("child_object_name")
# if child_object:
# self.mount_script_to_object(child_object, "RotatorScript")
# 示例4列出可用脚本
available_scripts = self.get_available_scripts()
self.log(f"可用脚本: {available_scripts}")
# 示例5列出已挂载的脚本
self.list_mounted_scripts()
def auto_mount_to_children(self, parent_object_name, script_name):
"""
自动将脚本挂载到指定父对象的所有子物体
Args:
parent_object_name: 父对象名称
script_name: 要挂载的脚本名称
Returns:
int: 成功挂载的子物体数量
"""
if not self.world:
self.log("错误: 世界对象引用不可用")
return 0
# 查找父对象
parent_object = self.find_object_by_name(parent_object_name)
if not parent_object:
self.log(f"✗ 未找到父对象 '{parent_object_name}'")
return 0
# 获取所有子对象
children = parent_object.getChildren()
success_count = 0
for child in children:
if self.mount_script_to_object(child, script_name):
success_count += 1
self.log(f"✓ 成功为 {success_count}/{len(children)} 个子物体挂载脚本 '{script_name}'")
return success_count
def batch_mount_scripts(self, object_script_pairs):
"""
批量挂载脚本
Args:
object_script_pairs: 对象-脚本对列表格式[(object_name, script_name), ...]
Returns:
int: 成功挂载的数量
"""
success_count = 0
for object_name, script_name in object_script_pairs:
if self.mount_script_by_name(object_name, script_name):
success_count += 1
self.log(f"✓ 批量挂载完成: {success_count}/{len(object_script_pairs)} 个脚本挂载成功")
return success_count
# ==================== 预设配置方法 ====================
def setup_rotation_demo(self, max_angle=30.0, rotation_speed=30.0, robot_mode=True, pause_duration=0.5):
"""
预设配置旋转演示
为当前对象及其子物体挂载旋转脚本
Args:
max_angle: 最大旋转角度相对于初始角度
rotation_speed: 旋转速度/
robot_mode: 是否启用机器人模式
pause_duration: 停顿时间
"""
self.log("=== 设置旋转演示 ===")
# 配置参数
config = {
'max_angle': max_angle,
'rotation_speed': rotation_speed,
'robot_mode': robot_mode,
'pause_duration': pause_duration
}
# 为当前对象挂载旋转脚本
self.mount_script_to_current_object_with_config("RotatorScript", config)
# 为所有子物体挂载旋转脚本
self.auto_mount_to_all_children_with_config("RotatorScript", config)
def setup_animation_combo(self):
"""
预设配置动画组合
为对象挂载多种动画脚本
"""
self.log("=== 设置动画组合 ===")
# 为当前对象挂载多个脚本
scripts_to_mount = ["RotatorScript", "MoverScript", "ScalerScript"]
for script_name in scripts_to_mount:
self.mount_script_to_current_object(script_name)
def setup_child_animation(self, script_name="RotatorScript"):
"""
预设配置子物体动画
只为子物体挂载脚本不影响父对象
Args:
script_name: 要挂载的脚本名称默认为RotatorScript
"""
self.log(f"=== 设置子物体动画 ({script_name}) ===")
self.auto_mount_to_all_children(script_name)
def setup_specific_objects(self, object_names, script_name="RotatorScript"):
"""
预设配置为指定的对象列表挂载脚本
Args:
object_names: 对象名称列表
script_name: 要挂载的脚本名称
"""
self.log(f"=== 为指定对象挂载脚本 ({script_name}) ===")
for obj_name in object_names:
self.mount_script_by_name(obj_name, script_name)
def setup_robot_arm_demo(self):
"""
预设配置机器人手臂演示
为不同关节设置不同的机器人旋转参数
"""
self.log("=== 设置机器人手臂演示 ===")
# 为当前对象设置慢速机器人模式
config = {'preset_mode': 'slow_robot'}
self.mount_script_to_current_object_with_config("RotatorScript", config)
# 为子物体设置快速机器人模式
config = {'preset_mode': 'fast_robot'}
self.auto_mount_to_all_children_with_config("RotatorScript", config)
def setup_industrial_robot_demo(self):
"""
预设配置工业机器人演示
模拟工业机器人的精确停顿动作
"""
self.log("=== 设置工业机器人演示 ===")
# 工业机器人配置:慢速、长停顿
config = {
'max_angle': 45.0,
'rotation_speed': 15.0,
'robot_mode': True,
'pause_duration': 1.2
}
self.mount_script_to_current_object_with_config("RotatorScript", config)
self.auto_mount_to_all_children_with_config("RotatorScript", config)
def setup_servo_motor_demo(self):
"""
预设配置伺服电机演示
模拟伺服电机的快速精确定位
"""
self.log("=== 设置伺服电机演示 ===")
# 伺服电机配置:快速、短停顿
config = {
'max_angle': 60.0,
'rotation_speed': 50.0,
'robot_mode': True,
'pause_duration': 0.2
}
self.mount_script_to_current_object_with_config("RotatorScript", config)
self.auto_mount_to_all_children_with_config("RotatorScript", config)
# ==================== 使用示例和说明 ====================
def show_usage_examples(self):
"""
显示使用示例和说明
"""
self.log("=== Rotate_P 脚本使用说明 ===")
self.log("此脚本被挂载时会自动执行脚本挂载操作")
self.log("")
self.log("可用的预设配置方法:")
self.log("1. setup_rotation_demo() - 为当前对象和子物体挂载旋转脚本")
self.log("2. setup_animation_combo() - 为当前对象挂载多种动画脚本")
self.log("3. setup_child_animation() - 只为子物体挂载脚本")
self.log("4. setup_specific_objects() - 为指定对象挂载脚本")
self.log("")
self.log("修改 auto_mount_scripts_on_start() 方法来自定义自动挂载行为")
self.log("=== 使用说明结束 ===")
def get_current_object_info(self):
"""
获取当前对象的详细信息
"""
if not self.gameObject:
self.log("错误: 无法获取当前对象")
return
obj_name = self.gameObject.getName()
children = self.gameObject.getChildren()
child_names = [child.getName() for child in children]
self.log("=== 当前对象信息 ===")
self.log(f"对象名称: {obj_name}")
self.log(f"子物体数量: {len(children)}")
if child_names:
self.log(f"子物体名称: {', '.join(child_names)}")
else:
self.log("无子物体")
self.log("=== 对象信息结束 ===")
# ==================== 带配置的便捷方法 ====================
def mount_script_to_current_object_with_config(self, script_name, config=None):
"""
将脚本挂载到当前对象并配置参数
Args:
script_name: 脚本名称
config: 配置参数字典
Returns:
bool: 挂载是否成功
"""
if not self.gameObject:
self.log("错误: 无法获取当前对象")
return False
return self.mount_script_to_object(self.gameObject, script_name, config)
def auto_mount_to_all_children_with_config(self, script_name, config=None):
"""
自动将脚本挂载到当前对象的所有子物体并配置参数
Args:
script_name: 要挂载的脚本名称
config: 配置参数字典
Returns:
int: 成功挂载的子物体数量
"""
if not self.gameObject:
self.log("错误: 无法获取当前对象")
return 0
# 获取所有子对象
children = self.gameObject.getChildren()
success_count = 0
self.log(f"开始为当前对象的 {len(children)} 个子物体挂载脚本 '{script_name}'")
if config:
self.log(f"使用配置: {config}")
for child in children:
if self.mount_script_to_object(child, script_name, config):
success_count += 1
self.log(f"✓ 成功为 {success_count}/{len(children)} 个子物体挂载脚本 '{script_name}'")
return success_count
def mount_rotator_with_custom_angle(self, object_name, max_angle=45.0, rotation_speed=20.0, robot_mode=True, pause_duration=0.5):
"""
便捷方法挂载自定义角度的旋转脚本
Args:
object_name: 目标对象名称
max_angle: 最大旋转角度
rotation_speed: 旋转速度
robot_mode: 是否启用机器人模式
pause_duration: 停顿时间
Returns:
bool: 挂载是否成功
"""
config = {
'max_angle': max_angle,
'rotation_speed': rotation_speed,
'robot_mode': robot_mode,
'pause_duration': pause_duration
}
target_object = self.find_object_by_name(object_name)
if not target_object:
self.log(f"✗ 未找到名为 '{object_name}' 的对象")
return False
return self.mount_script_to_object(target_object, "RotatorScript", config)
def mount_robot_rotator(self, object_name, max_angle=30.0, rotation_speed=20.0, pause_duration=0.8):
"""
便捷方法挂载机器人式旋转脚本
Args:
object_name: 目标对象名称
max_angle: 最大旋转角度
rotation_speed: 旋转速度
pause_duration: 停顿时间
Returns:
bool: 挂载是否成功
"""
return self.mount_rotator_with_custom_angle(object_name, max_angle, rotation_speed, True, pause_duration)
def mount_smooth_rotator(self, object_name, max_angle=45.0, rotation_speed=30.0):
"""
便捷方法挂载平滑旋转脚本无停顿
Args:
object_name: 目标对象名称
max_angle: 最大旋转角度
rotation_speed: 旋转速度
Returns:
bool: 挂载是否成功
"""
return self.mount_rotator_with_custom_angle(object_name, max_angle, rotation_speed, False, 0.0)

215
scripts/Rotate_P_Script.py Normal file
View File

@ -0,0 +1,215 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
旋转脚本 - 让对象持续旋转
"""
from core.script_system import ScriptBase
class RotatorScript(ScriptBase):
"""旋转脚本类"""
def __init__(self):
super().__init__()
self.rotation_speed_y = 30.0 # Y轴旋转速度 (度/秒)
self.max_angle = 25.0 # 最大旋转角度(相对于初始角度)
self.direction = 1
self.current_offset = 0.0 # 当前相对于初始角度的偏移
self.initial_angle = None # 模型的初始角度
self.is_rotating = True # 是否正在旋转
# 机器人式停顿参数
self.pause_duration = 0.5 # 停顿时间(秒)
self.current_pause_time = 0.0 # 当前停顿计时
self.is_paused = False # 是否正在停顿
self.robot_mode = True # 是否启用机器人模式
def start(self):
"""脚本开始时调用"""
self.log("机器人旋转脚本启动!")
self.log(f"旋转速度: {self.rotation_speed_y}度/秒")
self.log(f"最大旋转角度: ±{self.max_angle}")
self.log(f"机器人模式: {'开启' if self.robot_mode else '关闭'}")
if self.robot_mode:
self.log(f"停顿时间: {self.pause_duration}")
# 记录模型的初始角度
if self.gameObject:
initial_hpr = self.gameObject.getHpr()
self.initial_angle = initial_hpr.getY() # 记录Y轴的初始角度Pitch
self.log(f"模型初始角度: {self.initial_angle}")
self.log(f"旋转范围: {self.initial_angle - self.max_angle}° 到 {self.initial_angle + self.max_angle}°")
else:
self.log("⚠️ 无法获取游戏对象使用默认初始角度0")
self.initial_angle = 0.0
def update(self, dt):
"""每帧更新 - 机器人式旋转"""
if not self.is_rotating or self.initial_angle is None:
return
# 如果正在停顿中
if self.is_paused:
self.current_pause_time += dt
if self.current_pause_time >= self.pause_duration:
# 停顿结束,继续旋转
self.is_paused = False
self.current_pause_time = 0.0
self.log(f"停顿结束,继续旋转,方向: {'正向' if self.direction > 0 else '反向'}")
return
# 计算角度变化量
delta_angle = self.rotation_speed_y * dt * self.direction
self.current_offset += delta_angle
# 检查是否到达边界
reached_boundary = False
if self.current_offset > self.max_angle:
self.current_offset = self.max_angle
self.direction *= -1
reached_boundary = True
elif self.current_offset < -self.max_angle:
self.current_offset = -self.max_angle
self.direction *= -1
reached_boundary = True
# 如果到达边界且启用机器人模式,开始停顿
if reached_boundary and self.robot_mode:
self.is_paused = True
self.current_pause_time = 0.0
self.log(f"到达边界 ({self.current_offset}°),开始停顿 {self.pause_duration}")
# 计算最终角度(初始角度 + 偏移量)
final_angle = self.initial_angle + self.current_offset
# 设置新的旋转只改变Y轴保持其他不变
current_hpr = self.gameObject.getHpr()
self.gameObject.setHpr(current_hpr.getX(), final_angle, current_hpr.getZ())
# if not self.is_rotating:
# return
#
# # 获取当前旋转并应用增量
# current_hpr = self.gameObject.getHpr()
# new_r = current_hpr.getZ() + self.rotation_speed_y * dt
# self.gameObject.setHpr(current_hpr.getX(), current_hpr.getY(), new_r)
def on_destroy(self):
"""脚本销毁时调用"""
self.log("机器人旋转脚本停止")
# ==================== 机器人模式控制方法 ====================
def set_robot_mode(self, enabled=True):
"""
启用或禁用机器人模式
Args:
enabled: 是否启用机器人模式
"""
self.robot_mode = enabled
self.log(f"机器人模式: {'开启' if enabled else '关闭'}")
if not enabled:
self.is_paused = False # 如果禁用机器人模式,立即结束停顿
def set_pause_duration(self, duration):
"""
设置停顿时间
Args:
duration: 停顿时间
"""
self.pause_duration = max(0.1, duration) # 最小0.1秒
self.log(f"停顿时间已设置为: {self.pause_duration}")
def set_max_angle(self, new_max_angle):
"""
设置新的最大旋转角度
Args:
new_max_angle: 新的最大角度值
"""
self.max_angle = new_max_angle
self.log(f"最大旋转角度已设置为: ±{self.max_angle}")
if self.initial_angle is not None:
self.log(f"新的旋转范围: {self.initial_angle - self.max_angle}° 到 {self.initial_angle + self.max_angle}°")
def set_rotation_speed(self, new_speed):
"""
设置新的旋转速度
Args:
new_speed: 新的旋转速度/
"""
self.rotation_speed_y = new_speed
self.log(f"旋转速度已设置为: {self.rotation_speed_y}度/秒")
def pause_rotation(self):
"""暂停旋转"""
self.is_rotating = False
self.log("旋转已暂停")
def resume_rotation(self):
"""恢复旋转"""
self.is_rotating = True
self.is_paused = False # 同时结束停顿状态
self.log("旋转已恢复")
def reset_to_initial_angle(self):
"""重置到初始角度"""
if self.initial_angle is not None and self.gameObject:
current_hpr = self.gameObject.getHpr()
self.gameObject.setHpr(current_hpr.getX(), self.initial_angle, current_hpr.getZ())
self.current_offset = 0.0
self.direction = 1
self.is_paused = False
self.current_pause_time = 0.0
self.log(f"已重置到初始角度: {self.initial_angle}")
def get_current_info(self):
"""获取当前旋转信息"""
if self.gameObject and self.initial_angle is not None:
current_hpr = self.gameObject.getHpr()
current_angle = current_hpr.getY()
self.log("=== 机器人旋转信息 ===")
self.log(f"初始角度: {self.initial_angle}")
self.log(f"当前角度: {current_angle}")
self.log(f"偏移量: {self.current_offset}")
self.log(f"最大角度: ±{self.max_angle}")
self.log(f"旋转速度: {self.rotation_speed_y}度/秒")
self.log(f"旋转方向: {'正向' if self.direction > 0 else '反向'}")
self.log(f"旋转状态: {'运行中' if self.is_rotating else '已暂停'}")
self.log(f"机器人模式: {'开启' if self.robot_mode else '关闭'}")
if self.robot_mode:
self.log(f"停顿时间: {self.pause_duration}")
self.log(f"当前状态: {'停顿中' if self.is_paused else '旋转中'}")
if self.is_paused:
remaining_time = self.pause_duration - self.current_pause_time
self.log(f"剩余停顿时间: {remaining_time:.1f}")
self.log("=== 信息结束 ===")
else:
self.log("无法获取旋转信息")
# ==================== 预设配置方法 ====================
def set_slow_robot_mode(self):
"""预设:慢速机器人模式"""
self.set_rotation_speed(15.0)
self.set_pause_duration(1.0)
self.set_robot_mode(True)
self.log("已设置为慢速机器人模式")
def set_fast_robot_mode(self):
"""预设:快速机器人模式"""
self.set_rotation_speed(45.0)
self.set_pause_duration(0.3)
self.set_robot_mode(True)
self.log("已设置为快速机器人模式")
def set_smooth_mode(self):
"""预设:平滑模式(非机器人)"""
self.set_robot_mode(False)
self.set_rotation_speed(30.0)
self.log("已设置为平滑旋转模式")

View File

@ -0,0 +1,56 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
旋转脚本 - 让对象持续旋转
"""
from core.script_system import ScriptBase
class RotatorScript(ScriptBase):
"""旋转脚本类"""
def __init__(self):
super().__init__()
self.rotation_speed_y = 30.0 # Y轴旋转速度 (度/秒)
self.max_angle = 15.0
self.direction = 1
self.current_angle = 0.0
self.is_rotating = True # 是否正在旋转
def start(self):
"""脚本开始时调用"""
self.log("旋转脚本启动!")
self.log(f"旋转速度: {self.rotation_speed_y}度/秒")
def update(self, dt):
"""每帧更新"""
delta_angle = self.rotation_speed_y * dt * self.direction
self.current_angle += delta_angle
# 如果超出角度范围,则反向
if self.current_angle > self.max_angle:
self.current_angle = self.max_angle
self.direction *= -1
elif self.current_angle < -self.max_angle:
self.current_angle = -self.max_angle
self.direction *= -1
# 设置新的旋转只改变Z轴保持其他不变
base_hpr = self.gameObject.getHpr()
new_r = self.current_angle
self.gameObject.setHpr(base_hpr.getX(), base_hpr.getY(), new_r)
# if not self.is_rotating:
# return
#
# # 获取当前旋转并应用增量
# current_hpr = self.gameObject.getHpr()
# new_r = current_hpr.getZ() + self.rotation_speed_y * dt
# self.gameObject.setHpr(current_hpr.getX(), current_hpr.getY(), new_r)
def on_destroy(self):
"""脚本销毁时调用"""
self.log("旋转脚本停止")

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -5,6 +5,7 @@ from typing import Hashable
from PyQt5.QtWidgets import (QLabel, QLineEdit, QDoubleSpinBox, QPushButton, from PyQt5.QtWidgets import (QLabel, QLineEdit, QDoubleSpinBox, QPushButton,
QTreeWidget, QTreeWidgetItem, QMenu,QCheckBox) QTreeWidget, QTreeWidgetItem, QMenu,QCheckBox)
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from deploy_libs.unicodedata import normalize
from panda3d.core import Vec3, Vec4, transpose from panda3d.core import Vec3, Vec4, transpose
@ -175,6 +176,8 @@ class PropertyPanelManager:
zScale.valueChanged.connect(lambda v: model.setScale(model.getScale().getX(), model.getScale().getY(), v)) zScale.valueChanged.connect(lambda v: model.setScale(model.getScale().getX(), model.getScale().getY(), v))
self._propertyLayout.addRow("缩放 Z:", zScale) self._propertyLayout.addRow("缩放 Z:", zScale)
self._addSunAzimuthPanel()
material_title = QLabel("材质属性") material_title = QLabel("材质属性")
material_title.setStyleSheet("color: #FF6B6B;font-weight:bold;font-size:14px;margin-top:10px;") material_title.setStyleSheet("color: #FF6B6B;font-weight:bold;font-size:14px;margin-top:10px;")
self._propertyLayout.addRow(material_title) self._propertyLayout.addRow(material_title)
@ -398,10 +401,10 @@ class PropertyPanelManager:
fovSpinBox.valueChanged.connect(lambda v:self._updateLightFOV(light_object,v)) fovSpinBox.valueChanged.connect(lambda v:self._updateLightFOV(light_object,v))
self._propertyLayout.addRow("视野角度:",fovSpinBox) self._propertyLayout.addRow("视野角度:",fovSpinBox)
shadowCheckBox = QCheckBox() # shadowCheckBox = QCheckBox()
shadowCheckBox.setChecked(light_object.casts_shadows) # shadowCheckBox.setChecked(light_object.casts_shadows)
shadowCheckBox.stateChanged.connect(lambda state:self._updateLightCastsShadows(light_object,state==2)) # shadowCheckBox.stateChanged.connect(lambda state:self._updateLightCastsShadows(light_object,state==2))
self._propertyLayout.addRow("投射阴影:",shadowCheckBox) # self._propertyLayout.addRow("投射阴影:",shadowCheckBox)
current_scale = model.getScale() current_scale = model.getScale()
@ -862,6 +865,10 @@ class PropertyPanelManager:
self._addMaterialPresetPanel(material) self._addMaterialPresetPanel(material)
#self._addColorSpacePanel(material) #self._addColorSpacePanel(material)
# 添加太阳方位角控制面板(只在第一个材质时添加,避免重复)
# if i == 0:
# self._addSunAzimuthPanel()
# 分隔线 # 分隔线
if i < len(materials) - 1: if i < len(materials) - 1:
@ -1260,22 +1267,35 @@ class PropertyPanelManager:
if node and material: if node and material:
print(f"正在为节点 {node.getName()} 应用漫反射贴图") print(f"正在为节点 {node.getName()} 应用漫反射贴图")
# 确保启用PBR效果简单可靠版本 # 检查是否有金属性贴图选择合适的PBR效果
print("🔧 检查金属性贴图并选择合适的PBR效果...")
has_metallic = self._hasMetallicTexture(node)
needs_alpha = self._needsAlphaTesting(node)
if has_metallic:
print("✅ 检测到金属性贴图使用支持金属性的PBR效果")
effect_file = "effects/pbr_with_metallic.yaml"
else:
print("✅ 没有金属性贴图使用默认PBR效果")
effect_file = "effects/default.yaml"
print(f"🔍 透明度测试: {'需要' if needs_alpha else '不需要'}")
try: try:
self.world.render_pipeline.set_effect( self.world.render_pipeline.set_effect(
node, node,
"effects/default.yaml", effect_file,
{ {
"normal_mapping": True, # 启用法线映射支持 "normal_mapping": True, # 启用法线映射支持
"render_gbuffer": True, "render_gbuffer": True,
"alpha_testing": True, # 启用透明度测试支持透明贴图 "alpha_testing": needs_alpha, # 根据是否需要透明度决定
"parallax_mapping": False, "parallax_mapping": False,
"render_shadow": True, "render_shadow": True,
"render_envmap": True "render_envmap": True
}, },
100 100
) )
print("✅ 默认PBR效果已应用支持透明度") print(f"{effect_file} 效果已应用(支持透明度和金属性")
except Exception as e: except Exception as e:
print(f"⚠️ PBR效果应用失败: {e}") print(f"⚠️ PBR效果应用失败: {e}")
@ -1378,23 +1398,35 @@ class PropertyPanelManager:
print("⚠️ 没有漫反射贴图,创建白色纹理确保法线映射正常工作...") print("⚠️ 没有漫反射贴图,创建白色纹理确保法线映射正常工作...")
self._createWhiteDiffuseTexture(node) self._createWhiteDiffuseTexture(node)
# 使用默认PBR效果强制启用法线映射 # 检查是否有金属性贴图选择合适的PBR效果
print("🔧 应用默认PBR效果强制启用法线映射...") print("🔧 检查金属性贴图并选择合适的PBR效果...")
has_metallic = self._hasMetallicTexture(node)
needs_alpha = self._needsAlphaTesting(node)
if has_metallic:
print("✅ 检测到金属性贴图使用支持金属性的PBR效果")
effect_file = "effects/pbr_with_metallic.yaml"
else:
print("✅ 没有金属性贴图使用默认PBR效果")
effect_file = "effects/default.yaml"
print(f"🔍 透明度测试: {'需要' if needs_alpha else '不需要'}")
try: try:
self.world.render_pipeline.set_effect( self.world.render_pipeline.set_effect(
node, node,
"effects/default.yaml", effect_file,
{ {
"normal_mapping": True, # 强制启用法线映射 "normal_mapping": True, # 强制启用法线映射
"render_gbuffer": True, "render_gbuffer": True,
"alpha_testing": False, "alpha_testing": needs_alpha, # 根据是否需要透明度决定
"parallax_mapping": False, "parallax_mapping": False,
"render_shadow": True, "render_shadow": True,
"render_envmap": True "render_envmap": True
}, },
100 100
) )
print("✅ 默认PBR效果已应用(法线映射已启用)") print(f"{effect_file} 效果已应用(法线映射已启用)")
except Exception as e: except Exception as e:
print(f"⚠️ PBR效果应用失败: {e}") print(f"⚠️ PBR效果应用失败: {e}")
@ -3426,4 +3458,453 @@ class PropertyPanelManager:
return False return False
except Exception as e: except Exception as e:
print(f"⚠️ 检查透明度需求失败: {e}") print(f"⚠️ 检查透明度需求失败: {e}")
return True # 出错时默认启用透明度测试,更安全 return True # 出错时默认启用透明度测试,更安全
def _addSunAzimuthPanel(self):
"""添加太阳方位角控制面板"""
try:
from PyQt5.QtWidgets import QLabel, QSlider, QSpinBox, QHBoxLayout, QWidget
from PyQt5.QtCore import Qt
# 添加分隔线
separator = QLabel("" * 30)
separator.setStyleSheet("color: lightgray;")
self._propertyLayout.addRow(separator)
# 标题
title_label = QLabel("☀️ 太阳光照控制")
title_label.setStyleSheet("font-weight: bold; color: #FFA500; font-size: 12px;")
self._propertyLayout.addRow(title_label)
# 太阳方位角控制
azimuth_widget = QWidget()
azimuth_layout = QHBoxLayout(azimuth_widget)
azimuth_layout.setContentsMargins(0, 0, 0, 0)
# 滑块控制 (0-360度)
self.sun_azimuth_slider = QSlider(Qt.Horizontal)
self.sun_azimuth_slider.setMinimum(0)
self.sun_azimuth_slider.setMaximum(360)
self.sun_azimuth_slider.setValue(180) # 默认值180度
self.sun_azimuth_slider.setTickPosition(QSlider.TicksBelow)
self.sun_azimuth_slider.setTickInterval(45) # 每45度一个刻度
# 数值显示框
self.sun_azimuth_spinbox = QSpinBox()
self.sun_azimuth_spinbox.setMinimum(0)
self.sun_azimuth_spinbox.setMaximum(360)
self.sun_azimuth_spinbox.setValue(180)
self.sun_azimuth_spinbox.setSuffix("°")
self.sun_azimuth_spinbox.setFixedWidth(80)
# 连接信号
self.sun_azimuth_slider.valueChanged.connect(self._onSunAzimuthSliderChanged)
self.sun_azimuth_spinbox.valueChanged.connect(self._onSunAzimuthSpinboxChanged)
azimuth_layout.addWidget(self.sun_azimuth_slider)
azimuth_layout.addWidget(self.sun_azimuth_spinbox)
self._propertyLayout.addRow("太阳方位角:", azimuth_widget)
# 太阳高度角控制
altitude_widget = QWidget()
altitude_layout = QHBoxLayout(altitude_widget)
altitude_layout.setContentsMargins(0, 0, 0, 0)
# 滑块控制 (0-90度)
self.sun_altitude_slider = QSlider(Qt.Horizontal)
self.sun_altitude_slider.setMinimum(0)
self.sun_altitude_slider.setMaximum(90)
self.sun_altitude_slider.setValue(45) # 默认值45度
self.sun_altitude_slider.setTickPosition(QSlider.TicksBelow)
self.sun_altitude_slider.setTickInterval(15) # 每15度一个刻度
# 数值显示框
self.sun_altitude_spinbox = QSpinBox()
self.sun_altitude_spinbox.setMinimum(0)
self.sun_altitude_spinbox.setMaximum(90)
self.sun_altitude_spinbox.setValue(45)
self.sun_altitude_spinbox.setSuffix("°")
self.sun_altitude_spinbox.setFixedWidth(80)
# 连接信号
self.sun_altitude_slider.valueChanged.connect(self._onSunAltitudeSliderChanged)
self.sun_altitude_spinbox.valueChanged.connect(self._onSunAltitudeSpinboxChanged)
altitude_layout.addWidget(self.sun_altitude_slider)
altitude_layout.addWidget(self.sun_altitude_spinbox)
self._propertyLayout.addRow("太阳高度角:", altitude_widget)
# 添加预设按钮
preset_widget = QWidget()
preset_layout = QHBoxLayout(preset_widget)
preset_layout.setContentsMargins(0, 0, 0, 0)
# 预设按钮
sunrise_btn = QPushButton("🌅 日出")
sunrise_btn.setFixedWidth(60)
sunrise_btn.clicked.connect(lambda: self._setSunPreset("sunrise"))
noon_btn = QPushButton("☀️ 正午")
noon_btn.setFixedWidth(60)
noon_btn.clicked.connect(lambda: self._setSunPreset("noon"))
sunset_btn = QPushButton("🌇 日落")
sunset_btn.setFixedWidth(60)
sunset_btn.clicked.connect(lambda: self._setSunPreset("sunset"))
midnight_btn = QPushButton("🌙 午夜")
midnight_btn.setFixedWidth(60)
midnight_btn.clicked.connect(lambda: self._setSunPreset("midnight"))
preset_layout.addWidget(sunrise_btn)
preset_layout.addWidget(noon_btn)
preset_layout.addWidget(sunset_btn)
preset_layout.addWidget(midnight_btn)
self._propertyLayout.addRow("快速设置:", preset_widget)
print("✅ 太阳控制面板已添加")
except Exception as e:
print(f"❌ 添加太阳方位角控制面板失败: {e}")
import traceback
traceback.print_exc()
def _onSunAzimuthSliderChanged(self, value):
"""滑块值改变时的回调"""
try:
# 同步到数值框
self.sun_azimuth_spinbox.blockSignals(True)
self.sun_azimuth_spinbox.setValue(value)
self.sun_azimuth_spinbox.blockSignals(False)
# 应用到Day Time Editor
self._applySunAzimuth_new(value)
except Exception as e:
print(f"❌ 滑块值改变处理失败: {e}")
def _onSunAzimuthSpinboxChanged(self, value):
"""数值框值改变时的回调"""
try:
# 同步到滑块
self.sun_azimuth_slider.blockSignals(True)
self.sun_azimuth_slider.setValue(value)
self.sun_azimuth_slider.blockSignals(False)
# 应用到Day Time Editor
self._applySunAzimuth_new(value)
except Exception as e:
print(f"❌ 数值框值改变处理失败: {e}")
def _onSunAltitudeSliderChanged(self, value):
"""太阳高度角滑块值改变时的回调"""
try:
# 同步到数值框
self.sun_altitude_spinbox.blockSignals(True)
self.sun_altitude_spinbox.setValue(value)
self.sun_altitude_spinbox.blockSignals(False)
# 应用太阳高度角
self._applySunAltitude(value)
except Exception as e:
print(f"❌ 太阳高度角滑块值改变处理失败: {e}")
def _onSunAltitudeSpinboxChanged(self, value):
"""太阳高度角数值框值改变时的回调"""
try:
# 同步到滑块
self.sun_altitude_slider.blockSignals(True)
self.sun_altitude_slider.setValue(value)
self.sun_altitude_slider.blockSignals(False)
# 应用太阳高度角
self._applySunAltitude(value)
except Exception as e:
print(f"❌ 太阳高度角数值框值改变处理失败: {e}")
def _setSunPreset(self, preset_name):
"""设置太阳预设位置"""
try:
presets = {
"sunrise": (90, 45), # 东方,低角度
"noon": (180, 90), # 南方,天顶
"sunset": (270, 45), # 西方,低角度
"midnight": (0, 0) # 北方,地平线
}
if preset_name in presets:
azimuth, altitude = presets[preset_name]
# 更新滑块和数值框
self.sun_azimuth_slider.blockSignals(True)
self.sun_azimuth_spinbox.blockSignals(True)
self.sun_altitude_slider.blockSignals(True)
self.sun_altitude_spinbox.blockSignals(True)
self.sun_azimuth_slider.setValue(azimuth)
self.sun_azimuth_spinbox.setValue(azimuth)
self.sun_altitude_slider.setValue(altitude)
self.sun_altitude_spinbox.setValue(altitude)
self.sun_azimuth_slider.blockSignals(False)
self.sun_azimuth_spinbox.blockSignals(False)
self.sun_altitude_slider.blockSignals(False)
self.sun_altitude_spinbox.blockSignals(False)
# 应用设置 - 优先使用Day Time Editor
azimuth_success = self._updateDayTimeEditorSetting("scattering", "sun_azimuth", azimuth)
altitude_success = self._updateDayTimeEditorSetting("scattering", "sun_altitude", altitude)
if azimuth_success and altitude_success:
print(f"✅ 通过Day Time Editor设置太阳预设 {preset_name}: 方位角{azimuth}°, 高度角{altitude}°")
elif hasattr(self.world, 'sun_controller'):
self.world.sun_controller.set_sun_position(azimuth, altitude)
print(f"✅ 通过太阳控制器设置预设 {preset_name}: 方位角{azimuth}°, 高度角{altitude}°")
elif hasattr(self.world, 'setSunPosition'):
self.world.setSunPosition(azimuth, altitude)
print(f"✅ 通过主程序方法设置预设 {preset_name}: 方位角{azimuth}°, 高度角{altitude}°")
else:
# 分别应用方位角和高度角
self._applySunAzimuth_new(azimuth)
self._applySunAltitude(altitude)
print(f"✅ 设置太阳预设 {preset_name}: 方位角{azimuth}°, 高度角{altitude}°")
except Exception as e:
print(f"❌ 设置太阳预设失败: {e}")
import traceback
traceback.print_exc()
def _applySunAzimuth_new(self, azimuth_value):
"""应用太阳方位角 - 直接控制Day Time Editor"""
try:
# 方法1: 直接控制Day Time Editor中的sun_azimuth节点
success = self._updateDayTimeEditorSetting("scattering", "sun_azimuth", azimuth_value)
if success:
print(f"✅ 通过Day Time Editor设置方位角: {azimuth_value}°")
return
# 方法2: 使用我们的太阳控制器作为备用
if hasattr(self.world, 'sun_controller'):
self.world.sun_controller.set_sun_azimuth(azimuth_value)
print(f"✅ 通过太阳控制器设置方位角: {azimuth_value}°")
return
# 方法3: 直接调用主程序的太阳控制方法
if hasattr(self.world, 'setSunAzimuth'):
self.world.setSunAzimuth(azimuth_value)
print(f"✅ 通过主程序方法设置方位角: {azimuth_value}°")
return
print("⚠️ 所有方位角设置方法都不可用")
except Exception as e:
print(f"❌ 应用太阳方位角失败: {e}")
import traceback
traceback.print_exc()
def _applySunAltitude(self, altitude_value):
"""应用太阳高度角 - 直接控制Day Time Editor"""
try:
# 方法1: 直接控制Day Time Editor中的sun_altitude节点
success = self._updateDayTimeEditorSetting("scattering", "sun_altitude", altitude_value)
if success:
print(f"✅ 通过Day Time Editor设置高度角: {altitude_value}°")
return
# 方法2: 使用我们的太阳控制器作为备用
if hasattr(self.world, 'sun_controller'):
self.world.sun_controller.set_sun_altitude(altitude_value)
print(f"✅ 通过太阳控制器设置高度角: {altitude_value}°")
return
# 方法3: 直接调用主程序的太阳控制方法
if hasattr(self.world, 'setSunAltitude'):
self.world.setSunAltitude(altitude_value)
print(f"✅ 通过主程序方法设置高度角: {altitude_value}°")
return
print("⚠️ 所有高度角设置方法都不可用")
except Exception as e:
print(f"❌ 应用太阳高度角失败: {e}")
import traceback
traceback.print_exc()
def _updateDayTimeEditorSetting(self, plugin_name, setting_name, value):
"""直接更新Day Time Editor中的设置节点值"""
try:
# 检查是否有RenderPipeline和插件管理器
if not hasattr(self.world, 'render_pipeline') or not self.world.render_pipeline:
print("⚠️ RenderPipeline未初始化")
return False
pipeline = self.world.render_pipeline
if not hasattr(pipeline, 'plugin_mgr') or not pipeline.plugin_mgr:
print("⚠️ 插件管理器未初始化")
return False
plugin_mgr = pipeline.plugin_mgr
# 检查插件是否存在
if plugin_name not in plugin_mgr.instances:
print(f"⚠️ 插件 '{plugin_name}' 不存在")
return False
# 检查Day Time设置是否存在
if plugin_name not in plugin_mgr.day_settings:
print(f"⚠️ 插件 '{plugin_name}' 没有Day Time设置")
return False
day_settings = plugin_mgr.day_settings[plugin_name]
if setting_name not in day_settings:
print(f"⚠️ 设置 '{setting_name}' 在插件 '{plugin_name}' 中不存在")
return False
setting_handle = day_settings[setting_name]
# 根据设置类型转换值
if setting_name == "sun_azimuth":
# 方位角度数转换为0-1范围
normalized_value = (value % 360) / 360.0
elif setting_name == "sun_altitude":
# 高度角度数转换为0-1范围
normalized_value = max(0, min(90, value)) / 90.0
else:
# 其他设置直接使用原值
normalized_value = value
# 获取当前时间如果Day Time Editor正在运行
current_time = getattr(self, '_current_daytime', 0.5) # 默认中午12点
# 更新设置的曲线值
if hasattr(setting_handle, 'curves') and setting_handle.curves:
# 清除现有的控制点,设置单一值
setting_handle.curves[0].set_single_value(normalized_value)
print(f"✅ 更新Day Time设置: {plugin_name}.{setting_name} = {value} (归一化: {normalized_value:.3f})")
# 保存设置到配置文件
try:
plugin_mgr.save_daytime_overrides("/$$rpconfig/daytime.yaml")
print("✅ Day Time设置已保存到配置文件")
except Exception as e:
print(f"⚠️ 保存配置文件失败: {e}")
# 通知Day Time Editor重新加载配置如果正在运行
try:
from RenderPipelineFile.rpcore.util.network_communication import NetworkCommunication
NetworkCommunication.send_async(NetworkCommunication.DAYTIME_PORT, "loadconf")
print("✅ 已通知Day Time Editor重新加载配置")
except Exception as e:
print(f"⚠️ 通知Day Time Editor失败: {e}")
return True
else:
print(f"⚠️ 设置 '{setting_name}' 没有可用的曲线")
return False
except Exception as e:
print(f"❌ 更新Day Time Editor设置失败: {e}")
import traceback
traceback.print_exc()
return False
def _applySunAzimuth(self, azimuth_degrees):
"""应用太阳方位角到Day Time Editor中的Sun Azimuth节点"""
try:
if not hasattr(self, 'world') or not self.world:
print("⚠️ World对象不存在")
return
# Day Time Editor是独立进程需要通过进程间通信控制
print(f"🌞 尝试控制Day Time Editor中的Sun Azimuth: {azimuth_degrees}°")
# 方法1通过文件通信
success = self._sendSunAzimuthViaFile(azimuth_degrees)
if success:
return
# 方法2尝试直接控制RenderPipeline的daytime_mgr如果存在
# success = self._controlLocalDaytimeManager(azimuth_degrees)
# if success:
# return
# 方法3通过socket通信如果实现了
# success = self._sendSunAzimuthViaSocket(azimuth_degrees)
# if success:
# return
print("⚠️ 无法控制Day Time Editor中的Sun Azimuth节点")
print(" 原因Day Time Editor运行在独立进程中")
print(" 建议:")
print(" 1. 直接在Day Time Editor窗口中调整Sun Azimuth")
print(" 2. 或者实现进程间通信机制")
except Exception as e:
print(f"❌ 应用太阳方位角失败: {e}")
import traceback
traceback.print_exc()
def _sendSunAzimuthViaFile(self, azimuth_degrees):
"""通过文件通信发送Sun Azimuth值到Day Time Editor"""
try:
import json
import os
import tempfile
# 创建通信文件
comm_dir = os.path.join(tempfile.gettempdir(), "daytime_editor_comm")
os.makedirs(comm_dir, exist_ok=True)
comm_file = os.path.join(comm_dir, "sun_azimuth_command.json")
command = {
"command": "set_sun_azimuth",
"value": azimuth_degrees,
"timestamp": __import__('time').time()
}
with open(comm_file, 'w') as f:
json.dump(command, f)
print(f"📁 已通过文件发送Sun Azimuth命令: {azimuth_degrees}°")
print(f" 通信文件: {comm_file}")
return True
except Exception as e:
print(f"⚠️ 文件通信失败: {e}")
return False
# def _controlLocalDaytimeManager(self, azimuth_degrees):
# """尝试控制本地的RenderPipeline daytime_mgr"""
# try:
# # 检查RenderPipeline的daytime_mgr
# if hasattr(self.world, 'render_pipeline') and hasattr(self.world.render_pipeline, 'daytime_mgr'):
# daytime_mgr = self.world.render_pipeline.daytime_mgr
# print("✅ 找到 render_pipeline.daytime_mgr")
#
# # 尝试设置sun_azimuth
# if hasattr(daytime_mgr, 'sun_azimuth'):
# daytime_mgr.sun_azimuth = azimuth_degrees
# print(f"☀️ 本地Sun Azimuth已设置为: {azimuth_degrees}°")
# return True
# elif hasattr(daytime_mgr, 'set_sun_azimuth'):
# daytime_mgr.set_sun_azimuth(azimuth_degrees)
# print(f"☀️ 本地Sun Azimuth已设置为: {azimuth_degrees}°")
# return True
# else:
# print("🔍 本地daytime_mgr可用属性:")
# for attr in dir(daytime_mgr):
# if 'sun' in attr.lower() or 'azimuth' in attr.lower():
# print(f" • {attr}")
#
# return False
#
# except Exception as e:
# print(f"⚠️ 控制本地daytime_mgr失败: {e}")
# return False