graphic:python:blender

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
graphic:python:blender [2024/01/31 06:41] – [Blender UI code] yinggraphic:python:blender [2024/04/09 10:46] (current) – [My related dev notes] ying
Line 393: Line 393:
 ref:  ref: 
   * https://wiki.blender.org/wiki/Reference/Release_Notes/2.80/Python_API/Addons   * https://wiki.blender.org/wiki/Reference/Release_Notes/2.80/Python_API/Addons
 +  * https://developer.blender.org/docs/release_notes/2.80/python_api/addons/
   * https://s-nako.work/2020/12/blender-addon-naming-rules/   * https://s-nako.work/2020/12/blender-addon-naming-rules/
 +  * https://b3d.interplanety.org/en/class-naming-conventions-in-blender-2-8-python-api/
  
   * **Class name convention**: UPPER_CASE_{SEPARATOR}_mixed_case   * **Class name convention**: UPPER_CASE_{SEPARATOR}_mixed_case
Line 422: Line 424:
  
 Example of class name, balancing between Python PEP Example of class name, balancing between Python PEP
-  * BLOSM addon, <code python>+  * example addon, <code python> 
 +class BUILDMAKER_OT_CreateAsset(bpy.types.Operator): 
 +    bl_idname = "custom.buildmaker_create_asset" 
 +     
 +class CreateAsset(bpy.types.Operator): 
 +    bl_idname = "BUILDMAKER_OT_CreateAsset"
  
 +class BUILDMAKER_OT_createAsset(bpy.types.Operator):
 +
 +# 2.8x onwards, property
 +class MyOperator(bpy.types.Operator):
 +    value: IntProperty()
 +
 +class ToolProperties(bpy.types.PropertyGroup):
 +    commandLineMode: bpy.props.BoolProperty(
 +        name = "Command line mode",
 +        description = "The option specifies if the addon is used in the command line mode",
 +        default = False
 +    ) 
 +    
 +class ToolProps(bpy.types.PropertyGroup):
 +    email : StringProperty(
 +        name="email",
 +        description="User email",
 +        default=""
 +    )
 +class TOOL_PROPS(bpy.types.PropertyGroup):
 +    lon: FloatProperty()
 +    lat: FloatProperty()
 +class BUILDMAKER_PG_color(PropertyGroup):
 +    color: FloatVectorProperty(subtype='COLOR', min=0, max=1, update=updColor, size=4)
 </code> </code>
 ===== common blender api commands ===== ===== common blender api commands =====
Line 603: Line 634:
 ... ...
 layout.template_list(type_name, list_id, ptr, propname, active, rows, maxrows, ..) # a list widget layout.template_list(type_name, list_id, ptr, propname, active, rows, maxrows, ..) # a list widget
 +</code>
 +  * long label solution and auto wrap <code python>
 +import bpy
 +import textwrap
 +
 +long_text = """
 +        a long text  long text  long text
 +        b test
 +        c long text  long text  long text  long text  long text  long
 +        d text  long text  long text  long text """
 +
 +def get_max_label_width():
 +    # Get the 3D View area
 +    for area in bpy.context.screen.areas:
 +        if area.type == 'VIEW_3D':
 +            break
 +    # Calculate the width of the panel
 +    panel_width = 280 # default value
 +    for region in area.regions:
 +        if region.type == 'UI':
 +            panel_width = region.width
 +            return panel_width
 +            break
 +    # Calculate the maximum width of the label
 +    uifontscale = 9 * context.preferences.view.ui_scale
 +    max_label_width = int(panel_width // uifontscale)
 +    return max_label_width
 +
 +def create_long_label(layout, long_text, icon='',indent=''):
 +    max_label_width = get_max_label_width()
 +    first_icon_line_done = False
 +    rest_line_indent = indent
 +    if icon == '':
 +        first_icon_line_done = True
 +        rest_line_indent = ''
 +    # Split the text into lines and format each line
 +    for line in long_text.splitlines():
 +        # Remove leading and trailing whitespace
 +        line = line.strip()
 +        # Split the line into chunks that fit within the maximum label width
 +        for chunk in textwrap.wrap(line, width=max_label_width):
 +            if not first_icon_line_done:
 +                layout.label(text=chunk, icon=icon)
 +                first_icon_line_done = True
 +            else:
 +                layout.label(text=rest_line_indent+chunk)
 +
 +class MyPanel(bpy.types.Panel):
 +    bl_idname = "OBJECT_PT_my_panel"
 +    bl_label = "My Panel"
 +    bl_space_type = "VIEW_3D"
 +    bl_region_type = "UI"
 +    
 +    def draw(self, context):
 +        layout = self.layout
 +        create_long_label(layout, long_text, "KEYTYPE_JITTER_VEC",   ')
 +        
 +
 +# Register the panel
 +bpy.utils.register_class(MyPanel)
 </code> </code>
 ===== Blender preference ===== ===== Blender preference =====
Line 788: Line 879:
     * (Darkfall studio - tut,shorts,anime) https://www.youtube.com/@DarkfallBlender     * (Darkfall studio - tut,shorts,anime) https://www.youtube.com/@DarkfallBlender
     * (Luca Chirichella - blender tut) https://www.youtube.com/@RedKProduction     * (Luca Chirichella - blender tut) https://www.youtube.com/@RedKProduction
 +
 +====== My Blender addon template ======
 +
 +<code python>
 +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()
 +
 +</code>
 +
  • graphic/python/blender.1706683304.txt.gz
  • Last modified: 2024/01/31 06:41
  • by ying