[WIP] Panda3D Level Editor

This commit is contained in:
Gyedo Jeon 2009-09-10 23:41:39 +00:00
parent cb4b6986bb
commit 648730f3bb
14 changed files with 1714 additions and 5529 deletions

View File

@ -0,0 +1,38 @@
import os
import imp
import ObjectGlobals as OG
class FileMgr:
""" To handle data file """
def __init__(self, editor):
self.editor = editor
def saveToFile(self, fileName):
try:
f = open(fileName, 'w')
f.write("from pandac.PandaModules import *\n")
f.write("\nobjectMgr = base.le.objectMgr\n")
f.write("# temporary place holder for nodepath\n")
f.write("objects = {}\n")
saveData = self.editor.objectMgr.getSaveData()
for data in saveData:
f.write(data)
f.write('\n')
f.close()
except IOError:
print 'failed to save %s'%fileName
if f:
f.close()
def loadFromFile(self, fileName):
dirname, moduleName = os.path.split(fileName)
if moduleName.endswith('.py'):
moduleName = moduleName[:-3]
file, pathname, description = imp.find_module(moduleName, [dirname])
try:
module = imp.load_module(moduleName, file, pathname, description)
except:
print 'failed to load %s'%fileName

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,138 @@
"""
Base class for Level Editor
You should write your own LevelEditor class inheriting this.
Refer LevelEditor.py for example.
"""
from pandac.PandaModules import *
from direct.showbase.ShowBase import *
from direct.showbase.DirectObject import *
from direct.directtools.DirectGlobals import *
base = ShowBase(False)
from ObjectMgr import *
from FileMgr import *
class LevelEditorBase(DirectObject):
""" Base Class for Panda3D LevelEditor """
def __init__(self):
#loadPrcFileData('startup', 'window-type none')
self.actionEvents = []
self.objectMgr = ObjectMgr(self)
self.fileMgr = FileMgr(self)
def initialize(self):
""" You should call this in your __init__ method of inherited LevelEditor class """
fTk = base.config.GetBool('want-tk', 0)
fWx = 0
base.startDirect(fWantTk = fTk, fWantWx = fWx)
base.closeWindow(base.win)
base.win = base.winList[3]
base.direct.cameraControl.lockRoll = True
base.direct.setFScaleWidgetByCam(1)
unpickables = [
"z-guide",
"y-guide",
"x-guide",
"x-disc-geom",
"x-ring-line",
"x-post-line",
"y-disc-geom",
"y-ring-line",
"y-post-line",
"z-disc-geom",
"z-ring-line",
"z-post-line",
"centerLines",
"majorLines",
"minorLines",
"Sphere",]
for unpickable in unpickables:
base.direct.addUnpickable(unpickable)
base.direct.manipulationControl.optionalSkipFlags |= SKIP_UNPICKABLE
base.direct.manipulationControl.fAllowMarquee = 1
base.direct.manipulationControl.supportMultiView()
base.direct.cameraControl.useMayaCamControls = 1
# [gjeon] to handle delete here first
base.direct.ignore('DIRECT-delete')
# [gjeon] do not use the old way of finding current DR
base.direct.drList.tryToGetCurrentDr = False
# specifiy what obj can be 'selected' as objects
base.direct.selected.addTag('OBJRoot')
self.actionEvents.extend([
# Node path events
('DIRECT-delete', self.handleDelete),
('preRemoveNodePath', self.removeNodePathHook),
('DIRECT_selectedNodePath_fMulti_fTag', self.selectedNodePathHook),
('DIRECT_deselectAll', self.deselectAll),
])
# Add all the action events
for event in self.actionEvents:
if len(event) == 3:
self.accept(event[0], event[1], event[2])
else:
self.accept(event[0], event[1])
def removeNodePathHook(self, nodePath):
if nodePath is None:
return
base.direct.deselect(nodePath)
self.objectMgr.removeObjectByNodePath(nodePath)
if (base.direct.selected.last != None and nodePath.compareTo(base.direct.selected.last)==0):
# if base.direct.selected.last is refering to this
# removed obj, clear the reference
if (hasattr(__builtins__,'last')):
__builtins__.last = None
else:
__builtins__['last'] = None
base.direct.selected.last = None
def handleDelete(self):
reply = wx.MessageBox("Do you want to delete selected?", "Delete?",
wx.YES_NO | wx.ICON_QUESTION)
if reply == wx.YES:
base.direct.removeAllSelected()
self.objectMgr.deselectAll()
else:
# need to reset COA
dnp = base.direct.selected.last
# Update camera controls coa to this point
# Coa2Camera = Coa2Dnp * Dnp2Camera
mCoa2Camera = dnp.mCoa2Dnp * dnp.getMat(base.direct.camera)
row = mCoa2Camera.getRow(3)
coa = Vec3(row[0], row[1], row[2])
base.direct.cameraControl.updateCoa(coa)
def selectedNodePathHook(self, nodePath, fMultiSelect = 0, fSelectTag = 1):
# handle unpickable nodepath
if nodePath.getName() in base.direct.iRay.unpickable:
base.direct.deselect(nodePath)
return
self.objectMgr.selectObject(nodePath)
def deselectAll(self):
self.objectMgr.deselectAll()
def reset(self):
base.direct.deselectAll()
self.objectMgr.reset()
def save(self, fileName):
self.fileMgr.saveToFile(fileName)
def load(self, fileName):
self.reset()
self.fileMgr.loadFromFile(fileName)

View File

@ -1,3 +1,7 @@
from direct.directbase.DirectStart import *
import LevelEditor
base.l = LevelEditor.LevelEditor()
base.le = LevelEditor.LevelEditor()
# You should define LevelEditor instance as
# base.le so it can be reached in global scope
run()

View File

@ -0,0 +1,138 @@
import wx
import os
from wx.lib.agw import fourwaysplitter as FWS
from pandac.PandaModules import *
from direct.wxwidgets.WxAppShell import *
from ViewPort import *
from ObjectPaletteUI import *
from ObjectPropertyUI import *
class PandaTextDropTarget(wx.TextDropTarget):
def __init__(self, editor):
wx.TextDropTarget.__init__(self)
self.editor = editor
def OnDropText(self, x, y, text):
self.editor.objectMgr.addNewObject(text)
class LevelEditorUI(WxAppShell):
""" Class for Panda3D LevelEditor """
frameWidth = 800
frameHeight = 600
appversion = '0.1'
appname = 'Panda3D Level Editor'
def __init__(self, editor, *args, **kw):
# Create the Wx app
self.wxApp = wx.App(redirect = False)
self.wxApp.SetAppName("Panda3D LevelEditor")
self.wxApp.SetClassName("P3DLevelEditor")
self.editor = editor
if not kw.get('size'):
kw['size'] = wx.Size(self.frameWidth, self.frameHeight)
WxAppShell.__init__(self, *args, **kw)
self.initialize()
self.Bind(wx.EVT_SET_FOCUS, self.onSetFocus)
self.Bind(wx.EVT_KEY_DOWN, self.onSetFocus)
def createMenu(self):
menuItem = self.menuFile.Insert(0, -1 , "&New")
self.Bind(wx.EVT_MENU, self.onNew, menuItem)
menuItem = self.menuFile.Insert(1, -1 , "&Load")
self.Bind(wx.EVT_MENU, self.onLoad, menuItem)
menuItem = self.menuFile.Insert(2, -1 , "&Save")
self.Bind(wx.EVT_MENU, self.onSave, menuItem)
def createInterface(self):
self.createMenu()
self.mainFrame = wx.SplitterWindow(self, style = wx.SP_3D | wx.SP_BORDER)
self.leftFrame = wx.SplitterWindow(self.mainFrame, style = wx.SP_3D | wx.SP_BORDER)
self.baseFrame = wx.SplitterWindow(self.mainFrame, style = wx.SP_3D | wx.SP_BORDER)
self.viewFrame = FWS.FourWaySplitter(self.baseFrame, style=wx.SP_LIVE_UPDATE)
self.rightFrame = wx.SplitterWindow(self.baseFrame, style = wx.SP_3D | wx.SP_BORDER)
self.topView = Viewport.makeTop(self.viewFrame)
self.viewFrame.AppendWindow(self.topView)
self.frontView = Viewport.makeFront(self.viewFrame)
self.viewFrame.AppendWindow(self.frontView)
self.leftView = Viewport.makeLeft(self.viewFrame)
self.viewFrame.AppendWindow(self.leftView)
self.perspView = Viewport.makePerspective(self.viewFrame)
self.viewFrame.AppendWindow(self.perspView)
self.leftBarUpPane = wx.Panel(self.leftFrame)
self.leftBarDownPane = wx.Panel(self.leftFrame)
self.rightBarUpPane = wx.Panel(self.rightFrame)
self.rightBarDownPane = wx.Panel(self.rightFrame)
self.leftFrame.SplitHorizontally(self.leftBarUpPane, self.leftBarDownPane)
self.rightFrame.SplitHorizontally(self.rightBarUpPane, self.rightBarDownPane)
self.mainFrame.SplitVertically(self.leftFrame, self.baseFrame, 200)
self.baseFrame.SplitVertically(self.viewFrame, self.rightFrame, 600)
self.viewFrame.SetDropTarget(PandaTextDropTarget(self.editor))
self.leftFrame.SetSashGravity(0.5)
self.rightFrame.SetSashGravity(0.5)
self.baseFrame.SetSashGravity(1.0)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.mainFrame, 1, wx.EXPAND, 0)
self.SetSizer(sizer); self.Layout()
self.objectPaletteUI = ObjectPaletteUI(self.leftBarUpPane, self.editor)
self.objectPropertyUI = ObjectPropertyUI(self.rightBarUpPane, self.editor)
def onSetFocus(self):
print 'wx got focus'
def appInit(self):
"""Overridden from WxAppShell.py."""
# Create a new event loop (to overide default wxEventLoop)
self.evtLoop = wx.EventLoop()
self.oldLoop = wx.EventLoop.GetActive()
wx.EventLoop.SetActive(self.evtLoop)
taskMgr.add(self.wxStep, "evtLoopTask")
def initialize(self):
"""Initializes the viewports and editor."""
self.Update()
ViewportManager.updateAll()
self.wxStep()
ViewportManager.initializeAll()
# Position the camera
if base.trackball != None:
base.trackball.node().setPos(0, 30, 0)
base.trackball.node().setHpr(0, 15, 0)
def wxStep(self, task = None):
"""A step in the WX event loop. You can either call this yourself or use as task."""
while self.evtLoop.Pending():
self.evtLoop.Dispatch()
self.wxApp.ProcessIdle()
if task != None: return task.cont
def onNew(self, evt):
self.editor.reset()
def onLoad(self, evt):
dialog = wx.FileDialog(None, "Choose a file", os.getcwd(), "", "*.py", wx.OPEN)
if dialog.ShowModal() == wx.ID_OK:
self.editor.load(dialog.GetPath())
dialog.Destroy()
def onSave(self, evt):
dialog = wx.FileDialog(None, "Choose a file", os.getcwd(), "", "*.py", wx.SAVE)
if dialog.ShowModal() == wx.ID_OK:
self.editor.save(dialog.GetPath())
dialog.Destroy()

View File

@ -0,0 +1,46 @@
"""
This contains constants related with obj handling.
"""
# index for obj data structure
OBJ_UID = 0
OBJ_NP = 1
OBJ_DEF = 2
OBJ_MODEL = 3
OBJ_PROP = 4
# supported UI types
PROP_UI_ENTRY = '_PropUIEntry'
PROP_UI_COMBO = '_PropUIComboBox'
PROP_UI_RADIO = '_PropUIRadio'
PROP_UI_CHECK = '_PropUICheckBox'
PROP_UI_SLIDE = '_PropUISlider'
PROP_UI_SPIN = '_PropUISpinner'
# index for property definition
PROP_TYPE = 0
PROP_DATATYPE = 1
PROP_FUNC = 2
PROP_DEFAULT = 3
PROP_RANGE = 4
# index for slider UI
RANGE_MIN = 0
RANGE_MAX = 1
RANGE_RATE = 2
# index for create/update function declaration
FUNC_NAME = 0
FUNC_ARGS = 1
# data type of property value
PROP_INT = 0 # int type value
PROP_BOOL = 1 # bool type value
PROP_FLOAT = 2 # float type value
PROP_STR = 3 # string type value
TYPE_CONV = {PROP_INT: int, PROP_BOOL: bool, PROP_FLOAT: float, PROP_STR: str}
# these dynamic args should be used in update function declaration
ARG_VAL = '_arg_val' # value from UI
ARG_OBJ = '_arg_object' # obj information data structure

View File

@ -0,0 +1,77 @@
"""
This is just a sample code.
LevelEditor, ObjectHandler, ObjectPalette should be rewritten
to be game specific.
"""
from direct.actor import Actor
import ObjectGlobals as OG
class ObjectHandler:
""" ObjectHandler will create and update objects """
def __init__(self, editor):
self.editor = editor
def createDoubleSmiley(self, horizontal=True):
root = render.attachNewNode('doubleSmiley')
a = loader.loadModel('models/smiley.egg')
b = loader.loadModel('models/smiley.egg')
if horizontal:
a.setName('left')
b.setName('right')
a.setPos(-1, 0, 0)
b.setPos(1, 0, 0)
else:
a.setName('top')
b.setName('bottom')
a.setPos(0, 0, 1)
b.setPos(0, 0, -1)
a.reparentTo(root)
b.reparentTo(root)
return root
def updateDoubleSmiley(self, val, obj):
objNP = obj[OG.OBJ_NP]
if objNP.find('left'):
objNP.find('left').setPos(-1 * val, 0, 0)
objNP.find('right').setPos(val, 0, 0)
else:
objNP.find('top').setPos(0, 0, 1 * val)
objNP.find('bottom').setPos(0, 0, -1 * val)
def updateSmiley(self, val, obj):
objNP = obj[OG.OBJ_NP]
base.direct.deselectAll()
for child in objNP.findAllMatches("+GeomNode"):
child.removeNode()
for i in range(val):
a = loader.loadModel(obj[OG.OBJ_MODEL])
b = a.find("+GeomNode")
b.setPos(0, i*2, 0)
b.reparentTo(objNP)
a.removeNode()
base.direct.select(objNP)
def createPanda(self):
pandaActor = PandaActor()
return pandaActor
def createGrass(self):
environ = loader.loadModel("models/environment.egg")
environ.setScale(0.25,0.25,0.25)
environ.setPos(-8,42,0)
return environ
class PandaActor(Actor.Actor):
def __init__(self):
Actor.Actor.__init__(self, "models/panda-model.egg",{"walk":"models/panda-walk4.egg"})
self.setScale(0.005)
self.loop("walk")

View File

@ -0,0 +1,421 @@
"""
Defines ObjectMgr
"""
import os, time, wx
from direct.task import Task
from pandac.PandaModules import *
import ObjectGlobals as OG
class ObjectMgr:
""" ObjectMgr will create, manage, update objects in the scene """
def __init__(self, editor):
self.editor = editor
# main obj repository of objects in the scene
self.objects = {}
self.npIndex = {}
self.saveData = []
self.lastUid = ''
self.lastUidMode = 0
self.currNodePath = None
def reset(self):
self.deselectAll()
for id in self.objects.keys():
self.objects[id][OG.OBJ_NP].removeNode()
del self.objects[id]
for np in self.npIndex.keys():
del self.npIndex[np]
self.objects = {}
self.npIndex = {}
self.saveData = []
def genUniqueId(self):
# [gjeon] to solve the problem of unproper $USERNAME
userId = os.path.basename(os.path.expandvars('$USERNAME'))
if userId == '':
userId = base.config.GetString("le-user-id")
if userId == '':
userId = 'unknown'
newUid = str(time.time()) + userId
# prevent duplicates from being generated in the same frame (this can
# happen when creating several new objects at once)
if (self.lastUid == newUid):
# append a value to the end to uniquify the id
newUid = newUid + str(self.lastUidMod)
self.lastUidMod = self.lastUidMod + 1
else:
self.lastUid = newUid
self.lastUidMod = 0
return newUid
def addNewObject(self, typeName, uid = None, model = None, parent=None):
""" function to add new obj to the scene """
if parent is None:
parent = render
objDef = self.editor.objectPalette.findItem(typeName)
newobj = None
if objDef and type(objDef) != dict:
if objDef.createFunction:
funcName = objDef.createFunction[OG.FUNC_NAME]
funcArgs = objDef.createFunction[OG.FUNC_ARGS]
if funcName.startswith('.'):
# when it's using default objectHandler
func = Functor(eval("base.le.objectHandler%s"%funcName))
else:
# when it's not using default objectHandler, whole name of the handling obj
# should be included in function name
func = Functor(eval(funcName))
# create new obj using function and keyword arguments defined in ObjectPalette
newobj = func(**funcArgs)
elif objDef.model is not None:
# since this obj is simple model let's load the model
if model is None:
model = objDef.model
newobj = loader.loadModel(model)
if newobj is None:
return None
newobj.reparentTo(parent)
newobj.setTag('OBJRoot','1')
if uid is None:
uid = self.genUniqueId()
isLoadingArea = False
else:
isLoadingArea = True
# populate obj data using default values
properties = {}
for key in objDef.properties.keys():
properties[key] = objDef.properties[key][OG.PROP_DEFAULT]
# insert obj data to main repository
self.objects[uid] = [uid, newobj, objDef, model, properties]
self.npIndex[NodePath(newobj)] = uid
if not isLoadingArea:
base.direct.select(newobj)
return newobj
def removeObjectByNodePath(self, nodePath):
uid = self.npIndex.get(nodePath)
if uid:
del self.objects[uid]
del self.npIndex[nodePath]
def findObjectById(self, uid):
return self.objects.get(uid)
def findObjectByNodePath(self, nodePath):
uid = self.npIndex.get(nodePath)
if uid is None:
return None
else:
return self.objects[uid]
def deselectAll(self):
self.currNodePath = None
taskMgr.remove('_le_updateObjectUITask')
self.editor.ui.objectPropertyUI.clearPropUI()
def selectObject(self, nodePath):
obj = self.findObjectByNodePath(nodePath)
if obj is None:
return
self.currNodePath = obj[OG.OBJ_NP]
self.spawnUpdateObjectUITask()
self.updateObjectPropertyUI(obj)
def updateObjectPropertyUI(self, obj):
objDef = obj[OG.OBJ_DEF]
objProp = obj[OG.OBJ_PROP]
self.editor.ui.objectPropertyUI.updateProps(obj)
def onEnterObjectPropUI(self, event):
taskMgr.remove('_le_updateObjectUITask')
def onLeaveObjectPropUI(self, event):
self.spawnUpdateObjectUITask()
def spawnUpdateObjectUITask(self):
if self.currNodePath is None:
return
taskMgr.remove('_le_updateObjectUITask')
t = Task.Task(self.updateObjectUITask)
t.np = self.currNodePath
taskMgr.add(t, '_le_updateObjectUITask')
def updateObjectUITask(self, state):
self.editor.ui.objectPropertyUI.propX.setValue(state.np.getX())
self.editor.ui.objectPropertyUI.propY.setValue(state.np.getY())
self.editor.ui.objectPropertyUI.propZ.setValue(state.np.getZ())
h = state.np.getH()
while h < 0:
h = h + 360.0
while h > 360:
h = h - 360.0
p = state.np.getP()
while p < 0:
p = p + 360.0
while p > 360:
p = p - 360.0
r = state.np.getR()
while r < 0:
r = r + 360.0
while r > 360:
r = r - 360.0
self.editor.ui.objectPropertyUI.propH.setValue(h)
self.editor.ui.objectPropertyUI.propP.setValue(p)
self.editor.ui.objectPropertyUI.propR.setValue(r)
self.editor.ui.objectPropertyUI.propSX.setValue(state.np.getSx())
self.editor.ui.objectPropertyUI.propSY.setValue(state.np.getSy())
self.editor.ui.objectPropertyUI.propSZ.setValue(state.np.getSz())
return Task.cont
def updateObjectTransform(self, event):
if self.currNodePath is None:
return
np = self.currNodePath
np.setX(float(self.editor.ui.objectPropertyUI.propX.getValue()))
np.setY(float(self.editor.ui.objectPropertyUI.propY.getValue()))
np.setZ(float(self.editor.ui.objectPropertyUI.propZ.getValue()))
h = float(self.editor.ui.objectPropertyUI.propH.getValue())
while h < 0:
h = h + 360.0
while h > 360:
h = h - 360.0
p = float(self.editor.ui.objectPropertyUI.propP.getValue())
while p < 0:
p = p + 360.0
while p > 360:
p = p - 360.0
r = float(self.editor.ui.objectPropertyUI.propR.getValue())
while r < 0:
r = r + 360.0
while r > 360:
r = r - 360.0
np.setH(h)
np.setP(p)
np.setR(r)
np.setSx(float(self.editor.ui.objectPropertyUI.propSX.getValue()))
np.setSy(float(self.editor.ui.objectPropertyUI.propSY.getValue()))
np.setSz(float(self.editor.ui.objectPropertyUI.propSZ.getValue()))
def updateObjectModel(self, event, obj):
""" replace object's model """
model = event.GetString()
if model is None:
return
if obj[OG.OBJ_MODEL] != model:
base.direct.deselectAll()
objNP = obj[OG.OBJ_NP]
# load new model
newobj = loader.loadModel(model)
newobj.setTag('OBJRoot','1')
# reparent children
objNP.findAllMatches("=OBJRoot").reparentTo(newobj)
# reparent to parent
newobj.reparentTo(objNP.getParent())
# copy transform
newobj.setPos(objNP.getPos())
newobj.setHpr(objNP.getHpr())
newobj.setScale(objNP.getScale())
# delete old geom
del self.npIndex[NodePath(objNP)]
objNP.removeNode()
# register new geom
obj[OG.OBJ_NP] = newobj
obj[OG.OBJ_MODEL] = model
self.npIndex[NodePath(newobj)] = obj[OG.OBJ_UID]
base.direct.select(newobj)
def updateObjectProperty(self, event, obj, propName):
"""
When an obj's property is updated in UI,
this will update it's value in data structure.
And call update function if defined.
"""
objDef = obj[OG.OBJ_DEF]
objProp = obj[OG.OBJ_PROP]
propDef = objDef.properties[propName]
if propDef is None:
return
propType = propDef[OG.PROP_TYPE]
propDataType = propDef[OG.PROP_DATATYPE]
if propType == OG.PROP_UI_SLIDE:
if len(propDef) <= OG.PROP_RANGE:
return
strVal = event.GetString()
if strVal == '':
min = float(propDef[OG.PROP_RANGE][OG.RANGE_MIN])
max = float(propDef[OG.PROP_RANGE][OG.RANGE_MAX])
intVal = event.GetInt()
if intVal is None:
return
val = intVal / 100.0 * (max - min) + min
else:
val = strVal
elif propType == OG.PROP_UI_ENTRY:
val = event.GetString()
elif propType == OG.PROP_UI_SPIN:
val = event.GetInt()
elif propType == OG.PROP_UI_CHECK:
if event.GetInt():
val = True
else:
val = False
elif propType == OG.PROP_UI_RADIO:
val = event.GetString()
elif propType == OG.PROP_UI_COMBO:
val = event.GetString()
else:
# unsupported property type
return
# now update object prop value and call update function
self.updateObjectPropValue(obj, propName, val)
def updateObjectPropValue(self, obj, propName, val):
"""
Update object property value and
call update function if defined.
"""
objDef = obj[OG.OBJ_DEF]
objProp = obj[OG.OBJ_PROP]
propDef = objDef.properties[propName]
propDataType = propDef[OG.PROP_DATATYPE]
val = OG.TYPE_CONV[propDataType](val)
objProp[propName] = val
if propDef[OG.PROP_FUNC] is None:
return
funcName = propDef[OG.PROP_FUNC][OG.FUNC_NAME]
funcArgs = propDef[OG.PROP_FUNC][OG.FUNC_ARGS]
if funcName.startswith('.'):
func = Functor(eval("base.le.objectHandler%s"%funcName))
else:
func = Functor(eval(funcName))
# populate keyword arguments
kwargs = {}
for key in funcArgs.keys():
if funcArgs[key] == OG.ARG_VAL:
kwargs[key] = val
elif funcArgs[key] == OG.ARG_OBJ:
kwargs[key] = obj
else:
kwargs[key] = funcArgs[key]
# finally call update function
func(**kwargs)
def updateObjectProperties(self, nodePath, propValues):
"""
When a saved level is loaded,
update an object's properties
And call update function if defined.
"""
obj = self.findObjectByNodePath(nodePath)
if obj:
for propName in propValues:
self.updateObjectPropValue(obj, propName, propValues[propName])
def traverse(self, parent, parentId = None):
"""
Trasverse scene graph to gather data for saving
"""
for child in parent.getChildren():
if child.hasTag('OBJRoot'):
obj = self.findObjectByNodePath(child)
if obj:
uid = obj[OG.OBJ_UID]
np = obj[OG.OBJ_NP]
objDef = obj[OG.OBJ_DEF]
objModel = obj[OG.OBJ_MODEL]
objProp = obj[OG.OBJ_PROP]
if parentId:
parentStr = "objects['%s']"%parentId
else:
parentStr = "None"
if objModel is None:
self.saveData.append("\nobjects['%s'] = objectMgr.addNewObject('%s', '%s', None, %s)"%(uid, objDef.name, uid, parentStr))
else:
self.saveData.append("\nobjects['%s'] = objectMgr.addNewObject('%s', '%s', '%s', %s)"%(uid, objDef.name, uid, objModel, parentStr))
self.saveData.append("if objects['%s']:"%uid)
self.saveData.append(" objects['%s'].setPos(%s)"%(uid, np.getPos()))
self.saveData.append(" objects['%s'].setHpr(%s)"%(uid, np.getHpr()))
self.saveData.append(" objects['%s'].setScale(%s)"%(uid, np.getScale()))
self.saveData.append(" objectMgr.updateObjectProperties(objects['%s'], %s)"%(uid,objProp))
self.traverse(child, uid)
def getSaveData(self):
self.saveData = []
self.traverse(render)
return self.saveData

View File

@ -0,0 +1,111 @@
"""
This is just a sample code.
LevelEditor, ObjectHandler, ObjectPalette should be rewritten
to be game specific.
You can define object template class inheriting ObjectBase
to define properties shared by multiple object types.
When you are defining properties
you should specify their name, UI type, data type,
update function, default value, and value range.
Then you need implement ObjectPalette class inheriting ObjectPaletteBase,
and in the populate function you can define ObjectPalette tree structure.
"""
from ObjectPaletteBase import *
class ObjectProp(ObjectBase):
def __init__(self, *args, **kw):
ObjectBase.__init__(self, *args, **kw)
self.properties['Abc'] =[OG.PROP_UI_RADIO, # UI type
OG.PROP_STR, # data type
None, # update function
'a', # default value
['a', 'b', 'c']] # value range
class ObjectSmiley(ObjectProp):
def __init__(self, *args, **kw):
ObjectProp.__init__(self, *args, **kw)
self.properties['123'] = [OG.PROP_UI_COMBO,
OG.PROP_INT,
None,
1,
[1, 2, 3]]
class ObjectDoubleSmileys(ObjectProp):
def __init__(self, *args, **kw):
ObjectProp.__init__(self, *args, **kw)
self.properties['Distance'] = [OG.PROP_UI_SLIDE,
OG.PROP_FLOAT,
('.updateDoubleSmiley',
{'val':OG.ARG_VAL, 'obj':OG.ARG_OBJ}),
# In this case, an update function for property is defined
# so whenever you change the value of this property from UI
# this update function will be called with these arguments.
# OG.ARG_VAL will be replaced by the value from UI.
# OG.ARG_OBJ will be replaced by object data structure.
# When an update function is starting with .
# it means this function belongs to the default objectHandler.
1.0, [0, 10, 0.1]]
class ObjectPalette(ObjectPaletteBase):
def __init__(self):
ObjectPaletteBase.__init__(self)
def populate(self):
# Create a group called 'Prop' in the ObjectPalette tree
self.add('Prop')
# Create a group called 'Double Smileys' under 'Prop' group
self.add('Double Smileys', 'Prop')
# Add an object type 'Smiley' which is inheriting ObjectSmiley template
# and have following properties.
self.add(ObjectSmiley(name='Smiley',
model='models/smiley.egg',
models=['models/smiley.egg',
'models/frowney.egg',
'models/jack.egg'],
# when an object is just a simple geometry, you can define
# model, and models like this
# instead of defining createFunction
properties={'Happy':[OG.PROP_UI_CHECK,
OG.PROP_BOOL,
None,
True],
'Number':[OG.PROP_UI_SPIN,
OG.PROP_INT,
('.updateSmiley',
{'val':OG.ARG_VAL, 'obj':OG.ARG_OBJ}),
1, [1, 10]],
}),
'Prop') # This object type will be added under the 'Prop' group.
self.add(ObjectDoubleSmileys(name='H Double Smiley',
createFunction = ('.createDoubleSmiley', {})),
# When the createFunction is defined like this,
# this function will be called to create the object.
# When a create function is starting with .
# it means this function belongs to the default objectHandler.
'Double Smileys')
self.add(ObjectDoubleSmileys(name='V Double Smiley',
createFunction = ('.createDoubleSmiley', {'horizontal':False})),
# You can specify argument for the create function, too
'Double Smileys')
self.add('Animal')
self.add(ObjectBase(name='Panda',
createFunction = ('.createPanda', {}),
properties = {}),
'Animal')
self.add('BG')
self.add(ObjectBase(name='Grass',
createFunction = ('.createGrass', {}),
properties = {}),
'BG')

View File

@ -0,0 +1,84 @@
import ObjectGlobals as OG
class ObjectBase:
""" Base class for obj definitions """
def __init__(self, name='', createFunction = None, model = None, models= [], properties={},
movable = True):
self.name = name
self.createFunction = createFunction
self.model = model
self.models = models
self.properties = properties
self.movable = movable
class ObjectPaletteBase:
"""
Base class for objectPalette
You should write your own ObjectPalette class inheriting this.
Refer ObjectPalette.py for example.
"""
def __init__(self):
self.data = {}
self.populate()
def insertItem(self, item, parentName, data, obj=None):
"""
You can insert item to obj palette tree.
'item' is the name to be inserted, it can be either a group or obj.
If item is a group 'obj' will be None.
'parentName' is the name of parent under where this item will be inserted.
'data' is used in recursive searching.
"""
if type(data) != dict:
return
if parentName is None:
# when adding a group to the root
assert item not in data.keys()
if obj is None:
data[item] = {}
else:
data[item] = obj
return
if parentName in data.keys():
if obj is None:
data[parentName][item] = {}
else:
data[parentName][item] = obj
else:
for key in data.keys():
self.insertItem(item, parentName, data[key], obj)
def add(self, item, parentName = None):
if type(item) == str:
self.insertItem(item, parentName, self.data, None)
else:
self.insertItem(item.name, parentName, self.data, item)
def findItem(self, name, data=None):
if data is None:
data = self.data
if type(data) != dict:
return None
if name in data.keys():
return data[name]
else:
for key in data.keys():
result = self.findItem(name, data[key])
if result:
return result
return None
def populate(self):
# You should implement this in subclass
raise NotImplementedError('populate() must be implemented in ObjectPalette.py')

View File

@ -0,0 +1,53 @@
"""
Defines ObjectPalette tree UI
"""
import wx
import cPickle as pickle
from ObjectPalette import *
class ObjectPaletteUI(wx.Panel):
def __init__(self, parent, editor):
wx.Panel.__init__(self, parent)
self.editor = editor
self.palette = self.editor.objectPalette
self.tree = wx.TreeCtrl(self)
root = self.tree.AddRoot('Objects')
self.addTreeNodes(root, self.palette.data)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.tree, 1, wx.EXPAND, 0)
self.SetSizer(sizer); self.Layout()
parentSizer = wx.BoxSizer(wx.VERTICAL)
parentSizer.Add(self, 1, wx.EXPAND, 0)
parent.SetSizer(parentSizer); parent.Layout()
self.tree.Bind(wx.EVT_TREE_SEL_CHANGED, self.onSelected)
self.tree.Bind(wx.EVT_TREE_BEGIN_DRAG, self.onBeginDrag)
def addTreeNodes(self, parentItem, items):
for key in items.keys():
newItem = self.tree.AppendItem(parentItem, key)
if type(items[key]) == dict:
self.addTreeNodes(newItem, items[key])
else:
self.tree.SetItemPyData(newItem, items[key])
def onSelected(self, event):
data = self.tree.GetItemPyData(event.GetItem())
if data:
print data.properties
def onBeginDrag(self, event):
item = event.GetItem()
if item != self.tree.GetRootItem(): # prevent dragging root item
text = self.tree.GetItemText(item)
print "Starting drag'n'drop with %s..." % repr(text)
tdo = wx.TextDataObject(text)
tds = wx.DropSource(self.tree)
tds.SetData(tdo)
tds.DoDragDrop(True)

View File

@ -0,0 +1,263 @@
"""
UI for object property control
"""
import wx
from wx.lib.scrolledpanel import ScrolledPanel
from direct.wxwidgets.WxSlider import *
import ObjectGlobals as OG
class ObjectPropUI(wx.Panel):
"""
Base class for ObjectPropUIs,
It consists of label area and ui area.
"""
def __init__(self, parent, label):
wx.Panel.__init__(self, parent)
self.parent = parent
self.label = wx.StaticText(self, label=label)
self.uiPane = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.label)
sizer.Add(self.uiPane, 1, wx.EXPAND, 0)
self.SetSizer(sizer)
def setValue(self, value):
self.ui.SetValue(value)
def getValue(self):
return self.ui.GetValue()
def bindFunc(self, inFunc, outFunc, valFunc = None):
self.ui.Bind(wx.EVT_ENTER_WINDOW, inFunc)
self.ui.Bind(wx.EVT_LEAVE_WINDOW, outFunc)
if valFunc:
self.ui.Bind(self.eventType, valFunc)
class ObjectPropUIEntry(ObjectPropUI):
""" UI for string value properties """
def __init__(self, parent, label):
ObjectPropUI.__init__(self, parent, label)
self.ui = wx.TextCtrl(self.uiPane, -1)
self.eventType = wx.EVT_TEXT_ENTER
self.Layout()
def setValue(self, value):
self.ui.SetValue(str(value))
class ObjectPropUISlider(ObjectPropUI):
""" UI for float value properties """
def __init__(self, parent, label, value, minValue, maxValue):
ObjectPropUI.__init__(self, parent, label)
self.ui = WxSlider(self.uiPane, -1, value, minValue, maxValue,
pos = (0,0), size=(140, -1),
style=wx.SL_HORIZONTAL | wx.SL_LABELS)
self.ui.Enable()
self.Layout()
def bindFunc(self, inFunc, outFunc, valFunc = None):
self.ui.Bind(wx.EVT_ENTER_WINDOW, inFunc)
self.ui.Bind(wx.EVT_LEAVE_WINDOW, outFunc)
self.ui.textValue.Bind(wx.EVT_ENTER_WINDOW, inFunc)
self.ui.textValue.Bind(wx.EVT_LEAVE_WINDOW, outFunc)
if valFunc:
self.ui.bindFunc(valFunc)
class ObjectPropUISpinner(ObjectPropUI):
""" UI for int value properties """
def __init__(self, parent, label, value, minValue, maxValue):
ObjectPropUI.__init__(self, parent, label)
self.ui = wx.SpinCtrl(self.uiPane, -1, "", min=minValue, max=maxValue, initial=value)
self.eventType = wx.EVT_SPIN
self.Layout()
class ObjectPropUICheck(ObjectPropUI):
def __init__(self, parent, label, value):
ObjectPropUI.__init__(self, parent, label)
self.ui = wx.CheckBox(self.uiPane, -1, "", size=(50, 30))
self.setValue(value)
self.eventType = wx.EVT_CHECKBOX
self.Layout()
class ObjectPropUIRadio(ObjectPropUI):
def __init__(self, parent, label, value, valueList):
ObjectPropUI.__init__(self, parent, label)
self.ui = wx.RadioBox(self.uiPane, -1, "", choices=valueList, majorDimension=1, style=wx.RA_SPECIFY_COLS)
self.setValue(value)
self.eventType = wx.EVT_RADIOBOX
self.Layout()
def setValue(self, value):
self.ui.SetStringSelection(value)
def getValue(self):
return self.ui.GetStringSelection()
class ObjectPropUICombo(ObjectPropUI):
def __init__(self, parent, label, value, valueList):
ObjectPropUI.__init__(self, parent, label)
self.ui = wx.Choice(self.uiPane, -1, choices=valueList)
self.setValue(value)
self.eventType = wx.EVT_CHOICE
self.Layout()
def setValue(self, value):
self.ui.SetStringSelection(value)
def getValue(self):
return self.ui.GetStringSelection()
class ObjectPropertyUI(ScrolledPanel):
def __init__(self, parent, editor):
self.editor = editor
ScrolledPanel.__init__(self, parent)
parentSizer = wx.BoxSizer(wx.VERTICAL)
parentSizer.Add(self, 1, wx.EXPAND, 0)
parent.SetSizer(parentSizer); parent.Layout()
def clearPropUI(self):
sizer = self.GetSizer()
if sizer is not None:
sizer.Remove(self.propPane)
self.propPane.Destroy()
self.SetSizer(None)
self.Layout()
self.SetupScrolling(self, scroll_y = True, rate_y = 20)
def updateProps(self, obj):
self.clearPropUI()
self.propPane = wx.Panel(self)
mainSizer = wx.BoxSizer(wx.VERTICAL)
mainSizer.Add(self.propPane, 1, wx.EXPAND, 0)
self.SetSizer(mainSizer)
self.propX = ObjectPropUIEntry(self.propPane, 'X')
self.propY = ObjectPropUIEntry(self.propPane, 'Y')
self.propZ = ObjectPropUIEntry(self.propPane, 'Z')
self.propH = ObjectPropUISlider(self.propPane, 'H', 0, 0, 360)
self.propP = ObjectPropUISlider(self.propPane, 'P', 0, 0, 360)
self.propR = ObjectPropUISlider(self.propPane, 'R', 0, 0, 360)
self.propSX = ObjectPropUIEntry(self.propPane, 'SX')
self.propSY = ObjectPropUIEntry(self.propPane, 'SY')
self.propSZ = ObjectPropUIEntry(self.propPane, 'SZ')
transformProps = [self.propX, self.propY, self.propZ, self.propH, self.propP, self.propR,
self.propSX, self.propSY, self.propSZ]
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.AddMany([self.propX, self.propY, self.propZ, self.propH, self.propP, self.propR,
self.propSX, self.propSY, self.propSZ])
for transformProp in transformProps:
transformProp.bindFunc(self.editor.objectMgr.onEnterObjectPropUI,
self.editor.objectMgr.onLeaveObjectPropUI,
self.editor.objectMgr.updateObjectTransform)
objDef = obj[OG.OBJ_DEF]
if objDef.model is not None:
propUI = ObjectPropUICombo(self.propPane, 'model', obj[OG.OBJ_MODEL], objDef.models)
sizer.Add(propUI)
propUI.bindFunc(self.editor.objectMgr.onEnterObjectPropUI,
self.editor.objectMgr.onLeaveObjectPropUI,
lambda p0=None, p1=obj: self.editor.objectMgr.updateObjectModel(p0, p1))
for key in objDef.properties.keys():
propDef = objDef.properties[key]
propType = propDef[OG.PROP_TYPE]
propDataType = propDef[OG.PROP_DATATYPE]
value = obj[OG.OBJ_PROP].get(key)
if propType == OG.PROP_UI_ENTRY:
propUI = ObjectPropUIEntry(self.propPane, key)
propUI.setValue(value)
sizer.Add(propUI)
elif propType == OG.PROP_UI_SLIDE:
if len(propDef) <= OG.PROP_RANGE:
continue
propRange = propDef[OG.PROP_RANGE]
if value is None:
continue
propUI = ObjectPropUISlider(self.propPane, key, value, propRange[OG.RANGE_MIN], propRange[OG.RANGE_MAX])
sizer.Add(propUI)
elif propType == OG.PROP_UI_SPIN:
if len(propDef) <= OG.PROP_RANGE:
continue
propRange = propDef[OG.PROP_RANGE]
if value is None:
continue
propUI = ObjectPropUISpinner(self.propPane, key, value, propRange[OG.RANGE_MIN], propRange[OG.RANGE_MAX])
sizer.Add(propUI)
elif propType == OG.PROP_UI_CHECK:
if value is None:
continue
propUI = ObjectPropUICheck(self.propPane, key, value)
sizer.Add(propUI)
elif propType == OG.PROP_UI_RADIO:
if len(propDef) <= OG.PROP_RANGE:
continue
propRange = propDef[OG.PROP_RANGE]
if value is None:
continue
if propDataType != OG.PROP_STR:
for i in range(len(propRange)):
propRange[i] = str(propRange[i])
value = str(value)
propUI = ObjectPropUIRadio(self.propPane, key, value, propRange)
sizer.Add(propUI)
elif propType == OG.PROP_UI_COMBO:
if len(propDef) <= OG.PROP_RANGE:
continue
propRange = propDef[OG.PROP_RANGE]
if value is None:
continue
if propDataType != OG.PROP_STR:
for i in range(len(propRange)):
propRange[i] = str(propRange[i])
value = str(value)
propUI = ObjectPropUICombo(self.propPane, key, value, propRange)
sizer.Add(propUI)
else:
# unspported property type
continue
propUI.bindFunc(self.editor.objectMgr.onEnterObjectPropUI,
self.editor.objectMgr.onLeaveObjectPropUI,
lambda p0=None, p1=obj, p2=key: self.editor.objectMgr.updateObjectProperty(p0, p1, p2))
self.propPane.SetSizer(sizer);
self.Layout()
self.SetupScrolling(self, scroll_y = True, rate_y = 20)

View File

@ -0,0 +1,263 @@
"""
Contains classes useful for 3D viewports.
Originally written by pro-rsoft,
Modified by gjeon.
"""
__all__ = ["Viewport", "ViewportManager", "ViewportMenu"]
from direct.showbase.DirectObject import DirectObject
from direct.directtools.DirectGrid import DirectGrid
from direct.showbase.ShowBase import WindowControls
from pandac.PandaModules import WindowProperties, OrthographicLens, Point3, BitMask32
import wx
HORIZONTAL = wx.SPLIT_HORIZONTAL
VERTICAL = wx.SPLIT_VERTICAL
CREATENEW = 99
VPLEFT = 10
VPFRONT = 11
VPTOP = 12
VPPERSPECTIVE = 13
TopCameraBitmask = BitMask32.bit(0)
FrontCameraBitmask = BitMask32.bit(1)
LeftCameraBitmask = BitMask32.bit(2)
PerspCameraBitmask = BitMask32.bit(3)
CameraBitmasks = {'persp':PerspCameraBitmask,
'left':LeftCameraBitmask,
'front':FrontCameraBitmask,
'top':TopCameraBitmask}
class ViewportManager:
"""Manages the global viewport stuff."""
viewports = []
gsg = None
@staticmethod
def initializeAll(*args, **kwargs):
"""Calls initialize() on all the viewports."""
for v in ViewportManager.viewports:
v.initialize(*args, **kwargs)
@staticmethod
def updateAll(*args, **kwargs):
"""Calls Update() on all the viewports."""
for v in ViewportManager.viewports:
v.Update(*args, **kwargs)
@staticmethod
def layoutAll(*args, **kwargs):
"""Calls Layout() on all the viewports."""
for v in ViewportManager.viewports:
v.Layout(*args, **kwargs)
class Viewport(wx.Panel, DirectObject):
"""Class representing a 3D Viewport."""
CREATENEW = CREATENEW
VPLEFT = VPLEFT
VPFRONT = VPFRONT
VPTOP = VPTOP
VPPERSPECTIVE = VPPERSPECTIVE
def __init__(self, name, *args, **kwargs):
self.name = name
DirectObject.__init__(self)
wx.Panel.__init__(self, *args, **kwargs)
ViewportManager.viewports.append(self)
self.win = None
self.camera = None
self.lens = None
self.camPos = None
self.camLookAt = None
self.initialized = False
self.grid = None
#self.Bind(wx.EVT_RIGHT_DOWN, self.onRightDown)
def initialize(self):
self.Update()
wp = WindowProperties()
wp.setOrigin(0, 0)
wp.setSize(self.ClientSize.GetWidth(), self.ClientSize.GetHeight())
assert self.GetHandle() != 0
wp.setParentWindow(self.GetHandle())
# initializing panda window
base.windowType = "onscreen"
props = WindowProperties.getDefault()
props.addProperties(wp)
self.win = base.openWindow(props = props, gsg = ViewportManager.gsg)
if self.win:
self.cam2d = base.makeCamera2d(self.win)
self.cam2d.node().setCameraMask(CameraBitmasks[self.name])
if ViewportManager.gsg == None:
ViewportManager.gsg = self.win.getGsg()
self.cam = base.camList[-1]
self.camera = render.attachNewNode(self.name)
#self.camera.setName(self.name)
#self.camera.reparentTo(render)
self.cam.reparentTo(self.camera)
self.camNode = self.cam.node()
self.camNode.setCameraMask(CameraBitmasks[self.name])
bt = base.setupMouse(self.win, True)
mw = bt.getParent()
mk = mw.getParent()
winCtrl = WindowControls(
self.win, mouseWatcher=mw,
cam=self.camera,
camNode = self.camNode,
cam2d=None,
mouseKeyboard =mk)
base.setupWindowControls(winCtrl)
self.initialized = True
if self.lens != None: self.cam.node().setLens(self.lens)
if self.camPos != None: self.camera.setPos(self.camPos)
if self.camLookAt != None: self.camera.lookAt(self.camLookAt)
self.camLens = self.camNode.getLens()
self.Bind(wx.EVT_SIZE, self.onSize)
## self.accept("wheel_down", self.zoomOut)
## self.accept("wheel_up", self.zoomIn)
## self.accept("page_down", self.zoomOut)
## self.accept("page_down-repeat", self.zoomOut)
## self.accept("page_up", self.zoomIn)
## self.accept("page_up-repeat", self.zoomIn)
#self.accept("mouse3", self.onRightDown)
def close(self):
"""Closes the viewport."""
if self.initialized:
Window.close(self)
ViewportManager.viewports.remove(self)
def onSize(self, evt):
"""Invoked when the viewport is resized."""
if self.win != None:
wp = WindowProperties()
wp.setOrigin(0, 0)
newWidth = self.ClientSize.GetWidth()
newHeight = self.ClientSize.GetHeight()
wp.setSize(newWidth, newHeight)
self.win.requestProperties(wp)
if hasattr(base, "direct") and base.direct:
for dr in base.direct.drList:
if dr.camNode == self.camNode:
dr.updateFilmSize(newWidth, newHeight)
break
def onRightDown(self, evt = None):
print "RightDown captured by wx"
"""Invoked when the viewport is right-clicked."""
menu = ViewportMenu(self)
if evt == None:
mpos = wx.GetMouseState()
mpos = self.ScreenToClient((mpos.x, mpos.y))
else:
mpos = evt.GetPosition()
self.Update()
self.PopupMenu(menu, mpos)
menu.Destroy()
def zoomOut(self):
self.camera.setY(self.camera, -MOUSE_ZOO_SPEED)
def zoomIn(self):
self.camera.setY(self.camera, MOUSE_ZOOM_SPEED)
@staticmethod
def make(parent, vpType = None):
"""Safe constructor that also takes CREATENEW, VPLEFT, VPTOP, etc."""
if vpType == None or vpType == CREATENEW:
return Viewport(parent)
if isinstance(vpType, Viewport): return vpType
if vpType == VPLEFT: return Viewport.makeLeft(parent)
if vpType == VPFRONT: return Viewport.makeFront(parent)
if vpType == VPTOP: return Viewport.makeTop(parent)
if vpType == VPPERSPECTIVE: return Viewport.makePerspective(parent)
raise TypeError, "Unknown viewport type: %s" % vpType
@staticmethod
def makeOrthographic(parent, name, campos):
v = Viewport(name, parent)
v.lens = OrthographicLens()
v.lens.setFilmSize(30)
v.camPos = campos
v.camLookAt = Point3(0, 0, 0)
v.grid = DirectGrid(parent=render)
if name == 'left':
v.grid.setHpr(0, 0, 90)
#v.grid.gridBack.findAllMatches("**/+GeomNode")[0].setName("_leftViewGridBack")
v.grid.hide(PerspCameraBitmask)
v.grid.hide(FrontCameraBitmask)
v.grid.hide(TopCameraBitmask)
elif name == 'front':
v.grid.setHpr(90, 0, 90)
#v.grid.gridBack.findAllMatches("**/+GeomNode")[0].setName("_frontViewGridBack")
v.grid.hide(PerspCameraBitmask)
v.grid.hide(LeftCameraBitmask)
v.grid.hide(TopCameraBitmask)
else:
#v.grid.gridBack.findAllMatches("**/+GeomNode")[0].setName("_topViewGridBack")
v.grid.hide(PerspCameraBitmask)
v.grid.hide(LeftCameraBitmask)
v.grid.hide(FrontCameraBitmask)
return v
@staticmethod
def makePerspective(parent):
v = Viewport('persp', parent)
v.camPos = Point3(30, 30, 30)
v.camLookAt = Point3(0, 0, 0)
v.grid = DirectGrid(parent=render)
#v.grid.gridBack.findAllMatches("**/+GeomNode")[0].setName("_perspViewGridBack")
v.grid.hide(FrontCameraBitmask)
v.grid.hide(LeftCameraBitmask)
v.grid.hide(TopCameraBitmask)
return v
@staticmethod
def makeLeft(parent): return Viewport.makeOrthographic(parent, 'left', Point3(100, 0, 0))
@staticmethod
def makeFront(parent): return Viewport.makeOrthographic(parent, 'front', Point3(0, 100, 0))
@staticmethod
def makeTop(parent): return Viewport.makeOrthographic(parent, 'top', Point3(0, 0, 100))
class ViewportMenu(wx.Menu):
"""Represents a menu that appears when right-clicking a viewport."""
def __init__(self, viewport):
wx.Menu.__init__(self)
self.viewport = viewport
self.addItem("&Refresh", self.viewport.Update)
self.addItem("&Background Color...", self.onChooseColor)
def addItem(self, name, call = None, id = None):
if id == None: id = wx.NewId()
item = wx.MenuItem(self, id, name)
self.AppendItem(item)
if call != None:
self.Bind(wx.EVT_MENU, call, item)
def onChooseColor(self, evt = None):
"""Change the background color of the viewport."""
data = wx.ColourData()
bgcolor = self.viewport.win.getClearColor()
bgcolor = bgcolor[0] * 255.0, bgcolor[1] * 255.0, bgcolor[2] * 255.0
data.SetColour(bgcolor)
dlg = wx.ColourDialog(self, data)
try:
if dlg.ShowModal() == wx.ID_OK:
data = dlg.GetColourData().GetColour()
data = data[0] / 255.0, data[1] / 255.0, data[2] / 255.0
self.viewport.win.setClearColor(*data)
finally:
dlg.Destroy()

View File

@ -0,0 +1,54 @@
from pandac.PandaModules import *
objectMgr = base.le.objectMgr
# temporary place holder for nodepath
objects = {}
objects['1252538687.73gjeon'] = objectMgr.addNewObject('Smiley', '1252538687.73gjeon', 'models/smiley.egg', None)
if objects['1252538687.73gjeon']:
objects['1252538687.73gjeon'].setPos(Point3(8.66381, 0, 7.13246))
objects['1252538687.73gjeon'].setHpr(VBase3(180, 0, 0))
objects['1252538687.73gjeon'].setScale(VBase3(1, 1, 1))
objectMgr.updateObjectProperties(objects['1252538687.73gjeon'], {'123': 1, 'Abc': 'a', 'Number': 1, 'Happy': True})
objects['1252538690.2gjeon'] = objectMgr.addNewObject('H Double Smiley', '1252538690.2gjeon', None, objects['1252538687.73gjeon'])
if objects['1252538690.2gjeon']:
objects['1252538690.2gjeon'].setPos(Point3(0, 0, 2.12046))
objects['1252538690.2gjeon'].setHpr(VBase3(0, 0, 0))
objects['1252538690.2gjeon'].setScale(VBase3(1, 1, 1))
objectMgr.updateObjectProperties(objects['1252538690.2gjeon'], {'Distance': 2.0, 'Abc': 'a'})
objects['1252539696.69gjeon'] = objectMgr.addNewObject('H Double Smiley', '1252539696.69gjeon', None, objects['1252538687.73gjeon'])
if objects['1252539696.69gjeon']:
objects['1252539696.69gjeon'].setPos(Point3(-9.53674e-006, 0, 0))
objects['1252539696.69gjeon'].setHpr(VBase3(0, 0, 0))
objects['1252539696.69gjeon'].setScale(VBase3(1, 1, 1))
objectMgr.updateObjectProperties(objects['1252539696.69gjeon'], {'Distance': 2.0, 'Abc': 'a'})
objects['1252539897.22gjeon'] = objectMgr.addNewObject('H Double Smiley', '1252539897.22gjeon', None, objects['1252538687.73gjeon'])
if objects['1252539897.22gjeon']:
objects['1252539897.22gjeon'].setPos(Point3(0.06987, 0, -2.12046))
objects['1252539897.22gjeon'].setHpr(VBase3(0, 0, 0))
objects['1252539897.22gjeon'].setScale(VBase3(1, 1, 1))
objectMgr.updateObjectProperties(objects['1252539897.22gjeon'], {'Distance': 2.0, 'Abc': 'a'})
objects['1252538689.13gjeon'] = objectMgr.addNewObject('V Double Smiley', '1252538689.13gjeon', None, objects['1252538687.73gjeon'])
if objects['1252538689.13gjeon']:
objects['1252538689.13gjeon'].setPos(Point3(6.07152, 0, -0.863791))
objects['1252538689.13gjeon'].setHpr(VBase3(0, 0, 0))
objects['1252538689.13gjeon'].setScale(VBase3(1, 1, 1))
objectMgr.updateObjectProperties(objects['1252538689.13gjeon'], {'Distance': 1.0, 'Abc': 'a'})
objects['1252539974.19gjeon'] = objectMgr.addNewObject('Smiley', '1252539974.19gjeon', 'models/frowney.egg', objects['1252538689.13gjeon'])
if objects['1252539974.19gjeon']:
objects['1252539974.19gjeon'].setPos(Point3(0.06987, 0, 3.09209))
objects['1252539974.19gjeon'].setHpr(VBase3(0, 0, 0))
objects['1252539974.19gjeon'].setScale(VBase3(1, 1, 1))
objectMgr.updateObjectProperties(objects['1252539974.19gjeon'], {'123': 1, 'Abc': 'a', 'Number': 1, 'Happy': True})
objects['1252623762.9gjeon'] = objectMgr.addNewObject('Panda', '1252623762.9gjeon', None, None)
if objects['1252623762.9gjeon']:
objects['1252623762.9gjeon'].setPos(Point3(0, 0, 0))
objects['1252623762.9gjeon'].setHpr(VBase3(172.8, 0, 0))
objects['1252623762.9gjeon'].setScale(VBase3(0.005, 0.005, 0.005))
objectMgr.updateObjectProperties(objects['1252623762.9gjeon'], {'Distance': 1.0, 'Abc': 'a'})