1 from collections
import namedtuple
3 from importlib
import import_module
4 from os
.path
import basename
5 from inspect
import isclass
6 from panda3d
.core
import Texture
, TextNode
7 from direct
.gui
.OnscreenImage
import OnscreenImage
8 from direct
.gui
.DirectGui
import DirectButton
, DirectFrame
, DirectEntry
, DirectOptionMenu
, OkDialog
9 from direct
.gui
.DirectGuiGlobals
import FLAT
, NORMAL
10 from direct
.gui
.OnscreenText
import OnscreenText
11 from direct
.showbase
.DirectObject
import DirectObject
12 from pmachines
.items
.item
import ItemStrategy
, FixedStrategy
, StillStrategy
13 from pmachines
.items
.box
import HitStrategy
14 from pmachines
.items
.domino
import DownStrategy
, UpStrategy
17 class Inspector(DirectObject
):
19 def __init__(self
, item
, all_items
):
22 self
.__all
_items
= all_items
23 self
._font
= base
.loader
.load_font(
24 'assets/fonts/Hanken-Book.ttf')
26 self
._font
.set_pixels_per_unit(60)
27 self
._font
.set_minfilter(Texture
.FTLinearMipmapLinear
)
28 self
._font
.set_outline((0, 0, 0, 1), .8, .2)
31 'text_font': self
._font
,
32 'text_fg': (.9, .9, .9, 1),
34 'frameColor': (.4, .4, .4, .14),
35 'rolloverSound': loader
.load_sfx(
36 'assets/audio/sfx/rollover.ogg'),
37 'clickSound': loader
.load_sfx(
38 'assets/audio/sfx/click.ogg')}
40 self
._frm
= DirectFrame(frameColor
=(.4, .4, .4, .06),
41 frameSize
=(0, w
, -h
, 0),
42 parent
=base
.a2dTopRight
,
45 p
= self
.__item
._np
.get_pos()
46 r
= self
.__item
._np
.get_r()
47 s
= self
.__item
._np
.get_scale()[0]
49 restitution
= self
.__item
._restitution
50 f
= self
.__item
._friction
52 if 'id' in self
.__item
.json
:
53 _id
= self
.__item
.json
['id']
55 if 'strategy' in self
.__item
.json
:
56 _strategy
= self
.__item
.json
['strategy']
58 if 'strategy_args' in self
.__item
.json
:
59 _strategy_args
= ' '.join(self
.__item
.json
['strategy_args'])
60 t
, pos_entry
= self
.__add
_row
(_('position'), f
'{round(p.x, 3)}, {round(p.z, 3)}', self
.on_edit_position
)
61 t
, rot_entry
= self
.__add
_row
(_('roll'), f
'{round(r, 3)}', self
.on_edit_roll
)
62 t
, scale_entry
= self
.__add
_row
(_('scale'), f
'{round(s, 3)}', self
.on_edit_scale
)
63 t
, mass_entry
= self
.__add
_row
(_('mass'), f
'{round(m, 3)}', self
.on_edit_mass
)
64 t
, restitution_entry
= self
.__add
_row
(_('restitution'), f
'{round(restitution, 3)}', self
.on_edit_restitution
)
65 t
, friction_entry
= self
.__add
_row
(_('friction'), f
'{round(f, 3)}', self
.on_edit_friction
)
66 t
, id_entry
= self
.__add
_row
(_('id'), _id
, self
.on_edit_id
)
67 t
, strategy_entry
= self
.__add
_row
_option
(_('strategy'), _strategy
, self
.on_edit_strategy
)
68 t
, strategy_args_entry
= self
.__add
_row
(_('strategy_args'), _strategy_args
, self
.on_edit_strategy_args
)
69 fields
= ['position', 'roll', 'scale', 'mass', 'restitution', 'friction', 'id', 'strategy', 'strategy_args']
70 Entries
= namedtuple('Entries', fields
)
71 self
.__entries
= Entries(pos_entry
, rot_entry
, scale_entry
, mass_entry
, restitution_entry
, friction_entry
, id_entry
, strategy_entry
, strategy_args_entry
)
72 def load_images_btn(path
, col
):
75 (.6, .6, .6, 1), # ready
77 (.8, .8, .8, 1), # rollover
83 (.4, .1, .1, .4)]}[col
]
84 return [self
.__load
_img
_btn
(path
, col
) for col
in colors
]
85 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
87 image
=load_images_btn('exitRight', 'gray'), scale
=.05,
88 pos
=(.06, 1, -h
+ .06),
89 parent
=self
._frm
, command
=self
.destroy
, state
=NORMAL
, relief
=FLAT
,
91 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
92 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
93 self
.accept('item-rototranslated', self
.__on
_item
_rototranslated
)
95 image
=load_images_btn('trashcan', 'gray'), scale
=.05,
96 pos
=(.18, 1, -h
+ .06),
97 parent
=self
._frm
, command
=self
.__delete
_item
, state
=NORMAL
, relief
=FLAT
,
99 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
100 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
102 def __add_row(self
, label
, text
, callback
):
106 pos
=(.03, self
.__z
), parent
=self
._frm
,
107 font
=self
._common
['text_font'],
108 scale
=self
._common
['scale'],
109 fg
=self
._common
['text_fg'],
110 wordwrap
=20, align
=TextNode
.ALeft
)
112 scale
=self
._common
['scale'],
113 pos
=(.30, 1, self
.__z
),
114 entryFont
=self
._font
,
117 frameColor
=self
._common
['frameColor'],
120 text_fg
=self
._common
['text_fg'],
125 def __add_row_option(self
, label
, text
, callback
):
126 item_modules
= glob('pmachines/items/*.py')
127 item_modules
= [basename(i
)[:-3] for i
in item_modules
]
129 for item_module
in item_modules
:
130 mod_name
= 'pmachines.items.' + item_module
131 for member
in import_module(mod_name
).__dict
__.values():
132 if isclass(member
) and issubclass(member
, ItemStrategy
) and \
133 member
!= ItemStrategy
:
134 new_items
= list(set(new_items
+ [member
.__name
__]))
137 pos
=(.03, self
.__z
), parent
=self
._frm
,
138 font
=self
._common
['text_font'],
139 scale
=self
._common
['scale'],
140 fg
=self
._common
['text_fg'],
141 wordwrap
=20, align
=TextNode
.ALeft
)
142 e
= DirectOptionMenu(
143 scale
=self
._common
['scale'],
145 pos
=(.30, 1, self
.__z
),
152 frameColor
=self
._common
['frameColor'],
153 item_frameColor
=self
._common
['frameColor'],
154 popupMenu_frameColor
=self
._common
['frameColor'],
155 popupMarker_frameColor
=self
._common
['frameColor'],
156 text_font
=self
._font
,
157 text_fg
=self
._common
['text_fg'],
158 highlightColor
=(.9, .9, .9, .9),
159 item_text_font
=self
._font
,
160 item_text_fg
=self
._common
['text_fg'],
161 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
162 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
166 def __load_img_btn(self
, path
, col
):
167 img
= OnscreenImage('assets/images/buttons/%s.dds' % path
)
168 img
.set_transparency(True)
173 def __on_item_rototranslated(self
, np
):
176 self
.__entries
.position
.set('%s %s' % (str(round(pos
.x
, 3)), str(round(pos
.z
, 3))))
177 self
.__entries
.roll
.set('%s' % str(round(r
, 3)))
178 self
.__item
.json
['position'] = list(pos
)
179 self
.__item
.json
['roll'] = round(r
, 3)
181 def __delete_item(self
):
182 messenger
.send('editor-inspector-delete', [self
.__item
])
189 def on_edit_position(self
, txt
):
190 x
, z
= map(float, txt
.split())
191 self
.__item
.position
= [x
, 0, z
]
193 def on_edit_roll(self
, txt
):
194 self
.__item
.roll
= float(txt
)
196 def on_edit_scale(self
, txt
):
197 self
.__item
.scale
= float(txt
)
199 def on_edit_mass(self
, txt
):
200 self
.__item
.mass
= float(txt
)
202 def on_edit_restitution(self
, txt
):
203 self
.__item
.restitution
= float(txt
)
205 def on_edit_friction(self
, txt
):
206 self
.__item
.friction
= float(txt
)
208 def on_edit_id(self
, txt
):
211 def on_edit_strategy(self
, txt
):
213 self
.__entries
.strategy_args
.set('')
216 'StillStrategy': StillStrategy
,
217 'UpStrategy': UpStrategy
,
218 'HitStrategy': HitStrategy
,
219 'DownStrategy': DownStrategy
,
220 'FixedStrategy': FixedStrategy
}
221 class_
= name2class
[txt
]
224 if txt
== 'StillStrategy':
225 args
+= [self
.__item
._np
]
226 if txt
in ['UpStrategy', 'DownStrategy']:
227 args
+= [self
.__item
._np
]
229 args
+= [float(self
.__entries
.strategy_args
.get())]
232 if txt
== 'HitStrategy':
233 for item
in self
.__all
_items
:
234 if item
.id == self
.__entries
.strategy_args
.get():
236 args
+= [self
.__item
.node
]
237 args
+= [self
.__item
._world
]
239 self
.__item
.strategy
= class_(*args
)
240 self
.__item
.strategy_json
= txt
242 self
.__show
_error
_popup
()
244 def on_edit_strategy_args(self
, txt
):
245 self
.__item
.strategy_args_json
= txt
247 def __show_error_popup(self
):
248 self
.__dialog
= OkDialog(dialogName
='Strategy args errors',
249 text
=_('There are errors in the strategy args.'),
250 command
=self
.__actually
_close
)
251 self
.__dialog
['frameColor'] = (.4, .4, .4, .14)
252 self
.__dialog
['relief'] = FLAT
253 self
.__dialog
.component('text0')['fg'] = (.9, .9, .9, 1)
254 self
.__dialog
.component('text0')['font'] = self
._font
255 for b
in self
.__dialog
.buttonList
:
256 b
['frameColor'] = (.4, .4, .4, .14)
257 b
.component('text0')['fg'] = (.9, .9, .9, 1)
258 b
.component('text0')['font'] = self
._font
259 b
.component('text1')['fg'] = (.9, .1, .1, 1)
260 b
.component('text1')['font'] = self
._font
261 b
.component('text2')['fg'] = (.9, .9, .1, 1)
262 b
.component('text2')['font'] = self
._font
264 def __actually_close(self
, arg
):
265 self
.__entries
.strategy
.set('')
266 self
.__entries
.strategy_args
.set('')
267 self
.__dialog
.cleanup()
271 self
.ignore('item-rototranslated')
272 messenger
.send('editor-inspector-destroy')
275 class PixelSpaceInspector(DirectObject
):
277 def __init__(self
, item
, all_items
):
280 self
.__all
_items
= all_items
281 self
._font
= base
.loader
.load_font(
282 'assets/fonts/Hanken-Book.ttf')
284 self
._font
.set_pixels_per_unit(60)
285 self
._font
.set_minfilter(Texture
.FTLinearMipmapLinear
)
286 self
._font
.set_outline((0, 0, 0, 1), .8, .2)
289 'text_font': self
._font
,
290 'text_fg': (.9, .9, .9, 1),
292 'frameColor': (.4, .4, .4, .14),
293 'rolloverSound': loader
.load_sfx(
294 'assets/audio/sfx/rollover.ogg'),
295 'clickSound': loader
.load_sfx(
296 'assets/audio/sfx/click.ogg')}
298 self
._frm
= DirectFrame(frameColor
=(.4, .4, .4, .06),
299 frameSize
=(0, w
, -h
, 0),
300 parent
=base
.a2dTopRight
,
303 p
= self
.__item
._np
.get_pos()
305 if 'id' in self
.__item
.json
:
306 _id
= self
.__item
.json
['id']
307 t
, pos_entry
= self
.__add
_row
(_('position'), f
'{round(p.x, 3)}, {round(p.z, 3)}', self
.on_edit_position
)
308 t
, id_entry
= self
.__add
_row
(_('id'), _id
, self
.on_edit_id
)
309 fields
= ['position', 'id']
310 Entries
= namedtuple('Entries', fields
)
311 self
.__entries
= Entries(pos_entry
, id_entry
)
312 def load_images_btn(path
, col
):
315 (.6, .6, .6, 1), # ready
316 (1, 1, 1, 1), # press
317 (.8, .8, .8, 1), # rollover
323 (.4, .1, .1, .4)]}[col
]
324 return [self
.__load
_img
_btn
(path
, col
) for col
in colors
]
325 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
327 image
=load_images_btn('exitRight', 'gray'), scale
=.05,
328 pos
=(.06, 1, -h
+ .06),
329 parent
=self
._frm
, command
=self
.destroy
, state
=NORMAL
, relief
=FLAT
,
331 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
332 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
333 self
.accept('item-rototranslated', self
.__on
_item
_rototranslated
)
335 image
=load_images_btn('trashcan', 'gray'), scale
=.05,
336 pos
=(.18, 1, -h
+ .06),
337 parent
=self
._frm
, command
=self
.__delete
_item
, state
=NORMAL
, relief
=FLAT
,
339 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
340 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
342 def __add_row(self
, label
, text
, callback
):
346 pos
=(.03, self
.__z
), parent
=self
._frm
,
347 font
=self
._common
['text_font'],
348 scale
=self
._common
['scale'],
349 fg
=self
._common
['text_fg'],
350 wordwrap
=20, align
=TextNode
.ALeft
)
352 scale
=self
._common
['scale'],
353 pos
=(.30, 1, self
.__z
),
354 entryFont
=self
._font
,
357 frameColor
=self
._common
['frameColor'],
360 text_fg
=self
._common
['text_fg'],
365 def __load_img_btn(self
, path
, col
):
366 img
= OnscreenImage('assets/images/buttons/%s.dds' % path
)
367 img
.set_transparency(True)
372 def __on_item_rototranslated(self
, np
):
373 pos
= np
.pos2d_pixel()
374 self
.__entries
.position
.set('%s %s' % (str(round(pos
[0], 3)), str(round(pos
[1], 3))))
375 self
.__item
.json
['position'] = list(pos
)
377 def __delete_item(self
):
378 messenger
.send('editor-inspector-delete', [self
.__item
])
385 def on_edit_position(self
, txt
):
386 x
, z
= map(float, txt
.split())
387 self
.__item
.position
= [x
, 0, z
]
389 def on_edit_id(self
, txt
):
392 def __actually_close(self
, arg
):
393 self
.__entries
.strategy
.set('')
394 self
.__entries
.strategy_args
.set('')
395 self
.__dialog
.cleanup()
399 self
.ignore('item-rototranslated')
400 messenger
.send('editor-inspector-destroy')
403 class WorldSpaceInspector(DirectObject
):
405 def __init__(self
, item
, all_items
):
408 self
.__all
_items
= all_items
409 self
._font
= base
.loader
.load_font(
410 'assets/fonts/Hanken-Book.ttf')
412 self
._font
.set_pixels_per_unit(60)
413 self
._font
.set_minfilter(Texture
.FTLinearMipmapLinear
)
414 self
._font
.set_outline((0, 0, 0, 1), .8, .2)
417 'text_font': self
._font
,
418 'text_fg': (.9, .9, .9, 1),
420 'frameColor': (.4, .4, .4, .14),
421 'rolloverSound': loader
.load_sfx(
422 'assets/audio/sfx/rollover.ogg'),
423 'clickSound': loader
.load_sfx(
424 'assets/audio/sfx/click.ogg')}
426 self
._frm
= DirectFrame(frameColor
=(.4, .4, .4, .06),
427 frameSize
=(0, w
, -h
, 0),
428 parent
=base
.a2dTopRight
,
431 p
= self
.__item
._np
.get_pos()
433 if 'id' in self
.__item
.json
:
434 _id
= self
.__item
.json
['id']
435 t
, pos_entry
= self
.__add
_row
(_('position'), f
'{round(p.x, 3)}, {round(p.z, 3)}', self
.on_edit_position
)
436 t
, id_entry
= self
.__add
_row
(_('id'), _id
, self
.on_edit_id
)
437 fields
= ['position', 'id']
438 Entries
= namedtuple('Entries', fields
)
439 self
.__entries
= Entries(pos_entry
, id_entry
)
440 def load_images_btn(path
, col
):
443 (.6, .6, .6, 1), # ready
444 (1, 1, 1, 1), # press
445 (.8, .8, .8, 1), # rollover
451 (.4, .1, .1, .4)]}[col
]
452 return [self
.__load
_img
_btn
(path
, col
) for col
in colors
]
453 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
455 image
=load_images_btn('exitRight', 'gray'), scale
=.05,
456 pos
=(.06, 1, -h
+ .06),
457 parent
=self
._frm
, command
=self
.destroy
, state
=NORMAL
, relief
=FLAT
,
459 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
460 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
461 self
.accept('item-rototranslated', self
.__on
_item
_rototranslated
)
463 image
=load_images_btn('trashcan', 'gray'), scale
=.05,
464 pos
=(.18, 1, -h
+ .06),
465 parent
=self
._frm
, command
=self
.__delete
_item
, state
=NORMAL
, relief
=FLAT
,
467 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
468 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
470 def __add_row(self
, label
, text
, callback
):
474 pos
=(.03, self
.__z
), parent
=self
._frm
,
475 font
=self
._common
['text_font'],
476 scale
=self
._common
['scale'],
477 fg
=self
._common
['text_fg'],
478 wordwrap
=20, align
=TextNode
.ALeft
)
480 scale
=self
._common
['scale'],
481 pos
=(.30, 1, self
.__z
),
482 entryFont
=self
._font
,
485 frameColor
=self
._common
['frameColor'],
488 text_fg
=self
._common
['text_fg'],
493 def __load_img_btn(self
, path
, col
):
494 img
= OnscreenImage('assets/images/buttons/%s.dds' % path
)
495 img
.set_transparency(True)
500 def __on_item_rototranslated(self
, np
):
502 self
.__entries
.position
.set('%s %s' % (str(round(pos
.x
, 3)), str(round(pos
.z
, 3))))
503 self
.__item
.json
['position'] = list(pos
)
505 def __delete_item(self
):
506 messenger
.send('editor-inspector-delete', [self
.__item
])
513 def on_edit_position(self
, txt
):
514 x
, z
= map(float, txt
.split())
515 self
.__item
.position
= [x
, 0, z
]
517 def on_edit_id(self
, txt
):
520 def __actually_close(self
, arg
):
521 self
.__entries
.strategy
.set('')
522 self
.__entries
.strategy_args
.set('')
523 self
.__dialog
.cleanup()
527 self
.ignore('item-rototranslated')
528 messenger
.send('editor-inspector-destroy')