diff --git a/docs/config.rst b/docs/config.rst index 5db549c..e33e035 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -709,8 +709,8 @@ values. The valid configuration keys are listed below. .. _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, instead + of the entire thing. The format is [(min x, min z, max x, max z)]. The coordinates are block coordinates. The same you get with the debug menu in-game and the coordinates shown when you view a map. @@ -723,6 +723,14 @@ values. The valid configuration keys are listed below. '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 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..0499815 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 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.