3
$\begingroup$

I have a collection on boolean properties I want to display in a node. BoolVectorProperty isn't really applicable because I want to be able to change the size of the array dynamically.

My issue is that when displaying a compact grid, there's no way to tell the user the index of a button. That is, I would like to override the tooltip of a UILayout.prop widget. If I set the text-kwarg to something nonempty, icon_only seems to be ignored.

My current code:

class SequencerPosition(bpy.types.PropertyGroup):
    value: bpy.props.BoolProperty(name="Value")

class TriggerSequencer(Node):
    bl_idname = 'TriggerSequencerNode'
    bl_label = 'Trigger Sequencer'
    bl_width_min = 70.0

    sequence: bpy.props.CollectionProperty(type=SequencerPosition, name="Sequence")

    def init(self, context):
        for i in range(16):
            self.sequence.add()

    def draw_buttons(self, context, layout):
        grid = layout.grid_flow(row_major=True)
        for i, pg in enumerate(self.sequence):
            grid.prop(pg, 'value', icon_only=True)

results in this node:

node UI in editor

The tooltips are rather unhelpful, just saying "Value."

enter image description here

$\endgroup$

1 Answer 1

4
$\begingroup$

Dynamically re-annotate a pointer property

enter image description here I forgot to select one

Example below. Note I've used Scene properties to illustrate the concept, as it's simpler to put together using simple panel template code and column flow to layout, than making custom nodes. Pretty sure the same concepts apply.

The Scene.foo_int property unregisters re-annotates and re registers the propertygroup class. The init call is emulated by calling the rereg update method.

import bpy
from bpy.props import BoolProperty, PointerProperty, IntProperty


class SequencerPosition(bpy.types.PropertyGroup):
    __annotations__ = {}
    pass


def rereg(self, context):
    if getattr(SequencerPosition, "is_registered", False):
        bpy.utils.unregister_class(SequencerPosition)
    SequencerPosition.__annotations__.clear()
    for i in range(self.foo_int):

        SequencerPosition.__annotations__[f"value{i}"] = BoolProperty(
            name=f"Value{i}",
            description=f"Do something with value {i}")

    bpy.utils.register_class(SequencerPosition)      
    bpy.types.Scene.foo = PointerProperty(type=SequencerPosition)  




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
        scene = context.scene
        layout.prop(scene, "foo_int")
        grid = layout.column_flow(columns=8)
        for anno in SequencerPosition.__annotations__.keys():
            grid.prop(scene.foo, anno, icon_only=True)

def register():
    bpy.utils.register_class(HelloWorldPanel)
    bpy.types.Scene.foo_int = IntProperty(min=0, max=32, update=rereg)

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


if __name__ == "__main__":
    rereg(bpy.context.scene, bpy.context)
    register()

Oh, and this won't really work if I have multiple instances of the node, right?

Yes that's a catch on the dynamic registering, however can annotate the node with a maximum number, and only display so many based on a node int property

to demonstrate:

enter image description here

I've annotated the CustomNode with a max of 32 and used the int to set the layout

def draw_buttons(self, context, layout):
    layout.label(text="Node settings")
    layout.prop(self, "my_int_prop")
    grid = layout.grid_flow(row_major=True, columns=4)
    for i in range(self.my_int_prop):
        grid.prop(self, f'value{i}', icon_only=True)
$\endgroup$
8
  • $\begingroup$ Appreciated. At least it's less complicated to do it like this than I thought, but I'm not sure if I'd rather just not have tooltips. $\endgroup$
    – ollpu
    Commented Dec 21, 2019 at 17:30
  • $\begingroup$ Oh, and this won't really work if I have multiple instances of the node, right? $\endgroup$
    – ollpu
    Commented Dec 21, 2019 at 17:34
  • $\begingroup$ Oops, yep. Instead could register propertygroup class with max number of annotations, and only display based on length int. $\endgroup$
    – batFINGER
    Commented Dec 21, 2019 at 17:51
  • $\begingroup$ I now have something like __annotations__ = {f"step{i}": bpy.props.BoolProperty(name=f"Step {i}") for i in range(MAX_STEPS)}. I still don't like the string indexing (I tried having integers as keys and Blender did a royal segfault), but this is a lot cleaner. $\endgroup$
    – ollpu
    Commented Dec 21, 2019 at 18:39
  • 1
    $\begingroup$ Would have segfaulted because the string is the name of the prop, which cannot be an integer... can learn some facts the hard way. Could link to a collection such that it displays the step annotation (ie does the string indexing in the bg) but hardly seems worth the hassle. Been a pleasure helping. $\endgroup$
    – batFINGER
    Commented Dec 21, 2019 at 19:05

You must log in to answer this question.

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