You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Blender-TexTools/op_bake_organize_names.py

180 lines
5.7 KiB
Python

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_set_name(obj_A)+" high"
obj_A.select_set( state = True, view_layer = None)
obj_B.select_set( state = True, view_layer = None)
except:
print("Fail")
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)