3
$\begingroup$

For clarity, this question was closed for a while after I added the clarifying statement inside the parenthesis so I created a new question asking the same thing. This question was later opened and now there are two very similar questions.

I have a panel in which I want to display a few preset colours that a user could then click on but not change the value of (IE, I don't want the colour-picker dialogue to pop up like when clicking a FloatVectorProperty). When a color is clicked I want to call a function to do some things behind the scenes.

This image from windows paint is basically what I want:

Paint image showing colour boxes

You can use layout.template_node_socket(color=(R, G, B, A)) but as far as I know there's no way to make it clickable, neither do I think its very good UI wise (I'd much rather have a square).

So my question, is there a way to display a coloured square that's clickable (and calls a function) in the UI?

EDIT

In this gif from @pyCod3R (using a palette) its showing the colors exactly how I'd like it to be but there is a + and - sign which adds/removes indices to the list. I can't allow users to modify the list because its fixed and no items should be added or removed.

From what I can tell there isn't a way to call a function when a color inside the palette is clicked, am I wrong and there is a way to do it?

$\endgroup$
3
  • 1
    $\begingroup$ You want to use a color palette. see this answer for how. $\endgroup$
    – MrBill
    Jan 27 at 1:16
  • 1
    $\begingroup$ Does this answer your question? How do i create Palette UI object $\endgroup$
    – pyCod3R
    Jan 27 at 6:52
  • $\begingroup$ My question was poorly formulated, a palette is only a partial solution unless I'm missing how to remove the "+"/"-" signs and how to call a function when a color is clicked. $\endgroup$
    – CybranM
    Jan 27 at 17:00

3 Answers 3

6
$\begingroup$

Have a look into How do i create Palette UI object and How to get a specific color/slot from a Color Palette

Quickly combined the code of both answers to display 50 random generated colors and their rgb values directly on the panel. Notice that Alpha is not supported, PaletteColor is a float array of 3 items: https://docs.blender.org/api/current/bpy.types.PaletteColor.html#bpy.types.PaletteColor

enter image description here

Minor changes to @batFINGERs code from: How do i create Palette UI object

import bpy

class PaletteDemoPanel(bpy.types.Panel):
    """Creates a Panel in the tool panel of image editor"""
    bl_label = "Palette Demo"
    bl_idname = "MATERIAL_PT_palette"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'

    def draw(self, context):
        layout = self.layout

        ts = context.tool_settings
        palette = ts.image_paint.palette
        sel_r, sel_g, sel_b = palette.colors.active.color
        
        if ts.image_paint.palette:
            layout.template_palette(ts.image_paint, "palette", color=True)
            layout.label(text="Active Color: {:.2f} {:.2f} {:.2f}".format(
                sel_r, 
                sel_g, 
                sel_b))

def register():
    bpy.utils.register_class(PaletteDemoPanel)

def unregister():
    bpy.utils.unregister_class(PaletteDemoPanel)


if __name__ == "__main__":
    register()
    
    # Add a new pallete
    pal = bpy.data.palettes.get("CustomPalette")
    if pal is None:
        pal = bpy.data.palettes.new("CustomPalette")

        # Add a single color
        red = pal.colors.new()
        red.color = (1, 0, 0)

        # Add random colors to the palette
        from random import random
        for i in range(50):
            col = pal.colors.new()
            col.color = (random(), random(), random())

        # Make red the active/selected color
        pal.colors.active = red

    ts = bpy.context.tool_settings   
    ts.image_paint.palette = pal
$\endgroup$
4
  • $\begingroup$ This seems to be exactly what I'm looking for, thanks! $\endgroup$
    – CybranM
    Jan 27 at 13:51
  • $\begingroup$ How would I trigger a function call when a color is picked? $\endgroup$
    – CybranM
    Jan 27 at 15:49
  • $\begingroup$ Is there a way to disable the "+" and "-" signs? I cant allow the users to add more or remove colors since the list is fixed. $\endgroup$
    – CybranM
    Jan 27 at 16:53
  • $\begingroup$ @CybranM template_palette() is hard coded in C. I'll do some research and let you know. $\endgroup$
    – pyCod3R
    Jan 28 at 10:03
3
$\begingroup$

Another approach to achieve a very similar result and trigger an event is to use a series of BoolProperties (as elements of a collection) which also allows to have an active item.

The principle is the same as in my prevoius answer using operators. You can display the BoolProperty only as an icon and generate the icons for the colors on the fly by adding a new preview collection (as you normally would do for custom icons) and then assigning a pixel array of the color to each new icon added: https://docs.blender.org/api/current/bpy.types.ImagePreview.html

enter image description here

Example of a color palette using BoolProperties and assigning the selected color to the object's diffuse color property using the update function (make sure viewport shading is set to solid).

import bpy
import bpy.utils.previews
from random import uniform


class HelloWorldPanel(bpy.types.Panel):
    """Creates a Panel in the Object properties window"""
    bl_label = "Hello World Panel"
    bl_idname = "OBJECT_PT_hello"
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "object"

    def draw(self, context):
        layout = self.layout
        scn = context.scene
        
        col = layout.column(align=True)
        row = col.row(align=True)
        active_item = None
        for idx, item in enumerate(scn.color_collection, start=1):
            row.prop(item, "active", icon_value=item.icon, icon_only=True)
            if item.active == True:
                active_item = item
            if idx % 11 == 0:
                row = col.row(align=True)
            
        if active_item:
            row = layout.row()
            r, g, b, a = active_item.color
            row.label(text=f"Active item: {r:.2f} {g:.2f} {b:.2f} {a:.2f}")                


def random_rbga(alpha=1):
    return (uniform(0, 1), uniform(0, 1), uniform(0, 1), alpha)

def update_callback(self, context):
    if self.active:
        for i in self.id_data.color_collection:
            if i.name != self.name:
                i.active = False
    # set the diffuse color
    obj = context.object
    obj.active_material.diffuse_color = self.color

class ColorCollection(bpy.types.PropertyGroup):
    # name: bpy.props.StringProperty
    active: bpy.props.BoolProperty(default=False, update=update_callback)
    icon: bpy.props.IntProperty()
    color: bpy.props.FloatVectorProperty(
         name = "Color",
         subtype = "COLOR",
         default = (1.0,1.0,1.0,1.0),
         size = 4)


# We can store multiple preview collections here,
# however in this example we only store "main"
preview_collections = {}

def register():
    
    # register the classes
    bpy.utils.register_class(HelloWorldPanel)
    bpy.utils.register_class(ColorCollection)
    bpy.types.Scene.color_collection = bpy.props.CollectionProperty(type=ColorCollection)
    
    # clear the collection
    if hasattr(bpy.context.scene, "color_collection"):
        bpy.context.scene.color_collection.clear()
        
    # generate colors and icons
    pcoll = bpy.utils.previews.new()
    
    size = 32, 32
    for i in range(32):
        
        color_name = f"Color{i}"
        color = random_rbga(alpha=1)
        pixels = [*color] * size[0] * size[1]
        icon = pcoll.new(color_name) # name has to be unique!
        icon.icon_size = size
        icon.is_icon_custom = True
        icon.icon_pixels_float = pixels
        
        # add the item to the collection
        color_item = bpy.context.scene.color_collection.add()
        color_item.name = color_name
        color_item.color = color
        color_item.icon = pcoll[color_name].icon_id
        
    preview_collections["main"] = pcoll
    

def unregister():
    for pcoll in preview_collections.values():
        bpy.utils.previews.remove(pcoll)
    preview_collections.clear()
    
    bpy.utils.unregister_class(ColorCollection)
    bpy.utils.unregister_class(HelloWorldPanel)
    
    del bpy.types.Scene.color_collection


if __name__ == "__main__":
    register()
$\endgroup$
2
$\begingroup$

If you want somewhat similar without all the extra elements coming with template_palette() IE +-, a fixed list and also execute an operator when the user clicks one of the colors... you can implement some sort of dynamic operator for all colors, and display only the icon on the panel.

enter image description here

Example script using some of the default icons (If you want to use custom icons, have a look into: How to implement custom icons for my script/addon?).

import bpy

colors = {
    0: {"Color": (0.3, 0.3, 0.3, 1.0),
        "Icon": 'COLORSET_01_VEC'},
    1: {"Color": (0.5, 0.4, 0.5, 1.0),
        "Icon": 'COLORSET_02_VEC'},
    2: {"Color": (1.0, 0.4, 0.5, 1.0),
        "Icon": 'COLORSET_08_VEC'},
}

class SimpleOperator(bpy.types.Operator):
    """Tooltip"""
    bl_idname = "object.simple_operator"
    bl_label = "Simple Object Operator"
    
    color: bpy.props.FloatVectorProperty(
             name = "Color Picker",
             subtype = "COLOR",
             size = 4)

    def execute(self, context):
        context.scene.active_color = self.color
        print(self.color[0], self.color[1], self.color[2], self.color[3])
        return {'FINISHED'}


class HelloWorldPanel(bpy.types.Panel):
    """Creates a Panel in the Object properties window"""
    bl_label = "Hello World Panel"
    bl_idname = "OBJECT_PT_hello"
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "object"

    def draw(self, context):
        layout = self.layout
        
        row = layout.column_flow(columns=18)
        for k, v in colors.items(): 
            p = row.operator(SimpleOperator.bl_idname, text="", icon=v.get("Icon"))
            p.color = v.get("Color")
        
        r, g, b, a = context.scene.active_color
        layout.row().label(text=f"Active Color: {r:.2f}, {g:.2f}, {b:.2f}, {a:.2f}")

def register():
    bpy.utils.register_class(SimpleOperator)
    bpy.utils.register_class(HelloWorldPanel)
    bpy.types.Scene.active_color = bpy.props.FloatVectorProperty(
             name = "Color Picker",
             subtype = "COLOR",
             default = (1.0,1.0,1.0,1.0),
             size = 4)

def unregister():
    bpy.utils.unregister_class(HelloWorldPanel)
    bpy.utils.unregister_class(SimpleOperator)
    del bpy.Scene.active_color

if __name__ == "__main__":
    register()

UI code comes from Could I see a simple example of using a color picker in a script?

To refute claims in the comments, it works with custom icons (32x32 px) representing the colors as expected:

enter image description here

$\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.