Table of Contents

Output Console in Mac

Output Console in Windows

Common Operation

object type

class MyCustomNode(bpy.types.Node):
    bl_idname = "MY_CUSTOM_NODE"
    # other properties and methods...
 
bpy.utils.register_class(MyCustomNode)

object property

ref: https://docs.blender.org/api/current/bpy.types.Object.html

mesh object property

scene data operation

Native UI integration

Blender Pipeline and Directory structure

Blender Addon development

Blender custom addon path and pipeline integration

integration into blender UI

reload blender addon

Blender addon UI reference

Launch blender in its only console mode

Blender variable naming standards

ref:

Example of class name, balancing between Python PEP

common blender api commands

Blender UI code

related reading:

simple UI tutorial:

from bpy.props import

Property:

  1. property can be inside a operator
    class OBJECT_OT_property_example(bpy.types.Operator):
        my_float: bpy.props.FloatProperty(name="length")
  2. property can be added to all object at class level
    bpy.types.Object.myProp = bpy.props.FloatProperty(default=10)
     
    # to show in addon panel
    self.layout.prop(bpy.context.object,"myProp")
  3. property can have update=changeFunc call function to call once value change
  4. some property data are not directly read, like
    # define
    bpy.types.Object.testEnum = bpy.props.EnumProperty(items=[('option1','first',''),('option2','second','')])
    # access
    for item in bpy.context.object.bl_rna.properties["testEnum"].enum_items:
        print(item.name)

UI

Blender preference

3rd party library

Blender Addon to Launch QT Window

My related dev notes

2022-08-18 complete qt UI functions and workflow design todo: template integration, density check,
note system, shader lib, unreal asset conversion
2022-08-16 get blender to qt template working
2022-08-15 get pure blender addon working
2022-08-13 get blender addon template working
2022-08-12 complete blender UI system study
2022-08-08 study blender's concept of operator, panel
2022-08-01 study blender API (pc ready)
2022-07-15 go through standard blender develop workflow

My Blender addon template

import os
 
import bpy
from bpy.types import AddonPreferences, PropertyGroup, UIList, Operator
from bpy.props import (
    StringProperty,
    IntProperty,
    CollectionProperty,
    EnumProperty,
    PointerProperty,
)
 
bl_info = {
    "name": "UniversalTool",
    "description": "A demo template tool shows assets import and publish",
    "author": "Ying",
    "version": (1, 0, 0),
    "blender": (3, 2, 0),
    "location": "3D View > Tools",
    "category": "Object",
}
 
 
# =======================================
#  preference
# =======================================
class UniversalAddonPreferences(AddonPreferences):
    bl_idname = "UniversalTool"
 
    asset_author_name: StringProperty(
        name="Asset Author Name",
        default="",
        description="Enter the name of the asset author",
    )
 
    def draw(self, context):
        layout = self.layout
        layout.prop(self, "asset_author_name")
 
 
# =======================================
#  properties
# =======================================
class VariationItem(PropertyGroup):
    name: StringProperty(name="Name")
    variation_type: StringProperty(name="Type")
 
 
class VariationProps(PropertyGroup):
    variation_type_enum_items = [
        ("choice", "Visible Choice random", ""),
        ("option", "Visible Option random", ""),
        ("stack", "Visibile Stack end random", ""),
        ("linear", "Value Linear random", ""),
        ("step", "Value Step random", ""),
        ("material", "Value Material random", ""),
    ]
    variation_type: EnumProperty(
        items=variation_type_enum_items,
        name="Variation Type",
        description="Variation Type",
        default="choice",
    )
 
 
class PublishSetting(PropertyGroup):
    publish_type_enum_items = [
        ("new_asset", "New Asset", ""),
        ("update_asset", "Asset Update", ""),
    ]
    publish_name: StringProperty(
        name="Name", description="Asset name", default="", maxlen=1024
    )
    publish_version: IntProperty(
        name="Version", description="Version number.", default=1, min=1, max=9999
    )
    publish_dir: StringProperty(
        name="Publish Dir",
        description=("Asset Publish Directory."),
        subtype="DIR_PATH",
    )
    publish_type: EnumProperty(
        items=publish_type_enum_items,
        name="Publish Type",
        description="Publish Type",
        default="new_asset",
    )
 
 
# =======================================
#  operators
# =======================================
class AddCube(bpy.types.Operator):
    bl_idname = "custom.universal_add_cube"
    bl_label = "Simple Add Cube"
    bl_options = {"REGISTER", "UNDO"}  # Enable undo for this operator.
 
    def execute(self, context):
        bpy.ops.mesh.primitive_cube_add()
        return {"FINISHED"}
 
 
# example of AddObject operator class without UNIVERSAL_OT_AddObject naming pattern
class AddObject(Operator):
    bl_idname = "custom.universal_add_object"
    bl_label = "Add Object"
 
    def execute(self, context):
        obj = bpy.context.active_object
        wm = context.window_manager
        if obj:
            item = wm.variation_list.add()
            item.name = ",".join([x.name for x in context.selected_objects])
            item.variation_type = wm.variation_props.variation_type
        return {"FINISHED"}
 
 
class UNIVERSAL_OT_RemoveObject(Operator):
    bl_idname = "custom.universal_remove_object"
    bl_label = "Remove Object"
    bl_options = {"REGISTER"}
 
    @classmethod
    def poll(cls, context):
        wm = context.window_manager
        return len(wm.variation_list) > 0
 
    def execute(self, context):
        wm = context.window_manager
        idx = wm.variation_list_index
        wm.variation_list.remove(idx)
        return {"FINISHED"}
 
 
class PublishAsset(Operator):
    bl_idname = "custom.universal_publish_asset"
    bl_label = "Publish Asset"
 
    @classmethod
    def poll(self, context):
        wm = context.window_manager
        publish_prop = wm.publish_setting_props
        return (
            os.path.isdir(publish_prop.publish_dir)
            and publish_prop.publish_name.strip() != ""
        )
 
    def execute(self, context):
        wm = context.window_manager
        publish_prop = wm.publish_setting_props
        print(publish_prop.publish_dir)
        if publish_prop.publish_type != "new_asset":
            self.report({"ERROR"}, "Publish type must be new. (demo of error)")
            return {"FINISHED"}
        self.report({"INFO"}, "Publish successfully")
        return {"FINISHED"}
 
 
# =======================================
#  UI List
# =======================================
class UNIVERSAL_UL_VariationList(UIList):
    def draw_item(
        self, context, layout, data, item, icon, active_data, active_propname, index
    ):
        custom_icon = "BOOKMARKS"
        if self.layout_type in {"DEFAULT", "COMPACT"}:
            split = layout.split(factor=0.6)
            split.label(text=item.name, icon=custom_icon)
            split.label(text=item.variation_type)
        elif self.layout_type in {"GRID"}:
            layout.alignment = "CENTER"
            layout.label(text=item.name, icon=custom_icon)
            layout.label(text=item.variation_type)
 
 
# =======================================
#  panels
# =======================================
# UNIVERSAL_PT_VariationPanel: blender naming format
# VariationPanel: Python naming format results warning message
#    Warning: 'VariationPanel' does not contain '_PT_' with prefix and suffix
class UNIVERSAL_PT_VariationPanel(bpy.types.Panel):
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_context = ""
    bl_category = "Universal"  # SIDE the tab name
    bl_label = "Universal Tool Panel"  # tab tab > panel name
 
    def draw(self, context):
        wm = context.window_manager
        layout = self.layout
        publish_prop = wm.publish_setting_props
 
        layout.operator("custom.universal_add_cube")
        layout.label(text="Variation Object List:", icon="EXPORT")
 
        row = layout.row()
        row.template_list(
            listtype_name="UNIVERSAL_UL_VariationList",
            list_id="",
            dataptr=wm,
            propname="variation_list",
            active_dataptr=wm,
            active_propname="variation_list_index",
        )
 
        layout.prop(wm.variation_props, "variation_type")
        row = layout.row()
        row.operator("custom.universal_add_object", icon="ADD", text="Add Object")
        row.operator(
            "custom.universal_remove_object", icon="REMOVE", text="Remove Object"
        )
 
        layout.prop(publish_prop, "publish_name", text="Super Name")
        layout.prop(publish_prop, "publish_version")
        layout.prop(publish_prop, "publish_dir")
        layout.prop(publish_prop, "publish_type", expand=True)
        asset_author_name = bpy.context.preferences.addons[
            "UniversalTool"
        ].preferences.asset_author_name.replace(" ", "-")
        if (
            os.path.isdir(publish_prop.publish_dir)
            and publish_prop.publish_name.strip() != ""
        ):
            result_file = os.path.join(
                publish_prop.publish_dir,
                publish_prop.publish_name.strip()
                + f"_v{publish_prop.publish_version}_{asset_author_name}.json",
            )
            layout.label(text=f"output: {result_file}")
        else:
            layout.label(text="output: not valid setting")
        layout.operator("custom.universal_publish_asset")
 
 
# =======================================
#  class list to register
# =======================================
classes = (
    UniversalAddonPreferences,
    # properties
    VariationItem,
    VariationProps,
    PublishSetting,
    # operators
    AddCube,
    AddObject,
    UNIVERSAL_OT_RemoveObject,
    PublishAsset,
    # ui
    UNIVERSAL_UL_VariationList,
    UNIVERSAL_PT_VariationPanel,
)
 
 
def register():
    for cls in classes:
        bpy.utils.register_class(cls)
    bpy.types.WindowManager.variation_list = CollectionProperty(type=VariationItem)
    bpy.types.WindowManager.variation_list_index = IntProperty(default=0)
    bpy.types.WindowManager.variation_props = PointerProperty(type=VariationProps)
    bpy.types.WindowManager.publish_setting_props = PointerProperty(type=PublishSetting)
 
 
def unregister():
    del bpy.types.WindowManager.variation_list
    del bpy.types.WindowManager.variation_list_index
    del bpy.types.WindowManager.variation_type_props
    del bpy.types.WindowManager.publish_setting_props
    for cls in classes:
        bpy.utils.unregister_class(cls)
 
 
if __name__ == "__main__":
    register()