2

I want to rename objects and their meshes by their triangle counts in a particular collection. The script should identify similar objects and put them in a collection then rename them based on their triangle counts in a format below:

Format:

  • LOD0: Cube

  • LOD1: Cube_LOD1

  • LOD2: Cube_LOD2

  • LOD3: Cube_LOD3

  • LOD4: Cube_LOD4

    and so on...

LOD0 should not have a suffix.

enter image description here


From:

enter image description here

TO:

enter image description here


Karan
  • 1,984
  • 5
  • 21
  • What do you mean with "similar objects" ? Do you mean the name of the objects? – Bananenbrot May 10 '21 at 21:54
  • Yes the name of the objects – Karan May 11 '21 at 00:29
  • If there is a way to count triangles of meshes in the bpy, this should be very easy. Will see about it… – TheLabCat May 11 '21 at 01:55
  • 2
    @ZargulTheWizard you can count faces easily (bmesh instances have a faces property, which you can pass to len(). 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) or num_tris = len(bm.calc_loop_triangles()) – Markus von Broady May 11 '21 at 10:42
  • @MarkusvonBroady Okay, thanks! Now I just need to look up how to manage collections (will do this one unless you’re really interested in doing it tonight)… – TheLabCat May 11 '21 at 11:06
  • could edit tri-count into prior answer. to rename obs to "LOD's". (No getting around suffix if two (eg "Cube") objects have same tri count) Related https://blender.stackexchange.com/a/221829/15543 @ZargulTheWizard IMO would be a good exercise for OP to have a go at. – batFINGER May 11 '21 at 11:15
  • @batFINGER IDK what IMO or OP is in this context. – TheLabCat May 11 '21 at 11:29
  • 1
    IDK what IDK is? – batFINGER May 11 '21 at 11:30
  • IMO - In My Opinion, OP - Original Poster (question asker), IDK - I Don't Know. UW – Markus von Broady May 11 '21 at 11:58

2 Answers2

2

import bpy
name = 'Cube'  # case sensitive
collection_name = f'{name}.collection'
i = 0
obj = bpy.data.objects.get(name)
group = []
while obj: 
    # this loop assumes you don't have a missing object
    # (e.g. Cube, Cube.001, Cube.003 will not reach to the Cube.003)
    group.append(obj)
    i += 1
    obj = bpy.data.objects.get(f'{name}.{i:03d}')

'''lines 4-12 could be replaced with a single line below, but the line below will match objects with names 'Cube.000', 'Cubes', 'Cube_copy' etc. as well as it will go through all objects on the scene, which (rarely) could be slow'''

group = [o for o in bpy.data.objects if o.name.startswith(name)]

target_collection = bpy.data.collections.get(collection_name) if not target_collection: target_collection = bpy.data.collections.new(collection_name) bpy.context.scene.collection.children.link(target_collection)

def sort_key(obj): obj.data.calc_loop_triangles() return len(obj.data.loop_triangles)

result = list(enumerate(sorted(group, key=sort_key), start=1))

for i, obj in result: obj.name = name + (f'_LOD{i}' if i<len(result) else '') for collection in obj.users_collection: collection.objects.unlink(obj) target_collection.objects.link(obj)

How to put similar named objects into collection in python?

Finding Vertices, Edges, Faces, and Tris using Python

Markus von Broady
  • 36,563
  • 3
  • 30
  • 99
1

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 == &quot;MESH&quot;:
            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(&quot;.&quot;)
    if split[-1].isnumeric():
        return self.get_basename(&quot;.&quot;.join(split[:-1]))
    else:
        split = name.split(&quot;_LOD&quot;)
        if split[-1].isnumeric():
            return self.get_basename(&quot;_LOD&quot;.join(split[:-1]))
        else:
            return &quot;_LOD&quot;.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 + &quot;_LOD&quot; + 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(&quot;Meshes&quot;), [objects[0]])
    parent_collection = self.get_collection(&quot;LODs&quot;)
    if len(objects) &gt; 1 or objects[0].parent is not None:
        child_collection = self.get_collection(&quot;LOD_&quot; + 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)

Bananenbrot
  • 601
  • 3
  • 8