Source code for blenderproc.python.object.ObjectReplacer

"""Replaces mesh objects with other meshes objects, while checking for collisions"""

import random
from typing import Callable, List, Optional

import numpy as np

from blenderproc.python.types.MeshObjectUtility import MeshObject, get_all_mesh_objects
from blenderproc.python.utility.CollisionUtility import CollisionUtility


[docs] def replace_objects(objects_to_be_replaced: List[MeshObject], objects_to_replace_with: List[MeshObject], ignore_collision_with: Optional[List[MeshObject]] = None, replace_ratio: float = 1, copy_properties: bool = True, max_tries: int = 100, relative_pose_sampler: Callable[[MeshObject], None] = None): """ Replaces mesh objects with another mesh objects and scales them accordingly, the replaced objects and the objects to replace with in following steps: 1. Randomly select a subset of objects_to_be_replaced. 2. For each of these objects, sample other objects from objects_to_replace_with and try to replace them. 3. In each try, the poses of the objects are aligned and a check for collisions with other objects is done. 4. An object is skipped if max_tries is reached. :param objects_to_be_replaced: Objects, which should be removed from the scene. :param objects_to_replace_with: Objects, which will be tried to be added to the scene. :param ignore_collision_with: Objects, which are not checked for collisions with. :param replace_ratio: Ratio of objects in the original scene, which will be replaced. :param copy_properties: Copies the custom properties of the objects_to_be_replaced to the objects_to_replace_with. :param max_tries: Maximum number of tries to replace one object. :param relative_pose_sampler: A function that randomly perturbs the pose of the object to replace with (after it has been aligned to the object to replace). """ if ignore_collision_with is None: ignore_collision_with = [] # Hide new objects from renderers until they are added for obj in objects_to_replace_with: obj.hide() check_collision_with = [] for obj in get_all_mesh_objects(): if obj not in ignore_collision_with: check_collision_with.append(obj) # amount of replacements depends on the amount of objects and the replace ratio objects_to_be_replaced = random.sample(objects_to_be_replaced, k=int(len(objects_to_be_replaced) * replace_ratio)) if len(objects_to_be_replaced) == 0: print("Warning: The amount of objects, which should be replace is zero!") # Go over all objects we should replace for current_object_to_be_replaced in objects_to_be_replaced: print(current_object_to_be_replaced.get_name()) # Do at most max_tries to replace the object with a random object from objects_to_replace_with tries = 0 while tries < max_tries: current_object_to_replace_with = np.random.choice(objects_to_replace_with) if _ObjectReplacer.replace(current_object_to_be_replaced, current_object_to_replace_with, check_collision_with, relative_pose_sampler=relative_pose_sampler): # Duplicate the added object to be able to add it again duplicate_new_object = current_object_to_replace_with.duplicate() # Copy properties to the newly duplicated object if copy_properties: for key, value in current_object_to_be_replaced.get_all_cps().items(): duplicate_new_object.set_cp(key, value) duplicate_new_object.hide(False) print('Replaced ', current_object_to_be_replaced.get_name(), ' by ', duplicate_new_object.get_name()) # Delete the original object and remove it from the list check_collision_with.remove(current_object_to_be_replaced) current_object_to_be_replaced.delete() break tries += 1 if tries == max_tries: print("Could not replace " + current_object_to_be_replaced.get_name())
[docs] class _ObjectReplacer: """ Replaces mesh objects with another mesh objects and scales them accordingly, the replaced objects and the objects to replace with, can be selected over Selectors (getter.Entity). """
[docs] @staticmethod def bb_ratio(bb1: np.ndarray, bb2: np.ndarray) -> list: """ Rough estimation of the ratios between two bounding boxes sides, not axis aligned :param bb1: bounding box 1. Type: float multi-dimensional array of 8 * 3. :param bb2: bounding box 2. Type: float multi-dimensional array of 8 * 3. :return: the ratio between each side of the bounding box. Type: a list of floats. """ ratio_a = (bb1[0, 0] - bb1[4, 0]) / (bb2[0, 0] - bb2[4, 0]) ratio_b = (bb1[0, 1] - bb1[3, 1]) / (bb2[0, 1] - bb2[3, 1]) ratio_c = (bb1[0, 2] - bb1[1, 2]) / (bb2[0, 2] - bb2[1, 2]) return [ratio_a, ratio_b, ratio_c]
[docs] @staticmethod def replace(obj_to_remove: MeshObject, obj_to_add: MeshObject, check_collision_with: Optional[List[MeshObject]] = None, scale: bool = True, relative_pose_sampler: Callable[[MeshObject], None] = None): """ Scale, translate, rotate obj_to_add to match obj_to_remove and check if there is a bounding box collision returns a boolean. :param obj_to_remove: An object to remove from the scene. :param obj_to_add: An object to put in the scene instead of obj_to_remove. :param check_collision_with: A list of objects, which are not checked for collisions with. :param scale: Scales obj_to_add to match obj_to_remove dimensions. :param relative_pose_sampler: A function that randomly perturbs the pose of the object to replace with (after it has been aligned to the object to replace). """ if check_collision_with is None: check_collision_with = [] # New object takes location, rotation and rough scale of original object obj_to_add.set_location(obj_to_remove.get_location()) obj_to_add.set_rotation_euler(obj_to_remove.get_rotation_euler()) if scale: obj_to_add.set_scale(_ObjectReplacer.bb_ratio(obj_to_remove.get_bound_box(True), obj_to_add.get_bound_box(True))) if relative_pose_sampler is not None: relative_pose_sampler(obj_to_add) # Check for collision between the new object and other objects in the scene objects_to_check_against = [obj for obj in check_collision_with if obj not in (obj_to_add, obj_to_remove)] return CollisionUtility.check_intersections(obj_to_add, None, objects_to_check_against, [])