Category: Python

List Of Blender Properties

GOAL

Today’s goal is to list up property UI that can be used in Blender Addon.
Reference: Property Definitions

Environment

Blender 2.83(LTS)
Windows10

List of Properties

  • BoolProperty
  • BoolVectorProperty
  • CollectionProperty
  • EnumProperty
  • FloatProperty
  • FloatVectorProperty
  • IntProperty
  • IntVectorProperty
  • PointerProperty
  • StringProperty

How each property looks like

BoolProperty

bool: bpy.props.BoolProperty()

BoolVectorProperty

bool_vector: bpy.props.BoolVectorProperty()

subtypes of BoolVectorProperty

The UI style or label differs according to its subtype.

bool_vector_color: bpy.props.BoolVectorProperty(subtype='COLOR')
bool_vector_translation: bpy.props.BoolVectorProperty(subtype='TRANSLATION')
# and more...

CollectionProperty

UILayout.prop() just shows the number of CollectionProperty. (Use “template_list” to show the item list of CollectionProperty)

class PropertySetTest(bpy.types.PropertyGroup):
    test_int: bpy.props.IntProperty()
    test_string: bpy.props.StringProperty()
collection: bpy.props.CollectionProperty(type=PropertySetTest)

EnumProperty

items = [("id1", "name1", "description 1", "MESH_CUBE", 0),
         ("id2", "name2", "description 2", "MESH_TORUS", 1),
         ("id3", "mame3", "description 3", "CONE", 2),]
enum: bpy.props.EnumProperty(items=items)

FloatProperty

float: bpy.props.FloatProperty()

subtypes of FloatProperty

float_pixel: bpy.props.FloatProperty(subtype="PIXEL")
float_unsigned: bpy.props.FloatProperty(subtype="UNSIGNED")
# and more...

units of FloatProperty

float_none: bpy.props.FloatProperty(unit='NONE')
float_length: bpy.props.FloatProperty(unit='LENGTH')
# and more...

FloatVectorProperty

float_vector: bpy.props.FloatVectorProperty()

subtypes of FloatVectorProperty

units of FloatVectorProperty

IntProperty

subtypes of IntVectorProperty

IntVectorProperty

subtypes of FloatVectorProperty

PointerProperty

PointerProperty takes argument ‘type’ to set the type of pointer.

pointer_scene: bpy.props.PointerProperty(type=bpy.types.Scene)
pointer_object: bpy.props.PointerProperty(type=bpy.types.Object)
pointer_matarial: bpy.props.PointerProperty(type=bpy.types.Material)
pointer_mesh: bpy.props.PointerProperty(type=bpy.types.Mesh)
pointer_image: bpy.props.PointerProperty(type=bpy.types.Image)
pointer_brush: bpy.props.PointerProperty(type=bpy.types.Brush)
pointer_camera: bpy.props.PointerProperty(type=bpy.types.Camera)
pointer_light: bpy.props.PointerProperty(type=bpy.types.Light)

StringProperty

subtypes of FloatVectorProperty

How to Display Custom Property on Properties Editor in Blender Addon

GOAL

Today’s goal is to develop an addon and display custom property on Properties editor. For example, add “target_obj” property to bpy.types.Object for management and edit the value of “target_obj” with Properties editor.

Environment

Blender2.83(LTS)
Windows10

Method

1. Register property

The following is the code of addon just add the property “target_obj” to bpy.types.Object.

bl_info = {
        "name": "Add Target Property",
        "description": "test addon to add target_obj properties to Object",
        "version": (1, 0),
        "blender": (2, 80, 0),
        "category": "3D View",
        }
import bpy

def register():
    bpy.types.Object.target_obj = bpy.props.PointerProperty(name="TargetObj", type=bpy.types.Object)

def unregister():
    del bpy.types.Object.target_obj

if __name__ == '__main__':
    register()

At this time, the property “target_obj” is not displayed in Properties Editor.

Properties Editor
(more…)

How To Detect Object Moving In Blender Addon.

GOAL

Today’s goal is to develop addon to detect object’s property changes and call function. In my case, I created an addon to detect object moving and display the message “object <Name> moving” to the system console.

Environment

Blender 1.83(LTS)
Windows10

Method

Use handler

Use Application Handlers to call function by triggers. I used “bpy.app.handlers.depsgraph_update_post” for calling the function to check if the current object is moving in each time when the blender scene has changed.

Reference: Persistent Handler Example

import bpy
from bpy.app.handlers import persistent

@persistent
def obj_init(scene):
    # check if the bpy.context.object is moving or not

bpy.app.handlers.depsgraph_update_post.append(obj_init)

Add property to store location before move

I added property “before_loc” to compare current object location to the one before depsgraph updated. If the location changed, store new location to the property “before_loc”.

(more…)

How to Add Properties In Your Blender Addon UI

GOAL

Today’s goal is to develop 3 types of Blender addons with properties, showing properties in Panel or Properties Editor as below.

Note: Addons in this article are the simplest examples and not practical.

Environment

Blender 2.83(LTS)
Windows10

What is property?

At first, I recommend to read “Properties” in Blender manual and “Property Definitions (bpy.props)” in Blender Python API documentation. And check “Operator Property” for details about how to use properties in Addon.

Properties

The Properties shows and allows editing of many active data, including the active scene and object.

from Blender 2.93 Manual

Property has information about any type of data such as Object, Material, Scene and so on. For example, Objects have properties such as “location”, “scale”, “active_material”, “animation_data”, “bound_box” and more. (Be careful that some of them are internal or non-editable!) You can add custom properties to the existing data type or create property group in Addon.

Example of properties

1. Change Existing property

Change the value stored in Blender data with UI. For example, change the size of the object, change the color of material.

bl_info = {
        "name": "Existing Property Test",
        "description": "test addon to change existing property",
        "version": (1, 0),
        "blender": (2, 83, 0),
        "category": "3D View",
        }
import bpy

class PROPTEST_PT_my_panel(bpy.types.Panel):
    bl_label = "Prop Test Addon1"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'

    def draw(self, context):
        layout = self.layout
        col = layout.column(align=True)
        # existing property
        if "Cube" in bpy.data.objects:
            col.prop(bpy.data.objects["Cube"], "scale")
            
            active_material = bpy.data.objects["Cube"].active_material
            col.prop(active_material, "diffuse_color")
            
        if "Light" in bpy.data.lights:
            col.prop(bpy.data.lights["Light"], "type")

def register():
    bpy.utils.register_class(PROPTEST_PT_my_panel)
def unregister():
    bpy.utils.unregister_class(PROPTEST_PT_my_panel)

if __name__ == '__main__':
    register()
(more…)

Trying to fix Blender “Mirror” bug

The behavior of Mirror in blender is for some reason. So I tried to fix it by myself. I do not take any responsibility for any damage or loss if you follow this article.

Problem

I don’t know why but object menu “Mirror > X Local” calls operation of “X Global” in my blender 2.83 on windows10. Actually this problem can be solved by update and re-install blender 2.83.

# the same function is called as below
bpy.ops.transform.mirror(orient_type='GLOBAL', constraint_axis=(True, False, False), use_proportional_edit=False, proportional_edit_falloff='SMOOTH', proportional_size=1, use_proportional_connected=False, use_proportional_projected=False)

What I did to fix

I searched word “Mirror” in C:/Program Files/Blender Foundation/Blender 2.83/2.83 and find the definition of mirror menu in C:/Program Files/Blender Foundation/Blender 2.83/2.83/scripts/startup/bl_ui/space_view3d.py.

class VIEW3D_MT_mirror(Menu):
    bl_label = "Mirror"

    def draw(self, _context):
        layout = self.layout

        layout.operator("transform.mirror", text="Interactive Mirror")

        layout.separator()

        layout.operator_context = 'EXEC_REGION_WIN'

        for (space_name, space_id) in (("Global", 'GLOBAL'), ("Local", 'LOCAL')):
            for axis_index, axis_name in enumerate("XYZ"):
                props = layout.operator("transform.mirror", text=f"{axis_name!s} {space_name!s}")
                props.constraint_axis[axis_index] = True
                props.orient_type = 'GLOBAL' # the point that cause a problem!!!

            if space_id == 'GLOBAL':
                layout.separator()

So change ”props.orient_type = ‘GLOBAL’” into “props.orient_type = space_id” and save with administrative privileges.

Restart Blender and check the result.

bpy.ops.transform.mirror(orient_type='LOCAL', constraint_axis=(True, False, False), use_proportional_edit=False, proportional_edit_falloff='SMOOTH', proportional_size=1, use_proportional_connected=False, use_proportional_projected=False)

It was easy to modify blender program in python level.

How To Use QInputDialog in PySide

GOAL

Today’s goal is to create a simple dialog by using QInputDialog in PySide.

Environment

Windows 10
Python 3.8.7
PySide2 (5.15.2)

Method

Reference: QInputDialog

Create the simplest dialog

There are 4 types of dialogs, getText, getInt, getDouble and getItem. Here is an example of QInputDialog() in https://gist.github.com/s-nako/1821d3c25997ec4a868f1e5f0bd2f687.

main UI to display dialogs

getText Dialog

def show_get_text_dialog(self):
    name, ok = QInputDialog.getText(self, 'Name form', 'input name:')
    if ok:
        print("input is ", name)
(more…)

Command-line Arguments and argparse in Python

GOAL

Today’s goal is to understand how to get command-line arguments in Python and how to use argparse module.

Environment

Windows 10
Python 3.8.7

What is command-line arguments in Python?

This is an example for parsing command-line arguments without argparse. The system can get the list of command line arguments with sys.argv.

import sys

def args_test():
    args = sys.argv
    for idx, args in enumerate(args):
        print(idx, args)

args_test()

An example of input in command prompt and the result are as below. Please note that the arguments are only string type and separated by spaces.

> python\test\argparse_test.py aa 100 "cc" [item1, items]

0 python\test\argparse_test.py
1 aa
2 100
3 cc
4 [item1,
5 items]

Optional arguments and flags

The following is an example to use optional arguments in command-line. It takes an effort to parse the list sys.argv.

import sys

def print_student_info():
    # the argument template name, age, --classes class1 class2 --absence
    # example1 Sulley 12 --classes math music science
    # example2 Mike 11 --absence --classes math
    args = sys.argv
    print("{} is {} years old".format(args[1], args[2]))

    if "--absence" in args:
        print("He/She is absent today.")

    if "--classes" in args:
        classes = []
        class_start = args.index("--classes")+1
        flag_item = list(filter(lambda x: x.startswith("--"), args[class_start:]))
        if flag_item:
            class_end = args.index(flag_item[0])
            classes = args[class_start:class_end]
        else:
            classes = args[class_start:]
        print("He/She has {} classes".format(" ".join(classes)))

print_student_info()
>python\test\argparse_test.py Sulley 12 --classes math music science
Sulley is 12 years old
class_start 4
He/She has math music science classes

>python\test\argparse_test.py Mike 11 --absence --classes math
Mike is 11 years old
He/She is absent today.
He/She has math classes

What is argparse?

The argparse is the module that makes it easy to write user-friendly command-line interfaces. It is useful when you’d like to use optional command-line arguments in your python program.

How to use argparse

You can add the argument with add_argument() function. Check the Python documentation for details about parameters for add_argument() such as “nargs” or “action”.

import sys
import argparse

def print_student_info():
    parser = argparse.ArgumentParser()
    parser.add_argument("name", type=str)
    parser.add_argument("age", type=int)
    parser.add_argument("--classes", nargs='*') # for list of optional arguments
    parser.add_argument("--absence", action="store_true") # for optional arguments with no value

    args = parser.parse_args()

    print("{} is {} years old".format(args.name, args.age))
    if args.absence:
        print("He/She is absent today.")
    if args.classes:
        print("He/She has {} classes".format(" ".join(args.classes)))

print_student_info()
>python\test\argparse_test.py Mike 11 --classes math science --absence
Mike is 11 years old
He/She is absent today.
He/She has math science classes

How To Add Context Menu Into QTreeWidget

GOAL

Today’s goal is to add context menu to the QTreeWidget in PySide.

Environment

Windows 10
Python 3.8.7
PySide2 (5.15.2)

Method

Change the ContextMenuPolicy of the widget.

Use CustomContextMenu if you’d like to connect your function to the signal that is emitted when the context menu is requested by actions such as right-clicked. See Qt::ContextMenuPolicy in Qt Documentation for details about ContextMenuPolocy types.

self.tree_widget = QTreeWidget()
self.tree_widget.setContextMenuPolicy(Qt.CustomContextMenu)
self.tree_widget.customContextMenuRequested.connect(self._show_context_menu)

And define the function to connect. Please use exec_() instead of show() to display the menu. The mapToGlobal function is used to determines the position to display the menu.

    # the function to display context menu
    def _show_context_menu(self, position):
        display_action1 = QAction("Display Selection")
        display_action1.triggered.connect(self.display_selection)

        menu = QMenu(self.tree_widget)
        menu.addAction(display_action1)
        
        menu.exec_(self.tree_widget.mapToGlobal(position))

Complete Code

cotext_menu_test.py

import sys
from PySide2.QtWidgets import *
from PySide2.QtCore import Qt

class MyMainWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MyMainWindow, self).__init__(parent)
        self._generate_ui()
        self._init_tree_widget()

    def _generate_ui(self):
        main_widget = QWidget()
        main_layout = QVBoxLayout()
        main_widget.setLayout(main_layout)
        self.setCentralWidget(main_widget)
        self.tree_widget = QTreeWidget()
        self.tree_widget.setSelectionMode(QAbstractItemView.ExtendedSelection)

        #----------add context menu--------------------------------------
        self.tree_widget.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tree_widget.customContextMenuRequested.connect(self._show_context_menu)
        #----------------------------------------------------------------

        main_layout.addWidget(self.tree_widget)

    def _init_tree_widget(self):
        headers = ["header1", "header2"]
        self.tree_widget.setHeaderLabels(headers)
        tree_widget_item1 = QTreeWidgetItem(["group1"])
        tree_widget_item1.addChild(QTreeWidgetItem(["item1_1", "item1_2"]))
        self.tree_widget.addTopLevelItem(tree_widget_item1)
        tree_widget_item2 = QTreeWidgetItem(["group2"])
        self.tree_widget.addTopLevelItem(tree_widget_item2)
        tree_widget_item2.addChild(QTreeWidgetItem(["item2_1", "item2_2"]))
        tree_widget_item2.addChild(QTreeWidgetItem(["item3_1", "item3_2"]))

    # the function to display context menu
    def _show_context_menu(self, position):
        display_action1 = QAction("Display Selection")
        display_action1.triggered.connect(self.display_selection)

        menu = QMenu(self.tree_widget)
        menu.addAction(display_action1)
        menu.exec_(self.tree_widget.mapToGlobal(position))

    # the action executed when menu is clicked
    def display_selection(self):
        column = self.tree_widget.currentColumn()
        text = self.tree_widget.currentItem().text(column)
        print("right-clicked item is " + text)

def launch():
    app = QApplication.instance()
    if not app:
        app = QApplication(sys.argv)
    widget = MyMainWindow()
    widget.show()
    app.exec_()


launch()

Result

When the item “item1_2” is right-clicked, the menu appears.

Click the “Display Selection”, the message is displayed.

How Get Value With Format in Python

GOAL

Today’s goal is to check if the string follows the format and to get the value according to the format. The following is an example.

# the format is "My name is <NAME>, the phone number is <NUMBER>" (<NUMBER> is separated by 2 '-')

str1 = "My name is Nako, the phone number is 123-456-7890"
#str1 follows the format, <NAME> is "Nako" and <NUMBER> is "1234567890" in this case

str2 = "I am Nako, the phone number is 1234567890"
# str2 doesn't follow the format

Environment

Python 3.8.7

Method

Use regular expression

Create regular expression object with re.compile(pattern, flags=0). It can be used to check if the string matches the pattern or not by using Pattern.match(string) function.

# coding: utf-8
import re

str1 = "My name is Nako, the phone number is 123-456-7890"
str2 = "I am Nako, the phone number is 1234567890"

prog = re.compile("My name is [A-Za-z]+, the phone number is [0-9]+-[0-9]+-[0-9]+$")

print(prog.match(str1))
# output => <re.Match object; span=(0, 49), match='My name is Nako, the phone number is 123-456-7890>
print(prog.match(str2))
# output => None

You can get the value by grouping the regular expression using (). The value of each group can be get with Match.group([group1, …])

gourp(0) is the entire match and group(n) is the match for n-th group.

# coding: utf-8
import re

str1 = "My name is Nako, the phone number is 123-456-7890"

prog = re.compile("My name is ([A-Za-z]+), the phone number is ([0-9]+-[0-9]+-[0-9]+)$")

match = prog.match(str1)
if match:
    print(match.group(0)) # output => My name is Nako, the phone number is 123-456-7890
    print(match.group(1)) # output => Nako
    print(match.group(2)) # output => 123-456-7890
    print(match.group(2).replace('-', '')) # output => 1234567890

Postscript

$ as the end of the string

You should use $ that matches the end of the string because Pattern.match(string) finds zero or more characters at the beginning of string match.

fullmatch method

If you don’t use $, you should use Pattern.fullmatch(string) instead of Pattern.match(string).

str1 = "My name is Nako, the phone number is 123-456-7890ABCDE"
prog = re.compile("My name is ([A-Za-z]+), the phone number is ([0-9]+-[0-9]+-[0-9]+)")

print(prog.match(str1))
# output => <re.Match object; span=(0, 49), match='My name is Nako, the phone number is 123-456-7890>

print(prog.fullmatch(str1))
# output => None

pattern with length limitation

The length of the input can be limited by {} in regular expression.

str1 = "My name is Nako, the phone number is 123-456-78900000"
prog = re.compile("My name is ([A-Za-z]+), the phone number is ([0-9]{3}-[0-9]{3}-[0-9]{4})$")

print(prog.match(str1))
# output => None