Mercurial > fife-parpg
changeset 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 | cb35643ad1a7 |
children | 52d4a149389d |
files | clients/editor/gui/offsetedit.xml clients/editor/plugins/mapeditor.py clients/editor/plugins/objectedit.py clients/editor/run.py clients/editor/settings.py |
diffstat | 5 files changed, 640 insertions(+), 4 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/clients/editor/gui/offsetedit.xml Sat Sep 13 23:28:52 2008 +0000 @@ -0,0 +1,74 @@ +<Window title="Object editor" position="10,700"> <!-- size="200,250" min_size="200,250" max_size="200,250" > --> + + <Label text="Namespace:" min_size="85,20"/> + <TextBox text="None" name="object_namespace" min_size="30,20"/> + + <HBox> + <Label text="Object ID:" min_size="85,20"/> + <TextBox text="None" name="object_id" min_size="30,20"/> + </HBox> + <HBox> + <Label text="Blocking:" min_size="45,20"/> + <TextBox text="0" name="object_blocking" min_size="20,20"/> + + <Label text="Static:" min_size="45,20"/> + <TextBox text="0" name="object_static" min_size="20,20"/> + </HBox> + + + <HBox> + <Label text="Select Rotation:" min_size="85,20" /> + </HBox> + + <DropDown min_size="80,0" name="select_rotations"/> + + <VBox> + <HBox> + <TextBox text="0" name="x_offset" size="30,20" min_size="30,20" max_size="30,20" /> + <Button name="x_offset_up" text="+" max_size="20,20"/> + <Button name="x_offset_dn" text="-" max_size="20,20"/> + </HBox> + + <HBox> + <TextBox text="0" name="y_offset" size="30,20" min_size="30,20" max_size="30,20"/> + <Button name="y_offset_up" text="+" max_size="20,20"/> + <Button name="y_offset_dn" text="-" max_size="20,20"/> + </HBox> + </VBox> + + <Label text="Selected Instance" min_size="85,20" /> + + <HBox > + <Label text="Instance ID:" min_size="85,20"/> + <TextBox text="None" name="instance_id" min_size="30,20"/> + </HBox> + <HBox > + <Label text="Instance rot:" min_size="85,20"/> + <TextBox text="0" name="instance_rotation" min_size="30,20"/> + </HBox> + + + + <VBox name="animation_panel_wrapper" max_size="150,50"> + <Spacer /> + <VBox name="animation_panel"> + <Label text="Animation panel" min_size="85,20" /> + <HBox> + <Button name="anim_start_pos" text="S" max_size="20,20"/> + <Button name="anim_left" text="l1" max_size="20,20"/> + <TextBox name="anim_current_frame" text="0" min_size="30,20"/> + <Button name="anim_right" text="r1" max_size="20,20"/> + <Button name="anim_end_pos" text="E" max_size="20,20"/> + </HBox> + </VBox> + </VBox> + + <HBox> + <Button name="use_data" text="Use"/> + <Button name="previous_data" text="Previous"/> + <Button name="default_data" text="Default"/> + </HBox> + <Spacer /> + <Button name="change_data" text="Save object"/> + +</Window>
--- a/clients/editor/plugins/mapeditor.py Sat Aug 30 12:58:20 2008 +0000 +++ b/clients/editor/plugins/mapeditor.py Sat Sep 13 23:28:52 2008 +0000 @@ -13,7 +13,7 @@ from pychan.manager import DEFAULT_STYLE DEFAULT_STYLE['default']['base_color'] = fife.Color(85,128,151) -SCROLL_TOLERANCE = 30 +SCROLL_TOLERANCE = 10 SCROLL_SPEED = 1.0 states = ('NOTHING_LOADED', 'VIEWING', 'INSERTING', 'REMOVING', 'MOVING') @@ -332,7 +332,37 @@ self._assert(self._layer, 'No layer assigned in %s' % mname) for i in self._getInstancesFromPosition(self._selection, top_only=True): - i.setRotation((i.getRotation() + 90) % 360) +# by c 09/11/08 +# FIXME: + # "hardcoded" rotation is bad for offset editing + # instead we should use the angle list given from the object + # animations are an issue, as a workaround settings.py provides + # project specific animation angles + try: + if self._objectedit_rotations is not None: +# print "available angles: ", self._offsetedit_rotations + rotation_prev = i.getRotation() +# print "previous rotation: ", rotation_prev + length = len(self._offsetedit_rotations) +# print "length: ", length + index = self._offsetedit_rotations.index( str(rotation_prev) ) +# print "index, old: ", index + if index < length - 1: + index += 1 + elif index == length: + index = 0 + else: + index = 0 +# print "index, new: ", index + + i.setRotation( int(self._offsetedit_rotations[index]) ) +# print "new rotation: ", self._offsetedit_rotations[index] + except: + # Fallback + i.setRotation((i.getRotation() + 90) % 360) +# end FIXME +# end edit c + ## Surprisingly, the following "snap-to-rotation" code is actually incorrect. Object ## rotation is independent of the camera, whereas the choice of an actual rotation image ## depends very much on how the camera is situated. For example, suppose an object has
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/clients/editor/plugins/objectedit.py Sat Sep 13 23:28:52 2008 +0000 @@ -0,0 +1,513 @@ +#!/usr/bin/env python +# coding: utf-8 +# ################################################### +# Copyright (C) 2008 The Zero-Projekt team +# http://zero-projekt.net +# info@zero-projekt.net +# This file is part of Zero "Was vom Morgen blieb" +# +# The Zero-Projekt codebase is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the +# Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# ################################################### + +import fife +import plugin +import pychan +import pychan.widgets as widgets +from pychan.tools import callbackWithArguments as cbwa + +import settings as Settings + +class ObjectEdit(plugin.Plugin): + def __init__(self, engine, mapedit): + """ + ObjectEdit plugin for FIFEdit + + Mission: provide a gui mask to edit all important object data within the editor + (id, offsets, rotation, blocking, static) + + namespaces and object ids are excluded + + Current features: + - click instance and get all known data + - edit offsets, rotation, blocking, static + - outline highlighting of the selected object + - 3 data states: current, previous and default (so there is at least a one-step-undo) + + Missing features: + - object saving + - id saving (handled by Fifedit via save map, but we have to set the id from here) + - a lot of bug fixing concerning the rotation and the data records ^^ + - cleanup + + NOTE: + - this tool isn't ready for a working enviroment (yet) + """ + # Fifedit plugin data + self.menu_items = { 'ObjectEdit' : self.toggle_offsetedit } + + self._mapedit = mapedit + +# FIXME + # this is _very bad_ - but I need to change the current rotation code by providing + # project specific rotation angles. FIFE later should provide a list of the loaded + # object rotations (they are provided by the xml files, so we just need to use them...) + self._mapedit._objectedit_rotations = None +# end FIXME + self.active = False + + self.imagepool = engine.getImagePool() + self.animationpool = engine.getAnimationPool() + + self._camera = None + self._layer = None + + self.guidata = {} + self.objectdata = {} + + self._reset() + self.create_gui() + + + def _reset(self): + """ + resets all dynamic vars, but leaves out static ones (e.g. camera, layer) + + """ + self._instances = None + self._image = None + self._animation = False + self._rotation = None + self._avail_rotations = [] + self._namespace = None + self._blocking = 0 + self._static = 0 + self._object_id = None + self._instance_id = None + self._fixed_rotation = None + + self.guidata['instance_id'] = 'None' + self.guidata['object_id'] = 'None' + self.guidata['x_offset'] = 0 + self.guidata['y_offset'] = 0 + self.guidata['instance_rotation'] = 0 + self.guidata['namespace'] = 'None' + self.guidata['blocking'] = 0 + self.guidata['static'] = 0 + + if self._camera is not None: + self.renderer.removeAllOutlines() + + def create_gui(self): + """ + - creates the gui skeleton by loading the xml file + - finds some important childs and saves their widget in the object + """ + self.container = pychan.loadXML('gui/offsetedit.xml') + self.container.mapEvents({ + 'x_offset_up' : cbwa(self.change_offset_x, 1), + 'x_offset_dn' : cbwa(self.change_offset_x, -1), + + 'y_offset_up' : cbwa(self.change_offset_y, 1), + 'y_offset_dn' : cbwa(self.change_offset_y, -1), + + 'use_data' : cbwa(self.use_user_data), + 'previous_data' : cbwa(self.load_previous_data), + 'default_data' : cbwa(self.load_default_data) + }) + + self._gui_anim_panel_wrapper = self.container.findChild(name="animation_panel_wrapper") + self._gui_anim_panel = self._gui_anim_panel_wrapper.findChild(name="animation_panel") + + self._gui_anim_panel_wrapper.removeChild(self._gui_anim_panel) + + self._gui_rotation_dropdown = self.container.findChild(name="select_rotations") + + self._gui_xoffset_textfield = self.container.findChild(name="x_offset") + self._gui_yoffset_textfield = self.container.findChild(name="y_offset") + + def _get_gui_size(self): + """ + gets the current size of the gui window and calculates new position + (atm top right corner) + """ + size = self.container._getSize() + self.position = ((Settings.ScreenWidth - 10 - size[0]), 10) + + def update_gui(self): + """ + updates the gui widgets with current instance data + + FIXME: + - drop animation support or turn it into something useful + """ + #if self._animation is False: + #try: + #self._gui_anim_panel_wrapper.removeChild(self._gui_anim_panel) + #except: + #pass + #elif self._animation is True: + #try: + #self._gui_anim_panel_wrapper.resizeToContent() + #self._gui_anim_panel_wrapper.addChild(self._gui_anim_panel) + #self._gui_anim_panel_wrapper.resizeToContent() + #except: + #pass + + self.container.distributeInitialData({ + 'select_rotations' : self._avail_rotations, + 'instance_id' : self.guidata['instance_id'], + 'object_id' : self.guidata['object_id'], + 'x_offset' : self.guidata['x_offset'], + 'y_offset' : self.guidata['y_offset'], + 'instance_rotation' : self.guidata['instance_rotation'], + 'object_namespace' : self.guidata['namespace'], + 'object_blocking' : self.guidata['blocking'], + 'object_static' : self.guidata['static'], + }) + try: + print self._avail_rotations + print self._fixed_rotation + index = self._avail_rotations.index( str(self._fixed_rotation) ) + self._gui_rotation_dropdown._setSelected(index) + except: +# pass + print "Angle (", self._fixed_rotation, ") not supported by this instance" + + def toggle_gui(self): + """ + show / hide the gui + + FIXME: + - ATM not in use, needs some additional code when showing / hiding the gui (see input() ) + """ + if self.container.isVisible(): + self.container.hide() + else: + self.container.show() + + def toggle_offsetedit(self): + """ + - toggles the object editor activ / inactiv - just in case the user don't want to have + the gui popping up all the time while mapping :-) + - hides gui + """ + if self.active is True: + self.active = False + if self.container.isVisible(): + self.container.hide() + else: + self.active = True + + def highlight_selected_instance(self): + """ + just highlights selected instance + """ + self.renderer.removeAllOutlines() + self.renderer.addOutlined(self._instances[0], 205, 205, 205, 1) + + def change_offset_x(self, value=1): + """ + - callback for changing x offset + - changes x offset of current instance (image) + - updates gui + + @param int value the modifier for the x offset + """ + if self._image is not None: + self._image.setXShift(self._image.getXShift() + value) + + self.guidata['x_offset'] = str( self._image.getXShift() ) + self.update_gui() + + def change_offset_y(self, value=1): + """ + - callback for changing y offset + - changes y offset of current instance (image) + - updates gui + + @param int value the modifier for the y offset + """ + if self._image is not None: + self._image.setYShift(self._image.getYShift() + value) + + self.guidata['y_offset'] = str( self._image.getYShift() ) + self.update_gui() + + def use_user_data(self): + """ + - takes the users values and applies them directly to the current ._instance + - writes current data record + - writes previous data record + - updates gui + + FIXME: + - parse user data in case user think strings are considered to be integer offset values... + """ + xoffset = self._gui_xoffset_textfield._getText() + yoffset = self._gui_yoffset_textfield._getText() + + # workaround - dropdown list only has 2 entries, but sends 3 -> pychan bug? + if len(self._avail_rotations) < self._gui_rotation_dropdown._getSelected(): + index = len(self._avail_rotations) + else: + index = self._gui_rotation_dropdown._getSelected() + + # strange, but this helps to rotate the image correctly to the value the user selected + angle = int( self._avail_rotations[index] ) + angle = int(angle - abs( self._camera.getTilt() ) ) + if angle == 360: + angle = 0 + + self._instances[0].setRotation(angle) + self.get_instance_data(None, None, angle) + + try: + self._image.setXShift( int(xoffset) ) + except: + pass +# print "x offset must me of type int!" + try: + self._image.setYShift( int(yoffset) ) + except: + pass +# print "y offset must be of type int!" + + self.write_current_data() + self.objectdata[self._namespace][self._object_id]['previous'] = self.objectdata[self._namespace][self._object_id]['current'].copy() + self.update_gui() + + def load_previous_data(self): + """ + - writes a copy of the previous record back to the current record (aka one-step-undo) + - loads current data into class object + - updates gui + """ + self.objectdata[self._namespace][self._object_id]['current'] = self.objectdata[self._namespace][self._object_id]['previous'].copy() + self.load_current_data() + self.update_gui() + + def load_default_data(self): + """ + - writes a copy of the default record back to the current record + - loads current data into class object + - updates gui + """ + self.objectdata[self._namespace][self._object_id]['current'] = self.objectdata[self._namespace][self._object_id]['default'].copy() + self.load_current_data() + self.update_gui() + + def load_current_data(self): + """ + loads the current record into class object + """ + self._image = self.objectdata[self._namespace][self._object_id]['current']['image'] + self._animation = self.objectdata[self._namespace][self._object_id]['current']['animation'] + self._rotation = self.objectdata[self._namespace][self._object_id]['current']['rotation'] + self._fixed_rotation = self.objectdata[self._namespace][self._object_id]['current']['fixed_rotation'] + self._avail_rotations = self.objectdata[self._namespace][self._object_id]['current']['avail_rotations'] + self._blocking = self.objectdata[self._namespace][self._object_id]['current']['blocking'] + self._static = self.objectdata[self._namespace][self._object_id]['current']['static'] + self._instance_id = self.objectdata[self._namespace][self._object_id]['current']['instance_id'] + self._image.setXShift( self.objectdata[self._namespace][self._object_id]['current']['xoffset'] ) + self._image.setYShift( self.objectdata[self._namespace][self._object_id]['current']['yoffset'] ) + + self.write_current_guidata() + + def write_current_data(self): + """ + updates the current record + """ + self.objectdata[self._namespace][self._object_id]['current']['instance'] = self._instances[0] + self.objectdata[self._namespace][self._object_id]['current']['image'] = self._image + self.objectdata[self._namespace][self._object_id]['current']['animation'] = self._animation + self.objectdata[self._namespace][self._object_id]['current']['rotation'] = self._rotation + self.objectdata[self._namespace][self._object_id]['current']['fixed_rotation'] = self._fixed_rotation + self.objectdata[self._namespace][self._object_id]['current']['avail_rotations'] = self._avail_rotations + self.objectdata[self._namespace][self._object_id]['current']['blocking'] = self._blocking + self.objectdata[self._namespace][self._object_id]['current']['static'] = self._static + self.objectdata[self._namespace][self._object_id]['current']['instance_id'] = self._instance_id + self.objectdata[self._namespace][self._object_id]['current']['xoffset'] = self._image.getXShift() + self.objectdata[self._namespace][self._object_id]['current']['yoffset'] = self._image.getYShift() + + self.write_current_guidata() + + def write_current_guidata(self): + """ + updates the gui data with + """ + self.guidata['instance_rotation'] = str( self._instances[0].getRotation() ) + self.guidata['object_id'] = str( self._object_id ) + self.guidata['instance_id'] = str( self._instance_id ) + self.guidata['x_offset'] = str( self._image.getXShift() ) + self.guidata['y_offset'] = str( self._image.getYShift() ) + self.guidata['namespace'] = self._namespace + self.guidata['blocking'] = str( self._blocking ) + self.guidata['static'] = str( self._static ) + + def get_instance_data(self, timestamp=None, frame=None, angle=-1, instance=None): + """ + - grabs all available data from both object and instance + - checks if we already hold a record (namespace + object id) + + FIXME: + 1.) we need to fix the instance rotation / rotation issue + 2.) use correct instance rotations to store data for _each_ available rotation + 3.) move record code out of this method + """ + visual = None + self._avail_rotations = [] + + if instance is None: + instance = self._instances[0] + + object = instance.getObject() + self._namespace = object.getNamespace() + self._object_id = object.getId() + + if angle != -1: + del self.objectdata[self._namespace][self._object_id] + + if not self.objectdata.has_key(self._namespace): + self.objectdata[self._namespace] = {} + + if not self.objectdata[self._namespace].has_key(self._object_id): + self.objectdata[self._namespace][self._object_id] = {} + + # we hold 3 versions of the data: current, previous, default + # default is only set one time, current and previous are changing data + # due to the users actions + self.objectdata[self._namespace][self._object_id]['current'] = {} + self.objectdata[self._namespace][self._object_id]['previous'] = {} + + self._instance_id = instance.getId() + + if self._instance_id == '': + self._instance_id = 'None' + + if angle == -1: + angle = int(instance.getRotation()) + else: + angle = int(angle) + + self._rotation = angle + + if object.isBlocking(): + self._blocking = 1 + + if object.isStatic(): + self._static = 1 + + try: + visual = object.get2dGfxVisual() + except: + print 'Fetching visual of object - failed. :/' + raise + + self._fixed_rotation = int(instance.getRotation() + abs( self._camera.getTilt() ) ) + self._fixed_rotation = visual.getClosestMatchingAngle(self._fixed_rotation) + + index = visual.getStaticImageIndexByAngle(self._fixed_rotation) + + if index == -1: + # object is an animation + self._animation = True + # no static image available, try default action + action = object.getDefaultAction() + if action: + animation_id = action.get2dGfxVisual().getAnimationIndexByAngle(self._fixed_rotation) + animation = self.animationpool.getAnimation(animation_id) + if timestamp is None and frame is not None: + self._image = animation.getFrame(frame) + elif timestamp is not None and frame is None: + self._image = animation.getFrameByTimestamp(timestamp) + else: + self._image = animation.getFrameByTimestamp(0) + index = self._image.getPoolId() + elif index != -1: + # object is a static image + self._animation = False + self._image = self.imagepool.getImage(index) + + if self._animation: + self._avail_rotations = Settings.RotAngles['animations'] + else: + rotation_tuple = visual.getStaticImageAngles() + for angle in rotation_tuple: + self._avail_rotations.append( str(angle) ) + +# FIXME: see l. 40 + self._mapedit._objectedit_rotations = self._avail_rotations +# end FIXME + self.write_current_data() + + self.objectdata[self._namespace][self._object_id]['default'] = {} + self.objectdata[self._namespace][self._object_id]['default'] = self.objectdata[self._namespace][self._object_id]['current'].copy() + self.objectdata[self._namespace][self._object_id]['previous'] = self.objectdata[self._namespace][self._object_id]['current'].copy() + + self.write_current_guidata() + else: + self.load_current_data() + + def dump_objectdata(self): + """ + just a useful dumper ^^ + """ + print "#"*4, "Dump of objectdata", "#"*4, "\n" + for namespace in self.objectdata: + print "namespace: ", namespace + for key in self.objectdata[namespace]: + print "\tkey: ", key + for item in self.objectdata[namespace][key]: + if len(item) >= 9: + tab = "\t"*1 + else: + tab = "\t"*2 + print "\t\t", item, " : ", tab, self.objectdata[namespace][key][item] + + def input(self): + """ + if called _and_ the user wishes to edit offsets, + gets instance data and show gui + + (see run.py, pump() ) + """ + if self._mapedit._instances != self._instances: + if self.active is True: + self._instances = self._mapedit._instances + + if self._camera is None: + self._camera = self._mapedit._camera + self.renderer = fife.InstanceRenderer.getInstance(self._camera) + + self._layer = self._mapedit._layer + + if self._instances != (): + self.highlight_selected_instance() + self.get_instance_data() + + if self._animation is False: + self.update_gui() + self.container.adaptLayout() + self.container.show() + self._get_gui_size() + self.container._setPosition(self.position) + else: + self.container.hide() + print "Animation objects are not yet editable" +# self.dump_objectdata() + else: + self._reset() + self.container.hide()
--- a/clients/editor/run.py Sat Aug 30 12:58:20 2008 +0000 +++ b/clients/editor/run.py Sat Sep 13 23:28:52 2008 +0000 @@ -28,6 +28,9 @@ from plugins.mapeditor import MapEditor from plugins.mapwizard import MapWizard from fifedit import Fifedit +# by c 09/11/08 +from plugins.objectedit import ObjectEdit +# end edit c # Help display class Help(Plugin): @@ -57,7 +60,9 @@ self.mapsaver = MapSaver(self.engine) self.mapwizard = MapWizard(self.engine) self.importer = Importer(self.engine) - +# by c 09/11/08 + self.objectedit = ObjectEdit(self.engine, self.mapedit) +# end edit c # Register plugins with Fifedit. self.fifedit.registerPlugin(Help()) self.fifedit.registerPlugin(self.maploader) @@ -65,7 +70,9 @@ self.fifedit.registerPlugin(self.mapedit) self.fifedit.registerPlugin(self.mapwizard) self.fifedit.registerPlugin(self.importer) - +# by c 09/11/08 + self.fifedit.registerPlugin(self.objectedit) +# end edit c self.params = params def createListener(self): @@ -94,6 +101,10 @@ parts = self.params.split(s) self.maploader.loadFile(s.join(parts[0:-1]), parts[-1]) self.params = None +# edit by c 11/09 + if self.mapedit._instances is not None: + self.objectedit.input() +# end edit c if __name__ == '__main__': print sys.argv
--- a/clients/editor/settings.py Sat Aug 30 12:58:20 2008 +0000 +++ b/clients/editor/settings.py Sat Sep 13 23:28:52 2008 +0000 @@ -17,3 +17,11 @@ WindowTitle = 'FIFE - Editor client' WindowIcon = '' +#offset editor: +RotAngles = {} +# zero-projekt angles +#RotAngles['animations'] = ["0", "60", "120", "180", "240", "300"] +# rio de hola angles +RotAngles['animations'] = ["0", "45", "90", "135", "180", "225", "270", "315"] + +