Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Programatically specify the input group behaviour at the modifier panel #38

Closed
rsaccon opened this issue Nov 17, 2023 · 11 comments
Closed

Comments

@rsaccon
Copy link

rsaccon commented Nov 17, 2023

When directly editing nodes, there is the Group Input Panel to specify Input Default, Min, Max and more, see screenshot below. But with Geometry Script what I have found out so far, only Default can be set. Is there a way to programatically set the other fields from that panel ? For numbers it would be great to be able to specify Min and Max

Screenshot 2023-11-17 at 03 36 39

@carson-katri
Copy link
Owner

An implementation was started, but not completed here:

for s in filter(lambda x: isinstance(x, slice), args):
if (isinstance(s.start, float) or isinstance(s.start, int)) and (isinstance(s.stop, float) or isinstance(s.stop, int)):
print(f"minmax: ({s.start}, {s.stop})")
elif isinstance(s.start, str):
print(f"{s.start} = {s.stop}")

It would allow you to write something like this:

def min_max_demo(value: Int[slice(0, 1)]):
    pass

It's just a matter of completing the implementation.

@carson-katri
Copy link
Owner

carson-katri commented Nov 17, 2023

The API should probably be extended to support more options:

def test(value: Int[0, 1]): pass # min, max
def test(value: Int["My Value"]): pass # custom input name
def test(value: Int[InputOptions(min=0, max=1, tooltip="The value you want", ...)]): pass # full-featured configuration

Alternatively, wrapper types could be used. I find this is a bit more confusing to read though:

def test(value: MinMax[Int, 0, 1]): pass
def test(value: Named[Int, "My Value"]): pass
def test(value: Tooltip[MinMax[Int, 0, 1], "The value you want"]): pass

@rsaccon
Copy link
Author

rsaccon commented Nov 17, 2023

Great, thanks, so this will be coming eventually ...

@rsaccon
Copy link
Author

rsaccon commented Nov 24, 2023

I started looking at the source code, in order to try to complete the implementation. But I have a question regarding the missing parts of the implementation. I see at several places in the code base that a value gets assigned to the default_value attribute of an input node. Now I assume at those places also start and stop attributes need to be set for min and max. And the same or similar for custom input name, tooltip, etc. Does that make sense ?

@rsaccon
Copy link
Author

rsaccon commented Nov 24, 2023

Got one microstep further in understanding things. Could not find any attributes at he output sockets of the input group which suggest they serve to set min and max. These are the attributes for an example input socket I played with (as learning exercise for later actually setting the options via geometry script):

['__doc__', '__module__', '__slots__', 'bl_idname', 'bl_label', 'bl_rna', 'bl_subtype_label', 'default_value', 'description', 'display_shape', 'draw', 'draw_color', 'enabled', 'hide', 'hide_value', 'identifier', 'is_linked', 'is_multi_input', 'is_output', 'is_unavailable', 'label', 'link_limit', 'name', 'node', 'rna_type', 'show_expanded', 'type']

I can set visually in the UI things like default, bl_subtype_label and I can retrieve those those attributes in Python by walking the node tree and checking the links between the nodes. But there is nothing there for setting min and max. Anything obvious I am missing ?

@BradyAJohnston
Copy link

BradyAJohnston commented Nov 24, 2023

@rsaccon the min and max have to be set via the interface.items_tree for the node group, and not the output sockets for the Group Input.

node_group = bpy.data.node_groups['my_node']
node_group.interface.items_tree["Value"].default_value = 1
node_group.interface.items_tree["Value"].min_value = 1
node_group.interface.items_tree["Value"].max_value = 0

@rsaccon
Copy link
Author

rsaccon commented Nov 24, 2023

@BradyAJohnston That really helped. Thanks a lot.

@rsaccon
Copy link
Author

rsaccon commented Dec 1, 2023

After trying to halfway understand what is going on under the hood at Geometry Script, I managed to implement min and max input options at my fork. The API looks like this right now:

 # min
def test(value: Int[0]): pass
def test(value: Float[0.0]): pass

# min, max
def test(value: Int[0, 1]): pass
def test(value: Float[0.0, 1.0]): pass

# min, max (slice notation)
def test(value: Int[0:1]): pass
def test(value: Float[0.0:1.0]): pass

Before making a PR I want to test some more edge cases and properly handle them. And extend with an InputOption class to allow additional input options (subtype, custom-name, tooltip and hide-in-modifier).

@rsaccon
Copy link
Author

rsaccon commented Dec 3, 2023

Now I got stuck with trying to set the input subtype (such as Distance, Percentage, etc). While it is straight forward to set name, default, min, max and tooltip just by setting the proper input socket attribute, for the subtype it seems an operator needs to be called (just setting the attribute only changes the selector label but not the value). If I do it by hand in the Node UI, then this shows up in the console log:

bpy.ops.node.tree_socket_change_subtype(socket_subtype='DISTANCE')

so I try to do it programmatically in the console and it fails:

>>> bpy.ops.node.tree_socket_change_subtype(socket_subtype='DISTANCE')
...
Traceback (most recent call last):
  File "<blender_console>", line 1, in <module>
  File "/Applications/Blender36.app/Contents/Resources/3.6/scripts/modules/bpy/ops.py", line 113, in __call__
    ret = _op_call(self.idname_py(), None, kw)
RuntimeError: Operator bpy.ops.node.tree_socket_change_subtype.poll() failed, context is incorrect

next I try set the context. After taking a look at the node input options UI source code, I come up with another failed attempt:

>>> active_socket = bpy.data.node_groups['example'].inputs[0]
>>> context_override = bpy.context.copy()
>>> context_override["interface_socket"] = active_socket
>>> with bpy.context.temp_override(**context_override):
...     bpy.ops.node.tree_socket_change_subtype(socket_subtype='DISTANCE')
... 
Traceback (most recent call last):
  File "<blender_console>", line 2, in <module>
  File "/Applications/Blender36.app/Contents/Resources/3.6/scripts/modules/bpy/ops.py", line 113, in __call__
    ret = _op_call(self.idname_py(), None, kw)
RuntimeError: Operator bpy.ops.node.tree_socket_change_subtype.poll() failed, context is incorrect

So how do I set the right context ??? The second attempt failed because there is no interface_socket in bpy.context. But what context do I have to set ? Or is there a different way to set programmatically the input subtype ?

@rsaccon
Copy link
Author

rsaccon commented Dec 8, 2023

I did another attempt and I finally managed to change the input subtype (just in the python console for now), but there is still a context error:

>>> active_socket = bpy.data.node_groups['experiment'].inputs[3]
>>> for area in bpy.context.screen.areas:
...     for space in area.spaces:
...         if space.type == 'NODE_EDITOR':
...             with bpy.context.temp_override(area=area, space=space, interface_socket=active_socket):
...                 bpy.ops.node.tree_socket_change_subtype('INVOKE_DEFAULT', socket_subtype='DISTANCE')
...                 break
... 
{'FINISHED'}

Traceback (most recent call last):
  File "<blender_console>", line 7, in <module>
  File "/Applications/Blender36.app/Contents/Resources/3.6/scripts/modules/bpy/ops.py", line 111, in __call__
    ret = _op_call(self.idname_py(), C_dict, kw, C_exec, C_undo)
RuntimeError: Operator bpy.ops.node.tree_socket_change_subtype.poll() failed, context is incorrect

here is the poll source code of the tree_socket_change_subtype operator:
https://github.com/blender/blender/blob/c7fc78b81ecb8beb332d064f572d8e2ed9219717/source/blender/editors/space_node/node_edit.cc#L2501-L2513

I don't know what is causing that exception. Maybe my approach to set the context is wrong.

Anyway, I will continue now to implement this input options in my fork, for vectors as well.

@rsaccon
Copy link
Author

rsaccon commented Dec 11, 2023

My suggested solution for this issue is in this PR, so closing this.

@rsaccon rsaccon closed this as completed Dec 11, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants