def replace(obj_to_remove: MeshObject, obj_to_add: MeshObject, check_collision_with: [MeshObject] = [], 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). """ # 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()) 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) bpy.context.view_layer.update() # Check for collision between the new object and other objects in the scene for obj in check_collision_with: # for each object if obj != obj_to_add and obj_to_remove != obj: if check_bb_intersection(obj.blender_obj, obj_to_add.blender_obj): if check_intersection(obj.blender_obj, obj_to_add.blender_obj)[0]: return False return True
def run(self): """ For each object, 1- Place the object outside sampling volume 2- Until we have objects remaining and have not run out of tries, Sample a point 3- Put the top object in queue at the sampled point 4- If no collision then keep the position else reset Here we use any general sampling method supported by us """ pos_sampler_params = self.config.get_raw_dict("pos_sampler") rot_sampler_params = self.config.get_raw_dict("rot_sampler") # 2- Until we have objects remaining and have not run out of tries, Sample a point placed = [] # List of objects successfully placed max_tries = self.config.get_int( "max_iterations", 1000 ) # After this many tries we give up on current object and continue with rest cache = {} # cache to fasten collision detection for obj in bpy.context.scene.objects: # for each object if obj.type == "MESH": print("Trying to put ", obj.name) prior_location = obj.location prior_rotation = obj.rotation_euler no_collision = True for i in range(max_tries): # Try max_iter amount of times # 3- Put the top object in queue at the sampled point in space position = Utility.sample_based_on_config( pos_sampler_params) rotation = Utility.sample_based_on_config( rot_sampler_params) obj.location = position # assign it a new position obj.rotation_euler = rotation # and a rotation bpy.context.view_layer.update() # then udpate scene no_collision = True for already_placed in placed: # Now check for collisions intersection = check_bb_intersection( obj, already_placed ) # First check if bounding boxes collides if intersection: # if they do intersection, cache = check_intersection( obj, already_placed ) # then check for more refined collisions if intersection: no_collision = False break # 4- If no collision then keep the position else reset if no_collision: # if no collisions print("No collision detected, Moving forward!") placed.append(obj) break # then stop trying and keep assigned position and orientation else: # if any collisions then reset object to initial state print("collision detected, Retrying!!") obj.location = prior_location obj.rotation_euler = prior_rotation bpy.context.view_layer.update() if not no_collision: print("giving up on ", obj.name)
def check_pose_for_object(obj: bpy.types.Object, position: mathutils.Vector, rotation: mathutils.Vector, bvh_cache: dict, objects_to_check_against: list, list_of_objects_with_no_inside_check: list): """ Checks if a object placed at the given pose intersects with any object given in the list. The bvh_cache adds all current objects to the bvh tree, which increases the speed. If an object is already in the cache it is removed, before performing the check. :param obj: Object which should be checked. Type: :class:`bpy.types.Object` :param position: 3D Vector of the location of the object. Type: :class:`mathutils.Vector` :param rotation: 3D Vector of the rotation in euler angles. If this is None, the rotation is not changed \ Type: :class:`mathutils.Vector` :param bvh_cache: Dict of all the bvh trees, removes the `obj` from the cache before adding it again. \ Type: :class:`dict` :param objects_to_check_against: List of objects which the object is checked again \ Type: :class:`list` :param list_of_objects_with_no_inside_check: List of objects on which no inside check is performed. \ This check is only done for the objects in \ `objects_to_check_against`. Type: :class:`list` :return: Type: :class:`bool`, True if no collision was found, false if at least one collision was found """ # assign it a new pose obj.location = position if rotation: obj.rotation_euler = rotation bpy.context.view_layer.update() # Remove bvh cache, as object has changed if obj.name in bvh_cache: del bvh_cache[obj.name] no_collision = True # Now check for collisions for already_placed in objects_to_check_against: # First check if bounding boxes collides intersection = check_bb_intersection(obj, already_placed) # if they do if intersection: skip_inside_check = already_placed in list_of_objects_with_no_inside_check # then check for more refined collisions intersection, bvh_cache = check_intersection( obj, already_placed, bvh_cache=bvh_cache, skip_inside_check=skip_inside_check) if intersection: no_collision = False break return no_collision
def collision(first_obj, second_obj): """ Checks if two object intersect. :param first_obj: The first object for which the check is carried out. Type: blender object. :param second_obj: The second object for which the check is carried out. Type: blender Object. :return: True if objects are intersecting, if not - False. """ intersection = check_bb_intersection(first_obj, second_obj) if intersection: # check for more refined collisions intersection, cache = check_intersection(first_obj, second_obj) return intersection
def _replace_and_edit(self, obj_to_remove, obj_to_add, scale=True): """ 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: object to remove from the scene. Type: blender object. :param obj_to_add: object to put in the scene instead of obj_to_remove. Type: blender object. :param scale: Scales obj_to_add to match obj_to_remove dimensions. Type: bool. """ def _bb_ratio(bb1, bb2): """ 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. returns the ratio between each side of the bounding box. Type: a list of floats. """ def _two_points_distance(point1, point2): """ Eclidian distance between two points :param point1: Point 1 as a list of three floats. Type: list. :param point2: Point 2 as a list of three floats. Type: list. returns a float. """ return np.linalg.norm(np.array(point1) - np.array(point2)) ratio_a = _two_points_distance( bb1[0], bb1[3]) / _two_points_distance(bb2[0], bb2[3]) ratio_b = _two_points_distance( bb1[0], bb1[4]) / _two_points_distance(bb2[0], bb2[4]) ratio_c = _two_points_distance( bb1[0], bb1[1]) / _two_points_distance(bb2[0], bb2[1]) return [ratio_a, ratio_b, ratio_c] # New object takes location, rotation and rough scale of original object obj_to_add.location = obj_to_remove.location obj_to_add.rotation_euler = obj_to_remove.rotation_euler if scale: obj_to_add.scale = _bb_ratio(obj_to_remove.bound_box, obj_to_add.bound_box) # Check for collision between the new object and other objects in the scene for obj in get_all_mesh_objects(): # for each object if obj != obj_to_add and obj_to_remove != obj and obj not in self._ignore_collision_with: if check_bb_intersection(obj, obj_to_add): if check_intersection(obj, obj_to_add)[0]: return False return True
def run(self): """ Samples positions and rotations of selected object inside the sampling volume while performing mesh and bounding box collision checks in the following steps: 1. While we have objects remaining and have not run out of tries - sample a point. 2. If no collisions are found keep the point. """ # While we have objects remaining and have not run out of tries - sample a point # List of successfully placed objects placed = [] # After this many tries we give up on current object and continue with the rest max_tries = self.config.get_int("max_iterations", 1000) objects = self.config.get_list("objects_to_sample", get_all_mesh_objects()) # cache to fasten collision detection bvh_cache = {} # for every selected object for obj in objects: if obj.type == "MESH": no_collision = True # Try max_iter amount of times for i in range(max_tries): # Put the top object in queue at the sampled point in space position = self.config.get_vector3d("pos_sampler") rotation = self.config.get_vector3d("rot_sampler") # assign it a new pose obj.location = position obj.rotation_euler = rotation bpy.context.view_layer.update() # Remove bvh cache, as object has changed if obj.name in bvh_cache: del bvh_cache[obj.name] no_collision = True # Now check for collisions for already_placed in placed: # First check if bounding boxes collides intersection = check_bb_intersection( obj, already_placed) # if they do if intersection: # then check for more refined collisions intersection, bvh_cache = check_intersection( obj, already_placed, bvh_cache=bvh_cache) if intersection: no_collision = False break # If no collision then keep the position if no_collision: break placed.append(obj) if not no_collision: print("Could not place " + obj.name + " without a collision.") else: print("It took " + str(i + 1) + " tries to place " + obj.name)
def run(self): # While we have objects remaining and have not run out of tries - sample a point # List of successfully placed objects placed = [] # After this many tries we give up on current object and continue with the rest max_tries = self.config.get_int("max_iterations", 1000) objects = self.config.get_list("objects_to_sample") # cache to fasten collision detection cache = {} # for every selected object for obj in objects: if obj.type == "MESH": print("Trying to put ", obj.name) prior_location = obj.location prior_rotation = obj.rotation_euler no_collision = True # Try max_iter amount of times for i in range(max_tries): # Put the top object in queue at the sampled point in space position = self.config.get_vector3d("pos_sampler") rotation = self.config.get_vector3d("rot_sampler") # assign it a new position obj.location = position # and a rotation obj.rotation_euler = rotation # then update scene bpy.context.view_layer.update() no_collision = True # Now check for collisions for already_placed in placed: # First check if bounding boxes collides intersection = check_bb_intersection( obj, already_placed) # if they do if intersection: # then check for more refined collisions intersection, cache = check_intersection( obj, already_placed) if intersection: no_collision = False break # If no collision then keep the position, else: reset if no_collision: print("No collision detected, moving forward!") placed.append(obj) # then stop trying and keep assigned position and orientation break # if any collisions then reset object to initial state else: print("Collision detected, retrying!!") obj.location = prior_location obj.rotation_euler = prior_rotation bpy.context.view_layer.update() if not no_collision: print("Giving up on ", obj.name)