0

updated and cleaned up overviewer.py

- moved c_overviewer version checks into __init__.py
 - bare_console support (mostly) moved into util, slightly more sane
 - removed and updated a ton of old code
This commit is contained in:
Aaron Griffith
2012-01-14 01:11:05 -05:00
parent 16fec5085e
commit 624d6ba351
3 changed files with 140 additions and 288 deletions

View File

@@ -18,25 +18,13 @@
import platform
import sys
# quick version check
if not (sys.version_info[0] == 2 and sys.version_info[1] >= 6):
print "Sorry, the Overviewer requires at least Python 2.6 to run"
if sys.version_info[0] >= 3:
print "and will not run on Python 3.0 or later"
sys.exit(1)
isBareConsole = False
if platform.system() == 'Windows':
try:
import ctypes
GetConsoleProcessList = ctypes.windll.kernel32.GetConsoleProcessList
num = GetConsoleProcessList(ctypes.byref(ctypes.c_int(0)), ctypes.c_int(1))
if (num == 1):
isBareConsole = True
except Exception:
pass
import os
import os.path
import re
@@ -44,110 +32,13 @@ import subprocess
import multiprocessing
import time
import logging
from overviewer_core import util
def doExit(msg=None, code=1, wait=None, consoleMsg=True):
'''Exits Overviewer. If `wait` is None, the default
will be true is 'isBareConsole' is true'''
global isBareConsole
if msg:
print msg
if wait == None:
if isBareConsole:
if consoleMsg:
print "\n"
print "The Overviewer is a console program. Please open a Windows command prompt"
print "first and run Overviewer from there. Further documentation is available at"
print "http://docs.overviewer.org/\n"
print "Press [Enter] to close this window."
raw_input()
else:
if wait:
if consoleMsg:
print "\n"
print "The Overviewer is a console program. Please open a Windows command prompt"
print "first and run Overviewer from there. Further documentation is available at"
print "http://docs.overviewer.org/\n"
print "Press [Enter] to close this window."
raw_input()
sys.exit(code)
this_dir = util.get_program_path()
# make sure the c_overviewer extension is available
try:
from overviewer_core import c_overviewer
except ImportError:
## if this is a frozen windows package, the following error messages about
## building the c_overviewer extension are not appropriate
if hasattr(sys, "frozen"):
print "Something has gone wrong importing the c_overviewer extension. Please"
print "make sure the 2008 and 2010 redistributable packages from Microsoft"
print "are installed."
doExit()
## try to find the build extension
ext = os.path.join(this_dir, "overviewer_core", "c_overviewer.%s" % ("pyd" if platform.system() == "Windows" else "so"))
if os.path.exists(ext):
print "Something has gone wrong importing the c_overviewer extension. Please"
print "make sure it is up-to-date (clean and rebuild)"
doExit()
import traceback
traceback.print_exc()
print ""
print "You need to compile the c_overviewer module to run Minecraft Overviewer."
print "Run `python setup.py build`, or see the README for details."
doExit()
from overviewer_core import textures
if hasattr(sys, "frozen"):
pass # we don't bother with a compat test since it should always be in sync
elif "extension_version" in dir(c_overviewer):
# check to make sure the binary matches the headers
if os.path.exists(os.path.join(this_dir, "overviewer_core", "src", "overviewer.h")):
with open(os.path.join(this_dir, "overviewer_core", "src", "overviewer.h")) as f:
lines = f.readlines()
lines = filter(lambda x: x.startswith("#define OVERVIEWER_EXTENSION_VERSION"), lines)
if lines:
l = lines[0]
if int(l.split()[2].strip()) != c_overviewer.extension_version():
print "Please rebuild your c_overviewer module. It is out of date!"
doExit(code=1, consoleMsg=True)
else:
print "Please rebuild your c_overviewer module. It is out of date!"
doExit()
from optparse import OptionParser
from overviewer_core import util
from overviewer_core import textures
from overviewer_core import optimizeimages, world
#from overviewer_core import googlemap
from overviewer_core import configParser, tileset, assetmanager, dispatcher
# definitions of built-in custom modes
# usually because what used to be a mode became an option
# for example, night mode
builtin_custom_rendermodes = {}
# 'night' : {
# 'parent' : 'lighting',
# 'label' : 'Night',
# 'description' : 'like "lighting", except at night',
# 'options' : {'night' : True}
# },
# 'smooth-night' : {
# 'parent' : 'smooth-lighting',
# 'label' : 'Smooth Night',
# 'description' : 'like "lighting", except smooth and at night',
# 'options' : {'night' : True}
# },
# }
helptext = """
%prog [OPTIONS] <World # / Name / Path to World> <tiles dest dir>"""
@@ -195,7 +86,6 @@ def configure_logger(loglevel=logging.INFO, verbose=False):
logger.setLevel(loglevel)
def main():
# bootstrap the logger with defaults
configure_logger()
@@ -216,7 +106,6 @@ def main():
#parser.add_option("--forcerender", dest="forcerender", help="Force re-rendering the entire map (or the given regionlist). Useful for re-rendering without deleting it.", action="store_true")
#parser.add_option("--stochastic-render", dest="stochastic_render", help="Rerender a non-updated tile randomly, with the given probability (between 0 and 1). Useful for incrementally updating a map with a new mode.", type="float", advanced=True, default=0.0, metavar="PROBABILITY")
#parser.add_option("--rendermodes", dest="rendermode", help="Specifies the render types, separated by ',', ':', or '/'. Use --list-rendermodes to list them all.", type="choice", required=True, default=avail_rendermodes[0], listify=True)
parser.add_option("--list-rendermodes", dest="list_rendermodes", action="store_true", help="List available render modes and exit.")
#parser.add_option("--rendermode-options", dest="rendermode_options", default={}, advanced=True, help="Used to specify options for different rendermodes. Only useful in a settings.py file")
#parser.add_option("--custom-rendermodes", dest="custom_rendermodes", default={}, advanced=True, help="Used to define custom rendermodes. Only useful in a settings.py file")
#parser.add_option("--imgformat", dest="imgformat", help="The image output format to use. Currently supported: png(default), jpg.", advanced=True )
@@ -253,59 +142,54 @@ def main():
print "Build machine: %s %s" % (overviewer_version.BUILD_PLATFORM, overviewer_version.BUILD_OS)
except ImportError:
print "(build info not found)"
pass
doExit(code=0, consoleMsg=False)
# setup c_overviewer rendermode customs / options
for mode in builtin_custom_rendermodes:
c_overviewer.add_custom_render_mode(mode, builtin_custom_rendermodes[mode])
# TODO allow custom rendermodes to be specified in a settings.py file
#for mode in options.custom_rendermodes:
# c_overviewer.add_custom_render_mode(mode, options.custom_rendermodes[mode])
#for mode in options.rendermode_options:
# c_overviewer.set_render_mode_options(mode, options.rendermode_options[mode])
if options.list_rendermodes:
list_rendermodes()
doExit(code=0, consoleMsg=False)
return 0
if options.check_terrain:
import hashlib
from overviewer_core.textures import _find_file
if options.textures_path:
textures._find_file_local_path = options.textures_path
from overviewer_core.textures import Textures
# TODO custom textures path?
tex = Textures()
try:
f = _find_file("terrain.png", verbose=True)
f = tex.find_file("terrain.png", verbose=True)
except IOError:
logging.error("Could not find the file terrain.png")
doExit(code=1, consoleMsg=False)
return 1
h = hashlib.sha1()
h.update(f.read())
logging.info("Hash of terrain.png file is: `%s`", h.hexdigest())
doExit(code=0, consoleMsg=False)
return 0
if options.display_config:
# just display the config file and exit
parser.display_config()
return 0
# TODO remove advanced help? needs discussion
#if options.advanced_help:
# parser.advanced_help()
# doExit(code=0, consoleMsg=False)
# TODO right now, we will not let users specify worlds to render on the command line.
# TODO in the future, we need to also let worlds be specified on the command line
# if no arguments are provided, print out a helpful message
if len(args) == 0:
# first provide an appropriate error for bare-console users
# that don't provide any options
if util.is_bare_console():
print "\n"
print "The Overviewer is a console program. Please open a Windows command prompt"
print "first and run Overviewer from there. Further documentation is available at"
print "http://docs.overviewer.org/\n"
else:
# more helpful message for users who know what they're doing
logging.error("You need to give me your world number or directory")
parser.print_help()
list_worlds()
return 1
# for multiworld, we must specify the *outputdir* on the command line
if len(args) == 1:
elif len(args) == 1:
logging.debug("Using %r as the output_directory", args[0])
destdir = os.path.expanduser(args[0])
elif len(args) < 1:
logging.error("You need to give me your world number or directory")
parser.print_help()
list_worlds()
doExit(code=1, consoleMsg=True)
elif len(args) == 2: # TODO support this usecase
worlddir = os.path.expanduser(args[0])
destdir = os.path.expanduser(args[1])
@@ -319,14 +203,7 @@ def main():
if os.path.exists(" ".join(args[start:end])):
logging.warning("It looks like you meant to specify \"%s\" as your world dir or your output\n\
dir but you forgot to put quotes around the directory, since it contains spaces." % " ".join(args[start:end]))
doExit(code=1, consoleMsg=False)
if options.display_config:
# just display the config file and exit
parser.display_config()
doExit(code=0, consoleMsg=False)
return 1
# TODO regionlists are per-world
#if options.regionlist:
@@ -363,11 +240,11 @@ dir but you forgot to put quotes around the directory, since it contains spaces.
# except IOError as e:
# logging.error("Unable to open file %s to use for changelist." % options.changelist)
# logging.error("I/O Error: %s" % e.strerror)
# doExit(code=1, consoleMsg=False)
# return 1
#if options.changelist_format != "auto" and not options.changelist:
# logging.error("changelist_format specified without changelist.")
# doExit(code=1, consoleMsg=False)
# return 1
#if options.changelist_format == "auto":
# options.changelist_format = "relative"
@@ -382,7 +259,7 @@ dir but you forgot to put quotes around the directory, since it contains spaces.
tex.generate()
except IOError, e:
logging.error(str(e))
doExit(code=1, consoleMsg=False)
return 1
# look at our settings.py file
mw_parser = configParser.MultiWorldParser("settings.py")
@@ -429,127 +306,7 @@ dir but you forgot to put quotes around the directory, since it contains spaces.
dispatch.close()
assetMrg.finalize(tilesets)
sys.exit("early abort")
# First do world-level preprocessing. This scans the world hierarchy, reads
# in the region files and caches chunk modified times, and determines the
# chunk bounds (max and min in both dimensions)
w = world.World(worlddir)
rset = w.get_regionsets()[0] # use the default
if north_direction == 'auto':
# TODO get this from the asset manager # north_direction = w.persistentData['north_direction']
options.north_direction = north_direction
# TODO deal with changed north direction
# A couple other things we need to figure out about the world:
w.get_regionsets()[0].determine_bounds()
# w.find_true_spawn()
logging.info("Rendering the following tilesets: %s", ",".join(options.rendermode))
bgcolor = (int(options.bg_color[1:3],16),
int(options.bg_color[3:5],16),
int(options.bg_color[5:7],16),
0)
# Create the quadtrees. There is one quadtree per rendermode requested, and
# therefore, per output directory hierarchy of tiles. Each quadtree
# individually computes its depth and size. The constructor computes the
# depth of the tree, while the go() method re-arranges tiles if the current
# depth differs from the computed depth.
q = []
qtree_args = {'depth' : options.zoom,
'imgformat' : imgformat,
'imgquality' : options.imgquality,
'optimizeimg' : optimizeimg,
'bgcolor' : bgcolor,
'forcerender' : options.forcerender,
'rerender_prob' : options.stochastic_render
}
for rendermode in options.rendermode:
if rendermode == 'normal':
qtree = quadtree.QuadtreeGen(rset, destdir, rendermode=rendermode, tiledir='tiles', **qtree_args)
else:
qtree = quadtree.QuadtreeGen(rset, destdir, rendermode=rendermode, **qtree_args)
q.append(qtree)
# Make sure the quadtrees are the correct depth
for qtree in q:
qtree.check_depth()
# create the distributed render
r = rendernode.RenderNode(q, options)
# for the pool_initializer
r.builtin_custom_rendermodes = builtin_custom_rendermodes
# write out the map and web assets
m = googlemap.MapGen(q, configInfo=options)
m.go(options.procs)
# render the tiles!
r.go(options.procs)
# finish up the map
m.finalize()
if options.changelist:
changed=[]
for tile in r.rendered_tiles:
if options.changelist_format=="absolute":
tile=os.path.abspath(tile)
changed.append(tile)
for zl in range(q[0].p - 1):
tile=os.path.dirname(tile)
changed.append("%s.%s" % (tile, imgformat))
#Quick and nasty way to remove duplicate entries
changed=list(set(changed))
changed.sort()
for path in changed:
changefile.write("%s\n" % path)
changefile.close()
def list_rendermodes():
"Prints out a pretty list of supported rendermodes"
def print_mode_tree(line_max, mode, prefix='', last=False):
"Prints out a mode tree for the given mode, with an indent."
try:
info = c_overviewer.get_render_mode_info(mode)
except ValueError:
info = {}
print prefix + '+-', mode,
if 'description' in info:
print " " * (line_max - len(prefix) - len(mode) - 2),
print info['description']
else:
print
children = c_overviewer.get_render_mode_children(mode)
for child in children:
child_last = (child == children[-1])
if last:
child_prefix = ' '
else:
child_prefix = '| '
print_mode_tree(line_max, child, prefix=prefix + child_prefix, last=child_last)
avail_rendermodes = c_overviewer.get_render_modes()
line_lengths = {}
parent_modes = []
for mode in avail_rendermodes:
inherit = c_overviewer.get_render_mode_inheritance(mode)
if not inherit[0] in parent_modes:
parent_modes.append(inherit[0])
line_lengths[mode] = 2 * len(inherit) + 1 + len(mode)
line_length = max(line_lengths.values())
for mode in parent_modes:
print_mode_tree(line_length, mode, last=(mode == parent_modes[-1]))
return 0
def list_worlds():
"Prints out a brief summary of saves found in the default directory"
@@ -582,18 +339,14 @@ def list_worlds():
size = "%.2fMB" % (info['SizeOnDisk'] / 1024. / 1024.)
print formatString % (name, size, playstamp, timestamp)
if __name__ == "__main__":
multiprocessing.freeze_support()
try:
main()
ret = main()
util.exit(ret)
except Exception, e:
print e
if e.args[0] == "Exiting":
logging.info("Exiting...")
doExit(code=0, wait=False)
logging.exception("""An error has occurred. This may be a bug. Please let us know!
See http://docs.overviewer.org/en/latest/index.html#help
This is the error that occurred:""")
doExit(code=1, consoleMsg=False)
util.exit(1)

View File

@@ -0,0 +1,71 @@
#
# Code to check to make sure c_overviewer is built and working
#
import os.path
import platform
import traceback
import sys
import util
def check_c_overviewer():
"""Check to make sure c_overviewer works and is up-to-date. Prints
out a helpful error and returns 1 if something's wrong, returns 0
otherwise.
"""
root_dir = util.get_program_path()
# make sure the c_overviewer extension is available
try:
import c_overviewer
except ImportError:
## if this is a frozen windows package, the following error messages about
## building the c_overviewer extension are not appropriate
if hasattr(sys, "frozen") and platform.system() == 'Windows':
print "Something has gone wrong importing the c_overviewer extension. Please"
print "make sure the 2008 and 2010 redistributable packages from Microsoft"
print "are installed."
return 1
## try to find the build extension
ext = os.path.join(root_dir, "overviewer_core", "c_overviewer.%s" % ("pyd" if platform.system() == "Windows" else "so"))
if os.path.exists(ext):
traceback.print_exc()
print ""
print "Something has gone wrong importing the c_overviewer extension. Please"
print "make sure it is up-to-date (clean and rebuild)"
return 1
print "You need to compile the c_overviewer module to run Minecraft Overviewer."
print "Run `python setup.py build`, or see the README for details."
return 1
#
# make sure it's up-to-date
#
if hasattr(sys, "frozen"):
pass # we don't bother with a compat test since it should always be in sync
elif "extension_version" in dir(c_overviewer):
# check to make sure the binary matches the headers
if os.path.exists(os.path.join(root_dir, "overviewer_core", "src", "overviewer.h")):
with open(os.path.join(root_dir, "overviewer_core", "src", "overviewer.h")) as f:
lines = f.readlines()
lines = filter(lambda x: x.startswith("#define OVERVIEWER_EXTENSION_VERSION"), lines)
if lines:
l = lines[0]
if int(l.split()[2].strip()) != c_overviewer.extension_version():
print "Please rebuild your c_overviewer module. It is out of date!"
return 1
else:
print "Please rebuild your c_overviewer module. It is out of date!"
return 1
# all good!
return 0
# only check the module if we're not setup.py
if not sys.argv[0].endswith("setup.py"):
ret = check_c_overviewer()
if ret > 0:
util.exit(ret)

View File

@@ -21,6 +21,7 @@ import imp
import os
import os.path
import sys
import platform
from subprocess import Popen, PIPE
import logging
from cStringIO import StringIO
@@ -88,6 +89,33 @@ def findGitVersion():
except Exception:
return "unknown"
def is_bare_console():
"""Returns true if Overviewer is running in a bare console in
Windows, that is, if overviewer wasn't started in a cmd.exe
session.
"""
if platform.system() == 'Windows':
try:
import ctypes
GetConsoleProcessList = ctypes.windll.kernel32.GetConsoleProcessList
num = GetConsoleProcessList(ctypes.byref(ctypes.c_int(0)), ctypes.c_int(1))
if (num == 1):
return True
except Exception:
pass
return False
def exit(ret=0):
"""Drop-in replacement for sys.exit that will automatically detect
bare consoles and wait for user input before closing.
"""
if ret and is_bare_console():
print
print "Press [Enter] to close this window."
raw_input()
sys.exit(ret)
# http://docs.python.org/library/itertools.html
def roundrobin(iterables):
"roundrobin('ABC', 'D', 'EF') --> A D E B F C"