Select all faces with same HEX value in edit mode

I have searched and came close in a thread by indigofarmer, however the outcome was selecting all obj (not faces) by material, name, or diffuse.
I attempted to modify the code to work in edit mode so that I can select all faces that share the same HEX value, but to no avail.
I am aiming to have python check the object in edit mode with the HEX value that the user can manually input while in text editor, and when ran, the script selects all the faces on the obj that contain that specific HEX value, regardless of material group assignments.
The reasoning: Between DAZ and Blender, some characters I make I utilize the super suit in DAZ. When exporting the scene as an obj then import into Blender, we get an obj with a lot of assigned materials. Even if the whole body is the same diffuse value, it still divides up material groups in Blender based on how DAZ assigns it’s groups for editing the suit’s shape in DAZ.
So in order to avoid having to select a face, shift G, and select by similar material (which only selects those assigned in the same assigned material group), and repeat or go down the entire material list and select that way all just to assign those areas to one single material group…I just want a script to do it instead. That alone would save a but load of man hours spent on that.
Thanks.

“Hex” is not a property of a face, but a way to represent data. I assume you mean color? If so, do you wanna select by vertex color, or material color (diffuse?), or even texture color? (not sure how to measure it, average color of the texture pixels of a face?)

If you could provide a model example it will be useful too, I reread the post and I’m still not sure what you want to accomplish.
You just want to get rid of those “suit” assigned materials created when you import the .obj?

But of course. If I cannot merely indicate what the representation of the data is on the face to the script via the value of the color in the form of a HEX value then, well yes, simply put I want to choose by color. Or more appropriately diffuse which as you know is represented in RGB, HSV, and HEX…HEX being the simplest in my opinion to indicate in terms of value. Hence why I chose HEX as being the data variable assigned to the faces that I desired to select en mass due to it’s simplicity which means the code would be simpler to work with on the user end.
No I do not mean texture color because textures are mapped across the UV and hold no value per face in the respect that I would like to utilize.
The thought is simple really. It is my scripting skill that is lacking. It took quite a while for me to figure out how to batch change all materials to toon and specific values at once and that turned out to be a simple code in the end. This is why I need assistance and perhaps others would benefit from this as well.

Well the suit part is not the issue. That was an example. I know how to work with and assign materials. However certain issues I run into is importing certain obj from DAZ such as this suit populates the materials list with quite a lengthy list due to how DAZ separates this specific item’s vert groups. It is very fine tune oriented for creative flexibility and while I like that, it poses a problem for me in blender. Many sections of the upper body are 4 materials. The lighter grey comes out too light in rendering so I wanted to adjust the grey to a medium grey. The issue is that there are so many groups assigned with that color in the upper body that I have to go one of three ways to remove all the groups and assign all of them into one group so I can then adjust only one light grey material assigned group and not 15 groups of the same exact material.
All I want is for the script to allow me to adjust the diffuse value in the script code (my preference was the HEX value) and run the script so every face of that object with the same value would be highlighted while in object mode so I can then just hit “assign” to a single material data block and have them all grouped. Then I can go and clear the long list into literally 4 materials in the list instead of 30 or whatever it adds up to (I didn’t count so it may be more).
How can I provide the model? I would gladly show you what I am working with so that you can see the long unnecessary material list.

Ok I got it. Here


is a video I took of my model and the issue.
First I show the long list, then the fact that there are multiple same materials (the first two I selected in the video are both HEX 666666). Then I show how they are assigned, and select separately via the material group select button. Then I show the same materials being selected with the shift G, by material.
There has to be an easy way to select those areas you see of the same material in edit mode despite being assigned to different material groups. You can see how long that list is and if I could get it all selected at once, I can assign all of them to one material and shrink that massive list to literally 4 materials. I know we can apply a script but it is eluding me because, well, my scripting sucks lol.

Here you go:

import bpy
import bmesh
from mathutils import Matrix, Vector

yuv = Matrix(((0.299, 0.587, 0.114),
        (-0.14713, -0.28886, 0.436),
        (0.615, -0.51499, -0.10001)))

class MESH_OT_select_similar_color(bpy.types.Operator):
    """Select faces based on diffuse color similarity"""
    bl_idname = "mesh.select_similar_color"
    bl_label = "Select Similar Color"
    bl_options = {'REGISTER', 'UNDO'}

    threshold = bpy.props.FloatProperty(name="Threshold", description="Use 0.0 to select faces with equal color only, larger values to select more dissimilarly colored faces too.", min=0.0, max=1.3271, subtype='FACTOR')
    extend = bpy.props.BoolProperty(name="Extend", description="Extend the selection")

    @classmethod
    def poll(cls, context):
        return (context.object is not None and
                context.object.type == 'MESH' and
                context.object.data.is_editmode)

    def execute(self, context):
        context.tool_settings.mesh_select_mode = (False, False, True)
        ob = context.object
        me = ob.data
        bm = bmesh.from_edit_mesh(me)
        
        mat_active = ob.active_material_index
        mat_diffuse = {i: yuv * Vector(mat.diffuse_color) for i, mat in enumerate(me.materials)}
        
        for face in bm.faces:
            sel = (mat_diffuse[mat_active] - mat_diffuse[face.material_index]).length <= self.threshold
            if not self.extend or (self.extend and sel):
                face.select_set(sel)
        bmesh.update_edit_mesh(me)

        return {'FINISHED'}

def draw_func(self, context):
    self.layout.operator(MESH_OT_select_similar_color.bl_idname, icon='COLOR')

def register():
    bpy.utils.register_class(MESH_OT_select_similar_color)
    bpy.types.MATERIAL_MT_specials.append(draw_func)


def unregister():
    bpy.utils.unregister_class(MESH_OT_select_similar_color)
    bpy.types.MATERIAL_MT_specials.remove(draw_func)


if __name__ == "__main__":
    register()


It selects based on similarity (not sure how well it works for more complex cases). It adds an entry to the material specials menu. Threshold slider and extend option in the redo panel (3d view).

That is way more complex than I had imagined. No wonder I couldn’t make it work properly!
This, Sir, is a brilliant addition. Many thanks and kudos to you because it works exactly how I had imagined it.
I am going to post a video to show it in action so that everyone can see your great work making life a little easier in the blender world.
Thanks again CoDEmanX.

Alright everyone, now here is the video showing how CoDEmanX’s script works. First showing that selecting by material group, and then by shift G, by material method both demonstrate that one area get selected. It shows where the material specials menu is and before the script is ran. Then after it is ran you can see the option in the menu. Then you can see how using it selects all groups with the same color on the entire object despite their group assignments.

Cant we get a “SOLVED” attached to this?

I’ve taken the liberty of doing so for you. In the future, just edit your original post, click on the “Go Advanced” button, and change the post’s prefix from None to [Solved].

great :slight_smile:

I played around with possible color combinations to find the max difference between colors in YUV space (1.3 something), I hope this won’t cause any issues.

I’m still a bit concerned about the property naming, it’s not really a threshold but a “dissimilarity factor”. Maybe make it range from 0-100% and call it “Tolerance”?

As you may have noticed, the operator switches to face selection mode. Is it ok? Or should it stay in current selection mode and select all verts part of a face with similar diffuse color?

I’ve taken the liberty of doing so for you. In the future, just edit your original post, click on the “Go Advanced” button, and change the post’s prefix from None to [Solved].

Thank you. I did not realize that I had the power haha. I thought it was an admin thing but now I know.

CoDEManX; For a first run on the script I am very happy with the results. I was in face mode because it is the only way I saw to select the desired material directly on the obj other than selecting from the materials list. I noticed you scripted it to switch to face selection mode. Even if I select vert selection mode, when the script is ran it switches over automatically. I have no issue with that. In fact, I think it is preferable.
As far as the tolerance option. I like that too. That is a well thought out option there. I actually forgot to record that and I will go and record that as well and post that in action later today. I have to go get my wife from the airport and make some ribs for veterans day so I will be busy but I promise I will record and post it! :smiley:

Ok, changed threshold to tolerance and made it percantage. Cleaned up and made it fully pep8-80 compliant. It’s an addon now btw.

bl_info = {
    "name": "Select Similar Color",
    "author": "CoDEmanX",
    "version": (1, 0),
    "blender": (2, 68, 0),
    "location": "Properties Editor > Material > Specials",
    "description": "Select faces based on diffuse color similarity",
    "warning": "Blender Internal materials only!",
    "wiki_url": "",
    "tracker_url": "",
    "category": "Mesh"}

# <pep8-80 compliant>

import bpy
import bmesh
from mathutils import Matrix, Vector

# YUV color space matrix
#
# Should be closer to human perception
# (and expectation) in tolerance calculation
#
# http://stackoverflow.com/a/5392276/2044940

yuv = Matrix(((0.299, 0.587, 0.114),
              (-0.14713, -0.28886, 0.436),
              (0.615, -0.51499, -0.10001)))


class MESH_OT_select_similar_color(bpy.types.Operator):
    __doc__ = bl_info['description']
    bl_idname = "mesh.select_similar_color"
    bl_label = "Select Similar Color"
    bl_options = {'REGISTER', 'UNDO'}

    threshold = bpy.props.FloatProperty(
        name="Tolerance",
        description="Use 0% to select faces with equal color only, larger "
                    "values to select more differently colored faces too.",
        min=0.0,
        max=100.0,
        subtype='PERCENTAGE'
    )

    extend = bpy.props.BoolProperty(
        name="Extend",
        description="Extend the selection"
    )

    @classmethod
    def poll(cls, context):
        return (context.object is not None and
                context.object.type == 'MESH' and
                context.object.data.is_editmode and
                len(context.object.data.materials) > 0)

    def execute(self, context):

        # Force face selection mode, as it's probably preferable
        context.tool_settings.mesh_select_mode = (False, False, True)

        ob = context.object
        me = ob.data
        bm = bmesh.from_edit_mesh(me)

        threshold = self.threshold / 75.3522

        mat_diffuse = {i: yuv * Vector(mat.diffuse_color)
                       for i, mat in enumerate(me.materials)}

        mat_active = mat_diffuse[ob.active_material_index]

        mat_select = {i: (mat_active - color).length <= threshold
                      for i, color in mat_diffuse.items()}

        for face in bm.faces:
            sel = mat_select[face.material_index]

            if not self.extend or (self.extend and sel):
                face.select_set(sel)

        bmesh.update_edit_mesh(me)

        return {'FINISHED'}


def draw_func(self, context):
    self.layout.operator(MESH_OT_select_similar_color.bl_idname, icon='COLOR')


def register():
    bpy.utils.register_class(MESH_OT_select_similar_color)
    bpy.types.MATERIAL_MT_specials.append(draw_func)


def unregister():
    bpy.utils.unregister_class(MESH_OT_select_similar_color)
    bpy.types.MATERIAL_MT_specials.remove(draw_func)


if __name__ == "__main__":
    register()


Awesome! I tested it out with the newest code. Very good work.
I recorded it (in a higher definition this time :yes:) so everyone can see the tolerance percentage in action. The first thing you will notice is that it selects everything of similar color except a certain strip that looks like the same color but has a very slight variation. This is where your great skills as a coder came in giving the option to expand that selection by tolerance. I added 0.6% and you can see it then expanded the selection. Then for the fun of it I grabbed the slider to show that the higher the percent the more is selected and that it works as advertised. Once again very good work!

Thanks for the video promotion :slight_smile:

I updated the script above to be a bit more efficient (do the color difference calculation once per material, not per face)

@carnealse

Would you be willing to share these models? I am doing some retopology add-on dev work and would love to have those models as case studies to test on. You can PM me

-Patrick

No problem CoDEmanX. You made it happen!

patmo141 no problem. They are characters in a comic I am engaged in creating. Would be happy to share.

How would you do this but for vertex colors instead?

Hi CoDEmanX,
I know that this is an old post, but would it be difficult to update it to bender 2.9 or 3.0 ? I have no idea how to code and I’m too old to learn.

Maybe this?

bl_info = {
    "name": "Select Similar Color",
    "author": "CoDEmanX, alpacini",
    "version": (2, 0),
    "blender": (3, 4, 1),
    "location": "Properties Editor > Material > Specials",
    "description": "Select faces based on diffuse color similarity",
    "warning": "Blender Internal materials only!",
    "wiki_url": "",
    "tracker_url": "",
    "category": "Mesh"}

import bpy
import bmesh
from mathutils import Matrix, Vector

# YUV color space matrix
#
# Should be closer to human perception
# (and expectation) in tolerance calculation
#
# http://stackoverflow.com/a/5392276/2044940

yuv = Matrix(((0.299, 0.587, 0.114),
              (-0.14713, -0.28886, 0.436),
              (0.615, -0.51499, -0.10001)))


class MESH_OT_select_similar_color(bpy.types.Operator):
    __doc__ = bl_info['description']
    bl_idname = "mesh.select_similar_color"
    bl_label =  "Select Similar Color"
    bl_options = {'REGISTER', 'UNDO'}

    threshold : bpy.props.FloatProperty(
        name="Tolerance",
        description="Use 0% to select faces with equal color only, larger "
                    "values to select more differently colored faces too.",
        min=0.0,
        max=100.0,
        subtype='PERCENTAGE'
    )

    extend : bpy.props.BoolProperty(
        name="Extend",
        description="Extend the selection"
    )

    @classmethod
    def poll(cls, context):
        return (context.object is not None and
                context.object.type == 'MESH' and
                context.object.data.is_editmode and
                len(context.object.data.materials) > 0)

    def execute(self, context):

        # Force face selection mode, as it's probably preferable
        context.tool_settings.mesh_select_mode = (False, False, True)

        ob = context.object
        me = ob.data
        bm = bmesh.from_edit_mesh(me)

        threshold = self.threshold / 75.3522

        mat_diffuse = {i: yuv @ Vector(Vector(mat.diffuse_color)[0:3])
                       for i, mat in enumerate(me.materials)}

        mat_active = mat_diffuse[ob.active_material_index]

        mat_select = {i: (mat_active - color).length <= threshold
                      for i, color in mat_diffuse.items()}

        for face in bm.faces:
            sel = mat_select[face.material_index]

            if not self.extend or (self.extend and sel):
                face.select_set(sel)

        bmesh.update_edit_mesh(me)

        return {'FINISHED'}


def draw_func(self, context):
    self.layout.operator(MESH_OT_select_similar_color.bl_idname, icon='COLOR')


def register():
    bpy.utils.register_class(MESH_OT_select_similar_color)
    bpy.types.MATERIAL_MT_context_menu.append(draw_func)


def unregister():
    bpy.utils.unregister_class(MESH_OT_select_similar_color)
    bpy.types.MATERIAL_MT_context_menu.remove(draw_func)


if __name__ == "__main__":
    register()

It should work with F3 search operator or from the material menu, but I didn’t manage to activate the redo panel, unless you press F9.