| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534 |
- """ This module contains `.ShowBase`, an application framework responsible
- for opening a graphical display, setting up input devices and creating
- the scene graph.
- The simplest way to open a ShowBase instance is to execute this code:
- .. code-block:: python
- from direct.showbase.ShowBase import ShowBase
- base = ShowBase()
- base.run()
- A common approach is to create your own subclass inheriting from ShowBase.
- Built-in global variables
- -------------------------
- Some key variables used in all Panda3D scripts are actually attributes of the
- ShowBase instance. When creating an instance of this class, it will write many
- of these variables to the built-in scope of the Python interpreter, so that
- they are accessible to any Python module, without the need for extra imports.
- For example, the ShowBase instance itself is accessible anywhere through the
- :data:`~builtins.base` variable.
- While these are handy for prototyping, we do not recommend using them in bigger
- projects, as it can make the code confusing to read to other Python developers,
- to whom it may not be obvious where these variables are originating.
- Refer to the :mod:`builtins` page for a listing of the variables written to the
- built-in scope.
- """
- __all__ = ['ShowBase', 'WindowControls']
- # This module redefines the builtin import function with one
- # that prints out every import it does in a hierarchical form
- # Annoying and very noisy, but sometimes useful
- #import VerboseImport
- from panda3d.core import (
- AntialiasAttrib,
- AudioManager,
- AudioSound,
- BitMask32,
- ButtonThrower,
- Camera,
- ClockObject,
- CollisionTraverser,
- ColorBlendAttrib,
- ConfigPageManager,
- ConfigVariableBool,
- ConfigVariableDouble,
- ConfigVariableFilename,
- ConfigVariableInt,
- ConfigVariableManager,
- ConfigVariableString,
- DataGraphTraverser,
- DepthTestAttrib,
- DepthWriteAttrib,
- DriveInterface,
- ExecutionEnvironment,
- Filename,
- FisheyeMaker,
- FrameBufferProperties,
- FrameRateMeter,
- GeomNode,
- GraphicsEngine,
- GraphicsOutput,
- GraphicsPipe,
- GraphicsPipeSelection,
- GraphicsWindow,
- InputDeviceManager,
- InputDeviceNode,
- KeyboardButton,
- LensNode,
- Mat4,
- ModelNode,
- ModifierButtons,
- MouseAndKeyboard,
- MouseRecorder,
- MouseWatcher,
- NodePath,
- Notify,
- OrthographicLens,
- PandaNode,
- PandaSystem,
- PerspectiveLens,
- PGMouseWatcherBackground,
- PGTop,
- PNMImage,
- PStatClient,
- PythonCallbackObject,
- RecorderController,
- RenderModeAttrib,
- RenderState,
- RescaleNormalAttrib,
- SceneGraphAnalyzerMeter,
- TexGenAttrib,
- Texture,
- TextureStage,
- Thread,
- Trackball,
- Transform2SG,
- TransformState,
- TrueClock,
- VBase4,
- VirtualFileSystem,
- WindowProperties,
- getModelPath,
- )
- from panda3d.direct import throw_new_frame, init_app_for_gui
- from panda3d.direct import storeAccessibilityShortcutKeys, allowAccessibilityShortcutKeys
- from . import DConfig
- # Register the extension methods for NodePath.
- from direct.extensions_native import NodePath_extensions # pylint: disable=unused-import
- # This needs to be available early for DirectGUI imports
- import sys
- import builtins
- builtins.config = DConfig # type: ignore[attr-defined]
- from direct.directnotify.DirectNotifyGlobal import directNotify, giveNotify
- from direct.directnotify.Notifier import Notifier
- from .MessengerGlobal import messenger
- from .BulletinBoardGlobal import bulletinBoard
- from direct.task.TaskManagerGlobal import taskMgr
- from .JobManagerGlobal import jobMgr
- from .EventManagerGlobal import eventMgr
- from .PythonUtil import Stack
- #from PythonUtil import *
- from direct.interval import IntervalManager
- from direct.showbase.BufferViewer import BufferViewer
- from direct.task import Task
- from . import Loader
- import time
- import atexit
- import importlib
- from direct.showbase import ExceptionVarDump
- from . import DirectObject
- from . import SfxPlayer
- from typing import ClassVar, Optional
- if __debug__:
- from direct.showbase import GarbageReport
- from direct.directutil import DeltaProfiler
- from . import OnScreenDebug
- import warnings
- @atexit.register
- def exitfunc():
- if getattr(builtins, 'base', None) is not None:
- builtins.base.destroy()
- # Now ShowBase is a DirectObject. We need this so ShowBase can hang
- # hooks on messages, particularly on window-event. This doesn't
- # *seem* to cause anyone any problems.
- class ShowBase(DirectObject.DirectObject):
- #: The deprecated `.DConfig` interface for accessing config variables.
- config: ClassVar = DConfig
- notify: ClassVar[Notifier] = directNotify.newCategory("ShowBase")
- guiItems: ClassVar[dict]
- render2d: NodePath
- aspect2d: NodePath
- pixel2d: NodePath
- def __init__(self, fStartDirect=True, windowType=None):
- """Opens a window, sets up a 3-D and several 2-D scene graphs, and
- everything else needed to render the scene graph to the window.
- To prevent a window from being opened, set windowType to the string
- 'none' (or 'offscreen' to create an offscreen buffer). If this is not
- specified, the default value is taken from the 'window-type'
- configuration variable.
- This constructor will add various things to the Python builtins scope,
- including this instance itself (under the name ``base``).
- """
- from . import ShowBaseGlobal
- #: Set if the want-dev Config.prc variable is enabled. By default, it
- #: is set to True except when using Python with the -O flag.
- self.__dev__ = ShowBaseGlobal.__dev__
- builtins.__dev__ = self.__dev__
- logStackDump = (ConfigVariableBool('log-stack-dump', False).value or
- ConfigVariableBool('client-log-stack-dump', False).value)
- uploadStackDump = ConfigVariableBool('upload-stack-dump', False).value
- if logStackDump or uploadStackDump:
- ExceptionVarDump.install(logStackDump, uploadStackDump)
- if __debug__:
- self.__autoGarbageLogging = self.__dev__ and ConfigVariableBool('auto-garbage-logging', False)
- #: The directory containing the main Python file of this application.
- self.mainDir = ExecutionEnvironment.getEnvironmentVariable("MAIN_DIR")
- self.main_dir = self.mainDir
- # This contains the global appRunner instance, as imported from
- # `.AppRunnerGlobal`. This is deprecated and always None nowadays.
- self.appRunner = None
- self.app_runner = self.appRunner
- #debug running multiplier
- self.debugRunningMultiplier = 4
- # [gjeon] to disable sticky keys
- if ConfigVariableBool('disable-sticky-keys', False):
- storeAccessibilityShortcutKeys()
- allowAccessibilityShortcutKeys(False)
- self.__disabledStickyKeys = True
- else:
- self.__disabledStickyKeys = False
- self.printEnvDebugInfo()
- vfs = VirtualFileSystem.getGlobalPtr()
- self.nextWindowIndex = 1
- self.__directStarted = False
- self.__deadInputs = 0
- # Store dconfig variables
- self.sfxActive = ConfigVariableBool('audio-sfx-active', True).value
- self.musicActive = ConfigVariableBool('audio-music-active', True).value
- self.wantFog = ConfigVariableBool('want-fog', True).value
- self.wantRender2dp = ConfigVariableBool('want-render2dp', True).value
- self.screenshotExtension = ConfigVariableString('screenshot-extension', 'jpg').value
- self.musicManager = None
- self.musicManagerIsValid = None
- self.sfxManagerList = []
- self.sfxManagerIsValidList = []
- self.wantStats = ConfigVariableBool('want-pstats', False).value
- self.wantTk = False
- self.wantWx = False
- self.wantDirect = False
- #: Fill this in with a function to invoke when the user "exits"
- #: the program by closing the main window.
- self.exitFunc = None
- #: Add final-exit callbacks to this list. These will be called
- #: when sys.exit() is called, after Panda has unloaded, and
- #: just before Python is about to shut down.
- self.finalExitCallbacks = []
- # Set up the TaskManager to reset the PStats clock back
- # whenever we resume from a pause. This callback function is
- # a little hacky, but we can't call it directly from within
- # the TaskManager because he doesn't know about PStats (and
- # has to run before libpanda is even loaded).
- taskMgr.resumeFunc = PStatClient.resumeAfterPause
- if self.__dev__:
- self.__setupProfile()
- # If the aspect ratio is 0 or None, it means to infer the
- # aspect ratio from the window size.
- # If you need to know the actual aspect ratio call base.getAspectRatio()
- self.__configAspectRatio = ConfigVariableDouble('aspect-ratio', 0).value
- # This variable is used to see if the aspect ratio has changed when
- # we get a window-event.
- self.__oldAspectRatio = None
- #: This is set to the value of the window-type config variable, but may
- #: optionally be overridden in the Showbase constructor. Should either
- #: be 'onscreen' (the default), 'offscreen' or 'none'.
- self.windowType = windowType
- if self.windowType is None:
- self.windowType = ConfigVariableString('window-type', 'onscreen').value
- self.requireWindow = ConfigVariableBool('require-window', True).value
- #: This is the main, or only window; see `winList` for a list of *all* windows.
- self.win = None
- self.frameRateMeter = None
- self.sceneGraphAnalyzerMeter = None
- #: A list of all windows opened via `openWindow()`.
- self.winList = []
- self.winControls = []
- self.mainWinMinimized = 0
- self.mainWinForeground = 0
- #: Contains the :class:`~panda3d.core.GraphicsPipe` object created by
- #: `makeDefaultPipe()`.
- self.pipe = None
- #: The full list of :class:`~panda3d.core.GraphicsPipe` objects,
- #: including any auxiliary pipes. Filled by `makeAllPipes()`.
- self.pipeList = []
- self.mouse2cam = None
- self.buttonThrowers = None
- self.mouseWatcher = None
- #: The :class:`~panda3d.core.MouseWatcher` object, created by
- #: `setupMouse()`.
- self.mouseWatcherNode = None
- self.pointerWatcherNodes = None
- self.mouseInterface = None
- self.drive = None
- self.trackball = None
- self.texmem = None
- self.showVertices = None
- self.deviceButtonThrowers = []
- #: This is a :class:`~panda3d.core.NodePath` pointing to the
- #: :class:`~panda3d.core.Camera` object set up for the 3D scene.
- #: Usually a child of `camera`.
- self.cam = None
- #: Same as `cam`, but for the 2D scene graph.
- self.cam2d = None
- #: Same as `cam2d`, but for the 2D overlay scene graph.
- self.cam2dp = None
- #: This is the :class:`~panda3d.core.NodePath` that should be used to
- #: manipulate the camera. It points at the node to which the default
- #: camera (`cam`, `camNode`) is attached.
- self.camera = None
- #: Same as `camera`, but for the 2D scene graph. Parent of `cam2d`.
- self.camera2d = None
- #: Same as `camera2d`, but for the 2D overlay scene graph. Parent of
- #: `cam2dp`.
- self.camera2dp = None
- #: A list of all cameras created with `makeCamera()`, including `cam`.
- self.camList = []
- #: Convenience accessor for base.cam.node(), containing a
- #: :class:`~panda3d.core.Camera` object.
- self.camNode = None
- #: Convenience accessor for base.camNode.get_lens(), containing a
- #: :class:`~panda3d.core.Lens` object.
- self.camLens = None
- self.camFrustumVis = None
- self.direct = None
- #: This is used to store the wx.Application object used when want-wx is
- #: set or `startWx()` is called.
- self.wxApp = None
- self.wxAppCreated = False
- self.tkRoot = None
- self.tkRootCreated = False
- # This is used for syncing multiple PCs in a distributed cluster
- if hasattr(builtins, 'clusterSyncFlag'):
- # Has the cluster sync variable been set externally?
- self.clusterSyncFlag = builtins.clusterSyncFlag
- else:
- # Has the clusterSyncFlag been set via a config variable
- self.clusterSyncFlag = ConfigVariableBool('cluster-sync', False)
- # We've already created aspect2d in ShowBaseGlobal, for the
- # benefit of creating DirectGui elements before ShowBase.
- self.hidden = ShowBaseGlobal.hidden
- #: The global :class:`~panda3d.core.GraphicsEngine`, as returned by
- #: GraphicsEngine.getGlobalPtr()
- self.graphicsEngine = GraphicsEngine.getGlobalPtr()
- self.graphics_engine = self.graphicsEngine
- self.setupRender()
- self.setupRender2d()
- self.setupDataGraph()
- if self.wantRender2dp:
- self.setupRender2dp()
- #: A placeholder for a :class:`~panda3d.core.CollisionTraverser`. If
- #: someone stores a CollisionTraverser pointer here, ShowBase will
- #: traverse it automatically in the collisionLoop task, so you won't
- #: need to call :meth:`~panda3d.core.CollisionTraverser.traverse()`
- #: yourself every frame.
- self.cTrav = 0
- self.shadowTrav = 0
- self.cTravStack = Stack()
- # Ditto for an AppTraverser.
- self.appTrav = 0
- # This is the DataGraph traverser, which we might as well
- # create now.
- self.dgTrav = DataGraphTraverser()
- # Maybe create a RecorderController to record and/or play back
- # the user session.
- self.recorder = None
- playbackSession = ConfigVariableFilename('playback-session', '')
- recordSession = ConfigVariableFilename('record-session', '')
- if not playbackSession.empty():
- self.recorder = RecorderController()
- self.recorder.beginPlayback(playbackSession.value)
- elif not recordSession.empty():
- self.recorder = RecorderController()
- self.recorder.beginRecord(recordSession.value)
- if self.recorder:
- # If we're either playing back or recording, pass the
- # random seed into the system so each session will have
- # the same random seed.
- import random #, whrandom
- seed = self.recorder.getRandomSeed()
- random.seed(seed)
- #whrandom.seed(seed & 0xff, (seed >> 8) & 0xff, (seed >> 16) & 0xff)
- # For some reason, wx needs to be initialized before the graphics window
- if sys.platform == "darwin":
- if ConfigVariableBool("want-wx", False):
- wx = importlib.import_module('wx')
- self.wxApp = wx.App()
- # Same goes for Tk, which uses a conflicting NSApplication
- if ConfigVariableBool("want-tk", False):
- Pmw = importlib.import_module('Pmw')
- self.tkRoot = Pmw.initialise()
- # Open the default rendering window.
- if self.windowType != 'none':
- props = WindowProperties.getDefault()
- if ConfigVariableBool('read-raw-mice', False):
- props.setRawMice(1)
- self.openDefaultWindow(startDirect = False, props=props)
- # The default is trackball mode, which is more convenient for
- # ad-hoc development in Python using ShowBase. Applications
- # can explicitly call base.useDrive() if they prefer a drive
- # interface.
- self.mouseInterface = self.trackball
- self.useTrackball()
- #: `.Loader.Loader` object.
- self.loader = Loader.Loader(self)
- self.graphicsEngine.setDefaultLoader(self.loader.loader)
- #: The global event manager, as imported from `.EventManagerGlobal`.
- self.eventMgr = eventMgr
- #: The global messenger, as imported from `.MessengerGlobal`.
- self.messenger = messenger
- #: The global bulletin board, as imported from `.BulletinBoardGlobal`.
- self.bboard = bulletinBoard
- #: The global task manager, as imported from `.TaskManagerGlobal`.
- self.taskMgr = taskMgr
- self.task_mgr = taskMgr
- #: The global job manager, as imported from `.JobManagerGlobal`.
- self.jobMgr = jobMgr
- #: If `enableParticles()` has been called, this is the particle manager
- #: as imported from :mod:`direct.particles.ParticleManagerGlobal`.
- self.particleMgr = None
- self.particleMgrEnabled = 0
- #: If `enableParticles()` has been called, this is the physics manager
- #: as imported from :mod:`direct.showbase.PhysicsManagerGlobal`.
- self.physicsMgr = None
- self.physicsMgrEnabled = 0
- self.physicsMgrAngular = 0
- #: This is the global :class:`~panda3d.core.InputDeviceManager`, which
- #: keeps track of connected input devices.
- self.devices = InputDeviceManager.getGlobalPtr()
- self.__inputDeviceNodes = {}
- self.createStats()
- self.AppHasAudioFocus = 1
- # Get a pointer to Panda's global ClockObject, used for
- # synchronizing events between Python and C.
- clock = ClockObject.getGlobalClock()
- #: This is the global :class:`~panda3d.core.ClockObject`.
- self.clock = clock
- # Since we have already started up a TaskManager, and probably
- # a number of tasks; and since the TaskManager had to use the
- # TrueClock to tell time until this moment, make sure the
- # globalClock object is exactly in sync with the TrueClock.
- trueClock = TrueClock.getGlobalPtr()
- clock.setRealTime(trueClock.getShortTime())
- clock.tick()
- # Now we can make the TaskManager start using the new clock.
- taskMgr.globalClock = clock
- # client CPU affinity is determined by, in order:
- # - client-cpu-affinity-mask config
- # - pcalt-# (# is CPU number, 0-based)
- # - client-cpu-affinity config
- # - auto-single-cpu-affinity config
- affinityMask = ConfigVariableInt('client-cpu-affinity-mask', -1).value
- if affinityMask != -1:
- TrueClock.getGlobalPtr().setCpuAffinity(affinityMask)
- else:
- # this is useful on machines that perform better with each process
- # assigned to a single CPU
- autoAffinity = ConfigVariableBool('auto-single-cpu-affinity', False).value
- affinity = None
- if autoAffinity and hasattr(builtins, 'clientIndex'):
- affinity = abs(int(builtins.clientIndex))
- else:
- affinity = ConfigVariableInt('client-cpu-affinity', -1).value
- if (affinity in (None, -1)) and autoAffinity:
- affinity = 0
- if affinity not in (None, -1):
- # Windows XP supports a 32-bit affinity mask
- TrueClock.getGlobalPtr().setCpuAffinity(1 << (affinity % 32))
- # Make sure we're not making more than one ShowBase.
- if hasattr(builtins, 'base'):
- raise Exception("Attempt to spawn multiple ShowBase instances!")
- # DO NOT ADD TO THIS LIST. We're trying to phase out the use of
- # built-in variables by ShowBase. Use a Global module if necessary.
- builtins.base = self
- builtins.render2d = self.render2d
- builtins.aspect2d = self.aspect2d
- builtins.pixel2d = self.pixel2d
- builtins.render = self.render
- builtins.hidden = self.hidden
- builtins.camera = self.camera
- builtins.loader = self.loader
- builtins.taskMgr = self.taskMgr
- builtins.jobMgr = self.jobMgr
- builtins.eventMgr = self.eventMgr
- builtins.messenger = self.messenger
- builtins.bboard = self.bboard
- # Config needs to be defined before ShowBase is constructed
- #builtins.config = self.config
- builtins.ostream = Notify.out()
- builtins.directNotify = directNotify
- builtins.giveNotify = giveNotify
- builtins.globalClock = clock
- builtins.vfs = vfs
- builtins.cpMgr = ConfigPageManager.getGlobalPtr()
- builtins.cvMgr = ConfigVariableManager.getGlobalPtr()
- builtins.pandaSystem = PandaSystem.getGlobalPtr()
- if __debug__:
- builtins.deltaProfiler = DeltaProfiler.DeltaProfiler("ShowBase")
- self.onScreenDebug = OnScreenDebug.OnScreenDebug()
- builtins.onScreenDebug = self.onScreenDebug
- if self.wantRender2dp:
- builtins.render2dp = self.render2dp
- builtins.aspect2dp = self.aspect2dp
- builtins.pixel2dp = self.pixel2dp
- # Now add this instance to the ShowBaseGlobal module scope.
- builtins.run = ShowBaseGlobal.run
- ShowBaseGlobal.base = self
- ShowBaseGlobal.__dev__ = self.__dev__
- if self.__dev__:
- ShowBase.notify.debug('__dev__ == %s' % self.__dev__)
- else:
- ShowBase.notify.info('__dev__ == %s' % self.__dev__)
- self.createBaseAudioManagers()
- if self.__dev__ and ConfigVariableBool('track-gui-items', False):
- # dict of guiId to gui item, for tracking down leaks
- if not hasattr(ShowBase, 'guiItems'):
- ShowBase.guiItems = {}
- # optionally restore the default gui sounds from 1.7.2 and earlier
- if ConfigVariableBool('orig-gui-sounds', False).value:
- from direct.gui import DirectGuiGlobals as DGG
- DGG.setDefaultClickSound(self.loader.loadSfx("audio/sfx/GUI_click.wav"))
- DGG.setDefaultRolloverSound(self.loader.loadSfx("audio/sfx/GUI_rollover.wav"))
- # Create a private DirectObject - allowing base.accept for window-event
- # as well as allowing ShowBase's default handling of this.
- self.__directObject = DirectObject.DirectObject()
- # Now hang a hook on the window-event from Panda. This allows
- # us to detect when the user resizes, minimizes, or closes the
- # main window.
- self.__prevWindowProperties = None
- self.__directObject.accept('window-event', self.windowEvent)
- # Transition effects (fade, iris, etc)
- from . import Transitions
- #: `.Transitions.Transitions` object.
- self.transitions = Transitions.Transitions(self.loader)
- if self.win:
- # Setup the window controls - handy for multiwindow applications
- self.setupWindowControls()
- # Client sleep
- sleepTime = ConfigVariableDouble('client-sleep', 0.0)
- self.clientSleep = 0.0
- self.setSleep(sleepTime.value)
- # Extra sleep for running 4+ clients on a single machine
- # adds a sleep right after the main render in igloop
- # tends to even out the frame rate and keeps it from going
- # to zero in the out of focus windows
- self.multiClientSleep = ConfigVariableBool('multi-sleep', False)
- #: Utility for viewing offscreen buffers, see :mod:`.BufferViewer`.
- self.bufferViewer = BufferViewer(self.win, self.render2dp if self.wantRender2dp else self.render2d)
- if self.windowType != 'none':
- if fStartDirect: # [gjeon] if this is False let them start direct manually
- self.__doStartDirect()
- if ConfigVariableBool('show-tex-mem', False):
- if not self.texmem or self.texmem.cleanedUp:
- self.toggleTexMem()
- taskMgr.finalInit()
- # Start IGLOOP
- self.restart()
- # add a collision traverser via pushCTrav and remove it via popCTrav
- # that way the owner of the new cTrav doesn't need to hold onto the
- # previous one in order to put it back
- def pushCTrav(self, cTrav):
- self.cTravStack.push(self.cTrav)
- self.cTrav = cTrav
- def popCTrav(self):
- self.cTrav = self.cTravStack.pop()
- def __setupProfile(self):
- """ Sets up the Python profiler, if available, according to
- some Panda config settings. """
- try:
- profile = importlib.import_module('profile')
- pstats = importlib.import_module('pstats')
- except ImportError:
- return
- profile.Profile.bias = ConfigVariableDouble("profile-bias", 0.0).value
- def f8(x):
- return ("%" + "8.%df" % ConfigVariableInt("profile-decimals", 3)) % x
- pstats.f8 = f8
- # temp; see ToonBase.py
- def getExitErrorCode(self):
- return 0
- def printEnvDebugInfo(self):
- """Print some information about the environment that we are running
- in. Stuff like the model paths and other paths. Feel free to
- add stuff to this.
- """
- if ConfigVariableBool('want-env-debug-info', False):
- print("\n\nEnvironment Debug Info {")
- print("* model path:")
- print(getModelPath())
- #print "* dna path:"
- #print getDnaPath()
- print("}")
- def destroy(self):
- """ Call this function to destroy the ShowBase and stop all
- its tasks, freeing all of the Panda resources. Normally, you
- should not need to call it explicitly, as it is bound to the
- exitfunc and will be called at application exit time
- automatically.
- This function is designed to be safe to call multiple times.
- When called from a thread other than the main thread, this will create
- a task to schedule the destroy on the main thread, and wait for this to
- complete.
- """
- if Thread.getCurrentThread() != Thread.getMainThread():
- task = taskMgr.add(self.destroy, extraArgs=[])
- task.wait()
- return
- for cb in self.finalExitCallbacks[:]:
- cb()
- # Remove the built-in base reference
- if getattr(builtins, 'base', None) is self:
- del builtins.run
- del builtins.base
- del builtins.loader
- del builtins.taskMgr
- ShowBaseGlobal = sys.modules.get('direct.showbase.ShowBaseGlobal', None)
- if ShowBaseGlobal:
- del ShowBaseGlobal.base
- self.aspect2d.node().removeAllChildren()
- self.render2d.node().removeAllChildren()
- self.aspect2d.reparent_to(self.render2d)
- # [gjeon] restore sticky key settings
- if self.__disabledStickyKeys:
- allowAccessibilityShortcutKeys(True)
- self.__disabledStickyKeys = False
- self.__directObject.ignoreAll()
- self.ignoreAll()
- self.shutdown()
- if getattr(self, 'musicManager', None):
- self.musicManager.shutdown()
- self.musicManager = None
- for sfxManager in self.sfxManagerList:
- sfxManager.shutdown()
- self.sfxManagerList = []
- if getattr(self, 'loader', None):
- self.loader.destroy()
- self.loader = None
- if getattr(self, 'graphicsEngine', None):
- self.graphicsEngine.removeAllWindows()
- try:
- self.direct.panel.destroy()
- except Exception:
- pass
- self.win = None
- self.winList.clear()
- self.pipe = None
- def makeDefaultPipe(self, printPipeTypes = None):
- """
- Creates the default GraphicsPipe, which will be used to make
- windows unless otherwise specified.
- """
- assert self.pipe is None
- if printPipeTypes is None:
- # When the user didn't specify an explicit setting, take the value
- # from the config variable. We could just omit the parameter, however
- # this way we can keep backward compatibility.
- printPipeTypes = ConfigVariableBool("print-pipe-types", True).value
- selection = GraphicsPipeSelection.getGlobalPtr()
- if printPipeTypes:
- selection.printPipeTypes()
- self.pipe = selection.makeDefaultPipe()
- if not self.pipe:
- self.notify.error(
- "No graphics pipe is available!\n"
- "Your Config.prc file must name at least one valid panda display\n"
- "library via load-display or aux-display.")
- self.notify.info("Default graphics pipe is %s (%s)." % (
- self.pipe.getType().getName(), self.pipe.getInterfaceName()))
- self.pipeList.append(self.pipe)
- def makeModulePipe(self, moduleName):
- """
- Returns a GraphicsPipe from the indicated module,
- e.g. 'pandagl' or 'pandadx9'. Does not affect base.pipe or
- base.pipeList.
- :rtype: panda3d.core.GraphicsPipe
- """
- selection = GraphicsPipeSelection.getGlobalPtr()
- return selection.makeModulePipe(moduleName)
- def makeAllPipes(self):
- """
- Creates all GraphicsPipes that the system knows about and fill up
- `pipeList` with them.
- """
- selection = GraphicsPipeSelection.getGlobalPtr()
- selection.loadAuxModules()
- # First, we should make sure the default pipe exists.
- if self.pipe is None:
- self.makeDefaultPipe()
- # Now go through the list of known pipes, and make each one if
- # we don't have one already.
- numPipeTypes = selection.getNumPipeTypes()
- for i in range(numPipeTypes):
- pipeType = selection.getPipeType(i)
- # Do we already have a pipe of this type on the list?
- # This operation is n-squared, but presumably there won't
- # be more than a handful of pipe types, so who cares.
- already = 0
- for pipe in self.pipeList:
- if pipe.getType() == pipeType:
- already = 1
- if not already:
- pipe = selection.makePipe(pipeType)
- if pipe:
- self.notify.info("Got aux graphics pipe %s (%s)." % (
- pipe.getType().getName(), pipe.getInterfaceName()))
- self.pipeList.append(pipe)
- else:
- self.notify.info("Could not make graphics pipe %s." % (
- pipeType.getName()))
- def openWindow(self, props = None, fbprops = None, pipe = None, gsg = None,
- host = None, type = None, name = None, size = None,
- aspectRatio = None, makeCamera = True, keepCamera = False,
- scene = None, stereo = None, unexposedDraw = None,
- callbackWindowDict = None, requireWindow = None):
- """
- Creates a window and adds it to the list of windows that are
- to be updated every frame.
- :param props: the :class:`~panda3d.core.WindowProperties` that
- describes the window.
- :param fbprops: the :class:`~panda3d.core.FrameBufferProperties`
- indicating the requested framebuffer properties.
- :param type: Either 'onscreen', 'offscreen', or 'none'.
- :param keepCamera: If True, the existing base.cam is set up to
- render into the new window.
- :param makeCamera: If True (and keepCamera is False), a new camera is
- set up to render into the new window.
- :param unexposedDraw: If not None, it specifies the initial value
- of :meth:`~panda3d.core.GraphicsWindow.setUnexposedDraw()`.
- :param callbackWindowDict: If not None, a
- :class:`~panda3d.core.CallbackGraphicsWindow`
- is created instead, which allows the caller
- to create the actual window with its own
- OpenGL context, and direct Panda's rendering
- into that window.
- :param requireWindow: If True, the function should raise an exception
- if the window fails to open correctly.
- :rtype: panda3d.core.GraphicsWindow
- """
- # Save this lambda here for convenience; we'll use it to call
- # down to the underlying _doOpenWindow() with all of the above
- # parameters.
- func = lambda: self._doOpenWindow(
- props = props, fbprops = fbprops, pipe = pipe, gsg = gsg,
- host = host, type = type, name = name, size = size,
- aspectRatio = aspectRatio, makeCamera = makeCamera,
- keepCamera = keepCamera, scene = scene, stereo = stereo,
- unexposedDraw = unexposedDraw,
- callbackWindowDict = callbackWindowDict)
- if self.win:
- # If we've already opened a window before, this is just a
- # pass-through to _doOpenWindow().
- win = func()
- self.graphicsEngine.openWindows()
- return win
- if type is None:
- type = self.windowType
- if requireWindow is None:
- requireWindow = self.requireWindow
- win = func()
- # Give the window a chance to truly open.
- self.graphicsEngine.openWindows()
- if win is not None and not win.isValid():
- self.notify.info("Window did not open, removing.")
- self.closeWindow(win)
- win = None
- if win is None and pipe is None:
- # Try a little harder if the window wouldn't open.
- self.makeAllPipes()
- try:
- self.pipeList.remove(self.pipe)
- except ValueError:
- pass
- while self.win is None and self.pipeList:
- self.pipe = self.pipeList[0]
- self.notify.info("Trying pipe type %s (%s)" % (
- self.pipe.getType(), self.pipe.getInterfaceName()))
- win = func()
- self.graphicsEngine.openWindows()
- if win is not None and not win.isValid():
- self.notify.info("Window did not open, removing.")
- self.closeWindow(win)
- win = None
- if win is None:
- self.pipeList.remove(self.pipe)
- if win is None:
- self.notify.warning("Unable to open '%s' window." % (type))
- if requireWindow:
- # Unless require-window is set to false, it is an
- # error not to open a window.
- raise Exception('Could not open window.')
- else:
- self.notify.info("Successfully opened window of type %s (%s)" % (
- win.getType(), win.getPipe().getInterfaceName()))
- return win
- def _doOpenWindow(self, props = None, fbprops = None, pipe = None,
- gsg = None, host = None, type = None, name = None,
- size = None, aspectRatio = None,
- makeCamera = True, keepCamera = False,
- scene = None, stereo = None, unexposedDraw = None,
- callbackWindowDict = None):
- if pipe is None:
- pipe = self.pipe
- if pipe is None:
- self.makeDefaultPipe()
- pipe = self.pipe
- if pipe is None:
- # We couldn't get a pipe.
- return None
- if isinstance(gsg, GraphicsOutput):
- # If the gsg is a window or buffer, it means to use the
- # GSG from that buffer.
- host = gsg
- gsg = gsg.getGsg()
- # If we are using DirectX, force a new GSG to be created,
- # since at the moment DirectX seems to misbehave if we do
- # not do this. This will cause a delay while all textures
- # etc. are reloaded, so we should revisit this later if we
- # can fix the underlying bug in our DirectX support.
- if pipe.getType().getName().startswith('wdx'):
- gsg = None
- if type is None:
- type = self.windowType
- if props is None:
- props = WindowProperties.getDefault()
- if fbprops is None:
- fbprops = FrameBufferProperties.getDefault()
- if size is not None:
- # If we were given an explicit size, use it; otherwise,
- # the size from the properties is used.
- props = WindowProperties(props)
- props.setSize(size[0], size[1])
- if name is None:
- name = 'window%s' % (self.nextWindowIndex)
- self.nextWindowIndex += 1
- win = None
- flags = GraphicsPipe.BFFbPropsOptional
- if type == 'onscreen':
- flags = flags | GraphicsPipe.BFRequireWindow
- elif type == 'offscreen':
- flags = flags | GraphicsPipe.BFRefuseWindow
- if callbackWindowDict:
- flags = flags | GraphicsPipe.BFRequireCallbackWindow
- if host:
- assert host.isValid()
- win = self.graphicsEngine.makeOutput(pipe, name, 0, fbprops,
- props, flags, host.getGsg(), host)
- elif gsg:
- win = self.graphicsEngine.makeOutput(pipe, name, 0, fbprops,
- props, flags, gsg)
- else:
- win = self.graphicsEngine.makeOutput(pipe, name, 0, fbprops,
- props, flags)
- if win is None:
- # Couldn't create a window!
- return None
- if unexposedDraw is not None and hasattr(win, 'setUnexposedDraw'):
- win.setUnexposedDraw(unexposedDraw)
- if callbackWindowDict:
- # If we asked for (and received) a CallbackGraphicsWindow,
- # we now have to assign the callbacks, before we start
- # trying to do anything with the window.
- for callbackName in ['Events', 'Properties', 'Render']:
- func = callbackWindowDict.get(callbackName, None)
- if not func:
- continue
- setCallbackName = 'set%sCallback' % (callbackName)
- setCallback = getattr(win, setCallbackName)
- setCallback(PythonCallbackObject(func))
- # We also need to set up the mouse/keyboard objects.
- for inputName in callbackWindowDict.get('inputDevices', ['mouse']):
- win.createInputDevice(inputName)
- if hasattr(win, "requestProperties"):
- win.requestProperties(props)
- mainWindow = False
- if self.win is None:
- mainWindow = True
- self.win = win
- if hasattr(self, 'bufferViewer'):
- self.bufferViewer.win = win
- self.winList.append(win)
- # Set up a 3-d camera for the window by default.
- if keepCamera:
- self.makeCamera(win, scene = scene, aspectRatio = aspectRatio,
- stereo = stereo, useCamera = self.cam)
- elif makeCamera:
- self.makeCamera(win, scene = scene, aspectRatio = aspectRatio,
- stereo = stereo)
- messenger.send('open_window', [win, mainWindow])
- if mainWindow:
- messenger.send('open_main_window')
- return win
- def closeWindow(self, win, keepCamera = False, removeWindow = True):
- """
- Closes the indicated window and removes it from the list of
- windows. If it is the main window, clears the main window
- pointer to None.
- """
- win.setActive(False)
- # First, remove all of the cameras associated with display
- # regions on the window.
- numRegions = win.getNumDisplayRegions()
- for i in range(numRegions):
- dr = win.getDisplayRegion(i)
- # [gjeon] remove drc in base.direct.drList
- if self.direct is not None:
- for drc in self.direct.drList:
- if drc.cam == dr.getCamera():
- self.direct.drList.displayRegionList.remove(drc)
- break
- cam = NodePath(dr.getCamera())
- dr.setCamera(NodePath())
- if not cam.isEmpty() and \
- cam.node().getNumDisplayRegions() == 0 and \
- not keepCamera:
- # If the camera is used by no other DisplayRegions,
- # remove it.
- if self.camList.count(cam) != 0:
- self.camList.remove(cam)
- # Don't throw away self.camera; we want to
- # preserve it for reopening the window.
- if cam == self.cam:
- self.cam = None
- if cam == self.cam2d:
- self.cam2d = None
- if cam == self.cam2dp:
- self.cam2dp = None
- cam.removeNode()
- # [gjeon] remove winControl
- for winCtrl in self.winControls:
- if winCtrl.win == win:
- self.winControls.remove(winCtrl)
- break
- # Now we can actually close the window.
- if removeWindow:
- self.graphicsEngine.removeWindow(win)
- self.winList.remove(win)
- mainWindow = False
- if win == self.win:
- mainWindow = True
- self.win = None
- if self.frameRateMeter:
- self.frameRateMeter.clearWindow()
- self.frameRateMeter = None
- if self.sceneGraphAnalyzerMeter:
- self.sceneGraphAnalyzerMeter.clearWindow()
- self.sceneGraphAnalyzerMeter = None
- messenger.send('close_window', [win, mainWindow])
- if mainWindow:
- messenger.send('close_main_window')
- if not self.winList:
- # Give the window(s) a chance to actually close before we
- # continue.
- self.graphicsEngine.renderFrame()
- def openDefaultWindow(self, *args, **kw):
- """
- Creates the main window for the first time, without being too
- particular about the kind of graphics API that is chosen.
- The suggested window type from the load-display config variable is
- tried first; if that fails, the first window type that can be
- successfully opened at all is accepted.
- This is intended to be called only once, at application startup.
- It is normally called automatically unless window-type is configured
- to 'none'.
- :returns: True on success, False on failure.
- """
- startDirect = kw.get('startDirect', True)
- if 'startDirect' in kw:
- del kw['startDirect']
- self.openMainWindow(*args, **kw)
- if startDirect:
- self.__doStartDirect()
- return self.win is not None
- def openMainWindow(self, *args, **kw):
- """
- Creates the initial, main window for the application, and sets
- up the mouse and render2d structures appropriately for it. If
- this method is called a second time, it will close the
- previous main window and open a new one, preserving the lens
- properties in base.camLens.
- :returns: True on success, or False on failure (in which case base.win
- may be either None, or the previous, closed window).
- """
- keepCamera = kw.get('keepCamera', False)
- success = 1
- oldWin = self.win
- oldLens = self.camLens
- oldClearColorActive = None
- if self.win is not None:
- # Close the previous window.
- oldClearColorActive = self.win.getClearColorActive()
- oldClearColor = VBase4(self.win.getClearColor())
- oldClearDepthActive = self.win.getClearDepthActive()
- oldClearDepth = self.win.getClearDepth()
- oldClearStencilActive = self.win.getClearStencilActive()
- oldClearStencil = self.win.getClearStencil()
- self.closeWindow(self.win, keepCamera = keepCamera)
- # Open a new window.
- self.openWindow(*args, **kw)
- if self.win is None:
- self.win = oldWin
- self.winList.append(oldWin)
- success = 0
- if self.win is not None:
- if isinstance(self.win, GraphicsWindow):
- self.setupMouse(self.win)
- self.makeCamera2d(self.win)
- if self.wantRender2dp:
- self.makeCamera2dp(self.win)
- if oldLens is not None:
- # Restore the previous lens properties.
- self.camNode.setLens(oldLens)
- self.camLens = oldLens
- if oldClearColorActive is not None:
- # Restore the previous clear properties.
- self.win.setClearColorActive(oldClearColorActive)
- self.win.setClearColor(oldClearColor)
- self.win.setClearDepthActive(oldClearDepthActive)
- self.win.setClearDepth(oldClearDepth)
- self.win.setClearStencilActive(oldClearStencilActive)
- self.win.setClearStencil(oldClearStencil)
- flag = ConfigVariableBool('show-frame-rate-meter', False)
- self.setFrameRateMeter(flag.value)
- flag = ConfigVariableBool('show-scene-graph-analyzer-meter', False)
- self.setSceneGraphAnalyzerMeter(flag.value)
- return success
- def setSleep(self, amount):
- """
- Sets up a task that calls python 'sleep' every frame. This is a simple
- way to reduce the CPU usage (and frame rate) of a panda program.
- """
- if self.clientSleep == amount:
- return
- self.clientSleep = amount
- if amount == 0.0:
- self.taskMgr.remove('clientSleep')
- else:
- # Spawn it after igloop (at the end of each frame)
- self.taskMgr.remove('clientSleep')
- self.taskMgr.add(self.__sleepCycleTask, 'clientSleep', sort = 55)
- def __sleepCycleTask(self, task):
- Thread.sleep(self.clientSleep)
- #time.sleep(self.clientSleep)
- return Task.cont
- def setFrameRateMeter(self, flag):
- """
- Turns on or off (according to flag) a standard frame rate
- meter in the upper-right corner of the main window.
- """
- if flag:
- if not self.frameRateMeter:
- self.frameRateMeter = FrameRateMeter('frameRateMeter')
- self.frameRateMeter.setupWindow(self.win)
- else:
- if self.frameRateMeter:
- self.frameRateMeter.clearWindow()
- self.frameRateMeter = None
- def setSceneGraphAnalyzerMeter(self, flag):
- """
- Turns on or off (according to flag) a standard frame rate
- meter in the upper-right corner of the main window.
- """
- if flag:
- if not self.sceneGraphAnalyzerMeter:
- self.sceneGraphAnalyzerMeter = SceneGraphAnalyzerMeter('sceneGraphAnalyzerMeter', self.render.node())
- self.sceneGraphAnalyzerMeter.setupWindow(self.win)
- else:
- if self.sceneGraphAnalyzerMeter:
- self.sceneGraphAnalyzerMeter.clearWindow()
- self.sceneGraphAnalyzerMeter = None
- # [gjeon] now you can add more winControls after creating a showbase instance
- def setupWindowControls(self, winCtrl=None):
- if winCtrl is None:
- winCtrl = WindowControls(
- self.win, mouseWatcher=self.mouseWatcher,
- cam=self.camera, camNode = self.camNode, cam2d=self.camera2d,
- mouseKeyboard = self.dataRoot.find("**/*"))
- self.winControls.append(winCtrl)
- def setupRender(self):
- """
- Creates the render scene graph, the primary scene graph for
- rendering 3-d geometry.
- """
- #: This is the root of the 3-D scene graph.
- self.render = NodePath('render')
- self.render.setAttrib(RescaleNormalAttrib.makeDefault())
- self.render.setTwoSided(0)
- self.backfaceCullingEnabled = 1
- self.textureEnabled = 1
- self.wireframeEnabled = 0
- def setupRender2d(self):
- """
- Creates the render2d scene graph, the primary scene graph for
- 2-d objects and gui elements that are superimposed over the
- 3-d geometry in the window.
- """
- # We've already created render2d and aspect2d in ShowBaseGlobal,
- # for the benefit of creating DirectGui elements before ShowBase.
- from . import ShowBaseGlobal
- #: This is the root of the 2-D scene graph.
- self.render2d = ShowBaseGlobal.render2d
- # Set up some overrides to turn off certain properties which
- # we probably won't need for 2-d objects.
- # It's probably important to turn off the depth test, since
- # many 2-d objects will be drawn over each other without
- # regard to depth position.
- # We used to avoid clearing the depth buffer before drawing
- # render2d, but nowadays we clear it anyway, since we
- # occasionally want to put 3-d geometry under render2d, and
- # it's simplest (and seems to be easier on graphics drivers)
- # if the 2-d scene has been cleared first.
- self.render2d.setDepthTest(0)
- self.render2d.setDepthWrite(0)
- self.render2d.setMaterialOff(1)
- self.render2d.setTwoSided(1)
- #: The normal 2-d DisplayRegion has an aspect ratio that
- #: matches the window, but its coordinate system is square.
- #: This means anything we parent to render2d gets stretched.
- #: For things where that makes a difference, we set up
- #: aspect2d, which scales things back to the right aspect
- #: ratio along the X axis (Z is still from -1 to 1)
- self.aspect2d = ShowBaseGlobal.aspect2d
- aspectRatio = self.getAspectRatio()
- self.aspect2d.setScale(1.0 / aspectRatio, 1.0, 1.0)
- self.a2dBackground = self.aspect2d.attachNewNode("a2dBackground")
- #: The Z position of the top border of the aspect2d screen.
- self.a2dTop = 1.0
- #: The Z position of the bottom border of the aspect2d screen.
- self.a2dBottom = -1.0
- #: The X position of the left border of the aspect2d screen.
- self.a2dLeft = -aspectRatio
- #: The X position of the right border of the aspect2d screen.
- self.a2dRight = aspectRatio
- self.a2dTopCenter = self.aspect2d.attachNewNode("a2dTopCenter")
- self.a2dTopCenterNs = self.aspect2d.attachNewNode("a2dTopCenterNS")
- self.a2dBottomCenter = self.aspect2d.attachNewNode("a2dBottomCenter")
- self.a2dBottomCenterNs = self.aspect2d.attachNewNode("a2dBottomCenterNS")
- self.a2dLeftCenter = self.aspect2d.attachNewNode("a2dLeftCenter")
- self.a2dLeftCenterNs = self.aspect2d.attachNewNode("a2dLeftCenterNS")
- self.a2dRightCenter = self.aspect2d.attachNewNode("a2dRightCenter")
- self.a2dRightCenterNs = self.aspect2d.attachNewNode("a2dRightCenterNS")
- self.a2dTopLeft = self.aspect2d.attachNewNode("a2dTopLeft")
- self.a2dTopLeftNs = self.aspect2d.attachNewNode("a2dTopLeftNS")
- self.a2dTopRight = self.aspect2d.attachNewNode("a2dTopRight")
- self.a2dTopRightNs = self.aspect2d.attachNewNode("a2dTopRightNS")
- self.a2dBottomLeft = self.aspect2d.attachNewNode("a2dBottomLeft")
- self.a2dBottomLeftNs = self.aspect2d.attachNewNode("a2dBottomLeftNS")
- self.a2dBottomRight = self.aspect2d.attachNewNode("a2dBottomRight")
- self.a2dBottomRightNs = self.aspect2d.attachNewNode("a2dBottomRightNS")
- # Put the nodes in their places
- self.a2dTopCenter.setPos(0, 0, self.a2dTop)
- self.a2dTopCenterNs.setPos(0, 0, self.a2dTop)
- self.a2dBottomCenter.setPos(0, 0, self.a2dBottom)
- self.a2dBottomCenterNs.setPos(0, 0, self.a2dBottom)
- self.a2dLeftCenter.setPos(self.a2dLeft, 0, 0)
- self.a2dLeftCenterNs.setPos(self.a2dLeft, 0, 0)
- self.a2dRightCenter.setPos(self.a2dRight, 0, 0)
- self.a2dRightCenterNs.setPos(self.a2dRight, 0, 0)
- self.a2dTopLeft.setPos(self.a2dLeft, 0, self.a2dTop)
- self.a2dTopLeftNs.setPos(self.a2dLeft, 0, self.a2dTop)
- self.a2dTopRight.setPos(self.a2dRight, 0, self.a2dTop)
- self.a2dTopRightNs.setPos(self.a2dRight, 0, self.a2dTop)
- self.a2dBottomLeft.setPos(self.a2dLeft, 0, self.a2dBottom)
- self.a2dBottomLeftNs.setPos(self.a2dLeft, 0, self.a2dBottom)
- self.a2dBottomRight.setPos(self.a2dRight, 0, self.a2dBottom)
- self.a2dBottomRightNs.setPos(self.a2dRight, 0, self.a2dBottom)
- #: This special root, pixel2d, uses units in pixels that are relative
- #: to the window. The upperleft corner of the window is (0, 0),
- #: the lowerleft corner is (xsize, -ysize), in this coordinate system.
- self.pixel2d = self.render2d.attachNewNode(PGTop("pixel2d"))
- self.pixel2d.setPos(-1, 0, 1)
- xsize, ysize = self.getSize()
- if xsize > 0 and ysize > 0:
- self.pixel2d.setScale(2.0 / xsize, 1.0, 2.0 / ysize)
- def setupRender2dp(self):
- """
- Creates a render2d scene graph, the secondary scene graph for
- 2-d objects and gui elements that are superimposed over the
- 2-d and 3-d geometry in the window.
- """
- self.render2dp = NodePath('render2dp')
- # Set up some overrides to turn off certain properties which
- # we probably won't need for 2-d objects.
- # It's probably important to turn off the depth test, since
- # many 2-d objects will be drawn over each other without
- # regard to depth position.
- dt = DepthTestAttrib.make(DepthTestAttrib.MNone)
- dw = DepthWriteAttrib.make(DepthWriteAttrib.MOff)
- self.render2dp.setDepthTest(0)
- self.render2dp.setDepthWrite(0)
- self.render2dp.setMaterialOff(1)
- self.render2dp.setTwoSided(1)
- #: The normal 2-d DisplayRegion has an aspect ratio that
- #: matches the window, but its coordinate system is square.
- #: This means anything we parent to render2dp gets stretched.
- #: For things where that makes a difference, we set up
- #: aspect2dp, which scales things back to the right aspect
- #: ratio along the X axis (Z is still from -1 to 1)
- self.aspect2dp = self.render2dp.attachNewNode(PGTop("aspect2dp"))
- self.aspect2dp.node().setStartSort(16384)
- aspectRatio = self.getAspectRatio()
- self.aspect2dp.setScale(1.0 / aspectRatio, 1.0, 1.0)
- #: The Z position of the top border of the aspect2dp screen.
- self.a2dpTop = 1.0
- #: The Z position of the bottom border of the aspect2dp screen.
- self.a2dpBottom = -1.0
- #: The X position of the left border of the aspect2dp screen.
- self.a2dpLeft = -aspectRatio
- #: The X position of the right border of the aspect2dp screen.
- self.a2dpRight = aspectRatio
- self.a2dpTopCenter = self.aspect2dp.attachNewNode("a2dpTopCenter")
- self.a2dpBottomCenter = self.aspect2dp.attachNewNode("a2dpBottomCenter")
- self.a2dpLeftCenter = self.aspect2dp.attachNewNode("a2dpLeftCenter")
- self.a2dpRightCenter = self.aspect2dp.attachNewNode("a2dpRightCenter")
- self.a2dpTopLeft = self.aspect2dp.attachNewNode("a2dpTopLeft")
- self.a2dpTopRight = self.aspect2dp.attachNewNode("a2dpTopRight")
- self.a2dpBottomLeft = self.aspect2dp.attachNewNode("a2dpBottomLeft")
- self.a2dpBottomRight = self.aspect2dp.attachNewNode("a2dpBottomRight")
- # Put the nodes in their places
- self.a2dpTopCenter.setPos(0, 0, self.a2dpTop)
- self.a2dpBottomCenter.setPos(0, 0, self.a2dpBottom)
- self.a2dpLeftCenter.setPos(self.a2dpLeft, 0, 0)
- self.a2dpRightCenter.setPos(self.a2dpRight, 0, 0)
- self.a2dpTopLeft.setPos(self.a2dpLeft, 0, self.a2dpTop)
- self.a2dpTopRight.setPos(self.a2dpRight, 0, self.a2dpTop)
- self.a2dpBottomLeft.setPos(self.a2dpLeft, 0, self.a2dpBottom)
- self.a2dpBottomRight.setPos(self.a2dpRight, 0, self.a2dpBottom)
- #: This special root, pixel2dp, uses units in pixels that are relative
- #: to the window. The upperleft corner of the window is (0, 0),
- #: the lowerleft corner is (xsize, -ysize), in this coordinate system.
- self.pixel2dp = self.render2dp.attachNewNode(PGTop("pixel2dp"))
- self.pixel2dp.node().setStartSort(16384)
- self.pixel2dp.setPos(-1, 0, 1)
- xsize, ysize = self.getSize()
- if xsize > 0 and ysize > 0:
- self.pixel2dp.setScale(2.0 / xsize, 1.0, 2.0 / ysize)
- def setAspectRatio(self, aspectRatio):
- """ Sets the global aspect ratio of the main window. Set it
- to None to restore automatic scaling. """
- self.__configAspectRatio = aspectRatio
- self.adjustWindowAspectRatio(self.getAspectRatio())
- def getAspectRatio(self, win = None):
- # Returns the actual aspect ratio of the indicated (or main
- # window), or the default aspect ratio if there is not yet a
- # main window.
- # If the config it set, we return that
- if self.__configAspectRatio:
- return self.__configAspectRatio
- aspectRatio = 1
- if win is None:
- win = self.win
- if win is not None and win.hasSize() and win.getSbsLeftYSize() != 0:
- aspectRatio = float(win.getSbsLeftXSize()) / float(win.getSbsLeftYSize())
- else:
- if win is None or not hasattr(win, "getRequestedProperties"):
- props = WindowProperties.getDefault()
- else:
- props = win.getRequestedProperties()
- if not props.hasSize():
- props = WindowProperties.getDefault()
- if props.hasSize() and props.getYSize() != 0:
- aspectRatio = float(props.getXSize()) / float(props.getYSize())
- if aspectRatio == 0:
- return 1
- return aspectRatio
- def getSize(self, win = None):
- """
- Returns the actual size of the indicated (or main window), or the
- default size if there is not yet a main window.
- """
- if win is None:
- win = self.win
- if win is not None and win.hasSize():
- return win.getXSize(), win.getYSize()
- else:
- if win is None or not hasattr(win, "getRequestedProperties"):
- props = WindowProperties.getDefault()
- else:
- props = win.getRequestedProperties()
- if not props.hasSize():
- props = WindowProperties.getDefault()
- return props.getXSize(), props.getYSize()
- def makeCamera(self, win, sort = 0, scene = None,
- displayRegion = (0, 1, 0, 1), stereo = None,
- aspectRatio = None, clearDepth = 0, clearColor = None,
- lens = None, camName = 'cam', mask = None,
- useCamera = None):
- """
- Makes a new 3-d camera associated with the indicated window,
- and creates a display region in the indicated subrectangle.
- If stereo is True, then a stereo camera is created, with a
- pair of DisplayRegions. If stereo is False, then a standard
- camera is created. If stereo is None or omitted, a stereo
- camera is created if the window says it can render in stereo.
- If useCamera is not None, it is a NodePath to be used as the
- camera to apply to the window, rather than creating a new
- camera.
- :rtype: panda3d.core.NodePath
- """
- # self.camera is the parent node of all cameras: a node that
- # we can move around to move all cameras as a group.
- if self.camera is None:
- # We make it a ModelNode with the PTLocal flag, so that
- # a wayward flatten operations won't attempt to mangle the
- # camera.
- self.camera = self.render.attachNewNode(ModelNode('camera'))
- self.camera.node().setPreserveTransform(ModelNode.PTLocal)
- builtins.camera = self.camera
- self.mouse2cam.node().setNode(self.camera.node())
- if useCamera:
- # Use the existing camera node.
- cam = useCamera
- camNode = useCamera.node()
- assert isinstance(camNode, Camera)
- lens = camNode.getLens()
- cam.reparentTo(self.camera)
- else:
- # Make a new Camera node.
- camNode = Camera(camName)
- if lens is None:
- lens = PerspectiveLens()
- if aspectRatio is None:
- aspectRatio = self.getAspectRatio(win)
- lens.setAspectRatio(aspectRatio)
- cam = self.camera.attachNewNode(camNode)
- if lens is not None:
- camNode.setLens(lens)
- if scene is not None:
- camNode.setScene(scene)
- if mask is not None:
- if isinstance(mask, int):
- mask = BitMask32(mask)
- camNode.setCameraMask(mask)
- if self.cam is None:
- self.cam = cam
- self.camNode = camNode
- self.camLens = lens
- self.camList.append(cam)
- # Now, make a DisplayRegion for the camera.
- if stereo is not None:
- if stereo:
- dr = win.makeStereoDisplayRegion(*displayRegion)
- else:
- dr = win.makeMonoDisplayRegion(*displayRegion)
- else:
- dr = win.makeDisplayRegion(*displayRegion)
- dr.setSort(sort)
- # By default, we do not clear 3-d display regions (the entire
- # window will be cleared, which is normally sufficient). But
- # we will if clearDepth is specified.
- if clearDepth:
- dr.setClearDepthActive(1)
- if clearColor:
- dr.setClearColorActive(1)
- dr.setClearColor(clearColor)
- dr.setCamera(cam)
- return cam
- def makeCamera2d(self, win, sort = 10,
- displayRegion = (0, 1, 0, 1), coords = (-1, 1, -1, 1),
- lens = None, cameraName = None):
- """
- Makes a new camera2d associated with the indicated window, and
- assigns it to render the indicated subrectangle of render2d.
- :rtype: panda3d.core.NodePath
- """
- dr = win.makeMonoDisplayRegion(*displayRegion)
- dr.setSort(sort)
- # Enable clearing of the depth buffer on this new display
- # region (see the comment in setupRender2d, above).
- dr.setClearDepthActive(1)
- # Make any texture reloads on the gui come up immediately.
- dr.setIncompleteRender(False)
- left, right, bottom, top = coords
- # Now make a new Camera node.
- if cameraName:
- cam2dNode = Camera('cam2d_' + cameraName)
- else:
- cam2dNode = Camera('cam2d')
- if lens is None:
- lens = OrthographicLens()
- lens.setFilmSize(right - left, top - bottom)
- lens.setFilmOffset((right + left) * 0.5, (top + bottom) * 0.5)
- lens.setNearFar(-1000, 1000)
- cam2dNode.setLens(lens)
- # self.camera2d is the analog of self.camera, although it's
- # not as clear how useful it is.
- if self.camera2d is None:
- self.camera2d = self.render2d.attachNewNode('camera2d')
- camera2d = self.camera2d.attachNewNode(cam2dNode)
- dr.setCamera(camera2d)
- if self.cam2d is None:
- self.cam2d = camera2d
- return camera2d
- def makeCamera2dp(self, win, sort = 20,
- displayRegion = (0, 1, 0, 1), coords = (-1, 1, -1, 1),
- lens = None, cameraName = None):
- """
- Makes a new camera2dp associated with the indicated window, and
- assigns it to render the indicated subrectangle of render2dp.
- :rtype: panda3d.core.NodePath
- """
- dr = win.makeMonoDisplayRegion(*displayRegion)
- dr.setSort(sort)
- # Unlike render2d, we don't clear the depth buffer for
- # render2dp. Caveat emptor.
- if hasattr(dr, 'setIncompleteRender'):
- dr.setIncompleteRender(False)
- left, right, bottom, top = coords
- # Now make a new Camera node.
- if cameraName:
- cam2dNode = Camera('cam2dp_' + cameraName)
- else:
- cam2dNode = Camera('cam2dp')
- if lens is None:
- lens = OrthographicLens()
- lens.setFilmSize(right - left, top - bottom)
- lens.setFilmOffset((right + left) * 0.5, (top + bottom) * 0.5)
- lens.setNearFar(-1000, 1000)
- cam2dNode.setLens(lens)
- # self.camera2d is the analog of self.camera, although it's
- # not as clear how useful it is.
- if self.camera2dp is None:
- self.camera2dp = self.render2dp.attachNewNode('camera2dp')
- camera2dp = self.camera2dp.attachNewNode(cam2dNode)
- dr.setCamera(camera2dp)
- if self.cam2dp is None:
- self.cam2dp = camera2dp
- return camera2dp
- def setupDataGraph(self):
- """
- Creates the data graph and populates it with the basic input
- devices.
- """
- self.dataRoot = NodePath('dataRoot')
- # Cache the node so we do not ask for it every frame
- self.dataRootNode = self.dataRoot.node()
- # Now we have the main trackball & drive interfaces.
- # useTrackball() and useDrive() switch these in and out; only
- # one is in use at a given time.
- self.trackball = NodePath(Trackball('trackball'))
- self.drive = NodePath(DriveInterface('drive'))
- self.mouse2cam = NodePath(Transform2SG('mouse2cam'))
- # [gjeon] now you can create multiple mouse watchers to support multiple windows
- def setupMouse(self, win, fMultiWin=False):
- """
- Creates the structures necessary to monitor the mouse input,
- using the indicated window. If the mouse has already been set
- up for a different window, those structures are deleted first.
- :param fMultiWin: If True, then the previous mouse structures are not
- deleted; instead, multiple windows are allowed to
- monitor the mouse input. However, in this case, the
- trackball controls are not set up, and must be set up
- by hand if desired.
- :returns: The ButtonThrower NodePath created for this window.
- """
- if not fMultiWin and self.buttonThrowers is not None:
- for bt in self.buttonThrowers:
- mw = bt.getParent()
- mk = mw.getParent()
- bt.removeNode()
- mw.removeNode()
- mk.removeNode()
- bts, pws = self.setupMouseCB(win)
- if fMultiWin:
- return bts[0]
- self.buttonThrowers = bts[:]
- self.pointerWatcherNodes = pws[:]
- self.mouseWatcher = self.buttonThrowers[0].getParent()
- self.mouseWatcherNode = self.mouseWatcher.node()
- if self.mouseInterface:
- self.mouseInterface.reparentTo(self.mouseWatcher)
- if self.recorder:
- # If we have a recorder, the mouseWatcher belongs under a
- # special MouseRecorder node, which may intercept the
- # mouse activity.
- mw = self.buttonThrowers[0].getParent()
- mouseRecorder = MouseRecorder('mouse')
- self.recorder.addRecorder('mouse', mouseRecorder)
- np = mw.getParent().attachNewNode(mouseRecorder)
- mw.reparentTo(np)
- mw = self.buttonThrowers[0].getParent()
- #: A special ButtonThrower to generate keyboard events and
- #: include the time from the OS. This is separate only to
- #: support legacy code that did not expect a time parameter; it
- #: will eventually be folded into the normal ButtonThrower,
- #: above.
- self.timeButtonThrower = mw.attachNewNode(ButtonThrower('timeButtons'))
- self.timeButtonThrower.node().setPrefix('time-')
- self.timeButtonThrower.node().setTimeFlag(1)
- # Tell the gui system about our new mouse watcher.
- self.aspect2d.node().setMouseWatcher(mw.node())
- self.pixel2d.node().setMouseWatcher(mw.node())
- if self.wantRender2dp:
- self.aspect2dp.node().setMouseWatcher(mw.node())
- self.pixel2dp.node().setMouseWatcher(mw.node())
- mw.node().addRegion(PGMouseWatcherBackground())
- return self.buttonThrowers[0]
- # [gjeon] this function is seperated from setupMouse to allow multiple mouse watchers
- def setupMouseCB(self, win):
- # For each mouse/keyboard device, we create
- # - MouseAndKeyboard
- # - MouseWatcher
- # - ButtonThrower
- # The ButtonThrowers are stored in a list, self.buttonThrowers.
- # Given a ButtonThrower, one can access the MouseWatcher and
- # MouseAndKeyboard using getParent.
- #
- # The MouseAndKeyboard generates mouse events and mouse
- # button/keyboard events; the MouseWatcher passes them through
- # unchanged when the mouse is not over a 2-d button, and passes
- # nothing through when the mouse *is* over a 2-d button. Therefore,
- # objects that don't want to get events when the mouse is over a
- # button, like the driveInterface, should be parented to
- # MouseWatcher, while objects that want events in all cases, like the
- # chat interface, should be parented to the MouseAndKeyboard.
- buttonThrowers = []
- pointerWatcherNodes = []
- for i in range(win.getNumInputDevices()):
- name = win.getInputDeviceName(i)
- mk = self.dataRoot.attachNewNode(MouseAndKeyboard(win, i, name))
- mw = mk.attachNewNode(MouseWatcher("watcher%s" % (i)))
- if win.getSideBySideStereo():
- # If the window has side-by-side stereo enabled, then
- # we should constrain the MouseWatcher to the window's
- # DisplayRegion. This will enable the MouseWatcher to
- # track the left and right halves of the screen
- # individually.
- mw.node().setDisplayRegion(win.getOverlayDisplayRegion())
- mb = mw.node().getModifierButtons()
- mb.addButton(KeyboardButton.shift())
- mb.addButton(KeyboardButton.control())
- mb.addButton(KeyboardButton.alt())
- mb.addButton(KeyboardButton.meta())
- mw.node().setModifierButtons(mb)
- bt = mw.attachNewNode(ButtonThrower("buttons%s" % (i)))
- if i != 0:
- bt.node().setPrefix('mousedev%s-' % (i))
- mods = ModifierButtons()
- mods.addButton(KeyboardButton.shift())
- mods.addButton(KeyboardButton.control())
- mods.addButton(KeyboardButton.alt())
- mods.addButton(KeyboardButton.meta())
- bt.node().setModifierButtons(mods)
- buttonThrowers.append(bt)
- if win.hasPointer(i):
- pointerWatcherNodes.append(mw.node())
- return buttonThrowers, pointerWatcherNodes
- def enableSoftwareMousePointer(self):
- """
- Creates some geometry and parents it to render2d to show
- the currently-known mouse position. Useful if the mouse
- pointer is invisible for some reason.
- """
- mouseViz = self.render2d.attachNewNode('mouseViz')
- lilsmiley = self.loader.loadModel('lilsmiley')
- lilsmiley.reparentTo(mouseViz)
- aspectRatio = self.getAspectRatio()
- # Scale the smiley face to 32x32 pixels.
- height = self.win.getSbsLeftYSize()
- lilsmiley.setScale(
- 32.0 / height / aspectRatio,
- 1.0, 32.0 / height)
- self.mouseWatcherNode.setGeometry(mouseViz.node())
- def getAlt(self):
- """
- Returns True if the alt key is currently held down.
- """
- return self.mouseWatcherNode.getModifierButtons().isDown(
- KeyboardButton.alt())
- def getShift(self):
- """
- Returns True if the shift key is currently held down.
- """
- return self.mouseWatcherNode.getModifierButtons().isDown(
- KeyboardButton.shift())
- def getControl(self):
- """
- Returns True if the control key is currently held down.
- """
- return self.mouseWatcherNode.getModifierButtons().isDown(
- KeyboardButton.control())
- def getMeta(self):
- """
- Returns True if the meta key is currently held down.
- """
- return self.mouseWatcherNode.getModifierButtons().isDown(
- KeyboardButton.meta())
- def attachInputDevice(self, device, prefix=None, watch=False):
- """
- This function attaches an input device to the data graph, which will
- cause the device to be polled and generate events. If a prefix is
- given and not None, it is used to prefix events generated by this
- device, separated by a hyphen.
- The watch argument can be set to True (as of Panda3D 1.10.3) to set up
- the default MouseWatcher to receive inputs from this device, allowing
- it to be polled via mouseWatcherNode and control user interfaces.
- Setting this to True will also make it generate unprefixed events,
- regardless of the specified prefix.
- If you call this, you should consider calling detachInputDevice when
- you are done with the device or when it is disconnected.
- """
- # Protect against the same device being attached multiple times.
- assert device not in self.__inputDeviceNodes
- idn = self.dataRoot.attachNewNode(InputDeviceNode(device, device.name))
- # Setup the button thrower to generate events for the device.
- if prefix is not None or not watch:
- bt = idn.attachNewNode(ButtonThrower(device.name))
- if prefix is not None:
- bt.node().setPrefix(prefix + '-')
- self.deviceButtonThrowers.append(bt)
- assert self.notify.debug("Attached input device {0} with prefix {1}".format(device, prefix))
- self.__inputDeviceNodes[device] = idn
- if watch:
- idn.node().addChild(self.mouseWatcherNode)
- def detachInputDevice(self, device):
- """
- This should be called after attaching an input device using
- attachInputDevice and the device is disconnected or you no longer wish
- to keep polling this device for events.
- You do not strictly need to call this if you expect the device to be
- reconnected (but be careful that you don't reattach it).
- """
- if device not in self.__inputDeviceNodes:
- assert device in self.__inputDeviceNodes
- return
- assert self.notify.debug("Detached device {0}".format(device.name))
- # Remove the ButtonThrower from the deviceButtonThrowers list.
- idn = self.__inputDeviceNodes[device]
- for bt in self.deviceButtonThrowers:
- if idn.isAncestorOf(bt):
- self.deviceButtonThrowers.remove(bt)
- break
- idn.removeNode()
- del self.__inputDeviceNodes[device]
- def addAngularIntegrator(self):
- """
- Adds a :class:`~panda3d.physics.AngularEulerIntegrator` to the default
- physics manager. By default, only a
- :class:`~panda3d.physics.LinearEulerIntegrator` is attached.
- """
- if not self.physicsMgrAngular:
- physics = importlib.import_module('panda3d.physics')
- self.physicsMgrAngular = 1
- integrator = physics.AngularEulerIntegrator()
- self.physicsMgr.attachAngularIntegrator(integrator)
- def enableParticles(self):
- """
- Enables the particle and physics managers, which are stored in
- `particleMgr` and `physicsMgr` members, respectively. Also starts a
- task to periodically update these managers.
- By default, only a :class:`~panda3d.physics.LinearEulerIntegrator` is
- attached to the physics manager. To attach an angular integrator,
- follow this up with a call to `addAngularIntegrator()`.
- """
- if not self.particleMgrEnabled:
- # Use importlib to prevent this import from being picked up
- # by modulefinder when packaging an application.
- if not self.particleMgr:
- PMG = importlib.import_module('direct.particles.ParticleManagerGlobal')
- self.particleMgr = PMG.particleMgr
- self.particleMgr.setFrameStepping(1)
- if not self.physicsMgr:
- PMG = importlib.import_module('direct.showbase.PhysicsManagerGlobal')
- physics = importlib.import_module('panda3d.physics')
- self.physicsMgr = PMG.physicsMgr
- integrator = physics.LinearEulerIntegrator()
- self.physicsMgr.attachLinearIntegrator(integrator)
- self.particleMgrEnabled = 1
- self.physicsMgrEnabled = 1
- self.taskMgr.remove('manager-update')
- self.taskMgr.add(self.updateManagers, 'manager-update')
- def disableParticles(self):
- """
- The opposite of `enableParticles()`.
- """
- if self.particleMgrEnabled:
- self.particleMgrEnabled = 0
- self.physicsMgrEnabled = 0
- self.taskMgr.remove('manager-update')
- def toggleParticles(self):
- """
- Calls `enableParticles()` or `disableParticles()` depending on the
- current state.
- """
- if self.particleMgrEnabled == 0:
- self.enableParticles()
- else:
- self.disableParticles()
- def isParticleMgrEnabled(self):
- """
- Returns True if `enableParticles()` has been called.
- """
- return self.particleMgrEnabled
- def isPhysicsMgrEnabled(self):
- """
- Returns True if `enableParticles()` has been called.
- """
- return self.physicsMgrEnabled
- def updateManagers(self, state):
- dt = self.clock.dt
- if self.particleMgrEnabled:
- self.particleMgr.doParticles(dt)
- if self.physicsMgrEnabled:
- self.physicsMgr.doPhysics(dt)
- return Task.cont
- def createStats(self, hostname=None, port=None):
- """
- If want-pstats is set in Config.prc, or the `wantStats` member is
- otherwise set to True, connects to the PStats server.
- This is normally called automatically from the ShowBase constructor.
- """
- # You can specify pstats-host in your Config.prc or use ~pstats/~aipstats
- # The default is localhost
- if not self.wantStats:
- return False
- if PStatClient.isConnected():
- PStatClient.disconnect()
- # these default values match the C++ default values
- if hostname is None:
- hostname = ''
- if port is None:
- port = -1
- PStatClient.connect(hostname, port)
- if PStatClient.isConnected():
- PStatClient.mainTick()
- return True
- else:
- return False
- def addSfxManager(self, extraSfxManager):
- """
- Adds an additional SFX audio manager to `sfxManagerList`, the list of
- managers managed by ShowBase.
- """
- # keep a list of sfx manager objects to apply settings to,
- # since there may be others in addition to the one we create here
- self.sfxManagerList.append(extraSfxManager)
- newSfxManagerIsValid = extraSfxManager is not None and extraSfxManager.isValid()
- self.sfxManagerIsValidList.append(newSfxManagerIsValid)
- if newSfxManagerIsValid:
- extraSfxManager.setActive(self.sfxActive)
- def createBaseAudioManagers(self):
- """
- Creates the default SFX and music manager. Called automatically from
- the ShowBase constructor.
- """
- self.sfxPlayer = SfxPlayer.SfxPlayer()
- sfxManager = AudioManager.createAudioManager()
- self.addSfxManager(sfxManager)
- self.musicManager = AudioManager.createAudioManager()
- self.musicManagerIsValid = self.musicManager is not None \
- and self.musicManager.isValid()
- if self.musicManagerIsValid:
- # ensure only 1 midi song is playing at a time:
- self.musicManager.setConcurrentSoundLimit(1)
- self.musicManager.setActive(self.musicActive)
- # enableMusic/enableSoundEffects are meant to be called in response
- # to a user request so sfxActive/musicActive represent how things
- # *should* be, regardless of App/OS/HW state
- def enableMusic(self, bEnableMusic):
- """
- Enables or disables the music manager.
- """
- # don't setActive(1) if no audiofocus
- if self.AppHasAudioFocus and self.musicManagerIsValid:
- self.musicManager.setActive(bEnableMusic)
- self.musicActive = bEnableMusic
- if bEnableMusic:
- # This is useful when we want to play different music
- # from what the manager has queued
- messenger.send("MusicEnabled")
- self.notify.debug("Enabling music")
- else:
- self.notify.debug("Disabling music")
- def SetAllSfxEnables(self, bEnabled):
- """Calls ``setActive(bEnabled)`` on all valid SFX managers."""
- for i in range(len(self.sfxManagerList)):
- if self.sfxManagerIsValidList[i]:
- self.sfxManagerList[i].setActive(bEnabled)
- def enableSoundEffects(self, bEnableSoundEffects):
- """
- Enables or disables SFX managers.
- """
- # don't setActive(1) if no audiofocus
- if self.AppHasAudioFocus or not bEnableSoundEffects:
- self.SetAllSfxEnables(bEnableSoundEffects)
- self.sfxActive = bEnableSoundEffects
- if bEnableSoundEffects:
- self.notify.debug("Enabling sound effects")
- else:
- self.notify.debug("Disabling sound effects")
- # enable/disableAllAudio allow a programmable global override-off
- # for current audio settings. they're meant to be called when app
- # loses audio focus (switched out), so we can turn off sound without
- # affecting internal sfxActive/musicActive sound settings, so things
- # come back ok when the app is switched back to
- def disableAllAudio(self):
- """
- Disables all SFX and music managers, meant to be called when the app
- loses audio focus.
- """
- self.AppHasAudioFocus = 0
- self.SetAllSfxEnables(0)
- if self.musicManagerIsValid:
- self.musicManager.setActive(0)
- self.notify.debug("Disabling audio")
- def enableAllAudio(self):
- """
- Reenables the SFX and music managers that were active at the time
- `disableAllAudio()` was called. Meant to be called when the app regains
- audio focus.
- """
- self.AppHasAudioFocus = 1
- self.SetAllSfxEnables(self.sfxActive)
- if self.musicManagerIsValid:
- self.musicManager.setActive(self.musicActive)
- self.notify.debug("Enabling audio")
- # This function should only be in the loader but is here for
- # backwards compatibility. Please do not add code here, add
- # it to the loader.
- def loadSfx(self, name):
- """
- :deprecated: Use `.Loader.Loader.loadSfx()` instead.
- """
- if __debug__:
- warnings.warn("base.loadSfx is deprecated, use base.loader.loadSfx instead.", DeprecationWarning, stacklevel=2)
- return self.loader.loadSfx(name)
- # This function should only be in the loader but is here for
- # backwards compatibility. Please do not add code here, add
- # it to the loader.
- def loadMusic(self, name):
- """
- :deprecated: Use `.Loader.Loader.loadMusic()` instead.
- """
- if __debug__:
- warnings.warn("base.loadMusic is deprecated, use base.loader.loadMusic instead.", DeprecationWarning, stacklevel=2)
- return self.loader.loadMusic(name)
- def playSfx(
- self, sfx, looping = 0, interrupt = 1, volume = None,
- time = 0.0, node = None, listener = None, cutoff = None):
- # This goes through a special player for potential localization
- return self.sfxPlayer.playSfx(sfx, looping, interrupt, volume, time, node, listener, cutoff)
- def playMusic(self, music, looping = 0, interrupt = 1, volume = None, time = 0.0):
- if music:
- if volume is not None:
- music.setVolume(volume)
- # if interrupt was set to 0, start over even if it's
- # already playing
- if interrupt or (music.status() != AudioSound.PLAYING):
- music.setTime(time)
- music.setLoop(looping)
- music.play()
- def __resetPrevTransform(self, state):
- # Clear out the previous velocity deltas now, after we have
- # rendered (the previous frame). We do this after the render,
- # so that we have a chance to draw a representation of spheres
- # along with their velocities. At the beginning of the frame
- # really means after the command prompt, which allows the user
- # to interactively query these deltas meaningfully.
- PandaNode.resetAllPrevTransform()
- return Task.cont
- def __dataLoop(self, state):
- # Check if there were newly connected devices.
- self.devices.update()
- # traverse the data graph. This reads all the control
- # inputs (from the mouse and keyboard, for instance) and also
- # directly acts upon them (for instance, to move the avatar).
- self.dgTrav.traverse(self.dataRootNode)
- return Task.cont
- def __ivalLoop(self, state):
- # Execute all intervals in the global ivalMgr.
- IntervalManager.ivalMgr.step()
- return Task.cont
- def initShadowTrav(self):
- if not self.shadowTrav:
- # set up the shadow collision traverser
- self.shadowTrav = CollisionTraverser("base.shadowTrav")
- self.shadowTrav.setRespectPrevTransform(False)
- def __shadowCollisionLoop(self, state):
- # run the collision traversal if we have a
- # CollisionTraverser set.
- if self.shadowTrav:
- self.shadowTrav.traverse(self.render)
- return Task.cont
- def __collisionLoop(self, state):
- # run the collision traversal if we have a
- # CollisionTraverser set.
- if self.cTrav:
- self.cTrav.traverse(self.render)
- if self.appTrav:
- self.appTrav.traverse(self.render)
- if self.shadowTrav:
- self.shadowTrav.traverse(self.render)
- messenger.send("collisionLoopFinished")
- return Task.cont
- def __audioLoop(self, state):
- if self.musicManager is not None:
- self.musicManager.update()
- for x in self.sfxManagerList:
- x.update()
- return Task.cont
- def __garbageCollectStates(self, state):
- """ This task is started only when we have
- garbage-collect-states set in the Config.prc file, in which
- case we're responsible for taking out Panda's garbage from
- time to time. This is not to be confused with Python's
- garbage collection. """
- TransformState.garbageCollect()
- RenderState.garbageCollect()
- return Task.cont
- def __igLoop(self, state):
- if __debug__:
- # We render the watch variables for the onScreenDebug as soon
- # as we reasonably can before the renderFrame().
- self.onScreenDebug.render()
- if self.recorder:
- self.recorder.recordFrame()
- # Finally, render the frame.
- self.graphicsEngine.renderFrame()
- if self.clusterSyncFlag:
- self.graphicsEngine.syncFrame()
- if self.multiClientSleep:
- time.sleep(0)
- if __debug__:
- # We clear the text buffer for the onScreenDebug as soon
- # as we reasonably can after the renderFrame().
- self.onScreenDebug.clear()
- if self.recorder:
- self.recorder.playFrame()
- if self.mainWinMinimized:
- # If the main window is minimized, slow down the app a bit
- # by sleeping here in igLoop so we don't use all available
- # CPU needlessly.
- # Note: this isn't quite right if multiple windows are
- # open. We should base this on whether *all* windows are
- # minimized, not just the main window. But it will do for
- # now until someone complains.
- time.sleep(0.1)
- # Lerp stuff needs this event, and it must be generated in
- # C++, not in Python.
- throw_new_frame()
- return Task.cont
- def __igLoopSync(self, state):
- if __debug__:
- # We render the watch variables for the onScreenDebug as soon
- # as we reasonably can before the renderFrame().
- self.onScreenDebug.render()
- if self.recorder:
- self.recorder.recordFrame()
- self.cluster.collectData()
- # Finally, render the frame.
- self.graphicsEngine.renderFrame()
- if self.clusterSyncFlag:
- self.graphicsEngine.syncFrame()
- if self.multiClientSleep:
- time.sleep(0)
- if __debug__:
- # We clear the text buffer for the onScreenDebug as soon
- # as we reasonably can after the renderFrame().
- self.onScreenDebug.clear()
- if self.recorder:
- self.recorder.playFrame()
- if self.mainWinMinimized:
- # If the main window is minimized, slow down the app a bit
- # by sleeping here in igLoop so we don't use all available
- # CPU needlessly.
- # Note: this isn't quite right if multiple windows are
- # open. We should base this on whether *all* windows are
- # minimized, not just the main window. But it will do for
- # now until someone complains.
- time.sleep(0.1)
- self.graphicsEngine.readyFlip()
- self.cluster.waitForFlipCommand()
- self.graphicsEngine.flipFrame()
- # Lerp stuff needs this event, and it must be generated in
- # C++, not in Python.
- throw_new_frame()
- return Task.cont
- def restart(self, clusterSync=False, cluster=None):
- self.shutdown()
- # __resetPrevTransform goes at the very beginning of the frame.
- self.taskMgr.add(
- self.__resetPrevTransform, 'resetPrevTransform', sort = -51)
- # give the dataLoop task a reasonably "early" sort,
- # so that it will get run before most tasks
- self.taskMgr.add(self.__dataLoop, 'dataLoop', sort = -50)
- self.__deadInputs = 0
- # spawn the ivalLoop with a later sort, so that it will
- # run after most tasks, but before igLoop.
- self.taskMgr.add(self.__ivalLoop, 'ivalLoop', sort = 20)
- # make the collisionLoop task run before igLoop,
- # but leave enough room for the app to insert tasks
- # between collisionLoop and igLoop
- self.taskMgr.add(self.__collisionLoop, 'collisionLoop', sort = 30)
- if ConfigVariableBool('garbage-collect-states').value:
- self.taskMgr.add(self.__garbageCollectStates, 'garbageCollectStates', sort = 46)
- # give the igLoop task a reasonably "late" sort,
- # so that it will get run after most tasks
- self.cluster = cluster
- if not clusterSync or cluster is None:
- self.taskMgr.add(self.__igLoop, 'igLoop', sort = 50)
- else:
- self.taskMgr.add(self.__igLoopSync, 'igLoop', sort = 50)
- # the audioLoop updates the positions of 3D sounds.
- # as such, it needs to run after the cull traversal in the igLoop.
- self.taskMgr.add(self.__audioLoop, 'audioLoop', sort = 60)
- self.eventMgr.restart()
- def shutdown(self):
- self.taskMgr.remove('audioLoop')
- self.taskMgr.remove('igLoop')
- self.taskMgr.remove('shadowCollisionLoop')
- self.taskMgr.remove('collisionLoop')
- self.taskMgr.remove('dataLoop')
- self.taskMgr.remove('resetPrevTransform')
- self.taskMgr.remove('ivalLoop')
- self.taskMgr.remove('garbageCollectStates')
- self.eventMgr.shutdown()
- def getBackgroundColor(self, win = None):
- """
- Returns the current window background color. This assumes
- the window is set up to clear the color each frame (this is
- the normal setting).
- :rtype: panda3d.core.VBase4
- """
- if win is None:
- win = self.win
- return VBase4(win.getClearColor())
- def setBackgroundColor(self, r = None, g = None, b = None, a = 0.0, win = None):
- """
- Sets the window background color to the indicated value.
- This assumes the window is set up to clear the color each
- frame (this is the normal setting).
- The color may be either a VBase3 or a VBase4, or a 3-component
- tuple, or the individual r, g, b parameters.
- """
- if g is not None:
- color = VBase4(r, g, b, a)
- else:
- arg = r
- if isinstance(arg, VBase4):
- color = arg
- else:
- color = VBase4(arg[0], arg[1], arg[2], a)
- if win is None:
- win = self.win
- if win:
- win.setClearColor(color)
- def toggleBackface(self):
- """
- Toggles between `backfaceCullingOn()` and `backfaceCullingOff()`.
- """
- if self.backfaceCullingEnabled:
- self.backfaceCullingOff()
- else:
- self.backfaceCullingOn()
- def backfaceCullingOn(self):
- """
- Disables two-sided rendering on the entire 3D scene graph.
- """
- if not self.backfaceCullingEnabled:
- self.render.setTwoSided(0)
- self.backfaceCullingEnabled = 1
- def backfaceCullingOff(self):
- """
- Enables two-sided rendering on the entire 3D scene graph.
- """
- if self.backfaceCullingEnabled:
- self.render.setTwoSided(1)
- self.backfaceCullingEnabled = 0
- def toggleTexture(self):
- """
- Toggles between `textureOn()` and `textureOff()`.
- """
- if self.textureEnabled:
- self.textureOff()
- else:
- self.textureOn()
- def textureOn(self):
- """
- Undoes the effects of a previous call to `textureOff()`.
- """
- self.render.clearTexture()
- self.textureEnabled = 1
- def textureOff(self):
- """
- Disables texturing on the entire 3D scene graph.
- """
- self.render.setTextureOff(100)
- self.textureEnabled = 0
- def toggleWireframe(self):
- """
- Toggles between `wireframeOn()` and `wireframeOff()`.
- """
- if self.wireframeEnabled:
- self.wireframeOff()
- else:
- self.wireframeOn()
- def wireframeOn(self):
- """
- Enables wireframe rendering on the entire 3D scene graph.
- """
- self.render.setRenderModeWireframe(100)
- self.render.setTwoSided(1)
- self.wireframeEnabled = 1
- def wireframeOff(self):
- """
- Undoes the effects of a previous call to `wireframeOn()`.
- """
- self.render.clearRenderMode()
- render.setTwoSided(not self.backfaceCullingEnabled)
- self.wireframeEnabled = 0
- def disableMouse(self):
- """
- Temporarily disable the mouse control of the camera, either
- via the drive interface or the trackball, whichever is
- currently in use.
- """
- # We don't reparent the drive interface or the trackball;
- # whichever one was there before will remain in the data graph
- # and active. This way they won't lose button events while
- # the mouse is disabled. However, we do move the mouse2cam
- # object out of there, so we won't be updating the camera any
- # more.
- if self.mouse2cam:
- self.mouse2cam.detachNode()
- def enableMouse(self):
- """
- Reverse the effect of a previous call to `disableMouse()`.
- `useDrive()` also implicitly enables the mouse.
- """
- if self.mouse2cam:
- self.mouse2cam.reparentTo(self.mouseInterface)
- def silenceInput(self):
- """
- This is a heavy-handed way of temporarily turning off
- all inputs. Bring them back with `reviveInput()`.
- """
- if not self.__deadInputs:
- self.__deadInputs = taskMgr.remove('dataLoop')
- def reviveInput(self):
- """
- Restores inputs after a previous call to `silenceInput()`.
- """
- if self.__deadInputs:
- self.eventMgr.doEvents()
- self.dgTrav.traverse(self.dataRootNode)
- self.eventMgr.eventQueue.clear()
- self.taskMgr.add(self.__dataLoop, 'dataLoop', sort = -50)
- self.__deadInputs = 0
- def setMouseOnNode(self, newNode):
- if self.mouse2cam:
- self.mouse2cam.node().setNode(newNode)
- def changeMouseInterface(self, changeTo):
- """
- Change the mouse interface used to control the camera.
- """
- # Get rid of the prior interface:
- self.mouseInterface.detachNode()
- # Update the mouseInterface to point to the drive
- self.mouseInterface = changeTo
- self.mouseInterfaceNode = self.mouseInterface.node()
- # Hookup the drive to the camera.
- if self.mouseWatcher:
- self.mouseInterface.reparentTo(self.mouseWatcher)
- if self.mouse2cam:
- self.mouse2cam.reparentTo(self.mouseInterface)
- def useDrive(self):
- """
- Changes the mouse interface used for camera control to drive mode.
- """
- if self.drive:
- self.changeMouseInterface(self.drive)
- # Set the height to a good eyeheight
- self.mouseInterfaceNode.reset()
- self.mouseInterfaceNode.setZ(4.0)
- def useTrackball(self):
- """
- Changes the mouse interface used for camera control to trackball mode.
- """
- if self.trackball:
- self.changeMouseInterface(self.trackball)
- def toggleTexMem(self):
- """
- Toggles a handy texture memory watcher utility.
- See :mod:`direct.showutil.TexMemWatcher` for more information.
- """
- if self.texmem and not self.texmem.cleanedUp:
- self.texmem.cleanup()
- self.texmem = None
- return
- # Use importlib to prevent this import from being picked up
- # by modulefinder when packaging an application.
- TMW = importlib.import_module('direct.showutil.TexMemWatcher')
- self.texmem = TMW.TexMemWatcher()
- def toggleShowVertices(self):
- """ Toggles a mode that visualizes vertex density per screen
- area. """
- if self.showVertices:
- # Clean up the old mode.
- self.showVertices.node().setActive(0)
- dr = self.showVertices.node().getDisplayRegion(0)
- self.win.removeDisplayRegion(dr)
- self.showVertices.removeNode()
- self.showVertices = None
- return
- dr = self.win.makeDisplayRegion()
- dr.setSort(1000)
- cam = Camera('showVertices')
- cam.setLens(self.camLens)
- # Set up a funny state to render only vertices.
- override = 100000
- t = NodePath('t')
- t.setColor(1, 0, 1, 0.02, override)
- t.setColorScale(1, 1, 1, 1, override)
- t.setAttrib(ColorBlendAttrib.make(ColorBlendAttrib.MAdd, ColorBlendAttrib.OIncomingAlpha, ColorBlendAttrib.OOneMinusIncomingAlpha), override)
- t.setAttrib(RenderModeAttrib.make(RenderModeAttrib.MPoint, 10), override)
- t.setTwoSided(True, override)
- t.setBin('fixed', 0, override)
- t.setDepthTest(False, override)
- t.setDepthWrite(False, override)
- t.setLightOff(override)
- t.setShaderOff(override)
- t.setFogOff(override)
- t.setAttrib(AntialiasAttrib.make(AntialiasAttrib.MNone), override)
- t.setAttrib(RescaleNormalAttrib.make(RescaleNormalAttrib.MNone), override)
- t.setTextureOff(override)
- # Make the spots round, so there's less static in the display.
- # This forces software point generation on many drivers, so
- # it's not on by default.
- if ConfigVariableBool('round-show-vertices', False):
- spot = PNMImage(256, 256, 1)
- spot.renderSpot((1, 1, 1, 1), (0, 0, 0, 0), 0.8, 1)
- tex = Texture('spot')
- tex.load(spot)
- tex.setFormat(tex.FAlpha)
- t.setTexture(tex, override)
- t.setAttrib(TexGenAttrib.make(TextureStage.getDefault(), TexGenAttrib.MPointSprite), override)
- cam.setInitialState(t.getState())
- cam.setCameraMask(~PandaNode.getOverallBit())
- self.showVertices = self.cam.attachNewNode(cam)
- dr.setCamera(self.showVertices)
- def oobe(self, cam = None):
- """
- Enable a special "out-of-body experience" mouse-interface
- mode. This can be used when a "god" camera is needed; it
- moves the camera node out from under its normal node and sets
- the world up in trackball state. Button events are still sent
- to the normal mouse action node (e.g. the DriveInterface), and
- mouse events, if needed, may be sent to the normal node by
- holding down the Control key.
- This is different than `useTrackball()`, which simply changes
- the existing mouse action to a trackball interface. In fact,
- OOBE mode doesn't care whether `useDrive()` or `useTrackball()` is
- in effect; it just temporarily layers a new trackball
- interface on top of whatever the basic interface is. You can
- even switch between `useDrive()` and `useTrackball()` while OOBE
- mode is in effect.
- This is a toggle; the second time this function is called, it
- disables the mode.
- """
- if cam is None:
- cam = self.cam
- # If oobeMode was never set, set it to false and create the
- # structures we need to implement OOBE.
- if not hasattr(self, 'oobeMode'):
- self.oobeMode = 0
- self.oobeCamera = self.hidden.attachNewNode('oobeCamera')
- self.oobeCameraTrackball = self.oobeCamera.attachNewNode('oobeCameraTrackball')
- self.oobeLens = PerspectiveLens()
- self.oobeLens.setAspectRatio(self.getAspectRatio())
- self.oobeLens.setNearFar(0.1, 10000.0)
- self.oobeLens.setMinFov(40)
- self.oobeTrackball = NodePath(Trackball('oobeTrackball'))
- self.oobe2cam = self.oobeTrackball.attachNewNode(Transform2SG('oobe2cam'))
- self.oobe2cam.node().setNode(self.oobeCameraTrackball.node())
- self.oobeVis = base.loader.loadModel('models/misc/camera', okMissing = True)
- if not self.oobeVis:
- # Sometimes we have default-model-extension set to
- # egg, but the file might be a bam file.
- self.oobeVis = base.loader.loadModel('models/misc/camera.bam', okMissing = True)
- if not self.oobeVis:
- self.oobeVis = NodePath('oobeVis')
- self.oobeVis.node().setFinal(1)
- self.oobeVis.setLightOff(1)
- self.oobeCullFrustum = None
- self.__directObject.accept('oobe-down', self.__oobeButton, extraArgs = [''])
- self.__directObject.accept('oobe-repeat', self.__oobeButton, extraArgs = ['-repeat'])
- self.__directObject.accept('oobe-up', self.__oobeButton, extraArgs = ['-up'])
- if self.oobeMode:
- # Disable OOBE mode.
- if self.oobeCullFrustum is not None:
- # First, disable OOBE cull mode.
- self.oobeCull(cam = cam)
- if self.oobeVis:
- self.oobeVis.reparentTo(self.hidden)
- # Restore the mouse interface node, and remove the oobe
- # trackball from the data path.
- self.mouseInterfaceNode.clearButton(KeyboardButton.shift())
- self.oobeTrackball.detachNode()
- bt = self.buttonThrowers[0].node()
- bt.setSpecificFlag(1)
- bt.setButtonDownEvent('')
- bt.setButtonRepeatEvent('')
- bt.setButtonUpEvent('')
- cam.reparentTo(self.camera)
- #if cam == self.cam:
- # self.camNode.setLens(self.camLens)
- self.oobeCamera.reparentTo(self.hidden)
- self.oobeMode = 0
- self.bboard.post('oobeEnabled', False)
- else:
- self.bboard.post('oobeEnabled', True)
- try:
- cameraParent = localAvatar
- except NameError:
- # Make oobeCamera be a sibling of wherever camera is now.
- cameraParent = self.camera.getParent()
- self.oobeCamera.reparentTo(cameraParent)
- self.oobeCamera.clearMat()
- # Make the regular MouseInterface node respond only when
- # the shift button is pressed. And the oobe node will
- # respond only when shift is *not* pressed.
- self.mouseInterfaceNode.requireButton(KeyboardButton.shift(), True)
- self.oobeTrackball.node().requireButton(KeyboardButton.shift(), False)
- self.oobeTrackball.reparentTo(self.mouseWatcher)
- # Set our initial OOB position to be just behind the camera.
- mat = Mat4.translateMat(0, -10, 3) * self.camera.getMat(cameraParent)
- mat.invertInPlace()
- self.oobeTrackball.node().setMat(mat)
- cam.reparentTo(self.oobeCameraTrackball)
- # Temporarily disable button events by routing them
- # through the oobe filters.
- bt = self.buttonThrowers[0].node()
- bt.setSpecificFlag(0)
- bt.setButtonDownEvent('oobe-down')
- bt.setButtonRepeatEvent('oobe-repeat')
- bt.setButtonUpEvent('oobe-up')
- # Don't change the camera lens--keep it with the original lens.
- #if cam == self.cam:
- # self.camNode.setLens(self.oobeLens)
- if self.oobeVis:
- self.oobeVis.reparentTo(self.camera)
- self.oobeMode = 1
- def __oobeButton(self, suffix, button):
- if button.startswith('mouse'):
- # Eat mouse buttons.
- return
- # Transmit other buttons.
- messenger.send(button + suffix)
- def oobeCull(self, cam = None):
- """
- While in OOBE mode (see above), cull the viewing frustum as if
- it were still attached to our original camera. This allows us
- to visualize the effectiveness of our bounding volumes.
- """
- if cam is None:
- cam = self.cam
- # First, make sure OOBE mode is enabled.
- if not getattr(self, 'oobeMode', False):
- self.oobe(cam = cam)
- if self.oobeCullFrustum is None:
- # Enable OOBE culling.
- pnode = LensNode('oobeCull')
- pnode.setLens(self.camLens)
- pnode.showFrustum()
- self.oobeCullFrustum = self.camera.attachNewNode(pnode)
- # Tell the camera to cull from here instead of its own
- # origin.
- for c in self.camList:
- c.node().setCullCenter(self.oobeCullFrustum)
- if cam.node().isOfType(Camera):
- cam.node().setCullCenter(self.oobeCullFrustum)
- for c in cam.findAllMatches('**/+Camera'):
- c.node().setCullCenter(self.oobeCullFrustum)
- else:
- # Disable OOBE culling.
- for c in self.camList:
- c.node().setCullCenter(NodePath())
- if cam.node().isOfType(Camera):
- cam.node().setCullCenter(self.oobeCullFrustum)
- for c in cam.findAllMatches('**/+Camera'):
- c.node().setCullCenter(NodePath())
- self.oobeCullFrustum.removeNode()
- self.oobeCullFrustum = None
- def showCameraFrustum(self):
- # Create a visible representation of the frustum.
- self.removeCameraFrustum()
- geom = self.camLens.makeGeometry()
- if geom is not None:
- gn = GeomNode('frustum')
- gn.addGeom(geom)
- self.camFrustumVis = self.camera.attachNewNode(gn)
- def removeCameraFrustum(self):
- if self.camFrustumVis:
- self.camFrustumVis.removeNode()
- def screenshot(self, namePrefix = 'screenshot',
- defaultFilename = 1, source = None,
- imageComment="", blocking=True):
- """ Captures a screenshot from the main window or from the
- specified window or Texture and writes it to a filename in the
- current directory (or to a specified directory).
- If defaultFilename is True, the filename is synthesized by
- appending namePrefix to a default filename suffix (including
- the filename extension) specified in the Config variable
- screenshot-filename. Otherwise, if defaultFilename is False,
- the entire namePrefix is taken to be the filename to write,
- and this string should include a suitable filename extension
- that will be used to determine the type of image file to
- write.
- Normally, the source is a GraphicsWindow, GraphicsBuffer or
- DisplayRegion. If a Texture is supplied instead, it must have
- a ram image (that is, if it was generated by
- makeTextureBuffer() or makeCubeMap(), the parameter toRam
- should have been set true). If it is a cube map texture as
- generated by makeCubeMap(), namePrefix should contain the hash
- mark ('#') character.
- Normally, this call will block until the screenshot is fully
- written. To write the screenshot in a background thread
- instead, pass blocking = False. In this case, the return value
- is a future that can be awaited.
- A "screenshot" event will be sent once the screenshot is saved.
- :returns: The filename if successful, or None if there is a problem.
- """
- if source is None:
- source = self.win
- if defaultFilename:
- filename = GraphicsOutput.makeScreenshotFilename(namePrefix)
- else:
- filename = Filename(namePrefix)
- if isinstance(source, Texture):
- if source.getZSize() > 1:
- saved = source.write(filename, 0, 0, 1, 0)
- else:
- saved = source.write(filename)
- elif blocking:
- saved = source.saveScreenshot(filename, imageComment)
- else:
- request = source.saveAsyncScreenshot(filename, imageComment)
- request.addDoneCallback(lambda fut, filename=filename: messenger.send('screenshot', [filename]))
- return request
- if saved:
- # Announce to anybody that a screenshot has been taken
- messenger.send('screenshot', [filename])
- return filename
- return None
- def saveCubeMap(self, namePrefix = 'cube_map_#.png',
- defaultFilename = 0, source = None,
- camera = None, size = 128,
- cameraMask = PandaNode.getAllCameraMask(),
- sourceLens = None):
- """
- Similar to :meth:`screenshot()`, this sets up a temporary cube
- map Texture which it uses to take a series of six snapshots of
- the current scene, one in each of the six cube map directions.
- This requires rendering a new frame.
- Unlike `screenshot()`, source may only be a GraphicsWindow,
- GraphicsBuffer, or DisplayRegion; it may not be a Texture.
- camera should be the node to which the cubemap cameras will be
- parented. The default is the camera associated with source,
- if source is a DisplayRegion, or base.camera otherwise.
- :returns: The filename if successful, or None if there is a problem.
- """
- if source is None:
- source = self.win
- if camera is None:
- if hasattr(source, "getCamera"):
- camera = source.getCamera()
- if camera is None:
- camera = self.camera
- if sourceLens is None:
- sourceLens = self.camLens
- if hasattr(source, "getWindow"):
- source = source.getWindow()
- rig = NodePath(namePrefix)
- buffer = source.makeCubeMap(namePrefix, size, rig, cameraMask, 1)
- if buffer is None:
- raise Exception("Could not make cube map.")
- # Set the near and far planes from the default lens.
- lens = rig.find('**/+Camera').node().getLens()
- lens.setNearFar(sourceLens.getNear(), sourceLens.getFar())
- # Now render a frame to fill up the texture.
- rig.reparentTo(camera)
- self.graphicsEngine.openWindows()
- self.graphicsEngine.renderFrame()
- self.graphicsEngine.renderFrame()
- self.graphicsEngine.syncFrame()
- tex = buffer.getTexture()
- saved = self.screenshot(namePrefix = namePrefix,
- defaultFilename = defaultFilename,
- source = tex)
- self.graphicsEngine.removeWindow(buffer)
- rig.removeNode()
- return saved
- def saveSphereMap(self, namePrefix = 'spheremap.png',
- defaultFilename = 0, source = None,
- camera = None, size = 256,
- cameraMask = PandaNode.getAllCameraMask(),
- numVertices = 1000, sourceLens = None):
- """
- This works much like :meth:`saveCubeMap()`, and uses the
- graphics API's hardware cube-mapping ability to get a 360-degree
- view of the world. But then it converts the six cube map faces
- into a single fisheye texture, suitable for applying as a static
- environment map (sphere map).
- For eye-relative static environment maps, sphere maps are often
- preferable to cube maps because they require only a single
- texture and because they are supported on a broader range of
- hardware.
- :returns: The filename if successful, or None if there is a problem.
- """
- if source is None:
- source = self.win
- if camera is None:
- if hasattr(source, "getCamera"):
- camera = source.getCamera()
- if camera is None:
- camera = self.camera
- if sourceLens is None:
- sourceLens = self.camLens
- if hasattr(source, "getWindow"):
- source = source.getWindow()
- # First, make an offscreen buffer to convert the cube map to a
- # sphere map. We make it first so we can guarantee the
- # rendering order for the cube map.
- toSphere = source.makeTextureBuffer(namePrefix, size, size,
- Texture(), 1)
- # Now make the cube map buffer.
- rig = NodePath(namePrefix)
- buffer = toSphere.makeCubeMap(namePrefix, size, rig, cameraMask, 0)
- if buffer is None:
- self.graphicsEngine.removeWindow(toSphere)
- raise Exception("Could not make cube map.")
- # Set the near and far planes from the default lens.
- lens = rig.find('**/+Camera').node().getLens()
- lens.setNearFar(sourceLens.getNear(), sourceLens.getFar())
- # Set up the scene to convert the cube map. It's just a
- # simple scene, with only the FisheyeMaker object in it.
- dr = toSphere.makeMonoDisplayRegion()
- camNode = Camera('camNode')
- lens = OrthographicLens()
- lens.setFilmSize(2, 2)
- lens.setNearFar(-1000, 1000)
- camNode.setLens(lens)
- root = NodePath('buffer')
- cam = root.attachNewNode(camNode)
- dr.setCamera(cam)
- fm = FisheyeMaker('card')
- fm.setNumVertices(numVertices)
- fm.setSquareInscribed(1, 1.1)
- fm.setReflection(1)
- card = root.attachNewNode(fm.generate())
- card.setTexture(buffer.getTexture())
- # Now render a frame. This will render out the cube map and
- # then apply it to the the card in the toSphere buffer.
- rig.reparentTo(camera)
- self.graphicsEngine.openWindows()
- self.graphicsEngine.renderFrame()
- # One more frame for luck.
- self.graphicsEngine.renderFrame()
- self.graphicsEngine.syncFrame()
- saved = self.screenshot(namePrefix = namePrefix,
- defaultFilename = defaultFilename,
- source = toSphere.getTexture())
- self.graphicsEngine.removeWindow(buffer)
- self.graphicsEngine.removeWindow(toSphere)
- rig.removeNode()
- return saved
- def movie(self, namePrefix = 'movie', duration = 1.0, fps = 30,
- format = 'png', sd = 4, source = None):
- """
- Spawn a task to capture a movie using the screenshot function.
- Args:
- namePrefix (str): used to form output file names (can
- include path information (e.g. '/i/beta/frames/myMovie')
- duration (float): the length of the movie in seconds
- fps (float): the frame rate of the resulting movie
- format (str): specifies output file format (e.g. png, bmp)
- sd (int): specifies number of significant digits for frame
- count in the output file name (e.g. if sd = 4, the name
- will be something like movie_0001.png)
- source: the Window, Buffer, DisplayRegion, or Texture from
- which to save the resulting images. The default is the
- main window.
- Returns:
- A `~direct.task.Task` that can be awaited.
- """
- clock = self.clock
- clock.mode = ClockObject.MNonRealTime
- clock.dt = 1.0 / fps
- t = self.taskMgr.add(self._movieTask, namePrefix + '_task')
- t.frameIndex = 0 # Frame 0 is not captured.
- t.numFrames = int(duration * fps)
- t.source = source
- t.outputString = namePrefix + '_%0' + repr(sd) + 'd.' + format
- t.setUponDeath(lambda state: clock.setMode(ClockObject.MNormal))
- return t
- def _movieTask(self, state):
- if state.frameIndex != 0:
- frameName = state.outputString % state.frameIndex
- self.notify.info("Capturing frame: " + frameName)
- self.screenshot(namePrefix = frameName, defaultFilename = 0,
- source = state.source)
- state.frameIndex += 1
- if state.frameIndex > state.numFrames:
- return Task.done
- else:
- return Task.cont
- def windowEvent(self, win):
- if win != self.win:
- # This event isn't about our window.
- return
- properties = win.getProperties()
- if properties != self.__prevWindowProperties:
- self.__prevWindowProperties = properties
- self.notify.debug("Got window event: %s" % (repr(properties)))
- if not properties.getOpen():
- # If the user closes the main window, we should exit.
- self.notify.info("User closed main window.")
- if __debug__:
- if self.__autoGarbageLogging:
- GarbageReport.b_checkForGarbageLeaks()
- self.userExit()
- if properties.getForeground() and not self.mainWinForeground:
- self.mainWinForeground = 1
- elif not properties.getForeground() and self.mainWinForeground:
- self.mainWinForeground = 0
- if __debug__:
- if self.__autoGarbageLogging:
- GarbageReport.b_checkForGarbageLeaks()
- if properties.getMinimized() and not self.mainWinMinimized:
- # If the main window is minimized, throw an event to
- # stop the music.
- self.mainWinMinimized = 1
- messenger.send('PandaPaused')
- elif not properties.getMinimized() and self.mainWinMinimized:
- # If the main window is restored, throw an event to
- # restart the music.
- self.mainWinMinimized = 0
- messenger.send('PandaRestarted')
- # If we have not forced the aspect ratio, let's see if it has
- # changed and update the camera lenses and aspect2d parameters
- self.adjustWindowAspectRatio(self.getAspectRatio())
- if win.hasSize() and win.getSbsLeftYSize() != 0:
- self.pixel2d.setScale(2.0 / win.getSbsLeftXSize(), 1.0, 2.0 / win.getSbsLeftYSize())
- if self.wantRender2dp:
- self.pixel2dp.setScale(2.0 / win.getSbsLeftXSize(), 1.0, 2.0 / win.getSbsLeftYSize())
- else:
- xsize, ysize = self.getSize()
- if xsize > 0 and ysize > 0:
- self.pixel2d.setScale(2.0 / xsize, 1.0, 2.0 / ysize)
- if self.wantRender2dp:
- self.pixel2dp.setScale(2.0 / xsize, 1.0, 2.0 / ysize)
- def adjustWindowAspectRatio(self, aspectRatio):
- """ This function is normally called internally by
- `windowEvent()`, but it may also be called to explicitly adjust
- the aspect ratio of the render/render2d DisplayRegion, by a
- class that has redefined these. """
- if self.__configAspectRatio:
- aspectRatio = self.__configAspectRatio
- if aspectRatio != self.__oldAspectRatio:
- self.__oldAspectRatio = aspectRatio
- # Fix up some anything that depends on the aspectRatio
- if self.camLens:
- self.camLens.setAspectRatio(aspectRatio)
- if aspectRatio < 1:
- # If the window is TALL, lets expand the top and bottom
- self.aspect2d.setScale(1.0, aspectRatio, aspectRatio)
- self.a2dTop = 1.0 / aspectRatio
- self.a2dBottom = - 1.0 / aspectRatio
- self.a2dLeft = -1
- self.a2dRight = 1.0
- # Don't forget 2dp
- if self.wantRender2dp:
- self.aspect2dp.setScale(1.0, aspectRatio, aspectRatio)
- self.a2dpTop = 1.0 / aspectRatio
- self.a2dpBottom = - 1.0 / aspectRatio
- self.a2dpLeft = -1
- self.a2dpRight = 1.0
- else:
- # If the window is WIDE, lets expand the left and right
- self.aspect2d.setScale(1.0 / aspectRatio, 1.0, 1.0)
- self.a2dTop = 1.0
- self.a2dBottom = -1.0
- self.a2dLeft = -aspectRatio
- self.a2dRight = aspectRatio
- # Don't forget 2dp
- if self.wantRender2dp:
- self.aspect2dp.setScale(1.0 / aspectRatio, 1.0, 1.0)
- self.a2dpTop = 1.0
- self.a2dpBottom = -1.0
- self.a2dpLeft = -aspectRatio
- self.a2dpRight = aspectRatio
- # Reposition the aspect2d marker nodes
- self.a2dTopCenter.setPos(0, 0, self.a2dTop)
- self.a2dTopCenterNs.setPos(0, 0, self.a2dTop)
- self.a2dBottomCenter.setPos(0, 0, self.a2dBottom)
- self.a2dBottomCenterNs.setPos(0, 0, self.a2dBottom)
- self.a2dLeftCenter.setPos(self.a2dLeft, 0, 0)
- self.a2dLeftCenterNs.setPos(self.a2dLeft, 0, 0)
- self.a2dRightCenter.setPos(self.a2dRight, 0, 0)
- self.a2dRightCenterNs.setPos(self.a2dRight, 0, 0)
- self.a2dTopLeft.setPos(self.a2dLeft, 0, self.a2dTop)
- self.a2dTopLeftNs.setPos(self.a2dLeft, 0, self.a2dTop)
- self.a2dTopRight.setPos(self.a2dRight, 0, self.a2dTop)
- self.a2dTopRightNs.setPos(self.a2dRight, 0, self.a2dTop)
- self.a2dBottomLeft.setPos(self.a2dLeft, 0, self.a2dBottom)
- self.a2dBottomLeftNs.setPos(self.a2dLeft, 0, self.a2dBottom)
- self.a2dBottomRight.setPos(self.a2dRight, 0, self.a2dBottom)
- self.a2dBottomRightNs.setPos(self.a2dRight, 0, self.a2dBottom)
- # Reposition the aspect2dp marker nodes
- if self.wantRender2dp:
- self.a2dpTopCenter.setPos(0, 0, self.a2dpTop)
- self.a2dpBottomCenter.setPos(0, 0, self.a2dpBottom)
- self.a2dpLeftCenter.setPos(self.a2dpLeft, 0, 0)
- self.a2dpRightCenter.setPos(self.a2dpRight, 0, 0)
- self.a2dpTopLeft.setPos(self.a2dpLeft, 0, self.a2dpTop)
- self.a2dpTopRight.setPos(self.a2dpRight, 0, self.a2dpTop)
- self.a2dpBottomLeft.setPos(self.a2dpLeft, 0, self.a2dpBottom)
- self.a2dpBottomRight.setPos(self.a2dpRight, 0, self.a2dpBottom)
- # If anybody needs to update their GUI, put a callback on this event
- messenger.send("aspectRatioChanged")
- def userExit(self):
- # The user has requested we exit the program. Deal with this.
- if self.exitFunc:
- self.exitFunc()
- self.notify.info("Exiting ShowBase.")
- self.finalizeExit()
- def finalizeExit(self):
- """
- Called by `userExit()` to quit the application. The default
- implementation just calls `sys.exit()`.
- """
- sys.exit()
- # [gjeon] start wxPython
- def startWx(self, fWantWx = True):
- fWantWx = bool(fWantWx)
- if self.wantWx != fWantWx:
- self.wantWx = fWantWx
- if self.wantWx:
- self.spawnWxLoop()
- def spawnWxLoop(self):
- """ Call this method to hand the main loop over to wxPython.
- This sets up a wxTimer callback so that Panda still gets
- updated, but wxPython owns the main loop (which seems to make
- it happier than the other way around). """
- if self.wxAppCreated:
- # Don't do this twice.
- return
- init_app_for_gui()
- # Use importlib to prevent this import from being picked up
- # by modulefinder when packaging an application.
- wx = importlib.import_module('wx')
- if not self.wxApp:
- # Create a new base.wxApp.
- self.wxApp = wx.App(redirect = False)
- if ConfigVariableBool('wx-main-loop', True):
- # Put wxPython in charge of the main loop. It really
- # seems to like this better; some features of wx don't
- # work properly unless this is true.
- # Set a timer to run the Panda frame 60 times per second.
- wxFrameRate = ConfigVariableDouble('wx-frame-rate', 60.0)
- self.wxTimer = wx.Timer(self.wxApp)
- self.wxTimer.Start(int(round(1000.0 / wxFrameRate.value)))
- self.wxApp.Bind(wx.EVT_TIMER, self.__wxTimerCallback)
- # wx is now the main loop, not us any more.
- self.run = self.wxRun
- self.taskMgr.run = self.wxRun
- builtins.run = self.wxRun
- if self.appRunner:
- self.appRunner.run = self.wxRun
- else:
- # Leave Panda in charge of the main loop. This is
- # friendlier for IDE's and interactive editing in general.
- def wxLoop(task):
- # First we need to ensure that the OS message queue is
- # processed.
- self.wxApp.Yield()
- # Now do all the wxPython events waiting on this frame.
- while self.wxApp.Pending():
- self.wxApp.Dispatch()
- return task.again
- self.taskMgr.add(wxLoop, 'wxLoop')
- self.wxAppCreated = True
- def __wxTimerCallback(self, event):
- if Thread.getCurrentThread().getCurrentTask():
- # This happens when the wxTimer expires while igLoop is
- # rendering. Ignore it.
- return
- self.taskMgr.step()
- def wxRun(self):
- """ This method replaces `run()` after we have called `spawnWxLoop()`.
- Since at this point wxPython now owns the main loop, this method is a
- call to wxApp.MainLoop(). """
- if Thread.getCurrentThread().getCurrentTask():
- # This happens in the p3d environment during startup.
- # Ignore it.
- return
- self.wxApp.MainLoop()
- def startTk(self, fWantTk = True):
- fWantTk = bool(fWantTk)
- if self.wantTk != fWantTk:
- self.wantTk = fWantTk
- if self.wantTk:
- self.spawnTkLoop()
- def spawnTkLoop(self):
- """ Call this method to hand the main loop over to Tkinter.
- This sets up a timer callback so that Panda still gets
- updated, but Tkinter owns the main loop (which seems to make
- it happier than the other way around). """
- if self.tkRootCreated:
- # Don't do this twice.
- return
- # Use importlib to prevent this import from being picked up
- # by modulefinder when packaging an application.
- tkinter = importlib.import_module('_tkinter')
- Pmw = importlib.import_module('Pmw')
- # Create a new Tk root.
- if not self.tkRoot:
- self.tkRoot = Pmw.initialise()
- builtins.tkroot = self.tkRoot
- init_app_for_gui()
- # Disable the Windows message loop, since Tcl wants to handle this all
- # on its own, except if the Panda window is on a separate thread.
- if self.graphicsEngine.getThreadingModel().getDrawStage() == 0:
- ConfigVariableBool('disable-message-loop', False).value = True
- if ConfigVariableBool('tk-main-loop', True):
- # Put Tkinter in charge of the main loop. It really
- # seems to like this better; the GUI otherwise becomes
- # largely unresponsive on Mac OS X unless this is true.
- # Set a timer to run the Panda frame 60 times per second.
- tkFrameRate = ConfigVariableDouble('tk-frame-rate', 60.0)
- self.tkDelay = int(1000.0 / tkFrameRate.value)
- self.tkRoot.after(self.tkDelay, self.__tkTimerCallback)
- # wx is now the main loop, not us any more.
- self.run = self.tkRun
- self.taskMgr.run = self.tkRun
- builtins.run = self.tkRun
- if self.appRunner:
- self.appRunner.run = self.tkRun
- else:
- # Leave Panda in charge of the main loop. This is
- # friendlier for IDE's and interactive editing in general.
- def tkLoop(task):
- # Do all the tkinter events waiting on this frame
- # dooneevent will return 0 if there are no more events
- # waiting or 1 if there are still more.
- # DONT_WAIT tells tkinter not to block waiting for events
- while self.tkRoot.dooneevent(tkinter.ALL_EVENTS | tkinter.DONT_WAIT):
- pass
- return task.again
- self.taskMgr.add(tkLoop, 'tkLoop')
- self.tkRootCreated = True
- def __tkTimerCallback(self):
- if not Thread.getCurrentThread().getCurrentTask():
- self.taskMgr.step()
- self.tkRoot.after(self.tkDelay, self.__tkTimerCallback)
- def tkRun(self):
- """ This method replaces `run()` after we have called `spawnTkLoop()`.
- Since at this point Tkinter now owns the main loop, this method is a
- call to tkRoot.mainloop(). """
- if Thread.getCurrentThread().getCurrentTask():
- # This happens in the p3d environment during startup.
- # Ignore it.
- return
- self.tkRoot.mainloop()
- def startDirect(self, fWantDirect = 1, fWantTk = 1, fWantWx = 0):
- self.startTk(fWantTk)
- self.startWx(fWantWx)
- if self.wantDirect == fWantDirect:
- return
- self.wantDirect = fWantDirect
- if self.wantDirect:
- # Use importlib to prevent this import from being picked up
- # by modulefinder when packaging an application.
- DirectSession = importlib.import_module('direct.directtools.DirectSession')
- self.direct = DirectSession.DirectSession()
- self.direct.enable()
- builtins.direct = self.direct
- else:
- builtins.direct = self.direct = None
- def getRepository(self):
- return None
- def getAxes(self):
- """
- Loads and returns the ``models/misc/xyzAxis.bam`` model.
- :rtype: panda3d.core.NodePath
- """
- return self.loader.loadModel("models/misc/xyzAxis.bam")
- def __doStartDirect(self):
- if self.__directStarted:
- return
- self.__directStarted = False
- # Start Tk, Wx and DIRECT if specified by Config.prc
- fTk = ConfigVariableBool('want-tk', False).value
- fWx = ConfigVariableBool('want-wx', False).value
- # Start DIRECT if specified in Config.prc or in cluster mode
- fDirect = (ConfigVariableBool('want-directtools', 0).value or
- (not ConfigVariableString("cluster-mode", '').empty()))
- # Set fWantTk to 0 to avoid starting Tk with this call
- self.startDirect(fWantDirect = fDirect, fWantTk = fTk, fWantWx = fWx)
- def run(self): # pylint: disable=method-hidden
- """This method runs the :class:`~direct.task.Task.TaskManager`
- when ``self.appRunner is None``, which is to say, when we are
- not running from within a p3d file. When we *are* within a p3d
- file, the Panda3D runtime has to be responsible for running the
- main loop, so we can't allow the application to do it.
- This method must be called from the main thread, otherwise an error is
- thrown.
- """
- if Thread.getCurrentThread() != Thread.getMainThread():
- self.notify.error("run() must be called from the main thread.")
- return
- if self.appRunner is None or self.appRunner.dummy or \
- (self.appRunner.interactiveConsole and not self.appRunner.initialAppImport):
- self.taskMgr.run()
- # Snake-case aliases, for people who prefer these. We're in the process
- # of migrating everyone to use the snake-case alternatives.
- make_default_pipe = makeDefaultPipe
- make_module_pipe = makeModulePipe
- make_all_pipes = makeAllPipes
- open_window = openWindow
- close_window = closeWindow
- open_default_window = openDefaultWindow
- open_main_window = openMainWindow
- set_sleep = setSleep
- set_frame_rate_meter = setFrameRateMeter
- set_scene_graph_analyzer_meter = setSceneGraphAnalyzerMeter
- setup_window_controls = setupWindowControls
- setup_render = setupRender
- setup_render2d = setupRender2d
- setup_render2dp = setupRender2dp
- set_aspect_ratio = setAspectRatio
- get_aspect_ratio = getAspectRatio
- get_size = getSize
- make_camera = makeCamera
- make_camera2d = makeCamera2d
- make_camera2dp = makeCamera2dp
- setup_data_graph = setupDataGraph
- setup_mouse = setupMouse
- setup_mouse_cb = setupMouseCB
- enable_software_mouse_pointer = enableSoftwareMousePointer
- detach_input_device = detachInputDevice
- attach_input_device = attachInputDevice
- add_angular_integrator = addAngularIntegrator
- enable_particles = enableParticles
- disable_particles = disableParticles
- toggle_particles = toggleParticles
- create_stats = createStats
- add_sfx_manager = addSfxManager
- enable_music = enableMusic
- enable_sound_effects = enableSoundEffects
- disable_all_audio = disableAllAudio
- enable_all_audio = enableAllAudio
- init_shadow_trav = initShadowTrav
- get_background_color = getBackgroundColor
- set_background_color = setBackgroundColor
- toggle_backface = toggleBackface
- backface_culling_on = backfaceCullingOn
- backface_culling_off = backfaceCullingOff
- toggle_texture = toggleTexture
- texture_on = textureOn
- texture_off = textureOff
- toggle_wireframe = toggleWireframe
- wireframe_on = wireframeOn
- wireframe_off = wireframeOff
- disable_mouse = disableMouse
- enable_mouse = enableMouse
- silence_input = silenceInput
- revive_input = reviveInput
- set_mouse_on_node = setMouseOnNode
- change_mouse_interface = changeMouseInterface
- use_drive = useDrive
- use_trackball = useTrackball
- toggle_tex_mem = toggleTexMem
- toggle_show_vertices = toggleShowVertices
- oobe_cull = oobeCull
- show_camera_frustum = showCameraFrustum
- remove_camera_frustum = removeCameraFrustum
- save_cube_map = saveCubeMap
- save_sphere_map = saveSphereMap
- start_wx = startWx
- start_tk = startTk
- start_direct = startDirect
- # A class to encapsulate information necessary for multiwindow support.
- class WindowControls:
- def __init__(
- self, win, cam=None, camNode=None, cam2d=None, mouseWatcher=None,
- mouseKeyboard=None, closeCmd=lambda: 0, grid=None):
- self.win = win
- self.camera = cam
- if camNode is None and cam is not None:
- camNode = cam.node()
- self.camNode = camNode
- self.camera2d = cam2d
- self.mouseWatcher = mouseWatcher
- self.mouseKeyboard = mouseKeyboard
- self.closeCommand = closeCmd
- self.grid = grid
- def __str__(self):
- s = "window = " + str(self.win) + "\n"
- s += "camera = " + str(self.camera) + "\n"
- s += "camNode = " + str(self.camNode) + "\n"
- s += "camera2d = " + str(self.camera2d) + "\n"
- s += "mouseWatcher = " + str(self.mouseWatcher) + "\n"
- s += "mouseAndKeyboard = " + str(self.mouseKeyboard) + "\n"
- return s
|