Source code for concepts.simulator.blender.simple_render
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
# File : simple_render.py
# Author : Jiayuan Mao
# Email : maojiayuan@gmail.com
# Date : 01/25/2024
#
# This file is part of Project Concepts.
# Distributed under terms of the MIT license.
import os.path as osp
import numpy as np
from typing import Any, Optional, List, Dict
INSIDE_BLENDER = True
try:
import bpy
except ImportError as e:
print('Failed to import bpy (Blender Python API).', e)
INSIDE_BLENDER = False
BASE_DIR = osp.dirname(__file__)
def _set_rendering_engine(
use_eevee: bool = False, use_gpu: bool = False, render_timeout: Optional[float] = None
):
if use_eevee:
bpy.data.scenes["Scene"].render.engine = 'BLENDER_EEVEE'
else:
bpy.data.scenes["Scene"].render.engine = 'CYCLES'
bpy.data.scenes["Scene"].cycles.device = 'GPU' if use_gpu else 'CPU'
if render_timeout is not None:
bpy.data.scenes["Scene"].cycles.time_limit = render_timeout
def _set_camera_pos(camera_pos: Optional[List[float]] = None, camera_angle: Optional[List[float]] = None):
if camera_pos is not None:
bpy.data.objects['Camera'].location = camera_pos
if camera_angle is not None:
bpy.data.objects['Camera'].rotation_euler = camera_angle
[docs]
def render_scene_simple(
spec: List[Dict[str, Any]], scene_blend: str,
output_image: str = 'render.png', output_blendfile: Optional[str] = None,
camera_pos: Optional[List[float]] = None, camera_angle: Optional[List[float]] = None,
use_eevee: bool = False, use_gpu: bool = False, render_timeout: Optional[float] = None
):
"""Render a scene with the given specification.
Args:
spec: the specification of the scene. Each element is a dictionary with the following keys:
- filename: the path to the .obj file.
- scale: the scale of the object.
- transform: the 4x4 transformation matrix of the object.
scene_blend: the path to the scene blend file.
camera_pos: the position of the camera. If None, the default camera position will be used. Format: (x, y, z).
camera_angle: the angle of the camera. If None, the default camera angle will be used. Format: rotation_euler (x, y, z).
output_image: the path to the output image.
output_blendfile: the path to the output blend file.
use_eevee: whether to use eevee as the rendering engine. It is faster but less accurate.
use_gpu: whether to use GPU as the rendering device. This is only valid when use_eevee is False.
render_timeout: the timeout for rendering (in seconds). This is only valid when use_eevee is False.
"""
# Load the main blendfile
bpy.ops.wm.open_mainfile(filepath=scene_blend)
bpy.data.scenes["Scene"].render.filepath = output_image
_set_camera_pos(camera_pos, camera_angle)
_set_rendering_engine(use_eevee, use_gpu, render_timeout)
# Now make some random objects
for object_dict in spec:
filename = object_dict['filename']
scale = object_dict['scale']
transformation = object_dict['transform']
# Load the object from .obj file
bpy.ops.wm.obj_import(filepath=filename, global_scale=scale)
np_transformation = np.array(transformation).reshape((4, 4))
# print(np_transformation)
bpy.context.object.matrix_world = transformation
# NB(Jiayuan Mao @ 2024/01/25): for some reason, Blender does not update the location of the object.
bpy.context.object.location = np_transformation[:3, 3]
while True:
try:
bpy.ops.render.render(write_still=True)
break
except Exception as e:
print(e)
if output_blendfile is not None:
bpy.ops.wm.save_as_mainfile(filepath=output_blendfile)
[docs]
def render_scene_incremental_with_new_camera(
output_image: str = 'render.png',
camera_pos: Optional[List[float]] = None, camera_angle: Optional[List[float]] = None,
use_eevee: bool = False, use_gpu: bool = False, render_timeout: Optional[float] = None
):
bpy.data.scenes["Scene"].render.filepath = output_image
_set_camera_pos(camera_pos, camera_angle)
_set_rendering_engine(use_eevee, use_gpu, render_timeout)
while True:
try:
bpy.ops.render.render(write_still=True)
break
except Exception as e:
print(e)