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 platform
import sys import sys
# quick version check
if not (sys.version_info[0] == 2 and sys.version_info[1] >= 6): 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" print "Sorry, the Overviewer requires at least Python 2.6 to run"
if sys.version_info[0] >= 3: if sys.version_info[0] >= 3:
print "and will not run on Python 3.0 or later" print "and will not run on Python 3.0 or later"
sys.exit(1) 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
import os.path import os.path
import re import re
@@ -44,110 +32,13 @@ import subprocess
import multiprocessing import multiprocessing
import time import time
import logging 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 optparse import OptionParser
from overviewer_core import util
from overviewer_core import textures
from overviewer_core import optimizeimages, world from overviewer_core import optimizeimages, world
#from overviewer_core import googlemap
from overviewer_core import configParser, tileset, assetmanager, dispatcher 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 = """ helptext = """
%prog [OPTIONS] <World # / Name / Path to World> <tiles dest dir>""" %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) logger.setLevel(loglevel)
def main(): def main():
# bootstrap the logger with defaults # bootstrap the logger with defaults
configure_logger() 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("--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("--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("--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("--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("--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 ) #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) print "Build machine: %s %s" % (overviewer_version.BUILD_PLATFORM, overviewer_version.BUILD_OS)
except ImportError: except ImportError:
print "(build info not found)" print "(build info not found)"
pass return 0
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)
if options.check_terrain: if options.check_terrain:
import hashlib import hashlib
from overviewer_core.textures import _find_file from overviewer_core.textures import Textures
if options.textures_path: # TODO custom textures path?
textures._find_file_local_path = options.textures_path tex = Textures()
try: try:
f = _find_file("terrain.png", verbose=True) f = tex.find_file("terrain.png", verbose=True)
except IOError: except IOError:
logging.error("Could not find the file terrain.png") logging.error("Could not find the file terrain.png")
doExit(code=1, consoleMsg=False) return 1
h = hashlib.sha1() h = hashlib.sha1()
h.update(f.read()) h.update(f.read())
logging.info("Hash of terrain.png file is: `%s`", h.hexdigest()) 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 # 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 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 # 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 # 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]) logging.debug("Using %r as the output_directory", args[0])
destdir = os.path.expanduser(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 elif len(args) == 2: # TODO support this usecase
worlddir = os.path.expanduser(args[0]) worlddir = os.path.expanduser(args[0])
destdir = os.path.expanduser(args[1]) destdir = os.path.expanduser(args[1])
@@ -319,14 +203,7 @@ def main():
if os.path.exists(" ".join(args[start:end])): 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\ 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])) dir but you forgot to put quotes around the directory, since it contains spaces." % " ".join(args[start:end]))
doExit(code=1, consoleMsg=False) return 1
if options.display_config:
# just display the config file and exit
parser.display_config()
doExit(code=0, consoleMsg=False)
# TODO regionlists are per-world # TODO regionlists are per-world
#if options.regionlist: #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: # except IOError as e:
# logging.error("Unable to open file %s to use for changelist." % options.changelist) # logging.error("Unable to open file %s to use for changelist." % options.changelist)
# logging.error("I/O Error: %s" % e.strerror) # logging.error("I/O Error: %s" % e.strerror)
# doExit(code=1, consoleMsg=False) # return 1
#if options.changelist_format != "auto" and not options.changelist: #if options.changelist_format != "auto" and not options.changelist:
# logging.error("changelist_format specified without changelist.") # logging.error("changelist_format specified without changelist.")
# doExit(code=1, consoleMsg=False) # return 1
#if options.changelist_format == "auto": #if options.changelist_format == "auto":
# options.changelist_format = "relative" # options.changelist_format = "relative"
@@ -382,7 +259,7 @@ dir but you forgot to put quotes around the directory, since it contains spaces.
tex.generate() tex.generate()
except IOError, e: except IOError, e:
logging.error(str(e)) logging.error(str(e))
doExit(code=1, consoleMsg=False) return 1
# look at our settings.py file # look at our settings.py file
mw_parser = configParser.MultiWorldParser("settings.py") 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() dispatch.close()
assetMrg.finalize(tilesets) assetMrg.finalize(tilesets)
return 0
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]))
def list_worlds(): def list_worlds():
"Prints out a brief summary of saves found in the default directory" "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.) size = "%.2fMB" % (info['SizeOnDisk'] / 1024. / 1024.)
print formatString % (name, size, playstamp, timestamp) print formatString % (name, size, playstamp, timestamp)
if __name__ == "__main__": if __name__ == "__main__":
multiprocessing.freeze_support() multiprocessing.freeze_support()
try: try:
main() ret = main()
util.exit(ret)
except Exception, e: 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! 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 See http://docs.overviewer.org/en/latest/index.html#help
This is the error that occurred:""") 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
import os.path import os.path
import sys import sys
import platform
from subprocess import Popen, PIPE from subprocess import Popen, PIPE
import logging import logging
from cStringIO import StringIO from cStringIO import StringIO
@@ -88,6 +89,33 @@ def findGitVersion():
except Exception: except Exception:
return "unknown" 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 # http://docs.python.org/library/itertools.html
def roundrobin(iterables): def roundrobin(iterables):
"roundrobin('ABC', 'D', 'EF') --> A D E B F C" "roundrobin('ABC', 'D', 'EF') --> A D E B F C"