3

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?

Marty Fouts
  • 33,070
  • 10
  • 35
  • 79
CybranM
  • 413
  • 2
  • 13

3 Answers3

6

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

pyCod3R
  • 1,926
  • 4
  • 15
3

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()

pyCod3R
  • 1,926
  • 4
  • 15
2

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

pyCod3R
  • 1,926
  • 4
  • 15