Source code for render_scene

"""Render scenes with Blender."""
import argparse
import copy
import json
import math
import random
import os
import sys
from typing import Any, Dict, List, Mapping
from render_objaverse import render_scene_3drf

INSIDE_BLENDER = True
try:
    import bpy
    from mathutils import Vector
except ImportError as e:
    print('''WARNING: Not running inside Blender.
          Some functionality may not be available.''', e)
    INSIDE_BLENDER = False
if INSIDE_BLENDER:
    try:
        import utils
    except ImportError as e:
        print(
            '''ERROR: Running render_images.py from Blender and cannot import utils.py.
            You may need to add a .pth file to the site-packages of Blender's bundled python with a command like this:
            echo $PWD >> $BLENDER/$VERSION/python/lib/python3.5/site-packages/psy3dr.pth
            Where $BLENDER is the directory where Blender is installed, and $VERSION is your Blender version (such as 2.78).''',
            e
        )
        sys.exit(1)
DIR = os.path.dirname(os.path.realpath(__file__))

# List of directions
directions = ['front', 'right', 'behind', 'left']

[docs] def parse_args(argv: str = None) -> argparse.Namespace: """Parse command-line arguments. Args: argv: Argument string to parse. Returns: Parsed arguments. """ parser = argparse.ArgumentParser() # objects settings parser.add_argument('--objects', type=str, nargs='+', default=None, help='List of object names to render. If not specified, all available objects will be used.') parser.add_argument('--render-random', action='store_true', default=False, help='If set, render scenes with random rotations and positions.') parser.add_argument('--max-images', type=int, default=1, help='Maximum number of images to render for each object pair and direction.') parser.add_argument('--distance-between-objects', type=float, default=3, help='Distance between objects (in meters) between the objects centers.') parser.add_argument('--object1-rotation', type=float, default=0, help='Rotation angle (in degrees) for the first object in the scene.') parser.add_argument('--object2-rotation', type=float, default=0, help='Rotation angle (in degrees) for the second object in the scene.') parser.add_argument('--direction', default='front', choices=['left', 'right', 'behind', 'front'], help='Relative direction of the second object with respect to the first object.') # camera settings parser.add_argument('--max-camera-configs', type=int, default=1, help='Maximum number of camera configurations to render.') parser.add_argument('--camera-tilt', type=float, default=None, help='Tilt (vertical) angle (in degrees) for the camera.') parser.add_argument('--camera-pan', type=float, default=None, help='Pan (horizontal) angle (in degrees) for the camera.') parser.add_argument('--camera-height', type=float, default=None, help='Height (in meters) of the camera in the scene.') parser.add_argument('--camera-focal-length', type=float, default=None, help='Focal length (in millimeters) of the camera lens.') # return parsed arguments return parser.parse_args(argv)
[docs] def generate_camera_configs(max_configs: int) -> List[Dict[str, Any]]: """Generate a list of camera configurations. Args: args: Parsed arguments. Returns: A list of dictionaries with camera configurations. """ camera_configs = [] for tilt in [85, 90, 95]: for pan in [40, 45, 50]: for height in [0.5, 1, 1.5]: for focal_length in [50, 60, 70]: camera_configs.append({ 'tilt': tilt, 'pan': pan, 'height': height, 'focal_length': focal_length }) random.seed(42) random.shuffle(camera_configs) return camera_configs[:max_configs]
[docs] def candidate_objects_with_rotations(obj: Mapping[str, Any]) -> List[Mapping[str, Any]]: """Generate candidate objects with rotations. Args: obj: Object to generate candidates for. Returns: List of object configurations with rotations. """ default_orientation = obj['default_orientation'] if default_orientation: ret_objs = list() for i, rotate_direction in enumerate([0, math.pi / 2, math.pi, 3 * math.pi / 2]): ret_obj = copy.deepcopy(obj) ret_obj['rotation'] = rotate_direction ret_obj['orientation'] = directions[(directions.index(default_orientation) + i) % 4] ret_objs.append(ret_obj) return ret_objs else: ret_obj = copy.deepcopy(obj) ret_obj['rotation'] = 0 ret_obj['orientation'] = None return [ret_obj]
[docs] def render_random_scenes(args: argparse.Namespace, obj_names: List[str], properties: Mapping[str, Any], camera_configs: List[Dict[str, Any]], config: Dict[str, Any], max_images: int, prefix: str) -> None: """ Renders a variety of random scenes given objects and saving the scenes to files. Args: args: Parsed arguments. obj_names: List of object names to be used in the scenes. properties: Dictionary mapping object names to their properties. camera_configs: List of camera configurations to render. config: Config file for the scenes. img_template: Template string for image file paths. scene_template: Template string for scene file paths. Returns: None """ # Generate all possible combinations of objects object_combinations = [] for i, obj_i in enumerate(obj_names): for j, obj_j in enumerate(obj_names): if i < j: obj_i_with_rotation = candidate_objects_with_rotations(properties[obj_i]) obj_j_with_rotation = candidate_objects_with_rotations(properties[obj_j]) scene_combinations = [] # Create a shuffled list of direction combinations for direction in directions: for obj_i_r in obj_i_with_rotation: for obj_j_r in obj_j_with_rotation: scene_combinations.append((obj_i_r, obj_j_r, direction)) scenes_by_directions = {} for scene in scene_combinations: if scene[2] not in scenes_by_directions: scenes_by_directions[scene[2]] = [] if len(scenes_by_directions[scene[2]]) < max_images: scenes_by_directions[scene[2]].append(scene) filtered_scenes = [] for direction in directions: for scene in scenes_by_directions[direction]: for camera_config in camera_configs: filtered_scenes.append((*scene, camera_config)) object_combinations.append((obj_i, obj_j, filtered_scenes)) # Render scenes for each combination image_count = 0 for obj_i, obj_j, scene_combinations in object_combinations: # Create a top-level directory for this object pair overall_combination_prefix = f"{obj_i}_{obj_j}" overall_combination_images = os.path.join(config['output_image_dir'], overall_combination_prefix) overall_combination_scenes = os.path.join(config['output_scene_dir'], overall_combination_prefix) if not os.path.isdir(overall_combination_images): os.makedirs(overall_combination_images) if not os.path.isdir(overall_combination_scenes): os.makedirs(overall_combination_scenes) for obj_i_r, obj_j_r, direction, camera_config in scene_combinations: # Create a subdirectory for each direction inside the main combination directory direction_dir_images = os.path.join(overall_combination_images, f"{obj_i}_{obj_j}_{direction}") direction_dir_scenes = os.path.join(overall_combination_scenes, f"{obj_i}_{obj_j}_{direction}") if not os.path.isdir(direction_dir_images): os.makedirs(direction_dir_images) if not os.path.isdir(direction_dir_scenes): os.makedirs(direction_dir_scenes) img_template_comb = os.path.join(direction_dir_images, prefix + '%06d.png') scene_template_comb = os.path.join(direction_dir_scenes, prefix + '%06d.json') img_path = img_template_comb % image_count scene_path = scene_template_comb % image_count print(f"{obj_i_r}\n{obj_j_r}\n{direction}") # Generate a random camera setting for each image if all([args.camera_tilt, args.camera_pan, args.camera_height, args.camera_focal_length]): camera_settings = { 'tilt': args.camera_tilt, 'pan': args.camera_pan, 'height': args.camera_height, 'focal_length': args.camera_focal_length } else: camera_settings = camera_config render_scene_3drf(args, config, camera_settings, properties, image_count, img_path, scene_path, (obj_i_r, obj_j_r), direction) image_count += 1
[docs] def main(args: argparse.Namespace) -> None: """ Render scenes based on the provided arguments and configurations. Args: args: Command-line arguments for rendering scenes. """ # load the config file with open(os.path.join(DIR, 'config.json'), 'r') as file: config = json.load(file) prefix = config['filename_prefix'] + '_' img_template = prefix + '%06d.png' scene_template = prefix + '%06d.json' img_template = os.path.join(config['output_image_dir'], img_template) scene_template = os.path.join(config['output_scene_dir'], scene_template) config['output_image_dir'] = os.path.abspath(os.path.join(DIR, config['output_image_dir'])) config['output_scene_dir'] = os.path.abspath(os.path.join(DIR, config['output_scene_dir'])) config['masks_dir'] = os.path.abspath(os.path.join(DIR, config['masks_dir'])) if not os.path.isdir(config['output_image_dir']): os.makedirs(config['output_image_dir']) if not os.path.isdir(config['output_scene_dir']): os.makedirs(config['output_scene_dir']) if not os.path.isdir(config['masks_dir']): os.makedirs(config['masks_dir']) with open(config['properties_json'], 'r') as f: properties = json.load(f) if args.objects is None: obj_names = list(sorted(properties.keys())) else: obj_names = args.objects if args.render_random: camera_configs = generate_camera_configs(args.max_camera_configs) render_random_scenes(args, obj_names, properties, camera_configs, config, args.max_images, prefix) else: img_path = os.path.join(config['output_image_dir'], prefix + '.png') scene_path = os.path.join(config['output_scene_dir'], prefix + '.json') directions = ["front", "right", "behind", "left"] assert len(obj_names) == 2 ground_name, figure_name = obj_names ground = properties[ground_name] figure = properties[figure_name] ground['rotation'] = math.radians(args.object1_rotation if args.object1_rotation is not None else 0) figure['rotation'] = math.radians(args.object2_rotation if args.object2_rotation is not None else 0) if ground['default_orientation']: ground['orientation'] = directions[(directions.index(properties[ground_name]['default_orientation']) + round(args.object1_rotation / 90)) % 4] else: ground['orientation'] = None if figure['default_orientation']: figure['orientation'] = directions[(directions.index(properties[figure_name]['default_orientation']) + round(args.object2_rotation / 90)) % 4] else: figure['orientation'] = None direction = args.direction camera_settings = { 'tilt': args.camera_tilt, 'pan': args.camera_pan, 'height': args.camera_height, 'focal_length': args.camera_focal_length } render_scene_3drf(args, config, camera_settings, properties, 0, img_path, scene_path, (ground, figure), direction)
if __name__ == '__main__': if INSIDE_BLENDER: # Run normally argv = utils.extract_args() args = parse_args(argv) main(args) elif '--help' in sys.argv or '-h' in sys.argv: parse_args('--help') else: print( '''ERROR: This script is intended to be run from Blender like this: blender --background --python render_scene.py -- [args] You can also run as a standalone python script to view all arguments like this: python render_scene.py --help''' )