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
Next revisionBoth sides next revision
graphic:python:blender [2023/11/15 02:13] – [File related Operation] yinggraphic:python:blender [2024/02/01 09:13] – [Blender UI code] ying
Line 38: Line 38:
     if each in bpy.data.objects.keys():     if each in bpy.data.objects.keys():
         bpy.data.objects.remove(bpy.data.objects[each], do_unlink=True)         bpy.data.objects.remove(bpy.data.objects[each], do_unlink=True)
 +</code>
 +
 +===== cmd related operation =====
 +
 +  * send blender file and python script to blender process with parameter <code python>
 +# blender use empty scene to load task script with parameter after --, as (blender file, blender preview path, resolution)
 +app_path = bpy.app.binary_path
 +task_path = os.path.join( os.path.dirname(__file__), 'process', 'task_make_preview.py' )
 +info=subprocess.Popen(f'"{app_path}" --python "{task_path}" -- "{file_path}" "{blend_preview_dir}" 512 512',stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE)
 +out, err = info.communicate()
 +</code>
 +  * task script reading parameter inside blender <code python>
 +# task script
 +import sys
 +import bpy
 +argv = sys.argv
 +argv = argv[argv.index("--") + 1:]  # get all args after "--"
 +print(argv)  # --> ['example', 'args', '123']
 </code> </code>
  
Line 375: 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 is: UPPER_CASE_{SEPARATOR}_mixed_case +  * **Class name convention**: UPPER_CASE_{SEPARATOR}_mixed_case 
-    * Header : _HT_ +    * UPPER_CASE: normally the ADDON_NAME, or a Unique ID_WORD that make it not crashing into other existing class names 
-    * Menu : _MT_ +    * {SEPARATOR} 
-    * Operator : _OT_ +      * Header : _HT_ 
-    * Panel : _PT_ +      * Menu : _MT_ 
-    * UIList : _UL_ +      * Operator : _OT_ 
-  * like  [A-Z][A-Z0-9_]*_MT_[A-Za-z0-9_]+ +      * Panel : _PT_ 
-  * if not follow above: you get warning "doesn't contain '_PT_' with prefix & suffix"+      * UIList : _UL_ 
 +    * mixed_case 
 +      * _CheckSelection (this more like standard python PEP class naming) 
 +      * _Check_Selection 
 +      * _check_selection 
 +    * like  [A-Z][A-Z0-9_]*_MT_[A-Za-z0-9_]+ 
 +    * if not follow above: you get warning "doesn't contain '_PT_' with prefix & suffix" 
 +  * **operator ID name convention** (feel like a username for your operator inside blender ) 
 +    * category_name.operator_task_like_name 
 +      * custom.addon_name_task_name (snake_case like PEP function) 
 +    * For headers, menus and panels, the bl_idname is expected to match the class name 
 +      * class: ADDON_NAME_OT_check_selection 
 +      * bl_idname: addon_name.check_selection 
 +  * **other same as PEP** 
 +    * obj = "BigBox" 
 +    * obj_max_count = 2 
 +    * def function_name(arg_name) 
 +    * class ClassName()
  
 +Example of class name, balancing between Python PEP
 +  * 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>
 ===== common blender api commands ===== ===== common blender api commands =====
  
Line 508: Line 575:
 **UI** **UI**
   * bpy.types.Panel   * bpy.types.Panel
 +    * the panel display order is determined by the order bpy.utils.register_class processes
   * bpy.types.UIList   * bpy.types.UIList
   * bpy.types.Operator   * bpy.types.Operator
Line 566: 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 584: Line 712:
 bpy.context.preferences.addons[__name__] bpy.context.preferences.addons[__name__]
 bpy.context.preferences.addons[AddonName].preferences.attributeA bpy.context.preferences.addons[AddonName].preferences.attributeA
 +</code>
 +
 +===== 3rd party library =====
 +
 +  * use PIL image and blender image <code python>
 +# ref: https://blender.stackexchange.com/questions/270924/how-to-open-blenders-image-using-pillow
 +# ref: https://stackoverflow.com/questions/21233946/pil-image-save-function-fails-in-blender-python
 +import bpy
 +import os
 +import subprocess
 +import sys
 +import io
 +import struct
 +import numpy as np
 +
 +try:
 +    from PIL import Image
 +except: 
 +    python_exe = os.path.join(sys.prefix, 'bin', 'python.exe')
 +    subprocess.call([python_exe, '-m', 'ensurepip'])
 +    subprocess.call([python_exe, '-m', 'pip', 'install', '--upgrade', 'pip'])
 +    subprocess.call([python_exe, '-m', 'pip', 'install', '--upgrade', 'pillow'])
 +    
 +def pil_to_image(pil_image, name='NewImage'):
 +    width, height = pil_image.width, pil_image.height
 +    normalized = 1.0 / 255.0
 +    bpy_image = bpy.data.images.new(name, width=width, height=height)
 +    bpy_image.pixels[:] = (np.asarray(pil_image.convert('RGBA'),dtype=np.float32) * normalized).ravel()
 +    return bpy_image
 +
 +def image_to_pil(bpy_image):
 +    img = bpy_image
 +    pixels = [int(px * 255) for px in bpy_image.pixels[:]]
 +    bytes = struct.pack("%sB" % len(pixels), *pixels)
 +    pil_image = Image.frombytes('RGBA', (bpy_image.size[0], bpy_image.size[1]), bytes)
 +    return pil_image
 +
 +im = image_to_pil(bpy.data.images["test"])
 +im = im.transpose(Image.FLIP_LEFT_RIGHT).rotate(45)
 +pil_to_image(im)
 +</code>
 +  * alternative <code python>
 +bl_info = {
 +    "name": "My Test Addon",
 +    "category": "Object"}
 +    
 +def register():
 +    import pip
 +    pip.main(['install', 'pillow'])
 +    from PIL import Image
 +    
 +def unregister():
 +    print("Goodbye World")
 </code> </code>
  
  • graphic/python/blender.txt
  • Last modified: 2024/05/15 04:30
  • by ying