1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488
|
# -*- coding: utf-8 -*-
# ####################################################################
# Copyright (C) 2005-2009 by the FIFE team
# http://www.fifengine.de
# This file is part of FIFE.
#
# FIFE is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
# ####################################################################
import editor
import pdb
import math
from fife import fife
import editor
import events
import undomanager
from fife.extensions.pychan.tools import callbackWithArguments as cbwa
from fife.extensions.fife_settings import Setting
TDS = Setting(app_name="editor")
class MapController(object):
""" MapController provides an interface for editing maps """
def __init__(self, map):
self._editor = editor.getEditor()
self._engine = self._editor.getEngine()
self._camera = None # currently selected camera
self._layer = None # currently selected layer
self._selection = [] # currently selected cells
self._single_instance = False # flag to force selection of one single instance
self._instance = None # current selected single instance
self._map = None
self._undo = False
self._undomanager = undomanager.UndoManager()
undomanager.preUndo.connect(self._startUndo, sender=self._undomanager)
undomanager.preRedo.connect(self._startUndo, sender=self._undomanager)
undomanager.postUndo.connect(self._endUndo, sender=self._undomanager)
undomanager.postRedo.connect(self._endUndo, sender=self._undomanager)
self.debug = False
self._settings = TDS
self.overwriteInstances = True # Remove instances on cell before placing new instance
self.importList = {} # used to keep track of current imports needed by the map
if map is not None:
self.setMap(map.getId())
def cleanUp(self):
undomanager.preUndo.disconnect(self._startUndo, sender=self._undomanager)
undomanager.preRedo.disconnect(self._startUndo, sender=self._undomanager)
undomanager.postUndo.disconnect(self._endUndo, sender=self._undomanager)
undomanager.postRedo.disconnect(self._endUndo, sender=self._undomanager)
self._undomanager.clear()
self._editor = None
self._engine = None
self._map = None
self._selection = []
self._layer = None
self._camera = None
def setMap(self, mapid):
""" Set the map to be edited """
self._camera = None
self._map = None
self._layer = None
self._selection = []
self._map = self._engine.getModel().getMap(mapid)
if not self._map.getLayers():
raise AttributeError('Editor error: map ' + self._map.getId() + ' has no layers. Cannot edit.')
for cam in self._map.getCameras():
if cam.getLocationRef().getMap().getId() == self._map.getId():
self._camera = cam
break
# remove imports from previous map
self.importList.clear()
# keep track of imports that were loaded with the map
for layer in self._map.getLayers():
for i in layer.getInstances():
self.incrementReferenceCountForObject(i.getObject())
self._layer = self._map.getLayers()[0]
gridrenderer = fife.GridRenderer.getInstance(self._camera)
gridrenderer.activateAllLayers(self._map)
color = str(self._settings.get("Colors", "Grid", "0,255,0"))
gridrenderer.setColor(*[int(c) for c in color.split(',')])
blockrenderer = fife.BlockingInfoRenderer.getInstance(self._camera)
blockrenderer.activateAllLayers(self._map)
color = str(self._settings.get("Colors", "Blocking", "0,255,0"))
blockrenderer.setColor(*[int(c) for c in color.split(',')])
coordinaterenderer = fife.CoordinateRenderer.getInstance(self._camera)
coordinaterenderer.activateAllLayers(self._map)
color = str(self._settings.get("Colors", "Coordinate", "255,255,255"))
coordinaterenderer.setColor(*[int(c) for c in color.split(',')])
cellrenderer = fife.CellSelectionRenderer.getInstance(self._camera)
cellrenderer.activateAllLayers(self._map)
color = str(self._settings.get("Colors", "CellSelection", "255,0,0"))
cellrenderer.setColor(*[int(c) for c in color.split(',')])
cellrenderer.setEnabled(True)
def getMap(self):
return self._map
def selectLayer(self, layerid):
""" Select layer to be edited """
self.deselectSelection()
self._layer = None
layers = [l for l in self._map.getLayers() if l.getId() == layerid]
if len(layers) == 1:
self._layer = layers[0]
def deselectSelection(self):
""" Deselects all selected cells """
if not self._camera:
if self.debug: print 'No camera bind yet, cannot select any cell'
return
self._selection = []
fife.CellSelectionRenderer.getInstance(self._camera).reset()
def clearSelection(self):
""" Removes all instances on selected cells """
instances = self.getInstancesFromSelection()
self._undomanager.startGroup("Cleared selection")
self.removeInstances(instances)
self._undomanager.endGroup()
def fillSelection(self, object):
""" Adds an instance of object on each selected cell """
self._undomanager.startGroup("Filled selection")
for loc in self._selection:
self.placeInstance(loc.getLayerCoordinates(), object)
self._undomanager.endGroup()
def selectCell(self, screenx, screeny):
""" Selects a cell at a position on screen """
if not self._camera:
if self.debug: print 'No camera bind yet, cannot select any cell'
return
if not self._layer:
if self.debug: print 'No layer assigned in selectCell'
return
loc = fife.Location(self._layer)
loc.setMapCoordinates(self.screenToMapCoordinates(screenx, screeny))
for i in self._selection:
if loc.getLayerCoordinates() == i.getLayerCoordinates(): return
self._selection.append(loc)
fife.CellSelectionRenderer.getInstance(self._camera).selectLocation(loc)
def deselectCell(self, screenx, screeny):
""" Deselects a cell at a position on screen """
if not self._camera:
if self.debug: print 'No camera bind yet, cannot select any cell'
return
if not self._layer:
if self.debug: print 'No layer assigned in selectCell'
return
loc = fife.Location(self._layer)
loc.setMapCoordinates(self.screenToMapCoordinates(screenx, screeny))
for i in self._selection[:]:
if loc.getLayerCoordinates() == i.getLayerCoordinates():
self._selection.remove(i)
fife.CellSelectionRenderer.getInstance(self._camera).deselectLocation(i)
return
def getInstance(self):
""" Returns a single instance packed into a list (compat to API) """
if self._instance:
return [self._instance, ]
return []
def getInstancesFromSelection(self):
""" Returns all instances in the selected cells """
instances = []
for loc in self._selection:
instances.extend(self.getInstancesFromPosition(loc.getExactLayerCoordinates(), loc.getLayer()))
return instances
def getInstancesFromPosition(self, position, layer=None):
""" Returns all instances on a specified position
Interprets ivar _single_instance and returns only the
first instance if flag is set to True
"""
if not self._layer and not layer:
if self.debug: print 'No layer assigned in getInstancesFromPosition'
return
if not position:
if self.debug: print 'No position assigned in getInstancesFromPosition'
return
if layer:
loc = fife.Location(layer)
else:
loc = fife.Location(self._layer)
if isinstance(position, fife.ExactModelCoordinate):
loc.setExactLayerCoordinates(position)
else:
loc.setLayerCoordinates(position)
instances = self._camera.getMatchingInstances(loc)
if self._single_instance and instances:
return [instances[0], ]
return instances
def getUndoManager(self):
""" Returns undo manager """
return self._undomanager
def undo(self):
""" Undo one level """
self._undomanager.undo()
def redo(self):
""" Redo one level """
self._undomanager.redo()
def _startUndo(self):
""" Called before a undo operation is performed. Makes sure undo stack does not get corrupted """
self._undo = True
def _endUndo(self):
""" Called when a undo operation is done """
self._undo = False
def placeInstance(self, position, object, layer=None):
""" Places an instance of object at position. Any existing instances on position are removed. """
mname = 'placeInstance'
if not object:
if self.debug: print 'No object assigned in %s' % mname
return
if not position:
if self.debug: print 'No position assigned in %s' % mname
return
if not self._layer:
if self.debug: print 'No layer assigned in %s' % mname
return
if self.debug: print 'Placing instance of ' + object.getId() + ' at ' + str(position)
# Remove instances from target position
if not self._undo:
instances = self.getInstancesFromPosition(position)
if len(instances) == 1:
# Check if the only instance at position has the same object
objectId = instances[0].getObject().getId()
objectNs = instances[0].getObject().getNamespace()
if objectId == object.getId() and objectNs == object.getNamespace():
if self.debug: print "Tried to place duplicate instance"
return
self._undomanager.startGroup("Placed instance")
self.removeInstances(instances)
if layer:
inst = layer.createInstance(object, position)
else:
inst = self._layer.createInstance(object, position)
fife.InstanceVisual.create(inst)
# update reference count in import list for object
self.incrementReferenceCountForObject(object)
if not self._undo:
redocall = cbwa(self.placeInstance, position, object, inst.getLocation().getLayer())
undocall = cbwa(self.removeInstanceOfObjectAt, position, object, inst.getLocation().getLayer())
undoobject = undomanager.UndoObject(undocall, redocall, "Placed instance")
self._undomanager.addAction(undoobject)
self._undomanager.endGroup()
def removeInstanceOfObjectAt(self, position, object, layer=None):
""" Removes the first instance of object it can find on position """
instances = self.getInstancesFromPosition(position, layer)
for i in instances:
if i.getObject().getId() == object.getId() and i.getObject().getNamespace() == object.getNamespace():
self.removeInstances([i],layer)
return
def removeInstances(self, instances, layer=None):
""" Removes all provided instances """
mname = 'removeInstances'
if not instances:
if self.debug: print 'No instances assigned in %s' % mname
return
for i in instances:
if self.debug: print 'Deleting instance ' + i.getObject().getId() + ' at ' + str(i.getLocation().getExactLayerCoordinates())
if not self._undo:
object = i.getObject()
position = i.getLocation().getExactLayerCoordinates()
undocall = cbwa(self.placeInstance, position, object, i.getLocation().getLayer())
redocall = cbwa(self.removeInstanceOfObjectAt, position, object, i.getLocation().getLayer())
undoobject = undomanager.UndoObject(undocall, redocall, "Removed instance")
self._undomanager.addAction(undoobject)
if layer:
layer.deleteInstance(i)
else:
self._layer.deleteInstance(i)
self.decrementReferenceCountForObject(i.getObject())
def moveInstances(self, instances, moveBy, exact=False, origLoc=None, origFacing=None):
""" Moves provided instances by moveBy. If exact is false, the instances are
snapped to closest cell. origLoc and origFacing are only set when an undo/redo
operation takes place and will have no effect and should not be used under normal
circumstances.
@type instances: list
@param instances: a bunch of selected fife.Instance objects
@type moveBy: fife.Point3D
@param moveBy: diff of last and current exact model coordinate relative to the cursor
@type exact: bool
@param exact: flag to either set exactLayerCoordinates or LayerCoordinates
@rtype result: bool
@return result: flag wether the instances were moved or not (always True if exact=True)
"""
result = True
mname = 'moveInstances'
if not self._layer:
if self.debug: print 'No layer assigned in %s' % mname
return not result
moveBy = fife.ExactModelCoordinate(float(moveBy.x), float(moveBy.y), float(moveBy.z))
for i in instances:
loc = i.getLocation()
f = i.getFacingLocation()
nloc = fife.Location(self._layer)
nloc.setExactLayerCoordinates(loc.getExactLayerCoordinates() + moveBy)
if loc.getLayerCoordinates() == nloc.getLayerCoordinates() and not exact:
result = False
break
if exact:
loc.setExactLayerCoordinates(nloc.getExactLayerCoordinates())
f.setExactLayerCoordinates(loc.getExactLayerCoordinates())
else:
loc.setLayerCoordinates(nloc.getLayerCoordinates())
f.setLayerCoordinates(loc.getLayerCoordinates())
if not self._undo:
undocall = cbwa(self.moveInstances, [i], moveBy, exact, i.getLocation(), i.getFacingLocation())
redocall = cbwa(self.moveInstances, [i], moveBy, exact, i.getLocation(), i.getFacingLocation())
undoobject = undomanager.UndoObject(undocall, redocall, "Moved instance")
self._undomanager.addAction(undoobject)
i.setLocation(loc)
i.setFacingLocation(f)
else:
assert(origLoc)
assert(origFacing)
i.setLocation(origLoc)
i.setFacingLocation(origFacing)
return result
def rotateCounterClockwise(self):
""" Rotates map counterclockwise by 90 degrees """
currot = self._camera.getRotation()
self._camera.setRotation((currot + 270) % 360)
def rotateClockwise(self):
""" Rotates map clockwise by 90 degrees """
currot = self._camera.getRotation()
self._camera.setRotation((currot + 90) % 360)
def getZoom(self):
""" Returns camera zoom """
if not self._camera:
if self.debug: print 'No camera bind yet, cannot get zoom'
return 0
return self._camera.getZoom()
def setZoom(self, zoom):
""" Sets camera zoom """
if not self._camera:
if self.debug: print 'No camera bind yet, cannot get zoom'
return
self._camera.setZoom(zoom)
def moveCamera(self, screen_x, screen_y):
""" Move camera (scroll) by screen_x, screen_y """
if not self._camera:
return
coords = self._camera.getLocationRef().getMapCoordinates()
z = self._camera.getZoom()
r = self._camera.getRotation()
if screen_x:
coords.x -= screen_x / z * math.cos(r / 180.0 * math.pi) / 100
coords.y -= screen_x / z * math.sin(r / 180.0 * math.pi) / 100
if screen_y:
coords.x -= screen_y / z * math.sin(-r / 180.0 * math.pi) / 100
coords.y -= screen_y / z * math.cos(-r / 180.0 * math.pi) / 100
coords = self._camera.getLocationRef().setMapCoordinates(coords)
self._camera.refresh()
def screenToMapCoordinates(self, screenx, screeny):
""" Convert screen coordinates to map coordinates, including z """
if not self._camera:
if self.debug: print 'No camera bind yet in screenToMapCoordinates'
return
if not self._layer:
if self.debug: print 'No layer assigned in screenToMapCoordinates'
return
screencoords = fife.ScreenPoint(screenx, screeny)
z_offset = self._camera.getZOffset(self._layer)
if self._layer == self._camera.getLocation().getLayer():
instance_z = float(-self._camera.getLocation().getExactLayerCoordinates().z)
layer_z = float(self._layer.getCellGrid().getZShift() - (self._camera.getLocation().getMapCoordinates().z - self._camera.getLocation().getExactLayerCoordinates().z))
z_offset.x *= int(instance_z + layer_z * self._layer.getCellGrid().getXScale())
z_offset.y *= int(instance_z + layer_z * self._layer.getCellGrid().getYScale())
else:
instance_z = float(-self._camera.getLocation().getExactLayerCoordinates(self._layer).z)
layer_z = float(self._layer.getCellGrid().getZShift() - (self._camera.getLocation().getMapCoordinates().z - self._camera.getLocation().getExactLayerCoordinates(self._layer).z))
z_offset.x *= int(instance_z + layer_z * self._layer.getCellGrid().getXScale())
z_offset.y *= int(instance_z + layer_z * self._layer.getCellGrid().getYScale())
if (z_offset.z <= 0):
screencoords.y += int(z_offset.y)
else:
screencoords.y -= int(z_offset.y)
mapCoords = self._camera.toMapCoordinates(screencoords, False)
mapCoords.z = self._layer.getCellGrid().getZShift()
return mapCoords
def incrementReferenceCountForObject(self, object):
""" updates the import count for the object passed in """
objFilename = object.getFilename()
if objFilename not in self.importList.keys():
self.importList[objFilename] = 1
else:
self.importList[objFilename] += 1
def decrementReferenceCountForObject(self, object):
""" decrements the reference count for the object passed in
and removes the object from the import list if the reference count hits 0 """
objFilename = object.getFilename()
if objFilename in self.importList.keys():
self.importList[objFilename] -= 1
if self.importList[objFilename] == 0:
del self.importList[objFilename]
|