1 from copy
import deepcopy
3 from importlib
import import_module
4 from inspect
import isclass
6 from os
.path
import basename
7 from logging
import info
9 from panda3d
.core
import Texture
, TextNode
10 from direct
.gui
.OnscreenImage
import OnscreenImage
11 from direct
.gui
.DirectGui
import DirectButton
, DirectFrame
, DirectEntry
, \
12 YesNoDialog
, DirectOptionMenu
13 from direct
.gui
.DirectGuiGlobals
import FLAT
, NORMAL
14 from direct
.gui
.OnscreenText
import OnscreenText
15 from direct
.showbase
.DirectObject
import DirectObject
16 from pmachines
.items
.item
import Item
17 from pmachines
.items
.test_item
import PixelSpaceTestItem
, WorldSpaceTestItem
18 from pmachines
.editor
.scene_list
import SceneList
19 from pmachines
.editor
.inspector
import Inspector
, PixelSpaceInspector
, WorldSpaceInspector
20 from pmachines
.editor
.start_items
import StartItems
21 from ya2
.p3d
.gfx
import P3dGfxMgr
24 class SceneEditor(DirectObject
):
26 def __init__(self
, json
, json_name
, context
, add_item
, items
, world
, mouse_plane_node
):
31 self
.__mouse
_plane
_node
= mouse_plane_node
33 self
.__json
= json
= {
42 self
.__inspector
= None
43 self
.__context
= context
44 self
.__add
_item
= add_item
45 self
._font
= base
.loader
.load_font(
46 'assets/fonts/Hanken-Book.ttf')
48 self
._font
.set_pixels_per_unit(60)
49 self
._font
.set_minfilter(Texture
.FTLinearMipmapLinear
)
50 self
._font
.set_outline((0, 0, 0, 1), .8, .2)
53 'text_font': self
._font
,
54 'text_fg': (.9, .9, .9, 1),
56 'frameColor': (.4, .4, .4, .14),
57 'rolloverSound': loader
.load_sfx(
58 'assets/audio/sfx/rollover.ogg'),
59 'clickSound': loader
.load_sfx(
60 'assets/audio/sfx/click.ogg')}
61 w
, h
, tw
, l
= 1.8, 1, 30, .36
62 self
._frm
= DirectFrame(frameColor
=(.4, .4, .4, .06),
63 frameSize
=(0, w
, 0, h
),
64 parent
=base
.a2dBottomCenter
,
67 _('Filename'), pos
=(l
- .03, h
- .1), parent
=self
._frm
,
68 font
=self
._common
['text_font'],
69 scale
=self
._common
['scale'],
70 fg
=self
._common
['text_fg'],
71 align
=TextNode
.A_right
)
72 self
.__filenamename
_entry
= DirectEntry(
73 scale
=self
._common
['scale'],
77 frameColor
=self
._common
['frameColor'],
78 initialText
=json_name
,
80 text_fg
=self
._common
['text_fg'])
82 _('Name'), pos
=(l
- .03, h
- .2), parent
=self
._frm
,
83 font
=self
._common
['text_font'],
84 scale
=self
._common
['scale'],
85 fg
=self
._common
['text_fg'],
86 align
=TextNode
.A_right
)
87 self
.__name
_entry
= DirectEntry(
88 scale
=self
._common
['scale'],
92 frameColor
=self
._common
['frameColor'],
93 initialText
=json
['name'],
95 text_fg
=self
._common
['text_fg'])
97 _('Description'), pos
=(l
- .03, h
- .3), parent
=self
._frm
,
98 font
=self
._common
['text_font'],
99 scale
=self
._common
['scale'],
100 fg
=self
._common
['text_fg'],
101 align
=TextNode
.A_right
)
102 def add_line_break(txt
, entry
):
103 curpos
= entry
.node().getCursorPosition()
104 entry
.set(txt
[:curpos
]+ "\n" + txt
[curpos
:])
105 entry
.node().setCursorPosition(curpos
+1)
107 self
.__instructions
_entry
= DirectEntry(
108 scale
=self
._common
['scale'],
110 entryFont
=self
._font
,
114 frameColor
=self
._common
['frameColor'],
115 initialText
=json
['instructions'].replace('\\n', '\n'),
117 text_fg
=self
._common
['text_fg'])
118 self
.__instructions
_entry
['command'] = add_line_break
119 self
.__instructions
_entry
['extraArgs'] = [self
.__instructions
_entry
]
120 def load_images_btn(path
, col
):
123 (.6, .6, .6, 1), # ready
124 (1, 1, 1, 1), # press
125 (.8, .8, .8, 1), # rollover
131 (.4, .1, .1, .4)]}[col
]
132 return [self
.__load
_img
_btn
(path
, col
) for col
in colors
]
133 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
135 image
=load_images_btn('exitRight', 'gray'), scale
=.05,
137 parent
=self
._frm
, command
=self
.__on
_close
, state
=NORMAL
, relief
=FLAT
,
139 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
140 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
142 image
=load_images_btn('save', 'gray'), scale
=.05,
144 parent
=self
._frm
, command
=self
.__on
_save
, state
=NORMAL
, relief
=FLAT
,
146 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
147 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
149 image
=load_images_btn('menuList', 'gray'), scale
=.05,
151 parent
=self
._frm
, command
=self
.__on
_scene
_list
, state
=NORMAL
, relief
=FLAT
,
153 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
154 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
155 item_modules
= glob('pmachines/items/*.py')
156 item_modules
= [basename(i
)[:-3] for i
in item_modules
]
157 self
.__new
_items
= {}
158 for item_module
in item_modules
:
159 mod_name
= 'pmachines.items.' + item_module
160 for member
in import_module(mod_name
).__dict
__.values():
161 if isclass(member
) and issubclass(member
, Item
) and \
163 self
.__new
_items
[member
.__name
__] = member
165 _('new item'), pos
=(.02, .46), parent
=self
._frm
,
166 font
=self
._common
['text_font'],
167 scale
=self
._common
['scale'],
168 fg
=self
._common
['text_fg'],
169 align
=TextNode
.A_left
)
172 text
=_('new item'), pos
=(.02, 1, .4), items
=list(self
.__new
_items
.keys()),
173 parent
=self
._frm
, command
=self
.__on
_new
_item
, state
=NORMAL
,
174 relief
=FLAT
, item_relief
=FLAT
,
175 frameColor
=fcols
[0], item_frameColor
=fcols
[0],
176 popupMenu_frameColor
=fcols
[0],
177 popupMarker_frameColor
=fcols
[0],
178 text_font
=self
._font
, text_fg
=(.9, .9, .9, 1),
179 highlightColor
=(.9, .9, .9, .9),
180 item_text_font
=self
._font
, item_text_fg
=(.9, .9, .9, 1),
181 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
182 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
184 image
=load_images_btn('start_items', 'gray'), scale
=.05,
186 parent
=self
._frm
, command
=self
.__on
_start
_items
, state
=NORMAL
, relief
=FLAT
,
188 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
189 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
191 image
=load_images_btn('plus', 'gray'), scale
=.05,
193 parent
=self
._frm
, command
=self
.__on
_new
_scene
, state
=NORMAL
, relief
=FLAT
,
195 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
196 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
197 self
.__test
_items
= []
198 self
.__set
_test
_items
()
199 messenger
.send('editor-start')
200 self
.accept('editor-item-click', self
.__on
_item
_click
)
201 self
.accept('editor-inspector-destroy', self
.__on
_inspector
_destroy
)
202 self
.accept('editor-start-items-save', self
.__on
_start
_items
_save
)
203 self
.accept('editor-start-items-destroy', self
.__on
_start
_items
_destroy
)
205 def __set_test_items(self
):
206 for pixel_space_item
in self
.__json
['test_items']['pixel_space']:
207 print(pixel_space_item
['id'], pixel_space_item
['position'])
208 pos_pixel
= pixel_space_item
['position']
209 win_res
= base
.win
.getXSize(), base
.win
.getYSize()
210 pos_win
= (pos_pixel
[0] / win_res
[0] * 2 - 1,
211 1 - pos_pixel
[1] / win_res
[1] * 2)
212 p_from
, p_to
= P3dGfxMgr
.world_from_to(pos_win
)
213 hits
= self
.__world
.ray_test_all(p_from
, p_to
).get_hits()
214 for hit
in self
.__world
.ray_test_all(p_from
, p_to
).get_hits():
215 if hit
.get_node() == self
.__mouse
_plane
_node
:
216 pos
= hit
.get_hit_pos()
217 self
.__set
_test
_item
(pos
, pixel_space_item
, PixelSpaceTestItem
)
218 for world_space_item
in self
.__json
['test_items']['world_space']:
219 print(world_space_item
['id'], world_space_item
['position'])
220 self
.__set
_test
_item
(world_space_item
['position'], world_space_item
, WorldSpaceTestItem
)
222 def __set_test_item(self
, pos
, json
, item_class
):
223 test_item
= item_class(
224 self
.__context
.world
,
225 self
.__context
.plane_node
,
226 self
.__context
.cb_inst
,
227 self
.__context
.curr_bottom
,
228 self
.__context
.repos
,
230 pos
=(pos
[0], 0, pos
[-1]),
232 self
.__test
_items
+= [test_item
]
234 def __on_close(self
):
235 self
.__json
['name'] = self
.__name
_entry
.get()
236 self
.__json
['instructions'] = self
.__instructions
_entry
.get()
237 if self
.__compute
_hash
() == self
.__json
['version']:
240 self
.__dialog
= YesNoDialog(dialogName
='Unsaved changes',
241 text
=_('You have unsaved changes. Really quit?'),
242 command
=self
.__actually
_close
)
243 self
.__dialog
['frameColor'] = (.4, .4, .4, .14)
244 self
.__dialog
['relief'] = FLAT
245 self
.__dialog
.component('text0')['fg'] = (.9, .9, .9, 1)
246 self
.__dialog
.component('text0')['font'] = self
._font
247 for b
in self
.__dialog
.buttonList
:
248 b
['frameColor'] = (.4, .4, .4, .14)
249 b
.component('text0')['fg'] = (.9, .9, .9, 1)
250 b
.component('text0')['font'] = self
._font
251 b
.component('text1')['fg'] = (.9, .1, .1, 1)
252 b
.component('text1')['font'] = self
._font
253 b
.component('text2')['fg'] = (.9, .9, .1, 1)
254 b
.component('text2')['font'] = self
._font
256 def __on_new_item(self
, item
):
260 if item
in ['PixelSpaceTestItem', 'WorldSpaceTestItem']:
262 _item
= self
.__new
_items
[item
](
263 self
.__context
.world
,
264 self
.__context
.plane_node
,
265 self
.__context
.cb_inst
,
266 self
.__context
.curr_bottom
,
267 self
.__context
.repos
,
270 _item
._Item
__editing
= True
271 self
.__add
_item
(_item
)
272 item_json
['class'] = _item
.__class
__.__name
__
273 item_json
['position'] = list(_item
._np
.get_pos())
274 if item
== 'PixelSpaceTestItem':
275 del item_json
['class']
276 self
.__json
['test_items']['pixel_space'] += [item_json
]
277 elif item
== 'WorldSpaceTestItem':
278 del item_json
['class']
279 self
.__json
['test_items']['world_space'] += [item_json
]
281 self
.__json
['items'] += [item_json
]
283 def __actually_close(self
, arg
):
286 messenger
.send('editor-stop')
287 self
.__dialog
.cleanup()
290 self
.__json
['name'] = self
.__name
_entry
.get()
291 self
.__json
['instructions'] = self
.__instructions
_entry
.get()
292 self
.__json
['version'] = self
.__compute
_hash
()
293 json_name
= self
.__filenamename
_entry
.get()
294 with
open('assets/scenes/%s.json' % json_name
, 'w') as f
:
295 f
.write(dumps(self
.__json
, indent
=2, sort_keys
=True))
297 def __on_scene_list(self
):
300 def __load_img_btn(self
, path
, col
):
301 img
= OnscreenImage('assets/images/buttons/%s.dds' % path
)
302 img
.set_transparency(True)
307 def __compute_hash(self
):
308 new_dict
= deepcopy(self
.__json
)
309 del new_dict
['version']
310 new_dict_str
= str(dumps(new_dict
, indent
=2, sort_keys
=True))
311 h
= hashlib
.new('sha256')
312 h
.update(new_dict_str
.encode())
313 return h
.hexdigest()[:12]
315 def __on_item_click(self
, item
):
316 if self
.__inspector
and self
.__inspector
.item
== item
: return
318 self
.__inspector
.destroy()
319 if item
.__class
__ == PixelSpaceTestItem
:
320 self
.__inspector
= PixelSpaceInspector(item
, self
.__items
)
321 elif item
.__class
__ == WorldSpaceTestItem
:
322 self
.__inspector
= WorldSpaceInspector(item
, self
.__items
)
324 self
.__inspector
= Inspector(item
, self
.__items
)
326 def __on_inspector_destroy(self
):
327 self
.__inspector
= None
329 def __on_new_scene(self
):
331 messenger
.send('editor-stop')
332 messenger
.send('new_scene')
334 def __on_start_items(self
):
335 self
.__start
_items
= StartItems(self
.__json
['start_items'])
337 def __on_start_items_save(self
, start_items
):
338 self
.__json
['start_items'] = start_items
341 def __on_start_items_destroy(self
):
342 self
.__start
_items
= None
345 def test_items(self
):
346 return self
.__test
_items
351 self
.__inspector
.destroy()
352 self
.ignore('editor-item-click')
353 self
.ignore('editor-inspector-destroy')
354 self
.ignore('editor-start-items-save')
355 self
.ignore('editor-start-items-destroy')
356 for t
in self
.__test
_items
: t
.destroy()
357 self
.__test
_items
= []