0
$\begingroup$

I need to toggle between material and object color in the viewport shading, when I toggle on, the viewport shading should change to object and all the selected objects should show the same custom viewport display color value and when is toggle off the mode, it should return again to the previous state and each of the objects with their own viewport display color they had before the toggling.

What I have so far is far from working but I think the general idea is in the code. Somehow it changes the color of the object but give me some errors and I can't revert the previous viewport display color that each object had. Hopt all this makes sense.

import bpy
from bpy.types import Panel, Operator

bl_info = {
    "name": "Toggle Viewport Color",
    "author": "USERNAME",
    "version": (0, 0, 1),
    "blender": (2, 92, 0),
    "location": "View3D > N Panel > Toggle viewport color",
    "description": "", 
    "doc_url": "",
    "tracker_url": "",      
    "category": "3D View"
}

class Toggle_Viewport_Color_OT(Operator):
    """Toggle viewport color for selected objects"""
    bl_idname = "objects.toggle_viewport"
    bl_label = "Toggle viewport color"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        objs = context.selected_objects
        
        old_col = bpy.context.object.color = (1, 1, 1, 1)
        new_col = bpy.context.object.color = (0.5, 0, 0, 1)
        
        for o in sel_objs:
            for area in bpy.context.screen.areas: 
                if area.type == 'VIEW_3D':
                    for space in area.spaces: 
                        if space.type == 'VIEW_3D':
                            space.shading.color_type = 'OBJECT'            
            new_col

            
        for o in hid_objs:
            for area in bpy.context.screen.areas: 
                if area.type == 'VIEW_3D':
                    for space in area.spaces: 
                        if space.type == 'VIEW_3D':
                            space.shading.color_type = 'MATERIAL'
            old_col

        return {'FINISHED'}
    
class Toggle_Viewport_PT(Panel):          
    bl_label = "Toggle viewport"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = 'Toggle viewport'
    
    def draw (self, context):
        layout = self.layout
        
        col = layout.column()
        col.operator("objects.toggle_viewport")
        
classes = (
            Toggle_Viewport_Color_OT,
            Toggle_Viewport_PT,
)

def register():
    from bpy.utils import register_class
    for cls in classes:
        register_class(cls)
    
def unregister():
    from bpy.utils import unregister_class
    for cls in reversed(classes):
        unregister_class(cls)
        
if __name__ == "__main__":
    register()
$\endgroup$
2
  • $\begingroup$ thanks for noticing it. It shows in the N panel, and that I need to clean up once I get the main thing working. $\endgroup$ – Juan Carlos May 6 at 15:02
  • $\begingroup$ I updated the code with more clear definitions on where is located. Like in the line 9 says where you can see it, also updated the Operator and Panel names. $\endgroup$ – Juan Carlos May 6 at 15:54
1
$\begingroup$

That's not that easy as you might think and there are a few concepts involved.

enter image description here


For one, you would have to find a reliable way to store the actual object pointer or at least the name of the object and its corresponding viewport color for all selected objects. I would recommend register a CollectionProperty per scene and set its type to a prevously declared PropertyGroup to hold an object and its original color per item:

class CUSTOM_PG_objectColors(bpy.types.PropertyGroup):
    # name: bpy.props.StringProperty()
    obj_color: bpy.props.FloatVectorProperty(size=4)

bpy.types.Scene.custom_color_coll = bpy.props.CollectionProperty(
    name="Custom Object Collection",
    type=CUSTOM_PG_objectColors)

Even better would be using a PointerProperty for the actual objects, further reading:

Next, in case your custom color is already assigned to Material.diffuse_color, you would have compare the color components due to limitations of floating point values before adding the object and its color to the list:

# Get the collection
obj_coll = context.scene.custom_color_coll

# Add the name of the object and its material to the list
for obj in context.selected_objects:
    mat = obj.active_material
    
    # Compare the colors per component before adding the item to the list
    self.color_match(mat.diffuse_color, self.color)
        item = obj_coll.add()
        item.name = obj.name
        item.obj_color = mat.diffuse_color
        # ... and set your custom color
        mat.diffuse_color = self.color

Further reading:

And finally, notice that you do not need to iterate through the Screen.areas in order to set View3DShading.color_type since the operator is already part of the properties panel in 3D View. Consider adding a check if there is no material assigned to the object.

import bpy
from mathutils import Vector

class CUSTOM_PG_objectColors(bpy.types.PropertyGroup):
    # name: bpy.props.StringProperty()
    obj_color: bpy.props.FloatVectorProperty(size=4)
    
class CUSTOM_OT_displayColors(bpy.types.Operator):
    """Tooltip"""
    bl_idname = "view3d.custom_display_colors"
    bl_label = "Custom Display Colors"
    
    action : bpy.props.EnumProperty(
        items=(
            ('SET', "Set custom Display Colors", ""),
            ('RESET', "Reset Display Colors", "")))

    color : bpy.props.FloatVectorProperty(
        size=4, default=(1.0, 1.0, 0.0, 1.0))
        
    @classmethod
    def poll(cls, context):
        return len(context.selected_objects)

    def color_match(self, col1, col2, tol=0.001):
        def vector(col):
            # sanitize range (-2, 3, 5) = (0, 1, 1)
            return Vector([max(0, min(1, c)) for c in col])

        d = vector(col1) - vector(col2)
        return d.length <= tol

    def execute(self, context):
        # Get a reference to the list composition
        obj_coll = context.scene.custom_color_coll
        
        if self.action == 'SET': 
            
            # Add the name of the object and its material to the list
            for obj in context.selected_objects:
                mat = obj.active_material
 
                if not self.color_match(mat.diffuse_color, self.color):
                    item = obj_coll.add()
                    item.name = obj.name
                    item.obj_color = mat.diffuse_color
                    # ... and set your custom color
                    mat.diffuse_color = self.color

            # Set Shading to 'TEXTURE'
            context.area.spaces.active.shading.color_type = 'TEXTURE'
            
        else:
            # Iterate through the custom list and reset the color
            for item in obj_coll:
                obj = context.scene.objects.get(item.name)
                if obj:
                    obj.active_material.diffuse_color = item.obj_color
            
            # Clear the list
            obj_coll.clear()
            
            # Set Shading to 'OBJECT'
            #context.area.spaces.active.shading.color_type = 'OBJECT'
        
        return {'FINISHED'}

class CUSTOM_PT_viewportToggles(bpy.types.Panel):          
    bl_label = "Toggle viewport"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = 'Toggle viewport'
    
    def draw (self, context):
        layout = self.layout
        
        layout.operator(
            CUSTOM_OT_displayColors.bl_idname, 
            text="Set custom Viewport Colors").action = 'SET'
        layout.operator(
            CUSTOM_OT_displayColors.bl_idname, 
            text="Reset Viewport Colors").action = 'RESET'
        
        layout.prop(context.scene, "custom_display_bool")


def register():
    bpy.utils.register_class(CUSTOM_PG_objectColors)
    bpy.utils.register_class(CUSTOM_PT_viewportToggles)
    bpy.utils.register_class(CUSTOM_OT_displayColors)
    
    bpy.types.Scene.custom_color_coll = bpy.props.CollectionProperty(
        name="Custom Object Collection",
        type=CUSTOM_PG_objectColors)

def unregister():
    bpy.utils.unregister_class(CUSTOM_OT_displayColors)
    bpy.utils.unregister_class(CUSTOM_PT_viewportToggles)
    bpy.utils.unregister_class(CUSTOM_PG_objectColors)


if __name__ == "__main__":
    register()

Also a toggle usually means a BoolProperty. As the name suggests it's typically used to store a simple state rather than assigning values to other properties like the viewport color. You can trick it using its update function though:

def update_func(scene, context):
    if scene.custom_display_bool:
        bpy.ops.view3d.custom_display_colors(action='SET')
    else:
        bpy.ops.view3d.custom_display_colors(action='RESET')
...

bpy.types.Scene.custom_display_bool = bpy.props.BoolProperty(
    name="Display Toggle", update=update_func)

Related:

$\endgroup$
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.