3
$\begingroup$

Are there any ways or add-ons for smart multi-material object selection?

On the pictures below we have cubes colored with yellow, blue and white in different order. If we pick the big cube as active and try to select those two identical cubes with Shift+L>Material, we will never get the correct selection due to unnecessary cubes joining our group.

Is it possible to loop compare a set of materials active object has and select other objects with identical or, which is better, with most similar set of materials? Let's say 8/10 match will be fine.

enter image description here

$\endgroup$
4
  • 1
    $\begingroup$ please provide your blend file $\endgroup$
    – Karan
    Commented Jun 20, 2023 at 10:35
  • $\begingroup$ Well, I just don't get why I should, cause it's just a theoretical kind of question. For most real cases, set of materials is usually way bigger than on my picks, but here it is: drive.google.com/file/d/153CUjkVg5vkz7JSB6Fhljsutt8k51yFo/… :) $\endgroup$
    – Jeff
    Commented Jun 20, 2023 at 11:09
  • $\begingroup$ So, to rephrase, you don't want to select objects that share at least one material, you want to select objects that have exactly the same materials? $\endgroup$
    – Lauloque
    Commented Jun 20, 2023 at 19:46
  • $\begingroup$ Yes, that's it. $\endgroup$
    – Jeff
    Commented Jun 21, 2023 at 4:43

2 Answers 2

2
$\begingroup$

Without specifying the algorithm behind "smart" selection, it's hard to figure out what you mean exactly. The below code will select either all cubes or all but the one with red material, because non-red cubes all have same material sets (though the 3rd cube doesn't use the blue material):

import bpy
from bpy import context as C, data as D

the_id = "fuzzy_select_by_material"


def main(threshold):
    for ob in D.objects:
        if C.object.hide_get():
            continue
        ob.select_set(calc_similarity(C.object, ob) >= threshold)


def calc_similarity(a, b):
    """Contents of this function are arbitrary, e.g. does the order of materials
    matter? Do you want to consider to which faces a material is assigned to?"""
    set_a = set(a.data.materials)
    set_b = set(b.data.materials)
    return len(set_a.intersection(set_b)) / len(set_a.union(set_b))


# below boilerplate code


class FuzzySelectByMaterial(bpy.types.Operator):
    """Select other objects that have similar materials"""
    bl_idname = f"object.{the_id}"
    bl_label = "Fuzzy Select By Material"
    bl_options = {'REGISTER', 'UNDO'}

    similarity: bpy.props.FloatProperty(
        name = "similarity",
        min = 0.0,
        default = 0.8,
        max = 1.0,
        subtype = 'FACTOR',
        description = ("0 - select all other objects, 1 - select only objects"
            "with exactly same composition of materials")
    )

    @classmethod
    def poll(cls, context):
        return C.object  # need active object

    def execute(self, context):
        main(self.similarity)
        return {'FINISHED'}


def menu_func(self, context):
    self.layout.operator(FuzzySelectByMaterial.bl_idname,
        text=FuzzySelectByMaterial.bl_label)


def register():
    bpy.utils.register_class(FuzzySelectByMaterial)
    bpy.types.NODE_MT_view.append(menu_func)
    setattr(bpy.types.WindowManager, f"{the_id}_cls", FuzzySelectByMaterial)
    setattr(bpy.types.WindowManager, f"{the_id}_fun", menu_func)


def unregister():
    oldclass = getattr(bpy.types.WindowManager, f"{the_id}_cls")
    oldfunc = getattr(bpy.types.WindowManager, f"{the_id}_fun")
    bpy.utils.unregister_class(oldclass)
    bpy.types.NODE_MT_view.remove(oldfunc)
    delattr(bpy.types.WindowManager, f"{the_id}_cls")
    delattr(bpy.types.WindowManager, f"{the_id}_fun")


if __name__ == "__main__":
    try:
        unregister()
    except AttributeError:
        register()

To test it, enable Developer Extras option in User Preferences → Interface, run the script once, select (activate) an object, then F3 search for "Fuzzy Select By Material" operator, and after running it you can press F9 to pop up the operator settings and adjust:

Here's an example of a slightly more complex comparison function:

def calc_similarity(a, b):
    """Contents of this function are arbitrary, e.g. does the order of materials
    matter? Do you want to consider to which faces a material is assigned to?"""
    from collections import Counter
    c_a = Counter(a.data.materials[p.material_index] for p in a.data.polygons)
    c_b = Counter(b.data.materials[p.material_index] for p in b.data.polygons)
    
    c = c_a.copy()
    c.update(c_b)
    score = sum(1-abs((c_a[mat]-c_b[mat]) / c[mat]) for mat in c)
    return score / len(c)

Obviously you could optimize it and only count the active object's faces once, also avoid recalculating everything when dragging the slider, but "premature optimization…"

$\endgroup$
6
  • $\begingroup$ Looks amazing! But why may I be missing "Fuzzy Select By Material" operator in search bar? I I'm running the code, activating original (the one I'm going to search similars to) object, hit F3 and no combination of "Fuzzy"/"Fuzzy Select"/"Select By Material"/"Fuzzy Select By Material" leads me to desired operator. $\endgroup$
    – Jeff
    Commented Jun 21, 2023 at 4:47
  • $\begingroup$ @Jeff what version of Blender are you using? I tested on 3.5, but I can test on other versions. $\endgroup$ Commented Jun 21, 2023 at 9:14
  • $\begingroup$ I'm now forced to use 2.9.3 due to massive workflow files incompatibility with higher versions, so tried on it as well. $\endgroup$
    – Jeff
    Commented Jun 21, 2023 at 9:27
  • $\begingroup$ Missing on 3.5 and 3.5.1 either. $\endgroup$
    – Jeff
    Commented Jun 22, 2023 at 5:00
  • $\begingroup$ UPD: You have to set Developer Extras option active in User Preferences>Interface to see this operator. Works fine now, thanks! $\endgroup$
    – Jeff
    Commented Jun 22, 2023 at 6:02
3
$\begingroup$

enter image description here

import bpy
from bpy import context


for object in context.visible_objects:
    if object == context.object:
        continue
    
    # IF ATLEAST ONE MATERIAL MATCHES
    for material in [slot.material for slot in object.material_slots]:
        if material in [slot.material for slot in context.object.material_slots]:
            object.select_set(True)
            
    # IF EXACT MATERIALS MATCHES
    if [slot.material for slot in context.object.material_slots] == [slot.material for slot in object.material_slots]:
        object.select_set(True)
$\endgroup$
6
  • $\begingroup$ Unfortunately, not working for me at all (I added my file in the comments above). And by the way, in your example it's impossible to have wrong selection, because the second cube even doesn't have similar materials with 3 other ones, and cannot be erroneously selected by one of the desired colors. $\endgroup$
    – Jeff
    Commented Jun 20, 2023 at 11:17
  • $\begingroup$ You should tweak the answer as your need it. $\endgroup$
    – Karan
    Commented Jun 20, 2023 at 11:20
  • $\begingroup$ That's why I told you to provide your blend file. $\endgroup$
    – Karan
    Commented Jun 20, 2023 at 11:21
  • $\begingroup$ Sorry, now I did: drive.google.com/file/d/153CUjkVg5vkz7JSB6Fhljsutt8k51yFo/… $\endgroup$
    – Jeff
    Commented Jun 20, 2023 at 11:23
  • $\begingroup$ The script checks if any material of active object exist in other objects then select it. $\endgroup$
    – Karan
    Commented Jun 20, 2023 at 11:25

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .