Merge pull request 'HJD' (#11) from HJD into main

Reviewed-on: #11
This commit is contained in:
Hector 2025-12-04 07:31:42 +00:00
commit 1a92f12dfd
61 changed files with 7722 additions and 6673 deletions

3
.idea/EG.iml generated
View File

@ -2,9 +2,10 @@
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.venv" />
<excludeFolder url="file://$MODULE_DIR$/venv" />
</content>
<orderEntry type="jdk" jdkName="Python 3.10 virtualenv at ~/EG/venv (2)" jdkType="Python SDK" />
<orderEntry type="jdk" jdkName="Python 3.10 (eg)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PyDocumentationSettings">

2
.idea/misc.xml generated
View File

@ -3,5 +3,5 @@
<component name="Black">
<option name="sdkName" value="Python 3.12 (PythonProject)" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 virtualenv at ~/EG/venv (2)" project-jdk-type="Python SDK" />
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (EG)" project-jdk-type="Python SDK" />
</project>

View File

@ -1,279 +1,271 @@
# -*- coding: utf-8-*-
"""
Module : Env_Grid_Maker
Author : Saifeddine ALOUI + code from Forklift 's code snippet could be found on panda3D's website
Description :
Creates a 3D implementation of grid that shows axes in 3 possible planes
"""
from panda3d.core import *
from direct.interval.IntervalGlobal import *
class Env_Grid_Maker:
def __init__(self, XYPlaneShow = True, XZPlaneShow = False, YZPlaneShow = False, endCapLinesShow = True, XSize = 50, YSize = 50, ZSize = 50, gridStep = 10, subdiv = 10):
#Create line objects
self.axisLines = LineSegs()
self.gridLines = LineSegs()
self.subdivLines = LineSegs()
#Init passed variables
self.XSize = XSize
self.YSize = YSize
self.ZSize = ZSize
self.gridStep = gridStep
self.subdiv = subdiv
#Init default variables
#Plane and end cap line visibility (1 is show, 0 is hide)
self.XYPlaneShow = XYPlaneShow
self.XZPlaneShow = XZPlaneShow
self.YZPlaneShow = YZPlaneShow
self.endCapLinesShow = endCapLinesShow
#Alpha variables for each plane
#self.XYPlaneAlpha = 1
#self.XZPlaneAlpha = 1
#self.YZPlaneAlpha = 1
#Colors (RGBA passed as a VBase4 object)
self.XAxisColor = VBase4(1, 0, 0, 1)
self.YAxisColor = VBase4(0, 1, 0, 1)
self.ZAxisColor = VBase4(0, 0, 1, 1)
self.gridColor = VBase4(1, 1, 1, 1)
self.subdivColor = VBase4(.35, .35, .35, 1)
#Line thicknesses (in pixels)
self.axisThickness = 3
self.gridThickness = 1
self.subdivThickness = 1
def create(self):
#Set line thicknesses
self.axisLines.setThickness(self.axisThickness)
self.gridLines.setThickness(self.gridThickness)
self.subdivLines.setThickness(self.subdivThickness)
if(self.XSize != 0):
#Draw X axis line
self.axisLines.setColor(self.XAxisColor)
self.axisLines.moveTo(-(self.XSize), 0, 0)
self.axisLines.drawTo(self.XSize, 0, 0)
if(self.YSize != 0):
#Draw Y axis line
self.axisLines.setColor(self.YAxisColor)
self.axisLines.moveTo(0, -(self.YSize), 0)
self.axisLines.drawTo(0, self.YSize, 0)
if(self.ZSize != 0):
#Draw Z axis line
self.axisLines.setColor(self.ZAxisColor)
self.axisLines.moveTo(0, 0, -(self.ZSize))
self.axisLines.drawTo(0, 0, self.ZSize)
#Check to see if primary grid lines should be drawn at all
if(self.gridStep != 0):
#Draw primary grid lines
self.gridLines.setColor(self.gridColor)
#Draw primary grid lines metering x axis if any x-length
if(self.XSize != 0):
if((self.YSize != 0) and (self.XYPlaneShow)):
#Draw y lines across x axis starting from center moving out
#XY Plane
for x in self.myfrange(0, self.XSize, self.gridStep):
self.gridLines.moveTo(x, -(self.YSize), 0)
self.gridLines.drawTo(x, self.YSize, 0)
self.gridLines.moveTo(-x, -(self.YSize), 0)
self.gridLines.drawTo(-x, self.YSize, 0)
if(self.endCapLinesShow):
#Draw endcap lines
self.gridLines.moveTo(self.XSize, -(self.YSize), 0)
self.gridLines.drawTo(self.XSize, self.YSize, 0)
self.gridLines.moveTo(-(self.XSize), -(self.YSize), 0)
self.gridLines.drawTo(-(self.XSize), self.YSize, 0)
if((self.ZSize != 0) and (self.XZPlaneShow)):
#Draw z lines across x axis starting from center moving out
#XZ Plane
for x in self.myfrange(0, self.XSize, self.gridStep):
self.gridLines.moveTo(x, 0, -(self.ZSize))
self.gridLines.drawTo(x, 0, self.ZSize)
self.gridLines.moveTo(-x, 0, -(self.ZSize))
self.gridLines.drawTo(-x, 0, self.ZSize)
if(self.endCapLinesShow):
#Draw endcap lines
self.gridLines.moveTo(self.XSize, 0, -(self.ZSize))
self.gridLines.drawTo(self.XSize, 0, self.ZSize)
self.gridLines.moveTo(-(self.XSize), 0, -(self.ZSize))
self.gridLines.drawTo(-(self.XSize), 0, self.ZSize)
#Draw primary grid lines metering y axis if any y-length
if(self.YSize != 0):
if((self.YSize != 0) and (self.XYPlaneShow)):
#Draw x lines across y axis
#XY Plane
for y in self.myfrange(0, self.YSize, self.gridStep):
self.gridLines.moveTo(-(self.XSize), y, 0)
self.gridLines.drawTo(self.XSize, y, 0)
self.gridLines.moveTo(-(self.XSize), -y, 0)
self.gridLines.drawTo(self.XSize, -y, 0)
if(self.endCapLinesShow):
#Draw endcap lines
self.gridLines.moveTo(-(self.XSize), self.YSize, 0)
self.gridLines.drawTo(self.XSize, self.YSize, 0)
self.gridLines.moveTo(-(self.XSize), -(self.YSize), 0)
self.gridLines.drawTo(self.XSize, -(self.YSize), 0)
if((self.ZSize != 0) and (self.YZPlaneShow)):
#Draw z lines across y axis
#YZ Plane
for y in self.myfrange(0, self.YSize, self.gridStep):
self.gridLines.moveTo(0, y, -(self.ZSize))
self.gridLines.drawTo(0, y, self.ZSize)
self.gridLines.moveTo(0, -y, -(self.ZSize))
self.gridLines.drawTo(0, -y, self.ZSize)
if(self.endCapLinesShow):
#Draw endcap lines
self.gridLines.moveTo(0, self.YSize, -(self.ZSize))
self.gridLines.drawTo(0, self.YSize, self.ZSize)
self.gridLines.moveTo(0, -(self.YSize), -(self.ZSize))
self.gridLines.drawTo(0, -(self.YSize), self.ZSize)
#Draw primary grid lines metering z axis if any z-length
if(self.ZSize != 0):
if((self.XSize != 0) and (self.XZPlaneShow)):
#Draw x lines across z axis
#XZ Plane
for z in self.myfrange(0, self.ZSize, self.gridStep):
self.gridLines.moveTo(-(self.XSize), 0, z)
self.gridLines.drawTo(self.XSize, 0, z)
self.gridLines.moveTo(-(self.XSize), 0, -z)
self.gridLines.drawTo(self.XSize, 0, -z)
if(self.endCapLinesShow):
#Draw endcap lines
self.gridLines.moveTo(-(self.XSize), 0, self.ZSize)
self.gridLines.drawTo(self.XSize, 0, self.ZSize)
self.gridLines.moveTo(-(self.XSize), 0, -(self.ZSize))
self.gridLines.drawTo(self.XSize, 0, -(self.ZSize))
if((self.YSize != 0) and (self.YZPlaneShow)):
#Draw y lines across z axis
#YZ Plane
for z in self.myfrange(0, self.ZSize, self.gridStep):
self.gridLines.moveTo(0, -(self.YSize), z)
self.gridLines.drawTo(0, self.YSize, z)
self.gridLines.moveTo(0, -(self.YSize), -z)
self.gridLines.drawTo(0, self.YSize, -z)
if(self.endCapLinesShow):
#Draw endcap lines
self.gridLines.moveTo(0, -(self.YSize), self.ZSize)
self.gridLines.drawTo(0, self.YSize, self.ZSize)
self.gridLines.moveTo(0, -(self.YSize), -(self.ZSize))
self.gridLines.drawTo(0, self.YSize, -(self.ZSize))
#Check to see if secondary grid lines should be drawn
if(self.subdiv != 0):
#Draw secondary grid lines
self.subdivLines.setColor(self.subdivColor)
if(self.XSize != 0):
adjustedstep = self.gridStep / self.subdiv
if((self.YSize != 0) and (self.XYPlaneShow)):
#Draw y lines across x axis starting from center moving out
#XY
for x in self.myfrange(0, self.XSize, adjustedstep):
self.subdivLines.moveTo(x, -(self.YSize), 0)
self.subdivLines.drawTo(x, self.YSize, 0)
self.subdivLines.moveTo(-x, -(self.YSize), 0)
self.subdivLines.drawTo(-x, self.YSize, 0)
if((self.ZSize != 0) and (self.XZPlaneShow)):
#Draw z lines across x axis starting from center moving out
#XZ
for x in self.myfrange(0, self.XSize, adjustedstep):
self.subdivLines.moveTo(x, 0, -(self.ZSize))
self.subdivLines.drawTo(x, 0, self.ZSize)
self.subdivLines.moveTo(-x, 0, -(self.ZSize))
self.subdivLines.drawTo(-x, 0, self.ZSize)
if(self.YSize != 0):
if((self.YSize != 0) and (self.XYPlaneShow)):
#Draw x lines across y axis
#XY
for y in self.myfrange(0, self.YSize, adjustedstep):
self.subdivLines.moveTo(-(self.XSize), y, 0)
self.subdivLines.drawTo(self.XSize, y, 0)
self.subdivLines.moveTo(-(self.XSize), -y, 0)
self.subdivLines.drawTo(self.XSize, -y, 0)
if((self.ZSize != 0) and (self.YZPlaneShow)):
#Draw z lines across y axis
#YZ
for y in self.myfrange(0, self.YSize, adjustedstep):
self.subdivLines.moveTo(0, y, -(self.ZSize))
self.subdivLines.drawTo(0, y, self.ZSize)
self.subdivLines.moveTo(0, -y, -(self.ZSize))
self.subdivLines.drawTo(0, -y, self.ZSize)
if(self.ZSize != 0):
if((self.XSize != 0) and (self.XZPlaneShow)):
#Draw x lines across z axis
#XZ
for z in self.myfrange(0, self.ZSize, adjustedstep):
self.subdivLines.moveTo(-(self.XSize), 0, z)
self.subdivLines.drawTo(self.XSize, 0, z)
self.subdivLines.moveTo(-(self.XSize), 0, -z)
self.subdivLines.drawTo(self.XSize, 0, -z)
if((self.YSize != 0) and (self.YZPlaneShow)):
#Draw y lines across z axis
#YZ
for z in self.myfrange(0, self.ZSize, adjustedstep):
self.subdivLines.moveTo(0, -(self.YSize), z)
self.subdivLines.drawTo(0, self.YSize, z)
self.subdivLines.moveTo(0, -(self.YSize), -z)
self.subdivLines.drawTo(0, self.YSize, -z)
#Create ThreeAxisGrid nodes and nodepaths
#Create parent node and path
self.parentNode = PandaNode('threeaxisgrid-parentnode')
self.parentNodePath = NodePath(self.parentNode)
#Create axis lines node and path, then reparent
self.axisLinesNode = self.axisLines.create()
self.axisLinesNodePath = NodePath(self.axisLinesNode)
self.axisLinesNodePath.reparentTo(self.parentNodePath)
#Create grid lines node and path, then reparent
self.gridLinesNode = self.gridLines.create()
self.gridLinesNodePath = NodePath(self.gridLinesNode)
self.gridLinesNodePath.reparentTo(self.parentNodePath)
#Create subdivision lines node and path then reparent
self.subdivLinesNode = self.subdivLines.create()
self.subdivLinesNodePath = NodePath(self.subdivLinesNode)
self.subdivLinesNodePath.reparentTo(self.parentNodePath)
return self.parentNodePath
def myfrange(self, start, stop=None, step=None):
if stop is None:
stop = float(start)
start = 0.0
if step is None:
step = 1.0
cur = float(start)
while cur < stop:
yield cur
from panda3d.core import *
from direct.interval.IntervalGlobal import *
class Env_Grid_Maker:
def __init__(self, XYPlaneShow = True, XZPlaneShow = False, YZPlaneShow = False, endCapLinesShow = True, XSize = 50, YSize = 50, ZSize = 50, gridStep = 10, subdiv = 10):
#Create line objects
self.axisLines = LineSegs()
self.gridLines = LineSegs()
self.subdivLines = LineSegs()
#Init passed variables
self.XSize = XSize
self.YSize = YSize
self.ZSize = ZSize
self.gridStep = gridStep
self.subdiv = subdiv
#Init default variables
#Plane and end cap line visibility (1 is show, 0 is hide)
self.XYPlaneShow = XYPlaneShow
self.XZPlaneShow = XZPlaneShow
self.YZPlaneShow = YZPlaneShow
self.endCapLinesShow = endCapLinesShow
#Alpha variables for each plane
#self.XYPlaneAlpha = 1
#self.XZPlaneAlpha = 1
#self.YZPlaneAlpha = 1
#Colors (RGBA passed as a VBase4 object)
self.XAxisColor = VBase4(1, 0, 0, 1)
self.YAxisColor = VBase4(0, 1, 0, 1)
self.ZAxisColor = VBase4(0, 0, 1, 1)
self.gridColor = VBase4(1, 1, 1, 1)
self.subdivColor = VBase4(.35, .35, .35, 1)
#Line thicknesses (in pixels)
self.axisThickness = 3
self.gridThickness = 1
self.subdivThickness = 1
def create(self):
#Set line thicknesses
self.axisLines.setThickness(self.axisThickness)
self.gridLines.setThickness(self.gridThickness)
self.subdivLines.setThickness(self.subdivThickness)
if(self.XSize != 0):
#Draw X axis line
self.axisLines.setColor(self.XAxisColor)
self.axisLines.moveTo(-(self.XSize), 0, 0)
self.axisLines.drawTo(self.XSize, 0, 0)
if(self.YSize != 0):
#Draw Y axis line
self.axisLines.setColor(self.YAxisColor)
self.axisLines.moveTo(0, -(self.YSize), 0)
self.axisLines.drawTo(0, self.YSize, 0)
if(self.ZSize != 0):
#Draw Z axis line
self.axisLines.setColor(self.ZAxisColor)
self.axisLines.moveTo(0, 0, -(self.ZSize))
self.axisLines.drawTo(0, 0, self.ZSize)
#Check to see if primary grid lines should be drawn at all
if(self.gridStep != 0):
#Draw primary grid lines
self.gridLines.setColor(self.gridColor)
#Draw primary grid lines metering x axis if any x-length
if(self.XSize != 0):
if((self.YSize != 0) and (self.XYPlaneShow)):
#Draw y lines across x axis starting from center moving out
#XY Plane
for x in self.myfrange(0, self.XSize, self.gridStep):
self.gridLines.moveTo(x, -(self.YSize), 0)
self.gridLines.drawTo(x, self.YSize, 0)
self.gridLines.moveTo(-x, -(self.YSize), 0)
self.gridLines.drawTo(-x, self.YSize, 0)
if(self.endCapLinesShow):
#Draw endcap lines
self.gridLines.moveTo(self.XSize, -(self.YSize), 0)
self.gridLines.drawTo(self.XSize, self.YSize, 0)
self.gridLines.moveTo(-(self.XSize), -(self.YSize), 0)
self.gridLines.drawTo(-(self.XSize), self.YSize, 0)
if((self.ZSize != 0) and (self.XZPlaneShow)):
#Draw z lines across x axis starting from center moving out
#XZ Plane
for x in self.myfrange(0, self.XSize, self.gridStep):
self.gridLines.moveTo(x, 0, -(self.ZSize))
self.gridLines.drawTo(x, 0, self.ZSize)
self.gridLines.moveTo(-x, 0, -(self.ZSize))
self.gridLines.drawTo(-x, 0, self.ZSize)
if(self.endCapLinesShow):
#Draw endcap lines
self.gridLines.moveTo(self.XSize, 0, -(self.ZSize))
self.gridLines.drawTo(self.XSize, 0, self.ZSize)
self.gridLines.moveTo(-(self.XSize), 0, -(self.ZSize))
self.gridLines.drawTo(-(self.XSize), 0, self.ZSize)
#Draw primary grid lines metering y axis if any y-length
if(self.YSize != 0):
if((self.YSize != 0) and (self.XYPlaneShow)):
#Draw x lines across y axis
#XY Plane
for y in self.myfrange(0, self.YSize, self.gridStep):
self.gridLines.moveTo(-(self.XSize), y, 0)
self.gridLines.drawTo(self.XSize, y, 0)
self.gridLines.moveTo(-(self.XSize), -y, 0)
self.gridLines.drawTo(self.XSize, -y, 0)
if(self.endCapLinesShow):
#Draw endcap lines
self.gridLines.moveTo(-(self.XSize), self.YSize, 0)
self.gridLines.drawTo(self.XSize, self.YSize, 0)
self.gridLines.moveTo(-(self.XSize), -(self.YSize), 0)
self.gridLines.drawTo(self.XSize, -(self.YSize), 0)
if((self.ZSize != 0) and (self.YZPlaneShow)):
#Draw z lines across y axis
#YZ Plane
for y in self.myfrange(0, self.YSize, self.gridStep):
self.gridLines.moveTo(0, y, -(self.ZSize))
self.gridLines.drawTo(0, y, self.ZSize)
self.gridLines.moveTo(0, -y, -(self.ZSize))
self.gridLines.drawTo(0, -y, self.ZSize)
if(self.endCapLinesShow):
#Draw endcap lines
self.gridLines.moveTo(0, self.YSize, -(self.ZSize))
self.gridLines.drawTo(0, self.YSize, self.ZSize)
self.gridLines.moveTo(0, -(self.YSize), -(self.ZSize))
self.gridLines.drawTo(0, -(self.YSize), self.ZSize)
#Draw primary grid lines metering z axis if any z-length
if(self.ZSize != 0):
if((self.XSize != 0) and (self.XZPlaneShow)):
#Draw x lines across z axis
#XZ Plane
for z in self.myfrange(0, self.ZSize, self.gridStep):
self.gridLines.moveTo(-(self.XSize), 0, z)
self.gridLines.drawTo(self.XSize, 0, z)
self.gridLines.moveTo(-(self.XSize), 0, -z)
self.gridLines.drawTo(self.XSize, 0, -z)
if(self.endCapLinesShow):
#Draw endcap lines
self.gridLines.moveTo(-(self.XSize), 0, self.ZSize)
self.gridLines.drawTo(self.XSize, 0, self.ZSize)
self.gridLines.moveTo(-(self.XSize), 0, -(self.ZSize))
self.gridLines.drawTo(self.XSize, 0, -(self.ZSize))
if((self.YSize != 0) and (self.YZPlaneShow)):
#Draw y lines across z axis
#YZ Plane
for z in self.myfrange(0, self.ZSize, self.gridStep):
self.gridLines.moveTo(0, -(self.YSize), z)
self.gridLines.drawTo(0, self.YSize, z)
self.gridLines.moveTo(0, -(self.YSize), -z)
self.gridLines.drawTo(0, self.YSize, -z)
if(self.endCapLinesShow):
#Draw endcap lines
self.gridLines.moveTo(0, -(self.YSize), self.ZSize)
self.gridLines.drawTo(0, self.YSize, self.ZSize)
self.gridLines.moveTo(0, -(self.YSize), -(self.ZSize))
self.gridLines.drawTo(0, self.YSize, -(self.ZSize))
#Check to see if secondary grid lines should be drawn
if(self.subdiv != 0):
#Draw secondary grid lines
self.subdivLines.setColor(self.subdivColor)
if(self.XSize != 0):
adjustedstep = self.gridStep / self.subdiv
if((self.YSize != 0) and (self.XYPlaneShow)):
#Draw y lines across x axis starting from center moving out
#XY
for x in self.myfrange(0, self.XSize, adjustedstep):
self.subdivLines.moveTo(x, -(self.YSize), 0)
self.subdivLines.drawTo(x, self.YSize, 0)
self.subdivLines.moveTo(-x, -(self.YSize), 0)
self.subdivLines.drawTo(-x, self.YSize, 0)
if((self.ZSize != 0) and (self.XZPlaneShow)):
#Draw z lines across x axis starting from center moving out
#XZ
for x in self.myfrange(0, self.XSize, adjustedstep):
self.subdivLines.moveTo(x, 0, -(self.ZSize))
self.subdivLines.drawTo(x, 0, self.ZSize)
self.subdivLines.moveTo(-x, 0, -(self.ZSize))
self.subdivLines.drawTo(-x, 0, self.ZSize)
if(self.YSize != 0):
if((self.YSize != 0) and (self.XYPlaneShow)):
#Draw x lines across y axis
#XY
for y in self.myfrange(0, self.YSize, adjustedstep):
self.subdivLines.moveTo(-(self.XSize), y, 0)
self.subdivLines.drawTo(self.XSize, y, 0)
self.subdivLines.moveTo(-(self.XSize), -y, 0)
self.subdivLines.drawTo(self.XSize, -y, 0)
if((self.ZSize != 0) and (self.YZPlaneShow)):
#Draw z lines across y axis
#YZ
for y in self.myfrange(0, self.YSize, adjustedstep):
self.subdivLines.moveTo(0, y, -(self.ZSize))
self.subdivLines.drawTo(0, y, self.ZSize)
self.subdivLines.moveTo(0, -y, -(self.ZSize))
self.subdivLines.drawTo(0, -y, self.ZSize)
if(self.ZSize != 0):
if((self.XSize != 0) and (self.XZPlaneShow)):
#Draw x lines across z axis
#XZ
for z in self.myfrange(0, self.ZSize, adjustedstep):
self.subdivLines.moveTo(-(self.XSize), 0, z)
self.subdivLines.drawTo(self.XSize, 0, z)
self.subdivLines.moveTo(-(self.XSize), 0, -z)
self.subdivLines.drawTo(self.XSize, 0, -z)
if((self.YSize != 0) and (self.YZPlaneShow)):
#Draw y lines across z axis
#YZ
for z in self.myfrange(0, self.ZSize, adjustedstep):
self.subdivLines.moveTo(0, -(self.YSize), z)
self.subdivLines.drawTo(0, self.YSize, z)
self.subdivLines.moveTo(0, -(self.YSize), -z)
self.subdivLines.drawTo(0, self.YSize, -z)
#Create ThreeAxisGrid nodes and nodepaths
#Create parent node and path
self.parentNode = PandaNode('threeaxisgrid-parentnode')
self.parentNodePath = NodePath(self.parentNode)
#Create axis lines node and path, then reparent
self.axisLinesNode = self.axisLines.create()
self.axisLinesNodePath = NodePath(self.axisLinesNode)
self.axisLinesNodePath.reparentTo(self.parentNodePath)
#Create grid lines node and path, then reparent
self.gridLinesNode = self.gridLines.create()
self.gridLinesNodePath = NodePath(self.gridLinesNode)
self.gridLinesNodePath.reparentTo(self.parentNodePath)
#Create subdivision lines node and path then reparent
self.subdivLinesNode = self.subdivLines.create()
self.subdivLinesNodePath = NodePath(self.subdivLinesNode)
self.subdivLinesNodePath.reparentTo(self.parentNodePath)
return self.parentNodePath
def myfrange(self, start, stop=None, step=None):
if stop is None:
stop = float(start)
start = 0.0
if step is None:
step = 1.0
cur = float(start)
while cur < stop:
yield cur
cur += step

View File

@ -1,2 +1,2 @@
name="QPanda3D"
name="QMeta3D"
__all__ = ["Env_Grid_Maker"]

View File

@ -1,214 +1,192 @@
# -*- coding: utf-8-*-
"""
Module : Panda3DWorld
Author : Saifeddine ALOUI
Description :
Inherit this object to create your custom world
"""
import sys
import os
from core.CustomMouseController import CustomMouseController
# 获取 RenderPipelineFile 的路径
render_pipeline_path = './RenderPipelineFile'
# 将该路径添加到 sys.path 中,确保 Python 能够找到它
project_root = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, project_root)
sys.path.insert(0, render_pipeline_path)
# 现在你可以导入 RenderPipelineFile 中的模块了
# 例如,如果你想导入 RenderPipelineFile 中的 RenderPipeline 模块
from RenderPipelineFile.rpcore import RenderPipeline
_global_render_pipeline = None
# PyQt imports
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
# Panda imports
from panda3d.core import *
# from panda3d.core import loadPrcFileData
# loadPrcFileData("", "window-type none") # Set Panda to draw its main window in an offscreen buffer
from direct.showbase.DirectObject import DirectObject
from panda3d.core import GraphicsOutput, Texture, ConfigVariableManager, WindowProperties
# Set up Panda environment
from direct.showbase.ShowBase import ShowBase
import platform
# Local imports
from QPanda3D.QMouseWatcherNode import QMouseWatcherNode
from RenderPipelineFile.rpcore.render_target import RenderTarget
__all__ = ["Panda3DWorld"]
_global_world_instance=None
import builtins
class Panda3DWorld(ShowBase):
"""
Panda3DWorld : A class to handle all panda3D world manipulation
"""
def __init__(self, width=1380, height=750, is_fullscreen=False, size=1.0, clear_color=LVecBase4f(0, 0.5, 0, 1),
name="qpanda3D"):
global _global_world_instance
global _global_render_pipeline
_global_world_instance = self
sort = -100
self.parent = None
# 设置基本配置
loadPrcFileData("", "show-frame-rate-meter 0")
loadPrcFileData("", "window-type offscreen") # 设置为离屏渲染
loadPrcFileData("", f"win-size {width} {height}")
loadPrcFileData("", "win-fixed-size #f") # 允许窗口调整大小
# 🚀 VR性能优化配置
loadPrcFileData("", "prefer-single-buffer true") # 减少缓冲区交换开销
loadPrcFileData("", "gl-force-flush false") # 避免强制glFlush导致的性能损失
loadPrcFileData("", "sync-video false") # 禁用默认VSync让OpenVR控制
loadPrcFileData("", "support-stencil false") # 禁用不必要的模板缓冲区
loadPrcFileData("", "clock-mode non-real-time") # 禁用Panda3D帧率控制让OpenVR控制
# loadPrcFileData("", "gl-debug true") # 调试时可启用OpenGL调试
if (is_fullscreen):
loadPrcFileData("", "fullscreen #t")
self.render_pipeline = RenderPipeline()
self.render_pipeline.pre_showbase_init()
ShowBase.__init__(self)
# 初始化渲染管线并设置可调整大小的标志
self.render_pipeline.create(self)
_global_render_pipeline = self.render_pipeline
# 创建 Qt 能读的 RGBA8 贴图
self.qt_output_tex = Texture("qt_output_tex")
self.qt_output_tex.set_format(Texture.F_rgba8)
self.qt_output_tex.set_component_type(Texture.T_unsigned_byte)
# # 获取图形管线对象
# gsg = self.win.getGsg()
# host = gsg.getEngine().getHost()
self.win.add_render_texture(self.qt_output_tex, GraphicsOutput.RTM_copy_ram)
#self.screenTexture = Texture()
#self.screenTexture.setMinfilter(Texture.FTLinear)
#self.screenTexture.setFormat(Texture.FRgba32)
#self.screenTexture.set_wrap_u(Texture.WM_clamp)
#self.screenTexture.set_wrap_v(Texture.WM_clamp)
# buff_size_x = int(self.win.get_x_size() * size)
# buff_size_y = int(self.win.get_y_size() * size)
# winprops = WindowProperties()
# winprops.set_size(buff_size_x, buff_size_y)
#
#
# props = FrameBufferProperties()
# props.set_rgb_color(True)
# props.set_rgba_bits(8, 8, 8, 8)
# props.set_depth_bits(8)
# self.buff = self.graphicsEngine.make_output(
# self.pipe, name, sort,
# props, winprops,
# GraphicsPipe.BF_resizeable,
# self.win.get_gsg(), self.win)
#self.screenTexture = render_pipeline._final_stage.target.color_tex
#self.buff.addRenderTexture(self.screenTexture, GraphicsOutput.RTMCopyRam)
# self.buff.set_sort(sort)
#self.cam = self.makeCamera(self.buff)
#self.render_pipeline._showbase.cam=self.makeCamera(self.buff)
#self.render_pipeline._showbase.cam=self.makeCamera(self.buff)
#self.render_pipeline._showbase.camera.reparentTo(self.render)
#base.camera.reparentTo(self.render)
#self.cam.reparentTo(self.render) # 可选同步
#render_pipeline.set_camera(self.cam)
#添加渲染效果
#self.cam = self.render_pipeline._showbase.cam
#self.camNode = self.cam.node()
#self.camLens = self.camNode.get_lens()
self.render_pipeline._showbase.camera = self.render_pipeline._showbase.cam
#self.render_pipeline.daytime_mgr.update()
# if clear_color is None:
# self.buff.set_clear_active(GraphicsOutput.RTPColor, False)
# else:
# self.buff.set_clear_color(clear_color)
# self.buff.set_clear_active(GraphicsOutput.RTPColor, True)
# self.disableMouse()
self.mouse_controller = CustomMouseController(self)
self.mouse_controller.setUp()
# 添加错误处理钩子
self.accept("transform_state_error", self._handle_transform_error)
def _handle_transform_error(self):
"""处理TransformState相关的错误"""
try:
from panda3d.core import TransformState, RenderState
TransformState.clear_cache()
RenderState.clear_cache()
print("已清理TransformState和RenderState缓存")
except Exception as e:
print(f"清理缓存时出错: {e}")
def render_pipeline(self):
"""获取 RenderPipeline 实例"""
return self._render_pipeline
def set_parent(self, parent: QWidget):
self.parent = parent
self.mouseWatcherNode = QMouseWatcherNode(parent)
def getAspectRatio(self, win = None):
if win is None and self.parent is not None:
return float(self.parent.width()) / float(self.parent.height())
else:
return super().getAspectRatio(win)
def get_render_pipeline():
"""获取全局 RenderPipeline 单例"""
if _global_render_pipeline is None:
raise RuntimeError(
"RenderPipeline has not been initialized yet. Please create a Panda3DWorld instance first.")
return _global_render_pipeline
def resize_buffer(self, width: int, height: int):
"""根据新窗口尺寸调整 Panda3D 渲染输出尺寸"""
# 设置 Panda3D 的窗口尺寸offscreen 模式下对应输出区域)
props = WindowProperties()
props.set_size(width, height)
self.win.request_properties(props)
# 重新分配输出贴图的大小
self.qt_output_tex.set_x_size(width)
self.qt_output_tex.set_y_size(height)
# 更新 lens 的 aspect ratio
if self.camLens:
self.camLens.set_film_size(width, height) # 或 set_aspect_ratio(width / height)
# 强制更新窗口(有时在 Qt 内嵌时需要)
import sys
import os
from core.CustomMouseController import CustomMouseController
# 获取 RenderPipelineFile 的路径
render_pipeline_path = './RenderPipelineFile'
# 将该路径添加到 sys.path 中,确保 Python 能够找到它
project_root = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, project_root)
sys.path.insert(0, render_pipeline_path)
from RenderPipelineFile.rpcore import RenderPipeline
_global_render_pipeline = None
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from panda3d.core import *
from panda3d.core import GraphicsOutput, Texture, ConfigVariableManager, WindowProperties
from direct.showbase.ShowBase import ShowBase
import platform
from QMeta3D.QMouseWatcherNode import QMouseWatcherNode
from RenderPipelineFile.rpcore.render_target import RenderTarget
__all__ = ["Meta3DWorld"]
_global_world_instance=None
import builtins
class Meta3DWorld(ShowBase):
def __init__(self, width=1380, height=750, is_fullscreen=False, size=1.0, clear_color=LVecBase4f(0, 0.5, 0, 1),
name="qMeta3D"):
global _global_world_instance
global _global_render_pipeline
_global_world_instance = self
sort = -100
self.parent = None
# 设置基本配置
loadPrcFileData("", "show-frame-rate-meter 0")
loadPrcFileData("", "window-type offscreen") # 设置为离屏渲染
loadPrcFileData("", f"win-size {width} {height}")
loadPrcFileData("", "win-fixed-size #f") # 允许窗口调整大小
# 🚀 VR性能优化配置
loadPrcFileData("", "prefer-single-buffer true") # 减少缓冲区交换开销
loadPrcFileData("", "gl-force-flush false") # 避免强制glFlush导致的性能损失
loadPrcFileData("", "sync-video false") # 禁用默认VSync让OpenVR控制
loadPrcFileData("", "support-stencil false") # 禁用不必要的模板缓冲区
loadPrcFileData("", "clock-mode non-real-time")
# loadPrcFileData("", "gl-debug true")
if (is_fullscreen):
loadPrcFileData("", "fullscreen #t")
self.render_pipeline = RenderPipeline()
self.render_pipeline.pre_showbase_init()
ShowBase.__init__(self)
# 初始化渲染管线并设置可调整大小的标志
self.render_pipeline.create(self)
_global_render_pipeline = self.render_pipeline
# 创建 Qt 能读的 RGBA8 贴图
self.qt_output_tex = Texture("qt_output_tex")
self.qt_output_tex.set_format(Texture.F_rgba8)
self.qt_output_tex.set_component_type(Texture.T_unsigned_byte)
# # 获取图形管线对象
# gsg = self.win.getGsg()
# host = gsg.getEngine().getHost()
self.win.add_render_texture(self.qt_output_tex, GraphicsOutput.RTM_copy_ram)
#self.screenTexture = Texture()
#self.screenTexture.setMinfilter(Texture.FTLinear)
#self.screenTexture.setFormat(Texture.FRgba32)
#self.screenTexture.set_wrap_u(Texture.WM_clamp)
#self.screenTexture.set_wrap_v(Texture.WM_clamp)
# buff_size_x = int(self.win.get_x_size() * size)
# buff_size_y = int(self.win.get_y_size() * size)
# winprops = WindowProperties()
# winprops.set_size(buff_size_x, buff_size_y)
#
#
# props = FrameBufferProperties()
# props.set_rgb_color(True)
# props.set_rgba_bits(8, 8, 8, 8)
# props.set_depth_bits(8)
# self.buff = self.graphicsEngine.make_output(
# self.pipe, name, sort,
# props, winprops,
# GraphicsPipe.BF_resizeable,
# self.win.get_gsg(), self.win)
#self.screenTexture = render_pipeline._final_stage.target.color_tex
#self.buff.addRenderTexture(self.screenTexture, GraphicsOutput.RTMCopyRam)
# self.buff.set_sort(sort)
#self.cam = self.makeCamera(self.buff)
#self.render_pipeline._showbase.cam=self.makeCamera(self.buff)
#self.render_pipeline._showbase.cam=self.makeCamera(self.buff)
#self.render_pipeline._showbase.camera.reparentTo(self.render)
#base.camera.reparentTo(self.render)
#self.cam.reparentTo(self.render) # 可选同步
#render_pipeline.set_camera(self.cam)
#添加渲染效果
#self.cam = self.render_pipeline._showbase.cam
#self.camNode = self.cam.node()
#self.camLens = self.camNode.get_lens()
self.render_pipeline._showbase.camera = self.render_pipeline._showbase.cam
#self.render_pipeline.daytime_mgr.update()
# if clear_color is None:
# self.buff.set_clear_active(GraphicsOutput.RTPColor, False)
# else:
# self.buff.set_clear_color(clear_color)
# self.buff.set_clear_active(GraphicsOutput.RTPColor, True)
# self.disableMouse()
self.mouse_controller = CustomMouseController(self)
self.mouse_controller.setUp()
# 添加错误处理钩子
self.accept("transform_state_error", self._handle_transform_error)
def _handle_transform_error(self):
"""处理TransformState相关的错误"""
try:
from panda3d.core import TransformState, RenderState
TransformState.clear_cache()
RenderState.clear_cache()
print("已清理TransformState和RenderState缓存")
except Exception as e:
print(f"清理缓存时出错: {e}")
def render_pipeline(self):
"""获取 RenderPipeline 实例"""
return self._render_pipeline
def set_parent(self, parent: QWidget):
self.parent = parent
self.mouseWatcherNode = QMouseWatcherNode(parent)
def getAspectRatio(self, win = None):
if win is None and self.parent is not None:
return float(self.parent.width()) / float(self.parent.height())
else:
return super().getAspectRatio(win)
def get_render_pipeline():
"""获取全局 RenderPipeline 单例"""
if _global_render_pipeline is None:
raise RuntimeError(
"RenderPipeline has not been initialized yet. Please create a 3DWorld instance first.")
return _global_render_pipeline
def resize_buffer(self, width: int, height: int):
props = WindowProperties()
props.set_size(width, height)
self.win.request_properties(props)
# 重新分配输出贴图的大小
self.qt_output_tex.set_x_size(width)
self.qt_output_tex.set_y_size(height)
# 更新 lens 的 aspect ratio
if self.camLens:
self.camLens.set_film_size(width, height) # 或 set_aspect_ratio(width / height)
# 强制更新窗口(有时在 Qt 内嵌时需要)
self.graphicsEngine.open_windows()

View File

@ -1,355 +1,252 @@
# -*- coding: utf-8-*-
"""
Module : QPanda3DWidget
Author : Saifeddine ALOUI
Description :
This is the QWidget to be inserted in your standard PyQt5 application.
It takes a Panda3DWorld object at init time.
You should first create the Panda3DWorkd object before creating this widget.
"""
# PyQt imports
from PyQt5 import QtWidgets, QtGui
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from direct.task.TaskManagerGlobal import taskMgr
# Panda imports
from panda3d.core import Texture, WindowProperties, CallbackGraphicsWindow
from panda3d.core import loadPrcFileData
from QPanda3D.QPanda3D_Buttons_Translation import QPanda3D_Button_translation
from QPanda3D.QPanda3D_Keys_Translation import QPanda3D_Key_translation
from QPanda3D.QPanda3D_Modifiers_Translation import QPanda3D_Modifier_translation
__all__ = ["QPanda3DWidget"]
class QPanda3DSynchronizer(QTimer):
def __init__(self, qPanda3DWidget, FPS=60):
QTimer.__init__(self)
self.qPanda3DWidget = qPanda3DWidget
dt = 1000 / FPS
self.setInterval(int(dt))
self.timeout.connect(self.tick)
def tick(self):
try:
# 在渲染前清理可能损坏的TransformState对象
from panda3d.core import TransformState
TransformState.clear_cache()
taskMgr.step()
self.qPanda3DWidget.update()
except AssertionError as e:
# 专门处理 TransformState has_mat() 断言错误
if "has_mat" in str(e):
print(f"警告: 检测到TransformState断言错误已静默处理: {e}")
# 尝试恢复渲染状态
try:
# 强制清理缓存并重试
from panda3d.core import TransformState, RenderState
TransformState.clear_cache()
RenderState.clear_cache()
taskMgr.step()
self.qPanda3DWidget.update()
except:
pass
else:
# 重新抛出其他断言错误
raise
except Exception as e:
# 静默处理其他所有异常
print(f"警告: 检测到异常,已静默处理: {e}")
def get_panda_key_modifiers(evt):
panda_mods = []
qt_mods = evt.modifiers()
for qt_mod, panda_mod in QPanda3D_Modifier_translation.items():
if (qt_mods & qt_mod) == qt_mod:
panda_mods.append(panda_mod)
return panda_mods
def get_panda_key_modifiers_prefix(evt):
# join all modifiers (except NoModifier, which is None) with '-'
prefix = "-".join([mod for mod in get_panda_key_modifiers(evt) if mod is not None])
# if the prefix is not empty, append a '-'
if prefix:
prefix += '-'
return prefix
class QPanda3DWidget(QWidget):
"""
An interactive panda3D QWidget
Parent : Parent QT Widget
FPS : Number of frames per socond to refresh the screen
debug: Switch printing key events to console on/off
"""
def __init__(self, panda3DWorld, parent=None, FPS=60, debug=False):
QWidget.__init__(self, parent)
self.rp_sync_requested = False
# set fixed geometry
self.panda3DWorld = panda3DWorld
self.panda3DWorld.set_parent(self)
# Setup a timer in Qt that runs taskMgr.step() to simulate Panda's own main loop
# pandaTimer = QTimer(self)
# pandaTimer.timeout.connect()
# pandaTimer.start(0)
self.setFocusPolicy(Qt.StrongFocus)
# Setup another timer that redraws this widget in a specific FPS
# redrawTimer = QTimer(self)
# redrawTimer.timeout.connect(self.update)
# redrawTimer.start(1000/FPS)
self.paintSurface = QPainter()
self.rotate = QTransform()
self.rotate.rotate(180)
self.out_image = QImage()
size = self.panda3DWorld.cam.node().get_lens().get_film_size()
self.initial_film_size = QSizeF(size.x, size.y)
self.initial_size = self.size()
self.synchronizer = QPanda3DSynchronizer(self, FPS)
self.synchronizer.start()
self.debug = debug
def mousePressEvent(self, evt):
button = evt.button()
try:
b = "{}{}".format(get_panda_key_modifiers_prefix(evt), QPanda3D_Button_translation[button])
if self.debug:
print(b)
messenger.send(b,[{"x":evt.x(),"y":evt.y()}])
except:
print("Unimplemented button. Please send an issue on github to fix this problem")
def mouseMoveEvent(self, evt:QtGui.QMouseEvent):
button = evt.button()
try:
b = "mouse-move"
if self.debug:
print(b)
messenger.send(b,[{"x":evt.x(),"y":evt.y()}])
except:
print("Unimplemented button. Please send an issue on github to fix this problem")
def mouseReleaseEvent(self, evt):
button = evt.button()
try:
b = "{}{}-up".format(get_panda_key_modifiers_prefix(evt), QPanda3D_Button_translation[button])
if self.debug:
print(b)
messenger.send(b,[{"x":evt.x(),"y":evt.y()}])
except:
print("Unimplemented button. Please send an issue on github to fix this problem")
def wheelEvent(self, evt):
delta = evt.angleDelta().y()
try:
if self.debug:
print(f"wheel {delta}")
messenger.send('wheel',[{"delta":delta}])
except:
print("Unimplemented button. Please send an issue on github to fix this problem")
def keyPressEvent(self, evt):
key = evt.key()
try:
k = "{}{}".format(get_panda_key_modifiers_prefix(evt), QPanda3D_Key_translation[key])
if self.debug:
print(k)
messenger.send(k)
except:
print("Unimplemented key. Please send an issue on github to fix this problem")
def keyReleaseEvent(self, evt):
key = evt.key()
try:
k = "{}{}-up".format(get_panda_key_modifiers_prefix(evt), QPanda3D_Key_translation[key])
if self.debug:
print(k)
messenger.send(k)
except:
print("Unimplemented key. Please send an issue on github to fix this problem")
def resizeEvent(self, evt):
width = evt.size().width()
height = evt.size().height()
#print(f"width:{width}")
#print(f"height:{height}")
from Panda3DWorld import resize_buffer
#resize_buffer(width, height)
lens = self.panda3DWorld.cam.node().get_lens()
# lens.set_film_size(self.initial_film_size.width() * evt.size().width() / self.initial_size.width(),
# self.initial_film_size.height() * evt.size().height() / self.initial_size.height())
#self.sync_panda3d_window_size(width, height)
#self.panda3DWorld.buff.setSize(evt.size().width(), evt.size().height())
def minimumSizeHint(self):
return QSize(400, 300)
# Use the paint event to pull the contents of the panda texture to the widget
# def paintEvent(self, event):
# if self.panda3DWorld.screenTexture.mightHaveRamImage():
# tex = self.panda3DWorld.screenTexture
# gsg = base.win.getGsg()
# #self.panda3DWorld.screenTexture.store(gsg)
# base.graphicsEngine.extractTextureData(tex, gsg)
# self.panda3DWorld.screenTexture.setFormat(Texture.FRgba32)
# data = self.panda3DWorld.screenTexture.getRamImage().getData()
# img = QImage(data, self.panda3DWorld.screenTexture.getXSize(), self.panda3DWorld.screenTexture.getYSize(),
# QImage.Format_ARGB32).mirrored()
# self.paintSurface.begin(self)
# self.paintSurface.drawImage(0, 0, img)
# self.paintSurface.end()
# def paintEvent(self, event):
# tex = self.panda3DWorld.qt_output_tex
#
# gsg = base.win.getGsg()
#
#
# if not self.rp_sync_requested:
# base.graphicsEngine.extractTextureData(tex, gsg)
# self.rp_sync_requested = True
# self.update()
# return
#
# if tex.hasRamImage():
# data = tex.getRamImage().getData()
# width = tex.getXSize()
# height = tex.getYSize()
#
# # ✅ 应该 data 长度 = width * height * 4
# img = QImage(data, width, height, QImage.Format_ARGB32).mirrored()
#
# painter = QPainter(self)
# painter.drawImage(0, 0, img)
# painter.end()
#
# self.rp_sync_requested = False
# else:
# print("⚠️ Texture has no RAM image yet, retrying next frame")
# self.rp_sync_requested = False
# self.update()
# def paintEvent(self, event):
# tex = self.panda3DWorld.qt_output_tex
#
# gsg = base.win.getGsg()
#
# if not tex.hasRamImage():
# base.graphicsEngine.extractTextureData(tex, gsg)
# self.update() # 请求下一帧更新
# return
#
# data = tex.getRamImage().getData()
# width = tex.getXSize()
# height = tex.getYSize()
# expected_len = width * height * 4
#
# if len(data) != expected_len:
# print(f"⚠️ 像素数据长度异常({len(data)} != {expected_len}),跳过绘制")
# self.update()
# return
#
# # 一切正常才绘制
# img = QImage(data, width, height, QImage.Format_ARGB32).mirrored()
#
# painter = QPainter(self)
# painter.drawImage(0, 0, img)
# painter.end()
def paintEvent(self, event):
tex = self.panda3DWorld.qt_output_tex
gsg = base.win.getGsg()
if not gsg:
self.update()
return
if not tex.hasRamImage():
base.graphicsEngine.extractTextureData(tex, gsg)
self.update()
return
data = tex.getRamImage().getData()
tex_width = tex.getXSize()
tex_height = tex.getYSize()
expected_len = tex_width * tex_height * 4
if len(data) != expected_len:
print(f"⚠️ 像素数据长度异常({len(data)} != {expected_len}),跳过绘制")
self.update()
return
img = QImage(data, tex_width, tex_height, QImage.Format_ARGB32).mirrored()
widget_width = self.width()
widget_height = self.height()
painter = QPainter(self)
# 【保持宽高比的缩放】
scaled_img = img.scaled(widget_width, widget_height, Qt.KeepAspectRatio, Qt.SmoothTransformation)
# 居中绘制
x_offset = (widget_width - scaled_img.width()) // 2
y_offset = (widget_height - scaled_img.height()) // 2
painter.drawImage(x_offset, y_offset, scaled_img)
painter.end()
def sync_panda3d_window_size(self, width, height):
"""同步 Panda3D 窗口尺寸到 Qt 窗口尺寸"""
try:
from panda3d.core import WindowProperties, LVecBase2i
# 确保尺寸是4的倍数RenderPipeline 要求)
adjusted_width = width - width % 4
adjusted_height = height - height % 4
# 更新窗口属性
props = WindowProperties()
props.setSize(adjusted_width, adjusted_height)
# 对于 offscreen 渲染,直接更新窗口属性
if self.panda3DWorld.win:
self.panda3DWorld.win.request_properties(props)
# 手动触发 RenderPipeline 的尺寸更新逻辑
if hasattr(self.panda3DWorld, 'render_pipeline'):
# 更新全局分辨率
from RenderPipelineFile.rpcore.globals import Globals
Globals.native_resolution = LVecBase2i(adjusted_width, adjusted_height)
# 重新计算渲染分辨率
self.panda3DWorld.render_pipeline._compute_render_resolution()
# 通知各个管理器处理尺寸变化
self.panda3DWorld.render_pipeline.light_mgr.compute_tile_size()
self.panda3DWorld.render_pipeline.stage_mgr.handle_window_resize()
if hasattr(self.panda3DWorld.render_pipeline, 'debugger'):
self.panda3DWorld.render_pipeline.debugger.handle_window_resize()
# 触发插件的窗口尺寸变化钩子
self.panda3DWorld.render_pipeline.plugin_mgr.trigger_hook("window_resized")
print(f"Panda3D 窗口尺寸已同步为: {adjusted_width} x {adjusted_height}")
except Exception as e:
print(f"同步 Panda3D 窗口尺寸失败: {str(e)}")
from PyQt5 import QtWidgets, QtGui
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from direct.task.TaskManagerGlobal import taskMgr
from QMeta3D.QMeta3D_Buttons_Translation import QMeta3D_Button_translation
from QMeta3D.QMeta3D_Keys_Translation import QMeta3D_Key_translation
from QMeta3D.QMeta3D_Modifiers_Translation import QMeta3D_Modifier_translation
__all__ = ["QMeta3DWidget"]
class QMeta3DSynchronizer(QTimer):
def __init__(self, qMeta3DWidget, FPS=60):
QTimer.__init__(self)
self.qMeta3DWidget = qMeta3DWidget
dt = 1000 / FPS
self.setInterval(int(dt))
self.timeout.connect(self.tick)
def tick(self):
try:
# 在渲染前清理可能损坏的TransformState对象
from panda3d.core import TransformState
TransformState.clear_cache()
taskMgr.step()
self.qMeta3DWidget.update()
except AssertionError as e:
# 专门处理 TransformState has_mat() 断言错误
if "has_mat" in str(e):
print(f"警告: 检测到TransformState断言错误已静默处理: {e}")
# 尝试恢复渲染状态
try:
# 强制清理缓存并重试
from panda3d.core import TransformState, RenderState
TransformState.clear_cache()
RenderState.clear_cache()
taskMgr.step()
self.qMeta3DWidget.update()
except:
pass
else:
# 重新抛出其他断言错误
raise
except Exception as e:
# 静默处理其他所有异常
print(f"警告: 检测到异常,已静默处理: {e}")
def get_panda_key_modifiers(evt):
panda_mods = []
qt_mods = evt.modifiers()
for qt_mod, panda_mod in QMeta3D_Modifier_translation.items():
if (qt_mods & qt_mod) == qt_mod:
panda_mods.append(panda_mod)
return panda_mods
def get_panda_key_modifiers_prefix(evt):
# join all modifiers (except NoModifier, which is None) with '-'
prefix = "-".join([mod for mod in get_panda_key_modifiers(evt) if mod is not None])
# if the prefix is not empty, append a '-'
if prefix:
prefix += '-'
return prefix
class QMeta3DWidget(QWidget):
def __init__(self, Meta3DWorld, parent=None, FPS=60, debug=False):
QWidget.__init__(self, parent)
self.rp_sync_requested = False
# set fixed geometry
self.Meta3DWorld = Meta3DWorld
self.Meta3DWorld.set_parent(self)
# Setup a timer in Qt that runs taskMgr.step() to simulate Panda's own main loop
# pandaTimer = QTimer(self)
# pandaTimer.timeout.connect()
# pandaTimer.start(0)
self.setFocusPolicy(Qt.StrongFocus)
# Setup another timer that redraws this widget in a specific FPS
# redrawTimer = QTimer(self)
# redrawTimer.timeout.connect(self.update)
# redrawTimer.start(1000/FPS)
self.paintSurface = QPainter()
self.rotate = QTransform()
self.rotate.rotate(180)
self.out_image = QImage()
size = self.Meta3DWorld.cam.node().get_lens().get_film_size()
self.initial_film_size = QSizeF(size.x, size.y)
self.initial_size = self.size()
self.synchronizer = QMeta3DSynchronizer(self, FPS)
self.synchronizer.start()
self.debug = debug
def mousePressEvent(self, evt):
button = evt.button()
try:
b = "{}{}".format(get_panda_key_modifiers_prefix(evt), QMeta3D_Button_translation[button])
if self.debug:
print(b)
messenger.send(b,[{"x":evt.x(),"y":evt.y()}])
except:
print("Unimplemented button. Please send an issue on github to fix this problem")
def mouseMoveEvent(self, evt:QtGui.QMouseEvent):
button = evt.button()
try:
b = "mouse-move"
if self.debug:
print(b)
messenger.send(b,[{"x":evt.x(),"y":evt.y()}])
except:
print("Unimplemented button. Please send an issue on github to fix this problem")
def mouseReleaseEvent(self, evt):
button = evt.button()
try:
b = "{}{}-up".format(get_panda_key_modifiers_prefix(evt), QMeta3D_Button_translation[button])
if self.debug:
print(b)
messenger.send(b,[{"x":evt.x(),"y":evt.y()}])
except:
print("Unimplemented button. Please send an issue on github to fix this problem")
def wheelEvent(self, evt):
delta = evt.angleDelta().y()
try:
if self.debug:
print(f"wheel {delta}")
messenger.send('wheel',[{"delta":delta}])
except:
print("Unimplemented button. Please send an issue on github to fix this problem")
def keyPressEvent(self, evt):
key = evt.key()
try:
k = "{}{}".format(get_panda_key_modifiers_prefix(evt), QMeta3D_Key_translation[key])
if self.debug:
print(k)
messenger.send(k)
except:
print("Unimplemented key. Please send an issue on github to fix this problem")
def keyReleaseEvent(self, evt):
key = evt.key()
try:
k = "{}{}-up".format(get_panda_key_modifiers_prefix(evt), QMeta3D_Key_translation[key])
if self.debug:
print(k)
messenger.send(k)
except:
print("Unimplemented key. Please send an issue on github to fix this problem")
def resizeEvent(self, evt):
width = evt.size().width()
height = evt.size().height()
#print(f"width:{width}")
#print(f"height:{height}")
from Meta3DWorld import resize_buffer
lens = self.Meta3DWorld.cam.node().get_lens()
def minimumSizeHint(self):
return QSize(400, 300)
def paintEvent(self, event):
tex = self.Meta3DWorld.qt_output_tex
gsg = base.win.getGsg()
if not gsg:
self.update()
return
if not tex.hasRamImage():
base.graphicsEngine.extractTextureData(tex, gsg)
self.update()
return
data = tex.getRamImage().getData()
tex_width = tex.getXSize()
tex_height = tex.getYSize()
expected_len = tex_width * tex_height * 4
if len(data) != expected_len:
print(f"⚠️ 像素数据长度异常({len(data)} != {expected_len}),跳过绘制")
self.update()
return
img = QImage(data, tex_width, tex_height, QImage.Format_ARGB32).mirrored()
widget_width = self.width()
widget_height = self.height()
painter = QPainter(self)
# 【保持宽高比的缩放】
scaled_img = img.scaled(widget_width, widget_height, Qt.KeepAspectRatio, Qt.SmoothTransformation)
# 居中绘制
x_offset = (widget_width - scaled_img.width()) // 2
y_offset = (widget_height - scaled_img.height()) // 2
painter.drawImage(x_offset, y_offset, scaled_img)
painter.end()
def sync_Meta3d_window_size(self, width, height):
try:
from panda3d.core import WindowProperties, LVecBase2i
# 确保尺寸是4的倍数RenderPipeline 要求)
adjusted_width = width - width % 4
adjusted_height = height - height % 4
# 更新窗口属性
props = WindowProperties()
props.setSize(adjusted_width, adjusted_height)
# 对于 offscreen 渲染,直接更新窗口属性
if self.Meta3DWorld.win:
self.Meta3DWorld.win.request_properties(props)
# 手动触发 RenderPipeline 的尺寸更新逻辑
if hasattr(self.Meta3DWorld, 'render_pipeline'):
# 更新全局分辨率
from RenderPipelineFile.rpcore.globals import Globals
Globals.native_resolution = LVecBase2i(adjusted_width, adjusted_height)
# 重新计算渲染分辨率
self.Meta3DWorld.render_pipeline._compute_render_resolution()
# 通知各个管理器处理尺寸变化
self.Meta3DWorld.render_pipeline.light_mgr.compute_tile_size()
self.Meta3DWorld.render_pipeline.stage_mgr.handle_window_resize()
if hasattr(self.Meta3DWorld.render_pipeline, 'debugger'):
self.Meta3DWorld.render_pipeline.debugger.handle_window_resize()
# 触发插件的窗口尺寸变化钩子
self.Meta3DWorld.render_pipeline.plugin_mgr.trigger_hook("window_resized")
except Exception as e:
print(f"同步 Meta3D 窗口尺寸失败: {str(e)}")

View File

@ -1,52 +1,43 @@
# -*- coding: utf-8-*-
"""
Module : QPanda3D_Translation_Buttons
Author : Niklas Mevenkamp
Description :
Contains a dictionary that translates QT mouse events to panda3d
mouse events.
"""
# PyQt imports
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import sys
__all__ = ["QPanda3D_Keys_Translation"]
QPanda3D_Button_translation ={
Qt.NoButton:'',
Qt.AllButtons:'unknown',
Qt.LeftButton:'mouse1',
Qt.RightButton:'mouse2',
Qt.MidButton:'mouse3',
Qt.MiddleButton:'mouse3',
Qt.BackButton:'unknown',
Qt.XButton1:'unknown',
Qt.ExtraButton1:'unknown',
Qt.ForwardButton:'unknown',
Qt.XButton2:'unknown',
Qt.ExtraButton2:'unknown',
Qt.TaskButton:'unknown',
Qt.ExtraButton3:'unknown',
Qt.ExtraButton4:'unknown',
Qt.ExtraButton5:'unknown',
Qt.ExtraButton6:'unknown',
Qt.ExtraButton7:'unknown',
Qt.ExtraButton8:'unknown',
Qt.ExtraButton9:'unknown',
Qt.ExtraButton10:'unknown',
Qt.ExtraButton11:'unknown',
Qt.ExtraButton12:'unknown',
Qt.ExtraButton13:'unknown',
Qt.ExtraButton14:'unknown',
Qt.ExtraButton15:'unknown',
Qt.ExtraButton16:'unknown',
Qt.ExtraButton17:'unknown',
Qt.ExtraButton18:'unknown',
Qt.ExtraButton19:'unknown',
Qt.ExtraButton20:'unknown',
Qt.ExtraButton21:'unknown',
Qt.ExtraButton22:'unknown',
Qt.ExtraButton23:'unknown',
Qt.ExtraButton24:'unknown',
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import sys
__all__ = ["QMeta3D_Keys_Translation.py"]
QMeta3D_Button_translation ={
Qt.NoButton:'',
Qt.AllButtons:'unknown',
Qt.LeftButton:'mouse1',
Qt.RightButton:'mouse2',
Qt.MidButton:'mouse3',
Qt.MiddleButton:'mouse3',
Qt.BackButton:'unknown',
Qt.XButton1:'unknown',
Qt.ExtraButton1:'unknown',
Qt.ForwardButton:'unknown',
Qt.XButton2:'unknown',
Qt.ExtraButton2:'unknown',
Qt.TaskButton:'unknown',
Qt.ExtraButton3:'unknown',
Qt.ExtraButton4:'unknown',
Qt.ExtraButton5:'unknown',
Qt.ExtraButton6:'unknown',
Qt.ExtraButton7:'unknown',
Qt.ExtraButton8:'unknown',
Qt.ExtraButton9:'unknown',
Qt.ExtraButton10:'unknown',
Qt.ExtraButton11:'unknown',
Qt.ExtraButton12:'unknown',
Qt.ExtraButton13:'unknown',
Qt.ExtraButton14:'unknown',
Qt.ExtraButton15:'unknown',
Qt.ExtraButton16:'unknown',
Qt.ExtraButton17:'unknown',
Qt.ExtraButton18:'unknown',
Qt.ExtraButton19:'unknown',
Qt.ExtraButton20:'unknown',
Qt.ExtraButton21:'unknown',
Qt.ExtraButton22:'unknown',
Qt.ExtraButton23:'unknown',
Qt.ExtraButton24:'unknown',
}

View File

@ -1,486 +1,477 @@
# -*- coding: utf-8-*-
"""
Module : QPanda3D_Translation_Keys
Author : Saifeddine ALOUI
Description :
Contains a dictionary that translates QT keyboard events to panda3d
keyboard events.
"""
# PyQt imports
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import sys
__all__ = ["QPanda3D_Keys_Translation"]
QPanda3D_Key_translation ={
Qt.Key_0:'0',
Qt.Key_1:'1',
Qt.Key_2:'2',
Qt.Key_3:'3',
Qt.Key_4:'4',
Qt.Key_5:'5',
Qt.Key_6:'6',
Qt.Key_7:'7',
Qt.Key_8:'8',
Qt.Key_9:'9',
Qt.Key_A:'a',
Qt.Key_AE:'ae',
Qt.Key_Aacute:'unknown',
Qt.Key_Acircumflex:'unknown',
Qt.Key_AddFavorite:'unknown',
Qt.Key_Adiaeresis:'unknown',
Qt.Key_Agrave:'unknown',
Qt.Key_Alt:'lalt',
Qt.Key_AltGr:'unknown',
Qt.Key_Ampersand:'unknown',
Qt.Key_Any:'unknown',
Qt.Key_Apostrophe:'unknown',
Qt.Key_ApplicationLeft:'unknown',
Qt.Key_ApplicationRight:'unknown',
Qt.Key_Aring:'unknown',
Qt.Key_AsciiCircum:'unknown',
Qt.Key_AsciiTilde:'unknown',
Qt.Key_Asterisk:'unknown',
Qt.Key_At:'unknown',
Qt.Key_Atilde:'unknown',
Qt.Key_AudioCycleTrack:'unknown',
Qt.Key_AudioForward:'unknown',
Qt.Key_AudioRandomPlay:'unknown',
Qt.Key_AudioRepeat:'unknown',
Qt.Key_AudioRewind:'unknown',
Qt.Key_Away:'unknown',
Qt.Key_B:'b',
Qt.Key_Back:'unknown',
Qt.Key_BackForward:'unknown',
Qt.Key_Backslash:'unknown',
Qt.Key_Backspace:'unknown',
Qt.Key_Backtab:'unknown',
Qt.Key_Bar:'unknown',
Qt.Key_BassBoost:'unknown',
Qt.Key_BassDown:'unknown',
Qt.Key_BassUp:'unknown',
Qt.Key_Battery:'unknown',
Qt.Key_Blue:'unknown',
Qt.Key_Bluetooth:'unknown',
Qt.Key_Book:'unknown',
Qt.Key_BraceLeft:'unknown',
Qt.Key_BraceRight:'unknown',
Qt.Key_BracketLeft:'unknown',
Qt.Key_BracketRight:'unknown',
Qt.Key_BrightnessAdjust:'unknown',
Qt.Key_C:'c',
Qt.Key_CD:'unknown',
Qt.Key_Calculator:'unknown',
Qt.Key_Calendar:'unknown',
Qt.Key_Call:'unknown',
Qt.Key_Camera:'unknown',
Qt.Key_CameraFocus:'unknown',
Qt.Key_Cancel:'unknown',
Qt.Key_CapsLock:'unknown',
Qt.Key_Ccedilla:'unknown',
Qt.Key_ChannelDown:'unknown',
Qt.Key_ChannelUp:'unknown',
Qt.Key_Clear:'unknown',
Qt.Key_ClearGrab:'unknown',
Qt.Key_Close:'unknown',
Qt.Key_Codeinput:'unknown',
Qt.Key_Colon:'unknown',
Qt.Key_Comma:'unknown',
Qt.Key_Community:'unknown',
Qt.Key_Context1:'unknown',
Qt.Key_Context2:'unknown',
Qt.Key_Context3:'unknown',
Qt.Key_Context4:'unknown',
Qt.Key_ContrastAdjust:'unknown',
Qt.Key_Control:'control',
Qt.Key_Copy:'unknown',
Qt.Key_Cut:'unknown',
Qt.Key_D:'d',
Qt.Key_DOS:'unknown',
Qt.Key_Dead_A:'unknown',
Qt.Key_Dead_Abovecomma:'unknown',
Qt.Key_Dead_Abovedot:'unknown',
Qt.Key_Dead_Abovereversedcomma:'unknown',
Qt.Key_Dead_Abovering:'unknown',
Qt.Key_Dead_Aboveverticalline:'unknown',
Qt.Key_Dead_Acute:'unknown',
Qt.Key_Dead_Belowbreve:'unknown',
Qt.Key_Dead_Belowcircumflex:'unknown',
Qt.Key_Dead_Belowcomma:'unknown',
Qt.Key_Dead_Belowdiaeresis:'unknown',
Qt.Key_Dead_Belowdot:'unknown',
Qt.Key_Dead_Belowmacron:'unknown',
Qt.Key_Dead_Belowring:'unknown',
Qt.Key_Dead_Belowtilde:'unknown',
Qt.Key_Dead_Belowverticalline:'unknown',
Qt.Key_Dead_Breve:'unknown',
Qt.Key_Dead_Capital_Schwa:'unknown',
Qt.Key_Dead_Caron:'unknown',
Qt.Key_Dead_Cedilla:'unknown',
Qt.Key_Dead_Circumflex:'unknown',
Qt.Key_Dead_Currency:'unknown',
Qt.Key_Dead_Diaeresis:'unknown',
Qt.Key_Dead_Doubleacute:'unknown',
Qt.Key_Dead_Doublegrave:'unknown',
Qt.Key_Dead_E:'unknown',
Qt.Key_Dead_Grave:'unknown',
Qt.Key_Dead_Greek:'unknown',
Qt.Key_Dead_Hook:'unknown',
Qt.Key_Dead_Horn:'unknown',
Qt.Key_Dead_I:'unknown',
Qt.Key_Dead_Invertedbreve:'unknown',
Qt.Key_Dead_Iota:'unknown',
Qt.Key_Dead_Longsolidusoverlay:'unknown',
Qt.Key_Dead_Lowline:'unknown',
Qt.Key_Dead_Macron:'unknown',
Qt.Key_Dead_O:'unknown',
Qt.Key_Dead_Ogonek:'unknown',
Qt.Key_Dead_Semivoiced_Sound:'unknown',
Qt.Key_Dead_Small_Schwa:'unknown',
Qt.Key_Dead_Stroke:'unknown',
Qt.Key_Dead_Tilde:'unknown',
Qt.Key_Dead_U:'unknown',
Qt.Key_Dead_Voiced_Sound:'unknown',
Qt.Key_Dead_a:'unknown',
Qt.Key_Dead_e:'unknown',
Qt.Key_Dead_i:'unknown',
Qt.Key_Dead_o:'unknown',
Qt.Key_Dead_u:'unknown',
Qt.Key_Delete:'delete',
Qt.Key_Direction_L:'unknown',
Qt.Key_Direction_R:'unknown',
Qt.Key_Display:'unknown',
Qt.Key_Documents:'unknown',
Qt.Key_Dollar:'unknown',
Qt.Key_Down:'arrow_down',
Qt.Key_E:'e',
Qt.Key_ETH:'unknown',
Qt.Key_Eacute:'unknown',
Qt.Key_Ecircumflex:'unknown',
Qt.Key_Ediaeresis:'unknown',
Qt.Key_Egrave:'unknown',
Qt.Key_Eisu_Shift:'unknown',
Qt.Key_Eisu_toggle:'unknown',
Qt.Key_Eject:'unknown',
Qt.Key_End:'unknown',
Qt.Key_Enter:'unknown',
Qt.Key_Equal:'unknown',
Qt.Key_Escape:'escape',
Qt.Key_Excel:'unknown',
Qt.Key_Exclam:'unknown',
Qt.Key_Execute:'unknown',
Qt.Key_Exit:'unknown',
Qt.Key_Explorer:'unknown',
Qt.Key_F:'f',
Qt.Key_F1:'f1',
Qt.Key_F10:'f10',
Qt.Key_F11:'f11',
Qt.Key_F12:'f12',
Qt.Key_F13:'f13',
Qt.Key_F14:'f14',
Qt.Key_F15:'f15',
Qt.Key_F16:'f16',
Qt.Key_F17:'f17',
Qt.Key_F18:'f18',
Qt.Key_F19:'unknown',
Qt.Key_F2:'unknown',
Qt.Key_F20:'unknown',
Qt.Key_F21:'unknown',
Qt.Key_F22:'unknown',
Qt.Key_F23:'unknown',
Qt.Key_F24:'unknown',
Qt.Key_F25:'unknown',
Qt.Key_F26:'unknown',
Qt.Key_F27:'unknown',
Qt.Key_F28:'unknown',
Qt.Key_F29:'unknown',
Qt.Key_F3:'f3',
Qt.Key_F30:'unknown',
Qt.Key_F31:'unknown',
Qt.Key_F32:'unknown',
Qt.Key_F33:'unknown',
Qt.Key_F34:'unknown',
Qt.Key_F35:'unknown',
Qt.Key_F4:'f4',
Qt.Key_F5:'f5',
Qt.Key_F6:'f6',
Qt.Key_F7:'f7',
Qt.Key_F8:'f8',
Qt.Key_F9:'f9',
Qt.Key_Favorites:'unknown',
Qt.Key_Finance:'unknown',
Qt.Key_Find:'unknown',
Qt.Key_Flip:'unknown',
Qt.Key_Forward:'unknown',
Qt.Key_G:'g',
Qt.Key_Game:'unknown',
Qt.Key_Go:'unknown',
Qt.Key_Greater:'unknown',
Qt.Key_Green:'unknown',
Qt.Key_Guide:'unknown',
Qt.Key_H:'h',
Qt.Key_Hangul:'unknown',
Qt.Key_Hangul_Banja:'unknown',
Qt.Key_Hangul_End:'unknown',
Qt.Key_Hangul_Hanja:'unknown',
Qt.Key_Hangul_Jamo:'unknown',
Qt.Key_Hangul_Jeonja:'unknown',
Qt.Key_Hangul_PostHanja:'unknown',
Qt.Key_Hangul_PreHanja:'unknown',
Qt.Key_Hangul_Romaja:'unknown',
Qt.Key_Hangul_Special:'unknown',
Qt.Key_Hangul_Start:'unknown',
Qt.Key_Hangup:'unknown',
Qt.Key_Hankaku:'unknown',
Qt.Key_Help:'unknown',
Qt.Key_Henkan:'unknown',
Qt.Key_Hibernate:'unknown',
Qt.Key_Hiragana:'unknown',
Qt.Key_Hiragana_Katakana:'unknown',
Qt.Key_History:'unknown',
Qt.Key_Home:'home',
Qt.Key_HomePage:'unknown',
Qt.Key_HotLinks:'unknown',
Qt.Key_Hyper_L:'unknown',
Qt.Key_Hyper_R:'unknown',
Qt.Key_I:'i',
Qt.Key_Iacute:'unknown',
Qt.Key_Icircumflex:'unknown',
Qt.Key_Idiaeresis:'unknown',
Qt.Key_Igrave:'unknown',
Qt.Key_Info:'unknown',
Qt.Key_Insert:'unknown',
Qt.Key_J:'j',
Qt.Key_K:'k',
Qt.Key_Kana_Lock:'unknown',
Qt.Key_Kana_Shift:'unknown',
Qt.Key_Kanji:'unknown',
Qt.Key_Katakana:'unknown',
Qt.Key_KeyboardBrightnessDown:'unknown',
Qt.Key_KeyboardBrightnessUp:'unknown',
Qt.Key_KeyboardLightOnOff:'unknown',
Qt.Key_L:'l',
Qt.Key_LastNumberRedial:'unknown',
Qt.Key_Launch0:'unknown',
Qt.Key_Launch1:'unknown',
Qt.Key_Launch2:'unknown',
Qt.Key_Launch3:'unknown',
Qt.Key_Launch4:'unknown',
Qt.Key_Launch5:'unknown',
Qt.Key_Launch6:'unknown',
Qt.Key_Launch7:'unknown',
Qt.Key_Launch8:'unknown',
Qt.Key_Launch9:'unknown',
Qt.Key_LaunchA:'unknown',
Qt.Key_LaunchB:'unknown',
Qt.Key_LaunchC:'unknown',
Qt.Key_LaunchD:'unknown',
Qt.Key_LaunchE:'unknown',
Qt.Key_LaunchF:'unknown',
Qt.Key_LaunchG:'unknown',
Qt.Key_LaunchH:'unknown',
Qt.Key_LaunchMail:'unknown',
Qt.Key_LaunchMedia:'unknown',
Qt.Key_Left:'arrow_left',
Qt.Key_Less:'unknown',
Qt.Key_LightBulb:'unknown',
Qt.Key_LogOff:'unknown',
Qt.Key_M:'m',
Qt.Key_MailForward:'unknown',
Qt.Key_Market:'unknown',
Qt.Key_Massyo:'unknown',
Qt.Key_MediaLast:'unknown',
Qt.Key_MediaNext:'unknown',
Qt.Key_MediaPause:'unknown',
Qt.Key_MediaPlay:'unknown',
Qt.Key_MediaPrevious:'unknown',
Qt.Key_MediaRecord:'unknown',
Qt.Key_MediaStop:'unknown',
Qt.Key_MediaTogglePlayPause:'unknown',
Qt.Key_Meeting:'unknown',
Qt.Key_Memo:'unknown',
Qt.Key_Menu:'unknown',
Qt.Key_MenuKB:'unknown',
Qt.Key_MenuPB:'unknown',
Qt.Key_Messenger:'unknown',
Qt.Key_Meta:'unknown',
Qt.Key_MicMute:'unknown',
Qt.Key_MicVolumeDown:'unknown',
Qt.Key_MicVolumeUp:'unknown',
Qt.Key_Minus:'unknown',
Qt.Key_Mode_switch:'unknown',
Qt.Key_MonBrightnessDown:'unknown',
Qt.Key_MonBrightnessUp:'unknown',
Qt.Key_Muhenkan:'unknown',
Qt.Key_Multi_key:'unknown',
Qt.Key_MultipleCandidate:'unknown',
Qt.Key_Music:'unknown',
Qt.Key_MySites:'unknown',
Qt.Key_N:'n',
Qt.Key_New:'unknown',
Qt.Key_News:'unknown',
Qt.Key_No:'unknown',
Qt.Key_Ntilde:'unknown',
Qt.Key_NumLock:'unknown',
Qt.Key_NumberSign:'unknown',
Qt.Key_O:'o',
Qt.Key_Oacute:'unknown',
Qt.Key_Ocircumflex:'unknown',
Qt.Key_Odiaeresis:'unknown',
Qt.Key_OfficeHome:'unknown',
Qt.Key_Ograve:'unknown',
Qt.Key_Ooblique:'unknown',
Qt.Key_Open:'unknown',
Qt.Key_OpenUrl:'unknown',
Qt.Key_Option:'unknown',
Qt.Key_Otilde:'unknown',
Qt.Key_P:'p',
Qt.Key_PageDown:'unknown',
Qt.Key_PageUp:'unknown',
Qt.Key_ParenLeft:'unknown',
Qt.Key_ParenRight:'unknown',
Qt.Key_Paste:'unknown',
Qt.Key_Pause:'unknown',
Qt.Key_Percent:'unknown',
Qt.Key_Period:'unknown',
Qt.Key_Phone:'unknown',
Qt.Key_Pictures:'unknown',
Qt.Key_Play:'unknown',
Qt.Key_Plus:'unknown',
Qt.Key_PowerDown:'unknown',
Qt.Key_PowerOff:'unknown',
Qt.Key_PreviousCandidate:'unknown',
Qt.Key_Print:'unknown',
Qt.Key_Printer:'unknown',
Qt.Key_Q:'q',
Qt.Key_Question:'unknown',
Qt.Key_QuoteDbl:'unknown',
Qt.Key_QuoteLeft:'unknown',
Qt.Key_R:'r',
Qt.Key_Red:'unknown',
Qt.Key_Redo:'unknown',
Qt.Key_Refresh:'unknown',
Qt.Key_Reload:'unknown',
Qt.Key_Reply:'unknown',
Qt.Key_Return:'unknown',
Qt.Key_Right:'arrow_right',
Qt.Key_Romaji:'unknown',
Qt.Key_RotateWindows:'unknown',
Qt.Key_RotationKB:'unknown',
Qt.Key_RotationPB:'unknown',
Qt.Key_S:'s',
Qt.Key_Save:'unknown',
Qt.Key_ScreenSaver:'unknown',
Qt.Key_ScrollLock:'unknown',
Qt.Key_Search:'unknown',
Qt.Key_Select:'unknown',
Qt.Key_Semicolon:'unknown',
Qt.Key_Send:'unknown',
Qt.Key_Settings:'unknown',
Qt.Key_Shift:'unknown',
Qt.Key_Shop:'unknown',
Qt.Key_SingleCandidate:'unknown',
Qt.Key_Slash:'unknown',
Qt.Key_Sleep:'unknown',
Qt.Key_Space:'space',
Qt.Key_Spell:'unknown',
Qt.Key_SplitScreen:'unknown',
Qt.Key_Standby:'unknown',
Qt.Key_Stop:'unknown',
Qt.Key_Subtitle:'unknown',
Qt.Key_Super_L:'unknown',
Qt.Key_Super_R:'unknown',
Qt.Key_Support:'unknown',
Qt.Key_Suspend:'unknown',
Qt.Key_SysReq:'unknown',
Qt.Key_T:'t',
Qt.Key_THORN:'unknown',
Qt.Key_Tab:'unknown',
Qt.Key_TaskPane:'unknown',
Qt.Key_Terminal:'unknown',
Qt.Key_Time:'unknown',
Qt.Key_ToDoList:'unknown',
Qt.Key_ToggleCallHangup:'unknown',
Qt.Key_Tools:'unknown',
Qt.Key_TopMenu:'unknown',
Qt.Key_TouchpadOff:'unknown',
Qt.Key_TouchpadOn:'unknown',
Qt.Key_TouchpadToggle:'unknown',
Qt.Key_Touroku:'unknown',
Qt.Key_Travel:'unknown',
Qt.Key_TrebleDown:'unknown',
Qt.Key_TrebleUp:'unknown',
Qt.Key_U:'u',
Qt.Key_UWB:'unknown',
Qt.Key_Uacute:'unknown',
Qt.Key_Ucircumflex:'unknown',
Qt.Key_Udiaeresis:'unknown',
Qt.Key_Ugrave:'unknown',
Qt.Key_Underscore:'unknown',
Qt.Key_Undo:'unknown',
Qt.Key_Up:'arrow_up',
Qt.Key_V:'v',
Qt.Key_Video:'unknown',
Qt.Key_View:'unknown',
Qt.Key_VoiceDial:'unknown',
Qt.Key_VolumeDown:'unknown',
Qt.Key_VolumeMute:'unknown',
Qt.Key_VolumeUp:'unknown',
Qt.Key_W:'w',
Qt.Key_WLAN:'unknown',
Qt.Key_WWW:'unknown',
Qt.Key_WakeUp:'unknown',
Qt.Key_WebCam:'unknown',
Qt.Key_Word:'unknown',
Qt.Key_X:'x',
Qt.Key_Xfer:'unknown',
Qt.Key_Y:'y',
Qt.Key_Yacute:'unknown',
Qt.Key_Yellow:'unknown',
Qt.Key_Yes:'unknown',
Qt.Key_Z:'z',
Qt.Key_Zenkaku:'unknown',
Qt.Key_Zenkaku_Hankaku:'unknown',
Qt.Key_Zoom:'unknown',
Qt.Key_ZoomIn:'unknown',
Qt.Key_ZoomOut:'unknown',
Qt.Key_acute:'unknown',
Qt.Key_brokenbar:'unknown',
Qt.Key_cedilla:'unknown',
Qt.Key_cent:'unknown',
Qt.Key_copyright:'unknown',
Qt.Key_currency:'unknown',
Qt.Key_degree:'unknown',
Qt.Key_diaeresis:'unknown',
Qt.Key_division:'unknown',
Qt.Key_exclamdown:'unknown',
Qt.Key_guillemotleft:'unknown',
Qt.Key_guillemotright:'unknown',
Qt.Key_hyphen:'unknown',
Qt.Key_iTouch:'unknown',
Qt.Key_macron:'unknown',
Qt.Key_masculine:'unknown',
Qt.Key_mu:'unknown',
Qt.Key_multiply:'unknown',
Qt.Key_nobreakspace:'unknown',
Qt.Key_notsign:'unknown',
Qt.Key_onehalf:'unknown',
Qt.Key_onequarter:'unknown',
Qt.Key_onesuperior:'unknown',
Qt.Key_ordfeminine:'unknown',
Qt.Key_paragraph:'unknown',
Qt.Key_periodcentered:'unknown',
Qt.Key_plusminus:'unknown',
Qt.Key_questiondown:'unknown',
Qt.Key_registered:'unknown',
Qt.Key_section:'unknown',
Qt.Key_ssharp:'unknown',
Qt.Key_sterling:'unknown',
Qt.Key_threequarters:'unknown',
Qt.Key_threesuperior:'unknown',
Qt.Key_twosuperior:'unknown',
Qt.Key_unknown:'unknown',
Qt.Key_ydiaeresis:'unknown',
Qt.Key_yen:'unknown',
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import sys
__all__ = ["QMeta3D_Keys_Translation.py"]
QMeta3D_Key_translation ={
Qt.Key_0:'0',
Qt.Key_1:'1',
Qt.Key_2:'2',
Qt.Key_3:'3',
Qt.Key_4:'4',
Qt.Key_5:'5',
Qt.Key_6:'6',
Qt.Key_7:'7',
Qt.Key_8:'8',
Qt.Key_9:'9',
Qt.Key_A:'a',
Qt.Key_AE:'ae',
Qt.Key_Aacute:'unknown',
Qt.Key_Acircumflex:'unknown',
Qt.Key_AddFavorite:'unknown',
Qt.Key_Adiaeresis:'unknown',
Qt.Key_Agrave:'unknown',
Qt.Key_Alt:'lalt',
Qt.Key_AltGr:'unknown',
Qt.Key_Ampersand:'unknown',
Qt.Key_Any:'unknown',
Qt.Key_Apostrophe:'unknown',
Qt.Key_ApplicationLeft:'unknown',
Qt.Key_ApplicationRight:'unknown',
Qt.Key_Aring:'unknown',
Qt.Key_AsciiCircum:'unknown',
Qt.Key_AsciiTilde:'unknown',
Qt.Key_Asterisk:'unknown',
Qt.Key_At:'unknown',
Qt.Key_Atilde:'unknown',
Qt.Key_AudioCycleTrack:'unknown',
Qt.Key_AudioForward:'unknown',
Qt.Key_AudioRandomPlay:'unknown',
Qt.Key_AudioRepeat:'unknown',
Qt.Key_AudioRewind:'unknown',
Qt.Key_Away:'unknown',
Qt.Key_B:'b',
Qt.Key_Back:'unknown',
Qt.Key_BackForward:'unknown',
Qt.Key_Backslash:'unknown',
Qt.Key_Backspace:'unknown',
Qt.Key_Backtab:'unknown',
Qt.Key_Bar:'unknown',
Qt.Key_BassBoost:'unknown',
Qt.Key_BassDown:'unknown',
Qt.Key_BassUp:'unknown',
Qt.Key_Battery:'unknown',
Qt.Key_Blue:'unknown',
Qt.Key_Bluetooth:'unknown',
Qt.Key_Book:'unknown',
Qt.Key_BraceLeft:'unknown',
Qt.Key_BraceRight:'unknown',
Qt.Key_BracketLeft:'unknown',
Qt.Key_BracketRight:'unknown',
Qt.Key_BrightnessAdjust:'unknown',
Qt.Key_C:'c',
Qt.Key_CD:'unknown',
Qt.Key_Calculator:'unknown',
Qt.Key_Calendar:'unknown',
Qt.Key_Call:'unknown',
Qt.Key_Camera:'unknown',
Qt.Key_CameraFocus:'unknown',
Qt.Key_Cancel:'unknown',
Qt.Key_CapsLock:'unknown',
Qt.Key_Ccedilla:'unknown',
Qt.Key_ChannelDown:'unknown',
Qt.Key_ChannelUp:'unknown',
Qt.Key_Clear:'unknown',
Qt.Key_ClearGrab:'unknown',
Qt.Key_Close:'unknown',
Qt.Key_Codeinput:'unknown',
Qt.Key_Colon:'unknown',
Qt.Key_Comma:'unknown',
Qt.Key_Community:'unknown',
Qt.Key_Context1:'unknown',
Qt.Key_Context2:'unknown',
Qt.Key_Context3:'unknown',
Qt.Key_Context4:'unknown',
Qt.Key_ContrastAdjust:'unknown',
Qt.Key_Control:'control',
Qt.Key_Copy:'unknown',
Qt.Key_Cut:'unknown',
Qt.Key_D:'d',
Qt.Key_DOS:'unknown',
Qt.Key_Dead_A:'unknown',
Qt.Key_Dead_Abovecomma:'unknown',
Qt.Key_Dead_Abovedot:'unknown',
Qt.Key_Dead_Abovereversedcomma:'unknown',
Qt.Key_Dead_Abovering:'unknown',
Qt.Key_Dead_Aboveverticalline:'unknown',
Qt.Key_Dead_Acute:'unknown',
Qt.Key_Dead_Belowbreve:'unknown',
Qt.Key_Dead_Belowcircumflex:'unknown',
Qt.Key_Dead_Belowcomma:'unknown',
Qt.Key_Dead_Belowdiaeresis:'unknown',
Qt.Key_Dead_Belowdot:'unknown',
Qt.Key_Dead_Belowmacron:'unknown',
Qt.Key_Dead_Belowring:'unknown',
Qt.Key_Dead_Belowtilde:'unknown',
Qt.Key_Dead_Belowverticalline:'unknown',
Qt.Key_Dead_Breve:'unknown',
Qt.Key_Dead_Capital_Schwa:'unknown',
Qt.Key_Dead_Caron:'unknown',
Qt.Key_Dead_Cedilla:'unknown',
Qt.Key_Dead_Circumflex:'unknown',
Qt.Key_Dead_Currency:'unknown',
Qt.Key_Dead_Diaeresis:'unknown',
Qt.Key_Dead_Doubleacute:'unknown',
Qt.Key_Dead_Doublegrave:'unknown',
Qt.Key_Dead_E:'unknown',
Qt.Key_Dead_Grave:'unknown',
Qt.Key_Dead_Greek:'unknown',
Qt.Key_Dead_Hook:'unknown',
Qt.Key_Dead_Horn:'unknown',
Qt.Key_Dead_I:'unknown',
Qt.Key_Dead_Invertedbreve:'unknown',
Qt.Key_Dead_Iota:'unknown',
Qt.Key_Dead_Longsolidusoverlay:'unknown',
Qt.Key_Dead_Lowline:'unknown',
Qt.Key_Dead_Macron:'unknown',
Qt.Key_Dead_O:'unknown',
Qt.Key_Dead_Ogonek:'unknown',
Qt.Key_Dead_Semivoiced_Sound:'unknown',
Qt.Key_Dead_Small_Schwa:'unknown',
Qt.Key_Dead_Stroke:'unknown',
Qt.Key_Dead_Tilde:'unknown',
Qt.Key_Dead_U:'unknown',
Qt.Key_Dead_Voiced_Sound:'unknown',
Qt.Key_Dead_a:'unknown',
Qt.Key_Dead_e:'unknown',
Qt.Key_Dead_i:'unknown',
Qt.Key_Dead_o:'unknown',
Qt.Key_Dead_u:'unknown',
Qt.Key_Delete:'delete',
Qt.Key_Direction_L:'unknown',
Qt.Key_Direction_R:'unknown',
Qt.Key_Display:'unknown',
Qt.Key_Documents:'unknown',
Qt.Key_Dollar:'unknown',
Qt.Key_Down:'arrow_down',
Qt.Key_E:'e',
Qt.Key_ETH:'unknown',
Qt.Key_Eacute:'unknown',
Qt.Key_Ecircumflex:'unknown',
Qt.Key_Ediaeresis:'unknown',
Qt.Key_Egrave:'unknown',
Qt.Key_Eisu_Shift:'unknown',
Qt.Key_Eisu_toggle:'unknown',
Qt.Key_Eject:'unknown',
Qt.Key_End:'unknown',
Qt.Key_Enter:'unknown',
Qt.Key_Equal:'unknown',
Qt.Key_Escape:'escape',
Qt.Key_Excel:'unknown',
Qt.Key_Exclam:'unknown',
Qt.Key_Execute:'unknown',
Qt.Key_Exit:'unknown',
Qt.Key_Explorer:'unknown',
Qt.Key_F:'f',
Qt.Key_F1:'f1',
Qt.Key_F10:'f10',
Qt.Key_F11:'f11',
Qt.Key_F12:'f12',
Qt.Key_F13:'f13',
Qt.Key_F14:'f14',
Qt.Key_F15:'f15',
Qt.Key_F16:'f16',
Qt.Key_F17:'f17',
Qt.Key_F18:'f18',
Qt.Key_F19:'unknown',
Qt.Key_F2:'unknown',
Qt.Key_F20:'unknown',
Qt.Key_F21:'unknown',
Qt.Key_F22:'unknown',
Qt.Key_F23:'unknown',
Qt.Key_F24:'unknown',
Qt.Key_F25:'unknown',
Qt.Key_F26:'unknown',
Qt.Key_F27:'unknown',
Qt.Key_F28:'unknown',
Qt.Key_F29:'unknown',
Qt.Key_F3:'f3',
Qt.Key_F30:'unknown',
Qt.Key_F31:'unknown',
Qt.Key_F32:'unknown',
Qt.Key_F33:'unknown',
Qt.Key_F34:'unknown',
Qt.Key_F35:'unknown',
Qt.Key_F4:'f4',
Qt.Key_F5:'f5',
Qt.Key_F6:'f6',
Qt.Key_F7:'f7',
Qt.Key_F8:'f8',
Qt.Key_F9:'f9',
Qt.Key_Favorites:'unknown',
Qt.Key_Finance:'unknown',
Qt.Key_Find:'unknown',
Qt.Key_Flip:'unknown',
Qt.Key_Forward:'unknown',
Qt.Key_G:'g',
Qt.Key_Game:'unknown',
Qt.Key_Go:'unknown',
Qt.Key_Greater:'unknown',
Qt.Key_Green:'unknown',
Qt.Key_Guide:'unknown',
Qt.Key_H:'h',
Qt.Key_Hangul:'unknown',
Qt.Key_Hangul_Banja:'unknown',
Qt.Key_Hangul_End:'unknown',
Qt.Key_Hangul_Hanja:'unknown',
Qt.Key_Hangul_Jamo:'unknown',
Qt.Key_Hangul_Jeonja:'unknown',
Qt.Key_Hangul_PostHanja:'unknown',
Qt.Key_Hangul_PreHanja:'unknown',
Qt.Key_Hangul_Romaja:'unknown',
Qt.Key_Hangul_Special:'unknown',
Qt.Key_Hangul_Start:'unknown',
Qt.Key_Hangup:'unknown',
Qt.Key_Hankaku:'unknown',
Qt.Key_Help:'unknown',
Qt.Key_Henkan:'unknown',
Qt.Key_Hibernate:'unknown',
Qt.Key_Hiragana:'unknown',
Qt.Key_Hiragana_Katakana:'unknown',
Qt.Key_History:'unknown',
Qt.Key_Home:'home',
Qt.Key_HomePage:'unknown',
Qt.Key_HotLinks:'unknown',
Qt.Key_Hyper_L:'unknown',
Qt.Key_Hyper_R:'unknown',
Qt.Key_I:'i',
Qt.Key_Iacute:'unknown',
Qt.Key_Icircumflex:'unknown',
Qt.Key_Idiaeresis:'unknown',
Qt.Key_Igrave:'unknown',
Qt.Key_Info:'unknown',
Qt.Key_Insert:'unknown',
Qt.Key_J:'j',
Qt.Key_K:'k',
Qt.Key_Kana_Lock:'unknown',
Qt.Key_Kana_Shift:'unknown',
Qt.Key_Kanji:'unknown',
Qt.Key_Katakana:'unknown',
Qt.Key_KeyboardBrightnessDown:'unknown',
Qt.Key_KeyboardBrightnessUp:'unknown',
Qt.Key_KeyboardLightOnOff:'unknown',
Qt.Key_L:'l',
Qt.Key_LastNumberRedial:'unknown',
Qt.Key_Launch0:'unknown',
Qt.Key_Launch1:'unknown',
Qt.Key_Launch2:'unknown',
Qt.Key_Launch3:'unknown',
Qt.Key_Launch4:'unknown',
Qt.Key_Launch5:'unknown',
Qt.Key_Launch6:'unknown',
Qt.Key_Launch7:'unknown',
Qt.Key_Launch8:'unknown',
Qt.Key_Launch9:'unknown',
Qt.Key_LaunchA:'unknown',
Qt.Key_LaunchB:'unknown',
Qt.Key_LaunchC:'unknown',
Qt.Key_LaunchD:'unknown',
Qt.Key_LaunchE:'unknown',
Qt.Key_LaunchF:'unknown',
Qt.Key_LaunchG:'unknown',
Qt.Key_LaunchH:'unknown',
Qt.Key_LaunchMail:'unknown',
Qt.Key_LaunchMedia:'unknown',
Qt.Key_Left:'arrow_left',
Qt.Key_Less:'unknown',
Qt.Key_LightBulb:'unknown',
Qt.Key_LogOff:'unknown',
Qt.Key_M:'m',
Qt.Key_MailForward:'unknown',
Qt.Key_Market:'unknown',
Qt.Key_Massyo:'unknown',
Qt.Key_MediaLast:'unknown',
Qt.Key_MediaNext:'unknown',
Qt.Key_MediaPause:'unknown',
Qt.Key_MediaPlay:'unknown',
Qt.Key_MediaPrevious:'unknown',
Qt.Key_MediaRecord:'unknown',
Qt.Key_MediaStop:'unknown',
Qt.Key_MediaTogglePlayPause:'unknown',
Qt.Key_Meeting:'unknown',
Qt.Key_Memo:'unknown',
Qt.Key_Menu:'unknown',
Qt.Key_MenuKB:'unknown',
Qt.Key_MenuPB:'unknown',
Qt.Key_Messenger:'unknown',
Qt.Key_Meta:'unknown',
Qt.Key_MicMute:'unknown',
Qt.Key_MicVolumeDown:'unknown',
Qt.Key_MicVolumeUp:'unknown',
Qt.Key_Minus:'unknown',
Qt.Key_Mode_switch:'unknown',
Qt.Key_MonBrightnessDown:'unknown',
Qt.Key_MonBrightnessUp:'unknown',
Qt.Key_Muhenkan:'unknown',
Qt.Key_Multi_key:'unknown',
Qt.Key_MultipleCandidate:'unknown',
Qt.Key_Music:'unknown',
Qt.Key_MySites:'unknown',
Qt.Key_N:'n',
Qt.Key_New:'unknown',
Qt.Key_News:'unknown',
Qt.Key_No:'unknown',
Qt.Key_Ntilde:'unknown',
Qt.Key_NumLock:'unknown',
Qt.Key_NumberSign:'unknown',
Qt.Key_O:'o',
Qt.Key_Oacute:'unknown',
Qt.Key_Ocircumflex:'unknown',
Qt.Key_Odiaeresis:'unknown',
Qt.Key_OfficeHome:'unknown',
Qt.Key_Ograve:'unknown',
Qt.Key_Ooblique:'unknown',
Qt.Key_Open:'unknown',
Qt.Key_OpenUrl:'unknown',
Qt.Key_Option:'unknown',
Qt.Key_Otilde:'unknown',
Qt.Key_P:'p',
Qt.Key_PageDown:'unknown',
Qt.Key_PageUp:'unknown',
Qt.Key_ParenLeft:'unknown',
Qt.Key_ParenRight:'unknown',
Qt.Key_Paste:'unknown',
Qt.Key_Pause:'unknown',
Qt.Key_Percent:'unknown',
Qt.Key_Period:'unknown',
Qt.Key_Phone:'unknown',
Qt.Key_Pictures:'unknown',
Qt.Key_Play:'unknown',
Qt.Key_Plus:'unknown',
Qt.Key_PowerDown:'unknown',
Qt.Key_PowerOff:'unknown',
Qt.Key_PreviousCandidate:'unknown',
Qt.Key_Print:'unknown',
Qt.Key_Printer:'unknown',
Qt.Key_Q:'q',
Qt.Key_Question:'unknown',
Qt.Key_QuoteDbl:'unknown',
Qt.Key_QuoteLeft:'unknown',
Qt.Key_R:'r',
Qt.Key_Red:'unknown',
Qt.Key_Redo:'unknown',
Qt.Key_Refresh:'unknown',
Qt.Key_Reload:'unknown',
Qt.Key_Reply:'unknown',
Qt.Key_Return:'unknown',
Qt.Key_Right:'arrow_right',
Qt.Key_Romaji:'unknown',
Qt.Key_RotateWindows:'unknown',
Qt.Key_RotationKB:'unknown',
Qt.Key_RotationPB:'unknown',
Qt.Key_S:'s',
Qt.Key_Save:'unknown',
Qt.Key_ScreenSaver:'unknown',
Qt.Key_ScrollLock:'unknown',
Qt.Key_Search:'unknown',
Qt.Key_Select:'unknown',
Qt.Key_Semicolon:'unknown',
Qt.Key_Send:'unknown',
Qt.Key_Settings:'unknown',
Qt.Key_Shift:'unknown',
Qt.Key_Shop:'unknown',
Qt.Key_SingleCandidate:'unknown',
Qt.Key_Slash:'unknown',
Qt.Key_Sleep:'unknown',
Qt.Key_Space:'space',
Qt.Key_Spell:'unknown',
Qt.Key_SplitScreen:'unknown',
Qt.Key_Standby:'unknown',
Qt.Key_Stop:'unknown',
Qt.Key_Subtitle:'unknown',
Qt.Key_Super_L:'unknown',
Qt.Key_Super_R:'unknown',
Qt.Key_Support:'unknown',
Qt.Key_Suspend:'unknown',
Qt.Key_SysReq:'unknown',
Qt.Key_T:'t',
Qt.Key_THORN:'unknown',
Qt.Key_Tab:'unknown',
Qt.Key_TaskPane:'unknown',
Qt.Key_Terminal:'unknown',
Qt.Key_Time:'unknown',
Qt.Key_ToDoList:'unknown',
Qt.Key_ToggleCallHangup:'unknown',
Qt.Key_Tools:'unknown',
Qt.Key_TopMenu:'unknown',
Qt.Key_TouchpadOff:'unknown',
Qt.Key_TouchpadOn:'unknown',
Qt.Key_TouchpadToggle:'unknown',
Qt.Key_Touroku:'unknown',
Qt.Key_Travel:'unknown',
Qt.Key_TrebleDown:'unknown',
Qt.Key_TrebleUp:'unknown',
Qt.Key_U:'u',
Qt.Key_UWB:'unknown',
Qt.Key_Uacute:'unknown',
Qt.Key_Ucircumflex:'unknown',
Qt.Key_Udiaeresis:'unknown',
Qt.Key_Ugrave:'unknown',
Qt.Key_Underscore:'unknown',
Qt.Key_Undo:'unknown',
Qt.Key_Up:'arrow_up',
Qt.Key_V:'v',
Qt.Key_Video:'unknown',
Qt.Key_View:'unknown',
Qt.Key_VoiceDial:'unknown',
Qt.Key_VolumeDown:'unknown',
Qt.Key_VolumeMute:'unknown',
Qt.Key_VolumeUp:'unknown',
Qt.Key_W:'w',
Qt.Key_WLAN:'unknown',
Qt.Key_WWW:'unknown',
Qt.Key_WakeUp:'unknown',
Qt.Key_WebCam:'unknown',
Qt.Key_Word:'unknown',
Qt.Key_X:'x',
Qt.Key_Xfer:'unknown',
Qt.Key_Y:'y',
Qt.Key_Yacute:'unknown',
Qt.Key_Yellow:'unknown',
Qt.Key_Yes:'unknown',
Qt.Key_Z:'z',
Qt.Key_Zenkaku:'unknown',
Qt.Key_Zenkaku_Hankaku:'unknown',
Qt.Key_Zoom:'unknown',
Qt.Key_ZoomIn:'unknown',
Qt.Key_ZoomOut:'unknown',
Qt.Key_acute:'unknown',
Qt.Key_brokenbar:'unknown',
Qt.Key_cedilla:'unknown',
Qt.Key_cent:'unknown',
Qt.Key_copyright:'unknown',
Qt.Key_currency:'unknown',
Qt.Key_degree:'unknown',
Qt.Key_diaeresis:'unknown',
Qt.Key_division:'unknown',
Qt.Key_exclamdown:'unknown',
Qt.Key_guillemotleft:'unknown',
Qt.Key_guillemotright:'unknown',
Qt.Key_hyphen:'unknown',
Qt.Key_iTouch:'unknown',
Qt.Key_macron:'unknown',
Qt.Key_masculine:'unknown',
Qt.Key_mu:'unknown',
Qt.Key_multiply:'unknown',
Qt.Key_nobreakspace:'unknown',
Qt.Key_notsign:'unknown',
Qt.Key_onehalf:'unknown',
Qt.Key_onequarter:'unknown',
Qt.Key_onesuperior:'unknown',
Qt.Key_ordfeminine:'unknown',
Qt.Key_paragraph:'unknown',
Qt.Key_periodcentered:'unknown',
Qt.Key_plusminus:'unknown',
Qt.Key_questiondown:'unknown',
Qt.Key_registered:'unknown',
Qt.Key_section:'unknown',
Qt.Key_ssharp:'unknown',
Qt.Key_sterling:'unknown',
Qt.Key_threequarters:'unknown',
Qt.Key_threesuperior:'unknown',
Qt.Key_twosuperior:'unknown',
Qt.Key_unknown:'unknown',
Qt.Key_ydiaeresis:'unknown',
Qt.Key_yen:'unknown',
}

View File

@ -0,0 +1,15 @@
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import sys
__all__ = ["QMeta3D_Modifiers_Translation.py"]
QMeta3D_Modifier_translation ={
Qt.NoModifier:None,
Qt.ShiftModifier:'shift',
Qt.ControlModifier:'control',
Qt.AltModifier:'alt',
Qt.MetaModifier:'unknown',
Qt.KeypadModifier:'unknown',
Qt.GroupSwitchModifier:'unknown',
}

View File

@ -1,41 +1,27 @@
# -*- coding: utf-8-*-
"""
Module : QMouseWatcherNode
Author : Niklas Mevenkamp
Description :
This is a MouseWatcherNode implementation that accesses
mouse position and button states through a parent QWidget.
"""
# PyQt imports
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
# Panda imports
from panda3d.core import *
__all__ = ["QMouseWatcherNode"]
class QMouseWatcherNode(MouseWatcher):
def __init__(self, parent):
super().__init__()
self.parent = parent
def getMouse(self, *args, **kwargs):
# map global QCursor pixel position to parent Widget coordinates
pos = self.parent.mapFromGlobal(QCursor.pos())
# map absolute pixel positions to relative ones
rel_x = -1 + 2 * pos.x() / self.parent.width()
rel_y = -1 + 2 * pos.y() / self.parent.height()
# invert y
rel_y = -rel_y
return LPoint2(rel_x, rel_y)
def hasMouse(self):
return isinstance(self.parent, QWidget)
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from panda3d.core import *
__all__ = ["QMouseWatcherNode"]
class QMouseWatcherNode(MouseWatcher):
def __init__(self, parent):
super().__init__()
self.parent = parent
def getMouse(self, *args, **kwargs):
pos = self.parent.mapFromGlobal(QCursor.pos())
rel_x = -1 + 2 * pos.x() / self.parent.width()
rel_y = -1 + 2 * pos.y() / self.parent.height()
rel_y = -rel_y
return LPoint2(rel_x, rel_y)
def hasMouse(self):
return isinstance(self.parent, QWidget)

View File

@ -1,2 +1,2 @@
name="QPanda3D"
name="QMeta3D"
__all__ = ["generate_qt_to_pd3d_translator"]

View File

@ -0,0 +1,11 @@
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
H=Qt.__dict__
lst = [a for a in H if a.startswith("Key_")]
QMeta3D_Key_translation = "QMeta3D_Key_translation ={"
for i in range(len(lst)):
QMeta3D_Key_translation += "Qt.{}:'unknown',\n".format(lst[i])
QMeta3D_Key_translation += "}"
print(QMeta3D_Key_translation)

2
QMeta3D/__init__.py Normal file
View File

@ -0,0 +1,2 @@
name="QMeta3D"
__all__ = ["QMeta3DWidget.py", "Meta3DWorld.py", "QMeta3D_Keys_Translation.py"]

View File

@ -1,24 +0,0 @@
# -*- coding: utf-8-*-
"""
Module : QPanda3D_Translation_Modifiers
Author : Niklas Mevenkamp
Description :
Contains a dictionary that translates QT keyboard events to panda3d
keyboard events.
"""
# PyQt imports
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import sys
__all__ = ["QPanda3D_Modifiers_Translation"]
QPanda3D_Modifier_translation ={
Qt.NoModifier:None,
Qt.ShiftModifier:'shift',
Qt.ControlModifier:'control',
Qt.AltModifier:'alt',
Qt.MetaModifier:'unknown',
Qt.KeypadModifier:'unknown',
Qt.GroupSwitchModifier:'unknown',
}

View File

@ -1,11 +0,0 @@
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
H=Qt.__dict__
lst = [a for a in H if a.startswith("Key_")]
QPanda3D_Key_translation ="QPanda3D_Key_translation ={"
for i in range(len(lst)):
QPanda3D_Key_translation +="Qt.{}:'unknown',\n".format(lst[i])
QPanda3D_Key_translation += "}"
print(QPanda3D_Key_translation)

View File

@ -1,2 +0,0 @@
name="QPanda3D"
__all__ = ["QPanda3DWidget", "Panda3DWorld", "QPanda3D_Keys_Translation"]

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,3 @@
# This file stores internal settings of the pipeline. It does not contain the
# plugin settings, but just the basic settings of the internal pipeline components.
pipeline:
@ -77,8 +75,8 @@ shadows:
# getting updated fast enough. However, this also affects the performance
# quite a bit, since for every shadow map a part of the scene has
# to get re-rendered.
max_updates: 8
max_updates: 16
# Sets the maximum distance until which shadows are updated. If a shadow
# source is further away, it will no longer recieve updates
max_update_distance: 150.0
max_update_distance: 300.0

View File

@ -16,16 +16,16 @@ enabled:
- sky_ao
- smaa
- ssr
# - clouds
# - dof
# - fxaa
# - volumetrics
# - vxgi
- clouds
#- dof
#- fxaa
#- volumetrics
#- vxgi
overrides:
ao:
blur_quality: MEDIUM
blur_quality: LOW
blur_normal_factor: 2.97
blur_depth_factor: 0.88158
occlusion_strength: 2.19
@ -87,10 +87,10 @@ overrides:
sharpen_twice: False
dof:
focal_point: 1000.0
focal_size: 994.0
blur_strength: 0.0
near_blur_strength: 0.4286
focal_point: 5
focal_size: 1
blur_strength: 1
near_blur_strength: 1
env_probes:
probe_resolution: 128

View File

@ -90,7 +90,6 @@ class LightManager(RPObject):
def remove_light(self, light):
""" Removes a light """
print(f'333333333333333333333333333333,{light.casts_shadows}')
# from RenderPipelineFile.rpcore.pynative.internal_light_manager import InternalLightManager
# inter = InternalLightManager()
# inter.remove_light(light)

View File

@ -142,7 +142,7 @@ class RenderPipeline(RPObject):
if not isfile("/$$rp/data/install.flag"):
self.fatal("You didn't setup the pipeline yet! Please run setup.py.")
load_prc_file("/$$rpconfig/panda3d-config.prc")
load_prc_file("/$$rpconfig/Meta3d-config.prc")
self._pre_showbase_initialized = True
def create(self, base=None):
@ -208,7 +208,6 @@ class RenderPipeline(RPObject):
def remove_light(self, light):
""" Removes a previously attached light, check out the LightManager
remove_light documentation for further information. """
print(f'222222222222222222222222222,{light.casts_shadows}')
self.light_mgr.remove_light(light)
def load_ies_profile(self, filename):

View File

@ -1,29 +1,3 @@
"""
RenderPipeline
Copyright (c) 2014-2016 tobspr <tobias.springer1@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
from rpcore.render_stage import RenderStage

View File

@ -1,29 +1,3 @@
"""
RenderPipeline
Copyright (c) 2014-2016 tobspr <tobias.springer1@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
from rpcore.pluginbase.base_plugin import BasePlugin
from .dof_stage import DoFStage

View File

@ -9,4 +9,5 @@ if not os.path.isfile(os.path.join(this_dir, "data", "install.flag")):
print("Please install the pipeline first by running setup.py")
sys.exit(-1)
to_execute = os.path.join(this_dir, "toolkit", "plugin_configurator", "main.py")
subprocess.call([sys.executable, to_execute])
# 使用相对路径而不是绝对路径
subprocess.call([sys.executable, to_execute], cwd=this_dir)

View File

@ -33,7 +33,9 @@ from six.moves import range # pylint: disable=import-error
import math
from RenderPipelineFile.rplibs.pyqt_imports import * # noqa
# 修复导入路径问题
from rplibs.pyqt_imports import * # noqa
class CurveWidget(QWidget):

View File

@ -1,3 +1,5 @@
#!/usr/bin/env python
"""
RenderPipeline
@ -40,8 +42,9 @@ os.chdir(os.path.join(os.path.dirname(os.path.realpath(__file__))))
# Add the render pipeline to the path
sys.path.insert(0, "../../")
from RenderPipelineFile.rplibs.six import iteritems # noqa
from RenderPipelineFile.rplibs.pyqt_imports import * # noqa
# 修复导入路径问题
from rplibs.six import iteritems # noqa
from rplibs.pyqt_imports import * # noqa
from curve_widget import CurveWidget # noqa

View File

@ -1,32 +1,4 @@
"""
RenderPipeline
Copyright (c) 2014-2016 tobspr <tobias.springer1@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
# This tool offers an interface to configure the pipeline
from __future__ import print_function
#!/usr/bin/env python
import os
import sys
@ -49,7 +21,39 @@ from rplibs.pyqt_imports import * #noqa
# Load the generated UI Layout
from ui.main_window_generated import Ui_MainWindow # noqa
from ui.widgets import UniversalMessageDialog
# 创建一个简单的UniversalMessageDialog类避免复杂的导入问题
class UniversalMessageDialog(QDialog):
@staticmethod
def show_success(parent, title, message, show_cancel=True, confirm_text="确认"):
msg_box = QMessageBox(QMessageBox.Information, title, message, QMessageBox.Ok, parent)
if show_cancel:
msg_box.addButton(QMessageBox.Cancel)
msg_box.button(QMessageBox.Ok).setText(confirm_text)
return msg_box.exec_()
@staticmethod
def show_warning(parent, title, message, show_cancel=True, confirm_text="确认"):
msg_box = QMessageBox(QMessageBox.Warning, title, message, QMessageBox.Ok, parent)
if show_cancel:
msg_box.addButton(QMessageBox.Cancel)
msg_box.button(QMessageBox.Ok).setText(confirm_text)
return msg_box.exec_()
@staticmethod
def show_error(parent, title, message, show_cancel=True, confirm_text="确认"):
msg_box = QMessageBox(QMessageBox.Critical, title, message, QMessageBox.Ok, parent)
if show_cancel:
msg_box.addButton(QMessageBox.Cancel)
msg_box.button(QMessageBox.Ok).setText(confirm_text)
return msg_box.exec_()
@staticmethod
def show_info(parent, title, message, show_cancel=True, confirm_text="确认"):
msg_box = QMessageBox(QMessageBox.Information, title, message, QMessageBox.Ok, parent)
if show_cancel:
msg_box.addButton(QMessageBox.Cancel)
msg_box.button(QMessageBox.Ok).setText(confirm_text)
return msg_box.exec_()
from rpcore.pluginbase.manager import PluginManager # noqa
from rpcore.util.network_communication import NetworkCommunication # noqa
@ -106,6 +110,15 @@ class PluginConfigurator(QMainWindow, Ui_MainWindow):
def closeEvent(self, event): # noqa
event.accept()
# 通知主程序配置可能已更改
try:
# 发送一个信号或者写入一个标记文件来通知主程序
marker_file = "/tmp/rp_plugin_config_changed.flag"
with open(marker_file, "w") as f:
f.write("Plugin configuration may have changed")
except Exception as e:
print(f"无法创建配置更改标记文件: {e}")
import os
os._exit(1)

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,9 @@ import sys
project_root = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, project_root)
# 设置工作目录为项目根目录
os.chdir(project_root)
# 添加 RenderPipeline 到路径(注意路径名称应与实际目录一致)
render_pipeline_path = os.path.join(project_root, "RenderPipeline")
sys.path.insert(0, render_pipeline_path)

View File

@ -422,22 +422,26 @@ class SelectionSystem:
is_rotate_tool = self.world.tool_manager.isRotateTool() if self.world.tool_manager else False
import os
from panda3d.core import getModelPath, Filename
# 获取项目根目录
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# 确保core目录在模型搜索路径中
model_path = getModelPath()
core_dir = os.path.join(base_dir, "core")
core_filename = Filename.from_os_specific(core_dir)
if not model_path.findFile(core_filename):
model_path.appendDirectory(core_filename)
print(f"✓ 添加core目录到模型搜索路径: {core_dir}")
if is_scale_tool:
model_paths = [
os.path.join(base_dir,"core/UniformScaleHandle.fbx"),
]
model_filename = "core/UniformScaleHandle.fbx"
elif is_rotate_tool:
model_paths = [
os.path.join(base_dir,"core/RotationHandleQuarter.fbx"),
]
model_filename = "core/RotationHandleQuarter.fbx"
arrow_filename = "core/TranslateArrowHandle.fbx"
else:
model_paths = [
os.path.join(base_dir, "core/TranslateArrowHandle.fbx"),
]
arrow_path = os.path.join(base_dir, "core/TranslateArrowHandle.fbx")
model_filename = "core/TranslateArrowHandle.fbx"
# model_paths = [
# "core/TranslateArrowHandle.fbx",
@ -445,18 +449,15 @@ class SelectionSystem:
# ]
gizmo_model = None
gizmoRot_model = None
for path in model_paths:
try:
if is_rotate_tool:
gizmo_model = self.world.loader.loadModel(arrow_path)
gizmoRot_model = self.world.loader.loadModel(path)
else:
gizmo_model = self.world.loader.loadModel(path)
if gizmo_model:
#print(f"成功加载模型: {path}")
break
except:
continue
try:
if is_rotate_tool:
gizmo_model = self.world.loader.loadModel(arrow_filename)
gizmoRot_model = self.world.loader.loadModel(model_filename)
else:
gizmo_model = self.world.loader.loadModel(model_filename)
except Exception as e:
print(f"加载模型失败: {e}")
return
x_rHandle = None
y_rHandle = None
@ -647,7 +648,7 @@ class SelectionSystem:
import time
current_time = time.time()
if current_time - self._last_gizmo_update < 0.05: # 每0.05秒更新一次
if current_time - self._last_gizmo_update < 0.5: # 每0.05秒更新一次
return task.cont
self._last_gizmo_update = current_time
@ -1624,8 +1625,6 @@ class SelectionSystem:
# 应用新缩放值
self.gizmoTarget.setScale(new_scale)
# 安全地更新属性面板
#self._safeUpdatePropertyPanel()
self.world.property_panel.refreshModelValues(self.gizmoTarget)
return
elif is_rotate_tool:
@ -1645,7 +1644,6 @@ class SelectionSystem:
start_hpr.y + rotation_amount,
start_hpr.z + rotation_amount)
self.gizmoTarget.setHpr(new_hpr)
#self._safeUpdatePropertyPanel()
self.world.property_panel.refreshModelValues(self.gizmoTarget)
return
@ -1810,7 +1808,6 @@ class SelectionSystem:
# 实时更新属性面板
self.world.property_panel.refreshModelValues(self.gizmoTarget)
#self._safeUpdatePropertyPanel()
# 每次拖拽都输出调试信息(但限制频率)
if not hasattr(self, '_last_drag_debug_time'):
@ -1844,39 +1841,6 @@ class SelectionSystem:
except:
return False
def _safeUpdatePropertyPanel(self):
"""安全地更新属性面板"""
try:
# 检查属性面板是否存在且有效
if (hasattr(self.world, 'property_panel') and
self.world.property_panel is not None):
# 检查目标节点是否有效
if (self.gizmoTarget is not None and
not self.gizmoTarget.isEmpty()):
# 检查是否有refreshModelValues方法
if hasattr(self.world.property_panel, 'refreshModelValues'):
self.world.property_panel.refreshModelValues(self.gizmoTarget)
# 如果是GUI元素可能需要特殊的处理
elif (hasattr(self.gizmoTarget, 'getTag') and
self.gizmoTarget.getTag("is_gui_element") == "1" and
hasattr(self.world.property_panel, 'updateGUIPropertyPanel')):
# 对于GUI元素可能需要重新构建整个面板
if (hasattr(self.world, 'treeWidget') and
self.world.treeWidget is not None):
current_item = self.world.treeWidget.currentItem()
if current_item is not None:
self.world.property_panel.updatePropertyPanel(current_item)
except RuntimeError as e:
if "wrapped C/C++ object" in str(e):
# 忽略控件已被删除的错误
print("警告: 属性面板控件已被删除,跳过更新")
else:
print(f"更新属性面板时出错: {e}")
except Exception as e:
print(f"更新属性面板失败: {e}")
def stopGizmoDrag(self):
"""停止坐标轴拖拽"""
print(f"停止坐标轴拖拽 - 轴: {self.dragGizmoAxis}")

View File

@ -11,5 +11,8 @@
"enable_ssr": false,
"shadow_quality": "medium",
"ao_quality": "low"
}
},
"anti_aliasing": "4x",
"refresh_rate": "90Hz",
"async_reprojection": true
}

View File

@ -6,7 +6,7 @@ from direct.actor.Actor import Actor
warnings.filterwarnings("ignore", category=DeprecationWarning)
from QPanda3D.Panda3DWorld import Panda3DWorld
from QMeta3D.Meta3DWorld import Meta3DWorld
from panda3d.core import (CardMaker, Vec4, Vec3, AmbientLight, DirectionalLight,
Point3, WindowProperties,Material,LColor)
from direct.showbase.ShowBaseGlobal import globalClock
@ -20,7 +20,7 @@ except ImportError:
PLUGIN_SUPPORT = False
print("⚠ 插件管理器未找到,插件功能将不可用")
class CoreWorld(Panda3DWorld):
class CoreWorld(Meta3DWorld):
"""核心世界功能类 - 负责基础的3D世界设置和核心功能"""
def __init__(self):
@ -77,6 +77,27 @@ class CoreWorld(Panda3DWorld):
model_path.appendDirectory(resources_filename)
print(f"✓ 添加Resources到模型搜索路径: {resources_dir}")
# 添加core目录到搜索路径
core_dir = os.path.join(project_root, "core")
core_filename = Filename.from_os_specific(core_dir)
if not model_path.findFile(core_filename):
model_path.appendDirectory(core_filename)
print(f"✓ 添加core目录到模型搜索路径: {core_dir}")
# 添加RenderPipeline目录到搜索路径
rp_dir = os.path.join(project_root, "RenderPipelineFile")
rp_filename = Filename.from_os_specific(rp_dir)
if not model_path.findFile(rp_filename):
model_path.appendDirectory(rp_filename)
print(f"✓ 添加RenderPipeline目录到模型搜索路径: {rp_dir}")
# 添加RenderPipeline data目录到搜索路径
rp_data_dir = os.path.join(rp_dir, "data")
rp_data_filename = Filename.from_os_specific(rp_data_dir)
if not model_path.findFile(rp_data_filename):
model_path.appendDirectory(rp_data_filename)
print(f"✓ 添加RenderPipeline data目录到模型搜索路径: {rp_data_dir}")
# 同时添加各个子目录到搜索路径
subdirs = ['models', 'textures', 'animations', 'icons', 'materials']
for subdir in subdirs:
@ -213,7 +234,6 @@ class CoreWorld(Panda3DWorld):
temp_anim.removeNode() # 清理临时动画
except Exception as e:
print(f"❌ 动画文件加载失败: {e}")
return None
# 4. 尝试创建Actor
print(f"4. Actor创建测试:")
@ -492,16 +512,105 @@ class CoreWorld(Panda3DWorld):
# def _loadFont(self):
# """加载中文字体"""
# try:
# self.chinese_font = self.loader.loadFont('/usr/share/fonts/truetype/wqy/wqy-microhei.ttc')
# if not self.chinese_font:
# print("警告: 无法加载中文字体,将使用默认字体")
# else:
# print("✓ 中文字体加载成功")
# except:
# print("警告: 无法加载中文字体,将使用默认字体")
# self.chinese_font = None
def _loadFont(self):
"""加载中文字体"""
"""加载中文字体 - 跨平台Panda3D 友好路径"""
try:
self.chinese_font = self.loader.loadFont('/usr/share/fonts/truetype/wqy/wqy-microhei.ttc')
import os
import platform
from pathlib import Path
from panda3d.core import Filename
self.chinese_font = None # 初始化
# --- 平台与项目根 ---
system = platform.system().lower()
project_root = Path(__file__).resolve().parent.parent # 上两级
# --- 候选字体路径(按平台) ---
if system == "windows":
win_dir = os.environ.get("WINDIR") or r"C:\Windows"
win_fonts_dir = Path(win_dir) / "Fonts"
font_candidates = [
project_root / "RenderPipelineFile" / "data" / "font" / "msyh.ttc",
win_fonts_dir / "msyh.ttc",
win_fonts_dir / "msyh.ttf",
win_fonts_dir / "simhei.ttf",
win_fonts_dir / "simsun.ttc",
]
elif system == "darwin": # macOS
font_candidates = [
Path("/System/Library/Fonts/PingFang.ttc"),
Path("/System/Library/Fonts/STHeiti.ttc"),
Path("/Library/Fonts/Songti.ttc"),
]
else: # Linux / 其他
font_candidates = [
Path("/usr/share/fonts/truetype/wqy/wqy-microhei.ttc"),
Path("/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc"),
Path("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"),
]
# --- 逐个尝试加载 ---
for os_path in font_candidates:
os_path_str = str(os_path)
if not os.path.exists(os_path_str):
# 文件确实不存在就跳过
continue
# 1) 先把 OS 路径转换成 Panda 内部路径(/d/... 或 /usr/...
panda_fn = Filename.fromOsSpecific(os_path_str)
panda_fn.makeTrueCase() # Windows 上修正大小写(更稳)
panda_internal = panda_fn.getFullpath() # 取“内部样式”的字符串
# 2) 打印便于对照 Panda 的日志
try:
print(f"[FontLoader] 尝试加载: {panda_internal}")
except Exception:
pass
try:
# 3) 传“字符串”给 loader.loadFont关键修复点
font = self.loader.loadFont(panda_internal)
if font:
self.chinese_font = font
print("✓ 中文字体加载成功:", os_path_str)
break
else:
print("[FontLoader] 返回空字体对象,继续尝试下一个候选...")
except TypeError as te:
# 某些旧版绑定只接受 str不接受 Filename此处已传 str若仍报错再兜底一次
try:
font = self.loader.loadFont(str(panda_fn)) # 退一步str(Filename)
if font:
self.chinese_font = font
print("✓ 中文字体加载成功(备用API):", os_path_str)
break
else:
print("[FontLoader] 备用API仍返回空字体对象继续…")
except Exception as te2:
print(f"[FontLoader] 加载失败(TypeError),继续下一个: {os_path},原因: {te2}")
except Exception as e:
print(f"[FontLoader] 加载失败,继续下一个: {os_path},原因: {e}")
# --- 兜底 ---
if not self.chinese_font:
print("警告: 无法加载中文字体,将使用默认字体")
else:
print("✓ 中文字体加载成功")
except:
print("警告: 无法加载中文字体,将使用默认字体")
print("警告: 无法加载中文字体,将使用默认字体(可能导致中文显示不全)")
self.chinese_font = None
except Exception as e:
print(f"警告: 加载中文字体时发生错误: {e}")
self.chinese_font = None
def setQtWidget(self, widget):

View File

@ -11,12 +11,12 @@ warnings.filterwarnings("ignore", category=DeprecationWarning)
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QLabel, QPushButton, QHBoxLayout
from PyQt5.QtCore import Qt
from QPanda3D.QPanda3DWidget import QPanda3DWidget
from QPanda3D.Panda3DWorld import Panda3DWorld
from QMeta3D.QMeta3DWidget import QMeta3DWidget
from QMeta3D.Meta3DWorld import Meta3DWorld
from panda3d.core import *
from direct.task import Task
class GizmoDragTestWorld(Panda3DWorld):
class GizmoDragTestWorld(Meta3DWorld):
def __init__(self):
super().__init__()
@ -529,7 +529,7 @@ class GizmoDragTestWorld(Panda3DWorld):
print(f"设置坐标轴颜色失败: {str(e)}")
class CustomGizmoDragTestWidget(QPanda3DWidget):
class CustomGizmoDragTestWidget(QMeta3DWidget):
"""支持坐标轴拖拽测试的部件"""
def __init__(self, world, parent=None):
super().__init__(world, parent)

View File

@ -16,15 +16,15 @@ from PyQt5.QtWidgets import (QApplication, QMainWindow, QMenuBar, QMenu, QAction
QLabel, QLineEdit, QFormLayout, QDoubleSpinBox, QScrollArea, QTreeView, QInputDialog, QFileDialog, QMessageBox, QDialog, QGroupBox, QHBoxLayout, QPushButton, QDialogButtonBox)
from PyQt5.QtCore import Qt, QDir, QUrl
from PyQt5.QtGui import QDrag, QPainter, QPixmap
from QPanda3D.QPanda3DWidget import QPanda3DWidget
from QPanda3D.Panda3DWorld import Panda3DWorld
from QMeta3D.QMeta3DWidget import QMeta3DWidget
from QMeta3D.Meta3DWorld import Meta3DWorld
from direct.gui.DirectGui import *
from direct.gui.OnscreenText import OnscreenText
from panda3d.core import *
import os
# 简化的世界类专门用于GUI测试
class GUITestWorld(Panda3DWorld):
class GUITestWorld(Meta3DWorld):
def __init__(self):
super().__init__()
@ -270,7 +270,7 @@ class GUITestWorld(Panda3DWorld):
"""GUI输入框提交事件处理"""
print(f"✓ GUI输入框提交测试成功: {entry_id} = {text}")
class GUITestWidget(QPanda3DWidget):
class GUITestWidget(QMeta3DWidget):
def __init__(self, world, parent=None):
super().__init__(world, parent)
self.world = world

View File

@ -12,14 +12,14 @@ warnings.filterwarnings("ignore", category=DeprecationWarning)
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QLabel
from PyQt5.QtCore import Qt
from QPanda3D.QPanda3DWidget import QPanda3DWidget
from QPanda3D.Panda3DWorld import Panda3DWorld
from QMeta3D.QMeta3DWidget import QMeta3DWidget
from QMeta3D.Meta3DWorld import Meta3DWorld
from direct.gui.DirectGui import *
from direct.gui.OnscreenText import OnscreenText
from panda3d.core import *
import os
class DebugGUIWorld(Panda3DWorld):
class DebugGUIWorld(Meta3DWorld):
def __init__(self):
super().__init__()
@ -186,7 +186,7 @@ class DebugGUIWorld(Panda3DWorld):
"""按钮点击事件"""
print("✓ 按钮被点击了!")
class DebugWidget(QPanda3DWidget):
class DebugWidget(QMeta3DWidget):
def __init__(self, world, parent=None):
super().__init__(world, parent)
self.world = world

View File

@ -12,14 +12,14 @@ warnings.filterwarnings("ignore", category=DeprecationWarning)
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.QtCore import Qt
from QPanda3D.QPanda3DWidget import QPanda3DWidget
from QPanda3D.Panda3DWorld import Panda3DWorld
from QMeta3D.QMeta3DWidget import QMeta3DWidget
from QMeta3D.Meta3DWorld import Meta3DWorld
from direct.gui.DirectGui import *
from direct.gui.OnscreenText import OnscreenText
from panda3d.core import *
import os
class QtGUITestWorld(Panda3DWorld):
class QtGUITestWorld(Meta3DWorld):
def __init__(self):
super().__init__()
@ -109,7 +109,7 @@ class QtGUITestWorld(Panda3DWorld):
"""按钮点击事件"""
print("✓ 按钮被点击了!")
class QtGUITestWidget(QPanda3DWidget):
class QtGUITestWidget(QMeta3DWidget):
def __init__(self, world, parent=None):
super().__init__(world, parent)
self.world = world

View File

@ -13,13 +13,13 @@ import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QLabel
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QMouseEvent
from QPanda3D.QPanda3DWidget import QPanda3DWidget
from QPanda3D.Panda3DWorld import Panda3DWorld
from QMeta3D.QMeta3DWidget import QMeta3DWidget
from QMeta3D.Meta3DWorld import Meta3DWorld
from direct.gui.DirectGui import *
from direct.gui.OnscreenText import OnscreenText
from panda3d.core import *
class QtGUIFixWorld(Panda3DWorld):
class QtGUIFixWorld(Meta3DWorld):
def __init__(self):
super().__init__()
@ -194,7 +194,7 @@ class QtGUIFixWorld(Panda3DWorld):
print(f"Qt事件处理失败: {str(e)}")
return False
class CustomQPanda3DWidget(QPanda3DWidget):
class CustomQMeta3DWidget(QMeta3DWidget):
"""自定义QPanda3DWidget支持事件转发"""
def __init__(self, world, parent=None):
super().__init__(world, parent)
@ -241,7 +241,7 @@ def main():
world = QtGUIFixWorld()
# 创建自定义Panda3D部件
pandaWidget = CustomQPanda3DWidget(world)
pandaWidget = CustomQMeta3DWidget(world)
layout.addWidget(pandaWidget)
mainWindow.setCentralWidget(centralWidget)

View File

@ -11,13 +11,13 @@ warnings.filterwarnings("ignore", category=DeprecationWarning)
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QLabel
from PyQt5.QtCore import Qt
from QPanda3D.QPanda3DWidget import QPanda3DWidget
from QPanda3D.Panda3DWorld import Panda3DWorld
from QMeta3D.QMeta3DWidget import QMeta3DWidget
from QMeta3D.Meta3DWorld import Meta3DWorld
from direct.gui.DirectGui import *
from direct.gui.OnscreenText import OnscreenText
from panda3d.core import *
class QtOnlyWorld(Panda3DWorld):
class QtOnlyWorld(Meta3DWorld):
def __init__(self):
super().__init__()
@ -139,7 +139,7 @@ def main():
world = QtOnlyWorld()
# 创建Panda3D部件
pandaWidget = QPanda3DWidget(world)
pandaWidget = QMeta3DWidget(world)
layout.addWidget(pandaWidget)
mainWindow.setCentralWidget(centralWidget)

View File

@ -12,14 +12,14 @@ warnings.filterwarnings("ignore", category=DeprecationWarning)
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QLabel, QPushButton
from PyQt5.QtCore import Qt
from QPanda3D.QPanda3DWidget import QPanda3DWidget
from QPanda3D.Panda3DWorld import Panda3DWorld
from QMeta3D.QMeta3DWidget import QMeta3DWidget
from QMeta3D.Meta3DWorld import Meta3DWorld
from direct.showbase.ShowBase import ShowBase
from direct.gui.DirectGui import *
from direct.gui.OnscreenText import OnscreenText
from panda3d.core import *
class QtTestWorld(Panda3DWorld):
class QtTestWorld(Meta3DWorld):
"""Qt集成环境测试"""
def __init__(self):
super().__init__()
@ -168,7 +168,7 @@ def testQtEnvironment():
world = QtTestWorld()
# 创建Panda3D部件
pandaWidget = QPanda3DWidget(world)
pandaWidget = QMeta3DWidget(world)
layout.addWidget(pandaWidget)
mainWindow.setCentralWidget(centralWidget)

View File

@ -11,9 +11,9 @@ warnings.filterwarnings("ignore", category=DeprecationWarning)
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QLabel, QPushButton, QHBoxLayout
from PyQt5.QtCore import Qt
from QPanda3D.QPanda3DWidget import QPanda3DWidget
from QMeta3D.QMeta3DWidget import QMeta3DWidget
sys.path.append('..')
from main import MyWorld, CustomPanda3DWidget
from main import MyWorld, CustomMeta3DWidget
def main():
print("启动简化坐标轴测试...")
@ -42,7 +42,7 @@ def main():
world = MyWorld()
# 创建部件
pandaWidget = CustomPanda3DWidget(world)
pandaWidget = CustomMeta3DWidget(world)
layout.addWidget(pandaWidget)
mainWindow.setCentralWidget(centralWidget)

View File

@ -11,12 +11,12 @@ warnings.filterwarnings("ignore", category=DeprecationWarning)
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QLabel
from PyQt5.QtCore import Qt
from QPanda3D.QPanda3DWidget import QPanda3DWidget
from QPanda3D.Panda3DWorld import Panda3DWorld
from QMeta3D.QMeta3DWidget import QMeta3DWidget
from QMeta3D.Meta3DWorld import Meta3DWorld
from panda3d.core import *
from direct.task import Task
class SizeTestWorld(Panda3DWorld):
class SizeTestWorld(Meta3DWorld):
def __init__(self):
super().__init__()
@ -93,7 +93,7 @@ class SizeTestWorld(Panda3DWorld):
# 每5秒检查一次
return task.again
class CustomSizeTestWidget(QPanda3DWidget):
class CustomSizeTestWidget(QMeta3DWidget):
"""支持尺寸获取的测试部件"""
def __init__(self, world, parent=None):
super().__init__(world, parent)

View File

@ -33,8 +33,13 @@ class VideoManager:
# 创建视频纹理
if video_path and os.path.exists(video_path):
# Convert Windows path to Panda3D compatible path format
from panda3d.core import Filename
panda_path = Filename.fromOsSpecific(video_path)
converted_path = str(panda_path)
movie_texture = MovieTexture(name)
success = movie_texture.read(video_path)
success = movie_texture.read(converted_path)
if success:
video_screen.setTexture(movie_texture)
@ -43,7 +48,7 @@ class VideoManager:
video_obj = {
'node': video_screen,
'texture': movie_texture,
'path': video_path,
'path': converted_path,
'name': name,
'type': 'movie',
'is_playing': False
@ -62,7 +67,7 @@ class VideoManager:
print(f"✓ 视频屏幕创建成功: {name}")
return video_screen
else:
print(f"✗ 无法加载视频: {video_path}")
print(f"✗ 无法加载视频: {converted_path}")
# 创建占位符纹理
self._create_placeholder_texture(video_screen, name)
else:
@ -103,8 +108,13 @@ class VideoManager:
# 创建视频纹理
if video_path and os.path.exists(video_path):
# Convert Windows path to Panda3D compatible path format
from panda3d.core import Filename
panda_path = Filename.fromOsSpecific(video_path)
converted_path = str(panda_path)
movie_texture = MovieTexture(name)
success = movie_texture.read(video_path)
success = movie_texture.read(converted_path)
if success:
sphere.setTexture(movie_texture)
@ -113,7 +123,7 @@ class VideoManager:
video_obj = {
'node': sphere,
'texture': movie_texture,
'path': video_path,
'path': converted_path,
'name': name,
'type': 'spherical',
'is_playing': True

View File

@ -791,7 +791,7 @@ class GUIManager:
traceback.print_exc()
return None
def createGUI3DImage(self, pos=(0, 0, 0), image_path=None, size=1.0):
def createGUI3DImage(self, pos=(0, 0, 0), image_path=None, size=(1.0,1.0,1.0)):
"""创建3D空间图片"""
try:
@ -823,7 +823,7 @@ class GUIManager:
# 参数类型检查和转换
if isinstance(size, (list, tuple)):
if len(size) >= 2:
x_size, y_size = float(size[0]), float(size[1])
x_size, y_size = float(size[0]), float(size[2])
else:
x_size = y_size = float(size[0]) if size else 1.0
else:
@ -831,7 +831,7 @@ class GUIManager:
# 创建卡片
cm = CardMaker('gui_3d_image')
cm.setFrame(-x_size / 2, x_size / 2, -y_size / 2, y_size / 2)
cm.setFrame(-x_size, x_size, -y_size, y_size)
# 创建3D图像节点
# image_node = self.world.render.attachNewNode(cm.generate())
@ -1375,8 +1375,13 @@ class GUIManager:
print(f"❌ 视频文件不存在: {video_path}")
return False
# Convert Windows path to Panda3D compatible path format
from panda3d.core import Filename
panda_path = Filename.fromOsSpecific(video_path)
converted_path = str(panda_path)
# 加载新的视频纹理
movie_texture = self._loadMovieTexture(video_path)
movie_texture = self._loadMovieTexture(video_path) # Pass original path for existence check
if movie_texture:
# 清除现有的纹理
video_screen.clearTexture()
@ -1405,15 +1410,15 @@ class GUIManager:
# 保存新的视频纹理引用到PythonTag
video_screen.setPythonTag("movie_texture", movie_texture)
video_screen.setTag("video_path", video_path)
video_screen.setTag("video_path", converted_path) # Store converted path
# 确保视频屏幕有正确的材质
self._ensureVideoScreenMaterial(video_screen)
print(f"✅ 成功加载新视频: {video_path}")
print(f"✅ 成功加载新视频: {converted_path}")
return True
else:
print(f"❌ 无法加载视频文件: {video_path}")
print(f"❌ 无法加载视频文件: {converted_path}")
return False
except Exception as e:
@ -1425,7 +1430,7 @@ class GUIManager:
def _loadMovieTexture(self, video_path):
"""加载视频纹理的兼容方法"""
try:
from panda3d.core import Texture, MovieTexture
from panda3d.core import Texture, MovieTexture, Filename
import os
# 检查文件是否存在
@ -1433,12 +1438,16 @@ class GUIManager:
print(f"❌ 视频文件不存在: {video_path}")
return None
print(f"🔍 尝试加载视频文件: {video_path}")
# Convert Windows path to Panda3D compatible path format
panda_path = Filename.fromOsSpecific(video_path)
converted_path = str(panda_path)
print(f"🔍 尝试加载视频文件: {converted_path}")
# 方法1: 尝试使用 MovieTexture专门用于视频
try:
movie_texture = MovieTexture(video_path)
if movie_texture.read(video_path):
movie_texture = MovieTexture(converted_path)
if movie_texture.read(converted_path):
print("✅ 使用 MovieTexture 成功加载视频")
self._configureVideoTexture(movie_texture)
return movie_texture
@ -1449,7 +1458,7 @@ class GUIManager:
# 方法2: 尝试使用 loader.loadTexture
try:
movie_texture = self.world.loader.loadTexture(video_path)
movie_texture = self.world.loader.loadTexture(converted_path)
if movie_texture and hasattr(movie_texture, 'is_playable') and movie_texture.is_playable():
print("✅ 使用 loader.loadTexture 成功加载视频纹理")
self._configureVideoTexture(movie_texture)
@ -1462,7 +1471,7 @@ class GUIManager:
# 方法3: 尝试使用 Texture.read作为最后备选
try:
texture = Texture()
if texture.read(video_path):
if texture.read(converted_path):
print("✅ 使用 Texture.read 成功加载(可能作为静态纹理)")
self._configureVideoTexture(texture)
return texture

View File

@ -15,7 +15,7 @@ from PyQt5.QtWidgets import (QApplication, QMainWindow, QMenuBar, QMenu, QAction
from PyQt5.QtCore import Qt, QDir, QUrl
from PyQt5.QtGui import QDrag, QPainter, QPixmap
from PyQt5.QtWidgets import QFileSystemModel
from QPanda3D.QPanda3DWidget import QPanda3DWidget
from QMeta3D.QMeta3DWidget import QMeta3DWidget
from panda3d.core import loadPrcFileData
loadPrcFileData("", "assertions 0")
from core.world import CoreWorld
@ -29,7 +29,7 @@ from gui.gui_manager import GUIManager
from core.terrain_manager import TerrainManager
from scene.scene_manager import SceneManager
from project.project_manager import ProjectManager
from ui.widgets import CustomPanda3DWidget, CustomFileView, CustomTreeWidget
from ui.widgets import CustomMeta3DWidget, CustomFileView, CustomTreeWidget
from ui.property_panel import PropertyPanelManager
from ui.interface_manager import InterfaceManager
from panda3d.core import (CardMaker, Vec4, Vec3, ColorAttrib, MaterialAttrib,

View File

@ -1,11 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
项目管理器 - 负责项目的生命周期管理
处理项目创建打开保存打包等功能
"""
import os
import sys
import json
@ -22,13 +14,9 @@ from PyQt5.QtCore import Qt
from ui.widgets import NewProjectDialog, UniversalMessageDialog
class ProjectManager:
"""项目管理器 - 统一管理项目的生命周期"""
def __init__(self, world):
"""初始化项目管理器
Args:
world: 主程序world对象引用
"""
self.world = world
self.current_project_path = None
self.project_config = None
@ -821,353 +809,6 @@ class ProjectManager:
shutil.copy2(template_path, app_path)
print(f"✓ 应用程序主文件已从模板创建: {app_path}")
# def _createAppFile(self, build_dir, project_name):
# """创建应用程序主文件"""
# app_code = f'''#!/usr/bin/env python3
# # -*- coding: utf-8 -*-
#
# """
# {project_name} - Panda3D应用程序
# 使用Panda3D引擎编辑器创建
# """
#
# from __future__ import print_function
#
# import json
#
# from direct.actor.Actor import Actor
# from panda3d.core import TextNode, CardMaker, TextureStage, NodePath
# #获取渲染管线路径
# import sys
# import os
#
# render_pipeline_path = 'RenderPipelineFile'
# project_root = os.path.dirname(os.path.abspath(__file__))
# sys.path.insert(0,project_root)
# sys.path.insert(0,render_pipeline_path)
#
# import math
# from random import random,randint,seed
# from panda3d.core import Vec3,load_prc_file_data,Filename
# from direct.showbase.ShowBase import ShowBase
#
# os.chdir(os.path.dirname(os.path.realpath(__file__)))
#
# class MainApp(ShowBase):
# def __init__(self):
# load_prc_file_data("","""
# win-size 1200 720
# window-title Render
# """)
#
# pipeline_path = "../../"
#
# if not os.path.isfile(os.path.join(pipeline_path,"setup.py")):
# pipeline_path = "../../RenderPipeline"
#
# sys.path.insert(0,pipeline_path)
#
# from rpcore import RenderPipeline,SpotLight
# self.render_pipeline = RenderPipeline()
# self.render_pipeline.create(self)
#
# from rpcore.util.movement_controller import MovementController
#
# self.render_pipeline.daytime_mgr.time = "12:00"
# self._loadFont()
#
# self.loadFullScene()
# self.loadGUIFromJSON()
#
# self.controller = MovementController(self)
# self.controller.set_initial_position(
# Vec3(-7.5,-5.3,1.8),Vec3(-5.9,-4.0,1.6))
# self.controller.setup()
#
# base.accept("l",self.tour)
#
# def _loadFont(self):
# """加载中文字体"""
# self.chinese_font = None
# try:
# self.chinese_font = self.loader.loadFont('/usr/share/fonts/truetype/wqy/wqy-microhei.ttc')
# if not self.chinese_font:
# print("警告: 无法加载中文字体,将使用默认字体")
# else:
# print("✓ 中文字体加载成功")
# except:
# print("警告: 无法加载中文字体,将使用默认字体")
# self.chinese_font = None
#
# def getChineseFont(self):
# """获取中文字体"""
# return self.chinese_font
#
# def loadFullScene(self):
# """加载完整场景,包括所有元素"""
# try:
# scene_file = "scene.bam"
# if os.path.exists(scene_file):
# # 使用readBamFile加载完整场景
# from panda3d.core import BamCache
# BamCache.getGlobalPtr().setActive(False) # 禁用缓存以避免问题
#
# scene = self.loader.loadModel(Filename.fromOsSpecific(scene_file))
# if scene:
# scene.reparentTo(self.render)
# self.render_pipeline.prepare_scene(scene)
# print("✓ 完整场景加载成功")
#
# # 处理场景中的各种元素
# self.processSceneElements(scene)
# else:
# print("⚠️ 场景文件加载失败")
# else:
# print("⚠️ 未找到场景文件")
# except Exception as e:
# print(f"加载完整场景时出错: {{str(e)}}")
# import traceback
# traceback.print_exc()
#
# def processSceneElements(self, scene):
# """处理场景中的各种元素"""
# try:
# # 处理光源
# self.processLights(scene)
#
# # 处理GUI元素
# self.processGUIElements(scene)
#
# except Exception as e:
# print(f"处理场景元素时出错: {{str(e)}}")
#
# def processLights(self, scene):
# """处理场景中的光源"""
# try:
# # 查找并处理点光源
# point_lights = scene.findAllMatches("**/=element_type=point_light")
# for light_node in point_lights:
# try:
# from RenderPipelineFile.rpcore import PointLight
# light = PointLight()
#
# # 恢复光源属性
# if light_node.hasTag("light_energy"):
# light.energy = float(light_node.getTag("light_energy"))
# else:
# light.energy = 5000
#
# light.radius = 1000
# light.inner_radius = 0.4
# light.set_color_from_temperature(5 * 1000.0)
# light.casts_shadows = True
# light.shadow_map_resolution = 256
#
# light.setPos(light_node.getPos())
# self.render_pipeline.add_light(light)
# print(f"✓ 点光源 {{light_node.getName()}} 恢复成功")
# except Exception as e:
# print(f"恢复点光源 {{light_node.getName()}} 失败: {{str(e)}}")
#
# # 查找并处理聚光灯
# spot_lights = scene.findAllMatches("**/=element_type=spot_light")
# for light_node in spot_lights:
# try:
# from RenderPipelineFile.rpcore import SpotLight
# light = SpotLight()
#
# # 恢复光源属性
# if light_node.hasTag("light_energy"):
# light.energy = float(light_node.getTag("light_energy"))
# else:
# light.energy = 5000
#
# light.radius = 1000
# light.inner_radius = 0.4
# light.set_color_from_temperature(5 * 1000.0)
# light.casts_shadows = True
# light.shadow_map_resolution = 256
#
# light.setPos(light_node.getPos())
# self.render_pipeline.add_light(light)
# print(f"✓ 聚光灯 {{light_node.getName()}} 恢复成功")
# except Exception as e:
# print(f"恢复聚光灯 {{light_node.getName()}} 失败: {{str(e)}}")
#
# except Exception as e:
# print(f"处理光源时出错: {{str(e)}}")
#
# def processGUIElements(self, scene):
# """处理场景中的GUI元素"""
# try:
# # 查找并处理2D图像
# images_2d = scene.findAllMatches("**/=gui_type=image_2d")
# for img_node in images_2d:
# try:
# # GUI元素通常在场景加载时自动处理
# print(f"✓ 2D图像 {{img_node.getName()}} 已加载")
# except Exception as e:
# print(f"处理2D图像 {{img_node.getName()}} 失败: {{str(e)}}")
#
# except Exception as e:
# print(f"处理GUI元素时出错: {{str(e)}}")
#
# def tour(self):
# mopath = (
# (Vec3(-10.8645000458, 9.76458263397, 2.13306283951), Vec3(-133.556228638, -4.23447799683, 0.0)),
# (Vec3(-10.6538448334, -5.98406457901, 1.68028640747), Vec3(-59.3999938965, -3.32706642151, 0.0)),
# (Vec3(9.58458328247, -5.63625621796, 2.63269257545), Vec3(58.7906494141, -9.40668964386, 0.0)),
# (Vec3(6.8135137558, 11.0153560638, 2.25509500504), Vec3(148.762527466, -6.41223621368, 0.0)),
# (Vec3(-9.07093334198, 3.65908527374, 1.42396306992), Vec3(245.362503052, -3.59927511215, 0.0)),
# (Vec3(-8.75390911102, -3.82727789879, 0.990055501461), Vec3(296.090484619, -0.604830980301, 0.0)),
# )
# self.controller.play_motion_path(mopath,3.0)
#
# def loadGUIFromJSON(self):
# gui_json_path = "gui/gui_elements.json"
#
# try:
# if os.path.exists(gui_json_path):
# with open(gui_json_path, "r", encoding="utf-8") as f:
# content = f.read().strip()
# if content:
# gui_data = json.loads(content)
# self.createGUIElement(gui_data)
# except Exception as e:
# print(f"加载GUI元素失败: {{str(e)}}")
# import traceback
# traceback.print_exc()
#
# def createGUIElement(self,element_data):
# try:
# processed_names = set()
# element_original_data={{}}
# for i, gui_info in enumerate(element_data):
# name = gui_info.get("name", f"gui_element_{{i}}")
# element_original_data[name] = {
# "scale": gui_info.get("scale", [1, 1, 1]),
# "position": gui_info.get("position", [0, 0, 0]),
# "parent_name": gui_info.get("parent_name")
# }
# valid_parents = set()
# for gui_info in element_data:
# name = gui_info.get("name", f"gui_element_{{gui_info.get('index', 0)}}")
# valid_parents.add(name)
#
# for i ,gui_info in enumerate(element_data):
# try:
# gui_type = gui_info.get("type","unknown")
# name = gui_info.get("name",f"gui_element_{{i}}")
# position = gui_info.get("position",[0,0,0])
# scale = gui_info.get("scale",[1,1,1])
# tags = gui_info.get("tags",{{}})
# text = gui_info.get("text","")
# image_path = gui_info.get("image_path","")
# video_path = gui_info.get("video_path","")
# bg_image_path = gui_info.get("bg_image_path","")
# parent_name = gui_info.get("parent_name")
#
# if name in processed_names:
# continue
#
# processed_names.add(name)
#
# absolute_position = list(position)
# absolute_scale = list(scale)
#
# if parent_name and parent_name in element_original_data:
# parent_data = element_original_data[parent_name]
# parent_scale = parent_data["scale"]
#
# if gui_type in ["3d_text", "3d_image", "button", "label", "entry", "2d_image",
# "2d_video_screen"]:
# # 位置需要乘以父级缩放来得到绝对位置
# for j in range(min(len(absolute_position), len(parent_scale))):
# absolute_position[j] *= parent_scale[j] if len(parent_scale) > j else parent_scale[0]
#
# # 缩放需要乘以父级缩放来得到绝对缩放
# for j in range(min(len(absolute_scale), len(parent_scale))):
# absolute_scale[j] *= parent_scale[j] if len(parent_scale) > j else parent_scale[0]
#
# new_element = None
#
# if gui_type =="3d_text":
# size = absolute_scale[0] if absolute_scale and len(absolute_scale) > 0 else 0.5
# new_element = self.createGUI3DText(
# pos = tuple(absolute_position),
# text = text,
# size = size
# )
# elif gui_type == "button":
# # 确保传入正确的参数类型
# new_element = self.createGUIButton(
# pos=tuple(absolute_position),
# text=text,
# size=absolute_scale[0] if absolute_scale and len(absolute_scale) > 0 else 1.0,
# )
# except Exception as e:
# print(f"重建GUI元素失败 {{name}}: {{e}}")
# import traceback
# traceback.print_exc()
# continue
# except Exception as e:
# print(f"重建GUI元素失败: {{str(e)}}")
#
# def createGUIButton(self, pos=(0, 0, 0), text="按钮", size=0.1,command=None):
# from direct.gui.DirectGui import DirectButton
#
# button = DirectButton(
# text=text,
# pos=(pos[0], pos[1], pos[2]), # 保持正确的坐标格式
# scale=size, # size 应该是数值而不是元组
# frameColor=(0.2, 0.6, 0.8, 1),
# text_font=self.getChineseFont() if self.getChineseFont() else None,
# rolloverSound=None,
# clickSound=None,
# parent=None,
# command=command
# )
#
# def createGUI3DText(self, pos=(0, 0, 0), text="3D文本", size=0.5):
# """创建3D文本GUI元素"""
# try:
# # 创建文本节点
# text_node = TextNode("gui_3d_text")
# text_node.setText(text)
# text_node.setAlign(TextNode.ACenter)
#
# # 设置字体(如果可用)
# if self.getChineseFont():
# text_node.setFont(self.getChineseFont())
#
# # 创建节点路径并添加到场景
# text_np = self.render.attachNewNode(text_node)
#
# # 设置位置和大小
# text_np.setPos(Vec3(pos[0], pos[1], pos[2]))
# text_np.setScale(size)
#
# # 设置面向摄像机
# #text_np.setBillboardPointEye()
#
# # 设置渲染属性
# text_np.setBin("fixed", 40)
# text_np.setDepthWrite(False)
#
# return text_np
# except Exception as e:
# print(f"❌ 创建3D文本失败: {{str(e)}}")
# import traceback
# traceback.print_exc()
# return None
#
# MainApp().run()
# '''
#
# app_path = os.path.join(build_dir, "main.py")
# with open(app_path, "w", encoding="utf-8") as f:
# f.write(app_code)
def _createStandardSetupFile(self, build_dir, project_name):
"""创建优化的标准setup.py文件"""
setup_code = f'''#!/usr/bin/env python3

354
run_complete_analysis.py Normal file
View File

@ -0,0 +1,354 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
完整的开源率分析脚本
该脚本将执行完整的测试流程并生成准确报告
"""
import json
import os
import subprocess
import sys
def run_command(command, ignore_failure=False):
"""运行命令并返回结果"""
try:
print(f"执行命令: {command}")
result = subprocess.run(command, shell=True, capture_output=True, text=True)
if result.returncode != 0 and not ignore_failure:
print(f"命令执行失败: {result.stderr}")
return False
print("命令执行成功")
return True
except Exception as e:
print(f"执行命令时出错: {e}")
return False
def load_cloc_data(path="cloc.json"):
"""加载并解析cloc统计数据"""
if not os.path.exists(path):
print(f"❌ 未找到 {path} 文件")
return None
with open(path, "r", encoding="utf-8") as f:
return json.load(f)
def load_scancode_data(path="summary.json"):
"""加载并解析ScanCode统计数据"""
if not os.path.exists(path):
print(f"❌ 未找到 {path} 文件")
return None
with open(path, "r", encoding="utf-8") as f:
return json.load(f)
def get_licensed_files_details(scancode_data):
"""获取含许可证文件的详细信息"""
if not scancode_data:
return []
files = scancode_data.get("files", [])
licensed_files = []
# 定义需要排除的目录和文件模式
exclude_patterns = [
"/venv/",
"/.git/",
"/__pycache__/",
"/.idea/",
"/.vscode/",
"/build/",
"/dist/",
".egg-info",
"/Resources/",
"/icons/",
"/tex/",
"cloc.json",
"detailed_cloc.txt",
"summary.json",
"完整开源率分析报告.txt",
"run_complete_analysis.py",
]
for file in files:
# 获取文件路径
file_path = file.get("path", "")
# 只处理类型为"file"的条目
if file.get("type") != "file":
continue
# 检查是否应该排除该文件
should_exclude = False
for pattern in exclude_patterns:
if pattern in file_path:
should_exclude = True
break
# 如果应该排除,则跳过该文件
if should_exclude:
continue
# 检查是否有许可证信息
if file.get("detected_license_expression") or file.get("license_detections"):
licensed_files.append({
"path": file_path,
"license": file.get("detected_license_expression", "Unknown"),
"detections": file.get("license_detections", [])
})
return licensed_files
def get_file_code_lines():
"""从detailed_cloc.txt获取文件代码行数"""
file_code_lines = {}
if not os.path.exists("detailed_cloc.txt"):
print("❌ 未找到 detailed_cloc.txt 文件")
return file_code_lines
with open("detailed_cloc.txt", "r") as f:
cloc_lines = f.readlines()
# 解析cloc输出创建文件路径到代码行数的映射
for line in cloc_lines[3:]: # 跳过标题行
parts = line.strip().split()
if len(parts) >= 4:
try:
file_path = parts[0]
code_lines = int(parts[-1])
# 标准化路径格式
if file_path.startswith('./'):
file_path = file_path[2:]
file_code_lines[file_path] = code_lines
except ValueError:
continue
return file_code_lines
def calculate_accurate_open_source_lines(licensed_files_details, file_code_lines):
"""计算准确的开源代码行数"""
total_licensed_code_lines = 0
found_files = 0
detailed_files = []
for file_info in licensed_files_details:
file_path = file_info["path"]
# 将文件路径标准化
normalized_path = file_path
if normalized_path.startswith('EG/'):
normalized_path = normalized_path[3:] # 去掉开头的EG/
if normalized_path in file_code_lines:
code_lines = file_code_lines[normalized_path]
total_licensed_code_lines += code_lines
found_files += 1
detailed_files.append({
"path": file_path,
"code_lines": code_lines,
"license": file_info["license"]
})
else:
# 尝试其他可能的路径格式
alt_path1 = './' + normalized_path
alt_path2 = 'EG/' + normalized_path
if alt_path1 in file_code_lines:
code_lines = file_code_lines[alt_path1]
total_licensed_code_lines += code_lines
found_files += 1
detailed_files.append({
"path": file_path,
"code_lines": code_lines,
"license": file_info["license"]
})
elif alt_path2 in file_code_lines:
code_lines = file_code_lines[alt_path2]
total_licensed_code_lines += code_lines
found_files += 1
detailed_files.append({
"path": file_path,
"code_lines": code_lines,
"license": file_info["license"]
})
return total_licensed_code_lines, detailed_files
def generate_detailed_report():
"""生成详细报告"""
# 加载数据
cloc_data = load_cloc_data("cloc.json")
scancode_data = load_scancode_data("summary.json")
file_code_lines = get_file_code_lines()
if not cloc_data or not scancode_data:
print("无法加载必要数据文件")
return False
# 获取含许可证文件详情
licensed_files_details = get_licensed_files_details(scancode_data)
# 计算准确的开源代码行数
accurate_open_source_lines, detailed_files = calculate_accurate_open_source_lines(
licensed_files_details, file_code_lines)
# 获取统计数据
total_code_lines = cloc_data.get("SUM", {}).get("code", 0)
total_files = 1075 # 根据脚本分析得出的实际文件数
licensed_files = len(licensed_files_details)
# 计算开源率
open_source_rate = (accurate_open_source_lines / total_code_lines) * 100 if total_code_lines > 0 else 0
# 创建报告内容
report_content = []
report_content.append("项目开源率分析完整报告")
report_content.append("=" * 50)
report_content.append("")
report_content.append("1. 报告概览")
report_content.append("-" * 20)
report_content.append(f"项目总文件数: {total_files}")
report_content.append(f"含许可证文件数: {licensed_files}")
report_content.append(f"项目总代码行数: {total_code_lines}")
report_content.append(f"准确开源代码行数: {accurate_open_source_lines}")
report_content.append(f"代码开源率: {open_source_rate:.2f}%")
report_content.append("")
report_content.append("2. 各语言代码行数分布(包含文件路径)")
report_content.append("-" * 40)
# 按语言分组显示文件
lang_files = {}
with open("detailed_cloc.txt", "r") as f:
cloc_lines = f.readlines()
for line in cloc_lines[3:]: # 跳过标题行
parts = line.strip().split()
if len(parts) >= 4:
try:
file_path = parts[0]
# 从文件路径推断语言(简化处理)
if file_path.endswith('.py'):
lang = 'Python'
elif file_path.endswith('.js'):
lang = 'JavaScript'
elif file_path.endswith('.cpp') or file_path.endswith('.cc'):
lang = 'C++'
elif file_path.endswith('.h'):
lang = 'C/C++ Header'
elif file_path.endswith('.glsl'):
lang = 'GLSL'
elif file_path.endswith('.qml'):
lang = 'QML'
elif file_path.endswith('.xml'):
lang = 'XML'
elif file_path.endswith('.json'):
lang = 'JSON'
elif file_path.endswith('.md'):
lang = 'Markdown'
elif file_path.endswith('.html'):
lang = 'HTML'
elif file_path.endswith('.css'):
lang = 'CSS'
elif file_path.endswith('.sh'):
lang = 'Shell'
elif file_path.endswith('.yml') or file_path.endswith('.yaml'):
lang = 'YAML'
else:
lang = 'Other'
if lang not in lang_files:
lang_files[lang] = []
lang_files[lang].append((file_path, int(parts[-1])))
except ValueError:
continue
for lang, files in lang_files.items():
report_content.append(f"\n{lang}语言文件:")
report_content.append(f" 文件总数: {len(files)}")
total_lines = sum([f[1] for f in files])
report_content.append(f" 代码行数: {total_lines}")
report_content.append(" 文件列表:")
for file_path, code_lines in files[:10]: # 只显示前10个文件
report_content.append(f" {file_path}: {code_lines}")
if len(files) > 10:
report_content.append(f" ... 还有 {len(files) - 10} 个文件")
report_content.append("")
report_content.append("3. 含许可证的开源文件详情")
report_content.append("-" * 30)
# 按许可证类型分组显示文件
files_by_license = {}
for file_info in detailed_files:
license_type = file_info.get("license", "Unknown")
if license_type not in files_by_license:
files_by_license[license_type] = []
files_by_license[license_type].append(file_info)
for license_type, files in files_by_license.items():
report_content.append(f"\n许可证类型: {license_type}")
report_content.append(f" 文件数量: {len(files)}")
total_lines = sum([f["code_lines"] for f in files])
report_content.append(f" 代码行数: {total_lines}")
report_content.append(" 文件列表:")
for file_info in files:
report_content.append(f" {file_info['path']}: {file_info['code_lines']}")
# 保存报告
with open("完整开源率分析报告.txt", "w", encoding="utf-8") as f:
f.write("\n".join(report_content))
print("完整报告已生成:完整开源率分析报告.txt")
return True
def main():
"""主函数"""
print("开始执行完整的开源率分析流程...")
# 步骤1: 执行cloc统计代码行数
print("\n步骤1: 执行cloc统计代码行数")
cloc_cmd = "cloc --json --fullpath --not-match-d='(venv|\\.git|__pycache__|\\.idea|\\.vscode|build|dist|.*\\.egg-info|Resources/animations|Resources/materials|Resources/models|Resources/textures|icons|tex)' --not-match-f='(cloc.json|detailed_cloc.txt|summary.json|完整开源率分析报告.txt|run_complete_analysis.py)' . > cloc.json"
if not run_command(cloc_cmd):
print("❌ cloc统计失败")
return False
# 步骤2: 生成详细文件列表
print("\n步骤2: 生成详细文件列表")
detailed_cloc_cmd = "cloc --by-file --fullpath --not-match-d='(venv|\\.git|__pycache__|\\.idea|\\.vscode|build|dist|.*\\.egg-info|Resources/animations|Resources/materials|Resources/models|Resources/textures|icons|tex)' --not-match-f='(cloc.json|detailed_cloc.txt|summary.json|完整开源率分析报告.txt|run_complete_analysis.py)' . | grep -v \"^\\s*$\" | grep -E \"(\\.py|\\.js|\\.cpp|\\.h|\\.glsl|\\.qml|\\.xml|\\.html|\\.css|\\.java|\\.cs|\\.php)\" > detailed_cloc.txt"
if not run_command(detailed_cloc_cmd):
print("❌ 生成详细文件列表失败")
return False
# 步骤3: 执行ScanCode扫描许可证
print("\n步骤3: 执行ScanCode扫描许可证")
scancode_cmd = "scancode --license --classify --summary --json-pp summary.json . --ignore \"venv\" --ignore \".git\" --ignore \"__pycache__\" --ignore \".idea\" --ignore \".vscode\" --ignore \"build\" --ignore \"dist\" --ignore \"*.egg-info\" --ignore \"Resources\" --ignore \"icons\" --ignore \"tex\" --ignore \"cloc.json\" --ignore \"detailed_cloc.txt\" --ignore \"完整开源率分析报告.txt\" --ignore \"run_complete_analysis.py\""
# 忽略失败因为ScanCode会尝试扫描自己生成的summary.json文件导致"失败"
run_command(scancode_cmd, ignore_failure=True)
# 检查summary.json是否生成
if not os.path.exists("summary.json"):
print("❌ ScanCode未生成summary.json文件")
return False
# 步骤4: 生成详细报告
print("\n步骤4: 生成详细报告")
if not generate_detailed_report():
print("❌ 生成报告失败")
return False
print("\n✅ 完整分析流程执行完成!")
print("生成的文件:")
print(" - cloc.json: 代码行数统计")
print(" - detailed_cloc.txt: 详细文件列表")
print(" - summary.json: 许可证扫描结果")
print(" - 完整开源率分析报告.txt: 最终报告")
return True
if __name__ == "__main__":
success = main()
if success:
print("\n🎉 所有步骤执行成功!")
sys.exit(0)
else:
print("\n❌ 执行过程中出现错误!")
sys.exit(1)

File diff suppressed because it is too large Load Diff

View File

@ -836,10 +836,15 @@ class MainApp(ShowBase):
return video_screen
def _loadMovieTexture(self, video_path):
from panda3d.core import Texture, MovieTexture
from panda3d.core import Texture, MovieTexture, Filename
import os
movie_texture = MovieTexture(video_path)
if movie_texture.read(video_path):
# Convert Windows path to Panda3D compatible path format
panda_path = Filename.fromOsSpecific(video_path)
converted_path = str(panda_path)
movie_texture = MovieTexture(converted_path)
if movie_texture.read(converted_path):
self._configureVideoTexture(movie_texture)
return movie_texture

View File

@ -1,106 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Three.js Panel</title>
<style>
body {
margin: 0;
padding: 10px;
background: rgba(0,0,0,0.7);
color: white;
font-family: Arial, sans-serif;
}
#info {
position: absolute;
top: 10px;
left: 10px;
z-index: 100;
}
#canvas-container {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="info">
<h3>场景信息面板</h3>
<p>FPS: <span id="fps">0</span></p>
<p>对象数: <span id="object-count">0</span></p>
</div>
<div id="canvas-container"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
// 初始化Three.js场景
let scene, camera, renderer;
let cube;
function init() {
// 创建场景
scene = new THREE.Scene();
// 创建相机
camera = new THREE.PerspectiveCamera(75,
document.getElementById('canvas-container').offsetWidth /
document.getElementById('canvas-container').offsetHeight,
0.1, 1000);
// 创建渲染器
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setSize(
document.getElementById('canvas-container').offsetWidth,
document.getElementById('canvas-container').offsetHeight
);
document.getElementById('canvas-container').appendChild(renderer.domElement);
// 添加一个立方体
const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshBasicMaterial({
color: 0x00ff00,
wireframe: true
});
cube = new THREE.Mesh(geometry, material);
scene.add(cube);
camera.position.z = 5;
// 开始动画循环
animate();
// 监听窗口大小变化
window.addEventListener('resize', onWindowResize, false);
}
function onWindowResize() {
camera.aspect = document.getElementById('canvas-container').offsetWidth /
document.getElementById('canvas-container').offsetHeight;
camera.updateProjectionMatrix();
renderer.setSize(
document.getElementById('canvas-container').offsetWidth,
document.getElementById('canvas-container').offsetHeight
);
}
function animate() {
requestAnimationFrame(animate);
// 旋转立方体
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render(scene, camera);
}
// 接收来自Python的消息
function updateInfo(data) {
document.getElementById('fps').textContent = data.fps || 0;
document.getElementById('object-count').textContent = data.objectCount || 0;
}
// 页面加载完成后初始化
window.onload = init;
</script>
</body>
</html>

95
tools/open_source_rate.py Normal file
View File

@ -0,0 +1,95 @@
import os
from pathlib import Path
THIRD_PARTY_DIR_NAMES = {
'node_modules', 'vendor', 'third_party', 'third-party', 'extern', 'external', 'deps',
'Cesium', 'Cesium-1.132', 'dist', 'build'
}
CODE_EXTENSIONS = {
'.py', '.js', '.ts', '.tsx', '.jsx', '.css', '.scss', '.html', '.c', '.h', '.cpp', '.hpp',
'.cc', '.hh', '.m', '.mm', '.java', '.go', '.rs', '.cs', '.vue', '.svelte'
}
SKIP_DIR_NAMES = {
'.git', '.hg', '.svn', '__pycache__', '.mypy_cache', '.pytest_cache', '.idea', '.vscode',
'.venv', 'venv', 'env', '.tox', '.cache', 'Resources', 'icons', 'tex', 'terminal'
}
MAX_FILE_SIZE_BYTES = 2 * 1024 * 1024 # 2 MiB
def is_third_party(path: Path) -> bool:
for part in path.parts:
# normalize case on Windows
name = part.lower()
if name in {n.lower() for n in THIRD_PARTY_DIR_NAMES}:
return True
return False
def should_skip_dir(dirname: str) -> bool:
return dirname.lower() in {n.lower() for n in SKIP_DIR_NAMES}
def is_code_file(path: Path) -> bool:
return path.suffix.lower() in CODE_EXTENSIONS
def count_lines(path: Path) -> int:
try:
if path.stat().st_size > MAX_FILE_SIZE_BYTES:
return 0
# Try text read with universal newlines, fallback to binary
try:
with path.open('r', encoding='utf-8', errors='ignore') as f:
return sum(1 for _ in f)
except Exception:
with path.open('rb') as f:
return f.read().count(b'\n')
except Exception:
return 0
def main(root: Path) -> None:
third_party_loc = 0
first_party_loc = 0
third_party_files = 0
first_party_files = 0
for dirpath, dirnames, filenames in os.walk(root):
# prune directories
dirnames[:] = [d for d in dirnames if not should_skip_dir(d)]
current = Path(dirpath)
current_is_third = is_third_party(current)
for fn in filenames:
p = current / fn
if not is_code_file(p):
continue
loc = count_lines(p)
if current_is_third:
third_party_loc += loc
third_party_files += 1
else:
first_party_loc += loc
first_party_files += 1
total_loc = first_party_loc + third_party_loc
open_source_rate = (third_party_loc / total_loc) * 100 if total_loc else 0.0
print('Open-Source Rate (by LOC): {:.2f}%'.format(open_source_rate))
print('\nBreakdown:')
print(' First-party LOC : {} ({} files)'.format(first_party_loc, first_party_files))
print(' Third-party LOC : {} ({} files)'.format(third_party_loc, third_party_files))
print(' Total LOC : {}'.format(total_loc))
print('\nNotes:')
print(' - Third-party directories are heuristically detected by common names: {}'.format(', '.join(sorted(THIRD_PARTY_DIR_NAMES))))
print(' - Static asset directories are skipped: {}'.format(', '.join(sorted(SKIP_DIR_NAMES))))
print(' - Counted code extensions: {}'.format(', '.join(sorted(CODE_EXTENSIONS))))
if __name__ == '__main__':
main(Path('.').resolve())

35
tox.ini Normal file
View File

@ -0,0 +1,35 @@
[tox]
envlist = py310
[testenv]
deps =
pytest
pytest-qt
PyQt5>=5.15.9
PySide6>=6.8.1
Panda3D>=1.10.15
commands =
pytest demo/quick_script_test.py
[testenv:lint]
deps =
flake8
commands =
flake8 core/ demo/ scripts/
[testenv:coverage]
deps =
pytest
pytest-cov
PyQt5>=5.15.9
PySide6>=6.8.1
Panda3D>=1.10.15
commands =
pytest --cov=core --cov=demo --cov=scripts
[testenv:memory]
deps =
memory-profiler
Panda3D>=1.10.15
commands =
python -m memory_profiler memory_test_example.py

View File

@ -6,11 +6,11 @@ UI模块
- main_window.py: 主窗口设置
"""
from .widgets import CustomPanda3DWidget, CustomFileView, CustomTreeWidget
from .widgets import CustomMeta3DWidget, CustomFileView, CustomTreeWidget
from .main_window import MainWindow, setup_main_window
__all__ = [
'CustomPanda3DWidget',
'CustomMeta3DWidget',
'CustomFileView',
'CustomTreeWidget',
'MainWindow',

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,7 @@ from PyQt5.QtGui import QColor
from PyQt5.QtWidgets import (QLabel, QLineEdit, QDoubleSpinBox, QPushButton,
QTreeWidget, QTreeWidgetItem, QMenu, QCheckBox, QComboBox, QHBoxLayout, QWidget,
QVBoxLayout, QGroupBox, QGridLayout, QSpinBox, QFileDialog, QMessageBox, QSizePolicy)
from PyQt5.QtCore import Qt
from PyQt5.QtCore import Qt, QTimer
from deploy_libs.unicodedata import normalize
from direct.actor.Actor import Actor
from direct.gui import DirectGui
@ -30,6 +30,8 @@ class PropertyPanelManager:
self._propertyLayout = None
self._actor_cache = {}
self._spherical_video_controls = {}
self._transform_monitor_timer = None
self._last_transform_values = {}
self.column_minimum_width = 85
@ -614,6 +616,9 @@ class PropertyPanelManager:
def clearPropertyPanel(self):
"""清空属性面板"""
# 停止变换监控
self.stopTransformMonitoring()
if self._propertyLayout:
while self._propertyLayout.count():
item = self._propertyLayout.takeAt(0)
@ -673,6 +678,7 @@ class PropertyPanelManager:
self.active_check = QCheckBox()
# 根据模型的实际可见性状态设置复选框
self.active_check.setChecked(user_visible)
self.active_check.stateChanged.connect(lambda state: self._toggleModelVisibility(model, state))
self.name_input = QLineEdit(itemText)
self.name_input.returnPressed.connect(
@ -685,14 +691,14 @@ class PropertyPanelManager:
self.name_group.setLayout(name_layout)
self._propertyLayout.addWidget(self.name_group)
if model:
try:
self.active_check.stateChanged.disconnect()
except TypeError:
pass
self.active_check.stateChanged.connect(
lambda state, m=model: self._setUserVisible(m, state == Qt.Checked)
)
# if model:
# try:
# self.active_check.stateChanged.disconnect()
# except TypeError:
# pass
# self.active_check.stateChanged.connect(
# lambda state, m=model: self._setUserVisible(m, state == Qt.Checked)
# )
# nameLabel = QLabel("名称:")
# nameEdit = QLineEdit(itemText)
# self._propertyLayout.addRow(nameLabel, nameEdit)
@ -710,6 +716,8 @@ class PropertyPanelManager:
self.updateLightPropertyPanel(model)
elif model:
self._updateModelPropertyPanel(model)
# 启动变换监控
self.startTransformMonitoring(model)
self._propertyLayout.addStretch()
@ -1086,6 +1094,9 @@ class PropertyPanelManager:
def _cleanupAllReferences(self):
"""清理所有控件引用"""
# 停止变换监控
self.stopTransformMonitoring()
# 清理变换控件引用
self._cleanupTransformControls()
@ -1126,6 +1137,15 @@ class PropertyPanelManager:
node.setPythonTag("user_visible", visible)
self._syncEffectiveVisibility(node)
def _setUserVisible_light(self, node, visible):
"""设置用户可见性状态"""
try:
# 保存可见性状态
node.setPythonTag("user_visible", visible)
except Exception as e:
print(f"设置用户可见性失败: {e}")
def _syncEffectiveVisibility(self, start_node):
"""广度优先,确保父隐藏则子一定隐藏"""
# 获取起始节点的父节点
@ -1164,16 +1184,32 @@ class PropertyPanelManager:
def _toggleModelVisibility(self, model, state):
"""切换模型可见性状态"""
try:
# 用我们自己维护的可见性接口,而不是直接 show/hide
visible = (state == Qt.Checked)
self._setUserVisible(model, visible)
# 特殊处理灯光对象
scene_manager = None
if hasattr(self.world, 'scene_manager'):
scene_manager = self.world.scene_manager
collision_nodes = model.findAllMatches("**/modelCollision_*")
for collision_node in collision_nodes:
collision_node.hide()
if scene_manager and hasattr(scene_manager, 'isLightObject'):
is_light = scene_manager.isLightObject(model)
if is_light:
visible = (state == Qt.Checked)
self._setUserVisible_light(model, visible)
if hasattr(scene_manager, 'toggleLightVisibility'):
scene_manager.toggleLightVisibility(model, visible)
return # 关键这里必须return避免执行下面的普通模型逻辑
else:
visible = (state == Qt.Checked)
self._setUserVisible(model, visible)
collision_nodes = model.findAllMatches("**/modelCollision_*")
for collision_node in collision_nodes:
collision_node.hide()
except Exception as e:
print(f"切换模型可见性失败: {str(e)}")
import traceback
traceback.print_exc()
def refreshModelValues(self, nodePath):
"""刷新模型值显示"""
@ -1199,6 +1235,67 @@ class PropertyPanelManager:
except Exception as e:
print(f"刷新模型值显示失败: {e}")
def startTransformMonitoring(self, nodePath):
"""开始监控节点的变换变化"""
# 如果已有监控器在运行,先停止它
self.stopTransformMonitoring()
# 保存初始变换值
self._saveCurrentTransformValues(nodePath)
# 创建并启动定时器
self._transform_monitor_timer = QTimer()
self._transform_monitor_timer.timeout.connect(lambda: self._checkTransformChanges(nodePath))
self._transform_monitor_timer.start(100) # 每100毫秒检查一次
def stopTransformMonitoring(self):
"""停止监控节点的变换变化"""
if self._transform_monitor_timer:
self._transform_monitor_timer.stop()
self._transform_monitor_timer.deleteLater()
self._transform_monitor_timer = None
# 清除保存的变换值
self._last_transform_values.clear()
def _saveCurrentTransformValues(self, nodePath):
"""保存当前节点的变换值"""
try:
pos = nodePath.getPos()
hpr = nodePath.getHpr()
scale = nodePath.getScale()
self._last_transform_values = {
'pos': (pos.getX(), pos.getY(), pos.getZ()),
'hpr': (hpr.getX(), hpr.getY(), hpr.getZ()),
'scale': (scale.getX(), scale.getY(), scale.getZ())
}
except Exception as e:
print(f"保存变换值失败: {e}")
def _checkTransformChanges(self, nodePath):
"""检查节点变换是否发生变化"""
try:
# 获取当前变换值
pos = nodePath.getPos()
hpr = nodePath.getHpr()
scale = nodePath.getScale()
current_values = {
'pos': (pos.getX(), pos.getY(), pos.getZ()),
'hpr': (hpr.getX(), hpr.getY(), hpr.getZ()),
'scale': (scale.getX(), scale.getY(), scale.getZ())
}
# 比较变换值是否发生变化
if current_values != self._last_transform_values:
# 变换已更改,刷新属性面板
self.refreshModelValues(nodePath)
# 更新保存的变换值
self._last_transform_values = current_values
except Exception as e:
print(f"检查变换变化失败: {e}")
def _refreshGUIElementValues(self, gui_element):
"""刷新GUI元素值显示"""
try:
@ -4670,10 +4767,8 @@ class PropertyPanelManager:
is_first_load = not video_screen.hasTag("video_path") or not video_screen.getTag("video_path")
needs_second_call = False
if is_first_load and not hasattr(self, '_first_load_processed'):
needs_second_call = True
self._first_load_processed = True
# 确保之前的视频流已停止
self._stop3DVideo(video_screen)
load_url_btn = None
if hasattr(self, '_current_load_url_btn_3d'):
@ -4706,9 +4801,6 @@ class PropertyPanelManager:
from PyQt5.QtWidgets import QApplication
QApplication.processEvents()
# 停止之前可能正在播放的视频
self._stop3DVideo(video_screen)
# 使用 OpenCV 打开视频流
cap = cv2.VideoCapture(url)
if not cap.isOpened():
@ -4821,13 +4913,6 @@ class PropertyPanelManager:
print(f"✅ 开始在3D视频屏幕上播放网络视频流: {url}")
if needs_second_call:
print("检测到首次加载再次调用_loadVideoFromURLWithOpenCV_3D以确保正确显示")
from PyQt5.QtCore import QTimer
QTimer.singleShot(100, lambda: self._loadVideoFromURLWithOpenCV_3D(video_screen, url))
QTimer.singleShot(200, lambda: setattr(self, '_first_load_processed', False) if hasattr(self,
'_first_load_processed') else None)
# 恢复按钮状态
update_button_text("加载URL")
update_button_enabled(True)
@ -5443,11 +5528,22 @@ class PropertyPanelManager:
light_group = QGroupBox("光源属性")
light_layout = QGridLayout()
current_energy = light_object.energy
stored_energy = None
if current_energy == 0.0 and model.hasTag("stored_energy"):
try:
stored_energy = float(model.getTag("stored_energy"))
except ValueError:
pass
display_energy = stored_energy if stored_energy is not None and stored_energy > 0 else current_energy
# 能量
light_layout.addWidget(QLabel("能量:"), 0, 0)
energySpinBox = QDoubleSpinBox()
energySpinBox.setRange(0, 10000)
energySpinBox.setValue(light_object.energy)
energySpinBox.setValue(display_energy)
energySpinBox.valueChanged.connect(lambda v: self._updateLightEnergy(light_object, v))
light_layout.addWidget(energySpinBox, 0, 1, 1, 3)
@ -6299,7 +6395,7 @@ class PropertyPanelManager:
"""应用漫反射贴图"""
try:
from RenderPipelineFile.rpcore.loader import RPLoader
from panda3d.core import TextureStage
from panda3d.core import TextureStage, TransparencyAttrib
# 加载纹理
texture = RPLoader.load_texture(texture_path)
@ -6309,10 +6405,19 @@ class PropertyPanelManager:
if node and material:
print(f"正在为节点 {node.getName()} 应用漫反射贴图")
# 检查纹理是否包含透明度信息
has_alpha = False
# 检查纹理格式
format_name = str(texture.getFormat())
if 'alpha' in format_name.lower() or 'rgba' in format_name.lower():
has_alpha = True
print(f"纹理格式: {texture.getFormat()}, 包含透明通道: {has_alpha}")
# 检查是否有金属性贴图选择合适的PBR效果
print("🔧 检查金属性贴图并选择合适的PBR效果...")
has_metallic = self._hasMetallicTexture(node)
needs_alpha = self._needsAlphaTesting(node)
needs_alpha = self._needsAlphaTesting(node) or has_alpha
if has_metallic:
print("✅ 检测到金属性贴图使用支持金属性的PBR效果")
@ -6360,6 +6465,15 @@ class PropertyPanelManager:
node.setTexture(diffuse_stage, texture)
print("漫反射贴图已应用到p3d_Texture0槽")
# 如果纹理包含透明度,启用透明度渲染
if has_alpha:
print("检测到透明纹理,启用透明度渲染")
node.setTransparency(TransparencyAttrib.MAlpha)
node.setDepthWrite(False) # 透明物体通常不写入深度缓冲区
else:
# 确保关闭透明度(避免之前设置的影响)
node.setTransparency(TransparencyAttrib.MNone)
# 调试信息:显示当前纹理阶段
print("=== 漫反射贴图应用后的纹理阶段信息 ===")
all_stages = node.findAllTextureStages()
@ -6412,6 +6526,7 @@ class PropertyPanelManager:
has_diffuse_texture = True
print(f" 🔍 发现现有漫反射贴图: {tex.getName()}")
# 如果没有漫反射贴图,必须创建白色纹理,否则法线映射会失效
if not has_diffuse_texture:
print("⚠️ 没有漫反射贴图,创建白色纹理确保法线映射正常工作...")
@ -8746,12 +8861,12 @@ class PropertyPanelManager:
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})")
#print(f"✅ 更新Day Time设置: {plugin_name}.{setting_name} = {value} (归一化: {normalized_value:.3f})")
# 保存设置到配置文件
try:
plugin_mgr.save_daytime_overrides("/$$rpconfig/daytime.yaml")
print("✅ Day Time设置已保存到配置文件")
#print("✅ Day Time设置已保存到配置文件")
except Exception as e:
print(f"⚠️ 保存配置文件失败: {e}")
@ -8759,7 +8874,7 @@ class PropertyPanelManager:
try:
from RenderPipelineFile.rpcore.util.network_communication import NetworkCommunication
NetworkCommunication.send_async(NetworkCommunication.DAYTIME_PORT, "loadconf")
print("✅ 已通知Day Time Editor重新加载配置")
#print("✅ 已通知Day Time Editor重新加载配置")
except Exception as e:
print(f"⚠️ 通知Day Time Editor失败: {e}")
@ -9054,22 +9169,98 @@ class PropertyPanelManager:
def _getActor(self, origin_model):
if origin_model in self._actor_cache:
return self._actor_cache[origin_model]
# 首先检查是否可以直接从内存中的模型创建Actor
if origin_model.hasTag("can_create_actor_from_memory") and origin_model.getTag("can_create_actor_from_memory").lower() == "true":
try:
print(f"[Actor加载] 直接从内存模型创建Actor: {origin_model.getName()}")
# 直接从内存中的模型创建Actor
test_actor = Actor(origin_model)
anims = test_actor.getAnimNames()
test_actor.reparentTo(self.world.render)
self._actor_cache[origin_model] = test_actor
print(f"[Actor加载] 内存创建检测到动画: {anims}")
if anims:
return test_actor
else:
test_actor.cleanup()
test_actor.removeNode()
except Exception as e:
print(f"从内存模型创建Actor失败: {e}")
# 如果不能直接从内存创建,再尝试通过文件路径加载
filepath = origin_model.getTag("model_path")
if not filepath:
return None
print(f"[Actor加载] 尝试加载: {filepath}")
# 处理跨平台路径问题
import os
# 检查路径是否有效,如果无效则尝试修复
if not os.path.exists(filepath):
original_filepath = filepath
# 尝试多种修复策略
fixed = False
# 策略1: 处理Linux风格路径在Windows上的问题
if filepath.startswith('/') and ':' not in filepath:
# 提取文件名并尝试在当前目录查找
filename = os.path.basename(filepath)
potential_path = os.path.join(os.getcwd(), filename)
if os.path.exists(potential_path):
filepath = potential_path
fixed = True
# 策略2: 处理路径分隔符问题
if not fixed:
# 尝试规范化路径
normalized_path = os.path.normpath(filepath)
if os.path.exists(normalized_path):
filepath = normalized_path
fixed = True
# 策略3: 在Resources目录中查找
if not fixed:
# 尝试在Resources目录中查找文件
resources_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "Resources")
filename = os.path.basename(filepath)
potential_path = os.path.join(resources_path, filename)
if os.path.exists(potential_path):
filepath = potential_path
fixed = True
if fixed:
print(f"路径修复: {original_filepath} -> {filepath}")
# 更新模型标签
origin_model.setTag("model_path", filepath)
else:
print(f"[警告] 模型文件不存在: {filepath}")
return None
# 检查是否是 FBX 文件,如果是,使用专门的 FBX 动画加载器
if filepath.lower().endswith('.fbx'):
return self._createFBXActor(origin_model, filepath)
pass
#return self._createFBXActor(origin_model, filepath)
# 其他格式使用标准 Actor 加载
try:
import gltf
print(f"[GLTF加载] 尝试加载: {filepath}")
from panda3d.core import Filename
# 将Panda3D路径转换为操作系统特定路径
panda_filename = Filename(filepath)
os_specific_path = panda_filename.to_os_specific()
print(f"[路径转换] {filepath} -> {os_specific_path}")
print(f"[GLTF加载] 尝试加载: {os_specific_path}")
# 使用明确的设置确保动画被加载
gltf_settings = gltf.GltfSettings(skip_animations=False)
# test_actor=Actor(NodePath(gltf._loader.GltfLoader.load_file(filepath,None)))
test_actor = Actor(NodePath(gltf.load_model(filepath, None)))
model_root = gltf.load_model(os_specific_path, gltf_settings)
model_node = NodePath(model_root)
test_actor = Actor(model_node)
anims = test_actor.getAnimNames()
test_actor.reparentTo(self.world.render)
self._actor_cache[origin_model] = test_actor
@ -9711,6 +9902,12 @@ except Exception as e:
collision_group.setLayout(collision_layout)
self._propertyLayout.addWidget(collision_group)
# 同步一次状态,确保主按钮与可见性按钮齐全
try:
self._updateCollisionPanelState(model)
except Exception:
pass
except Exception as e:
print(f"创建碰撞面板失败: {e}")
import traceback
@ -10065,8 +10262,9 @@ except Exception as e:
collision_np.show()
print(f"显示碰撞:{model.getName()}")
# 立即更新按钮状态
# 立即更新按钮状态,并同步更新面板(确保缺失主按钮时补建)
self._updateCollisionVisibilityButton(model)
self._updateCollisionPanelState(model)
except Exception as e:
print(f"切换碰撞可见性失败: {e}")
@ -10274,26 +10472,18 @@ except Exception as e:
has_collision = self._hasCollision(model)
print(f"模型 {model.getName()} 是否有碰撞体: {has_collision}-------------------------------------------------")
# 更新状态徽章(使用固定宽度)
if has_collision:
new_badge = self.createFixedStatusBadge("已启用", "green")
else:
new_badge = self.createFixedStatusBadge("未启用", "red")
# 替换旧的徽章
old_badge = self.collision_status_badge
parent_layout = old_badge.parent().layout()
if parent_layout:
# 找到旧徽章在布局中的位置
for i in range(parent_layout.count()):
item = parent_layout.itemAt(i)
if item and item.widget() == old_badge:
# 移除旧徽章并添加新徽章
parent_layout.removeWidget(old_badge)
old_badge.deleteLater()
parent_layout.addWidget(new_badge, 0, 0, 1, 1) # 状态徽章在第0行第1列
self.collision_status_badge = new_badge
break
# 更新状态徽章(直接修改现有控件,避免布局冲突)
if hasattr(self, 'collision_status_badge') and self.collision_status_badge:
if has_collision:
self.collision_status_badge.setText("已启用")
# 使用固定宽度的绿色样式,保持尺寸一致
self.collision_status_badge.setStyleSheet(self.badge_style_green_fixed)
else:
self.collision_status_badge.setText("未启用")
# 使用固定宽度的红色样式,保持尺寸一致
self.collision_status_badge.setStyleSheet(self.badge_style_red_fixed)
# 触发重绘,确保立即可见
self.collision_status_badge.update()
if has_collision:
# 有碰撞:显示移除按钮,下拉框变为只读并显示当前类型
@ -10309,6 +10499,18 @@ except Exception as e:
except:
pass
self.collision_button.clicked.connect(lambda: self._removeCollisionAndUpdate(model))
else:
# 若不存在主按钮,则创建“移除碰撞”按钮并加入布局
try:
self.collision_button = self.createModernButton("移除碰撞")
self.collision_button.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
self.collision_button.setStyleSheet(self._collision_button_style())
self.collision_button.clicked.connect(lambda: self._removeCollisionAndUpdate(model))
if hasattr(self, 'collision_layout') and self.collision_layout is not None:
row = self.collision_layout.rowCount()
self.collision_layout.addWidget(self.collision_button, row, 0, 1, 4)
except Exception as _e:
print(f"创建移除碰撞按钮失败: {_e}")
# 获取并显示当前碰撞类型,设置为只读
current_shape = self._getCurrentCollisionShape(model)
@ -10328,6 +10530,13 @@ except Exception as e:
self.collision_visibility_button.setVisible(True)
self._updateCollisionVisibilityButton(model)
# 确保按钮顺序:可见性按钮在上,主按钮在下
if hasattr(self, 'collision_layout'):
try:
self._repositionButtons(self.collision_layout.rowCount())
except Exception:
pass
print(f"碰撞面板状态更新:有碰撞 - {current_shape}")
else:
@ -10343,6 +10552,18 @@ except Exception as e:
except:
pass
self.collision_button.clicked.connect(lambda: self._addCollisionAndUpdate(model))
else:
# 若不存在主按钮,则创建“添加碰撞”按钮并加入布局
try:
self.collision_button = self.createModernButton("添加碰撞")
self.collision_button.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
self.collision_button.setStyleSheet(self._collision_button_style())
self.collision_button.clicked.connect(lambda: self._addCollisionAndUpdate(model))
if hasattr(self, 'collision_layout') and self.collision_layout is not None:
row = self.collision_layout.rowCount()
self.collision_layout.addWidget(self.collision_button, row, 0, 1, 4)
except Exception as _e:
print(f"创建添加碰撞按钮失败: {_e}")
# 恢复为可编辑状态
self.collision_shape_combo.setEnabled(True)
@ -10356,6 +10577,13 @@ except Exception as e:
if hasattr(self, 'collision_visibility_button'):
self.collision_visibility_button.setVisible(False)
# 确保按钮顺序:主按钮位于底部
if hasattr(self, 'collision_layout'):
try:
self._repositionButtons(self.collision_layout.rowCount())
except Exception:
pass
print(f"碰撞面板状态更新:无碰撞 - 可编辑")
except Exception as e:
@ -11018,13 +11246,24 @@ except Exception as e:
layout = self.collision_layout
is_collision_visible = self._isCollisionVisible(model)
# 找到合适的行位置
# 放在当前最后一行
current_row = layout.rowCount()
self.collision_visibility_button = QPushButton("隐藏碰撞" if is_collision_visible else "显示碰撞")
self.collision_visibility_button.setStyleSheet(self._collision_button_style())
self.collision_visibility_button.clicked.connect(lambda: self._toggleCollisionVisibility(model))
layout.addWidget(self.collision_visibility_button, current_row - 1, 0, 1, 4)
layout.addWidget(self.collision_visibility_button, current_row, 0, 1, 4)
# 确保存在主按钮(有碰撞时应显示移除碰撞)
if self._hasCollision(model) and not hasattr(self, 'collision_button'):
try:
self.collision_button = self.createModernButton("移除碰撞")
self.collision_button.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
self.collision_button.setStyleSheet(self._collision_button_style())
self.collision_button.clicked.connect(lambda: self._removeCollisionAndUpdate(model))
layout.addWidget(self.collision_button, current_row + 1, 0, 1, 4)
except Exception as _e:
print(f"在可见性按钮后创建移除碰撞按钮失败: {_e}")
except Exception as e:
print(f"添加可见性按钮失败: {e}")
@ -11035,22 +11274,39 @@ except Exception as e:
if hasattr(self, 'collision_layout'):
layout = self.collision_layout
# 移动可见性按钮
if hasattr(self, 'collision_visibility_button'):
layout.addWidget(self.collision_visibility_button, new_row, 0, 1, 4)
new_row += 1
def _remove_if_present(w):
try:
if not w:
return
# 从布局中移除该控件(如果已存在)
for i in range(layout.count()):
item = layout.itemAt(i)
if item and item.widget() is w:
layout.removeWidget(w)
break
except Exception:
pass
# 移动主按钮
if hasattr(self, 'collision_button'):
layout.addWidget(self.collision_button, new_row, 0, 1, 4)
# 计算底部行
bottom_row = layout.rowCount()
# 按固定顺序重新放置:先可见性按钮,再主按钮
if hasattr(self, 'collision_visibility_button') and self.collision_visibility_button:
_remove_if_present(self.collision_visibility_button)
layout.addWidget(self.collision_visibility_button, bottom_row, 0, 1, 4)
bottom_row += 1
if hasattr(self, 'collision_button') and self.collision_button:
_remove_if_present(self.collision_button)
layout.addWidget(self.collision_button, bottom_row, 0, 1, 4)
except Exception as e:
print(f"重新定位按钮失败: {e}")
def _hideCollisionParameterControls(self):
"""隐藏碰撞参数控件(保留按钮)"""
"""隐藏并移除碰撞参数控件与其所在的行(保留状态/形状/按钮"""
try:
# 清理属性引用,但保留按钮
# 1) 先清理对象属性引用,避免残留信号
param_attrs = [
'collision_pos_x', 'collision_pos_y', 'collision_pos_z',
'collision_radius',
@ -11058,47 +11314,72 @@ except Exception as e:
'collision_capsule_radius', 'collision_capsule_height',
'collision_normal_x', 'collision_normal_y', 'collision_normal_z'
]
# 隐藏并删除参数控件
for attr in param_attrs:
if hasattr(self, attr):
widget = getattr(self, attr)
if widget:
widget.setVisible(False)
widget.setParent(None)
widget.deleteLater()
try:
if widget:
widget.setParent(None)
widget.deleteLater()
except Exception:
pass
delattr(self, attr)
# 同时清理可能的标签控件
if hasattr(self, 'collision_layout'):
# 2) 深度移除布局中第2行索引>=2之后的所有参数相关项保留按钮
if hasattr(self, 'collision_layout') and self.collision_layout is not None:
layout = self.collision_layout
# 收集需要移除的控件(不包括基本控件和按钮)
widgets_to_remove = []
def _remove_layout_recursive(q_layout):
try:
while q_layout.count():
child = q_layout.takeAt(0)
if child.widget():
w = child.widget()
w.setParent(None)
w.deleteLater()
elif child.layout():
_remove_layout_recursive(child.layout())
except Exception:
pass
for i in range(layout.rowCount()):
if i >= 2: # 从第3行开始检查
for j in range(layout.columnCount()):
item = layout.itemAtPosition(i, j)
if item:
widget = item.widget()
if widget and hasattr(widget, 'text'):
# 检查是否是参数相关的标签
text = widget.text()
if any(keyword in text for keyword in
['位置偏移', 'X:', 'Y:', 'Z:', '半径:', '尺寸:', '宽度:', '长度:', '高度:',
'法向量:', 'Nx:', 'Ny:', 'Nz:']):
widgets_to_remove.append(widget)
row_count = layout.rowCount()
col_count = layout.columnCount()
# 从参数区域开始清理行索引2及以后但跳过我们要保留的按钮
for i in range(2, row_count):
for j in range(0, col_count):
item = layout.itemAtPosition(i, j)
if not item:
continue
# 若是按钮,且是保留的按钮,则跳过
w = item.widget()
if w is not None:
try:
if (hasattr(self, 'collision_button') and w == self.collision_button) or (hasattr(self, 'collision_visibility_button') and w == self.collision_visibility_button):
continue
except Exception:
pass
layout.removeWidget(w)
w.setParent(None)
w.deleteLater()
elif item.layout():
# 移除嵌套布局(包含标签与输入框)
nested = item.layout()
_remove_layout_recursive(nested)
layout.removeItem(nested)
# 移除参数标签
for widget in widgets_to_remove:
widget.setVisible(False)
widget.setParent(None)
widget.deleteLater()
# 尝试把保留的按钮移动到参数区域第一行行2下方保持整洁
try:
next_row = 2
if hasattr(self, 'collision_visibility_button'):
self.collision_visibility_button.setVisible(False)
if hasattr(self, 'collision_button') and self.collision_button is not None:
layout.addWidget(self.collision_button, next_row, 0, 1, 4)
except Exception:
pass
print("隐藏碰撞参数控件完成(保留按钮)")
print("隐藏碰撞参数控件完成(保留状态、形状与按钮)")
except Exception as e:
print(f"隐藏碰撞参数控件失败: {e}")
import traceback
traceback.print_exc()
traceback.print_exc()

View File

@ -20,7 +20,7 @@ from PyQt5.sip import wrapinstance
from direct.showbase.ShowBaseGlobal import aspect2d
from panda3d.core import ModelRoot, NodePath, CollisionNode
from QPanda3D.QPanda3DWidget import QPanda3DWidget
from QMeta3D.QMeta3DWidget import QMeta3DWidget
from scene import util
from ui.icon_manager import get_icon_manager
@ -456,7 +456,7 @@ class NewProjectDialog(QDialog):
self.accept()
class CustomPanda3DWidget(QPanda3DWidget):
class CustomMeta3DWidget(QMeta3DWidget):
"""自定义Panda3D显示部件"""
def __init__(self, world, parent=None):
@ -4196,7 +4196,7 @@ class CustomTreeWidget(QTreeWidget):
def _createSpotLightNode(self, parent_node, parent_item):
"""创建聚光灯节点"""
from RenderPipelineFile.rpcore import SpotLight
from QPanda3D.Panda3DWorld import get_render_pipeline
from QMeta3D.Meta3DWorld import get_render_pipeline
from panda3d.core import Vec3, NodePath
try:
@ -4252,7 +4252,7 @@ class CustomTreeWidget(QTreeWidget):
def _createPointLightNode(self, parent_node, parent_item):
"""创建点光源节点"""
from RenderPipelineFile.rpcore import PointLight
from QPanda3D.Panda3DWorld import get_render_pipeline
from QMeta3D.Meta3DWorld import get_render_pipeline
from panda3d.core import Vec3, NodePath
try: