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)
facesproperty, 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: https://blender.stackexchange.com/questions/102597/finding-vertices-edges-faces-and-tris-using-python and the formula there is:num_tris = sum(len(f.verts) - 2 for f in bm.faces)ornum_tris = len(bm.calc_loop_triangles())– Markus von Broady May 11 '21 at 10:42