[WIP] Panda3D Level Editor
This commit is contained in:
parent
cb4b6986bb
commit
648730f3bb
|
|
@ -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
|
|
@ -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)
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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")
|
||||
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
@ -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')
|
||||
|
|
@ -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')
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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()
|
||||
|
||||
|
|
@ -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'})
|
||||
Loading…
Reference in New Issue