Skip to content

Instantly share code, notes, and snippets.

@tin2tin
Forked from AzureDVBB/basic_ui_documented.py
Last active April 10, 2024 03:38
Show Gist options
  • Star 39 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save tin2tin/ce4696795ad918448dfbad56668ed4d5 to your computer and use it in GitHub Desktop.
Save tin2tin/ce4696795ad918448dfbad56668ed4d5 to your computer and use it in GitHub Desktop.
A commented template for making simple UI in blender using the bpy python API
#import the bpy module to access blender API
import bpy
#WARNING: this is written and tested for blender 2.79
#blender 2.8 and newer will likely have a different python API
#create a property group, this is REALLY needed so that operators
#AND the UI can access, display and expose it to the user to change
#in here we will have all properties(variables) that is neccessary
class CustomPropertyGroup(bpy.types.PropertyGroup):
#NOTE: read documentation about 'props' to see them and their keyword arguments
#builtin float (variable)property that blender understands
float_slider: bpy.props.FloatProperty(name='float value', soft_min=0, soft_max=10)
#builtin integer (variable)property
int_slider: bpy.props.IntProperty(name='int value', soft_min=0, soft_max=10)
#builting boolean (variable)property
bool_toggle: bpy.props.BoolProperty(name='bool toggle')
#builting string (variable)property
string_field: bpy.props.StringProperty(name='string field')
#create a panel (class) by deriving from the bpy Panel, this be the UI
class CustomToolShelf(bpy.types.Panel):
#variable for determining which view this panel will be in
bl_space_type = 'VIEW_3D'
#this variable tells us where in that view it will be drawn
bl_region_type = 'UI'
#this variable is a label/name that is displayed to the user
bl_label = 'Custom Tool Shelf'
#this context variable tells when it will be displayed, edit mode, object mode etc
bl_context = 'objectmode'
#category is esentially the main UI element, the panels inside it are
#collapsible dropdown menus drawn under a category
#you can add your own name, or an existing one and it will be drawn accordingly
bl_category = 'View'
#now we define a draw method, in it we can tell what elements we want to draw
#in this new space we created, buttons, toggles etc.
def draw(self, context):
#shorten the self.layout to just layout for convenience
layout = self.layout
#add a button to it, which is called an operator, its a little tricky to do it but...
#first argument is a string with the operator name to be invoked
#in example 'bpy.ops.mesh.primitive_cube_add()' is the function we want to invoke
#so we invoke it by name 'mesh.primitive_cube_add'
#then the rest are keyword arguments based on documentation
#NOTE: for custom operations, you need to define and register an operator with
#custom name, and then call it by that custom name as we did here
layout.operator('mesh.primitive_cube_add', text = 'Add new cube')
#the custom operator that we just made will go here as a new button
layout.operator('custom.simple_op', text = 'Simple Op')
#add multiple items on the same line, like a column layout, from left to right
subrow = layout.row(align=True)
#the complex operator will be draw on the left, as a button
subrow.operator('custom.complex_op', text = 'Complex Op')
#the property will be drawn next to it on the right, as an adjustible slider thing
subrow.prop(context.scene.custom_props, 'float_slider')
#add a label to the UI
layout.label(text="v Testing layout, does nothing bellow this v")
#add a new row with multiple elements in a column
subrow = layout.row(align=True)
#add a toggle
subrow.prop(context.scene.custom_props, 'bool_toggle')
#add an int slider
subrow.prop(context.scene.custom_props, 'int_slider')
#add a custom text field in the usual layout
layout.prop(context.scene.custom_props, 'string_field')
#NOTE: for more layout things see the types.UILayout in the documentation
#in order to make a button do custom behavior we need to register and make an operator, a basic
#custom operator that does not take any property and just runs is easily made like so
class CustomSimpleOperator(bpy.types.Operator):
#the id variable by which we can invoke the operator in blender
#usually its good practice to have SOMETHING.other_thing as style so we can group
#many id's together by SOMETHING and we have less chance of overriding existing op's
bl_idname = 'custom.simple_op'
#this is the label that essentially is the text displayed on the button
bl_label = 'Simple Op'
#these are the options for the operator, this one makes it not appear
#in the search bar and only accessible by script, useful
#NOTE: it's a list of strings in {} braces, see blender documentation on types.operator
bl_options = {'INTERNAL'}
#this is needed to check if the operator can be executed/invoked
#in the current context, useful for some but not for this example
@classmethod
def poll(cls, context):
#check the context here
return context.object is not None
#this is the cream of the entire operator class, this one's the function that gets
#executed when the button is pressed
def execute(self, context):
#just do the logic here
#this is a report, it pops up in the area defined in the word
#in curly braces {} which is the first argument, second is the actual displayed text
self.report({'INFO'}, "The custom operator actually worked!")
#return value tells blender wether the operation finished sueccessfully
#needs to be in curly braces also {}
return {'FINISHED'}
#this is a more complex operator, it will take a property value and
#then use it for computation of some kind
class CustomComplexOperator(bpy.types.Operator):
#add an id to be able to access it
bl_idname = 'custom.complex_op'
#add label to show up on the button
bl_label = 'Complex Op'
#make it internal so we can't search for it
bl_options={'INTERNAL'}
#make it check if it can run in the context
@classmethod
def poll(cls, context):
#check the context here
return context.object is not None
#here we can define how the operator itself is drawn to the screne
#that means we can add toggles, sliders etc and be able to acess their
#set values in the code execution
#NOTE: this is automaticly done by default, if you have defined it
#then it will be used, this gives more control over the layout
#def draw(self, context):
#TODO: could not get it working so far, would like to make it work on tools shelf
#invoke runs before execute, it is useful to (run background tasts???)
#run code that sets up or reads values neccessary for script execution
#it is a little more involved then a simple operator
#NOTE: look at types.operator documentation for more information
#def invoke(self, context):
#execute method for... executing... this... on call(button press) (after invoke)
def execute(self, context):
#shorthand to reach properties of self
props = self.properties
#shorthand to scene
scene = context.scene
#this sends a report showing the set value of the slider
self.report({'INFO'}, "The value of the slider: " + str(scene.custom_props.float_slider))
#return value that tells blender we finished without failure
return {'FINISHED'}
#this is the addon info for when you choose to install it
#NOTE: for more information, see addon tutorial in the documentation
bl_info={
"name":"Ui test addon",
"category":"tests"
}
#this function is called on plugin loading(installing), adding class definitions into blender
#to be used, drawed and called
def register():
#register property group class
bpy.utils.register_class(CustomPropertyGroup)
#this one especially, it adds the property group class to the scene context (instantiates it)
bpy.types.Scene.custom_props = bpy.props.PointerProperty(type=CustomPropertyGroup)
#register the classes with the correct function
bpy.utils.register_class(CustomSimpleOperator)
bpy.utils.register_class(CustomComplexOperator)
bpy.utils.register_class(CustomToolShelf)
#same as register but backwards, deleting references
def unregister():
#delete the custom property pointer
#NOTE: this is different from its accessor, as that is a read/write only
#to delete this we have to delete its pointer, just like how we added it
del bpy.types.Scene.custom_props
#now we can continue to unregister classes normally
bpy.utils.unregister_class(CustomPropertyGroup)
bpy.utils.unregister_class(CustomSimpleOperator)
bpy.utils.unregister_class(CustomComplexOperator)
bpy.utils.unregister_class(CustomToolShelf)
#NOTE: during testing if this addon was installed from a file then that current version
#of that file will be copied over to the blender addons directory
#if you want to see what changes occour you HAVE TO REINSTALL from the new file for it to register
#a quick line to autorun the script from the text editor when we hit 'run script'
if __name__ == '__main__':
register()
@AzureDVBB
Copy link

In the line float_slider: bpy.props.FloatProperty(name='float value', soft_min=0, soft_max=10) any reason you use float_slider: instead of float_slider = ?

@JacRossiter
Copy link

JacRossiter commented Oct 9, 2019

This is fantastic, thanks so much! It would be handy to include an image here so that the script doesn't need to be run to see what it does, just an idea.

@3dcinetv
Copy link

3dcinetv commented Jan 6, 2020

I would love a 2.81 template. Is it possible? Pretty please?

@Slipher3D
Copy link

This is exactly what I have been searching for! Thank you so much for putting this together, it also helped me with with obtaining a better grasp of using the UI elements. Good work!

@LivelyLiz
Copy link

@sirdavid32 Works like a charm even in Blender 2.82. Really good template to learn how it works. :) To all who want to see an image: https://imgur.com/a/y5dmeGP

@3dcinetv
Copy link

Awesome! Thank you for sharing!

@clintonman
Copy link

Thank you for this. I've been searching for hours trying to find this information in an understandable format. Very nicely done.

@Neuroup
Copy link

Neuroup commented Jul 15, 2020

I have one question ? How can I add float_slider to change transperency in material for example.
principledBDSF.inputs[18].default_value = 1
what I should write to get value from float slider and put it instead of 1
Greets

@SuddenDevelopment
Copy link

kickass! I've been adding gists as I go as well, thanks for this one

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment