#!/usr/bin/env python3 ######################################################################### # Copyright (C) 2011 Tavian Barnes # # # # This file is part of Dimension. # # # # Dimension 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. # # # # Dimension 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 this program. If not, see . # ######################################################################### import argparse as _argparse import re as _re import os as _os import sys as _sys from contextlib import contextmanager as _contextmanager # Specialized parser to print --version output to stdout rather than stderr, # to pass distcheck class _DimensionArgumentParser(_argparse.ArgumentParser): def exit(self, status = 0, message = None): if message: file = _sys.stdout if status == 0 else _sys.stderr file.write(message) _sys.exit(status) # Change the working directory within a with statement @_contextmanager def _chdir(newwd): oldwd = _os.getcwd() try: _os.chdir(newwd) yield finally: _os.chdir(oldwd) # Parse the command line _parser = _DimensionArgumentParser( epilog = "@PACKAGE_STRING@\n" "@PACKAGE_URL@\n" "Copyright (C) 2009-2011 Tavian Barnes <@PACKAGE_BUGREPORT@>\n" "Licensed under the GNU General Public License", formatter_class = _argparse.RawDescriptionHelpFormatter, conflict_handler = "resolve", # For -h as height instead of help ) _parser.add_argument("-V", "--version", action = "version", version = "@PACKAGE_STRING@") _parser.add_argument("-w", "--width", action = "store", type = int, default = 768, help = "image width (default: %(default)s)") _parser.add_argument("-h", "--height", action = "store", type = int, default = 480, help = "image height (default: %(default)s)") _parser.add_argument("--region", action = "store", type = str, help = "subregion to render, as \"(x1, y1)->(x2, y2)\"") _parser.add_argument("-v", "--verbose", action = "store_true", help = "print more information") _parser.add_argument("-q", "--quiet", action = "store_true", help = "print less information") _parser.add_argument("--threads", action = "store", type = int, help = "the number of threads to render with") _parser.add_argument("--quality", action = "store", type = str, help = "the scene quality") _parser.add_argument("--adc-bailout", action = "store", type = str, help = "the ADC bailout (default: 1/255)") _parser.add_argument("-o", "--output", action = "store", type = str, help = "the output image file") _parser.add_argument("input", action = "store", type = str, help = "the input scene description file") # Debugging/testing options _parser.add_argument("--strict", action = "store_true", help = _argparse.SUPPRESS) _args = _parser.parse_args() # Calculate subregion if _args.region is None: _args.region_x = 0 _args.region_y = 0 _args.region_width = _args.width _args.region_height = _args.height else: _pattern = r"^\s*\(\s*(\d*)\s*,\s*(\d*)\s*\)\s*->\s*\(\s*(\d*)\s*,\s*(\d*)\s*\)\s*$" _match = _re.match(_pattern, _args.region) if _match is None: raise RuntimeError("range specified in invalid format.") _args.region_x = int(_match.group(1)) _args.region_y = int(_match.group(2)) _region_xmax = int(_match.group(3)) _region_ymax = int(_match.group(4)) _args.region_width = _region_xmax - _args.region_x _args.region_height = _region_ymax - _args.region_y if _args.region_width <= 0 or _args.region_height <= 0: raise RuntimeError("region is degenerate.") if _region_xmax >= _args.width or _region_ymax > _args.height: raise RuntimeError("region exceeds bounds of image.") # Default output is basename(input).png if _args.output is None: _noext = _os.path.splitext(_os.path.basename(_args.input))[0] _args.output = _noext + ".png" # Imports available to scripts from math import * from dimension import * # Display a progress bar def _progress_bar(str, progress): try: if not _args.quiet: print(str, end = ' ') _sys.stdout.flush() term_width = terminal_width() width = term_width - (len(str) + 1)%term_width for i in range(width): progress.wait((i + 1)/width) print('.', end = '') _sys.stdout.flush() print() _sys.stdout.flush() progress.finish() except KeyboardInterrupt: print() _sys.stdout.flush() progress.cancel() try: progress.finish() except: # Swallow the failure exception pass raise # --strict option die_on_warnings(_args.strict) # Defaults/available variables image_width = _args.width image_height = _args.height objects = [] lights = [] camera = PerspectiveCamera() default_texture = Texture(finish = Ambient(0.1) + Diffuse(0.7)) default_interior = Interior() background = Black recursion_limit = None # Execute the input script if not _args.quiet: print("Parsing scene ...") # Run with the script's dirname as the working directory _workdir = _os.path.dirname(_os.path.abspath(_args.input)) parse_timer = Timer() with open(_args.input) as _fh, _chdir(_workdir): exec(compile(_fh.read(), _args.input, "exec")) parse_timer.stop() # Make the canvas canvas = Canvas(width = _args.region_width, height = _args.region_height) canvas.optimize_PNG() # Make the scene object scene = Scene(canvas = canvas, objects = objects, lights = lights, camera = camera) scene.region_x = _args.region_x scene.region_y = _args.region_y scene.outer_width = _args.width scene.outer_height = _args.height scene.default_texture = default_texture scene.background = background if recursion_limit is not None: scene.recursion_limit = recursion_limit if _args.threads is not None: scene.nthreads = _args.threads if _args.quality is not None: scene.quality = _args.quality if _args.adc_bailout is not None: _pattern = r"^(.*)/(.*)" _match = _re.match(_pattern, _args.adc_bailout) if _match is not None: _args.adc_bailout = float(_match.group(1))/float(_match.group(2)) scene.adc_bailout = float(_args.adc_bailout) # Raytrace the scene if scene.nthreads == 1: render_message = "Rendering scene" else: render_message = "Rendering scene (using %d threads)" % scene.nthreads _progress_bar(render_message, scene.raytrace_async()) # Write the output file export_timer = Timer() _progress_bar("Writing %s" % _args.output, canvas.write_PNG_async(_args.output)) export_timer.stop() # Print execution times if _args.verbose: print() print("Parsing time: ", parse_timer) print("Bounding time: ", scene.bounding_timer) print("Rendering time: ", scene.render_timer) print("Exporting time: ", export_timer)