From 46545952385a9307769e721467fe297790ea8928 Mon Sep 17 00:00:00 2001 From: Lucas Hereld Date: Thu, 23 Feb 2012 09:10:17 -0800 Subject: [PATCH 01/17] if there is only one world, shouldnt show the world selector --- overviewer_core/data/js_src/views.js | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/overviewer_core/data/js_src/views.js b/overviewer_core/data/js_src/views.js index bde253e..70867e0 100644 --- a/overviewer_core/data/js_src/views.js +++ b/overviewer_core/data/js_src/views.js @@ -36,21 +36,23 @@ overviewer.views.WorldView = Backbone.View.extend({ overviewer.views.WorldSelectorView = Backbone.View.extend({ initialize: function() { - // a div will have already been created for us, we just - // need to register it with the google maps control - var selectBox = document.createElement('select'); - $.each(overviewer.collections.worldViews, function(index, elem) { - var o = document.createElement("option"); - o.value = elem.model.get("name"); - o.innerHTML = elem.model.get("name"); - $(o).data("viewObj", elem); - selectBox.appendChild(o); + if(overviewer.collections.worldViews.length > 1) { + // a div will have already been created for us, we just + // need to register it with the google maps control + var selectBox = document.createElement('select'); + $.each(overviewer.collections.worldViews, function(index, elem) { + var o = document.createElement("option"); + o.value = elem.model.get("name"); + o.innerHTML = elem.model.get("name"); + $(o).data("viewObj", elem); + selectBox.appendChild(o); - }); + }); - this.el.appendChild(selectBox); - overviewer.map.controls[google.maps.ControlPosition.TOP_LEFT].push(this.el); - }, + this.el.appendChild(selectBox); + overviewer.map.controls[google.maps.ControlPosition.TOP_LEFT].push(this.el); + } + }, events: { "change select": "changeWorld" }, From da663dadb3d1cc2d71fc7da8a521789616b01b61 Mon Sep 17 00:00:00 2001 From: Lucas Hereld Date: Thu, 23 Feb 2012 11:46:26 -0800 Subject: [PATCH 02/17] Picks the right protocal based on what is being used. This fixes ssl errors on sites that use https. --- overviewer_core/data/web_assets/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/overviewer_core/data/web_assets/index.html b/overviewer_core/data/web_assets/index.html index 0766727..636bdd3 100644 --- a/overviewer_core/data/web_assets/index.html +++ b/overviewer_core/data/web_assets/index.html @@ -10,8 +10,8 @@ - - + + From 997ec58161a9673f2456c6aa60bf839e82175229 Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Sat, 25 Feb 2012 15:57:29 -0500 Subject: [PATCH 03/17] Only copy real js files (not silly editor swap files) --- overviewer_core/assetmanager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/overviewer_core/assetmanager.py b/overviewer_core/assetmanager.py index 5d60cae..705be77 100644 --- a/overviewer_core/assetmanager.py +++ b/overviewer_core/assetmanager.py @@ -149,7 +149,7 @@ directory. fout.write(f.read()) # now copy in the rest for js in os.listdir(js_src): - if not js.endswith("overviewer.js"): + if not js.endswith("overviewer.js") and js.endswith(".js"): with open(os.path.join(js_src,js)) as f: fout.write(f.read()) From d1f13cadc5f414905da5d097b2f59fa7290b3d26 Mon Sep 17 00:00:00 2001 From: Andrew Brown Date: Sat, 25 Feb 2012 19:23:47 -0500 Subject: [PATCH 04/17] Revert "Picks the right protocal based on what is being used. This fixes ssl errors on sites that use https." Reason: breaks local viewing. Tries to access file://maps.google.com etc. This reverts commit da663dadb3d1cc2d71fc7da8a521789616b01b61. --- overviewer_core/data/web_assets/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/overviewer_core/data/web_assets/index.html b/overviewer_core/data/web_assets/index.html index 636bdd3..0766727 100644 --- a/overviewer_core/data/web_assets/index.html +++ b/overviewer_core/data/web_assets/index.html @@ -10,8 +10,8 @@ - - + + From f48f0445d1edfdec4c601170f705605056990173 Mon Sep 17 00:00:00 2001 From: Andrew Brown Date: Sat, 25 Feb 2012 19:33:35 -0500 Subject: [PATCH 05/17] Dump assets and "initial" data before rendering starts Now does preprocessing off of main() instead of in the dispatcher initializer. --- overviewer.py | 6 ++++++ overviewer_core/assetmanager.py | 22 +++++++++++++++++++--- overviewer_core/dispatcher.py | 5 ----- overviewer_core/tileset.py | 14 ++++++++++++++ 4 files changed, 39 insertions(+), 8 deletions(-) diff --git a/overviewer.py b/overviewer.py index 9ab4019..0821a7b 100755 --- a/overviewer.py +++ b/overviewer.py @@ -380,6 +380,12 @@ dir but you forgot to put quotes around the directory, since it contains spaces. tset = tileset.TileSet(rset, assetMrg, tex, tileSetOpts, tileset_dir) tilesets.append(tset) + # Do tileset preprocessing here, before we start dispatching jobs + for ts in tilesets: + ts.do_preprocessing() + + # Output initial static data and configuration + assetMrg.initialize(tilesets) # multiprocessing dispatcher if config['processes'] == 1: diff --git a/overviewer_core/assetmanager.py b/overviewer_core/assetmanager.py index 705be77..97d9ab0 100644 --- a/overviewer_core/assetmanager.py +++ b/overviewer_core/assetmanager.py @@ -72,7 +72,23 @@ directory. # TODO based on the type, so something POI[regionset.name].append - def finalize(self, tilesets): + def initialize(self, tilesets): + """Similar to finalize() but calls the tilesets' get_initial_data() + instead of get_persistent_data() to compile the generated javascript + config. + + """ + return self.finalize(tilesets, True) + + def finalize(self, tilesets, initial=False): + """Called to output the generated javascript and all static files to + the output directory + + """ + if not initial: + get_data = lambda tileset: tileset.get_persistent_data() + else: + get_data = lambda tileset: tileset.get_initial_data() # dictionary to hold the overviewerConfig.js settings that we will dumps dump = dict() @@ -94,7 +110,7 @@ directory. # based on the tilesets we have, group them by worlds worlds = [] for tileset in tilesets: - full_name = tileset.get_persistent_data()['world'] + full_name = get_data(tileset)['world'] if full_name not in worlds: worlds.append(full_name) @@ -120,7 +136,7 @@ directory. for tileset in tilesets: - dump['tilesets'].append(tileset.get_persistent_data()) + dump['tilesets'].append(get_data(tileset)) # write a blank image blank = Image.new("RGBA", (1,1), tileset.options.get('bgcolor')) diff --git a/overviewer_core/dispatcher.py b/overviewer_core/dispatcher.py index 2e8e353..9823eef 100644 --- a/overviewer_core/dispatcher.py +++ b/overviewer_core/dispatcher.py @@ -49,14 +49,9 @@ class Dispatcher(object): """ # TODO use status callback - # preprocessing - for tileset in tilesetlist: - tileset.do_preprocessing() - # setup tilesetlist self.setup_tilesets(tilesetlist) - # iterate through all possible phases num_phases = [tileset.get_num_phases() for tileset in tilesetlist] for phase in xrange(max(num_phases)): diff --git a/overviewer_core/tileset.py b/overviewer_core/tileset.py index a20a3c9..3698454 100644 --- a/overviewer_core/tileset.py +++ b/overviewer_core/tileset.py @@ -382,6 +382,20 @@ class TileSet(object): name = str(tilepath[-1]) self._render_compositetile(dest, name) + def get_initial_data(self): + """This is called similarly to get_persistent_data, but is called after + do_preprocessing but before any work is acutally done. + + """ + d = self.get_persistent_data() + # This is basically the same as get_persistent_data() with the + # following exceptions: + # * last_rendertime is not changed + # * A key "render_in_progress" is set to True + d['last_rendertime'] = self.last_rendertime + d['render_in_progress'] = True + return d + def get_persistent_data(self): """Returns a dictionary representing the persistent data of this TileSet. Typically this is called by AssetManager From 195e08d4a1f9c0888b300717ce2d28c817b04427 Mon Sep 17 00:00:00 2001 From: Andrew Brown Date: Sat, 25 Feb 2012 20:07:07 -0500 Subject: [PATCH 06/17] Determine an appropriate rendercheck mode automatically --- overviewer_core/settingsDefinition.py | 2 +- overviewer_core/tileset.py | 33 +++++++++++++++++++++++++-- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/overviewer_core/settingsDefinition.py b/overviewer_core/settingsDefinition.py index 91d8560..4c3dd02 100644 --- a/overviewer_core/settingsDefinition.py +++ b/overviewer_core/settingsDefinition.py @@ -71,7 +71,7 @@ renders = Setting(required=True, default={}, "optimizeimg": Setting(required=True, validator=validateOptImg, default=0), "nomarkers": Setting(required=False, validator=validateBool, default=None), "texturepath": Setting(required=False, validator=validateTexturePath, default=None), - "renderchecks": Setting(required=True, validator=validateInt, default=0), + "renderchecks": Setting(required=False, validator=validateInt, default=None), "rerenderprob": Setting(required=True, validator=validateFloat, default=0), # Remove this eventually (once people update their configs) diff --git a/overviewer_core/tileset.py b/overviewer_core/tileset.py index 3698454..7dcbc67 100644 --- a/overviewer_core/tileset.py +++ b/overviewer_core/tileset.py @@ -188,7 +188,9 @@ class TileSet(object): renderchecks An integer indicating how to determine which tiles need updating - and which don't. This is one of three levels: + and which don't. This key is optional; if not specified, an + appropriate mode is determined from the persistent config obtained + from the asset manager. This is one of three levels: 0 Only render tiles that have chunks with a greater mtime than @@ -257,9 +259,36 @@ class TileSet(object): self.am = assetmanagerobj self.textures = texturesobj - self.last_rendertime = self.am.get_tileset_config(self.options.get("name")).get('last_rendertime', 0) + config = self.am.get_tileset_config(self.options.get("name")) + + self.last_rendertime = config.get('last_rendertime', 0) self.this_rendertime = time.time() + if "renderchecks" not in self.options: + if not config: + # No persistent config? This is a full render then. + self.options['renderchecks'] = 2 + logging.debug("This is the first time rendering %s. Doing" + + " a full-render", + self.options['name']) + elif config.get("render_in_progress", False): + # The last render must have been interrupted. The default should be + # 1 then, not 0 + logging.warning( + "The last render for %s didn't finish. I'll be " + + "scanning all the tiles to make sure everything's up "+ + "to date.", + self.options['name'], + ) + self.options['renderchecks'] = 1 + else: + logging.debug("No rendercheck mode specified for %s. "+ + "Rendering tile whose chunks have changed since %s", + self.options['name'], + time.strftime("%x %X", time.localtime(self.last_rendertime)), + ) + self.options['renderchecks'] = 0 + # Throughout the class, self.outputdir is an absolute path to the # directory where we output tiles. It is assumed to exist. self.outputdir = os.path.abspath(outputdir) From 16ed65e0196a2173f88fbdd4ab72db18bdb57e25 Mon Sep 17 00:00:00 2001 From: Andrew Brown Date: Sat, 25 Feb 2012 21:24:16 -0500 Subject: [PATCH 07/17] Use zoom value from json to determine if maps should be expanded --- overviewer_core/tileset.py | 54 ++++++++++++-------------------------- 1 file changed, 17 insertions(+), 37 deletions(-) diff --git a/overviewer_core/tileset.py b/overviewer_core/tileset.py index 7dcbc67..432de90 100644 --- a/overviewer_core/tileset.py +++ b/overviewer_core/tileset.py @@ -260,6 +260,7 @@ class TileSet(object): self.textures = texturesobj config = self.am.get_tileset_config(self.options.get("name")) + self.config = config self.last_rendertime = config.get('last_rendertime', 0) self.this_rendertime = time.time() @@ -280,6 +281,9 @@ class TileSet(object): "to date.", self.options['name'], ) + logging.warning("You won't get percentage progress for "+ + "this run only, because I don't know how many tiles "+ + "need rendering. I'll be checking them as I go") self.options['renderchecks'] = 1 else: logging.debug("No rendercheck mode specified for %s. "+ @@ -326,8 +330,11 @@ class TileSet(object): logging.warning("Just letting you know, your map requries %s zoom levels. This is REALLY big!", self.treedepth) - # Do any tile re-arranging if necessary - self._rearrange_tiles() + # Do any tile re-arranging if necessary. Skip if there was no config + # from the asset-manager, which typically indicates this is a new + # render + if self.config: + self._rearrange_tiles() # Do the chunk scan here self.dirtytree = self._chunk_scan() @@ -505,16 +512,17 @@ class TileSet(object): """ try: - curdepth = get_dirdepth(self.outputdir) - except Exception: - logging.critical("Could not determine existing tile tree depth. Does it exist?") - raise + curdepth = self.config['zoomLevels'] + except KeyError: + return if curdepth == 1: # Skip a depth 1 tree. A depth 1 tree pretty much can't happen, so # when we detect this it usually means the tree is actually empty return - logging.debug("Current tree depth was detected to be %s. Target tree depth is %s", curdepth, self.treedepth) + logging.debug("Current tree depth for %s is reportedly %s. Target tree depth is %s", + self.options['name'], + curdepth, self.treedepth) if self.treedepth != curdepth: if self.treedepth > curdepth: logging.warning("Your map seems to have expanded beyond its previous bounds.") @@ -690,8 +698,8 @@ class TileSet(object): dirty.add(tile.path) t = int(time.time()-stime) - logging.debug("%s finished chunk scan. %s chunks scanned in %s second%s", - self, chunkcount, t, + logging.debug("Finished chunk scan for %s. %s chunks scanned in %s second%s", + self.options['name'], chunkcount, t, "s" if t != 1 else "") self.max_chunk_mtime = max_chunk_mtime @@ -1021,34 +1029,6 @@ class TileSet(object): # Nope. yield path, max_child_mtime, False - -def get_dirdepth(outputdir): - """Returns the current depth of the tree on disk - - """ - # Traverses down the first directory until it reaches one with no - # subdirectories. While all paths of the tree may not exist, all paths - # of the tree should and are assumed to be the same depth - - # This function returns a list of all subdirectories of the given - # directory. It's slightly more complicated than you'd think it should be - # because one must turn directory names returned by os.listdir into - # relative/absolute paths before they can be passed to os.path.isdir() - getsubdirs = lambda directory: [ - abssubdir - for abssubdir in - (os.path.join(directory,subdir) for subdir in os.listdir(directory)) - if os.path.isdir(abssubdir) - ] - - depth = 1 - subdirs = getsubdirs(outputdir) - while subdirs: - subdirs = getsubdirs(subdirs[0]) - depth += 1 - - return depth - class RendertileSet(object): """This object holds a set of render-tiles using a quadtree data structure. It is typically used to hold tiles that need rendering. This implementation From c3c559fcd37b663a4658db27ce105461951cf42b Mon Sep 17 00:00:00 2001 From: Andrew Brown Date: Sat, 25 Feb 2012 21:26:02 -0500 Subject: [PATCH 08/17] changed this_rendertime to be the max of all chunk mtimes --- overviewer_core/tileset.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/overviewer_core/tileset.py b/overviewer_core/tileset.py index 432de90..cf39271 100644 --- a/overviewer_core/tileset.py +++ b/overviewer_core/tileset.py @@ -263,7 +263,6 @@ class TileSet(object): self.config = config self.last_rendertime = config.get('last_rendertime', 0) - self.this_rendertime = time.time() if "renderchecks" not in self.options: if not config: @@ -449,7 +448,7 @@ class TileSet(object): bgcolor = bgcolorformat(self.options.get('bgcolor')), world = self.options.get('worldname_orig') + (" - " + self.options.get('dimension') if self.options.get('dimension') != 'default' else ''), - last_rendertime = self.this_rendertime, + last_rendertime = self.max_chunk_mtime, imgextension = self.imgextension, ) try: From 6d19ada4e4c1306885e10fe735e2bea4d0ecbf15 Mon Sep 17 00:00:00 2001 From: Andrew Brown Date: Sat, 25 Feb 2012 21:37:58 -0500 Subject: [PATCH 09/17] accept ~ in paths by calling os.path.expanduser() --- overviewer_core/settingsValidators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/overviewer_core/settingsValidators.py b/overviewer_core/settingsValidators.py index 2cbeaaf..95a958d 100644 --- a/overviewer_core/settingsValidators.py +++ b/overviewer_core/settingsValidators.py @@ -45,7 +45,7 @@ def checkBadEscape(s): def validateWorldPath(worldpath): _, worldpath = checkBadEscape(worldpath) - abs_path = os.path.abspath(worldpath) + abs_path = os.path.abspath(os.path.expanduser(worldpath)) if not os.path.exists(os.path.join(abs_path, "level.dat")): raise ValidationException("No level.dat file in '%s'. Are you sure you have the right path?" % (abs_path,)) return abs_path From c759b20f8e72462fd99f0ca734c51cc274be6330 Mon Sep 17 00:00:00 2001 From: Andrew Brown Date: Sat, 25 Feb 2012 21:44:21 -0500 Subject: [PATCH 10/17] added helpful notice about running into corrupted tiles --- overviewer_core/tileset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/overviewer_core/tileset.py b/overviewer_core/tileset.py index cf39271..74d2684 100644 --- a/overviewer_core/tileset.py +++ b/overviewer_core/tileset.py @@ -777,7 +777,7 @@ class TileSet(object): img.paste(quad, path[0]) except Exception, e: logging.warning("Couldn't open %s. It may be corrupt. Error was '%s'", path[1], e) - logging.warning("I'm going to try and delete it. You will need to run the render again") + logging.warning("I'm going to try and delete it. You will need to run the render again and with --check-tiles") try: os.unlink(path[1]) except Exception, e: From 19f6f136e4efcb7e822670af9abd83354d674b40 Mon Sep 17 00:00:00 2001 From: Andrew Brown Date: Sun, 26 Feb 2012 00:14:58 -0500 Subject: [PATCH 11/17] Now writes images to temporary files and atomically moves in place* * on systems with an atomic os.rename --- overviewer_core/assetmanager.py | 41 ++++++++------------ overviewer_core/tileset.py | 33 ++++++++-------- overviewer_core/util.py | 68 +++++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+), 40 deletions(-) diff --git a/overviewer_core/assetmanager.py b/overviewer_core/assetmanager.py index 97d9ab0..26734b4 100644 --- a/overviewer_core/assetmanager.py +++ b/overviewer_core/assetmanager.py @@ -144,11 +144,10 @@ directory. jsondump = json.dumps(dump, indent=4) - with codecs.open(os.path.join(self.outputdir, 'overviewerConfig.js'), 'w', encoding='UTF-8') as f: - f.write("var overviewerConfig = " + jsondump + ";\n") + with util.FileReplacer(os.path.join(self.outputdir, "overviewerConfig.js")) as tmpfile: + with codecs.open(tmpfile, 'w', encoding='UTF-8') as f: + f.write("var overviewerConfig = " + jsondump + ";\n") - - # copy web assets into destdir: global_assets = os.path.join(util.get_program_path(), "overviewer_core", "data", "web_assets") if not os.path.isdir(global_assets): @@ -159,23 +158,16 @@ directory. js_src = os.path.join(util.get_program_path(), "overviewer_core", "data", "js_src") if not os.path.isdir(js_src): js_src = os.path.join(util.get_program_path(), "js_src") - with open(os.path.join(self.outputdir, "overviewer.js"), "w") as fout: - # first copy in js_src/overviewer.js - with open(os.path.join(js_src, "overviewer.js")) as f: - fout.write(f.read()) - # now copy in the rest - for js in os.listdir(js_src): - if not js.endswith("overviewer.js") and js.endswith(".js"): - with open(os.path.join(js_src,js)) as f: - fout.write(f.read()) - - # do the same with the local copy, if we have it - # TODO - # if self.web_assets_path: - # util.mirror_dir(self.web_assets_path, self.outputdir) - - - + with util.FileReplacer(os.path.join(self.outputdir, "overviewer.js")) as tmpfile: + with open(tmpfile, "w") as fout: + # first copy in js_src/overviewer.js + with open(os.path.join(js_src, "overviewer.js"), 'r') as f: + fout.write(f.read()) + # now copy in the rest + for js in os.listdir(js_src): + if not js.endswith("overviewer.js") and js.endswith(".js"): + with open(os.path.join(js_src,js)) as f: + fout.write(f.read()) # helper function to get a label for the given rendermode def get_render_mode_label(rendermode): info = get_render_mode_info(rendermode) @@ -193,7 +185,6 @@ directory. versionstr = "%s (%s)" % (overviewer_version.VERSION, overviewer_version.HASH[:7]) index = index.replace("{version}", versionstr) - with codecs.open(os.path.join(self.outputdir, "index.html"), 'w', encoding='UTF-8') as output: - output.write(index) - - + with util.FileReplacer(indexpath) as indexpath: + with codecs.open(indexpath, 'w', encoding='UTF-8') as output: + output.write(index) diff --git a/overviewer_core/tileset.py b/overviewer_core/tileset.py index 74d2684..e815907 100644 --- a/overviewer_core/tileset.py +++ b/overviewer_core/tileset.py @@ -28,6 +28,7 @@ from collections import namedtuple from PIL import Image from .util import iterate_base4, convert_coords, unconvert_coords, get_tiles_by_chunk +from .util import FileReplacer from .optimizeimages import optimize_image import c_overviewer @@ -784,15 +785,16 @@ class TileSet(object): logging.error("While attempting to delete corrupt image %s, an error was encountered. You will need to delete it yourself. Error was '%s'", path[1], e) # Save it - if imgformat == 'jpg': - img.save(imgpath, quality=self.options['imgquality'], subsampling=0) - else: # png - img.save(imgpath) - - if self.options['optimizeimg']: - optimize_image(imgpath, imgformat, self.options['optimizeimg']) + with FileReplacer(imgpath) as tmppath: + if imgformat == 'jpg': + img.save(tmppath, "jpeg", quality=self.options['imgquality'], subsampling=0) + else: # png + img.save(tmppath, "png") + + if self.options['optimizeimg']: + optimize_image(tmppath, imgformat, self.options['optimizeimg']) - os.utime(imgpath, (max_mtime, max_mtime)) + os.utime(tmppath, (max_mtime, max_mtime)) def _render_rendertile(self, tile): """Renders the given render-tile. @@ -877,15 +879,16 @@ class TileSet(object): draw.text((96,96), "c,r: %s,%s" % (col, row), fill='red') # Save them - if self.imgextension == 'jpg': - tileimg.save(imgpath, quality=self.options['imgquality'], subsampling=0) - else: # png - tileimg.save(imgpath) + with FileReplacer(imgpath) as tmppath: + if self.imgextension == 'jpg': + tileimg.save(tmppath, "jpeg", quality=self.options['imgquality'], subsampling=0) + else: # png + tileimg.save(tmppath, "png") - if self.options['optimizeimg']: - optimize_image(imgpath, self.imgextension, self.options['optimizeimg']) + if self.options['optimizeimg']: + optimize_image(tmppath, self.imgextension, self.options['optimizeimg']) - os.utime(imgpath, (max_chunk_mtime, max_chunk_mtime)) + os.utime(tmppath, (max_chunk_mtime, max_chunk_mtime)) def _get_chunks_for_tile(self, tile): """Get chunks that are relevant to the given render-tile diff --git a/overviewer_core/util.py b/overviewer_core/util.py index 10db859..0c1cfbc 100644 --- a/overviewer_core/util.py +++ b/overviewer_core/util.py @@ -178,6 +178,74 @@ def get_tiles_by_chunk(chunkcol, chunkrow): return product(colrange, rowrange) +# Define a context manager to handle atomic renaming or "just forget it write +# straight to the file" depending on whether os.rename provides atomic +# overwrites. +# Detect whether os.rename will overwrite files +import tempfile +with tempfile.NamedTemporaryFile() as f1: + with tempfile.NamedTemporaryFile() as f2: + try: + os.rename(f1.name,f2.name) + except OSError: + renameworks = False + else: + renameworks = True + # re-make this file so it can be deleted without error + open(f1.name, 'w').close() +del tempfile,f1,f2 +doc = """This class acts as a context manager for files that are to be written +out overwriting an existing file. + +The parameter is the destination filename. The value returned into the context +is the filename that should be used. On systems that support an atomic +os.rename(), the filename will actually be a temporary file, and it will be +atomically replaced over the destination file on exit. + +On systems that don't support an atomic rename, the filename returned is the +filename given. + +If an error is encountered, the file is attempted to be removed, and the error +is propagated. + +Example: + +with FileReplacer("config") as configname: + with open(configout, 'w') as configout: + configout.write(newconfig) +""" +if renameworks: + class FileReplacer(object): + __doc__ = doc + def __init__(self, destname): + self.destname = destname + self.tmpname = destname + ".tmp" + def __enter__(self): + # rename works here. Return a temporary filename + return self.tmpname + def __exit__(self, exc_type, exc_val, exc_tb): + if exc_type: + # error + try: + os.remove(self.tmpname) + except Exception, e: + logging.warning("An error was raised, so I was doing " + "some cleanup first, but I couldn't remove " + "'%s'!", self.tmpname) + else: + # atomic rename into place + os.rename(self.tmpname, self.destname) +else: + class FileReplacer(object): + __doc__ = doc + def __init__(self, destname): + self.destname = destname + def __enter__(self): + return self.destname + def __exit__(self, exc_type, exc_val, exc_tb): + return +del renameworks + # Logging related classes are below # Some cool code for colored logging: From 2b7af1886a5c17adc464fb8af66d56d82129880a Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Sun, 26 Feb 2012 11:39:07 -0500 Subject: [PATCH 12/17] overlays converted into render primitives --- overviewer_core/rendermodes.py | 36 +++++++++ overviewer_core/src/overviewer.h | 2 +- .../overlay-mineral.c} | 73 +++++++------------ .../overlay-spawn.c} | 60 ++++++--------- .../overlay.c} | 52 +++++-------- overviewer_core/src/primitives/overlay.h | 32 ++++++++ overviewer_core/src/rendermodes.h | 34 --------- 7 files changed, 139 insertions(+), 150 deletions(-) rename overviewer_core/src/{rendermode-mineral.c => primitives/overlay-mineral.c} (68%) rename overviewer_core/src/{rendermode-spawn.c => primitives/overlay-spawn.c} (63%) rename overviewer_core/src/{rendermode-overlay.c => primitives/overlay.c} (69%) create mode 100644 overviewer_core/src/primitives/overlay.h diff --git a/overviewer_core/rendermodes.py b/overviewer_core/rendermodes.py index 12ab93b..76200c2 100644 --- a/overviewer_core/rendermodes.py +++ b/overviewer_core/rendermodes.py @@ -146,6 +146,42 @@ class Lighting(RenderPrimitive): class SmoothLighting(Lighting): name = "smooth-lighting" +class Overlay(RenderPrimitive): + name = "overlay" + + @property + def whitecolor(self): + whitecolor = getattr(self, "_whitecolor", None) + if whitecolor: + return whitecolor + white = Image.new("RGBA", (24,24), (255, 255, 255, 255)) + self._whitecolor = white + return white + + @property + def facemask_top(self): + facemask_top = getattr(self, "_facemask_top", None) + if facemask_top: + return facemask_top + + white = Image.new("L", (24,24), 255) + top = Image.new("L", (24,24), 0) + toppart = textures.Textures.transform_image_top(white) + top.paste(toppart, (0,0)) + for x,y in [(3,4), (7,2), (11,0)]: + top.putpixel((x,y), 255) + self._facemask_top = top + return top + +class SpawnOverlay(Overlay): + name = "overlay-spawn" + +class MineralOverlay(Overlay): + name = "overlay-mineral" + options = { + 'minerals' : ('a list of (blockid, (r, g, b)) tuples for coloring minerals', None), + } + # Built-in rendermodes for your convenience! normal = [Base(), EdgeLines()] lighting = [Base(), EdgeLines(), Lighting()] diff --git a/overviewer_core/src/overviewer.h b/overviewer_core/src/overviewer.h index e73fb36..b72f831 100644 --- a/overviewer_core/src/overviewer.h +++ b/overviewer_core/src/overviewer.h @@ -26,7 +26,7 @@ // increment this value if you've made a change to the c extesion // and want to force users to rebuild -#define OVERVIEWER_EXTENSION_VERSION 20 +#define OVERVIEWER_EXTENSION_VERSION 21 /* Python PIL, and numpy headers */ #include diff --git a/overviewer_core/src/rendermode-mineral.c b/overviewer_core/src/primitives/overlay-mineral.c similarity index 68% rename from overviewer_core/src/rendermode-mineral.c rename to overviewer_core/src/primitives/overlay-mineral.c index 858333b..b9b1634 100644 --- a/overviewer_core/src/rendermode-mineral.c +++ b/overviewer_core/src/primitives/overlay-mineral.c @@ -15,7 +15,14 @@ * with the Overviewer. If not, see . */ -#include "overviewer.h" +#include "overlay.h" + +typedef struct { + /* inherits from overlay */ + RenderPrimitiveOverlay parent; + + void *minerals; +} RenderPrimitiveMineral; struct MineralColor { unsigned char blockid; @@ -45,7 +52,7 @@ static void get_color(void *data, RenderState *state, int x = state->x, y = state->y, z_max = state->z + 1, z; int max_i = -1; - RenderModeMineral* self = (RenderModeMineral *)data; + RenderPrimitiveMineral* self = (RenderPrimitiveMineral *)data; struct MineralColor *minerals = (struct MineralColor *)(self->minerals); *a = 0; @@ -70,20 +77,21 @@ static void get_color(void *data, RenderState *state, } static int -rendermode_mineral_start(void *data, RenderState *state, PyObject *options) { +overlay_mineral_start(void *data, RenderState *state, PyObject *support) { PyObject *opt; - RenderModeMineral* self; + RenderPrimitiveMineral* self; /* first, chain up */ - int ret = rendermode_overlay.start(data, state, options); + int ret = primitive_overlay.start(data, state, support); if (ret != 0) return ret; /* now do custom initializations */ - self = (RenderModeMineral *)data; + self = (RenderPrimitiveMineral *)data; - opt = PyDict_GetItemString(options, "minerals"); - if (opt) { + if (!render_mode_parse_option(support, "minerals", "O", &(opt))) + return 1; + if (opt && opt != Py_None) { struct MineralColor *minerals = NULL; Py_ssize_t minerals_size = 0, i; /* create custom minerals */ @@ -110,6 +118,7 @@ rendermode_mineral_start(void *data, RenderState *state, PyObject *options) { } else { self->minerals = default_minerals; } + Py_XDECREF(opt); /* setup custom color */ self->parent.get_color = get_color; @@ -118,50 +127,24 @@ rendermode_mineral_start(void *data, RenderState *state, PyObject *options) { } static void -rendermode_mineral_finish(void *data, RenderState *state) { +overlay_mineral_finish(void *data, RenderState *state) { /* first free all *our* stuff */ - RenderModeMineral* self = (RenderModeMineral *)data; + RenderPrimitiveMineral* self = (RenderPrimitiveMineral *)data; if (self->minerals && self->minerals != default_minerals) { free(self->minerals); } /* now, chain up */ - rendermode_overlay.finish(data, state); + primitive_overlay.finish(data, state); } -static int -rendermode_mineral_occluded(void *data, RenderState *state, int x, int y, int z) { - /* no special occlusion here */ - return rendermode_overlay.occluded(data, state, x, y, z); -} - -static int -rendermode_mineral_hidden(void *data, RenderState *state, int x, int y, int z) { - /* no special hiding here */ - return rendermode_overlay.hidden(data, state, x, y, z); -} - -static void -rendermode_mineral_draw(void *data, RenderState *state, PyObject *src, PyObject *mask, PyObject *mask_light) { - /* draw normally */ - rendermode_overlay.draw(data, state, src, mask, mask_light); -} - -const RenderModeOption rendermode_mineral_options[] = { - {"minerals", "a list of (blockid, (r, g, b)) tuples for coloring minerals"}, - {NULL, NULL} -}; - -RenderModeInterface rendermode_mineral = { - "mineral", "Mineral", - "draws a colored overlay showing where ores are located", - rendermode_mineral_options, - &rendermode_overlay, - sizeof(RenderModeMineral), - rendermode_mineral_start, - rendermode_mineral_finish, - rendermode_mineral_occluded, - rendermode_mineral_hidden, - rendermode_mineral_draw, +RenderPrimitiveInterface primitive_overlay_mineral = { + "overlay-mineral", + sizeof(RenderPrimitiveMineral), + overlay_mineral_start, + overlay_mineral_finish, + overlay_occluded, + NULL, + overlay_draw, }; diff --git a/overviewer_core/src/rendermode-spawn.c b/overviewer_core/src/primitives/overlay-spawn.c similarity index 63% rename from overviewer_core/src/rendermode-spawn.c rename to overviewer_core/src/primitives/overlay-spawn.c index 0c8027b..1d00030 100644 --- a/overviewer_core/src/rendermode-spawn.c +++ b/overviewer_core/src/primitives/overlay-spawn.c @@ -15,13 +15,20 @@ * with the Overviewer. If not, see . */ -#include "overviewer.h" +#include "overlay.h" #include +typedef struct { + /* inherits from overlay */ + RenderPrimitiveOverlay parent; + + PyObject *skylight, *blocklight; +} RenderPrimitiveSpawn; + static void get_color(void *data, RenderState *state, unsigned char *r, unsigned char *g, unsigned char *b, unsigned char *a) { - RenderModeSpawn* self = (RenderModeSpawn *)data; + RenderPrimitiveSpawn* self = (RenderPrimitiveSpawn *)data; int x = state->x, y = state->y, z = state->z; int z_light = z + 1; unsigned char blocklight, skylight; @@ -59,16 +66,16 @@ static void get_color(void *data, RenderState *state, } static int -rendermode_spawn_start(void *data, RenderState *state, PyObject *options) { - RenderModeSpawn* self; +overlay_spawn_start(void *data, RenderState *state, PyObject *support) { + RenderPrimitiveSpawn* self; /* first, chain up */ - int ret = rendermode_overlay.start(data, state, options); + int ret = primitive_overlay.start(data, state, support); if (ret != 0) return ret; /* now do custom initializations */ - self = (RenderModeSpawn *)data; + self = (RenderPrimitiveSpawn *)data; self->blocklight = get_chunk_data(state, CURRENT, BLOCKLIGHT, 1); self->skylight = get_chunk_data(state, CURRENT, SKYLIGHT, 1); @@ -79,44 +86,23 @@ rendermode_spawn_start(void *data, RenderState *state, PyObject *options) { } static void -rendermode_spawn_finish(void *data, RenderState *state) { +overlay_spawn_finish(void *data, RenderState *state) { /* first free all *our* stuff */ - RenderModeSpawn* self = (RenderModeSpawn *)data; + RenderPrimitiveSpawn* self = (RenderPrimitiveSpawn *)data; Py_DECREF(self->blocklight); Py_DECREF(self->skylight); /* now, chain up */ - rendermode_overlay.finish(data, state); + primitive_overlay.finish(data, state); } -static int -rendermode_spawn_occluded(void *data, RenderState *state, int x, int y, int z) { - /* no special occlusion here */ - return rendermode_overlay.occluded(data, state, x, y, z); -} - -static int -rendermode_spawn_hidden(void *data, RenderState *state, int x, int y, int z) { - /* no special hiding here */ - return rendermode_overlay.hidden(data, state, x, y, z); -} - -static void -rendermode_spawn_draw(void *data, RenderState *state, PyObject *src, PyObject *mask, PyObject *mask_light) { - /* draw normally */ - rendermode_overlay.draw(data, state, src, mask, mask_light); -} - -RenderModeInterface rendermode_spawn = { - "spawn", "Spawn", - "draws a red overlay where monsters can spawn at night", +RenderPrimitiveInterface primitive_overlay_spawn = { + "overlay-spawn", + sizeof(RenderPrimitiveSpawn), + overlay_spawn_start, + overlay_spawn_finish, + overlay_occluded, NULL, - &rendermode_overlay, - sizeof(RenderModeSpawn), - rendermode_spawn_start, - rendermode_spawn_finish, - rendermode_spawn_occluded, - rendermode_spawn_hidden, - rendermode_spawn_draw, + overlay_draw, }; diff --git a/overviewer_core/src/rendermode-overlay.c b/overviewer_core/src/primitives/overlay.c similarity index 69% rename from overviewer_core/src/rendermode-overlay.c rename to overviewer_core/src/primitives/overlay.c index 9ae4e25..f2152d0 100644 --- a/overviewer_core/src/rendermode-overlay.c +++ b/overviewer_core/src/primitives/overlay.c @@ -15,7 +15,7 @@ * with the Overviewer. If not, see . */ -#include "overviewer.h" +#include "overlay.h" static void get_color(void *data, RenderState *state, unsigned char *r, unsigned char *g, unsigned char *b, unsigned char *a) { @@ -26,32 +26,27 @@ static void get_color(void *data, RenderState *state, } static int -rendermode_overlay_start(void *data, RenderState *state, PyObject *options) { +overlay_start(void *data, RenderState *state, PyObject *support) { PyObject *facemasks_py; - RenderModeOverlay *self = (RenderModeOverlay *)data; + RenderPrimitiveOverlay *self = (RenderPrimitiveOverlay *)data; - facemasks_py = PyObject_GetAttrString(state->support, "facemasks"); - /* borrowed reference, needs to be incref'd if we keep it */ - self->facemask_top = PyTuple_GetItem(facemasks_py, 0); - Py_INCREF(self->facemask_top); - Py_DECREF(facemasks_py); - - self->white_color = PyObject_GetAttrString(state->support, "white_color"); + self->facemask_top = PyObject_GetAttrString(support, "facemask_top"); + self->white_color = PyObject_GetAttrString(support, "whitecolor"); self->get_color = get_color; return 0; } static void -rendermode_overlay_finish(void *data, RenderState *state) { - RenderModeOverlay *self = (RenderModeOverlay *)data; +overlay_finish(void *data, RenderState *state) { + RenderPrimitiveOverlay *self = (RenderPrimitiveOverlay *)data; Py_DECREF(self->facemask_top); Py_DECREF(self->white_color); } -static int -rendermode_overlay_occluded(void *data, RenderState *state, int x, int y, int z) { +int +overlay_occluded(void *data, RenderState *state, int x, int y, int z) { if ( (x != 0) && (y != 15) && (z != 127) && !render_mode_hidden(state->rendermode, x-1, y, z) && !render_mode_hidden(state->rendermode, x, y, z+1) && @@ -65,15 +60,9 @@ rendermode_overlay_occluded(void *data, RenderState *state, int x, int y, int z) return 0; } -static int -rendermode_overlay_hidden(void *data, RenderState *state, int x, int y, int z) { - /* overlays hide nothing by default */ - return 0; -} - -static void -rendermode_overlay_draw(void *data, RenderState *state, PyObject *src, PyObject *mask, PyObject *mask_light) { - RenderModeOverlay *self = (RenderModeOverlay *)data; +void +overlay_draw(void *data, RenderState *state, PyObject *src, PyObject *mask, PyObject *mask_light) { + RenderPrimitiveOverlay *self = (RenderPrimitiveOverlay *)data; unsigned char r, g, b, a; // exactly analogous to edge-line code for these special blocks @@ -118,15 +107,12 @@ rendermode_overlay_draw(void *data, RenderState *state, PyObject *src, PyObject } } -RenderModeInterface rendermode_overlay = { - "overlay", "Overlay", - "base rendermode for informational overlays", +RenderPrimitiveInterface primitive_overlay = { + "overlay", + sizeof(RenderPrimitiveOverlay), + overlay_start, + overlay_finish, + overlay_occluded, NULL, - NULL, - sizeof(RenderModeOverlay), - rendermode_overlay_start, - rendermode_overlay_finish, - rendermode_overlay_occluded, - rendermode_overlay_hidden, - rendermode_overlay_draw, + overlay_draw, }; diff --git a/overviewer_core/src/primitives/overlay.h b/overviewer_core/src/primitives/overlay.h new file mode 100644 index 0000000..4529ab2 --- /dev/null +++ b/overviewer_core/src/primitives/overlay.h @@ -0,0 +1,32 @@ +/* + * This file is part of the Minecraft Overviewer. + * + * Minecraft Overviewer 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 3 of the License, or (at + * your option) any later version. + * + * Minecraft Overviewer 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 the Overviewer. If not, see . + */ + +#include "../overviewer.h" + +typedef struct { + /* top facemask and white color image, for drawing overlays */ + PyObject *facemask_top, *white_color; + /* can be overridden in derived classes to control + overlay alpha and color + last four vars are r, g, b, a out */ + void (*get_color)(void *, RenderState *, + unsigned char *, unsigned char *, unsigned char *, unsigned char *); +} RenderPrimitiveOverlay; +extern RenderPrimitiveInterface primitive_overlay; + +void overlay_draw(void *data, RenderState *state, PyObject *src, PyObject *mask, PyObject *mask_light); +int overlay_occluded(void *data, RenderState *state, int x, int y, int z); diff --git a/overviewer_core/src/rendermodes.h b/overviewer_core/src/rendermodes.h index 23df5e6..6408760 100644 --- a/overviewer_core/src/rendermodes.h +++ b/overviewer_core/src/rendermodes.h @@ -102,38 +102,4 @@ void render_mode_draw(RenderMode *self, PyObject *img, PyObject *mask, PyObject works like PyArg_ParseTuple on a support object */ int render_mode_parse_option(PyObject *support, const char *name, const char *format, ...); -/* XXX individual rendermode interface declarations follow */ -#ifdef OLD_MODES - -/* OVERLAY */ -typedef struct { - /* top facemask and white color image, for drawing overlays */ - PyObject *facemask_top, *white_color; - /* can be overridden in derived classes to control - overlay alpha and color - last four vars are r, g, b, a out */ - void (*get_color)(void *, RenderState *, - unsigned char *, unsigned char *, unsigned char *, unsigned char *); -} RenderModeOverlay; -extern RenderModeInterface rendermode_overlay; - -/* SPAWN */ -typedef struct { - /* inherits from overlay */ - RenderModeOverlay parent; - - PyObject *skylight, *blocklight; -} RenderModeSpawn; -extern RenderModeInterface rendermode_spawn; - -/* MINERAL */ -typedef struct { - /* inherits from overlay */ - RenderModeOverlay parent; - - void *minerals; -} RenderModeMineral; -extern RenderModeInterface rendermode_mineral; -#endif /* OLD_MODES */ - #endif /* __RENDERMODES_H_INCLUDED__ */ From 5d50f35ac12dc3ed98d822979194ec02a845049f Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Sun, 26 Feb 2012 11:52:43 -0500 Subject: [PATCH 13/17] broke out background-clearing code from base overlay primitive --- overviewer_core/rendermodes.py | 3 ++ overviewer_core/src/overviewer.h | 2 +- overviewer_core/src/primitives/clear-base.c | 49 +++++++++++++++++++ .../src/primitives/overlay-mineral.c | 2 +- .../src/primitives/overlay-spawn.c | 2 +- overviewer_core/src/primitives/overlay.c | 20 +------- overviewer_core/src/primitives/overlay.h | 1 - 7 files changed, 56 insertions(+), 23 deletions(-) create mode 100644 overviewer_core/src/primitives/clear-base.c diff --git a/overviewer_core/rendermodes.py b/overviewer_core/rendermodes.py index 76200c2..6548cb9 100644 --- a/overviewer_core/rendermodes.py +++ b/overviewer_core/rendermodes.py @@ -146,6 +146,9 @@ class Lighting(RenderPrimitive): class SmoothLighting(Lighting): name = "smooth-lighting" +class ClearBase(RenderPrimitive): + name = "clear-base" + class Overlay(RenderPrimitive): name = "overlay" diff --git a/overviewer_core/src/overviewer.h b/overviewer_core/src/overviewer.h index b72f831..c48ad3d 100644 --- a/overviewer_core/src/overviewer.h +++ b/overviewer_core/src/overviewer.h @@ -26,7 +26,7 @@ // increment this value if you've made a change to the c extesion // and want to force users to rebuild -#define OVERVIEWER_EXTENSION_VERSION 21 +#define OVERVIEWER_EXTENSION_VERSION 22 /* Python PIL, and numpy headers */ #include diff --git a/overviewer_core/src/primitives/clear-base.c b/overviewer_core/src/primitives/clear-base.c new file mode 100644 index 0000000..3ee36dc --- /dev/null +++ b/overviewer_core/src/primitives/clear-base.c @@ -0,0 +1,49 @@ +/* + * This file is part of the Minecraft Overviewer. + * + * Minecraft Overviewer 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 3 of the License, or (at + * your option) any later version. + * + * Minecraft Overviewer 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 the Overviewer. If not, see . + */ + +#include "../overviewer.h" + +static int +clear_base_occluded(void *data, RenderState *state, int x, int y, int z) { + if ( (x != 0) && (y != 15) && (z != 127) && + !render_mode_hidden(state->rendermode, x-1, y, z) && + !render_mode_hidden(state->rendermode, x, y, z+1) && + !render_mode_hidden(state->rendermode, x, y+1, z) && + !is_transparent(getArrayByte3D(state->blocks, x-1, y, z)) && + !is_transparent(getArrayByte3D(state->blocks, x, y, z+1)) && + !is_transparent(getArrayByte3D(state->blocks, x, y+1, z))) { + return 1; + } + + return 0; +} + +static void +clear_base_draw(void *data, RenderState *state, PyObject *src, PyObject *mask, PyObject *mask_light) { + /* clear the draw space -- set alpha to 0 within mask */ + tint_with_mask(state->img, 255, 255, 255, 0, mask, state->imgx, state->imgy, 0, 0); +} + +RenderPrimitiveInterface primitive_clear_base = { + "clear-base", + 0, + NULL, + NULL, + clear_base_occluded, + NULL, + clear_base_draw, +}; diff --git a/overviewer_core/src/primitives/overlay-mineral.c b/overviewer_core/src/primitives/overlay-mineral.c index b9b1634..8d98f1e 100644 --- a/overviewer_core/src/primitives/overlay-mineral.c +++ b/overviewer_core/src/primitives/overlay-mineral.c @@ -144,7 +144,7 @@ RenderPrimitiveInterface primitive_overlay_mineral = { sizeof(RenderPrimitiveMineral), overlay_mineral_start, overlay_mineral_finish, - overlay_occluded, + NULL, NULL, overlay_draw, }; diff --git a/overviewer_core/src/primitives/overlay-spawn.c b/overviewer_core/src/primitives/overlay-spawn.c index 1d00030..64405ea 100644 --- a/overviewer_core/src/primitives/overlay-spawn.c +++ b/overviewer_core/src/primitives/overlay-spawn.c @@ -102,7 +102,7 @@ RenderPrimitiveInterface primitive_overlay_spawn = { sizeof(RenderPrimitiveSpawn), overlay_spawn_start, overlay_spawn_finish, - overlay_occluded, + NULL, NULL, overlay_draw, }; diff --git a/overviewer_core/src/primitives/overlay.c b/overviewer_core/src/primitives/overlay.c index f2152d0..6e28062 100644 --- a/overviewer_core/src/primitives/overlay.c +++ b/overviewer_core/src/primitives/overlay.c @@ -45,21 +45,6 @@ overlay_finish(void *data, RenderState *state) { Py_DECREF(self->white_color); } -int -overlay_occluded(void *data, RenderState *state, int x, int y, int z) { - if ( (x != 0) && (y != 15) && (z != 127) && - !render_mode_hidden(state->rendermode, x-1, y, z) && - !render_mode_hidden(state->rendermode, x, y, z+1) && - !render_mode_hidden(state->rendermode, x, y+1, z) && - !is_transparent(getArrayByte3D(state->blocks, x-1, y, z)) && - !is_transparent(getArrayByte3D(state->blocks, x, y, z+1)) && - !is_transparent(getArrayByte3D(state->blocks, x, y+1, z))) { - return 1; - } - - return 0; -} - void overlay_draw(void *data, RenderState *state, PyObject *src, PyObject *mask, PyObject *mask_light) { RenderPrimitiveOverlay *self = (RenderPrimitiveOverlay *)data; @@ -72,9 +57,6 @@ overlay_draw(void *data, RenderState *state, PyObject *src, PyObject *mask, PyOb else if (state->block == 78) // snow increment=9; - /* clear the draw space -- set alpha to 0 within mask */ - tint_with_mask(state->img, 255, 255, 255, 0, mask, state->imgx, state->imgy, 0, 0); - /* skip rendering the overlay if we can't see it */ if (state->z != 127) { unsigned char top_block = getArrayByte3D(state->blocks, state->x, state->y, state->z+1); @@ -112,7 +94,7 @@ RenderPrimitiveInterface primitive_overlay = { sizeof(RenderPrimitiveOverlay), overlay_start, overlay_finish, - overlay_occluded, + NULL, NULL, overlay_draw, }; diff --git a/overviewer_core/src/primitives/overlay.h b/overviewer_core/src/primitives/overlay.h index 4529ab2..cc028bb 100644 --- a/overviewer_core/src/primitives/overlay.h +++ b/overviewer_core/src/primitives/overlay.h @@ -29,4 +29,3 @@ typedef struct { extern RenderPrimitiveInterface primitive_overlay; void overlay_draw(void *data, RenderState *state, PyObject *src, PyObject *mask, PyObject *mask_light); -int overlay_occluded(void *data, RenderState *state, int x, int y, int z); From 02e5ec17e58a540c25b320478ed9ecc670f28e74 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Sun, 26 Feb 2012 11:57:44 -0500 Subject: [PATCH 14/17] added docs for overlay primitives --- docs/config.rst | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/config.rst b/docs/config.rst index d6d26bd..5b073dd 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -430,6 +430,26 @@ SmoothLighting (same as Lighting) +ClearBase + Forces the background to be transparent. Use this in place of Base + for rendering pure overlays. + +SpawnOverlay + Color the map red in areas where monsters can spawn. Either use + this on top of other modes, or on top of ClearBase to create a + pure overlay. + +MineralOverlay + Color the map according to what minerals can be found + underneath. Either use this on top of other modes, or on top of + ClearBase to create a pure overlay. + + **Options** + + minerals + A list of (blockid, (r, g, b)) tuples to use as colors. If not + provided, a default list of common minerals is used. + Defining Custom Rendermodes --------------------------- Each rendermode primitive listed above is a Python *class* that is automatically From f74769e988609ffaffbcebf60206561fe3b885fe Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Sun, 26 Feb 2012 11:58:19 -0500 Subject: [PATCH 15/17] updated MANIFEST to include primitive sources --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index ecc747a..b5f518e 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -6,4 +6,5 @@ include sample.settings.py recursive-include contrib/ *.py recursive-include overviewer_core/ *.py recursive-include overviewer_core/src/ *.c *.h +recursive-include overviewer_core/src/primitives/ *.c *.h recursive-include overviewer_core/data/ * From 4f0e844bb53c9c48bd9dbd7c80f96b5cb11eed73 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Sun, 26 Feb 2012 13:10:05 -0500 Subject: [PATCH 16/17] pickled signals now work as expected --- overviewer_core/signals.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/overviewer_core/signals.py b/overviewer_core/signals.py index 3e8dc5e..ca2eab8 100755 --- a/overviewer_core/signals.py +++ b/overviewer_core/signals.py @@ -89,3 +89,12 @@ class Signal(object): # convenience def __call__(self, *args, **kwargs): self.emit(*args, **kwargs) + + # force pickled signals to redirect to existing signals + def __getstate__(self): + return self.fullname + def __setstate__(self, fullname): + for attr in dir(self.signals[fullname]): + if attr.startswith('_'): + continue + setattr(self, attr, getattr(self.signals[fullname], attr)) From e50cb2db88468dc94776471983485a3a3915f09d Mon Sep 17 00:00:00 2001 From: Andrew Brown Date: Sun, 26 Feb 2012 17:59:37 -0500 Subject: [PATCH 17/17] Generalized regionset wrapper objects so they can be chained --- overviewer.py | 5 ++- overviewer_core/world.py | 76 ++++++++++++++++++++++++++-------------- 2 files changed, 54 insertions(+), 27 deletions(-) diff --git a/overviewer.py b/overviewer.py index 0821a7b..83ee257 100755 --- a/overviewer.py +++ b/overviewer.py @@ -365,8 +365,11 @@ dir but you forgot to put quotes around the directory, since it contains spaces. if rset == None: # indicates no such dimension was found: logging.error("Sorry, you requested dimension '%s' for %s, but I couldn't find it", render['dimension'], render_name) return 1 + + # If this is to be a rotated regionset, wrap it in a RotatedRegionSet + # object if (render['northdirection'] > 0): - rset = rset.rotate(render['northdirection']) + rset = world.RotatedRegionSet(rset, render['northdirection']) logging.debug("Using RegionSet %r", rset) # create our TileSet from this RegionSet diff --git a/overviewer_core/world.py b/overviewer_core/world.py index 5e6e0c5..603d4ff 100644 --- a/overviewer_core/world.py +++ b/overviewer_core/world.py @@ -36,10 +36,10 @@ class BiomeDataDoesntExist(Exception): pass def log_other_exceptions(func): - """A decorator that prints out any errors that are not - ChunkDoesntExist or BiomeDataDoesntExist errors. This decorates - get_chunk because the C code is likely to swallow exceptions, so - this will at least make them visible. + """A decorator that prints out any errors that are not ChunkDoesntExist or + BiomeDataDoesntExist errors. This should decorate any functions or methods + called by the C code, such as get_chunk(), because the C code is likely to + swallow exceptions. This will at least make them visible. """ functools.wraps(func) @@ -265,7 +265,6 @@ class RegionSet(object): raise Exception("Woah, what kind of dimension is this! %r" % self.regiondir) # this is decorated with cache.lru_cache in __init__(). Be aware! - @log_other_exceptions def _get_biome_data_for_region(self, regionx, regionz): """Get the block of biome data for an entire region. Biome data is in the format output by Minecraft Biome Extractor: @@ -366,9 +365,6 @@ class RegionSet(object): return chunk_data - def rotate(self, north_direction): - return RotatedRegionSet(self.regiondir, north_direction) - def iterate_chunks(self): """Returns an iterator over all chunk metadata in this world. Iterates over tuples of integers (x,z,mtime) for each chunk. Other chunk data @@ -419,6 +415,33 @@ class RegionSet(object): x = int(p[1]) y = int(p[2]) yield (x, y, path) + +class RegionSetWrapper(object): + """This is the base class for all "wrappers" of RegionSet objects. A + wrapper is an object that acts similarly to a subclass: some methods are + overridden and functionality is changed, others may not be. The difference + here is that these wrappers may wrap each other, forming chains. + + In fact, subclasses of this object may act exactly as if they've subclassed + the original RegionSet object, except the first parameter of the + constructor is a regionset object, not a regiondir. + + This class must implement the full public interface of RegionSet objects + + """ + def __init__(self, rsetobj): + self._r = rsetobj + + def get_type(self): + return self._r.get_type() + def get_biome_data(self, x, z): + return self._r.get_biome_data(x,z) + def get_chunk(self, x, z): + return self._r.get_chunk(x,z) + def iterate_chunks(self): + return self._r.iterate_chunks() + def get_chunk_mtime(self, x, z): + return self._r.get_chunk_mtime(x,z) # see RegionSet.rotate. These values are chosen so that they can be # passed directly to rot90; this means that they're the number of @@ -428,7 +451,7 @@ UPPER_RIGHT = 1 ## - Return the world such that north is down the +X axis (rotat LOWER_RIGHT = 2 ## - Return the world such that north is down the +Z axis (rotate 180 degrees) LOWER_LEFT = 3 ## - Return the world such that north is down the -X axis (rotate 90 degrees clockwise) -class RotatedRegionSet(RegionSet): +class RotatedRegionSet(RegionSetWrapper): """A regionset, only rotated such that north points in the given direction """ @@ -440,32 +463,33 @@ class RotatedRegionSet(RegionSet): _ROTATE_180 = lambda x,z: (-x,-z) # These take rotated coords and translate into un-rotated coords - _unrotation_funcs = { - 0: _NO_ROTATION, - 1: _ROTATE_COUNTERCLOCKWISE, - 2: _ROTATE_180, - 3: _ROTATE_CLOCKWISE, - } + _unrotation_funcs = [ + _NO_ROTATION, + _ROTATE_COUNTERCLOCKWISE, + _ROTATE_180, + _ROTATE_CLOCKWISE, + ] # These translate un-rotated coordinates into rotated coordinates - _rotation_funcs = { - 0: _NO_ROTATION, - 1: _ROTATE_CLOCKWISE, - 2: _ROTATE_180, - 3: _ROTATE_COUNTERCLOCKWISE, - } + _rotation_funcs = [ + _NO_ROTATION, + _ROTATE_CLOCKWISE, + _ROTATE_180, + _ROTATE_COUNTERCLOCKWISE, + ] - def __init__(self, regiondir, north_dir): + def __init__(self, rsetobj, north_dir): self.north_dir = north_dir self.unrotate = self._unrotation_funcs[north_dir] self.rotate = self._rotation_funcs[north_dir] - super(RotatedRegionSet, self).__init__(regiondir) + super(RotatedRegionSet, self).__init__(rsetobj) - # Re-initialize upon unpickling + # Re-initialize upon unpickling. This is needed because we store a couple + # lambda functions as instance variables def __getstate__(self): - return (self.regiondir, self.north_dir) + return (self._r, self.north_dir) def __setstate__(self, args): self.__init__(args[0], args[1]) @@ -476,7 +500,7 @@ class RotatedRegionSet(RegionSet): def get_chunk(self, x, z): x,z = self.unrotate(x,z) - chunk_data = super(RotatedRegionSet, self).get_chunk(x,z) + chunk_data = dict(super(RotatedRegionSet, self).get_chunk(x,z)) chunk_data['Blocks'] = numpy.rot90(chunk_data['Blocks'], self.north_dir) chunk_data['Data'] = numpy.rot90(chunk_data['Data'], self.north_dir) chunk_data['SkyLight'] = numpy.rot90(chunk_data['SkyLight'], self.north_dir)