Mercurial > fife-parpg
comparison clients/editor/plugins/objectedit.py @ 134:ade070598fd1
- added object editor plugin
NOTES:
- plugin is not ready for productive enviroments, yet
- lacks saving functionality
- some issues left, but it works better as previous versions ;-)
author | chewie@33b003aa-7bff-0310-803a-e67f0ece8222 |
---|---|
date | Sat, 13 Sep 2008 23:28:52 +0000 |
parents | |
children | 7dc59bd3d6b1 |
comparison
equal
deleted
inserted
replaced
133:cb35643ad1a7 | 134:ade070598fd1 |
---|---|
1 #!/usr/bin/env python | |
2 # coding: utf-8 | |
3 # ################################################### | |
4 # Copyright (C) 2008 The Zero-Projekt team | |
5 # http://zero-projekt.net | |
6 # info@zero-projekt.net | |
7 # This file is part of Zero "Was vom Morgen blieb" | |
8 # | |
9 # The Zero-Projekt codebase is free software; you can redistribute it and/or modify | |
10 # it under the terms of the GNU General Public License as published by | |
11 # the Free Software Foundation; either version 2 of the License, or | |
12 # (at your option) any later version. | |
13 # | |
14 # This program is distributed in the hope that it will be useful, | |
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
17 # GNU General Public License for more details. | |
18 # | |
19 # You should have received a copy of the GNU General Public License | |
20 # along with this program; if not, write to the | |
21 # Free Software Foundation, Inc., | |
22 # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | |
23 # ################################################### | |
24 | |
25 import fife | |
26 import plugin | |
27 import pychan | |
28 import pychan.widgets as widgets | |
29 from pychan.tools import callbackWithArguments as cbwa | |
30 | |
31 import settings as Settings | |
32 | |
33 class ObjectEdit(plugin.Plugin): | |
34 def __init__(self, engine, mapedit): | |
35 """ | |
36 ObjectEdit plugin for FIFEdit | |
37 | |
38 Mission: provide a gui mask to edit all important object data within the editor | |
39 (id, offsets, rotation, blocking, static) | |
40 | |
41 namespaces and object ids are excluded | |
42 | |
43 Current features: | |
44 - click instance and get all known data | |
45 - edit offsets, rotation, blocking, static | |
46 - outline highlighting of the selected object | |
47 - 3 data states: current, previous and default (so there is at least a one-step-undo) | |
48 | |
49 Missing features: | |
50 - object saving | |
51 - id saving (handled by Fifedit via save map, but we have to set the id from here) | |
52 - a lot of bug fixing concerning the rotation and the data records ^^ | |
53 - cleanup | |
54 | |
55 NOTE: | |
56 - this tool isn't ready for a working enviroment (yet) | |
57 """ | |
58 # Fifedit plugin data | |
59 self.menu_items = { 'ObjectEdit' : self.toggle_offsetedit } | |
60 | |
61 self._mapedit = mapedit | |
62 | |
63 # FIXME | |
64 # this is _very bad_ - but I need to change the current rotation code by providing | |
65 # project specific rotation angles. FIFE later should provide a list of the loaded | |
66 # object rotations (they are provided by the xml files, so we just need to use them...) | |
67 self._mapedit._objectedit_rotations = None | |
68 # end FIXME | |
69 self.active = False | |
70 | |
71 self.imagepool = engine.getImagePool() | |
72 self.animationpool = engine.getAnimationPool() | |
73 | |
74 self._camera = None | |
75 self._layer = None | |
76 | |
77 self.guidata = {} | |
78 self.objectdata = {} | |
79 | |
80 self._reset() | |
81 self.create_gui() | |
82 | |
83 | |
84 def _reset(self): | |
85 """ | |
86 resets all dynamic vars, but leaves out static ones (e.g. camera, layer) | |
87 | |
88 """ | |
89 self._instances = None | |
90 self._image = None | |
91 self._animation = False | |
92 self._rotation = None | |
93 self._avail_rotations = [] | |
94 self._namespace = None | |
95 self._blocking = 0 | |
96 self._static = 0 | |
97 self._object_id = None | |
98 self._instance_id = None | |
99 self._fixed_rotation = None | |
100 | |
101 self.guidata['instance_id'] = 'None' | |
102 self.guidata['object_id'] = 'None' | |
103 self.guidata['x_offset'] = 0 | |
104 self.guidata['y_offset'] = 0 | |
105 self.guidata['instance_rotation'] = 0 | |
106 self.guidata['namespace'] = 'None' | |
107 self.guidata['blocking'] = 0 | |
108 self.guidata['static'] = 0 | |
109 | |
110 if self._camera is not None: | |
111 self.renderer.removeAllOutlines() | |
112 | |
113 def create_gui(self): | |
114 """ | |
115 - creates the gui skeleton by loading the xml file | |
116 - finds some important childs and saves their widget in the object | |
117 """ | |
118 self.container = pychan.loadXML('gui/offsetedit.xml') | |
119 self.container.mapEvents({ | |
120 'x_offset_up' : cbwa(self.change_offset_x, 1), | |
121 'x_offset_dn' : cbwa(self.change_offset_x, -1), | |
122 | |
123 'y_offset_up' : cbwa(self.change_offset_y, 1), | |
124 'y_offset_dn' : cbwa(self.change_offset_y, -1), | |
125 | |
126 'use_data' : cbwa(self.use_user_data), | |
127 'previous_data' : cbwa(self.load_previous_data), | |
128 'default_data' : cbwa(self.load_default_data) | |
129 }) | |
130 | |
131 self._gui_anim_panel_wrapper = self.container.findChild(name="animation_panel_wrapper") | |
132 self._gui_anim_panel = self._gui_anim_panel_wrapper.findChild(name="animation_panel") | |
133 | |
134 self._gui_anim_panel_wrapper.removeChild(self._gui_anim_panel) | |
135 | |
136 self._gui_rotation_dropdown = self.container.findChild(name="select_rotations") | |
137 | |
138 self._gui_xoffset_textfield = self.container.findChild(name="x_offset") | |
139 self._gui_yoffset_textfield = self.container.findChild(name="y_offset") | |
140 | |
141 def _get_gui_size(self): | |
142 """ | |
143 gets the current size of the gui window and calculates new position | |
144 (atm top right corner) | |
145 """ | |
146 size = self.container._getSize() | |
147 self.position = ((Settings.ScreenWidth - 10 - size[0]), 10) | |
148 | |
149 def update_gui(self): | |
150 """ | |
151 updates the gui widgets with current instance data | |
152 | |
153 FIXME: | |
154 - drop animation support or turn it into something useful | |
155 """ | |
156 #if self._animation is False: | |
157 #try: | |
158 #self._gui_anim_panel_wrapper.removeChild(self._gui_anim_panel) | |
159 #except: | |
160 #pass | |
161 #elif self._animation is True: | |
162 #try: | |
163 #self._gui_anim_panel_wrapper.resizeToContent() | |
164 #self._gui_anim_panel_wrapper.addChild(self._gui_anim_panel) | |
165 #self._gui_anim_panel_wrapper.resizeToContent() | |
166 #except: | |
167 #pass | |
168 | |
169 self.container.distributeInitialData({ | |
170 'select_rotations' : self._avail_rotations, | |
171 'instance_id' : self.guidata['instance_id'], | |
172 'object_id' : self.guidata['object_id'], | |
173 'x_offset' : self.guidata['x_offset'], | |
174 'y_offset' : self.guidata['y_offset'], | |
175 'instance_rotation' : self.guidata['instance_rotation'], | |
176 'object_namespace' : self.guidata['namespace'], | |
177 'object_blocking' : self.guidata['blocking'], | |
178 'object_static' : self.guidata['static'], | |
179 }) | |
180 try: | |
181 print self._avail_rotations | |
182 print self._fixed_rotation | |
183 index = self._avail_rotations.index( str(self._fixed_rotation) ) | |
184 self._gui_rotation_dropdown._setSelected(index) | |
185 except: | |
186 # pass | |
187 print "Angle (", self._fixed_rotation, ") not supported by this instance" | |
188 | |
189 def toggle_gui(self): | |
190 """ | |
191 show / hide the gui | |
192 | |
193 FIXME: | |
194 - ATM not in use, needs some additional code when showing / hiding the gui (see input() ) | |
195 """ | |
196 if self.container.isVisible(): | |
197 self.container.hide() | |
198 else: | |
199 self.container.show() | |
200 | |
201 def toggle_offsetedit(self): | |
202 """ | |
203 - toggles the object editor activ / inactiv - just in case the user don't want to have | |
204 the gui popping up all the time while mapping :-) | |
205 - hides gui | |
206 """ | |
207 if self.active is True: | |
208 self.active = False | |
209 if self.container.isVisible(): | |
210 self.container.hide() | |
211 else: | |
212 self.active = True | |
213 | |
214 def highlight_selected_instance(self): | |
215 """ | |
216 just highlights selected instance | |
217 """ | |
218 self.renderer.removeAllOutlines() | |
219 self.renderer.addOutlined(self._instances[0], 205, 205, 205, 1) | |
220 | |
221 def change_offset_x(self, value=1): | |
222 """ | |
223 - callback for changing x offset | |
224 - changes x offset of current instance (image) | |
225 - updates gui | |
226 | |
227 @param int value the modifier for the x offset | |
228 """ | |
229 if self._image is not None: | |
230 self._image.setXShift(self._image.getXShift() + value) | |
231 | |
232 self.guidata['x_offset'] = str( self._image.getXShift() ) | |
233 self.update_gui() | |
234 | |
235 def change_offset_y(self, value=1): | |
236 """ | |
237 - callback for changing y offset | |
238 - changes y offset of current instance (image) | |
239 - updates gui | |
240 | |
241 @param int value the modifier for the y offset | |
242 """ | |
243 if self._image is not None: | |
244 self._image.setYShift(self._image.getYShift() + value) | |
245 | |
246 self.guidata['y_offset'] = str( self._image.getYShift() ) | |
247 self.update_gui() | |
248 | |
249 def use_user_data(self): | |
250 """ | |
251 - takes the users values and applies them directly to the current ._instance | |
252 - writes current data record | |
253 - writes previous data record | |
254 - updates gui | |
255 | |
256 FIXME: | |
257 - parse user data in case user think strings are considered to be integer offset values... | |
258 """ | |
259 xoffset = self._gui_xoffset_textfield._getText() | |
260 yoffset = self._gui_yoffset_textfield._getText() | |
261 | |
262 # workaround - dropdown list only has 2 entries, but sends 3 -> pychan bug? | |
263 if len(self._avail_rotations) < self._gui_rotation_dropdown._getSelected(): | |
264 index = len(self._avail_rotations) | |
265 else: | |
266 index = self._gui_rotation_dropdown._getSelected() | |
267 | |
268 # strange, but this helps to rotate the image correctly to the value the user selected | |
269 angle = int( self._avail_rotations[index] ) | |
270 angle = int(angle - abs( self._camera.getTilt() ) ) | |
271 if angle == 360: | |
272 angle = 0 | |
273 | |
274 self._instances[0].setRotation(angle) | |
275 self.get_instance_data(None, None, angle) | |
276 | |
277 try: | |
278 self._image.setXShift( int(xoffset) ) | |
279 except: | |
280 pass | |
281 # print "x offset must me of type int!" | |
282 try: | |
283 self._image.setYShift( int(yoffset) ) | |
284 except: | |
285 pass | |
286 # print "y offset must be of type int!" | |
287 | |
288 self.write_current_data() | |
289 self.objectdata[self._namespace][self._object_id]['previous'] = self.objectdata[self._namespace][self._object_id]['current'].copy() | |
290 self.update_gui() | |
291 | |
292 def load_previous_data(self): | |
293 """ | |
294 - writes a copy of the previous record back to the current record (aka one-step-undo) | |
295 - loads current data into class object | |
296 - updates gui | |
297 """ | |
298 self.objectdata[self._namespace][self._object_id]['current'] = self.objectdata[self._namespace][self._object_id]['previous'].copy() | |
299 self.load_current_data() | |
300 self.update_gui() | |
301 | |
302 def load_default_data(self): | |
303 """ | |
304 - writes a copy of the default record back to the current record | |
305 - loads current data into class object | |
306 - updates gui | |
307 """ | |
308 self.objectdata[self._namespace][self._object_id]['current'] = self.objectdata[self._namespace][self._object_id]['default'].copy() | |
309 self.load_current_data() | |
310 self.update_gui() | |
311 | |
312 def load_current_data(self): | |
313 """ | |
314 loads the current record into class object | |
315 """ | |
316 self._image = self.objectdata[self._namespace][self._object_id]['current']['image'] | |
317 self._animation = self.objectdata[self._namespace][self._object_id]['current']['animation'] | |
318 self._rotation = self.objectdata[self._namespace][self._object_id]['current']['rotation'] | |
319 self._fixed_rotation = self.objectdata[self._namespace][self._object_id]['current']['fixed_rotation'] | |
320 self._avail_rotations = self.objectdata[self._namespace][self._object_id]['current']['avail_rotations'] | |
321 self._blocking = self.objectdata[self._namespace][self._object_id]['current']['blocking'] | |
322 self._static = self.objectdata[self._namespace][self._object_id]['current']['static'] | |
323 self._instance_id = self.objectdata[self._namespace][self._object_id]['current']['instance_id'] | |
324 self._image.setXShift( self.objectdata[self._namespace][self._object_id]['current']['xoffset'] ) | |
325 self._image.setYShift( self.objectdata[self._namespace][self._object_id]['current']['yoffset'] ) | |
326 | |
327 self.write_current_guidata() | |
328 | |
329 def write_current_data(self): | |
330 """ | |
331 updates the current record | |
332 """ | |
333 self.objectdata[self._namespace][self._object_id]['current']['instance'] = self._instances[0] | |
334 self.objectdata[self._namespace][self._object_id]['current']['image'] = self._image | |
335 self.objectdata[self._namespace][self._object_id]['current']['animation'] = self._animation | |
336 self.objectdata[self._namespace][self._object_id]['current']['rotation'] = self._rotation | |
337 self.objectdata[self._namespace][self._object_id]['current']['fixed_rotation'] = self._fixed_rotation | |
338 self.objectdata[self._namespace][self._object_id]['current']['avail_rotations'] = self._avail_rotations | |
339 self.objectdata[self._namespace][self._object_id]['current']['blocking'] = self._blocking | |
340 self.objectdata[self._namespace][self._object_id]['current']['static'] = self._static | |
341 self.objectdata[self._namespace][self._object_id]['current']['instance_id'] = self._instance_id | |
342 self.objectdata[self._namespace][self._object_id]['current']['xoffset'] = self._image.getXShift() | |
343 self.objectdata[self._namespace][self._object_id]['current']['yoffset'] = self._image.getYShift() | |
344 | |
345 self.write_current_guidata() | |
346 | |
347 def write_current_guidata(self): | |
348 """ | |
349 updates the gui data with | |
350 """ | |
351 self.guidata['instance_rotation'] = str( self._instances[0].getRotation() ) | |
352 self.guidata['object_id'] = str( self._object_id ) | |
353 self.guidata['instance_id'] = str( self._instance_id ) | |
354 self.guidata['x_offset'] = str( self._image.getXShift() ) | |
355 self.guidata['y_offset'] = str( self._image.getYShift() ) | |
356 self.guidata['namespace'] = self._namespace | |
357 self.guidata['blocking'] = str( self._blocking ) | |
358 self.guidata['static'] = str( self._static ) | |
359 | |
360 def get_instance_data(self, timestamp=None, frame=None, angle=-1, instance=None): | |
361 """ | |
362 - grabs all available data from both object and instance | |
363 - checks if we already hold a record (namespace + object id) | |
364 | |
365 FIXME: | |
366 1.) we need to fix the instance rotation / rotation issue | |
367 2.) use correct instance rotations to store data for _each_ available rotation | |
368 3.) move record code out of this method | |
369 """ | |
370 visual = None | |
371 self._avail_rotations = [] | |
372 | |
373 if instance is None: | |
374 instance = self._instances[0] | |
375 | |
376 object = instance.getObject() | |
377 self._namespace = object.getNamespace() | |
378 self._object_id = object.getId() | |
379 | |
380 if angle != -1: | |
381 del self.objectdata[self._namespace][self._object_id] | |
382 | |
383 if not self.objectdata.has_key(self._namespace): | |
384 self.objectdata[self._namespace] = {} | |
385 | |
386 if not self.objectdata[self._namespace].has_key(self._object_id): | |
387 self.objectdata[self._namespace][self._object_id] = {} | |
388 | |
389 # we hold 3 versions of the data: current, previous, default | |
390 # default is only set one time, current and previous are changing data | |
391 # due to the users actions | |
392 self.objectdata[self._namespace][self._object_id]['current'] = {} | |
393 self.objectdata[self._namespace][self._object_id]['previous'] = {} | |
394 | |
395 self._instance_id = instance.getId() | |
396 | |
397 if self._instance_id == '': | |
398 self._instance_id = 'None' | |
399 | |
400 if angle == -1: | |
401 angle = int(instance.getRotation()) | |
402 else: | |
403 angle = int(angle) | |
404 | |
405 self._rotation = angle | |
406 | |
407 if object.isBlocking(): | |
408 self._blocking = 1 | |
409 | |
410 if object.isStatic(): | |
411 self._static = 1 | |
412 | |
413 try: | |
414 visual = object.get2dGfxVisual() | |
415 except: | |
416 print 'Fetching visual of object - failed. :/' | |
417 raise | |
418 | |
419 self._fixed_rotation = int(instance.getRotation() + abs( self._camera.getTilt() ) ) | |
420 self._fixed_rotation = visual.getClosestMatchingAngle(self._fixed_rotation) | |
421 | |
422 index = visual.getStaticImageIndexByAngle(self._fixed_rotation) | |
423 | |
424 if index == -1: | |
425 # object is an animation | |
426 self._animation = True | |
427 # no static image available, try default action | |
428 action = object.getDefaultAction() | |
429 if action: | |
430 animation_id = action.get2dGfxVisual().getAnimationIndexByAngle(self._fixed_rotation) | |
431 animation = self.animationpool.getAnimation(animation_id) | |
432 if timestamp is None and frame is not None: | |
433 self._image = animation.getFrame(frame) | |
434 elif timestamp is not None and frame is None: | |
435 self._image = animation.getFrameByTimestamp(timestamp) | |
436 else: | |
437 self._image = animation.getFrameByTimestamp(0) | |
438 index = self._image.getPoolId() | |
439 elif index != -1: | |
440 # object is a static image | |
441 self._animation = False | |
442 self._image = self.imagepool.getImage(index) | |
443 | |
444 if self._animation: | |
445 self._avail_rotations = Settings.RotAngles['animations'] | |
446 else: | |
447 rotation_tuple = visual.getStaticImageAngles() | |
448 for angle in rotation_tuple: | |
449 self._avail_rotations.append( str(angle) ) | |
450 | |
451 # FIXME: see l. 40 | |
452 self._mapedit._objectedit_rotations = self._avail_rotations | |
453 # end FIXME | |
454 self.write_current_data() | |
455 | |
456 self.objectdata[self._namespace][self._object_id]['default'] = {} | |
457 self.objectdata[self._namespace][self._object_id]['default'] = self.objectdata[self._namespace][self._object_id]['current'].copy() | |
458 self.objectdata[self._namespace][self._object_id]['previous'] = self.objectdata[self._namespace][self._object_id]['current'].copy() | |
459 | |
460 self.write_current_guidata() | |
461 else: | |
462 self.load_current_data() | |
463 | |
464 def dump_objectdata(self): | |
465 """ | |
466 just a useful dumper ^^ | |
467 """ | |
468 print "#"*4, "Dump of objectdata", "#"*4, "\n" | |
469 for namespace in self.objectdata: | |
470 print "namespace: ", namespace | |
471 for key in self.objectdata[namespace]: | |
472 print "\tkey: ", key | |
473 for item in self.objectdata[namespace][key]: | |
474 if len(item) >= 9: | |
475 tab = "\t"*1 | |
476 else: | |
477 tab = "\t"*2 | |
478 print "\t\t", item, " : ", tab, self.objectdata[namespace][key][item] | |
479 | |
480 def input(self): | |
481 """ | |
482 if called _and_ the user wishes to edit offsets, | |
483 gets instance data and show gui | |
484 | |
485 (see run.py, pump() ) | |
486 """ | |
487 if self._mapedit._instances != self._instances: | |
488 if self.active is True: | |
489 self._instances = self._mapedit._instances | |
490 | |
491 if self._camera is None: | |
492 self._camera = self._mapedit._camera | |
493 self.renderer = fife.InstanceRenderer.getInstance(self._camera) | |
494 | |
495 self._layer = self._mapedit._layer | |
496 | |
497 if self._instances != (): | |
498 self.highlight_selected_instance() | |
499 self.get_instance_data() | |
500 | |
501 if self._animation is False: | |
502 self.update_gui() | |
503 self.container.adaptLayout() | |
504 self.container.show() | |
505 self._get_gui_size() | |
506 self.container._setPosition(self.position) | |
507 else: | |
508 self.container.hide() | |
509 print "Animation objects are not yet editable" | |
510 # self.dump_objectdata() | |
511 else: | |
512 self._reset() | |
513 self.container.hide() |