ya2 · news · projects · code · about

editor: scene list
[pmachines.git] / pmachines / app.py
CommitLineData
8ee66edd 1import argparse
4894bb48 2import simplepbr
420ce99a 3#import gltf
012cf969 4from json import loads
d6c157a0 5from sys import platform, exit
9209a23b 6from platform import node
94a18c21 7from logging import info, debug
8ee66edd
FC
8from os.path import exists
9from os import makedirs
df4f85fc 10from multiprocessing import cpu_count
6fff1464 11from panda3d.core import Filename, load_prc_file_data, AntialiasAttrib, \
c991401b 12 WindowProperties, LVector2i, TextNode
1be87278 13from panda3d.bullet import BulletWorld, BulletDebugNode
8ee66edd 14from direct.showbase.ShowBase import ShowBase
882c058d 15from direct.gui.OnscreenText import OnscreenText
5964572b 16from direct.fsm.FSM import FSM
4586cbf6
FC
17from pmachines.audio.music import MusicMgr
18from pmachines.items.background import Background
19from pmachines.gui.menu import Menu
20from pmachines.scene import Scene
4586cbf6 21from pmachines.posmgr import PositionMgr
92c29685 22from pmachines.persistent import Persistent
b35b1f62
FC
23from ya2.utils.dictfile import DctFile
24from ya2.p3d.p3d import LibP3d
25from ya2.utils.lang import LangMgr
26from ya2.utils.log import LogMgr
27from ya2.utils.functional import FunctionalTest
1bfdf72a
FC
28from ya2.p3d.asserts import assert_threads, assert_tasks, assert_render3d, \
29 assert_render2d, assert_aspect2d, assert_events, assert_buffers
d6c157a0 30import sys
8ee66edd
FC
31
32
5964572b
FC
33class MainFsm(FSM):
34
35 def __init__(self, pmachines):
36 super().__init__('Main FSM')
37 self._pmachines = pmachines
38
39 def enterMenu(self):
40 self._pmachines.on_menu_enter()
41
42 def exitMenu(self):
43 self._pmachines.on_menu_exit()
1bfdf72a 44 self.__do_asserts()
5964572b 45
8c9bf90e
FC
46 def enterScene(self, cls):
47 self._pmachines.on_scene_enter(cls)
5964572b
FC
48
49 def exitScene(self):
50 self._pmachines.on_scene_exit()
1bfdf72a
FC
51 self.__do_asserts()
52
53 def __do_asserts(self):
54 args = self._pmachines._args
46c16ae5 55 if not LibP3d.runtime() or args.functional_test or args.functional_ref:
1bfdf72a
FC
56 assert_threads()
57 assert_tasks()
58 assert_render3d()
59 assert_render2d()
60 assert_aspect2d()
61 assert_events()
62 assert_buffers()
5964572b
FC
63
64
aad6ced2 65class Pmachines:
8ee66edd 66
012cf969
FC
67 @staticmethod
68 def scenes():
69 with open('assets/scenes/index.json') as f:
70 json = loads(f.read())
71 return json['list']
6168d0c2 72
8ee66edd 73 def __init__(self):
8ee66edd
FC
74 info('platform: %s' % platform)
75 info('exists main.py: %s' % exists('main.py'))
e982cdde 76 self._args = args = self._parse_args()
d18f757d
FC
77 self._configure(args)
78 self.base = ShowBase()
8ce16d6c 79 self._pipeline = None
aad6ced2
FC
80 self.is_update_run = args.update
81 self.is_version_run = args.version
aed9737a 82 self.log_mgr = LogMgr.init_cls()()
2d1773b1 83 self._pos_mgr = PositionMgr()
8ce16d6c 84 self._prepare_window(args)
8ee66edd
FC
85 if args.update:
86 return
361d3942 87 if args.functional_test:
edeef6f9 88 self._options['settings']['volume'] = 0
e1e44d5c 89 self._music = MusicMgr(self._options['settings']['volume'])
a0b33e12 90 self.lang_mgr = LangMgr(self._options['settings']['language'],
2aaa10d3
FC
91 'pmachines',
92 'assets/locale/')
5964572b 93 self._fsm = MainFsm(self)
d6c157a0
FC
94 if args.functional_test or args.functional_ref:
95 FunctionalTest(args.functional_ref, self._pos_mgr)
96 if not LibP3d.runtime() or args.functional_test or args.functional_ref:
97 self.__fps_lst = []
98 taskMgr.do_method_later(1.0, self.__assert_fps, 'assert_fps')
99
100 def start(self):
101 if self._args.screenshot:
f7eade0f 102 #cls = [cls for cls in self.scenes if cls.__name__ == self._args.screenshot][0]
012cf969 103 scene = Scene(BulletWorld(), None, True, False, lambda: None, self.scenes(), self._pos_mgr, None, None, None, self._args.screenshot)
a747111f
FC
104 scene.screenshot()
105 scene.destroy()
63e7aeb2
FC
106 exit()
107 elif self._options['development']['auto_start']:
d3a2e50a
FC
108 # mod_name = 'pmachines.scenes.scene_' + self._options['development']['auto_start']
109 # for member in import_module(mod_name).__dict__.values():
110 # if isclass(member) and issubclass(member, Scene) and \
111 # member != Scene:
112 # cls = member
113 self._fsm.demand('Scene', self._options['development']['auto_start'])
a2a89363 114 else:
107a889e 115 Scene.scenes_done = self.__persistent.scenes_done
a2a89363 116 self._fsm.demand('Menu')
5964572b
FC
117
118 def on_menu_enter(self):
119 self._menu_bg = Background()
a9aba267
FC
120 self._menu = Menu(
121 self._fsm, self.lang_mgr, self._options, self._music,
012cf969 122 self._pipeline, self.scenes(), self._args.functional_test or self._args.functional_ref,
2d1773b1 123 self._pos_mgr)
5964572b
FC
124
125 def on_home(self):
107a889e 126 Scene.scenes_done = self.__persistent.scenes_done
5964572b
FC
127 self._fsm.demand('Menu')
128
129 def on_menu_exit(self):
130 self._menu_bg.destroy()
4071c6d8 131 self._menu.destroy()
5964572b 132
f7eade0f 133 def on_scene_enter(self, scene_name):
1be87278 134 self._set_physics()
f7eade0f 135 self._scene = Scene(
e669403e 136 self.world, self.on_home,
31237524 137 self._options['development']['auto_close_instructions'],
9914cfc9 138 self._options['development']['debug_items'],
6168d0c2 139 self.reload,
012cf969 140 self.scenes(),
ce302b41 141 self._pos_mgr,
7fa58640 142 self._args.functional_test or self._args.functional_ref,
92c29685 143 self._options['development']['mouse_coords'],
f7eade0f 144 self.__persistent,
d3a2e50a
FC
145 scene_name,
146 self._options['development']['editor'],
147 self._options['development']['auto_start_editor'])
5964572b
FC
148
149 def on_scene_exit(self):
150 self._unset_physics()
151 self._scene.destroy()
8ee66edd 152
9914cfc9
FC
153 def reload(self, cls):
154 self._fsm.demand('Scene', cls)
155
d18f757d 156 def _configure(self, args):
8ee66edd 157 load_prc_file_data('', 'window-title pmachines')
4894bb48 158 load_prc_file_data('', 'framebuffer-srgb true')
a9e8696e 159 load_prc_file_data('', 'sync-video true')
d982c0a5 160 if args.functional_test or args.functional_ref:
addec9c9 161 load_prc_file_data('', 'win-size 1360 768')
d982c0a5 162 # otherwise it is not centered in exwm
9914cfc9
FC
163 # load_prc_file_data('', 'threading-model Cull/Draw')
164 # it freezes when you go to the next scene
a747111f 165 if args.screenshot:
d18f757d
FC
166 load_prc_file_data('', 'window-type offscreen')
167 load_prc_file_data('', 'audio-library-name null')
8ee66edd
FC
168
169 def _parse_args(self):
170 parser = argparse.ArgumentParser()
171 parser.add_argument('--update', action='store_true')
172 parser.add_argument('--version', action='store_true')
9ba5488b 173 parser.add_argument('--optfile')
a747111f 174 parser.add_argument('--screenshot')
361d3942 175 parser.add_argument('--functional-test', action='store_true')
edeef6f9 176 parser.add_argument('--functional-ref', action='store_true')
d6c157a0 177 cmd_line = [arg for arg in iter(sys.argv[1:]) if not arg.startswith('-psn_')]
8ee66edd
FC
178 args = parser.parse_args(cmd_line)
179 return args
180
9ba5488b 181 def _prepare_window(self, args):
8ee66edd
FC
182 data_path = ''
183 if (platform.startswith('win') or platform.startswith('linux')) and (
184 not exists('main.py') or __file__.startswith('/app/bin/')):
185 # it is the deployed version for windows
186 data_path = str(Filename.get_user_appdata_directory()) + '/pmachines'
187 home = '/home/flavio' # we must force this for wine
188 if data_path.startswith('/c/users/') and exists(home + '/.wine/'):
189 data_path = home + '/.wine/drive_' + data_path[1:]
190 info('creating dirs: %s' % data_path)
191 makedirs(data_path, exist_ok=True)
9ba5488b
FC
192 optfile = args.optfile if args.optfile else 'options.ini'
193 info('data path: %s' % data_path)
194 info('option file: %s' % optfile)
195 info('fixed path: %s' % LibP3d.fixpath(data_path + '/' + optfile))
196 default_opt = {
197 'settings': {
a0b33e12 198 'volume': 1,
6fff1464
FC
199 'language': 'en',
200 'fullscreen': 1,
a9aba267 201 'resolution': '',
5fdf77d0
FC
202 'antialiasing': 1,
203 'shadows': 1},
92c29685
FC
204 'save': {
205 'scenes_done': []
206 },
9ba5488b 207 'development': {
a5dc83f4 208 'simplepbr': 1,
9ba5488b 209 'verbose_log': 0,
e669403e
FC
210 'physics_debug': 0,
211 'auto_start': 0,
212 'auto_close_instructions': 0,
31237524 213 'show_buffers': 0,
b41381b2 214 'debug_items': 0,
882c058d 215 'mouse_coords': 0,
d3a2e50a
FC
216 'fps': 0,
217 'editor': 0,
218 'auto_start_editor': 0}}
9ba5488b
FC
219 opt_path = LibP3d.fixpath(data_path + '/' + optfile) if data_path else optfile
220 opt_exists = exists(opt_path)
221 self._options = DctFile(
222 LibP3d.fixpath(data_path + '/' + optfile) if data_path else optfile,
223 default_opt)
224 if not opt_exists:
225 self._options.store()
92c29685
FC
226 self.__persistent = Persistent(self._options['save']['scenes_done'], self._options)
227 Scene.scenes_done = self.__persistent.scenes_done
6fff1464
FC
228 res = self._options['settings']['resolution']
229 if res:
230 res = LVector2i(*[int(_res) for _res in res.split('x')])
231 else:
dd32d640 232 resolutions = []
aad6ced2 233 if not self.is_version_run:
dd32d640
FC
234 d_i = base.pipe.get_display_information()
235 def _res(idx):
236 return d_i.get_display_mode_width(idx), \
237 d_i.get_display_mode_height(idx)
238 resolutions = [
239 _res(idx) for idx in range(d_i.get_total_display_modes())]
6e6ca4ad 240 res = sorted(resolutions)[-1]
edeef6f9 241 fullscreen = self._options['settings']['fullscreen']
d982c0a5 242 props = WindowProperties()
edeef6f9 243 if args.functional_test or args.functional_ref:
edeef6f9 244 fullscreen = False
aad6ced2 245 elif not self.is_version_run:
d982c0a5 246 props.set_size(res)
edeef6f9 247 props.set_fullscreen(fullscreen)
7e487769 248 props.set_icon_filename('assets/images/icon/pmachines.ico')
66b856f5 249 if not args.screenshot and not self.is_version_run and base.win:
d18f757d 250 base.win.request_properties(props)
420ce99a 251 #gltf.patch_loader(base.loader)
66b856f5 252 if self._options['development']['simplepbr'] and not self.is_version_run and base.win:
a9aba267 253 self._pipeline = simplepbr.init(
a5dc83f4
FC
254 use_normal_maps=True,
255 use_emission_maps=False,
a9aba267 256 use_occlusion_maps=True,
5fdf77d0
FC
257 msaa_samples=4 if self._options['settings']['antialiasing'] else 1,
258 enable_shadows=int(self._options['settings']['shadows']))
94a18c21
FC
259 debug(f'msaa: {self._pipeline.msaa_samples}')
260 debug(f'shadows: {self._pipeline.enable_shadows}')
4894bb48 261 render.setAntialias(AntialiasAttrib.MAuto)
1be87278
FC
262 self.base.set_background_color(0, 0, 0, 1)
263 self.base.disable_mouse()
e669403e
FC
264 if self._options['development']['show_buffers']:
265 base.bufferViewer.toggleEnable()
b41381b2
FC
266 if self._options['development']['fps']:
267 base.set_frame_rate_meter(True)
651713a9
FC
268 #self.base.accept('window-event', self._on_win_evt)
269 self.base.accept('aspectRatioChanged', self._on_aspect_ratio_changed)
882c058d
FC
270 if self._options['development']['mouse_coords']:
271 coords_txt = OnscreenText(
272 '', parent=base.a2dTopRight, scale=0.04,
273 pos=(-.03, -.06), fg=(.9, .9, .9, 1), align=TextNode.A_right)
274 def update_coords(task):
275 txt = '%s %s' % (int(base.win.get_pointer(0).x),
276 int(base.win.get_pointer(0).y))
277 coords_txt['text'] = txt
278 return task.cont
279 taskMgr.add(update_coords, 'update_coords')
280
1be87278 281 def _set_physics(self):
9ba5488b
FC
282 if self._options['development']['physics_debug']:
283 debug_node = BulletDebugNode('Debug')
284 debug_node.show_wireframe(True)
285 debug_node.show_constraints(True)
286 debug_node.show_bounding_boxes(True)
287 debug_node.show_normals(True)
5964572b
FC
288 self._debug_np = render.attach_new_node(debug_node)
289 self._debug_np.show()
1be87278
FC
290 self.world = BulletWorld()
291 self.world.set_gravity((0, 0, -9.81))
9ba5488b 292 if self._options['development']['physics_debug']:
5964572b 293 self.world.set_debug_node(self._debug_np.node())
1be87278
FC
294 def update(task):
295 dt = globalClock.get_dt()
0625cf49 296 self.world.do_physics(dt, 10, 1/180)
1be87278 297 return task.cont
5964572b
FC
298 self._phys_tsk = taskMgr.add(update, 'update')
299
300 def _unset_physics(self):
301 if self._options['development']['physics_debug']:
302 self._debug_np.remove_node()
303 self.world = None
304 taskMgr.remove(self._phys_tsk)
651713a9
FC
305
306 def _on_aspect_ratio_changed(self):
5964572b
FC
307 if self._fsm.state == 'Scene':
308 self._scene.on_aspect_ratio_changed()
2ef21fc3
FC
309
310 def __assert_fps(self, task):
311 if len(self.__fps_lst) > 3:
312 self.__fps_lst.pop(0)
313 self.__fps_lst += [globalClock.average_frame_rate]
314 if len(self.__fps_lst) == 4:
9209a23b
FC
315 fps_threshold = 55 if cpu_count() >= 4 and node() != 'localhost.localdomain' else 10 # i.e. it is the builder machine
316 assert not all(fps < fps_threshold for fps in self.__fps_lst), 'low fps %s' % self.__fps_lst
2ef21fc3 317 return task.again
aad6ced2
FC
318
319 def run(self):
d6c157a0 320 self.start()
aad6ced2 321 self.base.run()