Differences
This shows you the differences between two versions of the page.
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] ying | graphic: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], | bpy.data.objects.remove(bpy.data.objects[each], | ||
+ | </ | ||
+ | |||
+ | ===== 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__), | ||
+ | info=subprocess.Popen(f'" | ||
+ | out, err = info.communicate() | ||
+ | </ | ||
+ | * task script reading parameter inside blender <code python> | ||
+ | # task script | ||
+ | import sys | ||
+ | import bpy | ||
+ | argv = sys.argv | ||
+ | argv = argv[argv.index(" | ||
+ | print(argv) | ||
</ | </ | ||
Line 375: | Line 393: | ||
ref: | ref: | ||
* https:// | * https:// | ||
+ | * https:// | ||
* https:// | * https:// | ||
+ | * https:// | ||
- | * Class name convention | + | |
- | * Header : _HT_ | + | |
- | * Menu : _MT_ | + | * {SEPARATOR} |
- | * Operator : _OT_ | + | |
- | * 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 " | + | * UIList : _UL_ |
+ | * mixed_case | ||
+ | * _CheckSelection (this more like standard python PEP class naming) | ||
+ | * _Check_Selection | ||
+ | * _check_selection | ||
+ | | ||
+ | * if not follow above: you get warning " | ||
+ | * **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 = " | ||
+ | * 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 = " | ||
+ | | ||
+ | class CreateAsset(bpy.types.Operator): | ||
+ | bl_idname = " | ||
- | + | class BUILDMAKER_OT_createAsset(bpy.types.Operator): | |
+ | # 2.8x onwards, property | ||
+ | class MyOperator(bpy.types.Operator): | ||
+ | value: IntProperty() | ||
+ | |||
+ | class ToolProperties(bpy.types.PropertyGroup): | ||
+ | commandLineMode: | ||
+ | name = " | ||
+ | description = "The option specifies if the addon is used in the command line mode", | ||
+ | default = False | ||
+ | ) | ||
+ | | ||
+ | class ToolProps(bpy.types.PropertyGroup): | ||
+ | email : StringProperty( | ||
+ | name=" | ||
+ | description=" | ||
+ | default="" | ||
+ | ) | ||
+ | class TOOL_PROPS(bpy.types.PropertyGroup): | ||
+ | lon: FloatProperty() | ||
+ | lat: FloatProperty() | ||
+ | class BUILDMAKER_PG_color(PropertyGroup): | ||
+ | color: FloatVectorProperty(subtype=' | ||
+ | </ | ||
===== 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, | layout.template_list(type_name, | ||
+ | </ | ||
+ | * 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 == ' | ||
+ | break | ||
+ | # Calculate the width of the panel | ||
+ | panel_width = 280 # default value | ||
+ | for region in area.regions: | ||
+ | if region.type == ' | ||
+ | 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, | ||
+ | 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, | ||
+ | if not first_icon_line_done: | ||
+ | layout.label(text=chunk, | ||
+ | first_icon_line_done = True | ||
+ | else: | ||
+ | layout.label(text=rest_line_indent+chunk) | ||
+ | |||
+ | class MyPanel(bpy.types.Panel): | ||
+ | bl_idname = " | ||
+ | bl_label = "My Panel" | ||
+ | bl_space_type = " | ||
+ | bl_region_type = " | ||
+ | | ||
+ | def draw(self, context): | ||
+ | layout = self.layout | ||
+ | create_long_label(layout, | ||
+ | | ||
+ | |||
+ | # Register the panel | ||
+ | bpy.utils.register_class(MyPanel) | ||
</ | </ | ||
===== 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 | ||
+ | </ | ||
+ | |||
+ | ===== 3rd party library ===== | ||
+ | |||
+ | * use PIL image and blender image <code python> | ||
+ | # ref: https:// | ||
+ | # ref: https:// | ||
+ | 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, | ||
+ | subprocess.call([python_exe, | ||
+ | subprocess.call([python_exe, | ||
+ | subprocess.call([python_exe, | ||
+ | | ||
+ | def pil_to_image(pil_image, | ||
+ | width, height = pil_image.width, | ||
+ | normalized = 1.0 / 255.0 | ||
+ | bpy_image = bpy.data.images.new(name, | ||
+ | bpy_image.pixels[: | ||
+ | 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(" | ||
+ | pil_image = Image.frombytes(' | ||
+ | return pil_image | ||
+ | |||
+ | im = image_to_pil(bpy.data.images[" | ||
+ | im = im.transpose(Image.FLIP_LEFT_RIGHT).rotate(45) | ||
+ | pil_to_image(im) | ||
+ | </ | ||
+ | * alternative <code python> | ||
+ | bl_info = { | ||
+ | " | ||
+ | " | ||
+ | | ||
+ | def register(): | ||
+ | import pip | ||
+ | pip.main([' | ||
+ | from PIL import Image | ||
+ | | ||
+ | def unregister(): | ||
+ | print(" | ||
</ | </ | ||