Geometrynodes and bpy // Converting an int to a bool

Hello,
I want to create a UI element that lets me change the Inputs of a GeometryNode setup.
Despite only minor coding experience, I got along okay so far.

The GeometryNode inputs are not really that user friendly as of right now. I.E. Bools are a 0/1 Slider rather than your typical checkbox.
So since I am already putting everything in that UI panel I thought: “hey, let’s change that”…

When I add the bool directly from the geoNodes though it also displays it incorrectly (still a slider).
I managed to get an empty boolean to display the way I like - but I don’t know how I would make it influence the modifier.

A major problem to me is, that I couldn’t find satisfying documentation about “self.layout.row.prop()”.
Take i.E. “row.prop(geom_modifier, ‘[“Input_11”]’, text=“bool”)” from the code below:
text=“bool” - self explanatory
but why wouldn’t it say geom_modifier[“Input_7”] instead of splitting it into two with a comma? Because that’s how I’d change the value outside of this mysterious prop() command.
When I use prop() to call something from bpy.ops I just have to type in the one command into the parenthesis. With a variable from the modifier it becomes two.
And the row.prop(context.scene.custom_props, ‘bool_toggle’) makes even less sense to me.
‘bool_toggle’ is the name I gave it - alright. But where does […].custom_props come from? Is it an arbitrary name? Why am I getting the bool from context and not from the CustomPropertyGroup class?

Well - but If you can’t answer these questions it’s alright. I mainly need to know how to ‘convert’ my boolean into a checkbox. :slight_smile:
What’s the best tactic here?

here is the code. I marked the important part with “##############”

import bpy

###Variables###
Geo = bpy.data.objects['KBHR_Blank'] #my object with the modifier
geom_modifier = Geo.modifiers["geometrynodes"] #The Geonodes Modifier


class CustomPropertyGroup(bpy.types.PropertyGroup): #the class with the checkbox-type bool i draw
    bool_toggle: bpy.props.BoolProperty(name='bool toggle')


class MODS_PT_GEO (bpy.types.Panel):
    bl_label = "GeoNode Controller"
    bl_idname = "MODS_PT_GEO"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'

    def draw(self, context):
        layout = self.layout
        my_tool = context.scene
  
######################################################################
             
        row = layout.row()
        row.prop(geom_modifier, '["Input_7"]',  text="height") #some int slider
        row.prop(geom_modifier, '["Input_11"]',  text="bool") #the bool that is displayed as a slider
        
        row = layout.row()
        row.prop(context.scene.custom_props, 'bool_toggle') #the correct looking Bool I try to connect to geonodes
 
 ######################################################################
 
 
###registering and unregistering### 
 
def register():
    bpy.utils.register_class(CustomPropertyGroup)
    bpy.types.Scene.custom_props = bpy.props.PointerProperty(type=CustomPropertyGroup)
    bpy.utils.register_class(MODS_PT_GEO)   

 
def unregister():
    del bpy.types.Scene.custom_props 
    bpy.utils.unregister_class(CustomPropertyGroup)
    bpy.utils.unregister_class(MODS_PT_GEO)     
    
if __name__ == '__main__':
    register()

Here’s the File: https://drive.google.com/file/d/19VaXpiEFh_uXNEwusXV1v6Z_KmGayE5g/view?usp=sharing

Hi, I’ll try to answer your questions, but I’m certainly not an expert, so take my answers with a grain of salt.

  1. Here’s the documentation for UILayout.prop()
  2. The reason the parts of the property path are split up (I assume) is because if you just passed it as one block of code, e.g. prop(context.scene.custom_props.bool_toggle), you would actually only be passing the value of that property (e.g. True or False), rather than a reference to the property itself. Whereas, if you pass the main path and then a string, you can get the value using getattr(). docs here
  1. When you register your property group containing bool_toggle using:
bpy.types.Scene.custom_props = bpy.props.PointerProperty(type=CustomPropertyGroup)

Blender adds your property group as a custom property to all “scene” datablocks, which is more or less the equivalent of using this in the UI:
image
This means that you need to specify the scene that you want to edit bool_toggle on with bpy.context.scene. which returns the current scene.
In this way, if you interact with CustomPropertyGroup after registering, nothing will happen in blender without re registering the class.

To answer your main question: I think that the best way is to define an update function for bool_toggle like so:

def bool_toggle_update(self, context):
    modifier = #your modifier
    modifier["Input_11"] = int(self.bool_toggle) # here self refers to the property group

class CustomPropertyGroup(bpy.types.PropertyGroup):
    bool_toggle: bpy.props.BoolProperty(
    name='bool toggle',
    update=bool_toggle_update # set the update function
)

This will mean that when you make a change to bool_toggle, it will automatically change the modifier value to the same as bool_toggle

Unfortunately, I don’t think there’s a way to display an int value as a bool in the draw function directly.

I hope that makes sense, but feel free to say if it doesn’t :slightly_smiling_face:

1 Like

Hey,

thank you for your detailed response!

  1. Thanks for the documentation link. I swear I was on that page, but apparently, I was just blind :slight_smile:
  2. That makes a lot of sense! If I’d print(modifier[“Input_n”]) it would just return the value of the input. So it would just be the same as typing True or False there I guess. With the documentation, it’s easier to understand what to put there in the future!
  3. I didn’t know that. Up until now, I’ve actually never considered what these custom properties were for.
    Do I have to re-register that bool using code to have it keep working if the user creates a new scene? Or would Blender take care of that if I’d “install” it as an add on?
  4. Your code works gracefully! So def bool_toggle_update grabs the value of bool_toggle when called, and the update=[…] part of the BoolProperty defines what is fired whenever the checkbox is clicked, have I got that right?

Now the only thing I’d like to change is that the checkbox does not update the scene. Manipulating the sliders will always show changes immediately. But with the checkbox solution, I have to i.E. slightly move the object with the modifier to update it. Any Idea how that could be achieved?
Thanks for you very helpful tips anyways :smiley:

1 Like

Yes, exactly.

No, once you have registered the property, it is automatically added to all new data blocks of the same type. So when you make a new scene, it gets your custom property with its default values (that can be assigned when you define the property) assigned to it.

Yep, that’s right

I’m not sure why it doesn’t update the modifiers when you change that property with python (probably a because geo nodes is new), but to get around that, you can call:

your_object.update_tag()

at the end of bool_toggle_update, which will update the modifiers on that object. I just found that by searching around in the console, and seeing what worked.

also, did you find a solution to the collection being greyed out? I don’t know why that happens :face_with_raised_eyebrow:

1 Like

I found a thread where someone suggested “mesh.update()”, which works as well. There are actually other operations that don’t auto-update the mesh apparently. I think i.E. a couple of modifiers and moving an object in edit mode seems to behave differently - why? I have not found out.

No - I actually edited it out because I chose another way to do what I wanted^^ Didn’t know you still saw that.
You can change the collection via code. So it is not a protected variable or something at least.
I don’t know why I can’t edit it via the Panel… :thinking:

1 Like

Ah good, I’m glad it works!
As a side note, I’ve been subscribed to your YT channel for a while, and I just wanted to say that it has some of the best blender videos that I’ve seen in a while, especially about geometry nodes :slightly_smiling_face:

1 Like

Hey, Thanks! :smiley:
I am flattered!
Well, I am glad the videos helped you - because you certainly helped me out quite a bit.

This entire ordeal here is actually part of my next, improved building generator.
I want to make it more custom… Having a color picker to change the color of the walls, giving the building other shapes than rectangles, changing the style of the building with one drop-down menu instead of manually switching out 15 compositions in the Geonode Inputs and so on.
But I want it all collected in one place. So that’s Why I am getting into Python :smiley:

1 Like

Oh wow, that sounds great!

As it happens, i’m doing something (a bit) similar to that for my addon, that I’m porting over to use geometry nodes atm, so if you have any more questions about it, just say :slight_smile: .

One thing that I’ve learned about it is that it’s usually easier to interact with the nodes directly, rather than the geometry nodes modifier, and it would probably solve most of your earlier problems as well. By that I mean making a new node tree for each object using the building generator, and then adding the real generator as a node group like this:

The advantages of this are:

  • You can get the value you want to draw easily from your_node.inputs["input_name"].default_value, rather than having to guess the name used by the modifier for that property.
  • you can draw the node input easily with your_input.draw(), and it will look exactly as it does on the node (you won’t have to worry about things being greyed out).
  • Everything displays correctly (so bool values will look like bool values)

The only disadvantage is that you have to make a new node tree for each object, but thats quite easy with python.

Sorry if thats a bit long, but I thought it might help in the long run :slight_smile:

1 Like

My man, that addon looks great! I bet that took quite a lot of time to get everything running! You are right - I see a lot of things there I am trying to accomplish here.

Sounds reasonable. I will definitely look into that once I am more comfortable with the Blender API and the Python syntax. I’ve only ever “coded” with node-based systems (ue4 Blueprints etc.) - So while I understand theoretically what I am trying to achieve It’s quite a hassle to google every command :smiley:

Thats a generous offer. I have so many questions it’s unreal :smiley: I don’t know if I can steal you that much time ^^
I feel a lot of what I need to know is so broad it would be difficult to explain…

1 Like

Okay, I tried to put 2 pretty big questions into words.
Feel free to answer them or not. :slight_smile:

  1. Can I access the children of a collection and automatically put them into an enum/something similar?
    It would be a painless way to develop more presets for my Buildinggenerator - I would just have to add a new collection to my master collection and it would appear in the dropdown.
    But I haven’t found out how to access child collections. I can get the objects, but nothing more…
    I also don’t know if/how I even can dynamically add elements to an Enum like that.

On the other hand, I think the solution in your Addon looks even better (the part where I select the trees with the neat thumbnail) - which is probably a manual solution. That’d be alright too.
But I would really like to have a set structure for my collections. like


So I would only have to change the parent collection, and the other collections that I use to get all the building pieces - having the same names and order - should automatically adapt.

  1. This is a quite general question. Thinking in terms of having a convenient addon it would be lame if I always had to be in a specific blender project to generate a building.
    I would of course like it if one could just load the script up and generate something out of the blue in their own Project. After they made their changes they should just be left with one object and not all of the parts.

I know how to append stuff from another file. Possibly I could send people a utility blendfile with all the shaders/models and the addon.py. Then they would have to set the filepath to the .blend after installing and the script could access it.
I don’t know if that’s a good solution. I would still need to temporarily import the individual parts and collection structure and delete them after the building is done.
I don’t know if it’s possible/sensible to convert the models into a list of points and create them from zero or something crazy like that.
How would you approach this?

1 Like

I would probably say that you should have a look at learning the fundamentals of python (if you haven’t already) before getting really stuck into bpy. It might not be completely necessary, but it helps massively to be able to actually understand what everything is doing. There are some great free courses on YouTube, and if you’re already familiar with the theory side of programming, it shouldn’t take too long.

To be honest, I it would have been a great help for me when learning if I had someone who I could ask questions, so it’s no problem at all :grin: .

Yes, you can access the child collections simply with your_collection.children. Have you used the built in python console yet? Using it with autocomplete can be very useful for finding what you want (though you often have to go searching to find what you want):

To add the children to a custom enum property, you can provide a function that returns a list of the children, which you give while defining the property:

def get_enum_items(self, context):
    items = ()
    
    # add an enum item for every child collection
    for child in my_collection.children:
        enum_item = (child.name, child.name, child.name)
                    #identifier,    name,    description
        items.append(enum_item)

    return items

class CustomPropertyGroup(bpy.types.PropertyGroup):
    children_enum: bpy.props.EnumProperty(
    items=get_enum_items # set the function to get the enum items
    name='children',
)

The docs for that are here

Yeah, that would be harder to do with a dynamic enum (but possible). However, it’s probably a something for later as it involves loading custom icons, which is a bit advanced.

For your last question, this is how I would go about making the addon:

  1. I would make a blend file with all of the component parts organised with collections as you were suggesting.

  2. I would then put it in the same folder as the script file, and then zip it up. (making sure to rename the script file to __init.py__ so that it can be recognised by python). This way, you could simply install the zip (the way you normally install an addon) in blender and both the .blend and your script would be extracted to the same folder in the addons directory (the path on Windows is %USERPROFILE%\AppData\Roaming\Blender Foundation\Blender\2.93\scripts\addons\).

  3. You would then know for certain where the blend file is, and could access it using the os module.

  4. You can then append the collections, and the building node group, and put them together in the current file using an operator.

But that’s all a bit advanced for the moment, and I would first focus on getting the core of the addon working in the current blend file.

I hope that made sense (and not just in my head lol) :crossed_fingers:

Also this is probably getting a bit off topic, so if you want to message me, it would probably be better to continue there :slightly_smiling_face:

1 Like