From b6ac54a2b61d0b8f45adab36ac5179e83bcef710 Mon Sep 17 00:00:00 2001 From: matrixhacker Date: Fri, 9 May 2014 23:44:46 -0400 Subject: [PATCH 1/2] Added the ability to specify multiple crop zones. --- docs/config.rst | 12 ++++++++++-- overviewer.py | 17 +++++++++++------ overviewer_core/files.py | 19 +++++++++++++++++-- overviewer_core/settingsValidators.py | 26 +++++++++++++++++--------- overviewer_core/tileset.py | 9 ++++++++- 5 files changed, 63 insertions(+), 20 deletions(-) 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. From bb1c4a7b85e5ed3b81fc29118196613be3ef0fbd Mon Sep 17 00:00:00 2001 From: matrixhacker Date: Mon, 12 May 2014 14:47:45 -0400 Subject: [PATCH 2/2] Updated documentation and added an additional validation check for improperly formatted crop zones. --- docs/config.rst | 6 ++++-- overviewer_core/settingsValidators.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/config.rst b/docs/config.rst index e33e035..54ddfa1 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -709,8 +709,10 @@ values. The valid configuration keys are listed below. .. _crop: ``crop`` - 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)]. + 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. diff --git a/overviewer_core/settingsValidators.py b/overviewer_core/settingsValidators.py index 0499815..2b3d28f 100644 --- a/overviewer_core/settingsValidators.py +++ b/overviewer_core/settingsValidators.py @@ -230,7 +230,7 @@ def validateCrop(value): cropZones = [] for zone in value: - if len(zone) != 4: + 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)