main.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  1. """
  2. Show how to use libRocket in Panda3D.
  3. NOTE: libRocket is only available for Python 2.7, which is itself deprecated.
  4. We have therefore deprecated libRocket support in Panda3D, and it will be
  5. removed in Panda3D 1.11.
  6. """
  7. import sys
  8. from panda3d.core import loadPrcFile, loadPrcFileData, Point3,Vec4, Mat4, LoaderOptions # @UnusedImport
  9. from panda3d.core import DirectionalLight, AmbientLight, PointLight
  10. from panda3d.core import Texture, PNMImage
  11. from panda3d.core import PandaSystem
  12. import random
  13. from direct.interval.LerpInterval import LerpHprInterval, LerpPosInterval, LerpFunc
  14. from direct.showbase.ShowBase import ShowBase
  15. # workaround: https://www.panda3d.org/forums/viewtopic.php?t=10062&p=99697#p99054
  16. #from panda3d import rocket
  17. import _rocketcore as rocket
  18. from panda3d.rocket import RocketRegion, RocketInputHandler
  19. loadPrcFileData("", "model-path $MAIN_DIR/assets")
  20. import console
  21. global globalClock
  22. class MyApp(ShowBase):
  23. def __init__(self):
  24. ShowBase.__init__(self)
  25. self.win.setClearColor(Vec4(0.2, 0.2, 0.2, 1))
  26. self.disableMouse()
  27. self.render.setShaderAuto()
  28. dlight = DirectionalLight('dlight')
  29. alight = AmbientLight('alight')
  30. dlnp = self.render.attachNewNode(dlight)
  31. alnp = self.render.attachNewNode(alight)
  32. dlight.setColor((0.8, 0.8, 0.5, 1))
  33. alight.setColor((0.2, 0.2, 0.2, 1))
  34. dlnp.setHpr(0, -60, 0)
  35. self.render.setLight(dlnp)
  36. self.render.setLight(alnp)
  37. # Put lighting on the main scene
  38. plight = PointLight('plight')
  39. plnp = self.render.attachNewNode(plight)
  40. plnp.setPos(0, 0, 10)
  41. self.render.setLight(plnp)
  42. self.render.setLight(alnp)
  43. self.loadRocketFonts()
  44. self.loadingTask = None
  45. #self.startModelLoadingAsync()
  46. self.startModelLoading()
  47. self.inputHandler = RocketInputHandler()
  48. self.mouseWatcher.attachNewNode(self.inputHandler)
  49. self.openLoadingDialog()
  50. def loadRocketFonts(self):
  51. """ Load fonts referenced from e.g. 'font-family' RCSS directives.
  52. Note: the name of the font as used in 'font-family'
  53. is not always the same as the filename;
  54. open the font in your OS to see its display name.
  55. """
  56. rocket.LoadFontFace("modenine.ttf")
  57. def startModelLoading(self):
  58. self.monitorNP = None
  59. self.keyboardNP = None
  60. self.loadingError = False
  61. self.taskMgr.doMethodLater(1, self.loadModels, 'loadModels')
  62. def loadModels(self, task):
  63. self.monitorNP = self.loader.loadModel("monitor")
  64. self.keyboardNP = self.loader.loadModel("takeyga_kb")
  65. def startModelLoadingAsync(self):
  66. """
  67. NOTE: this seems to invoke a few bugs (crashes, sporadic model
  68. reading errors, etc) so is disabled for now...
  69. """
  70. self.monitorNP = None
  71. self.keyboardNP = None
  72. self.loadingError = False
  73. # force the "loading" to take some time after the first run...
  74. options = LoaderOptions()
  75. options.setFlags(options.getFlags() | LoaderOptions.LFNoCache)
  76. def gotMonitorModel(model):
  77. if not model:
  78. self.loadingError = True
  79. self.monitorNP = model
  80. self.loader.loadModel("monitor", loaderOptions=options, callback=gotMonitorModel)
  81. def gotKeyboardModel(model):
  82. if not model:
  83. self.loadingError = True
  84. self.keyboardNP = model
  85. self.loader.loadModel("takeyga_kb", loaderOptions=options, callback=gotKeyboardModel)
  86. def openLoadingDialog(self):
  87. self.userConfirmed = False
  88. self.windowRocketRegion = RocketRegion.make('pandaRocket', self.win)
  89. self.windowRocketRegion.setActive(1)
  90. self.windowRocketRegion.setInputHandler(self.inputHandler)
  91. self.windowContext = self.windowRocketRegion.getContext()
  92. self.loadingDocument = self.windowContext.LoadDocument("loading.rml")
  93. if not self.loadingDocument:
  94. raise AssertionError("did not find loading.rml")
  95. self.loadingDots = 0
  96. el = self.loadingDocument.GetElementById('loadingLabel')
  97. self.loadingText = el.first_child
  98. self.stopLoadingTime = globalClock.getFrameTime() + 3
  99. self.loadingTask = self.taskMgr.add(self.cycleLoading, 'doc changer')
  100. # note: you may encounter errors like 'KeyError: 'document'"
  101. # when invoking events using methods from your own scripts with this
  102. # obvious code:
  103. #
  104. # self.loadingDocument.AddEventListener('aboutToClose',
  105. # self.onLoadingDialogDismissed, True)
  106. #
  107. # A workaround is to define callback methods in standalone Python
  108. # files with event, self, and document defined to None.
  109. #
  110. # see https://www.panda3d.org/forums/viewtopic.php?f=4&t=16412
  111. #
  112. # Or, use this indirection technique to work around the problem,
  113. # by publishing the app into the context, then accessing it through
  114. # the document's context...
  115. self.windowContext.app = self
  116. self.loadingDocument.AddEventListener('aboutToClose',
  117. 'document.context.app.handleAboutToClose()', True)
  118. self.loadingDocument.Show()
  119. def handleAboutToClose(self):
  120. self.userConfirmed = True
  121. if self.monitorNP and self.keyboardNP:
  122. self.onLoadingDialogDismissed()
  123. def attachCustomRocketEvent(self, document, rocketEventName, pandaHandler, once=False):
  124. # handle custom event
  125. # note: you may encounter errors like 'KeyError: 'document'"
  126. # when invoking events using methods from your own scripts with this
  127. # obvious code:
  128. #
  129. # self.loadingDocument.AddEventListener('aboutToClose',
  130. # self.onLoadingDialogDismissed, True)
  131. #
  132. # see https://www.panda3d.org/forums/viewtopic.php?f=4&t=16412
  133. # this technique converts Rocket events to Panda3D events
  134. pandaEvent = 'panda.' + rocketEventName
  135. document.AddEventListener(
  136. rocketEventName,
  137. "messenger.send('" + pandaEvent + "', [event])")
  138. if once:
  139. self.acceptOnce(pandaEvent, pandaHandler)
  140. else:
  141. self.accept(pandaEvent, pandaHandler)
  142. def cycleLoading(self, task):
  143. """
  144. Update the "loading" text in the initial window until
  145. the user presses Space, Enter, or Escape or clicks (see loading.rxml)
  146. or sufficient time has elapsed (self.stopLoadingTime).
  147. """
  148. text = self.loadingText
  149. now = globalClock.getFrameTime()
  150. if self.monitorNP and self.keyboardNP:
  151. text.text = "Ready"
  152. if now > self.stopLoadingTime or self.userConfirmed:
  153. self.onLoadingDialogDismissed()
  154. return task.done
  155. elif self.loadingError:
  156. text.text = "Assets not found"
  157. else:
  158. count = 5
  159. intv = int(now * 4) % count # @UndefinedVariable
  160. text.text = "Loading" + ("." * (1+intv)) + (" " * (2 - intv))
  161. return task.cont
  162. def onLoadingDialogDismissed(self):
  163. """ Once a models are loaded, stop 'loading' and proceed to 'start' """
  164. if self.loadingDocument:
  165. if self.loadingTask:
  166. self.taskMgr.remove(self.loadingTask)
  167. self.loadingTask = None
  168. self.showStarting()
  169. def fadeOut(self, element, time):
  170. """ Example updating RCSS attributes from code
  171. by modifying the 'color' RCSS attribute to slowly
  172. change from solid to transparent.
  173. element: the Rocket element whose style to modify
  174. time: time in seconds for fadeout
  175. """
  176. # get the current color from RCSS effective style
  177. color = element.style.color
  178. # convert to RGBA form
  179. prefix = color[:color.rindex(',')+1].replace('rgb(', 'rgba(')
  180. def updateAlpha(t):
  181. # another way of setting style on a specific element
  182. attr = 'color: ' + prefix + str(int(t)) +');'
  183. element.SetAttribute('style', attr)
  184. alphaInterval = LerpFunc(updateAlpha,
  185. duration=time,
  186. fromData=255,
  187. toData=0,
  188. blendType='easeIn')
  189. return alphaInterval
  190. def showStarting(self):
  191. """ Models are loaded, so update the dialog,
  192. fade out, then transition to the console. """
  193. self.loadingText.text = 'Starting...'
  194. alphaInterval = self.fadeOut(self.loadingText, 0.5)
  195. alphaInterval.setDoneEvent('fadeOutFinished')
  196. def fadeOutFinished():
  197. if self.loadingDocument:
  198. self.loadingDocument.Close()
  199. self.loadingDocument = None
  200. self.createConsole()
  201. self.accept('fadeOutFinished', fadeOutFinished)
  202. alphaInterval.start()
  203. def createConsole(self):
  204. """ Create the in-world console, which displays
  205. a RocketRegion in a GraphicsBuffer, which appears
  206. in a Texture on the monitor model. """
  207. self.monitorNP.reparentTo(self.render)
  208. self.monitorNP.setScale(1.5)
  209. self.keyboardNP.reparentTo(self.render)
  210. self.keyboardNP.setHpr(-90, 0, 15)
  211. self.keyboardNP.setScale(20)
  212. self.placeItems()
  213. self.setupRocketConsole()
  214. # re-enable mouse
  215. mat=Mat4(self.camera.getMat())
  216. mat.invertInPlace()
  217. self.mouseInterfaceNode.setMat(mat)
  218. self.enableMouse()
  219. def placeItems(self):
  220. self.camera.setPos(0, -20, 0)
  221. self.camera.setHpr(0, 0, 0)
  222. self.monitorNP.setPos(0, 0, 1)
  223. self.keyboardNP.setPos(0, -5, -2.5)
  224. def setupRocketConsole(self):
  225. """
  226. Place a new rocket window onto a texture
  227. bound to the front of the monitor.
  228. """
  229. self.win.setClearColor(Vec4(0.5, 0.5, 0.8, 1))
  230. faceplate = self.monitorNP.find("**/Faceplate")
  231. assert faceplate
  232. mybuffer = self.win.makeTextureBuffer("Console Buffer", 1024, 512)
  233. tex = mybuffer.getTexture()
  234. tex.setMagfilter(Texture.FTLinear)
  235. tex.setMinfilter(Texture.FTLinear)
  236. faceplate.setTexture(tex, 1)
  237. self.rocketConsole = RocketRegion.make('console', mybuffer)
  238. self.rocketConsole.setInputHandler(self.inputHandler)
  239. self.consoleContext = self.rocketConsole.getContext()
  240. self.console = console.Console(self, self.consoleContext, 40, 13, self.handleCommand)
  241. self.console.addLine("Panda DOS")
  242. self.console.addLine("type 'help'")
  243. self.console.addLine("")
  244. self.console.allowEditing(True)
  245. def handleCommand(self, command):
  246. if command is None:
  247. # hack for Ctrl-Break
  248. self.spewInProgress = False
  249. self.console.addLine("*** break ***")
  250. self.console.allowEditing(True)
  251. return
  252. command = command.strip()
  253. if not command:
  254. return
  255. tokens = [x.strip() for x in command.split(' ')]
  256. command = tokens[0].lower()
  257. if command == 'help':
  258. self.console.addLines([
  259. "Sorry, this is utter fakery.",
  260. "You won't get much more",
  261. "out of this simulation unless",
  262. "you program it yourself. :)"
  263. ])
  264. elif command == 'dir':
  265. self.console.addLines([
  266. "Directory of C:\\:",
  267. "HELP COM 72 05-06-2015 14:07",
  268. "DIR COM 121 05-06-2015 14:11",
  269. "SPEW COM 666 05-06-2015 15:02",
  270. " 2 Files(s) 859 Bytes.",
  271. " 0 Dirs(s) 7333 Bytes free.",
  272. ""])
  273. elif command == 'cls':
  274. self.console.cls()
  275. elif command == 'echo':
  276. self.console.addLine(' '.join(tokens[1:]))
  277. elif command == 'ver':
  278. self.console.addLine('Panda DOS v0.01 in Panda3D ' + PandaSystem.getVersionString())
  279. elif command == 'spew':
  280. self.startSpew()
  281. elif command == 'exit':
  282. self.console.setPrompt("System is shutting down NOW!")
  283. self.terminateMonitor()
  284. else:
  285. self.console.addLine("command not found")
  286. def startSpew(self):
  287. self.console.allowEditing(False)
  288. self.console.addLine("LINE NOISE 1.0")
  289. self.console.addLine("")
  290. self.spewInProgress = True
  291. # note: spewage always occurs in 'doMethodLater';
  292. # time.sleep() would be pointless since the whole
  293. # UI would be frozen during the wait.
  294. self.queueSpew(2)
  295. def queueSpew(self, delay=0.1):
  296. self.taskMgr.doMethodLater(delay, self.spew, 'spew')
  297. def spew(self, task):
  298. # generate random spewage, just like on TV!
  299. if not self.spewInProgress:
  300. return
  301. def randchr():
  302. return chr(int(random.random() < 0.25 and 32 or random.randint(32, 127)))
  303. line = ''.join([randchr() for _ in range(40) ])
  304. self.console.addLine(line)
  305. self.queueSpew()
  306. def terminateMonitor(self):
  307. alphaInterval = self.fadeOut(self.console.getTextContainer(), 2)
  308. alphaInterval.setDoneEvent('fadeOutFinished')
  309. def fadeOutFinished():
  310. sys.exit(0)
  311. self.accept('fadeOutFinished', fadeOutFinished)
  312. alphaInterval.start()
  313. app = MyApp()
  314. app.run()