diff --git a/docs/config.rst b/docs/config.rst index 3789990..fe90c6a 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -609,8 +609,10 @@ Rendering .. _crop: ``crop`` - You can use this to render a small subset of your map, instead of the entire - thing. The format is (min x, min z, max x, max z). + You can use this to render one or more small subsets of your map. The format + of an individual crop zone is (min x, min z, max x, max z); if you wish to + specify multiple crop zones, you may do so by specifying a list of crop zones, + i.e. [(min x1, min z1, max x1, max z1), (min x2, min z2, max x2, max z2)] The coordinates are block coordinates. The same you get with the debug menu in-game and the coordinates shown when you view a map. @@ -623,6 +625,14 @@ Rendering 'crop': (-500, -500, 500, 500), } + Example that renders two 500 by 500 squares of land: + + renders['myrender'] = { + 'world': 'myworld', + 'title': "Multi cropped Example", + 'crop': [(-500, -500, 0, 0), (0, 0, 500, 500)] + } + This option performs a similar function to the old ``--regionlist`` option (which no longer exists). It is useful for example if someone has wandered really far off and made your map too large. You can set the crop for the @@ -830,7 +840,6 @@ Other HTML/JS output options markers and signs on our map, you must also run the genPO script. See the :doc:`Signs and markers` section for more details and documenation. - **Default:** ``[]`` (an empty list) diff --git a/overviewer.py b/overviewer.py index 43bd44f..83bb6de 100755 --- a/overviewer.py +++ b/overviewer.py @@ -488,16 +488,20 @@ dir but you forgot to put quotes around the directory, since it contains spaces. # regionset cache pulls from the same underlying cache object. rset = world.CachedRegionSet(rset, caches) - # If a crop is requested, wrap the regionset here - if "crop" in render: - rset = world.CroppedRegionSet(rset, *render['crop']) - # If this is to be a rotated regionset, wrap it in a RotatedRegionSet # object if (render['northdirection'] > 0): rset = world.RotatedRegionSet(rset, render['northdirection']) logging.debug("Using RegionSet %r", rset) + # If a crop is requested, wrap the regionset here + if "crop" in render: + rsets = [] + for zone in render['crop']: + rsets.append(world.CroppedRegionSet(rset, *zone)) + else: + rsets = [rset] + ############################### # Do the final prep and create the TileSet object @@ -508,8 +512,9 @@ dir but you forgot to put quotes around the directory, since it contains spaces. render['name'] = render_name # perhaps a hack. This is stored here for the asset manager tileSetOpts = util.dict_subset(render, ["name", "imgformat", "renderchecks", "rerenderprob", "bgcolor", "defaultzoom", "imgquality", "optimizeimg", "rendermode", "worldname_orig", "title", "dimension", "changelist", "showspawn", "overlay", "base", "poititle", "maxzoom", "showlocationmarker", "minzoom"]) tileSetOpts.update({"spawn": w.find_true_spawn()}) # TODO find a better way to do this - tset = tileset.TileSet(w, rset, assetMrg, tex, tileSetOpts, tileset_dir) - tilesets.append(tset) + for rset in rsets: + tset = tileset.TileSet(w, rset, assetMrg, tex, tileSetOpts, tileset_dir) + tilesets.append(tset) # Do tileset preprocessing here, before we start dispatching jobs logging.info("Preprocessing...") diff --git a/overviewer_core/files.py b/overviewer_core/files.py index ca6d8a6..15647fe 100644 --- a/overviewer_core/files.py +++ b/overviewer_core/files.py @@ -19,6 +19,7 @@ import tempfile import shutil import logging import stat +import errno default_caps = {"chmod_works": True, "rename_works": True} @@ -150,6 +151,20 @@ class FileReplacer(object): else: # copy permission bits, if needed if self.caps.get("chmod_works") and os.path.exists(self.destname): - shutil.copymode(self.destname, self.tmpname) + try: + shutil.copymode(self.destname, self.tmpname) + except OSError, e: + # Ignore errno ENOENT: file does not exist. Due to a race + # condition, two processes could conceivably try and update + # the same temp file at the same time + if e.errno != errno.ENOENT: + raise # atomic rename into place - os.rename(self.tmpname, self.destname) + try: + os.rename(self.tmpname, self.destname) + except OSError, e: + # Ignore errno ENOENT: file does not exist. Due to a race + # condition, two processes could conceivably try and update + # the same temp file at the same time + if e.errno != errno.ENOENT: + raise diff --git a/overviewer_core/settingsValidators.py b/overviewer_core/settingsValidators.py index 29553a7..2b3d28f 100644 --- a/overviewer_core/settingsValidators.py +++ b/overviewer_core/settingsValidators.py @@ -225,15 +225,23 @@ def validateOutputDir(d): return expand_path(d) def validateCrop(value): - if len(value) != 4: - raise ValidationException("The value for the 'crop' setting must be a tuple of length 4") - a, b, c, d = tuple(int(x) for x in value) - - if a >= c: - a, c = c, a - if b >= d: - b, d = d, b - return (a, b, c, d) + if not isinstance(value, list): + value = [value] + + cropZones = [] + for zone in value: + if not isinstance(zone, tuple) or len(zone) != 4: + raise ValidationException("The value for the 'crop' setting must be an array of tuples of length 4") + a, b, c, d = tuple(int(x) for x in zone) + + if a >= c: + a, c = c, a + if b >= d: + b, d = d, b + + cropZones.append((a, b, c, d)) + + return cropZones def validateObserver(observer): if all(map(lambda m: hasattr(observer, m), ['start', 'add', 'update', 'finish'])): diff --git a/overviewer_core/tileset.py b/overviewer_core/tileset.py index 0d90446..b744d6e 100644 --- a/overviewer_core/tileset.py +++ b/overviewer_core/tileset.py @@ -947,7 +947,14 @@ class TileSet(object): if self.options['optimizeimg']: optimize_image(tmppath, imgformat, self.options['optimizeimg']) - os.utime(tmppath, (max_mtime, max_mtime)) + try: + os.utime(tmppath, (max_mtime, max_mtime)) + except OSError, e: + # Ignore errno ENOENT: file does not exist. Due to a race + # condition, two processes could conceivably try and update + # the same temp file at the same time + if e.errno != errno.ENOENT: + raise def _render_rendertile(self, tile): """Renders the given render-tile.