2
0

ShowBase.py 120 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127
  1. """ This module contains ShowBase, an application framework responsible
  2. for opening a graphical display, setting up input devices and creating
  3. the scene graph. """
  4. __all__ = ['ShowBase', 'WindowControls']
  5. # This module redefines the builtin import function with one
  6. # that prints out every import it does in a hierarchical form
  7. # Annoying and very noisy, but sometimes useful
  8. #import VerboseImport
  9. from panda3d.core import *
  10. from panda3d.direct import get_config_showbase, throw_new_frame, init_app_for_gui
  11. from panda3d.direct import storeAccessibilityShortcutKeys, allowAccessibilityShortcutKeys
  12. # Register the extension methods for NodePath.
  13. from direct.extensions_native import NodePath_extensions
  14. # This needs to be available early for DirectGUI imports
  15. import sys
  16. if sys.version_info >= (3, 0):
  17. import builtins
  18. else:
  19. import __builtin__ as builtins
  20. builtins.config = get_config_showbase()
  21. from direct.directnotify.DirectNotifyGlobal import directNotify, giveNotify
  22. from .MessengerGlobal import messenger
  23. from .BulletinBoardGlobal import bulletinBoard
  24. from direct.task.TaskManagerGlobal import taskMgr
  25. from .JobManagerGlobal import jobMgr
  26. from .EventManagerGlobal import eventMgr
  27. #from PythonUtil import *
  28. from direct.interval import IntervalManager
  29. from direct.showbase.BufferViewer import BufferViewer
  30. from direct.task import Task
  31. from . import Loader
  32. import time
  33. import atexit
  34. import importlib
  35. from direct.showbase import ExceptionVarDump
  36. from . import DirectObject
  37. from . import SfxPlayer
  38. if __debug__:
  39. from direct.showbase import GarbageReport
  40. from direct.directutil import DeltaProfiler
  41. from . import OnScreenDebug
  42. from . import AppRunnerGlobal
  43. def legacyRun():
  44. assert builtins.base.notify.warning("run() is deprecated, use base.run() instead")
  45. builtins.base.run()
  46. @atexit.register
  47. def exitfunc():
  48. if getattr(builtins, 'base', None) is not None:
  49. builtins.base.destroy()
  50. # Now ShowBase is a DirectObject. We need this so ShowBase can hang
  51. # hooks on messages, particularly on window-event. This doesn't
  52. # *seem* to cause anyone any problems.
  53. class ShowBase(DirectObject.DirectObject):
  54. config = get_config_showbase()
  55. notify = directNotify.newCategory("ShowBase")
  56. def __init__(self, fStartDirect = True, windowType = None):
  57. self.__dev__ = self.config.GetBool('want-dev', __debug__)
  58. builtins.__dev__ = self.__dev__
  59. logStackDump = (self.config.GetBool('log-stack-dump', False) or
  60. self.config.GetBool('client-log-stack-dump', False))
  61. uploadStackDump = self.config.GetBool('upload-stack-dump', False)
  62. if logStackDump or uploadStackDump:
  63. ExceptionVarDump.install(logStackDump, uploadStackDump)
  64. if __debug__:
  65. self.__autoGarbageLogging = self.__dev__ and self.config.GetBool('auto-garbage-logging', False)
  66. ## The directory containing the main Python file of this application.
  67. self.mainDir = ExecutionEnvironment.getEnvironmentVariable("MAIN_DIR")
  68. self.main_dir = self.mainDir
  69. ## This contains the global appRunner instance, as imported from
  70. ## AppRunnerGlobal. This will be None if we are not running in the
  71. ## runtime environment (ie. from a .p3d file).
  72. self.appRunner = AppRunnerGlobal.appRunner
  73. self.app_runner = self.appRunner
  74. #debug running multiplier
  75. self.debugRunningMultiplier = 4
  76. # [gjeon] to disable sticky keys
  77. if self.config.GetBool('disable-sticky-keys', 0):
  78. storeAccessibilityShortcutKeys()
  79. allowAccessibilityShortcutKeys(False)
  80. self.printEnvDebugInfo()
  81. vfs = VirtualFileSystem.getGlobalPtr()
  82. self.nextWindowIndex = 1
  83. self.__directStarted = False
  84. self.__deadInputs = 0
  85. # Store dconfig variables
  86. self.sfxActive = self.config.GetBool('audio-sfx-active', 1)
  87. self.musicActive = self.config.GetBool('audio-music-active', 1)
  88. self.wantFog = self.config.GetBool('want-fog', 1)
  89. self.wantRender2dp = self.config.GetBool('want-render2dp', 1)
  90. self.screenshotExtension = self.config.GetString('screenshot-extension', 'jpg')
  91. self.musicManager = None
  92. self.musicManagerIsValid = None
  93. self.sfxManagerList = []
  94. self.sfxManagerIsValidList = []
  95. self.wantStats = self.config.GetBool('want-pstats', 0)
  96. self.wantTk = False
  97. self.wantWx = False
  98. ## Fill this in with a function to invoke when the user "exits"
  99. ## the program by closing the main window.
  100. self.exitFunc = None
  101. ## Add final-exit callbacks to this list. These will be called
  102. ## when sys.exit() is called, after Panda has unloaded, and
  103. ## just before Python is about to shut down.
  104. self.finalExitCallbacks = []
  105. # Set up the TaskManager to reset the PStats clock back
  106. # whenever we resume from a pause. This callback function is
  107. # a little hacky, but we can't call it directly from within
  108. # the TaskManager because he doesn't know about PStats (and
  109. # has to run before libpanda is even loaded).
  110. taskMgr.resumeFunc = PStatClient.resumeAfterPause
  111. if self.__dev__:
  112. self.__setupProfile()
  113. # If the aspect ratio is 0 or None, it means to infer the
  114. # aspect ratio from the window size.
  115. # If you need to know the actual aspect ratio call base.getAspectRatio()
  116. self.__configAspectRatio = ConfigVariableDouble('aspect-ratio', 0).getValue()
  117. # This variable is used to see if the aspect ratio has changed when
  118. # we get a window-event.
  119. self.__oldAspectRatio = None
  120. ## This is set to the value of the window-type config variable, but may
  121. ## optionally be overridden in the Showbase constructor. Should either be
  122. ## 'onscreen' (the default), 'offscreen' or 'none'.
  123. self.windowType = windowType
  124. if self.windowType is None:
  125. self.windowType = self.config.GetString('window-type', 'onscreen')
  126. self.requireWindow = self.config.GetBool('require-window', 1)
  127. ## This is the main, or only window; see winList for a list of *all* windows.
  128. self.win = None
  129. self.frameRateMeter = None
  130. self.sceneGraphAnalyzerMeter = None
  131. self.winList = []
  132. self.winControls = []
  133. self.mainWinMinimized = 0
  134. self.mainWinForeground = 0
  135. self.pipe = None
  136. self.pipeList = []
  137. self.mouse2cam = None
  138. self.buttonThrowers = None
  139. self.mouseWatcher = None
  140. self.mouseWatcherNode = None
  141. self.pointerWatcherNodes = None
  142. self.mouseInterface = None
  143. self.drive = None
  144. self.trackball = None
  145. self.texmem = None
  146. self.showVertices = None
  147. ## This is a NodePath pointing to the Camera object set up for the 3D scene.
  148. ## This is usually a child of self.camera.
  149. self.cam = None
  150. self.cam2d = None
  151. self.cam2dp = None
  152. ## This is the NodePath that should be used to manipulate the camera. This
  153. ## is the node to which the default camera is attached.
  154. self.camera = None
  155. self.camera2d = None
  156. self.camera2dp = None
  157. ## This is a list of all cameras created with makeCamera, including base.cam.
  158. self.camList = []
  159. ## Convenience accessor for base.cam.node()
  160. self.camNode = None
  161. ## Convenience accessor for base.camNode.get_lens()
  162. self.camLens = None
  163. self.camFrustumVis = None
  164. self.direct = None
  165. ## This is used to store the wx.Application object used when want-wx is
  166. ## set or base.startWx() is called.
  167. self.wxApp = None
  168. self.wxAppCreated = False
  169. self.tkRoot = None
  170. self.tkRootCreated = False
  171. # This is used for syncing multiple PCs in a distributed cluster
  172. try:
  173. # Has the cluster sync variable been set externally?
  174. self.clusterSyncFlag = clusterSyncFlag
  175. except NameError:
  176. # Has the clusterSyncFlag been set via a config variable
  177. self.clusterSyncFlag = self.config.GetBool('cluster-sync', 0)
  178. self.hidden = NodePath('hidden')
  179. ## The global graphics engine, ie. GraphicsEngine.getGlobalPtr()
  180. self.graphicsEngine = GraphicsEngine.getGlobalPtr()
  181. self.graphics_engine = self.graphicsEngine
  182. self.setupRender()
  183. self.setupRender2d()
  184. self.setupDataGraph()
  185. if self.wantRender2dp:
  186. self.setupRender2dp()
  187. ## This is a placeholder for a CollisionTraverser. If someone
  188. ## stores a CollisionTraverser pointer here, we'll traverse it
  189. ## in the collisionLoop task.
  190. self.cTrav = 0
  191. self.shadowTrav = 0
  192. self.cTravStack = Stack()
  193. # Ditto for an AppTraverser.
  194. self.appTrav = 0
  195. # This is the DataGraph traverser, which we might as well
  196. # create now.
  197. self.dgTrav = DataGraphTraverser()
  198. # Maybe create a RecorderController to record and/or play back
  199. # the user session.
  200. self.recorder = None
  201. playbackSession = self.config.GetString('playback-session', '')
  202. recordSession = self.config.GetString('record-session', '')
  203. if playbackSession:
  204. self.recorder = RecorderController()
  205. self.recorder.beginPlayback(Filename.fromOsSpecific(playbackSession))
  206. elif recordSession:
  207. self.recorder = RecorderController()
  208. self.recorder.beginRecord(Filename.fromOsSpecific(recordSession))
  209. if self.recorder:
  210. # If we're either playing back or recording, pass the
  211. # random seed into the system so each session will have
  212. # the same random seed.
  213. import random #, whrandom
  214. seed = self.recorder.getRandomSeed()
  215. random.seed(seed)
  216. #whrandom.seed(seed & 0xff, (seed >> 8) & 0xff, (seed >> 16) & 0xff)
  217. # For some reason, wx needs to be initialized before the graphics window
  218. if sys.platform == "darwin":
  219. if self.config.GetBool("want-wx", 0):
  220. wx = importlib.import_module('wx')
  221. self.wxApp = wx.App()
  222. # Same goes for Tk, which uses a conflicting NSApplication
  223. if self.config.GetBool("want-tk", 0):
  224. Pmw = importlib.import_module('Pmw')
  225. self.tkRoot = Pmw.initialise()
  226. # Open the default rendering window.
  227. if self.windowType != 'none':
  228. props = WindowProperties.getDefault()
  229. if (self.config.GetBool('read-raw-mice', 0)):
  230. props.setRawMice(1)
  231. self.openDefaultWindow(startDirect = False, props=props)
  232. # The default is trackball mode, which is more convenient for
  233. # ad-hoc development in Python using ShowBase. Applications
  234. # can explicitly call base.useDrive() if they prefer a drive
  235. # interface.
  236. self.mouseInterface = self.trackball
  237. self.useTrackball()
  238. self.loader = Loader.Loader(self)
  239. self.graphicsEngine.setDefaultLoader(self.loader.loader)
  240. ## The global event manager, as imported from EventManagerGlobal.
  241. self.eventMgr = eventMgr
  242. ## The global messenger, as imported from MessengerGlobal.
  243. self.messenger = messenger
  244. ## The global bulletin board, as imported from BulletinBoardGlobal.
  245. self.bboard = bulletinBoard
  246. ## The global task manager, as imported from TaskManagerGlobal.
  247. self.taskMgr = taskMgr
  248. self.task_mgr = taskMgr
  249. ## The global job manager, as imported from JobManagerGlobal.
  250. self.jobMgr = jobMgr
  251. ## Particle manager
  252. self.particleMgr = None
  253. self.particleMgrEnabled = 0
  254. ## Physics manager
  255. self.physicsMgr = None
  256. self.physicsMgrEnabled = 0
  257. self.physicsMgrAngular = 0
  258. self.createStats()
  259. self.AppHasAudioFocus = 1
  260. # Get a pointer to Panda's global ClockObject, used for
  261. # synchronizing events between Python and C.
  262. globalClock = ClockObject.getGlobalClock()
  263. # Since we have already started up a TaskManager, and probably
  264. # a number of tasks; and since the TaskManager had to use the
  265. # TrueClock to tell time until this moment, make sure the
  266. # globalClock object is exactly in sync with the TrueClock.
  267. trueClock = TrueClock.getGlobalPtr()
  268. globalClock.setRealTime(trueClock.getShortTime())
  269. globalClock.tick()
  270. # Now we can make the TaskManager start using the new globalClock.
  271. taskMgr.globalClock = globalClock
  272. # client CPU affinity is determined by, in order:
  273. # - client-cpu-affinity-mask config
  274. # - pcalt-# (# is CPU number, 0-based)
  275. # - client-cpu-affinity config
  276. # - auto-single-cpu-affinity config
  277. affinityMask = self.config.GetInt('client-cpu-affinity-mask', -1)
  278. if affinityMask != -1:
  279. TrueClock.getGlobalPtr().setCpuAffinity(affinityMask)
  280. else:
  281. # this is useful on machines that perform better with each process
  282. # assigned to a single CPU
  283. autoAffinity = self.config.GetBool('auto-single-cpu-affinity', 0)
  284. affinity = None
  285. if autoAffinity and hasattr(builtins, 'clientIndex'):
  286. affinity = abs(int(builtins.clientIndex))
  287. else:
  288. affinity = self.config.GetInt('client-cpu-affinity', -1)
  289. if (affinity in (None, -1)) and autoAffinity:
  290. affinity = 0
  291. if affinity not in (None, -1):
  292. # Windows XP supports a 32-bit affinity mask
  293. TrueClock.getGlobalPtr().setCpuAffinity(1 << (affinity % 32))
  294. # Make sure we're not making more than one ShowBase.
  295. if hasattr(builtins, 'base'):
  296. raise Exception("Attempt to spawn multiple ShowBase instances!")
  297. # DO NOT ADD TO THIS LIST. We're trying to phase out the use of
  298. # built-in variables by ShowBase. Use a Global module if necessary.
  299. builtins.base = self
  300. builtins.render2d = self.render2d
  301. builtins.aspect2d = self.aspect2d
  302. builtins.pixel2d = self.pixel2d
  303. builtins.render = self.render
  304. builtins.hidden = self.hidden
  305. builtins.camera = self.camera
  306. builtins.loader = self.loader
  307. builtins.taskMgr = self.taskMgr
  308. builtins.jobMgr = self.jobMgr
  309. builtins.eventMgr = self.eventMgr
  310. builtins.messenger = self.messenger
  311. builtins.bboard = self.bboard
  312. # Config needs to be defined before ShowBase is constructed
  313. #builtins.config = self.config
  314. builtins.run = legacyRun
  315. builtins.ostream = Notify.out()
  316. builtins.directNotify = directNotify
  317. builtins.giveNotify = giveNotify
  318. builtins.globalClock = globalClock
  319. builtins.vfs = vfs
  320. builtins.cpMgr = ConfigPageManager.getGlobalPtr()
  321. builtins.cvMgr = ConfigVariableManager.getGlobalPtr()
  322. builtins.pandaSystem = PandaSystem.getGlobalPtr()
  323. builtins.wantUberdog = self.config.GetBool('want-uberdog', 1)
  324. if __debug__:
  325. builtins.deltaProfiler = DeltaProfiler.DeltaProfiler("ShowBase")
  326. self.onScreenDebug = OnScreenDebug.OnScreenDebug()
  327. builtins.onScreenDebug = self.onScreenDebug
  328. if self.wantRender2dp:
  329. builtins.render2dp = self.render2dp
  330. builtins.aspect2dp = self.aspect2dp
  331. builtins.pixel2dp = self.pixel2dp
  332. if self.__dev__:
  333. ShowBase.notify.debug('__dev__ == %s' % self.__dev__)
  334. else:
  335. ShowBase.notify.info('__dev__ == %s' % self.__dev__)
  336. self.createBaseAudioManagers()
  337. if self.__dev__ or self.config.GetBool('want-e3-hacks', False):
  338. if self.config.GetBool('track-gui-items', True):
  339. # dict of guiId to gui item, for tracking down leaks
  340. self.guiItems = {}
  341. # optionally restore the default gui sounds from 1.7.2 and earlier
  342. if ConfigVariableBool('orig-gui-sounds', False).getValue():
  343. from direct.gui import DirectGuiGlobals as DGG
  344. DGG.setDefaultClickSound(self.loader.loadSfx("audio/sfx/GUI_click.wav"))
  345. DGG.setDefaultRolloverSound(self.loader.loadSfx("audio/sfx/GUI_rollover.wav"))
  346. # Now hang a hook on the window-event from Panda. This allows
  347. # us to detect when the user resizes, minimizes, or closes the
  348. # main window.
  349. self.__prevWindowProperties = None
  350. self.accept('window-event', self.windowEvent)
  351. # Transition effects (fade, iris, etc)
  352. from . import Transitions
  353. self.transitions = Transitions.Transitions(self.loader)
  354. if self.win:
  355. # Setup the window controls - handy for multiwindow applications
  356. self.setupWindowControls()
  357. # Client sleep
  358. sleepTime = self.config.GetFloat('client-sleep', 0.0)
  359. self.clientSleep = 0.0
  360. self.setSleep(sleepTime)
  361. # Extra sleep for running 4+ clients on a single machine
  362. # adds a sleep right after the main render in igloop
  363. # tends to even out the frame rate and keeps it from going
  364. # to zero in the out of focus windows
  365. if self.config.GetBool('multi-sleep', 0):
  366. self.multiClientSleep = 1
  367. else:
  368. self.multiClientSleep = 0
  369. # Offscreen buffer viewing utility.
  370. # This needs to be allocated even if the viewer is off.
  371. if self.wantRender2dp:
  372. self.bufferViewer = BufferViewer(self.win, self.render2dp)
  373. else:
  374. self.bufferViewer = BufferViewer(self.win, self.render2d)
  375. if self.windowType != 'none':
  376. if fStartDirect: # [gjeon] if this is False let them start direct manually
  377. self.__doStartDirect()
  378. if self.config.GetBool('show-tex-mem', False):
  379. if not self.texmem or self.texmem.cleanedUp:
  380. self.toggleTexMem()
  381. taskMgr.finalInit()
  382. # Start IGLOOP
  383. self.restart()
  384. # add a collision traverser via pushCTrav and remove it via popCTrav
  385. # that way the owner of the new cTrav doesn't need to hold onto the
  386. # previous one in order to put it back
  387. def pushCTrav(self, cTrav):
  388. self.cTravStack.push(self.cTrav)
  389. self.cTrav = cTrav
  390. def popCTrav(self):
  391. self.cTrav = self.cTravStack.pop()
  392. def __setupProfile(self):
  393. """ Sets up the Python profiler, if available, according to
  394. some Panda config settings. """
  395. try:
  396. profile = importlib.import_module('profile')
  397. pstats = importlib.import_module('pstats')
  398. except ImportError:
  399. return
  400. profile.Profile.bias = float(self.config.GetString("profile-bias","0"))
  401. def f8(x):
  402. return ("%" + "8.%df" % self.config.GetInt("profile-decimals", 3)) % x
  403. pstats.f8 = f8
  404. # temp; see ToonBase.py
  405. def getExitErrorCode(self):
  406. return 0
  407. def printEnvDebugInfo(self):
  408. """Print some information about the environment that we are running
  409. in. Stuff like the model paths and other paths. Feel free to
  410. add stuff to this.
  411. """
  412. if self.config.GetBool('want-env-debug-info', 0):
  413. print("\n\nEnvironment Debug Info {")
  414. print("* model path:")
  415. print(getModelPath())
  416. #print "* dna path:"
  417. #print getDnaPath()
  418. print("}")
  419. def destroy(self):
  420. """ Call this function to destroy the ShowBase and stop all
  421. its tasks, freeing all of the Panda resources. Normally, you
  422. should not need to call it explicitly, as it is bound to the
  423. exitfunc and will be called at application exit time
  424. automatically.
  425. This function is designed to be safe to call multiple times."""
  426. for cb in self.finalExitCallbacks[:]:
  427. cb()
  428. # Remove the built-in base reference
  429. if getattr(builtins, 'base', None) is self:
  430. del builtins.base
  431. del builtins.loader
  432. del builtins.taskMgr
  433. # [gjeon] restore sticky key settings
  434. if self.config.GetBool('disable-sticky-keys', 0):
  435. allowAccessibilityShortcutKeys(True)
  436. self.ignoreAll()
  437. self.shutdown()
  438. if getattr(self, 'musicManager', None):
  439. self.musicManager.shutdown()
  440. self.musicManager = None
  441. for sfxManager in self.sfxManagerList:
  442. sfxManager.shutdown()
  443. self.sfxManagerList = []
  444. if getattr(self, 'loader', None):
  445. self.loader.destroy()
  446. self.loader = None
  447. if getattr(self, 'graphicsEngine', None):
  448. self.graphicsEngine.removeAllWindows()
  449. try:
  450. self.direct.panel.destroy()
  451. except:
  452. pass
  453. if hasattr(self, 'win'):
  454. del self.win
  455. del self.winList
  456. del self.pipe
  457. def makeDefaultPipe(self, printPipeTypes = None):
  458. """
  459. Creates the default GraphicsPipe, which will be used to make
  460. windows unless otherwise specified.
  461. """
  462. assert self.pipe == None
  463. if printPipeTypes is None:
  464. # When the user didn't specify an explicit setting, take the value
  465. # from the config variable. We could just omit the parameter, however
  466. # this way we can keep backward compatibility.
  467. printPipeTypes = ConfigVariableBool("print-pipe-types", True)
  468. selection = GraphicsPipeSelection.getGlobalPtr()
  469. if printPipeTypes:
  470. selection.printPipeTypes()
  471. self.pipe = selection.makeDefaultPipe()
  472. if not self.pipe:
  473. self.notify.error(
  474. "No graphics pipe is available!\n"
  475. "Your Config.prc file must name at least one valid panda display\n"
  476. "library via load-display or aux-display.")
  477. self.notify.info("Default graphics pipe is %s (%s)." % (
  478. self.pipe.getType().getName(), self.pipe.getInterfaceName()))
  479. self.pipeList.append(self.pipe)
  480. def makeModulePipe(self, moduleName):
  481. """
  482. Returns a GraphicsPipe from the indicated module,
  483. e.g. 'pandagl' or 'pandadx9'. Does not affect base.pipe or
  484. base.pipeList.
  485. """
  486. selection = GraphicsPipeSelection.getGlobalPtr()
  487. return selection.makeModulePipe(moduleName)
  488. def makeAllPipes(self):
  489. """
  490. Creates all GraphicsPipes that the system knows about and fill up
  491. self.pipeList with them.
  492. """
  493. selection = GraphicsPipeSelection.getGlobalPtr()
  494. selection.loadAuxModules()
  495. # First, we should make sure the default pipe exists.
  496. if self.pipe == None:
  497. self.makeDefaultPipe()
  498. # Now go through the list of known pipes, and make each one if
  499. # we don't have one already.
  500. numPipeTypes = selection.getNumPipeTypes()
  501. for i in range(numPipeTypes):
  502. pipeType = selection.getPipeType(i)
  503. # Do we already have a pipe of this type on the list?
  504. # This operation is n-squared, but presumably there won't
  505. # be more than a handful of pipe types, so who cares.
  506. already = 0
  507. for pipe in self.pipeList:
  508. if pipe.getType() == pipeType:
  509. already = 1
  510. if not already:
  511. pipe = selection.makePipe(pipeType)
  512. if pipe:
  513. self.notify.info("Got aux graphics pipe %s (%s)." % (
  514. pipe.getType().getName(), pipe.getInterfaceName()))
  515. self.pipeList.append(pipe)
  516. else:
  517. self.notify.info("Could not make graphics pipe %s." % (
  518. pipeType.getName()))
  519. def openWindow(self, props = None, fbprops = None, pipe = None, gsg = None,
  520. host = None, type = None, name = None, size = None,
  521. aspectRatio = None, makeCamera = True, keepCamera = False,
  522. scene = None, stereo = None, unexposedDraw = None,
  523. callbackWindowDict = None, requireWindow = None):
  524. """
  525. Creates a window and adds it to the list of windows that are
  526. to be updated every frame.
  527. props is the WindowProperties that describes the window.
  528. type is either 'onscreen', 'offscreen', or 'none'.
  529. If keepCamera is true, the existing base.cam is set up to
  530. render into the new window.
  531. If keepCamera is false but makeCamera is true, a new camera is
  532. set up to render into the new window.
  533. If unexposedDraw is not None, it specifies the initial value
  534. of GraphicsWindow.setUnexposedDraw().
  535. If callbackWindowDict is not None, a CallbackGraphicWindow is
  536. created instead, which allows the caller to create the actual
  537. window with its own OpenGL context, and direct Panda's
  538. rendering into that window.
  539. If requireWindow is true, it means that the function should
  540. raise an exception if the window fails to open correctly.
  541. """
  542. # Save this lambda here for convenience; we'll use it to call
  543. # down to the underlying _doOpenWindow() with all of the above
  544. # parameters.
  545. func = lambda : self._doOpenWindow(
  546. props = props, fbprops = fbprops, pipe = pipe, gsg = gsg,
  547. host = host, type = type, name = name, size = size,
  548. aspectRatio = aspectRatio, makeCamera = makeCamera,
  549. keepCamera = keepCamera, scene = scene, stereo = stereo,
  550. unexposedDraw = unexposedDraw,
  551. callbackWindowDict = callbackWindowDict)
  552. if self.win:
  553. # If we've already opened a window before, this is just a
  554. # pass-through to _doOpenWindow().
  555. win = func()
  556. self.graphicsEngine.openWindows()
  557. return win
  558. if type is None:
  559. type = self.windowType
  560. if requireWindow is None:
  561. requireWindow = self.requireWindow
  562. win = func()
  563. # Give the window a chance to truly open.
  564. self.graphicsEngine.openWindows()
  565. if win != None and not win.isValid():
  566. self.notify.info("Window did not open, removing.")
  567. self.closeWindow(win)
  568. win = None
  569. if win == None and pipe == None:
  570. # Try a little harder if the window wouldn't open.
  571. self.makeAllPipes()
  572. try:
  573. self.pipeList.remove(self.pipe)
  574. except ValueError:
  575. pass
  576. while self.win == None and self.pipeList:
  577. self.pipe = self.pipeList[0]
  578. self.notify.info("Trying pipe type %s (%s)" % (
  579. self.pipe.getType(), self.pipe.getInterfaceName()))
  580. win = func()
  581. self.graphicsEngine.openWindows()
  582. if win != None and not win.isValid():
  583. self.notify.info("Window did not open, removing.")
  584. self.closeWindow(win)
  585. win = None
  586. if win == None:
  587. self.pipeList.remove(self.pipe)
  588. if win == None:
  589. self.notify.warning("Unable to open '%s' window." % (type))
  590. if requireWindow:
  591. # Unless require-window is set to false, it is an
  592. # error not to open a window.
  593. raise Exception('Could not open window.')
  594. else:
  595. self.notify.info("Successfully opened window of type %s (%s)" % (
  596. win.getType(), win.getPipe().getInterfaceName()))
  597. return win
  598. def _doOpenWindow(self, props = None, fbprops = None, pipe = None,
  599. gsg = None, host = None, type = None, name = None,
  600. size = None, aspectRatio = None,
  601. makeCamera = True, keepCamera = False,
  602. scene = None, stereo = None, unexposedDraw = None,
  603. callbackWindowDict = None):
  604. if pipe == None:
  605. pipe = self.pipe
  606. if pipe == None:
  607. self.makeDefaultPipe()
  608. pipe = self.pipe
  609. if pipe == None:
  610. # We couldn't get a pipe.
  611. return None
  612. if isinstance(gsg, GraphicsOutput):
  613. # If the gsg is a window or buffer, it means to use the
  614. # GSG from that buffer.
  615. host = gsg
  616. gsg = gsg.getGsg()
  617. # If we are using DirectX, force a new GSG to be created,
  618. # since at the moment DirectX seems to misbehave if we do
  619. # not do this. This will cause a delay while all textures
  620. # etc. are reloaded, so we should revisit this later if we
  621. # can fix the underlying bug in our DirectX support.
  622. if pipe.getType().getName().startswith('wdx'):
  623. gsg = None
  624. if type == None:
  625. type = self.windowType
  626. if props == None:
  627. props = WindowProperties.getDefault()
  628. if fbprops == None:
  629. fbprops = FrameBufferProperties.getDefault()
  630. if size != None:
  631. # If we were given an explicit size, use it; otherwise,
  632. # the size from the properties is used.
  633. props = WindowProperties(props)
  634. props.setSize(size[0], size[1])
  635. if name == None:
  636. name = 'window%s' % (self.nextWindowIndex)
  637. self.nextWindowIndex += 1
  638. win = None
  639. flags = GraphicsPipe.BFFbPropsOptional
  640. if type == 'onscreen':
  641. flags = flags | GraphicsPipe.BFRequireWindow
  642. elif type == 'offscreen':
  643. flags = flags | GraphicsPipe.BFRefuseWindow
  644. if callbackWindowDict:
  645. flags = flags | GraphicsPipe.BFRequireCallbackWindow
  646. if host:
  647. assert host.isValid()
  648. win = self.graphicsEngine.makeOutput(pipe, name, 0, fbprops,
  649. props, flags, host.getGsg(), host)
  650. elif gsg:
  651. win = self.graphicsEngine.makeOutput(pipe, name, 0, fbprops,
  652. props, flags, gsg)
  653. else:
  654. win = self.graphicsEngine.makeOutput(pipe, name, 0, fbprops,
  655. props, flags)
  656. if win == None:
  657. # Couldn't create a window!
  658. return None
  659. if unexposedDraw is not None and hasattr(win, 'setUnexposedDraw'):
  660. win.setUnexposedDraw(unexposedDraw)
  661. if callbackWindowDict:
  662. # If we asked for (and received) a CallbackGraphicsWindow,
  663. # we now have to assign the callbacks, before we start
  664. # trying to do anything with the window.
  665. for callbackName in ['Events', 'Properties', 'Render']:
  666. func = callbackWindowDict.get(callbackName, None)
  667. if not func:
  668. continue
  669. setCallbackName = 'set%sCallback' % (callbackName)
  670. setCallback = getattr(win, setCallbackName)
  671. setCallback(PythonCallbackObject(func))
  672. # We also need to set up the mouse/keyboard objects.
  673. for inputName in callbackWindowDict.get('inputDevices', ['mouse']):
  674. win.createInputDevice(inputName)
  675. if hasattr(win, "requestProperties"):
  676. win.requestProperties(props)
  677. mainWindow = False
  678. if self.win == None:
  679. mainWindow = True
  680. self.win = win
  681. self.winList.append(win)
  682. # Set up a 3-d camera for the window by default.
  683. if keepCamera:
  684. self.makeCamera(win, scene = scene, aspectRatio = aspectRatio,
  685. stereo = stereo, useCamera = self.cam)
  686. elif makeCamera:
  687. self.makeCamera(win, scene = scene, aspectRatio = aspectRatio,
  688. stereo = stereo)
  689. messenger.send('open_window', [win, mainWindow])
  690. if mainWindow:
  691. messenger.send('open_main_window')
  692. return win
  693. def closeWindow(self, win, keepCamera = False, removeWindow = True):
  694. """
  695. Closes the indicated window and removes it from the list of
  696. windows. If it is the main window, clears the main window
  697. pointer to None.
  698. """
  699. win.setActive(False)
  700. # First, remove all of the cameras associated with display
  701. # regions on the window.
  702. numRegions = win.getNumDisplayRegions()
  703. for i in range(numRegions):
  704. dr = win.getDisplayRegion(i)
  705. # [gjeon] remove drc in base.direct.drList
  706. if self.direct is not None:
  707. for drc in self.direct.drList:
  708. if drc.cam == dr.getCamera():
  709. self.direct.drList.displayRegionList.remove(drc)
  710. break
  711. cam = NodePath(dr.getCamera())
  712. dr.setCamera(NodePath())
  713. if not cam.isEmpty() and \
  714. cam.node().getNumDisplayRegions() == 0 and \
  715. not keepCamera:
  716. # If the camera is used by no other DisplayRegions,
  717. # remove it.
  718. if self.camList.count(cam) != 0:
  719. self.camList.remove(cam)
  720. # Don't throw away self.camera; we want to
  721. # preserve it for reopening the window.
  722. if cam == self.cam:
  723. self.cam = None
  724. if cam == self.cam2d:
  725. self.cam2d = None
  726. if cam == self.cam2dp:
  727. self.cam2dp = None
  728. cam.removeNode()
  729. # [gjeon] remove winControl
  730. for winCtrl in self.winControls:
  731. if winCtrl.win == win:
  732. self.winControls.remove(winCtrl)
  733. break
  734. # Now we can actually close the window.
  735. if removeWindow:
  736. self.graphicsEngine.removeWindow(win)
  737. self.winList.remove(win)
  738. mainWindow = False
  739. if win == self.win:
  740. mainWindow = True
  741. self.win = None
  742. if self.frameRateMeter:
  743. self.frameRateMeter.clearWindow()
  744. self.frameRateMeter = None
  745. if self.sceneGraphAnalyzerMeter:
  746. self.sceneGraphAnalyzerMeter.clearWindow()
  747. self.sceneGraphAnalyzerMeter = None
  748. messenger.send('close_window', [win, mainWindow])
  749. if mainWindow:
  750. messenger.send('close_main_window')
  751. if not self.winList:
  752. # Give the window(s) a chance to actually close before we
  753. # continue.
  754. self.graphicsEngine.renderFrame()
  755. def openDefaultWindow(self, *args, **kw):
  756. # Creates the main window for the first time, without being
  757. # too particular about the kind of graphics API that is
  758. # chosen. The suggested window type from the load-display
  759. # config variable is tried first; if that fails, the first
  760. # window type that can be successfully opened at all is
  761. # accepted. Returns true on success, false otherwise.
  762. #
  763. # This is intended to be called only once, at application
  764. # startup. It is normally called automatically unless
  765. # window-type is configured to 'none'.
  766. startDirect = kw.get('startDirect', True)
  767. if 'startDirect' in kw:
  768. del kw['startDirect']
  769. self.openMainWindow(*args, **kw)
  770. if startDirect:
  771. self.__doStartDirect()
  772. return self.win != None
  773. def openMainWindow(self, *args, **kw):
  774. """
  775. Creates the initial, main window for the application, and sets
  776. up the mouse and render2d structures appropriately for it. If
  777. this method is called a second time, it will close the
  778. previous main window and open a new one, preserving the lens
  779. properties in base.camLens.
  780. The return value is true on success, or false on failure (in
  781. which case base.win may be either None, or the previous,
  782. closed window).
  783. """
  784. keepCamera = kw.get('keepCamera', False)
  785. success = 1
  786. oldWin = self.win
  787. oldLens = self.camLens
  788. oldClearColorActive = None
  789. if self.win != None:
  790. # Close the previous window.
  791. oldClearColorActive = self.win.getClearColorActive()
  792. oldClearColor = VBase4(self.win.getClearColor())
  793. oldClearDepthActive = self.win.getClearDepthActive()
  794. oldClearDepth = self.win.getClearDepth()
  795. oldClearStencilActive = self.win.getClearStencilActive()
  796. oldClearStencil = self.win.getClearStencil()
  797. self.closeWindow(self.win, keepCamera = keepCamera)
  798. # Open a new window.
  799. self.openWindow(*args, **kw)
  800. if self.win == None:
  801. self.win = oldWin
  802. self.winList.append(oldWin)
  803. success = 0
  804. if self.win != None:
  805. if isinstance(self.win, GraphicsWindow):
  806. self.setupMouse(self.win)
  807. self.makeCamera2d(self.win)
  808. if self.wantRender2dp:
  809. self.makeCamera2dp(self.win)
  810. if oldLens != None:
  811. # Restore the previous lens properties.
  812. self.camNode.setLens(oldLens)
  813. self.camLens = oldLens
  814. if oldClearColorActive != None:
  815. # Restore the previous clear properties.
  816. self.win.setClearColorActive(oldClearColorActive)
  817. self.win.setClearColor(oldClearColor)
  818. self.win.setClearDepthActive(oldClearDepthActive)
  819. self.win.setClearDepth(oldClearDepth)
  820. self.win.setClearStencilActive(oldClearStencilActive)
  821. self.win.setClearStencil(oldClearStencil)
  822. flag = self.config.GetBool('show-frame-rate-meter', False)
  823. if self.appRunner is not None and self.appRunner.allowPythonDev:
  824. # In an allow_python_dev p3d application, we always
  825. # start up with the frame rate meter enabled, to
  826. # provide a visual reminder that this flag has been
  827. # set.
  828. flag = True
  829. self.setFrameRateMeter(flag)
  830. flag = self.config.GetBool('show-scene-graph-analyzer-meter', False)
  831. self.setSceneGraphAnalyzerMeter(flag)
  832. return success
  833. def setSleep(self, amount):
  834. """
  835. Sets up a task that calls python 'sleep' every frame. This is a simple
  836. way to reduce the CPU usage (and frame rate) of a panda program.
  837. """
  838. if (self.clientSleep == amount):
  839. return
  840. self.clientSleep = amount
  841. if (amount == 0.0):
  842. self.taskMgr.remove('clientSleep')
  843. else:
  844. # Spawn it after igloop (at the end of each frame)
  845. self.taskMgr.remove('clientSleep')
  846. self.taskMgr.add(self.__sleepCycleTask, 'clientSleep', sort = 55)
  847. def __sleepCycleTask(self, task):
  848. Thread.sleep(self.clientSleep)
  849. #time.sleep(self.clientSleep)
  850. return Task.cont
  851. def setFrameRateMeter(self, flag):
  852. """
  853. Turns on or off (according to flag) a standard frame rate
  854. meter in the upper-right corner of the main window.
  855. """
  856. if flag:
  857. if not self.frameRateMeter:
  858. self.frameRateMeter = FrameRateMeter('frameRateMeter')
  859. self.frameRateMeter.setupWindow(self.win)
  860. else:
  861. if self.frameRateMeter:
  862. self.frameRateMeter.clearWindow()
  863. self.frameRateMeter = None
  864. def setSceneGraphAnalyzerMeter(self, flag):
  865. """
  866. Turns on or off (according to flag) a standard frame rate
  867. meter in the upper-right corner of the main window.
  868. """
  869. if flag:
  870. if not self.sceneGraphAnalyzerMeter:
  871. self.sceneGraphAnalyzerMeter = SceneGraphAnalyzerMeter('sceneGraphAnalyzerMeter', self.render.node())
  872. self.sceneGraphAnalyzerMeter.setupWindow(self.win)
  873. else:
  874. if self.sceneGraphAnalyzerMeter:
  875. self.sceneGraphAnalyzerMeter.clearWindow()
  876. self.sceneGraphAnalyzerMeter = None
  877. # [gjeon] now you can add more winControls after creating a showbase instance
  878. def setupWindowControls(self, winCtrl=None):
  879. if winCtrl is None:
  880. winCtrl = WindowControls(
  881. self.win, mouseWatcher=self.mouseWatcher,
  882. cam=self.camera, camNode = self.camNode, cam2d=self.camera2d,
  883. mouseKeyboard = self.dataRoot.find("**/*"))
  884. self.winControls.append(winCtrl)
  885. def setupRender(self):
  886. """
  887. Creates the render scene graph, the primary scene graph for
  888. rendering 3-d geometry.
  889. """
  890. ## This is the root of the 3-D scene graph.
  891. self.render = NodePath('render')
  892. self.render.setAttrib(RescaleNormalAttrib.makeDefault())
  893. self.render.setTwoSided(0)
  894. self.backfaceCullingEnabled = 1
  895. self.textureEnabled = 1
  896. self.wireframeEnabled = 0
  897. def setupRender2d(self):
  898. """
  899. Creates the render2d scene graph, the primary scene graph for
  900. 2-d objects and gui elements that are superimposed over the
  901. 3-d geometry in the window.
  902. """
  903. ## This is the root of the 2-D scene graph.
  904. self.render2d = NodePath('render2d')
  905. # Set up some overrides to turn off certain properties which
  906. # we probably won't need for 2-d objects.
  907. # It's probably important to turn off the depth test, since
  908. # many 2-d objects will be drawn over each other without
  909. # regard to depth position.
  910. # We used to avoid clearing the depth buffer before drawing
  911. # render2d, but nowadays we clear it anyway, since we
  912. # occasionally want to put 3-d geometry under render2d, and
  913. # it's simplest (and seems to be easier on graphics drivers)
  914. # if the 2-d scene has been cleared first.
  915. self.render2d.setDepthTest(0)
  916. self.render2d.setDepthWrite(0)
  917. self.render2d.setMaterialOff(1)
  918. self.render2d.setTwoSided(1)
  919. ## The normal 2-d DisplayRegion has an aspect ratio that
  920. ## matches the window, but its coordinate system is square.
  921. ## This means anything we parent to render2d gets stretched.
  922. ## For things where that makes a difference, we set up
  923. ## aspect2d, which scales things back to the right aspect
  924. ## ratio along the X axis (Z is still from -1 to 1)
  925. self.aspect2d = self.render2d.attachNewNode(PGTop("aspect2d"))
  926. aspectRatio = self.getAspectRatio()
  927. self.aspect2d.setScale(1.0 / aspectRatio, 1.0, 1.0)
  928. self.a2dBackground = self.aspect2d.attachNewNode("a2dBackground")
  929. ## The Z position of the top border of the aspect2d screen.
  930. self.a2dTop = 1.0
  931. ## The Z position of the bottom border of the aspect2d screen.
  932. self.a2dBottom = -1.0
  933. ## The X position of the left border of the aspect2d screen.
  934. self.a2dLeft = -aspectRatio
  935. ## The X position of the right border of the aspect2d screen.
  936. self.a2dRight = aspectRatio
  937. self.a2dTopCenter = self.aspect2d.attachNewNode("a2dTopCenter")
  938. self.a2dTopCenterNs = self.aspect2d.attachNewNode("a2dTopCenterNS")
  939. self.a2dBottomCenter = self.aspect2d.attachNewNode("a2dBottomCenter")
  940. self.a2dBottomCenterNs = self.aspect2d.attachNewNode("a2dBottomCenterNS")
  941. self.a2dLeftCenter = self.aspect2d.attachNewNode("a2dLeftCenter")
  942. self.a2dLeftCenterNs = self.aspect2d.attachNewNode("a2dLeftCenterNS")
  943. self.a2dRightCenter = self.aspect2d.attachNewNode("a2dRightCenter")
  944. self.a2dRightCenterNs = self.aspect2d.attachNewNode("a2dRightCenterNS")
  945. self.a2dTopLeft = self.aspect2d.attachNewNode("a2dTopLeft")
  946. self.a2dTopLeftNs = self.aspect2d.attachNewNode("a2dTopLeftNS")
  947. self.a2dTopRight = self.aspect2d.attachNewNode("a2dTopRight")
  948. self.a2dTopRightNs = self.aspect2d.attachNewNode("a2dTopRightNS")
  949. self.a2dBottomLeft = self.aspect2d.attachNewNode("a2dBottomLeft")
  950. self.a2dBottomLeftNs = self.aspect2d.attachNewNode("a2dBottomLeftNS")
  951. self.a2dBottomRight = self.aspect2d.attachNewNode("a2dBottomRight")
  952. self.a2dBottomRightNs = self.aspect2d.attachNewNode("a2dBottomRightNS")
  953. # Put the nodes in their places
  954. self.a2dTopCenter.setPos(0, 0, self.a2dTop)
  955. self.a2dTopCenterNs.setPos(0, 0, self.a2dTop)
  956. self.a2dBottomCenter.setPos(0, 0, self.a2dBottom)
  957. self.a2dBottomCenterNs.setPos(0, 0, self.a2dBottom)
  958. self.a2dLeftCenter.setPos(self.a2dLeft, 0, 0)
  959. self.a2dLeftCenterNs.setPos(self.a2dLeft, 0, 0)
  960. self.a2dRightCenter.setPos(self.a2dRight, 0, 0)
  961. self.a2dRightCenterNs.setPos(self.a2dRight, 0, 0)
  962. self.a2dTopLeft.setPos(self.a2dLeft, 0, self.a2dTop)
  963. self.a2dTopLeftNs.setPos(self.a2dLeft, 0, self.a2dTop)
  964. self.a2dTopRight.setPos(self.a2dRight, 0, self.a2dTop)
  965. self.a2dTopRightNs.setPos(self.a2dRight, 0, self.a2dTop)
  966. self.a2dBottomLeft.setPos(self.a2dLeft, 0, self.a2dBottom)
  967. self.a2dBottomLeftNs.setPos(self.a2dLeft, 0, self.a2dBottom)
  968. self.a2dBottomRight.setPos(self.a2dRight, 0, self.a2dBottom)
  969. self.a2dBottomRightNs.setPos(self.a2dRight, 0, self.a2dBottom)
  970. ## This special root, pixel2d, uses units in pixels that are relative
  971. ## to the window. The upperleft corner of the window is (0, 0),
  972. ## the lowerleft corner is (xsize, -ysize), in this coordinate system.
  973. self.pixel2d = self.render2d.attachNewNode(PGTop("pixel2d"))
  974. self.pixel2d.setPos(-1, 0, 1)
  975. xsize, ysize = self.getSize()
  976. if xsize > 0 and ysize > 0:
  977. self.pixel2d.setScale(2.0 / xsize, 1.0, 2.0 / ysize)
  978. def setupRender2dp(self):
  979. """
  980. Creates a render2d scene graph, the secondary scene graph for
  981. 2-d objects and gui elements that are superimposed over the
  982. 2-d and 3-d geometry in the window.
  983. """
  984. self.render2dp = NodePath('render2dp')
  985. # Set up some overrides to turn off certain properties which
  986. # we probably won't need for 2-d objects.
  987. # It's probably important to turn off the depth test, since
  988. # many 2-d objects will be drawn over each other without
  989. # regard to depth position.
  990. dt = DepthTestAttrib.make(DepthTestAttrib.MNone)
  991. dw = DepthWriteAttrib.make(DepthWriteAttrib.MOff)
  992. self.render2dp.setDepthTest(0)
  993. self.render2dp.setDepthWrite(0)
  994. self.render2dp.setMaterialOff(1)
  995. self.render2dp.setTwoSided(1)
  996. ## The normal 2-d DisplayRegion has an aspect ratio that
  997. ## matches the window, but its coordinate system is square.
  998. ## This means anything we parent to render2dp gets stretched.
  999. ## For things where that makes a difference, we set up
  1000. ## aspect2dp, which scales things back to the right aspect
  1001. ## ratio along the X axis (Z is still from -1 to 1)
  1002. self.aspect2dp = self.render2dp.attachNewNode(PGTop("aspect2dp"))
  1003. self.aspect2dp.node().setStartSort(16384)
  1004. aspectRatio = self.getAspectRatio()
  1005. self.aspect2dp.setScale(1.0 / aspectRatio, 1.0, 1.0)
  1006. ## The Z position of the top border of the aspect2dp screen.
  1007. self.a2dpTop = 1.0
  1008. ## The Z position of the bottom border of the aspect2dp screen.
  1009. self.a2dpBottom = -1.0
  1010. ## The X position of the left border of the aspect2dp screen.
  1011. self.a2dpLeft = -aspectRatio
  1012. ## The X position of the right border of the aspect2dp screen.
  1013. self.a2dpRight = aspectRatio
  1014. self.a2dpTopCenter = self.aspect2dp.attachNewNode("a2dpTopCenter")
  1015. self.a2dpBottomCenter = self.aspect2dp.attachNewNode("a2dpBottomCenter")
  1016. self.a2dpLeftCenter = self.aspect2dp.attachNewNode("a2dpLeftCenter")
  1017. self.a2dpRightCenter = self.aspect2dp.attachNewNode("a2dpRightCenter")
  1018. self.a2dpTopLeft = self.aspect2dp.attachNewNode("a2dpTopLeft")
  1019. self.a2dpTopRight = self.aspect2dp.attachNewNode("a2dpTopRight")
  1020. self.a2dpBottomLeft = self.aspect2dp.attachNewNode("a2dpBottomLeft")
  1021. self.a2dpBottomRight = self.aspect2dp.attachNewNode("a2dpBottomRight")
  1022. # Put the nodes in their places
  1023. self.a2dpTopCenter.setPos(0, 0, self.a2dpTop)
  1024. self.a2dpBottomCenter.setPos(0, 0, self.a2dpBottom)
  1025. self.a2dpLeftCenter.setPos(self.a2dpLeft, 0, 0)
  1026. self.a2dpRightCenter.setPos(self.a2dpRight, 0, 0)
  1027. self.a2dpTopLeft.setPos(self.a2dpLeft, 0, self.a2dpTop)
  1028. self.a2dpTopRight.setPos(self.a2dpRight, 0, self.a2dpTop)
  1029. self.a2dpBottomLeft.setPos(self.a2dpLeft, 0, self.a2dpBottom)
  1030. self.a2dpBottomRight.setPos(self.a2dpRight, 0, self.a2dpBottom)
  1031. ## This special root, pixel2d, uses units in pixels that are relative
  1032. ## to the window. The upperleft corner of the window is (0, 0),
  1033. ## the lowerleft corner is (xsize, -ysize), in this coordinate system.
  1034. self.pixel2dp = self.render2dp.attachNewNode(PGTop("pixel2dp"))
  1035. self.pixel2dp.node().setStartSort(16384)
  1036. self.pixel2dp.setPos(-1, 0, 1)
  1037. xsize, ysize = self.getSize()
  1038. if xsize > 0 and ysize > 0:
  1039. self.pixel2dp.setScale(2.0 / xsize, 1.0, 2.0 / ysize)
  1040. def setAspectRatio(self, aspectRatio):
  1041. """ Sets the global aspect ratio of the main window. Set it
  1042. to None to restore automatic scaling. """
  1043. self.__configAspectRatio = aspectRatio
  1044. self.adjustWindowAspectRatio(self.getAspectRatio())
  1045. def getAspectRatio(self, win = None):
  1046. # Returns the actual aspect ratio of the indicated (or main
  1047. # window), or the default aspect ratio if there is not yet a
  1048. # main window.
  1049. # If the config it set, we return that
  1050. if self.__configAspectRatio:
  1051. return self.__configAspectRatio
  1052. aspectRatio = 1
  1053. if win == None:
  1054. win = self.win
  1055. if win != None and win.getSideBySideStereo() and \
  1056. win.hasSize() and win.getSbsLeftYSize() != 0:
  1057. aspectRatio = float(win.getSbsLeftXSize()) / float(win.getSbsLeftYSize())
  1058. else:
  1059. if win == None or not hasattr(win, "getRequestedProperties"):
  1060. props = WindowProperties.getDefault()
  1061. else:
  1062. props = win.getRequestedProperties()
  1063. if not props.hasSize():
  1064. props = WindowProperties.getDefault()
  1065. if props.hasSize() and props.getYSize() != 0:
  1066. aspectRatio = float(props.getXSize()) / float(props.getYSize())
  1067. if aspectRatio == 0:
  1068. return 1
  1069. return aspectRatio
  1070. def getSize(self, win = None):
  1071. # Returns the actual size of the indicated (or main
  1072. # window), or the default size if there is not yet a
  1073. # main window.
  1074. if win == None:
  1075. win = self.win
  1076. if win != None and win.hasSize():
  1077. return win.getXSize(), win.getYSize()
  1078. else:
  1079. if win == None or not hasattr(win, "getRequestedProperties"):
  1080. props = WindowProperties.getDefault()
  1081. else:
  1082. props = win.getRequestedProperties()
  1083. if not props.hasSize():
  1084. props = WindowProperties.getDefault()
  1085. return props.getXSize(), props.getYSize()
  1086. def makeCamera(self, win, sort = 0, scene = None,
  1087. displayRegion = (0, 1, 0, 1), stereo = None,
  1088. aspectRatio = None, clearDepth = 0, clearColor = None,
  1089. lens = None, camName = 'cam', mask = None,
  1090. useCamera = None):
  1091. """
  1092. Makes a new 3-d camera associated with the indicated window,
  1093. and creates a display region in the indicated subrectangle.
  1094. If stereo is True, then a stereo camera is created, with a
  1095. pair of DisplayRegions. If stereo is False, then a standard
  1096. camera is created. If stereo is None or omitted, a stereo
  1097. camera is created if the window says it can render in stereo.
  1098. If useCamera is not None, it is a NodePath to be used as the
  1099. camera to apply to the window, rather than creating a new
  1100. camera.
  1101. """
  1102. # self.camera is the parent node of all cameras: a node that
  1103. # we can move around to move all cameras as a group.
  1104. if self.camera == None:
  1105. # We make it a ModelNode with the PTLocal flag, so that
  1106. # a wayward flatten operations won't attempt to mangle the
  1107. # camera.
  1108. self.camera = self.render.attachNewNode(ModelNode('camera'))
  1109. self.camera.node().setPreserveTransform(ModelNode.PTLocal)
  1110. builtins.camera = self.camera
  1111. self.mouse2cam.node().setNode(self.camera.node())
  1112. if useCamera:
  1113. # Use the existing camera node.
  1114. cam = useCamera
  1115. camNode = useCamera.node()
  1116. assert(isinstance(camNode, Camera))
  1117. lens = camNode.getLens()
  1118. cam.reparentTo(self.camera)
  1119. else:
  1120. # Make a new Camera node.
  1121. camNode = Camera(camName)
  1122. if lens == None:
  1123. lens = PerspectiveLens()
  1124. if aspectRatio == None:
  1125. aspectRatio = self.getAspectRatio(win)
  1126. lens.setAspectRatio(aspectRatio)
  1127. cam = self.camera.attachNewNode(camNode)
  1128. if lens != None:
  1129. camNode.setLens(lens)
  1130. if scene != None:
  1131. camNode.setScene(scene)
  1132. if mask != None:
  1133. if (isinstance(mask, int)):
  1134. mask = BitMask32(mask)
  1135. camNode.setCameraMask(mask)
  1136. if self.cam == None:
  1137. self.cam = cam
  1138. self.camNode = camNode
  1139. self.camLens = lens
  1140. self.camList.append(cam)
  1141. # Now, make a DisplayRegion for the camera.
  1142. if stereo is not None:
  1143. if stereo:
  1144. dr = win.makeStereoDisplayRegion(*displayRegion)
  1145. else:
  1146. dr = win.makeMonoDisplayRegion(*displayRegion)
  1147. else:
  1148. dr = win.makeDisplayRegion(*displayRegion)
  1149. dr.setSort(sort)
  1150. # By default, we do not clear 3-d display regions (the entire
  1151. # window will be cleared, which is normally sufficient). But
  1152. # we will if clearDepth is specified.
  1153. if clearDepth:
  1154. dr.setClearDepthActive(1)
  1155. if clearColor:
  1156. dr.setClearColorActive(1)
  1157. dr.setClearColor(clearColor)
  1158. dr.setCamera(cam)
  1159. return cam
  1160. def makeCamera2d(self, win, sort = 10,
  1161. displayRegion = (0, 1, 0, 1), coords = (-1, 1, -1, 1),
  1162. lens = None, cameraName = None):
  1163. """
  1164. Makes a new camera2d associated with the indicated window, and
  1165. assigns it to render the indicated subrectangle of render2d.
  1166. """
  1167. dr = win.makeMonoDisplayRegion(*displayRegion)
  1168. dr.setSort(sort)
  1169. # Enable clearing of the depth buffer on this new display
  1170. # region (see the comment in setupRender2d, above).
  1171. dr.setClearDepthActive(1)
  1172. # Make any texture reloads on the gui come up immediately.
  1173. dr.setIncompleteRender(False)
  1174. left, right, bottom, top = coords
  1175. # Now make a new Camera node.
  1176. if (cameraName):
  1177. cam2dNode = Camera('cam2d_' + cameraName)
  1178. else:
  1179. cam2dNode = Camera('cam2d')
  1180. if lens == None:
  1181. lens = OrthographicLens()
  1182. lens.setFilmSize(right - left, top - bottom)
  1183. lens.setFilmOffset((right + left) * 0.5, (top + bottom) * 0.5)
  1184. lens.setNearFar(-1000, 1000)
  1185. cam2dNode.setLens(lens)
  1186. # self.camera2d is the analog of self.camera, although it's
  1187. # not as clear how useful it is.
  1188. if self.camera2d == None:
  1189. self.camera2d = self.render2d.attachNewNode('camera2d')
  1190. camera2d = self.camera2d.attachNewNode(cam2dNode)
  1191. dr.setCamera(camera2d)
  1192. if self.cam2d == None:
  1193. self.cam2d = camera2d
  1194. return camera2d
  1195. def makeCamera2dp(self, win, sort = 20,
  1196. displayRegion = (0, 1, 0, 1), coords = (-1, 1, -1, 1),
  1197. lens = None, cameraName = None):
  1198. """
  1199. Makes a new camera2dp associated with the indicated window, and
  1200. assigns it to render the indicated subrectangle of render2dp.
  1201. """
  1202. dr = win.makeMonoDisplayRegion(*displayRegion)
  1203. dr.setSort(sort)
  1204. # Unlike render2d, we don't clear the depth buffer for
  1205. # render2dp. Caveat emptor.
  1206. if hasattr(dr, 'setIncompleteRender'):
  1207. dr.setIncompleteRender(False)
  1208. left, right, bottom, top = coords
  1209. # Now make a new Camera node.
  1210. if (cameraName):
  1211. cam2dNode = Camera('cam2dp_' + cameraName)
  1212. else:
  1213. cam2dNode = Camera('cam2dp')
  1214. if lens == None:
  1215. lens = OrthographicLens()
  1216. lens.setFilmSize(right - left, top - bottom)
  1217. lens.setFilmOffset((right + left) * 0.5, (top + bottom) * 0.5)
  1218. lens.setNearFar(-1000, 1000)
  1219. cam2dNode.setLens(lens)
  1220. # self.camera2d is the analog of self.camera, although it's
  1221. # not as clear how useful it is.
  1222. if self.camera2dp == None:
  1223. self.camera2dp = self.render2dp.attachNewNode('camera2dp')
  1224. camera2dp = self.camera2dp.attachNewNode(cam2dNode)
  1225. dr.setCamera(camera2dp)
  1226. if self.cam2dp == None:
  1227. self.cam2dp = camera2dp
  1228. return camera2dp
  1229. def setupDataGraph(self):
  1230. """
  1231. Creates the data graph and populates it with the basic input
  1232. devices.
  1233. """
  1234. self.dataRoot = NodePath('dataRoot')
  1235. # Cache the node so we do not ask for it every frame
  1236. self.dataRootNode = self.dataRoot.node()
  1237. # Now we have the main trackball & drive interfaces.
  1238. # useTrackball() and useDrive() switch these in and out; only
  1239. # one is in use at a given time.
  1240. self.trackball = NodePath(Trackball('trackball'))
  1241. self.drive = NodePath(DriveInterface('drive'))
  1242. self.mouse2cam = NodePath(Transform2SG('mouse2cam'))
  1243. # [gjeon] now you can create multiple mouse watchers to support multiple windows
  1244. def setupMouse(self, win, fMultiWin=False):
  1245. """
  1246. Creates the structures necessary to monitor the mouse input,
  1247. using the indicated window. If the mouse has already been set
  1248. up for a different window, those structures are deleted first.
  1249. The return value is the ButtonThrower NodePath created for
  1250. this window.
  1251. If fMultiWin is true, then the previous mouse structures are
  1252. not deleted; instead, multiple windows are allowed to monitor
  1253. the mouse input. However, in this case, the trackball
  1254. controls are not set up, and must be set up by hand if
  1255. desired.
  1256. """
  1257. if not fMultiWin and self.buttonThrowers != None:
  1258. for bt in self.buttonThrowers:
  1259. mw = bt.getParent()
  1260. mk = mw.getParent()
  1261. bt.removeNode()
  1262. mw.removeNode()
  1263. mk.removeNode()
  1264. bts, pws = self.setupMouseCB(win)
  1265. if fMultiWin:
  1266. return bts[0]
  1267. self.buttonThrowers = bts[:]
  1268. self.pointerWatcherNodes = pws[:]
  1269. self.mouseWatcher = self.buttonThrowers[0].getParent()
  1270. self.mouseWatcherNode = self.mouseWatcher.node()
  1271. if self.mouseInterface:
  1272. self.mouseInterface.reparentTo(self.mouseWatcher)
  1273. if self.recorder:
  1274. # If we have a recorder, the mouseWatcher belongs under a
  1275. # special MouseRecorder node, which may intercept the
  1276. # mouse activity.
  1277. mw = self.buttonThrowers[0].getParent()
  1278. mouseRecorder = MouseRecorder('mouse')
  1279. self.recorder.addRecorder(
  1280. 'mouse', mouseRecorder.upcastToRecorderBase())
  1281. np = mw.getParent().attachNewNode(mouseRecorder)
  1282. mw.reparentTo(np)
  1283. mw = self.buttonThrowers[0].getParent()
  1284. ## A special ButtonThrower to generate keyboard events and
  1285. ## include the time from the OS. This is separate only to
  1286. ## support legacy code that did not expect a time parameter; it
  1287. ## will eventually be folded into the normal ButtonThrower,
  1288. ## above.
  1289. self.timeButtonThrower = mw.attachNewNode(ButtonThrower('timeButtons'))
  1290. self.timeButtonThrower.node().setPrefix('time-')
  1291. self.timeButtonThrower.node().setTimeFlag(1)
  1292. # Tell the gui system about our new mouse watcher.
  1293. self.aspect2d.node().setMouseWatcher(mw.node())
  1294. self.pixel2d.node().setMouseWatcher(mw.node())
  1295. if self.wantRender2dp:
  1296. self.aspect2dp.node().setMouseWatcher(mw.node())
  1297. self.pixel2dp.node().setMouseWatcher(mw.node())
  1298. mw.node().addRegion(PGMouseWatcherBackground())
  1299. return self.buttonThrowers[0]
  1300. # [gjeon] this function is seperated from setupMouse to allow multiple mouse watchers
  1301. def setupMouseCB(self, win):
  1302. # For each mouse/keyboard device, we create
  1303. # - MouseAndKeyboard
  1304. # - MouseWatcher
  1305. # - ButtonThrower
  1306. # The ButtonThrowers are stored in a list, self.buttonThrowers.
  1307. # Given a ButtonThrower, one can access the MouseWatcher and
  1308. # MouseAndKeyboard using getParent.
  1309. #
  1310. # The MouseAndKeyboard generates mouse events and mouse
  1311. # button/keyboard events; the MouseWatcher passes them through
  1312. # unchanged when the mouse is not over a 2-d button, and passes
  1313. # nothing through when the mouse *is* over a 2-d button. Therefore,
  1314. # objects that don't want to get events when the mouse is over a
  1315. # button, like the driveInterface, should be parented to
  1316. # MouseWatcher, while objects that want events in all cases, like the
  1317. # chat interface, should be parented to the MouseAndKeyboard.
  1318. buttonThrowers = []
  1319. pointerWatcherNodes = []
  1320. for i in range(win.getNumInputDevices()):
  1321. name = win.getInputDeviceName(i)
  1322. mk = self.dataRoot.attachNewNode(MouseAndKeyboard(win, i, name))
  1323. mw = mk.attachNewNode(MouseWatcher("watcher%s" % (i)))
  1324. if win.getSideBySideStereo():
  1325. # If the window has side-by-side stereo enabled, then
  1326. # we should constrain the MouseWatcher to the window's
  1327. # DisplayRegion. This will enable the MouseWatcher to
  1328. # track the left and right halves of the screen
  1329. # individually.
  1330. mw.node().setDisplayRegion(win.getOverlayDisplayRegion())
  1331. mb = mw.node().getModifierButtons()
  1332. mb.addButton(KeyboardButton.shift())
  1333. mb.addButton(KeyboardButton.control())
  1334. mb.addButton(KeyboardButton.alt())
  1335. mb.addButton(KeyboardButton.meta())
  1336. mw.node().setModifierButtons(mb)
  1337. bt = mw.attachNewNode(ButtonThrower("buttons%s" % (i)))
  1338. if (i != 0):
  1339. bt.node().setPrefix('mousedev%s-' % (i))
  1340. mods = ModifierButtons()
  1341. mods.addButton(KeyboardButton.shift())
  1342. mods.addButton(KeyboardButton.control())
  1343. mods.addButton(KeyboardButton.alt())
  1344. mods.addButton(KeyboardButton.meta())
  1345. bt.node().setModifierButtons(mods)
  1346. buttonThrowers.append(bt)
  1347. if (win.hasPointer(i)):
  1348. pointerWatcherNodes.append(mw.node())
  1349. return buttonThrowers, pointerWatcherNodes
  1350. def enableSoftwareMousePointer(self):
  1351. """
  1352. Creates some geometry and parents it to render2d to show
  1353. the currently-known mouse position. Useful if the mouse
  1354. pointer is invisible for some reason.
  1355. """
  1356. mouseViz = render2d.attachNewNode('mouseViz')
  1357. lilsmiley = loader.loadModel('lilsmiley')
  1358. lilsmiley.reparentTo(mouseViz)
  1359. aspectRatio = self.getAspectRatio()
  1360. # Scale the smiley face to 32x32 pixels.
  1361. height = self.win.getSbsLeftYSize()
  1362. lilsmiley.setScale(
  1363. 32.0 / height / aspectRatio,
  1364. 1.0, 32.0 / height)
  1365. self.mouseWatcherNode.setGeometry(mouseViz.node())
  1366. def getAlt(self):
  1367. return self.mouseWatcherNode.getModifierButtons().isDown(
  1368. KeyboardButton.alt())
  1369. def getShift(self):
  1370. return self.mouseWatcherNode.getModifierButtons().isDown(
  1371. KeyboardButton.shift())
  1372. def getControl(self):
  1373. return self.mouseWatcherNode.getModifierButtons().isDown(
  1374. KeyboardButton.control())
  1375. def getMeta(self):
  1376. return self.mouseWatcherNode.getModifierButtons().isDown(
  1377. KeyboardButton.meta())
  1378. def addAngularIntegrator(self):
  1379. if not self.physicsMgrAngular:
  1380. physics = importlib.import_module('panda3d.physics')
  1381. self.physicsMgrAngular = 1
  1382. integrator = physics.AngularEulerIntegrator()
  1383. self.physicsMgr.attachAngularIntegrator(integrator)
  1384. def enableParticles(self):
  1385. if not self.particleMgrEnabled:
  1386. # Use importlib to prevent this import from being picked up
  1387. # by modulefinder when packaging an application.
  1388. if not self.particleMgr:
  1389. PMG = importlib.import_module('direct.particles.ParticleManagerGlobal')
  1390. self.particleMgr = PMG.particleMgr
  1391. self.particleMgr.setFrameStepping(1)
  1392. if not self.physicsMgr:
  1393. PMG = importlib.import_module('direct.showbase.PhysicsManagerGlobal')
  1394. physics = importlib.import_module('panda3d.physics')
  1395. self.physicsMgr = PMG.physicsMgr
  1396. integrator = physics.LinearEulerIntegrator()
  1397. self.physicsMgr.attachLinearIntegrator(integrator)
  1398. self.particleMgrEnabled = 1
  1399. self.physicsMgrEnabled = 1
  1400. self.taskMgr.remove('manager-update')
  1401. self.taskMgr.add(self.updateManagers, 'manager-update')
  1402. def disableParticles(self):
  1403. if self.particleMgrEnabled:
  1404. self.particleMgrEnabled = 0
  1405. self.physicsMgrEnabled = 0
  1406. self.taskMgr.remove('manager-update')
  1407. def toggleParticles(self):
  1408. if self.particleMgrEnabled == 0:
  1409. self.enableParticles()
  1410. else:
  1411. self.disableParticles()
  1412. def isParticleMgrEnabled(self):
  1413. return self.particleMgrEnabled
  1414. def isPhysicsMgrEnabled(self):
  1415. return self.physicsMgrEnabled
  1416. def updateManagers(self, state):
  1417. dt = globalClock.getDt()
  1418. if (self.particleMgrEnabled == 1):
  1419. self.particleMgr.doParticles(dt)
  1420. if (self.physicsMgrEnabled == 1):
  1421. self.physicsMgr.doPhysics(dt)
  1422. return Task.cont
  1423. def createStats(self, hostname=None, port=None):
  1424. # You can specify pstats-host in your Config.prc or use ~pstats/~aipstats
  1425. # The default is localhost
  1426. if not self.wantStats:
  1427. return False
  1428. if PStatClient.isConnected():
  1429. PStatClient.disconnect()
  1430. # these default values match the C++ default values
  1431. if hostname is None:
  1432. hostname = ''
  1433. if port is None:
  1434. port = -1
  1435. PStatClient.connect(hostname, port)
  1436. return PStatClient.isConnected()
  1437. def addSfxManager(self, extraSfxManager):
  1438. # keep a list of sfx manager objects to apply settings to,
  1439. # since there may be others in addition to the one we create here
  1440. self.sfxManagerList.append(extraSfxManager)
  1441. newSfxManagerIsValid = (extraSfxManager!=None) and extraSfxManager.isValid()
  1442. self.sfxManagerIsValidList.append(newSfxManagerIsValid)
  1443. if newSfxManagerIsValid:
  1444. extraSfxManager.setActive(self.sfxActive)
  1445. def createBaseAudioManagers(self):
  1446. self.sfxPlayer = SfxPlayer.SfxPlayer()
  1447. sfxManager = AudioManager.createAudioManager()
  1448. self.addSfxManager(sfxManager)
  1449. self.musicManager = AudioManager.createAudioManager()
  1450. self.musicManagerIsValid=self.musicManager!=None \
  1451. and self.musicManager.isValid()
  1452. if self.musicManagerIsValid:
  1453. # ensure only 1 midi song is playing at a time:
  1454. self.musicManager.setConcurrentSoundLimit(1)
  1455. self.musicManager.setActive(self.musicActive)
  1456. # enableMusic/enableSoundEffects are meant to be called in response
  1457. # to a user request so sfxActive/musicActive represent how things
  1458. # *should* be, regardless of App/OS/HW state
  1459. def enableMusic(self, bEnableMusic):
  1460. # don't setActive(1) if no audiofocus
  1461. if self.AppHasAudioFocus and self.musicManagerIsValid:
  1462. self.musicManager.setActive(bEnableMusic)
  1463. self.musicActive = bEnableMusic
  1464. if bEnableMusic:
  1465. # This is useful when we want to play different music
  1466. # from what the manager has queued
  1467. messenger.send("MusicEnabled")
  1468. self.notify.debug("Enabling music")
  1469. else:
  1470. self.notify.debug("Disabling music")
  1471. def SetAllSfxEnables(self, bEnabled):
  1472. for i in range(len(self.sfxManagerList)):
  1473. if (self.sfxManagerIsValidList[i]):
  1474. self.sfxManagerList[i].setActive(bEnabled)
  1475. def enableSoundEffects(self, bEnableSoundEffects):
  1476. # don't setActive(1) if no audiofocus
  1477. if self.AppHasAudioFocus or (bEnableSoundEffects==0):
  1478. self.SetAllSfxEnables(bEnableSoundEffects)
  1479. self.sfxActive=bEnableSoundEffects
  1480. if bEnableSoundEffects:
  1481. self.notify.debug("Enabling sound effects")
  1482. else:
  1483. self.notify.debug("Disabling sound effects")
  1484. # enable/disableAllAudio allow a programmable global override-off
  1485. # for current audio settings. they're meant to be called when app
  1486. # loses audio focus (switched out), so we can turn off sound without
  1487. # affecting internal sfxActive/musicActive sound settings, so things
  1488. # come back ok when the app is switched back to
  1489. def disableAllAudio(self):
  1490. self.AppHasAudioFocus = 0
  1491. self.SetAllSfxEnables(0)
  1492. if self.musicManagerIsValid:
  1493. self.musicManager.setActive(0)
  1494. self.notify.debug("Disabling audio")
  1495. def enableAllAudio(self):
  1496. self.AppHasAudioFocus = 1
  1497. self.SetAllSfxEnables(self.sfxActive)
  1498. if self.musicManagerIsValid:
  1499. self.musicManager.setActive(self.musicActive)
  1500. self.notify.debug("Enabling audio")
  1501. # This function should only be in the loader but is here for
  1502. # backwards compatibility. Please do not add code here, add
  1503. # it to the loader.
  1504. def loadSfx(self, name):
  1505. assert self.notify.warning("base.loadSfx is deprecated, use base.loader.loadSfx instead.")
  1506. return self.loader.loadSfx(name)
  1507. # This function should only be in the loader but is here for
  1508. # backwards compatibility. Please do not add code here, add
  1509. # it to the loader.
  1510. def loadMusic(self, name):
  1511. assert self.notify.warning("base.loadMusic is deprecated, use base.loader.loadMusic instead.")
  1512. return self.loader.loadMusic(name)
  1513. def playSfx(
  1514. self, sfx, looping = 0, interrupt = 1, volume = None,
  1515. time = 0.0, node = None, listener = None, cutoff = None):
  1516. # This goes through a special player for potential localization
  1517. return self.sfxPlayer.playSfx(sfx, looping, interrupt, volume, time, node, listener, cutoff)
  1518. def playMusic(self, music, looping = 0, interrupt = 1, volume = None, time = 0.0):
  1519. if music:
  1520. if volume != None:
  1521. music.setVolume(volume)
  1522. # if interrupt was set to 0, start over even if it's
  1523. # already playing
  1524. if interrupt or (music.status() != AudioSound.PLAYING):
  1525. music.setTime(time)
  1526. music.setLoop(looping)
  1527. music.play()
  1528. def __resetPrevTransform(self, state):
  1529. # Clear out the previous velocity deltas now, after we have
  1530. # rendered (the previous frame). We do this after the render,
  1531. # so that we have a chance to draw a representation of spheres
  1532. # along with their velocities. At the beginning of the frame
  1533. # really means after the command prompt, which allows the user
  1534. # to interactively query these deltas meaningfully.
  1535. PandaNode.resetAllPrevTransform()
  1536. return Task.cont
  1537. def __dataLoop(self, state):
  1538. # traverse the data graph. This reads all the control
  1539. # inputs (from the mouse and keyboard, for instance) and also
  1540. # directly acts upon them (for instance, to move the avatar).
  1541. self.dgTrav.traverse(self.dataRootNode)
  1542. return Task.cont
  1543. def __ivalLoop(self, state):
  1544. # Execute all intervals in the global ivalMgr.
  1545. IntervalManager.ivalMgr.step()
  1546. return Task.cont
  1547. def initShadowTrav(self):
  1548. if not self.shadowTrav:
  1549. # set up the shadow collision traverser
  1550. self.shadowTrav = CollisionTraverser("base.shadowTrav")
  1551. self.shadowTrav.setRespectPrevTransform(False)
  1552. def __shadowCollisionLoop(self, state):
  1553. # run the collision traversal if we have a
  1554. # CollisionTraverser set.
  1555. if self.shadowTrav:
  1556. self.shadowTrav.traverse(self.render)
  1557. return Task.cont
  1558. def __collisionLoop(self, state):
  1559. # run the collision traversal if we have a
  1560. # CollisionTraverser set.
  1561. if self.cTrav:
  1562. self.cTrav.traverse(self.render)
  1563. if self.appTrav:
  1564. self.appTrav.traverse(self.render)
  1565. if self.shadowTrav:
  1566. self.shadowTrav.traverse(self.render)
  1567. messenger.send("collisionLoopFinished")
  1568. return Task.cont
  1569. def __audioLoop(self, state):
  1570. if (self.musicManager != None):
  1571. self.musicManager.update()
  1572. for x in self.sfxManagerList:
  1573. x.update()
  1574. return Task.cont
  1575. def __garbageCollectStates(self, state):
  1576. """ This task is started only when we have
  1577. garbage-collect-states set in the Config.prc file, in which
  1578. case we're responsible for taking out Panda's garbage from
  1579. time to time. This is not to be confused with Python's
  1580. garbage collection. """
  1581. TransformState.garbageCollect()
  1582. RenderState.garbageCollect()
  1583. return Task.cont
  1584. def __igLoop(self, state):
  1585. if __debug__:
  1586. # We render the watch variables for the onScreenDebug as soon
  1587. # as we reasonably can before the renderFrame().
  1588. self.onScreenDebug.render()
  1589. if self.recorder:
  1590. self.recorder.recordFrame()
  1591. # Finally, render the frame.
  1592. self.graphicsEngine.renderFrame()
  1593. if self.clusterSyncFlag:
  1594. self.graphicsEngine.syncFrame()
  1595. if self.multiClientSleep:
  1596. time.sleep(0)
  1597. if __debug__:
  1598. # We clear the text buffer for the onScreenDebug as soon
  1599. # as we reasonably can after the renderFrame().
  1600. self.onScreenDebug.clear()
  1601. if self.recorder:
  1602. self.recorder.playFrame()
  1603. if self.mainWinMinimized:
  1604. # If the main window is minimized, slow down the app a bit
  1605. # by sleeping here in igLoop so we don't use all available
  1606. # CPU needlessly.
  1607. # Note: this isn't quite right if multiple windows are
  1608. # open. We should base this on whether *all* windows are
  1609. # minimized, not just the main window. But it will do for
  1610. # now until someone complains.
  1611. time.sleep(0.1)
  1612. # Lerp stuff needs this event, and it must be generated in
  1613. # C++, not in Python.
  1614. throw_new_frame()
  1615. return Task.cont
  1616. def __igLoopSync(self, state):
  1617. if __debug__:
  1618. # We render the watch variables for the onScreenDebug as soon
  1619. # as we reasonably can before the renderFrame().
  1620. self.onScreenDebug.render()
  1621. if self.recorder:
  1622. self.recorder.recordFrame()
  1623. self.cluster.collectData()
  1624. # Finally, render the frame.
  1625. self.graphicsEngine.renderFrame()
  1626. if self.clusterSyncFlag:
  1627. self.graphicsEngine.syncFrame()
  1628. if self.multiClientSleep:
  1629. time.sleep(0)
  1630. if __debug__:
  1631. # We clear the text buffer for the onScreenDebug as soon
  1632. # as we reasonably can after the renderFrame().
  1633. self.onScreenDebug.clear()
  1634. if self.recorder:
  1635. self.recorder.playFrame()
  1636. if self.mainWinMinimized:
  1637. # If the main window is minimized, slow down the app a bit
  1638. # by sleeping here in igLoop so we don't use all available
  1639. # CPU needlessly.
  1640. # Note: this isn't quite right if multiple windows are
  1641. # open. We should base this on whether *all* windows are
  1642. # minimized, not just the main window. But it will do for
  1643. # now until someone complains.
  1644. time.sleep(0.1)
  1645. self.graphicsEngine.readyFlip()
  1646. self.cluster.waitForFlipCommand()
  1647. self.graphicsEngine.flipFrame()
  1648. # Lerp stuff needs this event, and it must be generated in
  1649. # C++, not in Python.
  1650. throw_new_frame()
  1651. return Task.cont
  1652. def restart(self, clusterSync=False, cluster=None):
  1653. self.shutdown()
  1654. # __resetPrevTransform goes at the very beginning of the frame.
  1655. self.taskMgr.add(
  1656. self.__resetPrevTransform, 'resetPrevTransform', sort = -51)
  1657. # give the dataLoop task a reasonably "early" sort,
  1658. # so that it will get run before most tasks
  1659. self.taskMgr.add(self.__dataLoop, 'dataLoop', sort = -50)
  1660. self.__deadInputs = 0
  1661. # spawn the ivalLoop with a later sort, so that it will
  1662. # run after most tasks, but before igLoop.
  1663. self.taskMgr.add(self.__ivalLoop, 'ivalLoop', sort = 20)
  1664. # make the collisionLoop task run before igLoop,
  1665. # but leave enough room for the app to insert tasks
  1666. # between collisionLoop and igLoop
  1667. self.taskMgr.add(self.__collisionLoop, 'collisionLoop', sort = 30)
  1668. if ConfigVariableBool('garbage-collect-states').getValue():
  1669. self.taskMgr.add(self.__garbageCollectStates, 'garbageCollectStates', sort = 46)
  1670. # give the igLoop task a reasonably "late" sort,
  1671. # so that it will get run after most tasks
  1672. self.cluster = cluster
  1673. if (not clusterSync or (cluster == None)):
  1674. self.taskMgr.add(self.__igLoop, 'igLoop', sort = 50)
  1675. else:
  1676. self.taskMgr.add(self.__igLoopSync, 'igLoop', sort = 50)
  1677. # the audioLoop updates the positions of 3D sounds.
  1678. # as such, it needs to run after the cull traversal in the igLoop.
  1679. self.taskMgr.add(self.__audioLoop, 'audioLoop', sort = 60)
  1680. self.eventMgr.restart()
  1681. def shutdown(self):
  1682. self.taskMgr.remove('audioLoop')
  1683. self.taskMgr.remove('igLoop')
  1684. self.taskMgr.remove('shadowCollisionLoop')
  1685. self.taskMgr.remove('collisionLoop')
  1686. self.taskMgr.remove('dataLoop')
  1687. self.taskMgr.remove('resetPrevTransform')
  1688. self.taskMgr.remove('ivalLoop')
  1689. self.taskMgr.remove('garbageCollectStates')
  1690. self.eventMgr.shutdown()
  1691. def getBackgroundColor(self, win = None):
  1692. """
  1693. Returns the current window background color. This assumes
  1694. the window is set up to clear the color each frame (this is
  1695. the normal setting).
  1696. """
  1697. if win == None:
  1698. win = self.win
  1699. return VBase4(win.getClearColor())
  1700. def setBackgroundColor(self, r = None, g = None, b = None, a = 0.0, win = None):
  1701. """
  1702. Sets the window background color to the indicated value.
  1703. This assumes the window is set up to clear the color each
  1704. frame (this is the normal setting).
  1705. The color may be either a VBase3 or a VBase4, or a 3-component
  1706. tuple, or the individual r, g, b parameters.
  1707. """
  1708. if g != None:
  1709. color = VBase4(r, g, b, a)
  1710. else:
  1711. arg = r
  1712. if isinstance(arg, VBase4):
  1713. color = arg
  1714. else:
  1715. color = VBase4(arg[0], arg[1], arg[2], a)
  1716. if win == None:
  1717. win = self.win
  1718. if win:
  1719. win.setClearColor(color)
  1720. def toggleBackface(self):
  1721. if self.backfaceCullingEnabled:
  1722. self.backfaceCullingOff()
  1723. else:
  1724. self.backfaceCullingOn()
  1725. def backfaceCullingOn(self):
  1726. if not self.backfaceCullingEnabled:
  1727. self.render.setTwoSided(0)
  1728. self.backfaceCullingEnabled = 1
  1729. def backfaceCullingOff(self):
  1730. if self.backfaceCullingEnabled:
  1731. self.render.setTwoSided(1)
  1732. self.backfaceCullingEnabled = 0
  1733. def toggleTexture(self):
  1734. if self.textureEnabled:
  1735. self.textureOff()
  1736. else:
  1737. self.textureOn()
  1738. def textureOn(self):
  1739. self.render.clearTexture()
  1740. self.textureEnabled = 1
  1741. def textureOff(self):
  1742. self.render.setTextureOff(100)
  1743. self.textureEnabled = 0
  1744. def toggleWireframe(self):
  1745. if self.wireframeEnabled:
  1746. self.wireframeOff()
  1747. else:
  1748. self.wireframeOn()
  1749. def wireframeOn(self):
  1750. self.render.setRenderModeWireframe(100)
  1751. self.render.setTwoSided(1)
  1752. self.wireframeEnabled = 1
  1753. def wireframeOff(self):
  1754. self.render.clearRenderMode()
  1755. render.setTwoSided(not self.backfaceCullingEnabled)
  1756. self.wireframeEnabled = 0
  1757. def disableMouse(self):
  1758. """
  1759. Temporarily disable the mouse control of the camera, either
  1760. via the drive interface or the trackball, whichever is
  1761. currently in use.
  1762. """
  1763. # We don't reparent the drive interface or the trackball;
  1764. # whichever one was there before will remain in the data graph
  1765. # and active. This way they won't lose button events while
  1766. # the mouse is disabled. However, we do move the mouse2cam
  1767. # object out of there, so we won't be updating the camera any
  1768. # more.
  1769. if self.mouse2cam:
  1770. self.mouse2cam.detachNode()
  1771. def enableMouse(self):
  1772. """
  1773. Reverse the effect of a previous call to disableMouse().
  1774. useDrive() also implicitly enables the mouse.
  1775. """
  1776. if self.mouse2cam:
  1777. self.mouse2cam.reparentTo(self.mouseInterface)
  1778. def silenceInput(self):
  1779. """
  1780. This is a heavy-handed way of temporarily turning off
  1781. all inputs. Bring them back with reviveInput().
  1782. """
  1783. if not self.__deadInputs:
  1784. self.__deadInputs = taskMgr.remove('dataLoop')
  1785. def reviveInput(self):
  1786. """
  1787. Restores inputs after a previous call to silenceInput.
  1788. """
  1789. if self.__deadInputs:
  1790. self.eventMgr.doEvents()
  1791. self.dgTrav.traverse(self.dataRootNode)
  1792. self.eventMgr.eventQueue.clear()
  1793. self.taskMgr.add(self.__dataLoop, 'dataLoop', sort = -50)
  1794. self.__deadInputs = 0
  1795. def setMouseOnNode(self, newNode):
  1796. if self.mouse2cam:
  1797. self.mouse2cam.node().setNode(newNode)
  1798. def changeMouseInterface(self, changeTo):
  1799. """
  1800. Switch mouse action
  1801. """
  1802. # Get rid of the prior interface:
  1803. self.mouseInterface.detachNode()
  1804. # Update the mouseInterface to point to the drive
  1805. self.mouseInterface = changeTo
  1806. self.mouseInterfaceNode = self.mouseInterface.node()
  1807. # Hookup the drive to the camera.
  1808. if self.mouseWatcher:
  1809. self.mouseInterface.reparentTo(self.mouseWatcher)
  1810. if self.mouse2cam:
  1811. self.mouse2cam.reparentTo(self.mouseInterface)
  1812. def useDrive(self):
  1813. """
  1814. Switch mouse action to drive mode
  1815. """
  1816. if self.drive:
  1817. self.changeMouseInterface(self.drive)
  1818. # Set the height to a good eyeheight
  1819. self.mouseInterfaceNode.reset()
  1820. self.mouseInterfaceNode.setZ(4.0)
  1821. def useTrackball(self):
  1822. """
  1823. Switch mouse action to trackball mode
  1824. """
  1825. if self.trackball:
  1826. self.changeMouseInterface(self.trackball)
  1827. def toggleTexMem(self):
  1828. """ Toggles a handy texture memory watcher. See TexMemWatcher
  1829. for more information. """
  1830. if self.texmem and not self.texmem.cleanedUp:
  1831. self.texmem.cleanup()
  1832. self.texmem = None
  1833. return
  1834. # Use importlib to prevent this import from being picked up
  1835. # by modulefinder when packaging an application.
  1836. TMW = importlib.import_module('direct.showutil.TexMemWatcher')
  1837. self.texmem = TMW.TexMemWatcher()
  1838. def toggleShowVertices(self):
  1839. """ Toggles a mode that visualizes vertex density per screen
  1840. area. """
  1841. if self.showVertices:
  1842. # Clean up the old mode.
  1843. self.showVertices.node().setActive(0)
  1844. dr = self.showVertices.node().getDisplayRegion(0)
  1845. self.win.removeDisplayRegion(dr)
  1846. self.showVertices.removeNode()
  1847. self.showVertices = None
  1848. return
  1849. dr = self.win.makeDisplayRegion()
  1850. dr.setSort(1000)
  1851. cam = Camera('showVertices')
  1852. cam.setLens(self.camLens)
  1853. # Set up a funny state to render only vertices.
  1854. override = 100000
  1855. t = NodePath('t')
  1856. t.setColor(1, 0, 1, 0.02, override)
  1857. t.setColorScale(1, 1, 1, 1, override)
  1858. t.setAttrib(ColorBlendAttrib.make(ColorBlendAttrib.MAdd, ColorBlendAttrib.OIncomingAlpha, ColorBlendAttrib.OOneMinusIncomingAlpha), override)
  1859. t.setAttrib(RenderModeAttrib.make(RenderModeAttrib.MPoint, 10), override)
  1860. t.setTwoSided(True, override)
  1861. t.setBin('fixed', 0, override)
  1862. t.setDepthTest(False, override)
  1863. t.setDepthWrite(False, override)
  1864. t.setLightOff(override)
  1865. t.setShaderOff(override)
  1866. t.setFogOff(override)
  1867. t.setAttrib(AntialiasAttrib.make(AntialiasAttrib.MNone), override)
  1868. t.setAttrib(RescaleNormalAttrib.make(RescaleNormalAttrib.MNone), override)
  1869. t.setTextureOff(override)
  1870. # Make the spots round, so there's less static in the display.
  1871. # This forces software point generation on many drivers, so
  1872. # it's not on by default.
  1873. if self.config.GetBool('round-show-vertices', False):
  1874. spot = PNMImage(256, 256, 1)
  1875. spot.renderSpot((1, 1, 1, 1), (0, 0, 0, 0), 0.8, 1)
  1876. tex = Texture('spot')
  1877. tex.load(spot)
  1878. tex.setFormat(tex.FAlpha)
  1879. t.setTexture(tex, override)
  1880. t.setAttrib(TexGenAttrib.make(TextureStage.getDefault(), TexGenAttrib.MPointSprite), override)
  1881. cam.setInitialState(t.getState())
  1882. cam.setCameraMask(~PandaNode.getOverallBit())
  1883. self.showVertices = self.cam.attachNewNode(cam)
  1884. dr.setCamera(self.showVertices)
  1885. def oobe(self, cam = None):
  1886. """
  1887. Enable a special "out-of-body experience" mouse-interface
  1888. mode. This can be used when a "god" camera is needed; it
  1889. moves the camera node out from under its normal node and sets
  1890. the world up in trackball state. Button events are still sent
  1891. to the normal mouse action node (e.g. the DriveInterface), and
  1892. mouse events, if needed, may be sent to the normal node by
  1893. holding down the Control key.
  1894. This is different than useTrackball(), which simply changes
  1895. the existing mouse action to a trackball interface. In fact,
  1896. OOBE mode doesn't care whether useDrive() or useTrackball() is
  1897. in effect; it just temporarily layers a new trackball
  1898. interface on top of whatever the basic interface is. You can
  1899. even switch between useDrive() and useTrackball() while OOBE
  1900. mode is in effect.
  1901. This is a toggle; the second time this function is called, it
  1902. disables the mode.
  1903. """
  1904. if cam is None:
  1905. cam = self.cam
  1906. # If oobeMode was never set, set it to false and create the
  1907. # structures we need to implement OOBE.
  1908. if not hasattr(self, 'oobeMode'):
  1909. self.oobeMode = 0
  1910. self.oobeCamera = self.hidden.attachNewNode('oobeCamera')
  1911. self.oobeCameraTrackball = self.oobeCamera.attachNewNode('oobeCameraTrackball')
  1912. self.oobeLens = PerspectiveLens()
  1913. self.oobeLens.setAspectRatio(self.getAspectRatio())
  1914. self.oobeLens.setNearFar(0.1, 10000.0)
  1915. self.oobeLens.setMinFov(40)
  1916. self.oobeTrackball = NodePath(Trackball('oobeTrackball'))
  1917. self.oobe2cam = self.oobeTrackball.attachNewNode(Transform2SG('oobe2cam'))
  1918. self.oobe2cam.node().setNode(self.oobeCameraTrackball.node())
  1919. self.oobeVis = loader.loadModel('models/misc/camera', okMissing = True)
  1920. if not self.oobeVis:
  1921. # Sometimes we have default-model-extension set to
  1922. # egg, but the file might be a bam file.
  1923. self.oobeVis = loader.loadModel('models/misc/camera.bam', okMissing = True)
  1924. if not self.oobeVis:
  1925. self.oobeVis = NodePath('oobeVis')
  1926. self.oobeVis.node().setFinal(1)
  1927. self.oobeVis.setLightOff(1)
  1928. self.oobeCullFrustum = None
  1929. self.accept('oobe-down', self.__oobeButton, extraArgs = [''])
  1930. self.accept('oobe-repeat', self.__oobeButton, extraArgs = ['-repeat'])
  1931. self.accept('oobe-up', self.__oobeButton, extraArgs = ['-up'])
  1932. if self.oobeMode:
  1933. # Disable OOBE mode.
  1934. if self.oobeCullFrustum != None:
  1935. # First, disable OOBE cull mode.
  1936. self.oobeCull(cam = cam)
  1937. if self.oobeVis:
  1938. self.oobeVis.reparentTo(self.hidden)
  1939. # Restore the mouse interface node, and remove the oobe
  1940. # trackball from the data path.
  1941. self.mouseInterfaceNode.clearButton(KeyboardButton.shift())
  1942. self.oobeTrackball.detachNode()
  1943. bt = self.buttonThrowers[0].node()
  1944. bt.setSpecificFlag(1)
  1945. bt.setButtonDownEvent('')
  1946. bt.setButtonRepeatEvent('')
  1947. bt.setButtonUpEvent('')
  1948. cam.reparentTo(self.camera)
  1949. #if cam == self.cam:
  1950. # self.camNode.setLens(self.camLens)
  1951. self.oobeCamera.reparentTo(self.hidden)
  1952. self.oobeMode = 0
  1953. bboard.post('oobeEnabled', False)
  1954. else:
  1955. bboard.post('oobeEnabled', True)
  1956. try:
  1957. cameraParent = localAvatar
  1958. except:
  1959. # Make oobeCamera be a sibling of wherever camera is now.
  1960. cameraParent = self.camera.getParent()
  1961. self.oobeCamera.reparentTo(cameraParent)
  1962. self.oobeCamera.clearMat()
  1963. # Make the regular MouseInterface node respond only when
  1964. # the shift button is pressed. And the oobe node will
  1965. # respond only when shift is *not* pressed.
  1966. self.mouseInterfaceNode.requireButton(KeyboardButton.shift(), True)
  1967. self.oobeTrackball.node().requireButton(KeyboardButton.shift(), False)
  1968. self.oobeTrackball.reparentTo(self.mouseWatcher)
  1969. # Set our initial OOB position to be just behind the camera.
  1970. mat = Mat4.translateMat(0, -10, 3) * self.camera.getMat(cameraParent)
  1971. mat.invertInPlace()
  1972. self.oobeTrackball.node().setMat(mat)
  1973. cam.reparentTo(self.oobeCameraTrackball)
  1974. # Temporarily disable button events by routing them
  1975. # through the oobe filters.
  1976. bt = self.buttonThrowers[0].node()
  1977. bt.setSpecificFlag(0)
  1978. bt.setButtonDownEvent('oobe-down')
  1979. bt.setButtonRepeatEvent('oobe-repeat')
  1980. bt.setButtonUpEvent('oobe-up')
  1981. # Don't change the camera lens--keep it with the original lens.
  1982. #if cam == self.cam:
  1983. # self.camNode.setLens(self.oobeLens)
  1984. if self.oobeVis:
  1985. self.oobeVis.reparentTo(self.camera)
  1986. self.oobeMode = 1
  1987. def __oobeButton(self, suffix, button):
  1988. if button.startswith('mouse'):
  1989. # Eat mouse buttons.
  1990. return
  1991. # Transmit other buttons.
  1992. messenger.send(button + suffix)
  1993. def oobeCull(self, cam = None):
  1994. """
  1995. While in OOBE mode (see above), cull the viewing frustum as if
  1996. it were still attached to our original camera. This allows us
  1997. to visualize the effectiveness of our bounding volumes.
  1998. """
  1999. if cam is None:
  2000. cam = self.cam
  2001. # First, make sure OOBE mode is enabled.
  2002. if not getattr(self, 'oobeMode', False):
  2003. self.oobe(cam = cam)
  2004. if self.oobeCullFrustum == None:
  2005. # Enable OOBE culling.
  2006. pnode = LensNode('oobeCull')
  2007. pnode.setLens(self.camLens)
  2008. pnode.showFrustum()
  2009. self.oobeCullFrustum = self.camera.attachNewNode(pnode)
  2010. # Tell the camera to cull from here instead of its own
  2011. # origin.
  2012. for c in self.camList:
  2013. c.node().setCullCenter(self.oobeCullFrustum)
  2014. if cam.node().isOfType(Camera):
  2015. cam.node().setCullCenter(self.oobeCullFrustum)
  2016. for c in cam.findAllMatches('**/+Camera'):
  2017. c.node().setCullCenter(self.oobeCullFrustum)
  2018. else:
  2019. # Disable OOBE culling.
  2020. for c in self.camList:
  2021. c.node().setCullCenter(NodePath())
  2022. if cam.node().isOfType(Camera):
  2023. cam.node().setCullCenter(self.oobeCullFrustum)
  2024. for c in cam.findAllMatches('**/+Camera'):
  2025. c.node().setCullCenter(NodePath())
  2026. self.oobeCullFrustum.removeNode()
  2027. self.oobeCullFrustum = None
  2028. def showCameraFrustum(self):
  2029. # Create a visible representation of the frustum.
  2030. self.removeCameraFrustum()
  2031. geom = self.camLens.makeGeometry()
  2032. if geom != None:
  2033. gn = GeomNode('frustum')
  2034. gn.addGeom(geom)
  2035. self.camFrustumVis = self.camera.attachNewNode(gn)
  2036. def removeCameraFrustum(self):
  2037. if self.camFrustumVis:
  2038. self.camFrustumVis.removeNode()
  2039. def screenshot(self, namePrefix = 'screenshot',
  2040. defaultFilename = 1, source = None,
  2041. imageComment=""):
  2042. """ Captures a screenshot from the main window or from the
  2043. specified window or Texture and writes it to a filename in the
  2044. current directory (or to a specified directory).
  2045. If defaultFilename is True, the filename is synthesized by
  2046. appending namePrefix to a default filename suffix (including
  2047. the filename extension) specified in the Config variable
  2048. screenshot-filename. Otherwise, if defaultFilename is False,
  2049. the entire namePrefix is taken to be the filename to write,
  2050. and this string should include a suitable filename extension
  2051. that will be used to determine the type of image file to
  2052. write.
  2053. Normally, the source is a GraphicsWindow, GraphicsBuffer or
  2054. DisplayRegion. If a Texture is supplied instead, it must have
  2055. a ram image (that is, if it was generated by
  2056. makeTextureBuffer() or makeCubeMap(), the parameter toRam
  2057. should have been set true). If it is a cube map texture as
  2058. generated by makeCubeMap(), namePrefix should contain the hash
  2059. mark ('#') character.
  2060. The return value is the filename if successful, or None if
  2061. there is a problem.
  2062. """
  2063. if source == None:
  2064. source = self.win
  2065. if defaultFilename:
  2066. filename = GraphicsOutput.makeScreenshotFilename(namePrefix)
  2067. else:
  2068. filename = Filename(namePrefix)
  2069. if isinstance(source, Texture):
  2070. if source.getZSize() > 1:
  2071. saved = source.write(filename, 0, 0, 1, 0)
  2072. else:
  2073. saved = source.write(filename)
  2074. else:
  2075. saved = source.saveScreenshot(filename, imageComment)
  2076. if saved:
  2077. # Announce to anybody that a screenshot has been taken
  2078. messenger.send('screenshot', [filename])
  2079. return filename
  2080. return None
  2081. def saveCubeMap(self, namePrefix = 'cube_map_#.png',
  2082. defaultFilename = 0, source = None,
  2083. camera = None, size = 128,
  2084. cameraMask = PandaNode.getAllCameraMask(),
  2085. sourceLens = None):
  2086. """
  2087. Similar to screenshot(), this sets up a temporary cube map
  2088. Texture which it uses to take a series of six snapshots of the
  2089. current scene, one in each of the six cube map directions.
  2090. This requires rendering a new frame.
  2091. Unlike screenshot(), source may only be a GraphicsWindow,
  2092. GraphicsBuffer, or DisplayRegion; it may not be a Texture.
  2093. camera should be the node to which the cubemap cameras will be
  2094. parented. The default is the camera associated with source,
  2095. if source is a DisplayRegion, or base.camera otherwise.
  2096. The return value is the filename if successful, or None if
  2097. there is a problem.
  2098. """
  2099. if source == None:
  2100. source = self.win
  2101. if camera == None:
  2102. if hasattr(source, "getCamera"):
  2103. camera = source.getCamera()
  2104. if camera == None:
  2105. camera = self.camera
  2106. if sourceLens == None:
  2107. sourceLens = self.camLens
  2108. if hasattr(source, "getWindow"):
  2109. source = source.getWindow()
  2110. rig = NodePath(namePrefix)
  2111. buffer = source.makeCubeMap(namePrefix, size, rig, cameraMask, 1)
  2112. if buffer == None:
  2113. raise Exception("Could not make cube map.")
  2114. # Set the near and far planes from the default lens.
  2115. lens = rig.find('**/+Camera').node().getLens()
  2116. lens.setNearFar(sourceLens.getNear(), sourceLens.getFar())
  2117. # Now render a frame to fill up the texture.
  2118. rig.reparentTo(camera)
  2119. self.graphicsEngine.openWindows()
  2120. self.graphicsEngine.renderFrame()
  2121. self.graphicsEngine.renderFrame()
  2122. self.graphicsEngine.syncFrame()
  2123. tex = buffer.getTexture()
  2124. saved = self.screenshot(namePrefix = namePrefix,
  2125. defaultFilename = defaultFilename,
  2126. source = tex)
  2127. self.graphicsEngine.removeWindow(buffer)
  2128. rig.removeNode()
  2129. return saved
  2130. def saveSphereMap(self, namePrefix = 'spheremap.png',
  2131. defaultFilename = 0, source = None,
  2132. camera = None, size = 256,
  2133. cameraMask = PandaNode.getAllCameraMask(),
  2134. numVertices = 1000, sourceLens = None):
  2135. """
  2136. This works much like saveCubeMap(), and uses the graphics
  2137. API's hardware cube-mapping ability to get a 360-degree view
  2138. of the world. But then it converts the six cube map faces
  2139. into a single fisheye texture, suitable for applying as a
  2140. static environment map (sphere map).
  2141. For eye-relative static environment maps, sphere maps are
  2142. often preferable to cube maps because they require only a
  2143. single texture and because they are supported on a broader
  2144. range of hardware.
  2145. The return value is the filename if successful, or None if
  2146. there is a problem.
  2147. """
  2148. if source == None:
  2149. source = self.win
  2150. if camera == None:
  2151. if hasattr(source, "getCamera"):
  2152. camera = source.getCamera()
  2153. if camera == None:
  2154. camera = self.camera
  2155. if sourceLens == None:
  2156. sourceLens = self.camLens
  2157. if hasattr(source, "getWindow"):
  2158. source = source.getWindow()
  2159. # First, make an offscreen buffer to convert the cube map to a
  2160. # sphere map. We make it first so we can guarantee the
  2161. # rendering order for the cube map.
  2162. toSphere = source.makeTextureBuffer(namePrefix, size, size,
  2163. Texture(), 1)
  2164. # Now make the cube map buffer.
  2165. rig = NodePath(namePrefix)
  2166. buffer = toSphere.makeCubeMap(namePrefix, size, rig, cameraMask, 0)
  2167. if buffer == None:
  2168. self.graphicsEngine.removeWindow(toSphere)
  2169. raise Exception("Could not make cube map.")
  2170. # Set the near and far planes from the default lens.
  2171. lens = rig.find('**/+Camera').node().getLens()
  2172. lens.setNearFar(sourceLens.getNear(), sourceLens.getFar())
  2173. # Set up the scene to convert the cube map. It's just a
  2174. # simple scene, with only the FisheyeMaker object in it.
  2175. dr = toSphere.makeMonoDisplayRegion()
  2176. camNode = Camera('camNode')
  2177. lens = OrthographicLens()
  2178. lens.setFilmSize(2, 2)
  2179. lens.setNearFar(-1000, 1000)
  2180. camNode.setLens(lens)
  2181. root = NodePath('buffer')
  2182. cam = root.attachNewNode(camNode)
  2183. dr.setCamera(cam)
  2184. fm = FisheyeMaker('card')
  2185. fm.setNumVertices(numVertices)
  2186. fm.setSquareInscribed(1, 1.1)
  2187. fm.setReflection(1)
  2188. card = root.attachNewNode(fm.generate())
  2189. card.setTexture(buffer.getTexture())
  2190. # Now render a frame. This will render out the cube map and
  2191. # then apply it to the the card in the toSphere buffer.
  2192. rig.reparentTo(camera)
  2193. self.graphicsEngine.openWindows()
  2194. self.graphicsEngine.renderFrame()
  2195. # One more frame for luck.
  2196. self.graphicsEngine.renderFrame()
  2197. self.graphicsEngine.syncFrame()
  2198. saved = self.screenshot(namePrefix = namePrefix,
  2199. defaultFilename = defaultFilename,
  2200. source = toSphere.getTexture())
  2201. self.graphicsEngine.removeWindow(buffer)
  2202. self.graphicsEngine.removeWindow(toSphere)
  2203. rig.removeNode()
  2204. return saved
  2205. def movie(self, namePrefix = 'movie', duration = 1.0, fps = 30,
  2206. format = 'png', sd = 4, source = None):
  2207. """
  2208. Spawn a task to capture a movie using the screenshot function.
  2209. - namePrefix will be used to form output file names (can include
  2210. path information (e.g. '/i/beta/frames/myMovie')
  2211. - duration is the length of the movie in seconds
  2212. - fps is the frame rate of the resulting movie
  2213. - format specifies output file format (e.g. png, bmp)
  2214. - sd specifies number of significant digits for frame count in the
  2215. output file name (e.g. if sd = 4, movie_0001.png)
  2216. - source is the Window, Buffer, DisplayRegion, or Texture from which
  2217. to save the resulting images. The default is the main window.
  2218. """
  2219. globalClock.setMode(ClockObject.MNonRealTime)
  2220. globalClock.setDt(1.0/float(fps))
  2221. t = taskMgr.add(self._movieTask, namePrefix + '_task')
  2222. t.frameIndex = 0 # Frame 0 is not captured.
  2223. t.numFrames = int(duration * fps)
  2224. t.source = source
  2225. t.outputString = namePrefix + '_%0' + repr(sd) + 'd.' + format
  2226. t.setUponDeath(lambda state: globalClock.setMode(ClockObject.MNormal))
  2227. def _movieTask(self, state):
  2228. if state.frameIndex != 0:
  2229. frameName = state.outputString % state.frameIndex
  2230. self.notify.info("Capturing frame: " + frameName)
  2231. self.screenshot(namePrefix = frameName, defaultFilename = 0,
  2232. source = state.source)
  2233. state.frameIndex += 1
  2234. if state.frameIndex > state.numFrames:
  2235. return Task.done
  2236. else:
  2237. return Task.cont
  2238. def windowEvent(self, win):
  2239. if win != self.win:
  2240. # This event isn't about our window.
  2241. return
  2242. properties = win.getProperties()
  2243. if properties != self.__prevWindowProperties:
  2244. self.__prevWindowProperties = properties
  2245. self.notify.debug("Got window event: %s" % (repr(properties)))
  2246. if not properties.getOpen():
  2247. # If the user closes the main window, we should exit.
  2248. self.notify.info("User closed main window.")
  2249. if __debug__:
  2250. if self.__autoGarbageLogging:
  2251. GarbageReport.b_checkForGarbageLeaks()
  2252. self.userExit()
  2253. if properties.getForeground() and not self.mainWinForeground:
  2254. self.mainWinForeground = 1
  2255. elif not properties.getForeground() and self.mainWinForeground:
  2256. self.mainWinForeground = 0
  2257. if __debug__:
  2258. if self.__autoGarbageLogging:
  2259. GarbageReport.b_checkForGarbageLeaks()
  2260. if properties.getMinimized() and not self.mainWinMinimized:
  2261. # If the main window is minimized, throw an event to
  2262. # stop the music.
  2263. self.mainWinMinimized = 1
  2264. messenger.send('PandaPaused')
  2265. elif not properties.getMinimized() and self.mainWinMinimized:
  2266. # If the main window is restored, throw an event to
  2267. # restart the music.
  2268. self.mainWinMinimized = 0
  2269. messenger.send('PandaRestarted')
  2270. # If we have not forced the aspect ratio, let's see if it has
  2271. # changed and update the camera lenses and aspect2d parameters
  2272. self.adjustWindowAspectRatio(self.getAspectRatio())
  2273. if win.getSideBySideStereo() and win.hasSize() and win.getSbsLeftYSize() != 0:
  2274. self.pixel2d.setScale(2.0 / win.getSbsLeftXSize(), 1.0, 2.0 / win.getSbsLeftYSize())
  2275. self.pixel2dp.setScale(2.0 / win.getSbsLeftXSize(), 1.0, 2.0 / win.getSbsLeftYSize())
  2276. else:
  2277. xsize, ysize = self.getSize()
  2278. if xsize > 0 and ysize > 0:
  2279. self.pixel2d.setScale(2.0 / xsize, 1.0, 2.0 / ysize)
  2280. if self.wantRender2dp:
  2281. self.pixel2dp.setScale(2.0 / xsize, 1.0, 2.0 / ysize)
  2282. def adjustWindowAspectRatio(self, aspectRatio):
  2283. """ This function is normally called internally by
  2284. windowEvent(), but it may also be called to explicitly adjust
  2285. the aspect ratio of the render/render2d DisplayRegion, by a
  2286. class that has redefined these. """
  2287. if self.__configAspectRatio:
  2288. aspectRatio = self.__configAspectRatio
  2289. if aspectRatio != self.__oldAspectRatio:
  2290. self.__oldAspectRatio = aspectRatio
  2291. # Fix up some anything that depends on the aspectRatio
  2292. if self.camLens:
  2293. self.camLens.setAspectRatio(aspectRatio)
  2294. if aspectRatio < 1:
  2295. # If the window is TALL, lets expand the top and bottom
  2296. self.aspect2d.setScale(1.0, aspectRatio, aspectRatio)
  2297. self.a2dTop = 1.0 / aspectRatio
  2298. self.a2dBottom = - 1.0 / aspectRatio
  2299. self.a2dLeft = -1
  2300. self.a2dRight = 1.0
  2301. # Don't forget 2dp
  2302. if self.wantRender2dp:
  2303. self.aspect2dp.setScale(1.0, aspectRatio, aspectRatio)
  2304. self.a2dpTop = 1.0 / aspectRatio
  2305. self.a2dpBottom = - 1.0 / aspectRatio
  2306. self.a2dpLeft = -1
  2307. self.a2dpRight = 1.0
  2308. else:
  2309. # If the window is WIDE, lets expand the left and right
  2310. self.aspect2d.setScale(1.0 / aspectRatio, 1.0, 1.0)
  2311. self.a2dTop = 1.0
  2312. self.a2dBottom = -1.0
  2313. self.a2dLeft = -aspectRatio
  2314. self.a2dRight = aspectRatio
  2315. # Don't forget 2dp
  2316. if self.wantRender2dp:
  2317. self.aspect2dp.setScale(1.0 / aspectRatio, 1.0, 1.0)
  2318. self.a2dpTop = 1.0
  2319. self.a2dpBottom = -1.0
  2320. self.a2dpLeft = -aspectRatio
  2321. self.a2dpRight = aspectRatio
  2322. # Reposition the aspect2d marker nodes
  2323. self.a2dTopCenter.setPos(0, 0, self.a2dTop)
  2324. self.a2dTopCenterNs.setPos(0, 0, self.a2dTop)
  2325. self.a2dBottomCenter.setPos(0, 0, self.a2dBottom)
  2326. self.a2dBottomCenterNs.setPos(0, 0, self.a2dBottom)
  2327. self.a2dLeftCenter.setPos(self.a2dLeft, 0, 0)
  2328. self.a2dLeftCenterNs.setPos(self.a2dLeft, 0, 0)
  2329. self.a2dRightCenter.setPos(self.a2dRight, 0, 0)
  2330. self.a2dRightCenterNs.setPos(self.a2dRight, 0, 0)
  2331. self.a2dTopLeft.setPos(self.a2dLeft, 0, self.a2dTop)
  2332. self.a2dTopLeftNs.setPos(self.a2dLeft, 0, self.a2dTop)
  2333. self.a2dTopRight.setPos(self.a2dRight, 0, self.a2dTop)
  2334. self.a2dTopRightNs.setPos(self.a2dRight, 0, self.a2dTop)
  2335. self.a2dBottomLeft.setPos(self.a2dLeft, 0, self.a2dBottom)
  2336. self.a2dBottomLeftNs.setPos(self.a2dLeft, 0, self.a2dBottom)
  2337. self.a2dBottomRight.setPos(self.a2dRight, 0, self.a2dBottom)
  2338. self.a2dBottomRightNs.setPos(self.a2dRight, 0, self.a2dBottom)
  2339. # Reposition the aspect2dp marker nodes
  2340. if self.wantRender2dp:
  2341. self.a2dpTopCenter.setPos(0, 0, self.a2dpTop)
  2342. self.a2dpBottomCenter.setPos(0, 0, self.a2dpBottom)
  2343. self.a2dpLeftCenter.setPos(self.a2dpLeft, 0, 0)
  2344. self.a2dpRightCenter.setPos(self.a2dpRight, 0, 0)
  2345. self.a2dpTopLeft.setPos(self.a2dpLeft, 0, self.a2dpTop)
  2346. self.a2dpTopRight.setPos(self.a2dpRight, 0, self.a2dpTop)
  2347. self.a2dpBottomLeft.setPos(self.a2dpLeft, 0, self.a2dpBottom)
  2348. self.a2dpBottomRight.setPos(self.a2dpRight, 0, self.a2dpBottom)
  2349. # If anybody needs to update their GUI, put a callback on this event
  2350. messenger.send("aspectRatioChanged")
  2351. def userExit(self):
  2352. # The user has requested we exit the program. Deal with this.
  2353. if self.exitFunc:
  2354. self.exitFunc()
  2355. self.notify.info("Exiting ShowBase.")
  2356. self.finalizeExit()
  2357. def finalizeExit(self):
  2358. sys.exit()
  2359. # [gjeon] start wxPython
  2360. def startWx(self, fWantWx = True):
  2361. fWantWx = bool(fWantWx)
  2362. if self.wantWx != fWantWx:
  2363. self.wantWx = fWantWx
  2364. if self.wantWx:
  2365. self.spawnWxLoop()
  2366. def spawnWxLoop(self):
  2367. """ Call this method to hand the main loop over to wxPython.
  2368. This sets up a wxTimer callback so that Panda still gets
  2369. updated, but wxPython owns the main loop (which seems to make
  2370. it happier than the other way around). """
  2371. if self.wxAppCreated:
  2372. # Don't do this twice.
  2373. return
  2374. init_app_for_gui()
  2375. # Use importlib to prevent this import from being picked up
  2376. # by modulefinder when packaging an application.
  2377. wx = importlib.import_module('wx')
  2378. if not self.wxApp:
  2379. # Create a new base.wxApp.
  2380. self.wxApp = wx.PySimpleApp(redirect = False)
  2381. if ConfigVariableBool('wx-main-loop', True):
  2382. # Put wxPython in charge of the main loop. It really
  2383. # seems to like this better; some features of wx don't
  2384. # work properly unless this is true.
  2385. # Set a timer to run the Panda frame 60 times per second.
  2386. wxFrameRate = ConfigVariableDouble('wx-frame-rate', 60.0)
  2387. self.wxTimer = wx.Timer(self.wxApp)
  2388. self.wxTimer.Start(1000.0 / wxFrameRate.getValue())
  2389. self.wxApp.Bind(wx.EVT_TIMER, self.__wxTimerCallback)
  2390. # wx is now the main loop, not us any more.
  2391. self.run = self.wxRun
  2392. self.taskMgr.run = self.wxRun
  2393. builtins.run = self.wxRun
  2394. if self.appRunner:
  2395. self.appRunner.run = self.wxRun
  2396. else:
  2397. # Leave Panda in charge of the main loop. This is
  2398. # friendlier for IDE's and interactive editing in general.
  2399. def wxLoop(task):
  2400. # First we need to ensure that the OS message queue is
  2401. # processed.
  2402. self.wxApp.Yield()
  2403. # Now do all the wxPython events waiting on this frame.
  2404. while self.wxApp.Pending():
  2405. self.wxApp.Dispatch()
  2406. return task.again
  2407. self.taskMgr.add(wxLoop, 'wxLoop')
  2408. self.wxAppCreated = True
  2409. def __wxTimerCallback(self, event):
  2410. if Thread.getCurrentThread().getCurrentTask():
  2411. # This happens when the wxTimer expires while igLoop is
  2412. # rendering. Ignore it.
  2413. return
  2414. self.taskMgr.step()
  2415. def wxRun(self):
  2416. """ This method replaces base.run() after we have called
  2417. spawnWxLoop(). Since at this point wxPython now owns the main
  2418. loop, this method is a call to wxApp.MainLoop(). """
  2419. if Thread.getCurrentThread().getCurrentTask():
  2420. # This happens in the p3d environment during startup.
  2421. # Ignore it.
  2422. return
  2423. self.wxApp.MainLoop()
  2424. def startTk(self, fWantTk = True):
  2425. fWantTk = bool(fWantTk)
  2426. if self.wantTk != fWantTk:
  2427. self.wantTk = fWantTk
  2428. if self.wantTk:
  2429. self.spawnTkLoop()
  2430. def spawnTkLoop(self):
  2431. """ Call this method to hand the main loop over to Tkinter.
  2432. This sets up a timer callback so that Panda still gets
  2433. updated, but Tkinter owns the main loop (which seems to make
  2434. it happier than the other way around). """
  2435. if self.tkRootCreated:
  2436. # Don't do this twice.
  2437. return
  2438. # Use importlib to prevent this import from being picked up
  2439. # by modulefinder when packaging an application.
  2440. tkinter = importlib.import_module('_tkinter')
  2441. Pmw = importlib.import_module('Pmw')
  2442. # Create a new Tk root.
  2443. if not self.tkRoot:
  2444. self.tkRoot = Pmw.initialise()
  2445. builtins.tkroot = self.tkRoot
  2446. init_app_for_gui()
  2447. if ConfigVariableBool('tk-main-loop', True):
  2448. # Put Tkinter in charge of the main loop. It really
  2449. # seems to like this better; the GUI otherwise becomes
  2450. # largely unresponsive on Mac OS X unless this is true.
  2451. # Set a timer to run the Panda frame 60 times per second.
  2452. tkFrameRate = ConfigVariableDouble('tk-frame-rate', 60.0)
  2453. self.tkDelay = int(1000.0 / tkFrameRate.getValue())
  2454. self.tkRoot.after(self.tkDelay, self.__tkTimerCallback)
  2455. # wx is now the main loop, not us any more.
  2456. self.run = self.tkRun
  2457. self.taskMgr.run = self.tkRun
  2458. builtins.run = self.tkRun
  2459. if self.appRunner:
  2460. self.appRunner.run = self.tkRun
  2461. else:
  2462. # Leave Panda in charge of the main loop. This is
  2463. # friendlier for IDE's and interactive editing in general.
  2464. def tkLoop(task):
  2465. # Do all the tkinter events waiting on this frame
  2466. # dooneevent will return 0 if there are no more events
  2467. # waiting or 1 if there are still more.
  2468. # DONT_WAIT tells tkinter not to block waiting for events
  2469. while self.tkRoot.dooneevent(tkinter.ALL_EVENTS | tkinter.DONT_WAIT):
  2470. pass
  2471. return task.again
  2472. self.taskMgr.add(tkLoop, 'tkLoop')
  2473. self.tkRootCreated = True
  2474. def __tkTimerCallback(self):
  2475. if not Thread.getCurrentThread().getCurrentTask():
  2476. self.taskMgr.step()
  2477. self.tkRoot.after(self.tkDelay, self.__tkTimerCallback)
  2478. def tkRun(self):
  2479. """ This method replaces base.run() after we have called
  2480. spawnTkLoop(). Since at this point Tkinter now owns the main
  2481. loop, this method is a call to tkRoot.mainloop(). """
  2482. if Thread.getCurrentThread().getCurrentTask():
  2483. # This happens in the p3d environment during startup.
  2484. # Ignore it.
  2485. return
  2486. self.tkRoot.mainloop()
  2487. def startDirect(self, fWantDirect = 1, fWantTk = 1, fWantWx = 0):
  2488. self.startTk(fWantTk)
  2489. self.startWx(fWantWx)
  2490. self.wantDirect = fWantDirect
  2491. if self.wantDirect:
  2492. # Use importlib to prevent this import from being picked up
  2493. # by modulefinder when packaging an application.
  2494. DirectSession = importlib.import_module('direct.directtools.DirectSession')
  2495. self.direct = DirectSession.DirectSession()
  2496. self.direct.enable()
  2497. builtins.direct = self.direct
  2498. else:
  2499. builtins.direct = self.direct = None
  2500. def getRepository(self):
  2501. return None
  2502. def getAxes(self):
  2503. return loader.loadModel("models/misc/xyzAxis.bam")
  2504. def __doStartDirect(self):
  2505. if self.__directStarted:
  2506. return
  2507. self.__directStarted = False
  2508. # Start Tk, Wx and DIRECT if specified by Config.prc
  2509. fTk = self.config.GetBool('want-tk', 0)
  2510. fWx = self.config.GetBool('want-wx', 0)
  2511. # Start DIRECT if specified in Config.prc or in cluster mode
  2512. fDirect = (self.config.GetBool('want-directtools', 0) or
  2513. (self.config.GetString("cluster-mode", '') != ''))
  2514. # Set fWantTk to 0 to avoid starting Tk with this call
  2515. self.startDirect(fWantDirect = fDirect, fWantTk = fTk, fWantWx = fWx)
  2516. def run(self):
  2517. """ This method runs the TaskManager when self.appRunner is
  2518. None, which is to say, when we are not running from within a
  2519. p3d file. When we *are* within a p3d file, the Panda
  2520. runtime has to be responsible for running the main loop, so
  2521. we can't allow the application to do it. """
  2522. if self.appRunner is None or self.appRunner.dummy or \
  2523. (self.appRunner.interactiveConsole and not self.appRunner.initialAppImport):
  2524. self.taskMgr.run()
  2525. # Snake-case aliases, for people who prefer these. We're in the process
  2526. # of migrating everyone to use the snake-case alternatives.
  2527. make_default_pipe = makeDefaultPipe
  2528. make_module_pipe = makeModulePipe
  2529. make_all_pipes = makeAllPipes
  2530. open_window = openWindow
  2531. close_window = closeWindow
  2532. open_default_window = openDefaultWindow
  2533. open_main_window = openMainWindow
  2534. set_sleep = setSleep
  2535. set_frame_rate_meter = setFrameRateMeter
  2536. set_scene_graph_analyzer_meter = setSceneGraphAnalyzerMeter
  2537. setup_window_controls = setupWindowControls
  2538. setup_render = setupRender
  2539. setup_render2d = setupRender2d
  2540. setup_render2dp = setupRender2dp
  2541. set_aspect_ratio = setAspectRatio
  2542. get_aspect_ratio = getAspectRatio
  2543. get_size = getSize
  2544. make_camera = makeCamera
  2545. make_camera2d = makeCamera2d
  2546. make_camera2dp = makeCamera2dp
  2547. setup_data_graph = setupDataGraph
  2548. setup_mouse = setupMouse
  2549. setup_mouse_cb = setupMouseCB
  2550. enable_software_mouse_pointer = enableSoftwareMousePointer
  2551. add_angular_integrator = addAngularIntegrator
  2552. enable_particles = enableParticles
  2553. disable_particles = disableParticles
  2554. toggle_particles = toggleParticles
  2555. create_stats = createStats
  2556. add_sfx_manager = addSfxManager
  2557. enable_music = enableMusic
  2558. enable_sound_effects = enableSoundEffects
  2559. disable_all_audio = disableAllAudio
  2560. enable_all_audio = enableAllAudio
  2561. init_shadow_trav = initShadowTrav
  2562. get_background_color = getBackgroundColor
  2563. set_background_color = setBackgroundColor
  2564. toggle_backface = toggleBackface
  2565. backface_culling_on = backfaceCullingOn
  2566. backface_culling_off = backfaceCullingOff
  2567. toggle_texture = toggleTexture
  2568. texture_on = textureOn
  2569. texture_off = textureOff
  2570. toggle_wireframe = toggleWireframe
  2571. wireframe_on = wireframeOn
  2572. wireframe_off = wireframeOff
  2573. disable_mouse = disableMouse
  2574. enable_mouse = enableMouse
  2575. silence_input = silenceInput
  2576. revive_input = reviveInput
  2577. set_mouse_on_node = setMouseOnNode
  2578. change_mouse_interface = changeMouseInterface
  2579. use_drive = useDrive
  2580. use_trackball = useTrackball
  2581. toggle_tex_mem = toggleTexMem
  2582. toggle_show_vertices = toggleShowVertices
  2583. oobe_cull = oobeCull
  2584. show_camera_frustum = showCameraFrustum
  2585. remove_camera_frustum = removeCameraFrustum
  2586. save_cube_map = saveCubeMap
  2587. save_sphere_map = saveSphereMap
  2588. start_wx = startWx
  2589. start_tk = startTk
  2590. start_direct = startDirect
  2591. # A class to encapsulate information necessary for multiwindow support.
  2592. class WindowControls:
  2593. def __init__(
  2594. self, win, cam=None, camNode=None, cam2d=None, mouseWatcher=None,
  2595. mouseKeyboard=None, closeCmd=lambda: 0, grid=None):
  2596. self.win = win
  2597. self.camera = cam
  2598. if camNode is None and cam is not None:
  2599. camNode = cam.node()
  2600. self.camNode = camNode
  2601. self.camera2d = cam2d
  2602. self.mouseWatcher = mouseWatcher
  2603. self.mouseKeyboard = mouseKeyboard
  2604. self.closeCommand = closeCmd
  2605. self.grid = grid
  2606. def __str__(self):
  2607. s = "window = " + str(self.win) + "\n"
  2608. s += "camera = " + str(self.camera) + "\n"
  2609. s += "camNode = " + str(self.camNode) + "\n"
  2610. s += "camera2d = " + str(self.camera2d) + "\n"
  2611. s += "mouseWatcher = " + str(self.mouseWatcher) + "\n"
  2612. s += "mouseAndKeyboard = " + str(self.mouseKeyboard) + "\n"
  2613. return s