diff --git a/chunk.py b/chunk.py index 8454c2b..ad4f45c 100644 --- a/chunk.py +++ b/chunk.py @@ -371,16 +371,16 @@ class ChunkRenderer(object): # and finally check for a block with same blockid. I we aren't in the border of a chunk, # check for the block having the sme blockid. - if (up_right_blocks != None and up_right_blocks[0,y,z] == blockid) if x == 15 else blocks[x+1,y,z] == blockid: + if (up_right_blocks is not None and up_right_blocks[0,y,z] == blockid) if x == 15 else blocks[x+1,y,z] == blockid: pseudo_data = pseudo_data | 0b1000 - if (right_blocks != None and right_blocks[x,0,z] == blockid) if y == 15 else blocks[x,y + 1,z] == blockid: + if (right_blocks is not None and right_blocks[x,0,z] == blockid) if y == 15 else blocks[x,y + 1,z] == blockid: pseudo_data = pseudo_data | 0b0100 - if (left_blocks != None and left_blocks[15,y,z] == blockid) if x == 0 else blocks[x - 1,y,z] == blockid: + if (left_blocks is not None and left_blocks[15,y,z] == blockid) if x == 0 else blocks[x - 1,y,z] == blockid: pseudo_data = pseudo_data | 0b0010 - if (up_left_blocks != None and up_left_blocks[x,15,z] == blockid) if y == 0 else blocks[x,y - 1,z] == blockid: + if (up_left_blocks is not None and up_left_blocks[x,15,z] == blockid) if y == 0 else blocks[x,y - 1,z] == blockid: pseudo_data = pseudo_data | 0b0001 # rotate the bits for other north orientations @@ -443,7 +443,7 @@ class ChunkRenderer(object): # make sure we have a correctly-ranged coordinates and enough # info about the chunk - if not (blocks != None and skylight != None and blocklight != None and + if not (blocks is not None and skylight is not None and blocklight is not None and local_x >= 0 and local_x < 16 and local_y >= 0 and local_y < 16 and local_z >= 0 and local_z < 128): # we have no useful info, return default diff --git a/composite.py b/composite.py index f3edf6b..6cbe44e 100644 --- a/composite.py +++ b/composite.py @@ -36,11 +36,11 @@ def alpha_over(dest, src, pos_or_rect=(0, 0), mask=None): either be a position or a rectangle, specifying where on dest to put src. Falls back to dest.paste() if the alpha_over extension can't be found.""" - if mask == None: + if mask is None: mask = src global extension_alpha_over - if extension_alpha_over != None: + if extension_alpha_over is not None: # extension ALWAYS expects rects, so convert if needed if len(pos_or_rect) == 2: pos_or_rect = (pos_or_rect[0], pos_or_rect[1], src.size[0], src.size[1]) diff --git a/quadtree.py b/quadtree.py index 1edbb9e..1d43a8d 100644 --- a/quadtree.py +++ b/quadtree.py @@ -26,6 +26,7 @@ import logging import util import cPickle import stat +import errno from time import gmtime, strftime, sleep from PIL import Image @@ -438,7 +439,7 @@ class QuadtreeGen(object): # return (col, row, chunkx, chunky, regionpath) chunkx, chunky = self.world.unconvert_coords(col, row) c = self.world.get_region_path(chunkx, chunky) - if os.path.exists(c): + if c is not None: chunklist.append((col, row, chunkx, chunky, c)) return chunklist @@ -451,53 +452,38 @@ def render_innertile(dest, name, imgformat, optimizeimg): imgpath = os.path.join(dest, name) + "." + imgformat if name == "base": - q0path = os.path.join(dest, "0." + imgformat) - q1path = os.path.join(dest, "1." + imgformat) - q2path = os.path.join(dest, "2." + imgformat) - q3path = os.path.join(dest, "3." + imgformat) + quadPath = [[(0,0),os.path.join(dest, "0." + imgformat)],[(192,0),os.path.join(dest, "1." + imgformat)], [(0, 192),os.path.join(dest, "2." + imgformat)],[(192,192),os.path.join(dest, "3." + imgformat)]] else: - q0path = os.path.join(dest, name, "0." + imgformat) - q1path = os.path.join(dest, name, "1." + imgformat) - q2path = os.path.join(dest, name, "2." + imgformat) - q3path = os.path.join(dest, name, "3." + imgformat) - - # Check which ones exist - if not os.path.exists(q0path): - q0path = None - if not os.path.exists(q1path): - q1path = None - if not os.path.exists(q2path): - q2path = None - if not os.path.exists(q3path): - q3path = None - + quadPath = [[(0,0),os.path.join(dest, name, "0." + imgformat)],[(192,0),os.path.join(dest, name, "1." + imgformat)],[(0, 192),os.path.join(dest, name, "2." + imgformat)],[(192,192),os.path.join(dest, name, "3." + imgformat)]] + + #stat the tile, we need to know if it exists or it's mtime + try: + tile_mtime = os.stat(imgpath)[stat.ST_MTIME]; + except OSError, e: + if e.errno != errno.ENOENT: + raise + tile_mtime = None + + #check mtimes on each part of the quad, this also checks if they exist + needs_rerender = tile_mtime is None + quadPath_filtered = [] + for path in quadPath: + try: + quad_mtime = os.stat(path[1])[stat.ST_MTIME]; + quadPath_filtered.append(path) + if quad_mtime > tile_mtime: + needs_rerender = True + except OSError: + # We need to stat all the quad files, so keep looping + pass # do they all not exist? - if not (q0path or q1path or q2path or q3path): - if os.path.exists(imgpath): + if quadPath_filtered == []: + if tile_mtime is not None: os.unlink(imgpath) return - - # check the mtimes - try: - tile_mtime = os.path.getmtime(imgpath) - needs_rerender = False - - # remove non-existant paths - components = [q0path, q1path, q2path, q3path] - components = filter(lambda p: p != None, components) - - for mtime in [os.path.getmtime(path) for path in components]: - if mtime > tile_mtime: - needs_rerender = True - break - - # quit now if we don't need rerender - if not needs_rerender: - return - except OSError: - # one of our mtime calls failed, so we'll continue - pass - + # quit now if we don't need rerender + if not needs_rerender: + return #logging.debug("writing out innertile {0}".format(imgpath)) # Create the actual image now @@ -506,30 +492,12 @@ def render_innertile(dest, name, imgformat, optimizeimg): # we'll use paste (NOT alpha_over) for quadtree generation because # this is just straight image stitching, not alpha blending - if q0path: + for path in quadPath_filtered: try: - quad0 = Image.open(q0path).resize((192,192), Image.ANTIALIAS) - img.paste(quad0, (0,0)) + quad = Image.open(path[1]).resize((192,192), Image.ANTIALIAS) + img.paste(quad, path[0]) except Exception, e: - logging.warning("Couldn't open %s. It may be corrupt, you may need to delete it. %s", q0path, e) - if q1path: - try: - quad1 = Image.open(q1path).resize((192,192), Image.ANTIALIAS) - img.paste(quad1, (192,0)) - except Exception, e: - logging.warning("Couldn't open %s. It may be corrupt, you may need to delete it. %s", q1path, e) - if q2path: - try: - quad2 = Image.open(q2path).resize((192,192), Image.ANTIALIAS) - img.paste(quad2, (0, 192)) - except Exception, e: - logging.warning("Couldn't open %s. It may be corrupt, you may need to delete it. %s", q2path, e) - if q3path: - try: - quad3 = Image.open(q3path).resize((192,192), Image.ANTIALIAS) - img.paste(quad3, (192, 192)) - except Exception, e: - logging.warning("Couldn't open %s. It may be corrupt, you may need to delete it. %s", q3path, e) + logging.warning("Couldn't open %s. It may be corrupt, you may need to delete it. %s", path[1], e) # Save it if imgformat == 'jpg': @@ -594,12 +562,20 @@ def render_worldtile(quadtree, chunks, colstart, colend, rowstart, rowend, path) _, _, chunkx, chunky, region = chunk with open(region, 'rb') as region: r = nbt.MCRFileReader(region) - return r.load_chunk(chunkx, chunky) != None + return r.chunkExists(chunkx, chunky) chunks = filter(chunk_exists, chunks) + #stat the file, we need to know if it exists or it's mtime + try: + tile_mtime = os.stat(imgpath)[stat.ST_MTIME]; + except OSError, e: + if e.errno != errno.ENOENT: + raise + tile_mtime = None + if not chunks: # No chunks were found in this tile - if os.path.exists(imgpath): + if tile_mtime is not None: os.unlink(imgpath) return None @@ -611,18 +587,23 @@ def render_worldtile(quadtree, chunks, colstart, colend, rowstart, rowend, path) except OSError, e: # Ignore errno EEXIST: file exists. Since this is multithreaded, # two processes could conceivably try and create the same directory - # at the same time. - import errno + # at the same time. if e.errno != errno.EEXIST: raise # check chunk mtimes to see if they are newer try: - tile_mtime = os.path.getmtime(imgpath) + #tile_mtime = os.path.getmtime(imgpath) + regionMtimes = {} needs_rerender = False for col, row, chunkx, chunky, regionfile in chunks: - # check region file mtime first - if os.path.getmtime(regionfile) <= tile_mtime: + # check region file mtime first. + # Note: we cache the value since it's actually very likely we will have multipule chunks in the same region, and syscalls are expensive + regionMtime = regionMtimes.get(regionfile,None) + if regionMtime is None: + regionMtime = os.path.getmtime(regionfile) + regionMtimes[regionfile] = regionMtime + if regionMtime <= tile_mtime: continue # checking chunk mtime diff --git a/setup.py b/setup.py index 059631d..cf12c18 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ setup_kwargs['cmdclass'] = {} # py2exe options # -if py2exe != None: +if py2exe is not None: setup_kwargs['console'] = ['gmap.py'] setup_kwargs['data_files'] = [('textures', ['textures/lava.png', 'textures/water.png', 'textures/fire.png']), ('', ['config.js', 'COPYING.txt', 'README.rst']), diff --git a/textures.py b/textures.py index f19c5c3..08d70f8 100644 --- a/textures.py +++ b/textures.py @@ -878,15 +878,16 @@ def getBiomeData(worlddir, chunkX, chunkY): if biomeFile == currentBiomeFile: return currentBiomeData - f = open(os.path.join(worlddir, "biomes", biomeFile), "rb") - rawdata = f.read() - f.close() - - # make sure the file size is correct - if not len(rawdata) == 512 * 512 * 2: - raise Exception("Biome file %s is not valid." % (biomeFile,)) - - data = numpy.frombuffer(rawdata, dtype=numpy.dtype(">u2")) + try: + with open(os.path.join(worlddir, "biomes", biomeFile), "rb") as f: + rawdata = f.read() + # make sure the file size is correct + if not len(rawdata) == 512 * 512 * 2: + raise Exception("Biome file %s is not valid." % (biomeFile,)) + data = numpy.frombuffer(rawdata, dtype=numpy.dtype(">u2")) + except IOError: + data = None + pass # no biome data currentBiomeFile = biomeFile currentBiomeData = data diff --git a/world.py b/world.py index 826a651..b50ddfa 100644 --- a/world.py +++ b/world.py @@ -67,10 +67,16 @@ class World(object): mincol = maxcol = minrow = maxrow = 0 - def __init__(self, worlddir, useBiomeData=False): + def __init__(self, worlddir, useBiomeData=False,regionlist=None): self.worlddir = worlddir self.useBiomeData = useBiomeData + #find region files, or load the region list + regionfiles = {} + for x, y, regionfile in self._iterate_regionfiles(): + regionfiles[(x,y)] = (x,y,regionfile) + self.regionfiles = regionfiles + # figure out chunk format is in use # if not mcregion, error out early data = nbt.load(os.path.join(self.worlddir, "level.dat"))[1]['Data'] @@ -102,11 +108,9 @@ class World(object): def get_region_path(self, chunkX, chunkY): """Returns the path to the region that contains chunk (chunkX, chunkY) """ + _, _, regionfile = self.regionfiles.get((chunkX//32, chunkY//32),(None,None,None)); + return regionfile - chunkFile = "region/r.%s.%s.mcr" % (chunkX//32, chunkY//32) - - return os.path.join(self.worlddir, chunkFile) - def convert_coords(self, chunkx, chunky): """Takes a coordinate (chunkx, chunky) where chunkx and chunky are in the chunk coordinate system, and figures out the row and column @@ -168,7 +172,7 @@ class World(object): # find the dimensions of the map, in region files minx = maxx = miny = maxy = 0 found_regions = False - for x, y, regionfile in self._iterate_regionfiles(): + for x, y in self.regionfiles: found_regions = True minx = min(minx, x) maxx = max(maxx, x) @@ -203,18 +207,27 @@ class World(object): self.findTrueSpawn() - def _iterate_regionfiles(self): + def _iterate_regionfiles(self,regionlist=None): """Returns an iterator of all of the region files, along with their coordinates Returns (regionx, regiony, filename)""" - - for dirpath, dirnames, filenames in os.walk(os.path.join(self.worlddir, 'region')): - if not dirnames and filenames and "DIM-1" not in dirpath: - for f in filenames: - if f.startswith("r.") and f.endswith(".mcr"): - p = f.split(".") - yield (int(p[1]), int(p[2]), os.path.join(dirpath, f)) + join = os.path.join + if regionlist is not None: + for path in regionlist: + if path.endswith("\n"): + path = path[:-1] + f = os.path.basename(path) + if f.startswith("r.") and f.endswith(".mcr"): + p = f.split(".") + yield (int(p[1]), int(p[2]), join(self.worlddir, 'region', f)) + else: + for dirpath, dirnames, filenames in os.walk(os.path.join(self.worlddir, 'region')): + if not dirnames and filenames and "DIM-1" not in dirpath: + for f in filenames: + if f.startswith("r.") and f.endswith(".mcr"): + p = f.split(".") + yield (int(p[1]), int(p[2]), join(dirpath, f)) def get_save_dir(): """Returns the path to the local saves directory