how to make a blender addon part 2, ui layouts
in this part we are going to have the addon show up in the 3dview space, so that when a user uses the N key the addon will show in the side panel like below.
File structure
To keep our code organised we are going to add one new file call it the ui_panel.py the name does not really matter but lets make sure its something that makes sense, you don’t want to forget what file handles one yeah later when you are updating the addon and all your file names are a variation of dsfsdf.py
to make sure the addon loads the new file we have created we need to update the __init__.py file as well to look like this.
bl_info ={
"name" : "Addon Template",
"author" : "Your Name",
"description":"Making blender faster to use",
"version" : (2,1,0),
"blender" : (2,91,0),
"location" : "Right 3d View Panel -> Addon Template",
"category" : "Object"
}
import bpy
from . import ui_panel
def register():
print('//////////Addon registered//////////')
import importlib
modules = [ui_panel]
for mod in modules:
importlib.reload(mod)
ui_panel.register()
def unregister():
ui_panel.unregister()
if __name__ == "__main__":
register()
the lines we have added include:
from . import ui_panel
this tells python where the new file is located and that is the root folder or addon folder denoted with ‘.’ period key. we want the ui_panel so we add import ui_panel, this is the conventional way of importing in python
import ui_panel
if you just try writing import ui_panel that will you will get the error below because of the way blender handles running addons, you have to import it from the root folder thus adding the from . import ui_panel this is how will also import other files we create for the addon
Traceback (most recent call last):
File "C:\Program Files\Blender Foundation\Blender 3.2\3.2\scripts\modules\addon_utils.py", line 314, in enable
importlib.reload(mod)
File "C:\Program Files\Blender Foundation\Blender 3.2\3.2\python\lib\importlib\__init__.py", line 169, in reload
_bootstrap._exec(spec, module)
File "<frozen importlib._bootstrap>", line 619, in _exec
File "<frozen importlib._bootstrap_external>", line 883, in exec_module
File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
File "C:\Users\PC\AppData\Roaming\Blender Foundation\Blender\3.2\scripts\addons\Addon template\__init__.py", line 13, in <module>
import ui_panel
ModuleNotFoundError: No module named 'ui_panel'
in the modules list, modules in this context are the new python files we create like the ui_panel, and we use the importlib builtin module to make sure any changes we make to our files are reloaded when we reload the addon by deactivating it and reactivating it.
to make sure we dont have alot of lines that do the same thing say:
importlib.reload(ui_panel)
importlib.reload(functions)
importlib.reload(buttons)
the lines would be as many as the number of modules you want to reload, or new .py file you want to run, so to make our code more clean and easier to update we create a python list and run a for loop to do reload each module in the list.
modules = [ui_panel]
for mod in modules:
importlib.reload(mod)
currently we only have one module in the list which is the ui_panel but later we can add as many as we want and the for loop will handle the reloading
Adding a blender addon side panel in 3d view
ui_panel.py
import bpy
class main_panel(bpy.types.Panel):
bl_label = "Addon Template"
bl_idname = "ADDON_TP_PT_layout"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = "addon template"
def draw(self, context):
layout = self.layout
layout.label(text='This is text')
layout.box().label(text='This is text in a box')
layout.box().box().label(text='This is text in a box inside a box')
layout.box().box().box().label(text='This is text in a box inside a box')
# row layout
row = layout.row()
row.operator('mesh.primitive_cube_add')
row.operator('mesh.primitive_cube_add', text='Click to add Cube')
# bigger buttons
row = layout.row()
row.scale_y = 1.5
row.operator('mesh.primitive_cube_add', text='big 1')
row.operator('mesh.primitive_cube_add', text='big 2')
# bigger buttons
row = layout.row(align=True)
row.scale_y = 1.5
row.operator('mesh.primitive_cube_add', text='btn aligned')
row.operator('mesh.primitive_cube_add', text='btn aligned')
# column layout
col = layout.column()
col.operator('mesh.primitive_cube_add', text='column 1')
col.operator('mesh.primitive_cube_add', text='column 2')
# column layout
col = layout.column(align=True)
col.scale_y = 1.5
col.operator('mesh.primitive_cube_add', text='big 1')
col.operator('mesh.primitive_cube_add', text='big 2')
grid = layout.grid_flow(columns=3)
grid.operator('mesh.primitive_cube_add', text='btn 1')
grid.operator('mesh.primitive_cube_add', text='btn 2')
grid.operator('mesh.primitive_cube_add', text='btn 3')
grid.operator('mesh.primitive_cube_add', text='btn 4')
grid.operator('mesh.primitive_cube_add', text='btn 5')
grid = layout.grid_flow(columns=3, align=True)
grid.operator('mesh.primitive_cube_add', text='btn 1')
grid.operator('mesh.primitive_cube_add', text='btn 2')
grid.operator('mesh.primitive_cube_add', text='btn 3')
grid.operator('mesh.primitive_cube_add', text='btn 4')
grid.operator('mesh.primitive_cube_add', text='btn 5')
box = layout.box()
box.operator('mesh.primitive_cube_add', text='btn 1')
box.operator('mesh.primitive_cube_add', text='btn 2')
box.operator('mesh.primitive_cube_add', text='btn 3')
box.operator('mesh.primitive_cube_add', text='btn 4')
box.operator('mesh.primitive_cube_add', text='btn 5')
main_ui_classes = [main_panel,
]
def register():
for cls in main_ui_classes:
bpy.utils.register_class(cls)
def unregister():
for cls in main_ui_classes:
bpy.utils.unregister_class(cls)
the ui_panel gives us the panel in the 3d viewport
the first line imports the bpy module, then we create a class main_panel, you can name it what you want it does not matter, this class should inherit from bpy.types.Panel, so we have asses to methods that will enable the creation of the panel.
class main_panel(bpy.types.Panel):
bl_label = "Addon Template"
bl_idname = "ADDON_TP_PT_layout"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = "addon template"
bl_label will be the name of the side panel and as the header of the panel
BL_idname is a unique id identifier for the panel it can be any text followed by “_TP_PT_layout”, bl_space_type tells the addon in which area of blender to appear, in this case it will be the view_3d but it can also be the node_editor, image_Editor and more.
def draw(self, context):
layout = self.layout
layout.label(text='This is text')
layout.box().label(text='This is text in a box')
layout.box().box().label(text='This is text in a box inside a box')
layout.box().box().box().label(text='This is text in a box inside a box')
the draw method, lets us draw items like buttons text and more inside the addon panel, it takes in self, and context.
layout tells the addon that we want to draw a layout item and the preceding .box(), .row(), .column, .label() tells the addon what type of layout we want, wether a box, a column, a text/label or more. for the case of the above line we draw this:
# row layout
row = layout.row()
row.operator('mesh.primitive_cube_add')
row.operator('mesh.primitive_cube_add', text='Click to add Cube')
# bigger buttons
row = layout.row()
row.scale_y = 1.5
row.operator('mesh.primitive_cube_add', text='big 1')
row.operator('mesh.primitive_cube_add', text='big 2')
# bigger buttons
row = layout.row(align=True)
row.scale_y = 1.5
row.operator('mesh.primitive_cube_add', text='btn aligned')
row.operator('mesh.primitive_cube_add', text='btn aligned')
# column layout
col = layout.column()
col.operator('mesh.primitive_cube_add', text='column 1')
col.operator('mesh.primitive_cube_add', text='column 2')
# column layout
col = layout.column(align=True)
col.scale_y = 1.5
col.operator('mesh.primitive_cube_add', text='big 1')
col.operator('mesh.primitive_cube_add', text='big 2')
grid = layout.grid_flow(columns=3)
grid.operator('mesh.primitive_cube_add', text='btn 1')
grid.operator('mesh.primitive_cube_add', text='btn 2')
grid.operator('mesh.primitive_cube_add', text='btn 3')
grid.operator('mesh.primitive_cube_add', text='btn 4')
grid.operator('mesh.primitive_cube_add', text='btn 5')
grid = layout.grid_flow(columns=3, align=True)
grid.operator('mesh.primitive_cube_add', text='btn 1')
grid.operator('mesh.primitive_cube_add', text='btn 2')
grid.operator('mesh.primitive_cube_add', text='btn 3')
grid.operator('mesh.primitive_cube_add', text='btn 4')
grid.operator('mesh.primitive_cube_add', text='btn 5')
box = layout.box()
box.operator('mesh.primitive_cube_add', text='btn 1')
box.operator('mesh.primitive_cube_add', text='btn 2')
box.operator('mesh.primitive_cube_add', text='btn 3')
box.operator('mesh.primitive_cube_add', text='btn 4')
box.operator('mesh.primitive_cube_add', text='btn 5')
the row.operator(), row part tell the addon the ui type is going to be a row, operator tells the addon that this is going to be a button that does something, and that something is what ever we add in the brackets, and in this instance its to add a mesh.primitive_cube_add
row.operator('mesh.primitive_cube_add')
the above code draws :
after we have to register the class and also handle the unregistering after the addon is deactivated.
because we may want more than one panel in the addon we will also need a seperate class for each panel we create, so to prepare for that i created main_ui_classes = [main_panel, ] python list and added the first panel we have later we can add more if we need.
in the register and unregister methods we shall use a for loop to go through the main_ui_classes list to register and unregister them easily
main_ui_classes = [main_panel,
]
def register():
for cls in main_ui_classes:
bpy.utils.register_class(cls)
def unregister():
for cls in main_ui_classes:
bpy.utils.unregister_class(cls)
finally we need to call the register and unregister methods in the __init__.py file otherwise in the ui_panel we just declared them and currently don’t do anything
def register():
print('//////////Addon registered//////////')
import importlib
modules = [ui_panel]
for mod in modules:
importlib.reload(mod)
ui_panel.register()
def unregister():
ui_panel.unregister()
Download addon files at this point of the tutorial
Hi there. Very well explained. Finally, I understood the structure and basics of panel making. I am very much intrigued to learn further and looking forward to further parts like context-sensitive pie menu making, adding icons to panels and then further more things like adding custom gizmos to operators and drawing text in 3d view as a notification for the running operator etc.
thank you and am glad you learnt something i will continue to update the series