import bpy import bmesh import operator import math from mathutils import Vector from collections import defaultdict from math import pi from . import utilities_bake class op(bpy.types.Operator): bl_idname = "uv.textools_bake_organize_names" bl_label = "Match Names" bl_description = "Match high poly object names to low poly objects by their bounding boxes." bl_options = {'REGISTER', 'UNDO'} @classmethod def poll(cls, context): # Require 2 or more objects to sort if len(bpy.context.selected_objects) <= 1: return False return True def execute(self, context): sort_objects(self) return {'FINISHED'} def sort_objects(self): # Collect objects objects = [] bounds = {} for obj in bpy.context.selected_objects: if obj.type == 'MESH': objects.append(obj) bounds[obj] = get_bbox(obj) print("Objects {}x".format(len(objects))) # Get smallest side of any bounding box min_side = min(bounds[objects[0]]['size'].x, bounds[objects[0]]['size'].y, bounds[objects[0]]['size'].z) avg_side = 0 for obj in bounds: min_side = min(min_side, bounds[obj]['size'].x, bounds[obj]['size'].y, bounds[obj]['size'].z) avg_side+=bounds[obj]['size'].x avg_side+=bounds[obj]['size'].y avg_side+=bounds[obj]['size'].z avg_side/=(len(bounds)*3) # Get all Low and high poly objects objects_low = [obj for obj in objects if utilities_bake.get_object_type(obj)=='low'] objects_high = [obj for obj in objects if utilities_bake.get_object_type(obj)=='high'] if len(objects_low) == 0: self.report({'ERROR_INVALID_INPUT'}, "There are no low poly objects selected") return elif len(objects_high) == 0: self.report({'ERROR_INVALID_INPUT'}, "There are no high poly objects selected") return print("Low {}x, High {}x".format(len(objects_low),len(objects_high))) pairs_low_high = {} objects_left_high = objects_high.copy() for obj_A in objects_low: matches = {} for obj_B in objects_left_high: score = get_score(obj_A, obj_B) p = score / avg_side if p > 0 and p <= 0.65: matches[obj_B] = p else: print("Not matched: {} ".format(p)) if(len(matches) > 0): sorted_matches = sorted(matches.items(), key=operator.itemgetter(1)) for i in range(0, len(sorted_matches)): A = obj_A B = sorted_matches[i][0] p = sorted_matches[i][1] print("Check: {}% '{}' = '{}' ".format(int(p * 100.0), A.name, B.name )) # Remove from list objects_left_high.remove(sorted_matches[0][0]) pairs_low_high[obj_A] = sorted_matches[0][0] print("") # objects_unsorted = [obj for obj in objects if (obj not in pairs_low_high.values() and obj not in pairs_low_high.keys() )] bpy.ops.object.select_all(action='DESELECT') for obj_A in pairs_low_high: obj_B = pairs_low_high[obj_A] try: obj_B.name = utilities_bake.get_bake_name(obj_A)+" high" obj_A.select = True obj_B.select = True except: print("Fallo") print("Matched {}x".format(len(pairs_low_high))) def get_score(A, B): bbox_A = get_bbox(A) bbox_B = get_bbox(B) # Not colliding if not is_colliding(bbox_A, bbox_B): return -1.0 # Position delta_pos = (bbox_B['center'] - bbox_A['center']).length # Volume volume_A = bbox_A['size'].x * bbox_A['size'].y * bbox_A['size'].z volume_B = bbox_B['size'].x * bbox_B['size'].y * bbox_B['size'].z delta_vol = (max(volume_A, volume_B) - min(volume_A, volume_B))/3.0 # Longest side side_A_max = max(bbox_A['size'].x, bbox_A['size'].y, bbox_A['size'].z ) side_B_max = max(bbox_B['size'].x, bbox_B['size'].y, bbox_B['size'].z ) delta_size_max = abs(side_A_max - side_B_max) return delta_pos + delta_vol + delta_size_max def get_bbox(obj): corners = [obj.matrix_world @ Vector(corner) for corner in obj.bound_box] # Get world space Min / Max box_min = Vector((corners[0].x, corners[0].y, corners[0].z)) box_max = Vector((corners[0].x, corners[0].y, corners[0].z)) for corner in corners: # box_min.x = -8 box_min.x = min(box_min.x, corner.x) box_min.y = min(box_min.y, corner.y) box_min.z = min(box_min.z, corner.z) box_max.x = max(box_max.x, corner.x) box_max.y = max(box_max.y, corner.y) box_max.z = max(box_max.z, corner.z) return { 'min':box_min, 'max':box_max, 'size':(box_max-box_min), 'center':box_min+(box_max-box_min)/2 } def is_colliding(bbox_A, bbox_B): def is_collide_1D(A_min, A_max, B_min, B_max): # One line is inside the other length_A = A_max-A_min length_B = B_max-B_min center_A = A_min + length_A/2 center_B = B_min + length_B/2 return abs(center_A - center_B) <= (length_A+length_B)/2 collide_x = is_collide_1D(bbox_A['min'].x, bbox_A['max'].x, bbox_B['min'].x, bbox_B['max'].x) collide_y = is_collide_1D(bbox_A['min'].y, bbox_A['max'].y, bbox_B['min'].y, bbox_B['max'].y) collide_z = is_collide_1D(bbox_A['min'].z, bbox_A['max'].z, bbox_B['min'].z, bbox_B['max'].z) return collide_x and collide_y and collide_z bpy.utils.register_class(op)