I came up with this script as a solution.
It automatically finds similar objects, sorts them by triangles, add them to collection and rename the object and the mesh.
Edit: doesn't make new set of collection when press twice
import bpy
def get_object_with_mesh():
l = []
for obj in bpy.data.objects:
if obj.type == "MESH":
l.append(obj)
return l
def get_collection(name):
collection = bpy.data.collections.get(name, None)
if collection is None:
coll = bpy.data.collections.new(name)
bpy.context.scene.collection.children.link(coll)
return coll
return collection
def get_basename(name):
split = name.split(".")
if split[-1].isnumeric():
return get_basename(".".join(split[:-1]))
else:
split = name.split("_LOD")
if split[-1].isnumeric():
return get_basename("_LOD".join(split[:-1]))
else:
return "_LOD".join(split)
def get_similars(objects):
object_dict = {}
for obj in objects:
name = get_basename(obj.name)
object_dict.setdefault(name, [])
object_dict[name].append(obj)
return object_dict
def change_link_collection(collection, objects):
# unlinkt
for obj in objects:
if obj in bpy.context.scene.collection.objects.values():
bpy.context.scene.collection.objects.unlink(obj)
# link
if obj not in collection.objects.values():
collection.objects.link(obj)
def count_triangles(object):
object.data.calc_loop_triangles()
return len(object.data.loop_triangles)
def rename_objects_by_triangles(objects, name, reverse):
sorted_objects = sorted(objects, key= count_triangles, reverse= reverse)
sorted_objects[0].name = name
sorted_objects[0].data.name = name
for i, obj in enumerate(sorted_objects[1:], 1):
new_name = name + "_LOD" + str(i)
obj.name = new_name
obj.data.name = new_name
return sorted_objects
def parent_objects(objects):
for object in objects[1:]:
object.parent = objects[0]
object.matrix_parent_inverse = parent.matrix_world.inverted()
def append_to_collection(objects, name):
if objects[0].parent is None:
change_link_collection(self.get_collection("Meshes"), [objects[0]])
parent_collection = get_collection("LODs")
if len(objects) > 1 or objects[0].parent is not None:
child_collection = get_collection("LOD_" + name)
if child_collection in bpy.context.scene.collection.children.values():
bpy.context.scene.collection.children.unlink(child_collection)
if not (child_collection in parent_collection.children.values()):
parent_collection.children.link(child_collection)
if objects[0].parent is None:
change_link_collection(child_collection, objects[1:])
else:
change_link_collection(child_collection, objects)
def main():
object_dict = get_similars(get_object_with_mesh())
for name in object_dict:
# change the boolean value of the function "rename_objects_by_triangles" to sort:
# from low to high triangles -> False |
# from high to low triangles -> True V
objects = rename_objects_by_triangles(object_dict[name], name, False)
append_to_collection(objects, name)
parent_objects(objects)
main()
To convert this script to an Operator:
I added an EnumProperty to decide with a dropdown to sort from 'High to Low' or from 'Low to High'
import bpy
from bpy.props import EnumProperty
class OBJECT_OT_collect_similars(bpy.types.Operator):
bl_idname = 'object.collect_similars'
bl_label = 'Collect Similar Object'
bl_options = {'REGISTER'}
sort : EnumProperty(name= 'sort', items= [('LTH','Low To High', ''),('HTL','High To Low','')])
def execute(self, context):
object_dict = self.get_similars(self.get_object_with_mesh())
for name in object_dict:
objects = self.rename_objects_by_triangles(object_dict[name], name, self.sort == 'HTL')
self.parent_objects(objects)
self.append_to_collection(objects, name)
return {'FINISHED'}
def get_object_with_mesh(self):
l = []
for obj in bpy.data.objects:
if obj.type == "MESH":
l.append(obj)
return l
def get_collection(self, name):
collection = bpy.data.collections.get(name, None)
if collection is None:
coll = bpy.data.collections.new(name)
bpy.context.scene.collection.children.link(coll)
return coll
return collection
def get_basename(self, name):
split = name.split(".")
if split[-1].isnumeric():
return self.get_basename(".".join(split[:-1]))
else:
split = name.split("_LOD")
if split[-1].isnumeric():
return self.get_basename("_LOD".join(split[:-1]))
else:
return "_LOD".join(split)
def get_similars(self, objects):
object_dict = {}
for obj in objects:
name = self.get_basename(obj.name)
object_dict.setdefault(name, [])
object_dict[name].append(obj)
return object_dict
def change_link_collection(self, collection, objects):
# unlinkt
for obj in objects:
if obj in bpy.context.scene.collection.objects.values():
bpy.context.scene.collection.objects.unlink(obj)
# link
if obj not in collection.objects.values():
collection.objects.link(obj)
def count_triangles(self, object):
object.data.calc_loop_triangles()
return len(object.data.loop_triangles)
def rename_objects_by_triangles(self, objects, name, reverse):
sorted_objects = sorted(objects, key= self.count_triangles, reverse= reverse)
sorted_objects[0].name = name
sorted_objects[0].data.name = name
for i, obj in enumerate(sorted_objects[1:], 1):
new_name = name + "_LOD" + str(i)
obj.name = new_name
obj.data.name = new_name
return sorted_objects
def parent_objects(self, objects):
parent = objects[0]
for object in objects[1:]:
object.parent = parent
object.matrix_parent_inverse = parent.matrix_world.inverted()
def append_to_collection(self, objects, name):
if objects[0].parent is None:
self.change_link_collection(self.get_collection("Meshes"), [objects[0]])
parent_collection = self.get_collection("LODs")
if len(objects) > 1 or objects[0].parent is not None:
child_collection = self.get_collection("LOD_" + name)
if child_collection in bpy.context.scene.collection.children.values():
bpy.context.scene.collection.children.unlink(child_collection)
if not (child_collection in parent_collection.children.values()):
parent_collection.children.link(child_collection)
if objects[0].parent is None:
self.change_link_collection(child_collection, objects[1:])
else:
self.change_link_collection(child_collection, objects)
bpy.utils.register_class(OBJECT_OT_collect_similars)
faces
property, which you can pass tolen()
. However, if not all faces are triangles, you either need to triangulate first, or for higher performance, count vertices of each face and determine the number of triangles that face would produce. I found this question: blender.stackexchange.com/questions/102597/… and the formula there is:num_tris = sum(len(f.verts) - 2 for f in bm.faces)
ornum_tris = len(bm.calc_loop_triangles())
$\endgroup$