ShowBase.py 137 KB

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