├── Addons_Introduction.md ├── Areas.md ├── BMesh.md ├── BlenderPythonApiChange.md ├── Blender_Addons_Development.md ├── Blender_Addons_Development.py ├── Blender_Python_Templates ├── README.md ├── addon_add_object.py ├── background_job.py ├── batch_export.py ├── bmesh_simple.py ├── bmesh_simple_editmode.py ├── builtin_keyingset.py ├── custom_nodes.py ├── driver_functions.py ├── external_script_stub.py ├── gizmo_custom_geometry.py ├── gizmo_operator.py ├── gizmo_operator_target.py ├── gizmo_simple.py ├── operator_file_export.py ├── operator_file_import.py ├── operator_mesh_add.py ├── operator_mesh_uv.py ├── operator_modal.py ├── operator_modal_draw.py ├── operator_modal_timer.py ├── operator_modal_view3d.py ├── operator_modal_view3d_raycast.py ├── operator_node.py ├── operator_simple.py ├── ui_list.py ├── ui_list_simple.py ├── ui_menu.py ├── ui_menu_simple.py ├── ui_panel.py ├── ui_panel_simple.py ├── ui_pie_menu.py ├── ui_previews_custom_icon.py └── ui_previews_dynamic_enum.py ├── Blender_python_sample ├── README.md ├── clock.py └── clock_27.py ├── Curves.md ├── Drivers.md ├── Duplication.md ├── Empty-(null-object).md ├── GeneralPythonSnippets.md ├── GreasePencil.md ├── Home.md ├── Image_Pixels.md ├── Keyframes.md ├── Layers.md ├── Layout.md ├── Mesh.md ├── Metaballs.md ├── OperatorTricks.md ├── Operators.md ├── Parenting.md ├── Particle-Systems.md ├── Preface.md ├── Properties.md ├── PyGeom.md ├── README.md ├── README_en.md ├── Scripted-Keyframes.md ├── Text.md ├── Themedata.md ├── UV---DPI-(variable-or-homogeneous).md ├── Vertex-Colors.md ├── _Sidebar.md ├── bgl_blf.md ├── bmesh_ops_meshops.md ├── bmesh_ops_primitives.md ├── bpy_data_materials.md ├── bpy_data_texts.md ├── icons.md ├── mDrivEngine ├── README.md └── clock.gif └── mathutils.md /Addons_Introduction.md: -------------------------------------------------------------------------------- 1 | Add-ons are scripts with extra 'boilerplate' and 'interface' code added. The extra code is for giving Blender a way to automatically load the add-on and have it load in the right places (menus) or appear in the right context (in edit mode for example). 2 | 3 | Blender now has [great docs showing best practices](http://www.blender.org/api/blender_python_api_current/info_tutorial_addon.html) for approaching add-on code. The page also links to additional Python tutorials that cover the level of Python that you will need to be able to write stable add-ons. Even if your goal is to only write Blender add-ons, learning Python as fully as possible will never be a waste of time. 4 | 5 | That tutorial shows how to register addons / operators and how to define and remove user defined keyboard shortcuts. I think most of that code is not obvious until you see working examples, and you experiment with dummy add-ons. Just like with the Operators programming page on this wiki I'll suggest you read more closely the content of Blender's `/addons` folder. If you've followed the above tutorial then seeing real add-ons with already working code will quickly reinforce the concepts. 6 | 7 | ### When not to code an add-on 8 | 9 | - Not all scripts need to have full blown add-on code straight away. Sometimes you'll write a script for a specific task and never use it again or only use it 2 years later. Infrequently used scripts like that don't need add-on code (if you can get away with it). 10 | -------------------------------------------------------------------------------- /Areas.md: -------------------------------------------------------------------------------- 1 | stub: 2 | 3 | 4 | This finds the largest open editor, splits it, then sets the editor type to for instance.. NODE_EDITOR. 5 | 6 | ```python 7 | import bpy 8 | 9 | def get_largest_area(): 10 | window_reference = None 11 | surface_area = 0 12 | ctx = None 13 | for window in bpy.context.window_manager.windows: 14 | for area in window.screen.areas: 15 | w, h = area.width, area.height 16 | surface = w * h 17 | if surface > surface_area: 18 | surface_area = surface 19 | window_reference = area 20 | override = bpy.context.copy() 21 | override['area'] = area 22 | 23 | return window_reference, surface_area, override 24 | 25 | area, size, ctx = get_largest_area() 26 | bpy.ops.screen.area_split(ctx, direction='HORIZONTAL',factor=0.5) 27 | bpy.ops.wm.context_set_string(ctx, data_path="area.type", value="NODE_EDITOR") 28 | ``` -------------------------------------------------------------------------------- /BMesh.md: -------------------------------------------------------------------------------- 1 | 2 | Blender comes with a great set of templates for BMesh, I won't reproduce them here. 3 | 4 | - _TextEditor -> Templates -> Python -> BMesh Simple_ 5 | - _TextEditor -> Templates -> Python -> BMesh Simple EditMode_ 6 | 7 | ### Snippet 1 (simple sequential geometry additions) 8 | 9 | This example works if the Object is in _Object Mode_. 10 | 11 | ```python 12 | import bpy 13 | import bmesh 14 | 15 | mesh = bpy.data.objects['object_name'].data 16 | bm = bmesh.new() 17 | bm.from_mesh(mesh) 18 | 19 | # some aliases 20 | verts = bm.verts 21 | faces = bm.faces 22 | edges = bm.edges 23 | 24 | # a Triangle 25 | v1 = verts.new((2.0, 2.0, 2.0)) 26 | v2 = verts.new((-2.0, 2.0, 2.0)) 27 | v3 = verts.new((-2.0, -2.0, 2.0)) 28 | faces.new((v1, v2, v3)) 29 | 30 | # a Quad 31 | v1 = verts.new((3.0, 2.0, -3.0)) 32 | v2 = verts.new((-3.0, 2.0, -3.0)) 33 | v3 = verts.new((-3.0, -2.0, -3.0)) 34 | v4 = verts.new((3.0, -2.0, -3.0)) 35 | faces.new((v1, v2, v3, v4)) 36 | 37 | # an Edge 38 | v1 = verts.new((0.0, 1.0, 1.0)) 39 | v2 = verts.new((0.0, 0.0, 1.0)) 40 | edges.new((v1, v2)) 41 | 42 | bm.to_mesh(mesh) 43 | bm.free() 44 | mesh.update() 45 | ``` 46 | 47 | ### Snippet 2 (pydata_from_bmesh) 48 | 49 | This can be handy for debugging 50 | 51 | ```python 52 | def pydata_from_bmesh(bm): 53 | v = [v.co[:] for v in bm.verts] 54 | e = [[i.index for i in e.verts] for e in bm.edges[:]] 55 | p = [[i.index for i in p.verts] for p in bm.faces[:]] 56 | return v, e, p 57 | ``` 58 | 59 | ### Snippet 3 (bmesh_from_pydata): 60 | 61 | if you've ever wondered what it might look like: 62 | ```python 63 | import bmesh 64 | 65 | 66 | def bmesh_from_pydata(verts=None, edges=None, faces=None): 67 | ''' verts is necessary, edges/faces are optional ''' 68 | 69 | bm = bmesh.new() 70 | if not verts: 71 | return bm 72 | 73 | add_vert = bm.verts.new 74 | 75 | bm_verts = [add_vert(co) for co in verts] 76 | bm.verts.index_update() 77 | 78 | if faces: 79 | add_face = bm.faces.new 80 | for face in faces: 81 | add_face([bm_verts[i] for i in face]) 82 | bm.faces.index_update() 83 | 84 | if edges: 85 | add_edge = bm.edges.new 86 | for edge in edges: 87 | edge_seq = bm_verts[edge[0]], bm_verts[edge[1]] 88 | try: 89 | add_edge(edge_seq) 90 | except ValueError: 91 | # edge exists! 92 | pass 93 | bm.edges.index_update() 94 | 95 | return bm 96 | 97 | ``` 98 | 99 | ### Snippet 4 : Bmesh primitives 100 | 101 | Bmesh has a number of primitive meshes just like `bpy.ops`, with the main difference being that you have to pass a receiver bmesh to fill with the geometry, and it doesn't create objects. 102 | 103 | 104 | ```python 105 | import bmesh 106 | 107 | def create_icospehere(subdiv, d): 108 | bm = bmesh.new() 109 | bmesh.ops.create_icosphere(bm, subdivisions=subdiv, diameter=d) 110 | v, e, p = pydata_from_bmesh(bm) 111 | bm.free() 112 | return v, e, p 113 | ``` 114 | 115 | ## bmesh methods / attributes 116 | 117 | bmesh will evolve steadily to make scripting meshes easier. I will try to keep this updated and will post-mark this section so you know when I last updated it. 118 | 119 | 19 Dec 2015 120 | 121 | | method | description | 122 | |-------------------------|--------------------------------------------------------------| 123 | | bm.from_mesh(data) | get a bm representation of obj.data (Mesh data only) | 124 | | bm.clear() | wipe all geometry data in the current bmesh | 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /BlenderPythonApiChange.md: -------------------------------------------------------------------------------- 1 | |2.7|2.8| 2 | |--|---| 3 | |bpy.types.VIEW3D_MT_object_specials|bpy.types.VIEW3D_MT_object_context_menu| 4 | |bpy.types.INFO_MT_add|bpy.types.VIEW3D_MT_mesh_add| 5 | |bpy.context.object.data.uv_textures|bpy.context.object.data.uv_layers| 6 | |bpy.context.object.users_group|bpy.context.object.users_collection| 7 | |bpy.context.scene.objects.active|bpy.context.view_layer.objects.active| 8 | |bpy.context.object.select=True|bpy.context.object.select_set(True)| 9 | |bpy.context.scene.objects.link|bpy.context.scene.collection.objects.link| 10 | |bpy.context.object.draw_type|bpy.context.object.display_type| 11 | -------------------------------------------------------------------------------- /Blender_Addons_Development.md: -------------------------------------------------------------------------------- 1 | [插件模板](https://github.com/BlenderCN/BlenderPython/blob/master/Blender_Python_Templates/README.md) 2 | -------------------------------------------------------------------------------- /Blender_Addons_Development.py: -------------------------------------------------------------------------------- 1 | 2 | bl_info = { 3 | "name": "cs name", #插件名字 4 | "author": "cs author",#作者名字 5 | "version": (9, 9, 9),#插件版本 6 | "blender": (9, 99, 0),#blender版本 7 | "location": "3DView > Tools",#插件所在位置 8 | "description": "Cha Jian cs???",#描述 9 | "warning": "xxx",#警告 10 | "wiki_url": "http://tieba.baidu.com/" 11 | "f?kw=黑猫鸣泣时&fr=index&red_tag=2858782483", #文档地址 12 | "support": 'OFFICIAL',# 13 | "category": "Mesh",#分类 14 | } 15 | 16 | import bpy 17 | 18 | class H_class(bpy.types.Panel): 19 | """Creates a Panel in the Object properties window""" 20 | bl_label = "菜单标题" 21 | bl_idname = "OBJECT_PT_hello"#未知 可以改 22 | bl_space_type = 'PROPERTIES' #枚举('EMPTY', 'VIEW_3D', 'TIMELINE', 'GRAPH_EDITOR', 'DOPESHEET_EDITOR', 'NLA_EDITOR', 'IMAGE_EDITOR', 'SEQUENCE_EDITOR', 'CLIP_EDITOR', 'TEXT_EDITOR', 'NODE_EDITOR', 'LOGIC_EDITOR', 'PROPERTIES', 'OUTLINE) 23 | bl_region_type = 'WINDOW' #枚举 ('WINDOW', 'HEADER', 'CHANNELS', 'TEMPORARY', 'UI', 'TOOLS', 'TOOL_PROPS', 'PREVIEW') 24 | bl_context = "object" #未知 不可以改 25 | 26 | 27 | def draw(self, context): 28 | layout = self.layout 29 | obj = context.object 30 | row = layout.row() 31 | row.label(text="文字1", icon='WORLD_DATA') 32 | row = layout.row() 33 | row.label(text="文字2" + obj.name) 34 | row = layout.row() 35 | row.prop(obj, "文字3") 36 | row = layout.row() 37 | row.operator("mesh.primitive_cube_add") 38 | 39 | 40 | class panel_1(bpy.types.Panel): #物体属性面板 41 | """Creates a Panel in the Object properties window""" 42 | bl_label = "菜单标题" 43 | bl_idname = "OBJECT_PT_hello"#未知 可以改 44 | bl_space_type = 'PROPERTIES' #枚举('EMPTY', 'VIEW_3D', 'TIMELINE', 'GRAPH_EDITOR', 'DOPESHEET_EDITOR', 'NLA_EDITOR', 'IMAGE_EDITOR', 'SEQUENCE_EDITOR', 'CLIP_EDITOR', 'TEXT_EDITOR', 'NODE_EDITOR', 'LOGIC_EDITOR', 'PROPERTIES', 'OUTLINE) 45 | bl_region_type = 'WINDOW' #枚举 ('WINDOW', 'HEADER', 'CHANNELS', 'TEMPORARY', 'UI', 'TOOLS', 'TOOL_PROPS', 'PREVIEW') 46 | bl_context = "object" #未知 不可以改 47 | 48 | 49 | def draw(self, context): 50 | layout = self.layout 51 | obj = context.object 52 | row = layout.row() 53 | row.label(text="文字1", icon='WORLD_DATA') 54 | row = layout.row() 55 | row.label(text="文字2" + obj.name) 56 | row = layout.row() 57 | row.prop(obj, "文字3") 58 | row = layout.row() 59 | row.operator("mesh.primitive_cube_add") 60 | 61 | 62 | from bpy.types import Operator, Panel 63 | 64 | 65 | class AddChain(bpy.types.Operator): 66 | """Add a Chain""" 67 | bl_idname = "mesh.primitive_chain_add" 68 | bl_label = "Add Chain" 69 | bl_options = {'REGISTER', 'UNDO'} 70 | 71 | 72 | def execute(self, context): #点击按钮时候执行 73 | #Add_Chain() 74 | print("cs") 75 | return {'FINISHED'} 76 | 77 | 78 | class panel_2(Panel): 79 | bl_space_type = 'VIEW_3D' 80 | bl_region_type = 'TOOLS' 81 | bl_category = 'Create' 82 | bl_label = "Add Chain" 83 | bl_context = "objectmode" 84 | bl_options = {'DEFAULT_CLOSED'} 85 | 86 | def draw(self, context): 87 | layout = self.layout 88 | layout.operator(AddChain.bl_idname, text="Chain") 89 | 90 | class panel_2(bpy.types.Panel): 91 | bl_space_type = 'VIEW_3D' 92 | bl_region_type = 'TOOLS' 93 | bl_category = '标题' #标题名字 94 | bl_label = "测试" #菜单名字 95 | bl_context = "objectmode" 96 | bl_options = {'DEFAULT_CLOSED'} 97 | 98 | def draw(self, context): 99 | layout = self.layout 100 | layout.operator(AddChain.bl_idname, text="按钮") #按钮名字 101 | 102 | 103 | class AddChain(bpy.types.Operator): 104 | """提示信息""" 105 | bl_idname = "mesh.primitive_chain_add" 106 | bl_idname = "mesh.cs_add" # python 提示 107 | bl_label = "Add Chain" 108 | bl_category = '标题' #标题名字 109 | bl_label = "测试" #菜单名字 110 | bl_options = {'REGISTER', 'UNDO'} 111 | bl_options = {'DEFAULT_CLOSED'} # HIDE_HEADER 为隐藏菜单 112 | 113 | 114 | def execute(self, context): #点击按钮时候执行 115 | cs() 116 | return {'FINISHED'} 117 | 118 | 119 | 120 | def register(): #启用插件时候执行 121 | print('525495787') 122 | bpy.utils.register_class(H_class) #注册一个类 123 | bpy.utils.register_module(__name__) #大概是注册这个模块 124 | 125 | def unregister(): #关闭插件时候执行 126 | print('ZX') 127 | bpy.utils.unregister_module(__name__) #大概是注销这个模块 128 | bpy.utils.unregister_class(H_class) #注销一个类 129 | 130 | 131 | if __name__ == "__main__": 132 | register() 133 | -------------------------------------------------------------------------------- /Blender_Python_Templates/README.md: -------------------------------------------------------------------------------- 1 | # 插件模板 2 | 3 | -------------------------------------------------------------------------------- /Blender_Python_Templates/addon_add_object.py: -------------------------------------------------------------------------------- 1 | bl_info = { 2 | "name": "New Object", # 插件名字 3 | "author": "Your Name Here", # 作者名字 4 | "version": (1, 0), # 插件版本 5 | "blender": (2, 80, 0), # blender版本 6 | "location": "View3D > Add > Mesh > New Object", # 插件所在位置 7 | "description": "Adds a new Mesh Object", # 描述 8 | "warning": "", # 警告 9 | "wiki_url": "", # 文档地址 10 | "category": "Add Mesh", #分类 11 | } 12 | 13 | 14 | import bpy 15 | from bpy.types import Operator 16 | from bpy.props import FloatVectorProperty 17 | from bpy_extras.object_utils import AddObjectHelper, object_data_add 18 | from mathutils import Vector 19 | 20 | 21 | def add_object(self, context): 22 | scale_x = self.scale.x 23 | scale_y = self.scale.y 24 | 25 | verts = [ 26 | Vector((-1 * scale_x, 1 * scale_y, 0)), 27 | Vector((1 * scale_x, 1 * scale_y, 0)), 28 | Vector((1 * scale_x, -1 * scale_y, 0)), 29 | Vector((-1 * scale_x, -1 * scale_y, 0)), 30 | ] 31 | 32 | edges = [] 33 | faces = [[0, 1, 2, 3]] 34 | 35 | mesh = bpy.data.meshes.new(name="New Object Mesh") 36 | mesh.from_pydata(verts, edges, faces) 37 | # useful for development when the mesh may be invalid. 38 | # mesh.validate(verbose=True) 39 | object_data_add(context, mesh, operator=self) 40 | 41 | 42 | class OBJECT_OT_add_object(Operator, AddObjectHelper): 43 | """Create a new Mesh Object""" 44 | bl_idname = "mesh.add_object" # ID名称/python提示 45 | bl_label = "Add Mesh Object" # 标签,也就是面板显示的标题 46 | bl_options = {'REGISTER', 'UNDO'} #HIDE_DEADER为隐藏菜单 47 | 48 | scale: FloatVectorProperty( 49 | name="scale", 50 | default=(1.0, 1.0, 1.0), 51 | subtype='TRANSLATION', 52 | description="scaling", 53 | ) 54 | 55 | def execute(self, context): #点击按钮时候执行 56 | 57 | add_object(self, context) 58 | 59 | return {'FINISHED'} 60 | 61 | 62 | # Registration 63 | 64 | def add_object_button(self, context): 65 | self.layout.operator( 66 | OBJECT_OT_add_object.bl_idname, 67 | text="Add Object", 68 | icon='PLUGIN') 69 | 70 | 71 | # This allows you to right click on a button and link to the manual 72 | def add_object_manual_map(): 73 | url_manual_prefix = "https://docs.blender.org/manual/en/dev/" 74 | url_manual_mapping = ( 75 | ("bpy.ops.mesh.add_object", "editors/3dview/object"), 76 | ) 77 | return url_manual_prefix, url_manual_mapping 78 | 79 | 80 | def register(): #启用插件的时候执行 81 | bpy.utils.register_class(OBJECT_OT_add_object) #注册一个类 82 | bpy.utils.register_manual_map(add_object_manual_map) 83 | bpy.types.VIEW3D_MT_mesh_add.append(add_object_button) 84 | 85 | 86 | def unregister(): #关闭插件时候执行 87 | bpy.utils.unregister_class(OBJECT_OT_add_object) #注销一个类 88 | bpy.utils.unregister_manual_map(add_object_manual_map) 89 | bpy.types.VIEW3D_MT_mesh_add.remove(add_object_button) 90 | 91 | 92 | if __name__ == "__main__": 93 | register() 94 | -------------------------------------------------------------------------------- /Blender_Python_Templates/background_job.py: -------------------------------------------------------------------------------- 1 | # This script is an example of how you can run blender from the command line 2 | # (in background mode with no interface) to automate tasks, in this example it 3 | # creates a text object, camera and light, then renders and/or saves it. 4 | # This example also shows how you can parse command line options to scripts. 5 | # 6 | # Example usage for this test. 7 | # blender --background --factory-startup --python $HOME/background_job.py -- \ 8 | # --text="Hello World" \ 9 | # --render="/tmp/hello" \ 10 | # --save="/tmp/hello.blend" 11 | # 12 | # Notice: 13 | # '--factory-startup' is used to avoid the user default settings from 14 | # interfering with automated scene generation. 15 | # 16 | # '--' causes blender to ignore all following arguments so python can use them. 17 | # 18 | # See blender --help for details. 19 | 20 | 21 | import bpy 22 | 23 | 24 | def example_function(text, save_path, render_path): 25 | # Clear existing objects. 26 | bpy.ops.wm.read_factory_settings(use_empty=True) 27 | 28 | scene = bpy.context.scene 29 | 30 | txt_data = bpy.data.curves.new(name="MyText", type='FONT') 31 | 32 | # Text Object 33 | txt_ob = bpy.data.objects.new(name="MyText", object_data=txt_data) 34 | scene.collection.objects.link(txt_ob) # add the data to the scene as an object 35 | txt_data.body = text # the body text to the command line arg given 36 | txt_data.align_x = 'CENTER' # center text 37 | 38 | # Camera 39 | cam_data = bpy.data.cameras.new("MyCam") 40 | cam_ob = bpy.data.objects.new(name="MyCam", object_data=cam_data) 41 | scene.collection.objects.link(cam_ob) # instance the camera object in the scene 42 | scene.camera = cam_ob # set the active camera 43 | cam_ob.location = 0.0, 0.0, 10.0 44 | 45 | # Light 46 | light_data = bpy.data.lights.new("MyLight", 'POINT') 47 | light_ob = bpy.data.objects.new(name="MyCam", object_data=light_data) 48 | scene.collection.objects.link(light_ob) 49 | light_ob.location = 2.0, 2.0, 5.0 50 | 51 | scene.update() 52 | 53 | if save_path: 54 | bpy.ops.wm.save_as_mainfile(filepath=save_path) 55 | 56 | if render_path: 57 | render = scene.render 58 | render.use_file_extension = True 59 | render.filepath = render_path 60 | bpy.ops.render.render(write_still=True) 61 | 62 | 63 | def main(): 64 | import sys # to get command line args 65 | import argparse # to parse options for us and print a nice help message 66 | 67 | # get the args passed to blender after "--", all of which are ignored by 68 | # blender so scripts may receive their own arguments 69 | argv = sys.argv 70 | 71 | if "--" not in argv: 72 | argv = [] # as if no args are passed 73 | else: 74 | argv = argv[argv.index("--") + 1:] # get all args after "--" 75 | 76 | # When --help or no args are given, print this help 77 | usage_text = ( 78 | "Run blender in background mode with this script:" 79 | " blender --background --python " + __file__ + " -- [options]" 80 | ) 81 | 82 | parser = argparse.ArgumentParser(description=usage_text) 83 | 84 | # Example utility, add some text and renders or saves it (with options) 85 | # Possible types are: string, int, long, choice, float and complex. 86 | parser.add_argument( 87 | "-t", "--text", dest="text", type=str, required=True, 88 | help="This text will be used to render an image", 89 | ) 90 | 91 | parser.add_argument( 92 | "-s", "--save", dest="save_path", metavar='FILE', 93 | help="Save the generated file to the specified path", 94 | ) 95 | parser.add_argument( 96 | "-r", "--render", dest="render_path", metavar='FILE', 97 | help="Render an image to the specified path", 98 | ) 99 | 100 | args = parser.parse_args(argv) # In this example we won't use the args 101 | 102 | if not argv: 103 | parser.print_help() 104 | return 105 | 106 | if not args.text: 107 | print("Error: --text=\"some string\" argument not given, aborting.") 108 | parser.print_help() 109 | return 110 | 111 | # Run the example function 112 | example_function(args.text, args.save_path, args.render_path) 113 | 114 | print("batch job finished, exiting") 115 | 116 | 117 | if __name__ == "__main__": 118 | main() 119 | -------------------------------------------------------------------------------- /Blender_Python_Templates/batch_export.py: -------------------------------------------------------------------------------- 1 | # exports each selected object into its own file 2 | 3 | import bpy 4 | import os 5 | 6 | # export to blend file location 7 | basedir = os.path.dirname(bpy.data.filepath) 8 | 9 | if not basedir: 10 | raise Exception("Blend file is not saved") 11 | 12 | view_layer = bpy.context.view_layer 13 | 14 | obj_active = view_layer.objects.active 15 | selection = bpy.context.selected_objects 16 | 17 | bpy.ops.object.select_all(action='DESELECT') 18 | 19 | for obj in selection: 20 | 21 | obj.select_set(True) 22 | 23 | # some exporters only use the active object 24 | view_layer.objects.active = obj 25 | 26 | name = bpy.path.clean_name(obj.name) 27 | fn = os.path.join(basedir, name) 28 | 29 | bpy.ops.export_scene.fbx(filepath=fn + ".fbx", use_selection=True) 30 | 31 | # Can be used for multiple formats 32 | # bpy.ops.export_scene.x3d(filepath=fn + ".x3d", use_selection=True) 33 | 34 | obj.select_set(False) 35 | 36 | print("written:", fn) 37 | 38 | 39 | view_layer.objects.active = obj_active 40 | 41 | for obj in selection: 42 | obj.select_set(True) 43 | -------------------------------------------------------------------------------- /Blender_Python_Templates/bmesh_simple.py: -------------------------------------------------------------------------------- 1 | # This example assumes we have a mesh object selected 2 | 3 | import bpy 4 | import bmesh 5 | 6 | # Get the active mesh 7 | me = bpy.context.object.data 8 | 9 | 10 | # Get a BMesh representation 11 | bm = bmesh.new() # create an empty BMesh 12 | bm.from_mesh(me) # fill it in from a Mesh 13 | 14 | 15 | # Modify the BMesh, can do anything here... 16 | for v in bm.verts: 17 | v.co.x += 1.0 18 | 19 | 20 | # Finish up, write the bmesh back to the mesh 21 | bm.to_mesh(me) 22 | bm.free() # free and prevent further access 23 | -------------------------------------------------------------------------------- /Blender_Python_Templates/bmesh_simple_editmode.py: -------------------------------------------------------------------------------- 1 | # This example assumes we have a mesh object in edit-mode 2 | 3 | import bpy 4 | import bmesh 5 | 6 | # Get the active mesh 7 | obj = bpy.context.edit_object 8 | me = obj.data 9 | 10 | 11 | # Get a BMesh representation 12 | bm = bmesh.from_edit_mesh(me) 13 | 14 | bm.faces.active = None 15 | 16 | # Modify the BMesh, can do anything here... 17 | for v in bm.verts: 18 | v.co.x += 1.0 19 | 20 | 21 | # Show the updates in the viewport 22 | # and recalculate n-gon tessellation. 23 | bmesh.update_edit_mesh(me, True) 24 | -------------------------------------------------------------------------------- /Blender_Python_Templates/builtin_keyingset.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | 4 | class BUILTIN_KSI_hello(bpy.types.KeyingSetInfo): 5 | bl_label = "Hello World KeyingSet" 6 | 7 | # poll - test for whether Keying Set can be used at all 8 | def poll(ksi, context): 9 | return context.active_object or context.selected_objects 10 | 11 | # iterator - go over all relevant data, calling generate() 12 | def iterator(ksi, context, ks): 13 | for ob in context.selected_objects: 14 | ksi.generate(context, ks, ob) 15 | 16 | # generator - populate Keying Set with property paths to use 17 | def generate(ksi, context, ks, data): 18 | id_block = data.id_data 19 | 20 | ks.paths.add(id_block, "location") 21 | 22 | for i in range(5): 23 | ks.paths.add(id_block, "layers", i, group_method='NAMED', group_name="5x Hello Layers") 24 | 25 | ks.paths.add(id_block, "show_in_front", group_method='NONE') 26 | 27 | 28 | def register(): 29 | bpy.utils.register_class(BUILTIN_KSI_hello) 30 | 31 | 32 | def unregister(): 33 | bpy.utils.unregister_class(BUILTIN_KSI_hello) 34 | 35 | 36 | if __name__ == '__main__': 37 | register() 38 | -------------------------------------------------------------------------------- /Blender_Python_Templates/custom_nodes.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.types import NodeTree, Node, NodeSocket 3 | 4 | # Implementation of custom nodes from Python 5 | 6 | 7 | # Derived from the NodeTree base type, similar to Menu, Operator, Panel, etc. 8 | class MyCustomTree(NodeTree): 9 | # Description string 10 | '''A custom node tree type that will show up in the node editor header''' 11 | # Optional identifier string. If not explicitly defined, the python class name is used. 12 | bl_idname = 'CustomTreeType' 13 | # Label for nice name display 14 | bl_label = "Custom Node Tree" 15 | # Icon identifier 16 | bl_icon = 'NODETREE' 17 | 18 | 19 | # Custom socket type 20 | class MyCustomSocket(NodeSocket): 21 | # Description string 22 | '''Custom node socket type''' 23 | # Optional identifier string. If not explicitly defined, the python class name is used. 24 | bl_idname = 'CustomSocketType' 25 | # Label for nice name display 26 | bl_label = "Custom Node Socket" 27 | 28 | # Enum items list 29 | my_items = ( 30 | ('DOWN', "Down", "Where your feet are"), 31 | ('UP', "Up", "Where your head should be"), 32 | ('LEFT', "Left", "Not right"), 33 | ('RIGHT', "Right", "Not left"), 34 | ) 35 | 36 | my_enum_prop: bpy.props.EnumProperty( 37 | name="Direction", 38 | description="Just an example", 39 | items=my_items, 40 | default='UP', 41 | ) 42 | 43 | # Optional function for drawing the socket input value 44 | def draw(self, context, layout, node, text): 45 | if self.is_output or self.is_linked: 46 | layout.label(text=text) 47 | else: 48 | layout.prop(self, "my_enum_prop", text=text) 49 | 50 | # Socket color 51 | def draw_color(self, context, node): 52 | return (1.0, 0.4, 0.216, 0.5) 53 | 54 | 55 | # Mix-in class for all custom nodes in this tree type. 56 | # Defines a poll function to enable instantiation. 57 | class MyCustomTreeNode: 58 | @classmethod 59 | def poll(cls, ntree): 60 | return ntree.bl_idname == 'CustomTreeType' 61 | 62 | 63 | # Derived from the Node base type. 64 | class MyCustomNode(Node, MyCustomTreeNode): 65 | # === Basics === 66 | # Description string 67 | '''A custom node''' 68 | # Optional identifier string. If not explicitly defined, the python class name is used. 69 | bl_idname = 'CustomNodeType' 70 | # Label for nice name display 71 | bl_label = "Custom Node" 72 | # Icon identifier 73 | bl_icon = 'SOUND' 74 | 75 | # === Custom Properties === 76 | # These work just like custom properties in ID data blocks 77 | # Extensive information can be found under 78 | # http://wiki.blender.org/index.php/Doc:2.6/Manual/Extensions/Python/Properties 79 | my_string_prop: bpy.props.StringProperty() 80 | my_float_prop: bpy.props.FloatProperty(default=3.1415926) 81 | 82 | # === Optional Functions === 83 | # Initialization function, called when a new node is created. 84 | # This is the most common place to create the sockets for a node, as shown below. 85 | # NOTE: this is not the same as the standard __init__ function in Python, which is 86 | # a purely internal Python method and unknown to the node system! 87 | def init(self, context): 88 | self.inputs.new('CustomSocketType', "Hello") 89 | self.inputs.new('NodeSocketFloat', "World") 90 | self.inputs.new('NodeSocketVector', "!") 91 | 92 | self.outputs.new('NodeSocketColor', "How") 93 | self.outputs.new('NodeSocketColor', "are") 94 | self.outputs.new('NodeSocketFloat', "you") 95 | 96 | # Copy function to initialize a copied node from an existing one. 97 | def copy(self, node): 98 | print("Copying from node ", node) 99 | 100 | # Free function to clean up on removal. 101 | def free(self): 102 | print("Removing node ", self, ", Goodbye!") 103 | 104 | # Additional buttons displayed on the node. 105 | def draw_buttons(self, context, layout): 106 | layout.label(text="Node settings") 107 | layout.prop(self, "my_float_prop") 108 | 109 | # Detail buttons in the sidebar. 110 | # If this function is not defined, the draw_buttons function is used instead 111 | def draw_buttons_ext(self, context, layout): 112 | layout.prop(self, "my_float_prop") 113 | # my_string_prop button will only be visible in the sidebar 114 | layout.prop(self, "my_string_prop") 115 | 116 | # Optional: custom label 117 | # Explicit user label overrides this, but here we can define a label dynamically 118 | def draw_label(self): 119 | return "I am a custom node" 120 | 121 | 122 | ### Node Categories ### 123 | # Node categories are a python system for automatically 124 | # extending the Add menu, toolbar panels and search operator. 125 | # For more examples see release/scripts/startup/nodeitems_builtins.py 126 | 127 | import nodeitems_utils 128 | from nodeitems_utils import NodeCategory, NodeItem 129 | 130 | # our own base class with an appropriate poll function, 131 | # so the categories only show up in our own tree type 132 | 133 | 134 | class MyNodeCategory(NodeCategory): 135 | @classmethod 136 | def poll(cls, context): 137 | return context.space_data.tree_type == 'CustomTreeType' 138 | 139 | 140 | # all categories in a list 141 | node_categories = [ 142 | # identifier, label, items list 143 | MyNodeCategory('SOMENODES', "Some Nodes", items=[ 144 | # our basic node 145 | NodeItem("CustomNodeType"), 146 | ]), 147 | MyNodeCategory('OTHERNODES', "Other Nodes", items=[ 148 | # the node item can have additional settings, 149 | # which are applied to new nodes 150 | # NB: settings values are stored as string expressions, 151 | # for this reason they should be converted to strings using repr() 152 | NodeItem("CustomNodeType", label="Node A", settings={ 153 | "my_string_prop": repr("Lorem ipsum dolor sit amet"), 154 | "my_float_prop": repr(1.0), 155 | }), 156 | NodeItem("CustomNodeType", label="Node B", settings={ 157 | "my_string_prop": repr("consectetur adipisicing elit"), 158 | "my_float_prop": repr(2.0), 159 | }), 160 | ]), 161 | ] 162 | 163 | classes = ( 164 | MyCustomTree, 165 | MyCustomSocket, 166 | MyCustomNode, 167 | ) 168 | 169 | 170 | def register(): 171 | from bpy.utils import register_class 172 | for cls in classes: 173 | register_class(cls) 174 | 175 | nodeitems_utils.register_node_categories('CUSTOM_NODES', node_categories) 176 | 177 | 178 | def unregister(): 179 | nodeitems_utils.unregister_node_categories('CUSTOM_NODES') 180 | 181 | from bpy.utils import unregister_class 182 | for cls in reversed(classes): 183 | unregister_class(cls) 184 | 185 | 186 | if __name__ == "__main__": 187 | register() 188 | -------------------------------------------------------------------------------- /Blender_Python_Templates/driver_functions.py: -------------------------------------------------------------------------------- 1 | # This script defines functions to be used directly in drivers expressions to 2 | # extend the builtin set of python functions. 3 | # 4 | # This can be executed on manually or set to 'Register' to 5 | # initialize thefunctions on file load. 6 | 7 | 8 | # two sample functions 9 | def invert(f): 10 | """ Simple function call: 11 | 12 | invert(val) 13 | """ 14 | return 1.0 - f 15 | 16 | 17 | uuid_store = {} 18 | 19 | 20 | def slow_value(value, fac, uuid): 21 | """ Delay the value by a factor, use a unique string to allow 22 | use in multiple drivers without conflict: 23 | 24 | slow_value(val, 0.5, "my_value") 25 | """ 26 | value_prev = uuid_store.get(uuid, value) 27 | uuid_store[uuid] = value_new = (value_prev * fac) + (value * (1.0 - fac)) 28 | return value_new 29 | 30 | 31 | import bpy 32 | 33 | # Add variable defined in this script into the drivers namespace. 34 | bpy.app.driver_namespace["invert"] = invert 35 | bpy.app.driver_namespace["slow_value"] = slow_value 36 | -------------------------------------------------------------------------------- /Blender_Python_Templates/external_script_stub.py: -------------------------------------------------------------------------------- 1 | # This stub runs a python script relative to the currently open 2 | # blend file, useful when editing scripts externally. 3 | 4 | import bpy 5 | import os 6 | 7 | # Use your own script name here: 8 | filename = "my_script.py" 9 | 10 | filepath = os.path.join(os.path.dirname(bpy.data.filepath), filename) 11 | global_namespace = {"__file__": filepath, "__name__": "__main__"} 12 | with open(filepath, 'rb') as file: 13 | exec(compile(file.read(), filepath, 'exec'), global_namespace) 14 | -------------------------------------------------------------------------------- /Blender_Python_Templates/gizmo_custom_geometry.py: -------------------------------------------------------------------------------- 1 | # Example of a custom widget that defines it's own geometry. 2 | # 3 | # Usage: Select a light in the 3D view and drag the arrow at it's rear 4 | # to change it's energy value. 5 | # 6 | import bpy 7 | from bpy.types import ( 8 | Gizmo, 9 | GizmoGroup, 10 | ) 11 | 12 | # Coordinates (each one is a triangle). 13 | custom_shape_verts = ( 14 | (3.0, 1.0, -1.0), (2.0, 2.0, -1.0), (3.0, 3.0, -1.0), 15 | (1.0, 3.0, 1.0), (3.0, 3.0, -1.0), (1.0, 3.0, -1.0), 16 | (3.0, 3.0, 1.0), (3.0, 1.0, -1.0), (3.0, 3.0, -1.0), 17 | (2.0, 0.0, 1.0), (3.0, 1.0, -1.0), (3.0, 1.0, 1.0), 18 | (2.0, 0.0, -1.0), (2.0, 2.0, 1.0), (2.0, 2.0, -1.0), 19 | (2.0, 2.0, -1.0), (0.0, 2.0, 1.0), (0.0, 2.0, -1.0), 20 | (1.0, 3.0, 1.0), (2.0, 2.0, 1.0), (3.0, 3.0, 1.0), 21 | (0.0, 2.0, -1.0), (1.0, 3.0, 1.0), (1.0, 3.0, -1.0), 22 | (2.0, 2.0, 1.0), (3.0, 1.0, 1.0), (3.0, 3.0, 1.0), 23 | (2.0, 2.0, -1.0), (1.0, 3.0, -1.0), (3.0, 3.0, -1.0), 24 | (-3.0, -1.0, -1.0), (-2.0, -2.0, -1.0), (-3.0, -3.0, -1.0), 25 | (-1.0, -3.0, 1.0), (-3.0, -3.0, -1.0), (-1.0, -3.0, -1.0), 26 | (-3.0, -3.0, 1.0), (-3.0, -1.0, -1.0), (-3.0, -3.0, -1.0), 27 | (-2.0, 0.0, 1.0), (-3.0, -1.0, -1.0), (-3.0, -1.0, 1.0), 28 | (-2.0, 0.0, -1.0), (-2.0, -2.0, 1.0), (-2.0, -2.0, -1.0), 29 | (-2.0, -2.0, -1.0), (0.0, -2.0, 1.0), (0.0, -2.0, -1.0), 30 | (-1.0, -3.0, 1.0), (-2.0, -2.0, 1.0), (-3.0, -3.0, 1.0), 31 | (0.0, -2.0, -1.0), (-1.0, -3.0, 1.0), (-1.0, -3.0, -1.0), 32 | (-2.0, -2.0, 1.0), (-3.0, -1.0, 1.0), (-3.0, -3.0, 1.0), 33 | (-2.0, -2.0, -1.0), (-1.0, -3.0, -1.0), (-3.0, -3.0, -1.0), 34 | (1.0, -1.0, 0.0), (-1.0, -1.0, 0.0), (0.0, 0.0, -5.0), 35 | (-1.0, -1.0, 0.0), (1.0, -1.0, 0.0), (0.0, 0.0, 5.0), 36 | (1.0, -1.0, 0.0), (1.0, 1.0, 0.0), (0.0, 0.0, 5.0), 37 | (1.0, 1.0, 0.0), (-1.0, 1.0, 0.0), (0.0, 0.0, 5.0), 38 | (-1.0, 1.0, 0.0), (-1.0, -1.0, 0.0), (0.0, 0.0, 5.0), 39 | (-1.0, -1.0, 0.0), (-1.0, 1.0, 0.0), (0.0, 0.0, -5.0), 40 | (-1.0, 1.0, 0.0), (1.0, 1.0, 0.0), (0.0, 0.0, -5.0), 41 | (1.0, 1.0, 0.0), (1.0, -1.0, 0.0), (0.0, 0.0, -5.0), 42 | (3.0, 1.0, -1.0), (2.0, 0.0, -1.0), (2.0, 2.0, -1.0), 43 | (1.0, 3.0, 1.0), (3.0, 3.0, 1.0), (3.0, 3.0, -1.0), 44 | (3.0, 3.0, 1.0), (3.0, 1.0, 1.0), (3.0, 1.0, -1.0), 45 | (2.0, 0.0, 1.0), (2.0, 0.0, -1.0), (3.0, 1.0, -1.0), 46 | (2.0, 0.0, -1.0), (2.0, 0.0, 1.0), (2.0, 2.0, 1.0), 47 | (2.0, 2.0, -1.0), (2.0, 2.0, 1.0), (0.0, 2.0, 1.0), 48 | (1.0, 3.0, 1.0), (0.0, 2.0, 1.0), (2.0, 2.0, 1.0), 49 | (0.0, 2.0, -1.0), (0.0, 2.0, 1.0), (1.0, 3.0, 1.0), 50 | (2.0, 2.0, 1.0), (2.0, 0.0, 1.0), (3.0, 1.0, 1.0), 51 | (2.0, 2.0, -1.0), (0.0, 2.0, -1.0), (1.0, 3.0, -1.0), 52 | (-3.0, -1.0, -1.0), (-2.0, 0.0, -1.0), (-2.0, -2.0, -1.0), 53 | (-1.0, -3.0, 1.0), (-3.0, -3.0, 1.0), (-3.0, -3.0, -1.0), 54 | (-3.0, -3.0, 1.0), (-3.0, -1.0, 1.0), (-3.0, -1.0, -1.0), 55 | (-2.0, 0.0, 1.0), (-2.0, 0.0, -1.0), (-3.0, -1.0, -1.0), 56 | (-2.0, 0.0, -1.0), (-2.0, 0.0, 1.0), (-2.0, -2.0, 1.0), 57 | (-2.0, -2.0, -1.0), (-2.0, -2.0, 1.0), (0.0, -2.0, 1.0), 58 | (-1.0, -3.0, 1.0), (0.0, -2.0, 1.0), (-2.0, -2.0, 1.0), 59 | (0.0, -2.0, -1.0), (0.0, -2.0, 1.0), (-1.0, -3.0, 1.0), 60 | (-2.0, -2.0, 1.0), (-2.0, 0.0, 1.0), (-3.0, -1.0, 1.0), 61 | (-2.0, -2.0, -1.0), (0.0, -2.0, -1.0), (-1.0, -3.0, -1.0), 62 | ) 63 | 64 | 65 | class MyCustomShapeWidget(Gizmo): 66 | bl_idname = "VIEW3D_GT_auto_facemap" 67 | bl_target_properties = ( 68 | {"id": "offset", "type": 'FLOAT', "array_length": 1}, 69 | ) 70 | 71 | __slots__ = ( 72 | "custom_shape", 73 | "init_mouse_y", 74 | "init_value", 75 | ) 76 | 77 | def _update_offset_matrix(self): 78 | # offset behind the light 79 | self.matrix_offset.col[3][2] = self.target_get_value("offset") / -10.0 80 | 81 | def draw(self, context): 82 | self._update_offset_matrix() 83 | self.draw_custom_shape(self.custom_shape) 84 | 85 | def draw_select(self, context, select_id): 86 | self._update_offset_matrix() 87 | self.draw_custom_shape(self.custom_shape, select_id=select_id) 88 | 89 | def setup(self): 90 | if not hasattr(self, "custom_shape"): 91 | self.custom_shape = self.new_custom_shape('TRIS', custom_shape_verts) 92 | 93 | def invoke(self, context, event): 94 | self.init_mouse_y = event.mouse_y 95 | self.init_value = self.target_get_value("offset") 96 | return {'RUNNING_MODAL'} 97 | 98 | def exit(self, context, cancel): 99 | context.area.header_text_set(None) 100 | if cancel: 101 | self.target_set_value("offset", self.init_value) 102 | 103 | def modal(self, context, event, tweak): 104 | delta = (event.mouse_y - self.init_mouse_y) / 10.0 105 | if 'SNAP' in tweak: 106 | delta = round(delta) 107 | if 'PRECISE' in tweak: 108 | delta /= 10.0 109 | value = self.init_value + delta 110 | self.target_set_value("offset", value) 111 | context.area.header_text_set("My Gizmo: %.4f" % value) 112 | return {'RUNNING_MODAL'} 113 | 114 | 115 | class MyCustomShapeWidgetGroup(GizmoGroup): 116 | bl_idname = "OBJECT_GGT_light_test" 117 | bl_label = "Test Light Widget" 118 | bl_space_type = 'VIEW_3D' 119 | bl_region_type = 'WINDOW' 120 | bl_options = {'3D', 'PERSISTENT'} 121 | 122 | @classmethod 123 | def poll(cls, context): 124 | ob = context.object 125 | return (ob and ob.type == 'LIGHT') 126 | 127 | def setup(self, context): 128 | # Assign the 'offset' target property to the light energy. 129 | ob = context.object 130 | mpr = self.gizmos.new(MyCustomShapeWidget.bl_idname) 131 | mpr.target_set_prop("offset", ob.data, "energy") 132 | 133 | mpr.color = 1.0, 0.5, 1.0 134 | mpr.alpha = 0.5 135 | 136 | mpr.color_highlight = 1.0, 1.0, 1.0 137 | mpr.alpha_highlight = 0.5 138 | 139 | # units are large, so shrink to something more reasonable. 140 | mpr.scale_basis = 0.1 141 | mpr.use_draw_modal = True 142 | 143 | self.energy_widget = mpr 144 | 145 | def refresh(self, context): 146 | ob = context.object 147 | mpr = self.energy_widget 148 | mpr.matrix_basis = ob.matrix_world.normalized() 149 | 150 | 151 | classes = ( 152 | MyCustomShapeWidget, 153 | MyCustomShapeWidgetGroup, 154 | ) 155 | 156 | for cls in classes: 157 | bpy.utils.register_class(cls) 158 | -------------------------------------------------------------------------------- /Blender_Python_Templates/gizmo_operator.py: -------------------------------------------------------------------------------- 1 | # Example of an operator which uses gizmos to control its properties. 2 | # 3 | # Usage: Run this script, then in mesh edit-mode press F3 4 | # to activate the operator "Select Side of Plane" 5 | # The gizmos can then be used to adjust the plane in the 3D view. 6 | # 7 | import bpy 8 | import bmesh 9 | 10 | from bpy.types import ( 11 | Operator, 12 | GizmoGroup, 13 | ) 14 | 15 | from bpy.props import ( 16 | FloatVectorProperty, 17 | ) 18 | 19 | 20 | def main(context, plane_co, plane_no): 21 | obj = context.active_object 22 | matrix = obj.matrix_world.copy() 23 | me = obj.data 24 | bm = bmesh.from_edit_mesh(me) 25 | 26 | plane_dot = plane_no.dot(plane_co) 27 | 28 | for v in bm.verts: 29 | co = matrix @ v.co 30 | v.select = (plane_no.dot(co) > plane_dot) 31 | bm.select_flush_mode() 32 | 33 | bmesh.update_edit_mesh(me) 34 | 35 | 36 | class SelectSideOfPlane(Operator): 37 | """UV Operator description""" 38 | bl_idname = "mesh.select_side_of_plane" 39 | bl_label = "Select Side of Plane" 40 | bl_options = {'REGISTER', 'UNDO'} 41 | 42 | plane_co: FloatVectorProperty( 43 | size=3, 44 | default=(0, 0, 0), 45 | ) 46 | plane_no: FloatVectorProperty( 47 | size=3, 48 | default=(0, 0, 1), 49 | ) 50 | 51 | @classmethod 52 | def poll(cls, context): 53 | return (context.mode == 'EDIT_MESH') 54 | 55 | def invoke(self, context, event): 56 | 57 | if not self.properties.is_property_set("plane_co"): 58 | self.plane_co = context.scene.cursor_location 59 | 60 | if not self.properties.is_property_set("plane_no"): 61 | if context.space_data.type == 'VIEW_3D': 62 | rv3d = context.space_data.region_3d 63 | view_inv = rv3d.view_matrix.to_3x3() 64 | # view y axis 65 | self.plane_no = view_inv[1].normalized() 66 | 67 | self.execute(context) 68 | 69 | if context.space_data.type == 'VIEW_3D': 70 | wm = context.window_manager 71 | wm.gizmo_group_type_ensure(SelectSideOfPlaneGizmoGroup.bl_idname) 72 | 73 | return {'FINISHED'} 74 | 75 | def execute(self, context): 76 | from mathutils import Vector 77 | main(context, Vector(self.plane_co), Vector(self.plane_no)) 78 | return {'FINISHED'} 79 | 80 | 81 | # Gizmos for plane_co, plane_no 82 | class SelectSideOfPlaneGizmoGroup(GizmoGroup): 83 | bl_idname = "MESH_GGT_select_side_of_plane" 84 | bl_label = "Side of Plane Gizmo" 85 | bl_space_type = 'VIEW_3D' 86 | bl_region_type = 'WINDOW' 87 | bl_options = {'3D'} 88 | 89 | # Helper functions 90 | @staticmethod 91 | def my_target_operator(context): 92 | wm = context.window_manager 93 | op = wm.operators[-1] if wm.operators else None 94 | if isinstance(op, SelectSideOfPlane): 95 | return op 96 | return None 97 | 98 | @staticmethod 99 | def my_view_orientation(context): 100 | rv3d = context.space_data.region_3d 101 | view_inv = rv3d.view_matrix.to_3x3() 102 | return view_inv.normalized() 103 | 104 | @classmethod 105 | def poll(cls, context): 106 | op = cls.my_target_operator(context) 107 | if op is None: 108 | wm = context.window_manager 109 | wm.gizmo_group_type_unlink_delayed(SelectSideOfPlaneGizmoGroup.bl_idname) 110 | return False 111 | return True 112 | 113 | def setup(self, context): 114 | from mathutils import Matrix, Vector 115 | 116 | # ---- 117 | # Move 118 | 119 | def move_get_cb(): 120 | op = SelectSideOfPlaneGizmoGroup.my_target_operator(context) 121 | return op.plane_co 122 | 123 | def move_set_cb(value): 124 | op = SelectSideOfPlaneGizmoGroup.my_target_operator(context) 125 | op.plane_co = value 126 | # XXX, this may change! 127 | op.execute(context) 128 | 129 | mpr = self.gizmos.new("GIZMO_GT_move_3d") 130 | mpr.target_set_handler("offset", get=move_get_cb, set=move_set_cb) 131 | 132 | mpr.use_draw_value = True 133 | 134 | mpr.color = 0.8, 0.8, 0.8 135 | mpr.alpha = 0.5 136 | 137 | mpr.color_highlight = 1.0, 1.0, 1.0 138 | mpr.alpha_highlight = 1.0 139 | 140 | mpr.scale_basis = 0.2 141 | 142 | self.widget_move = mpr 143 | 144 | # ---- 145 | # Dial 146 | 147 | def direction_get_cb(): 148 | op = SelectSideOfPlaneGizmoGroup.my_target_operator(context) 149 | 150 | no_a = self.widget_dial.matrix_basis.col[1].xyz 151 | no_b = Vector(op.plane_no) 152 | 153 | no_a = (no_a @ self.view_inv).xy.normalized() 154 | no_b = (no_b @ self.view_inv).xy.normalized() 155 | return no_a.angle_signed(no_b) 156 | 157 | def direction_set_cb(value): 158 | op = SelectSideOfPlaneGizmoGroup.my_target_operator(context) 159 | matrix_rotate = Matrix.Rotation(-value, 3, self.rotate_axis) 160 | no = matrix_rotate @ self.widget_dial.matrix_basis.col[1].xyz 161 | op.plane_no = no 162 | op.execute(context) 163 | 164 | mpr = self.gizmos.new("GIZMO_GT_dial_3d") 165 | mpr.target_set_handler("offset", get=direction_get_cb, set=direction_set_cb) 166 | mpr.draw_options = {'ANGLE_START_Y'} 167 | 168 | mpr.use_draw_value = True 169 | 170 | mpr.color = 0.8, 0.8, 0.8 171 | mpr.alpha = 0.5 172 | 173 | mpr.color_highlight = 1.0, 1.0, 1.0 174 | mpr.alpha_highlight = 1.0 175 | 176 | self.widget_dial = mpr 177 | 178 | def draw_prepare(self, context): 179 | from mathutils import Vector 180 | 181 | view_inv = self.my_view_orientation(context) 182 | 183 | self.view_inv = view_inv 184 | self.rotate_axis = view_inv[2].xyz 185 | self.rotate_up = view_inv[1].xyz 186 | 187 | op = self.my_target_operator(context) 188 | 189 | co = Vector(op.plane_co) 190 | no = Vector(op.plane_no).normalized() 191 | 192 | # Move 193 | no_z = no 194 | no_y = no_z.orthogonal() 195 | no_x = no_z.cross(no_y) 196 | 197 | matrix = self.widget_move.matrix_basis 198 | matrix.identity() 199 | matrix.col[0].xyz = no_x 200 | matrix.col[1].xyz = no_y 201 | matrix.col[2].xyz = no_z 202 | matrix.col[3].xyz = co 203 | 204 | # Dial 205 | no_z = self.rotate_axis 206 | no_y = (no - (no.project(no_z))).normalized() 207 | no_x = self.rotate_axis.cross(no_y) 208 | 209 | matrix = self.widget_dial.matrix_basis 210 | matrix.identity() 211 | matrix.col[0].xyz = no_x 212 | matrix.col[1].xyz = no_y 213 | matrix.col[2].xyz = no_z 214 | matrix.col[3].xyz = co 215 | 216 | 217 | classes = ( 218 | SelectSideOfPlane, 219 | SelectSideOfPlaneGizmoGroup, 220 | ) 221 | 222 | 223 | def register(): 224 | for cls in classes: 225 | bpy.utils.register_class(cls) 226 | 227 | 228 | def unregister(): 229 | for cls in reversed(classes): 230 | bpy.utils.unregister_class(cls) 231 | 232 | 233 | if __name__ == "__main__": 234 | register() 235 | -------------------------------------------------------------------------------- /Blender_Python_Templates/gizmo_operator_target.py: -------------------------------------------------------------------------------- 1 | # Example of a gizmo that activates an operator 2 | # using the predefined dial gizmo to change the camera roll. 3 | # 4 | # Usage: Run this script and select a camera in the 3D view. 5 | # 6 | import bpy 7 | from bpy.types import ( 8 | GizmoGroup, 9 | ) 10 | 11 | 12 | class MyCameraWidgetGroup(GizmoGroup): 13 | bl_idname = "OBJECT_GGT_test_camera" 14 | bl_label = "Object Camera Test Widget" 15 | bl_space_type = 'VIEW_3D' 16 | bl_region_type = 'WINDOW' 17 | bl_options = {'3D', 'PERSISTENT'} 18 | 19 | @classmethod 20 | def poll(cls, context): 21 | ob = context.object 22 | return (ob and ob.type == 'CAMERA') 23 | 24 | def setup(self, context): 25 | # Run an operator using the dial gizmo 26 | ob = context.object 27 | mpr = self.gizmos.new("GIZMO_GT_dial_3d") 28 | props = mpr.target_set_operator("transform.rotate") 29 | props.constraint_axis = False, False, True 30 | props.constraint_orientation = 'LOCAL' 31 | props.release_confirm = True 32 | 33 | mpr.matrix_basis = ob.matrix_world.normalized() 34 | mpr.line_width = 3 35 | 36 | mpr.color = 0.8, 0.8, 0.8 37 | mpr.alpha = 0.5 38 | 39 | mpr.color_highlight = 1.0, 1.0, 1.0 40 | mpr.alpha_highlight = 1.0 41 | 42 | self.roll_widget = mpr 43 | 44 | def refresh(self, context): 45 | ob = context.object 46 | mpr = self.roll_widget 47 | mpr.matrix_basis = ob.matrix_world.normalized() 48 | 49 | 50 | bpy.utils.register_class(MyCameraWidgetGroup) 51 | -------------------------------------------------------------------------------- /Blender_Python_Templates/gizmo_simple.py: -------------------------------------------------------------------------------- 1 | # Example of a group that edits a single property 2 | # using the predefined gizmo arrow. 3 | # 4 | # Usage: Select a light in the 3D view and drag the arrow at it's rear 5 | # to change it's energy value. 6 | # 7 | import bpy 8 | from bpy.types import ( 9 | GizmoGroup, 10 | ) 11 | 12 | 13 | class MyLightWidgetGroup(GizmoGroup): 14 | bl_idname = "OBJECT_GGT_light_test" 15 | bl_label = "Test Light Widget" 16 | bl_space_type = 'VIEW_3D' 17 | bl_region_type = 'WINDOW' 18 | bl_options = {'3D', 'PERSISTENT'} 19 | 20 | @classmethod 21 | def poll(cls, context): 22 | ob = context.object 23 | return (ob and ob.type == 'LIGHT') 24 | 25 | def setup(self, context): 26 | # Arrow gizmo has one 'offset' property we can assign to the light energy. 27 | ob = context.object 28 | mpr = self.gizmos.new("GIZMO_GT_arrow_3d") 29 | mpr.target_set_prop("offset", ob.data, "energy") 30 | mpr.matrix_basis = ob.matrix_world.normalized() 31 | mpr.draw_style = 'BOX' 32 | 33 | mpr.color = 1.0, 0.5, 0.0 34 | mpr.alpha = 0.5 35 | 36 | mpr.color_highlight = 1.0, 0.5, 1.0 37 | mpr.alpha_highlight = 0.5 38 | 39 | self.energy_widget = mpr 40 | 41 | def refresh(self, context): 42 | ob = context.object 43 | mpr = self.energy_widget 44 | mpr.matrix_basis = ob.matrix_world.normalized() 45 | 46 | 47 | bpy.utils.register_class(MyLightWidgetGroup) 48 | -------------------------------------------------------------------------------- /Blender_Python_Templates/operator_file_export.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | 4 | def write_some_data(context, filepath, use_some_setting): 5 | print("running write_some_data...") 6 | f = open(filepath, 'w', encoding='utf-8') 7 | f.write("Hello World %s" % use_some_setting) 8 | f.close() 9 | 10 | return {'FINISHED'} 11 | 12 | 13 | # ExportHelper is a helper class, defines filename and 14 | # invoke() function which calls the file selector. 15 | from bpy_extras.io_utils import ExportHelper 16 | from bpy.props import StringProperty, BoolProperty, EnumProperty 17 | from bpy.types import Operator 18 | 19 | 20 | class ExportSomeData(Operator, ExportHelper): 21 | """This appears in the tooltip of the operator and in the generated docs""" 22 | bl_idname = "export_test.some_data" # important since its how bpy.ops.import_test.some_data is constructed 23 | bl_label = "Export Some Data" 24 | 25 | # ExportHelper mixin class uses this 26 | filename_ext = ".txt" 27 | 28 | filter_glob: StringProperty( 29 | default="*.txt", 30 | options={'HIDDEN'}, 31 | maxlen=255, # Max internal buffer length, longer would be clamped. 32 | ) 33 | 34 | # List of operator properties, the attributes will be assigned 35 | # to the class instance from the operator settings before calling. 36 | use_setting: BoolProperty( 37 | name="Example Boolean", 38 | description="Example Tooltip", 39 | default=True, 40 | ) 41 | 42 | type: EnumProperty( 43 | name="Example Enum", 44 | description="Choose between two items", 45 | items=( 46 | ('OPT_A', "First Option", "Description one"), 47 | ('OPT_B', "Second Option", "Description two"), 48 | ), 49 | default='OPT_A', 50 | ) 51 | 52 | def execute(self, context): 53 | return write_some_data(context, self.filepath, self.use_setting) 54 | 55 | 56 | # Only needed if you want to add into a dynamic menu 57 | def menu_func_export(self, context): 58 | self.layout.operator(ExportSomeData.bl_idname, text="Text Export Operator") 59 | 60 | 61 | def register(): 62 | bpy.utils.register_class(ExportSomeData) 63 | bpy.types.TOPBAR_MT_file_export.append(menu_func_export) 64 | 65 | 66 | def unregister(): 67 | bpy.utils.unregister_class(ExportSomeData) 68 | bpy.types.TOPBAR_MT_file_export.remove(menu_func_export) 69 | 70 | 71 | if __name__ == "__main__": 72 | register() 73 | 74 | # test call 75 | bpy.ops.export_test.some_data('INVOKE_DEFAULT') 76 | -------------------------------------------------------------------------------- /Blender_Python_Templates/operator_file_import.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | 4 | def read_some_data(context, filepath, use_some_setting): 5 | print("running read_some_data...") 6 | f = open(filepath, 'r', encoding='utf-8') 7 | data = f.read() 8 | f.close() 9 | 10 | # would normally load the data here 11 | print(data) 12 | 13 | return {'FINISHED'} 14 | 15 | 16 | # ImportHelper is a helper class, defines filename and 17 | # invoke() function which calls the file selector. 18 | from bpy_extras.io_utils import ImportHelper 19 | from bpy.props import StringProperty, BoolProperty, EnumProperty 20 | from bpy.types import Operator 21 | 22 | 23 | class ImportSomeData(Operator, ImportHelper): 24 | """This appears in the tooltip of the operator and in the generated docs""" 25 | bl_idname = "import_test.some_data" # important since its how bpy.ops.import_test.some_data is constructed 26 | bl_label = "Import Some Data" 27 | 28 | # ImportHelper mixin class uses this 29 | filename_ext = ".txt" 30 | 31 | filter_glob: StringProperty( 32 | default="*.txt", 33 | options={'HIDDEN'}, 34 | maxlen=255, # Max internal buffer length, longer would be clamped. 35 | ) 36 | 37 | # List of operator properties, the attributes will be assigned 38 | # to the class instance from the operator settings before calling. 39 | use_setting: BoolProperty( 40 | name="Example Boolean", 41 | description="Example Tooltip", 42 | default=True, 43 | ) 44 | 45 | type: EnumProperty( 46 | name="Example Enum", 47 | description="Choose between two items", 48 | items=( 49 | ('OPT_A', "First Option", "Description one"), 50 | ('OPT_B', "Second Option", "Description two"), 51 | ), 52 | default='OPT_A', 53 | ) 54 | 55 | def execute(self, context): 56 | return read_some_data(context, self.filepath, self.use_setting) 57 | 58 | 59 | # Only needed if you want to add into a dynamic menu 60 | def menu_func_import(self, context): 61 | self.layout.operator(ImportSomeData.bl_idname, text="Text Import Operator") 62 | 63 | 64 | def register(): 65 | bpy.utils.register_class(ImportSomeData) 66 | bpy.types.TOPBAR_MT_file_import.append(menu_func_import) 67 | 68 | 69 | def unregister(): 70 | bpy.utils.unregister_class(ImportSomeData) 71 | bpy.types.TOPBAR_MT_file_import.remove(menu_func_import) 72 | 73 | 74 | if __name__ == "__main__": 75 | register() 76 | 77 | # test call 78 | bpy.ops.import_test.some_data('INVOKE_DEFAULT') 79 | -------------------------------------------------------------------------------- /Blender_Python_Templates/operator_mesh_add.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import bmesh 3 | 4 | 5 | def add_box(width, height, depth): 6 | """ 7 | This function takes inputs and returns vertex and face arrays. 8 | no actual mesh data creation is done here. 9 | """ 10 | 11 | verts = [ 12 | (+1.0, +1.0, -1.0), 13 | (+1.0, -1.0, -1.0), 14 | (-1.0, -1.0, -1.0), 15 | (-1.0, +1.0, -1.0), 16 | (+1.0, +1.0, +1.0), 17 | (+1.0, -1.0, +1.0), 18 | (-1.0, -1.0, +1.0), 19 | (-1.0, +1.0, +1.0), 20 | ] 21 | 22 | faces = [ 23 | (0, 1, 2, 3), 24 | (4, 7, 6, 5), 25 | (0, 4, 5, 1), 26 | (1, 5, 6, 2), 27 | (2, 6, 7, 3), 28 | (4, 0, 3, 7), 29 | ] 30 | 31 | # apply size 32 | for i, v in enumerate(verts): 33 | verts[i] = v[0] * width, v[1] * depth, v[2] * height 34 | 35 | return verts, faces 36 | 37 | 38 | from bpy.props import ( 39 | BoolProperty, 40 | BoolVectorProperty, 41 | FloatProperty, 42 | FloatVectorProperty, 43 | ) 44 | 45 | 46 | class AddBox(bpy.types.Operator): 47 | """Add a simple box mesh""" 48 | bl_idname = "mesh.primitive_box_add" 49 | bl_label = "Add Box" 50 | bl_options = {'REGISTER', 'UNDO'} 51 | 52 | width: FloatProperty( 53 | name="Width", 54 | description="Box Width", 55 | min=0.01, max=100.0, 56 | default=1.0, 57 | ) 58 | height: FloatProperty( 59 | name="Height", 60 | description="Box Height", 61 | min=0.01, max=100.0, 62 | default=1.0, 63 | ) 64 | depth: FloatProperty( 65 | name="Depth", 66 | description="Box Depth", 67 | min=0.01, max=100.0, 68 | default=1.0, 69 | ) 70 | layers: BoolVectorProperty( 71 | name="Layers", 72 | description="Object Layers", 73 | size=20, 74 | options={'HIDDEN', 'SKIP_SAVE'}, 75 | ) 76 | 77 | # generic transform props 78 | view_align: BoolProperty( 79 | name="Align to View", 80 | default=False, 81 | ) 82 | location: FloatVectorProperty( 83 | name="Location", 84 | subtype='TRANSLATION', 85 | ) 86 | rotation: FloatVectorProperty( 87 | name="Rotation", 88 | subtype='EULER', 89 | ) 90 | 91 | def execute(self, context): 92 | 93 | verts_loc, faces = add_box( 94 | self.width, 95 | self.height, 96 | self.depth, 97 | ) 98 | 99 | mesh = bpy.data.meshes.new("Box") 100 | 101 | bm = bmesh.new() 102 | 103 | for v_co in verts_loc: 104 | bm.verts.new(v_co) 105 | 106 | bm.verts.ensure_lookup_table() 107 | for f_idx in faces: 108 | bm.faces.new([bm.verts[i] for i in f_idx]) 109 | 110 | bm.to_mesh(mesh) 111 | mesh.update() 112 | 113 | # add the mesh as an object into the scene with this utility module 114 | from bpy_extras import object_utils 115 | object_utils.object_data_add(context, mesh, operator=self) 116 | 117 | return {'FINISHED'} 118 | 119 | 120 | def menu_func(self, context): 121 | self.layout.operator(AddBox.bl_idname, icon='MESH_CUBE') 122 | 123 | 124 | def register(): 125 | bpy.utils.register_class(AddBox) 126 | bpy.types.VIEW3D_MT_mesh_add.append(menu_func) 127 | 128 | 129 | def unregister(): 130 | bpy.utils.unregister_class(AddBox) 131 | bpy.types.VIEW3D_MT_mesh_add.remove(menu_func) 132 | 133 | 134 | if __name__ == "__main__": 135 | register() 136 | 137 | # test call 138 | bpy.ops.mesh.primitive_box_add() 139 | -------------------------------------------------------------------------------- /Blender_Python_Templates/operator_mesh_uv.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import bmesh 3 | 4 | 5 | def main(context): 6 | obj = context.active_object 7 | me = obj.data 8 | bm = bmesh.from_edit_mesh(me) 9 | 10 | uv_layer = bm.loops.layers.uv.verify() 11 | 12 | # adjust UVs 13 | for f in bm.faces: 14 | for l in f.loops: 15 | luv = l[uv_layer] 16 | if luv.select: 17 | # apply the location of the vertex as a UV 18 | luv.uv = l.vert.co.xy 19 | 20 | bmesh.update_edit_mesh(me) 21 | 22 | 23 | class UvOperator(bpy.types.Operator): 24 | """UV Operator description""" 25 | bl_idname = "uv.simple_operator" 26 | bl_label = "Simple UV Operator" 27 | 28 | @classmethod 29 | def poll(cls, context): 30 | return (context.mode == 'EDIT_MESH') 31 | 32 | def execute(self, context): 33 | main(context) 34 | return {'FINISHED'} 35 | 36 | 37 | def register(): 38 | bpy.utils.register_class(UvOperator) 39 | 40 | 41 | def unregister(): 42 | bpy.utils.unregister_class(UvOperator) 43 | 44 | 45 | if __name__ == "__main__": 46 | register() 47 | 48 | # test call 49 | bpy.ops.uv.simple_operator() 50 | -------------------------------------------------------------------------------- /Blender_Python_Templates/operator_modal.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.props import IntProperty, FloatProperty 3 | 4 | 5 | class ModalOperator(bpy.types.Operator): 6 | """Move an object with the mouse, example""" 7 | bl_idname = "object.modal_operator" 8 | bl_label = "Simple Modal Operator" 9 | 10 | first_mouse_x: IntProperty() 11 | first_value: FloatProperty() 12 | 13 | def modal(self, context, event): 14 | if event.type == 'MOUSEMOVE': 15 | delta = self.first_mouse_x - event.mouse_x 16 | context.object.location.x = self.first_value + delta * 0.01 17 | 18 | elif event.type == 'LEFTMOUSE': 19 | return {'FINISHED'} 20 | 21 | elif event.type in {'RIGHTMOUSE', 'ESC'}: 22 | context.object.location.x = self.first_value 23 | return {'CANCELLED'} 24 | 25 | return {'RUNNING_MODAL'} 26 | 27 | def invoke(self, context, event): 28 | if context.object: 29 | self.first_mouse_x = event.mouse_x 30 | self.first_value = context.object.location.x 31 | 32 | context.window_manager.modal_handler_add(self) 33 | return {'RUNNING_MODAL'} 34 | else: 35 | self.report({'WARNING'}, "No active object, could not finish") 36 | return {'CANCELLED'} 37 | 38 | 39 | def register(): 40 | bpy.utils.register_class(ModalOperator) 41 | 42 | 43 | def unregister(): 44 | bpy.utils.unregister_class(ModalOperator) 45 | 46 | 47 | if __name__ == "__main__": 48 | register() 49 | 50 | # test call 51 | bpy.ops.object.modal_operator('INVOKE_DEFAULT') 52 | -------------------------------------------------------------------------------- /Blender_Python_Templates/operator_modal_draw.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import bgl 3 | import blf 4 | 5 | 6 | def draw_callback_px(self, context): 7 | print("mouse points", len(self.mouse_path)) 8 | 9 | font_id = 0 # XXX, need to find out how best to get this. 10 | 11 | # draw some text 12 | blf.position(font_id, 15, 30, 0) 13 | blf.size(font_id, 20, 72) 14 | blf.draw(font_id, "Hello Word " + str(len(self.mouse_path))) 15 | 16 | # 50% alpha, 2 pixel width line 17 | bgl.glEnable(bgl.GL_BLEND) 18 | bgl.glColor4f(0.0, 0.0, 0.0, 0.5) 19 | bgl.glLineWidth(2) 20 | 21 | bgl.glBegin(bgl.GL_LINE_STRIP) 22 | for x, y in self.mouse_path: 23 | bgl.glVertex2i(x, y) 24 | 25 | bgl.glEnd() 26 | 27 | # restore opengl defaults 28 | bgl.glLineWidth(1) 29 | bgl.glDisable(bgl.GL_BLEND) 30 | bgl.glColor4f(0.0, 0.0, 0.0, 1.0) 31 | 32 | 33 | class ModalDrawOperator(bpy.types.Operator): 34 | """Draw a line with the mouse""" 35 | bl_idname = "view3d.modal_operator" 36 | bl_label = "Simple Modal View3D Operator" 37 | 38 | def modal(self, context, event): 39 | context.area.tag_redraw() 40 | 41 | if event.type == 'MOUSEMOVE': 42 | self.mouse_path.append((event.mouse_region_x, event.mouse_region_y)) 43 | 44 | elif event.type == 'LEFTMOUSE': 45 | bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') 46 | return {'FINISHED'} 47 | 48 | elif event.type in {'RIGHTMOUSE', 'ESC'}: 49 | bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') 50 | return {'CANCELLED'} 51 | 52 | return {'RUNNING_MODAL'} 53 | 54 | def invoke(self, context, event): 55 | if context.area.type == 'VIEW_3D': 56 | # the arguments we pass the the callback 57 | args = (self, context) 58 | # Add the region OpenGL drawing callback 59 | # draw in view space with 'POST_VIEW' and 'PRE_VIEW' 60 | self._handle = bpy.types.SpaceView3D.draw_handler_add(draw_callback_px, args, 'WINDOW', 'POST_PIXEL') 61 | 62 | self.mouse_path = [] 63 | 64 | context.window_manager.modal_handler_add(self) 65 | return {'RUNNING_MODAL'} 66 | else: 67 | self.report({'WARNING'}, "View3D not found, cannot run operator") 68 | return {'CANCELLED'} 69 | 70 | 71 | def register(): 72 | bpy.utils.register_class(ModalDrawOperator) 73 | 74 | 75 | def unregister(): 76 | bpy.utils.unregister_class(ModalDrawOperator) 77 | 78 | 79 | if __name__ == "__main__": 80 | register() 81 | -------------------------------------------------------------------------------- /Blender_Python_Templates/operator_modal_timer.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | 4 | class ModalTimerOperator(bpy.types.Operator): 5 | """Operator which runs its self from a timer""" 6 | bl_idname = "wm.modal_timer_operator" 7 | bl_label = "Modal Timer Operator" 8 | 9 | _timer = None 10 | 11 | def modal(self, context, event): 12 | if event.type in {'RIGHTMOUSE', 'ESC'}: 13 | self.cancel(context) 14 | return {'CANCELLED'} 15 | 16 | if event.type == 'TIMER': 17 | # change theme color, silly! 18 | color = context.preferences.themes[0].view_3d.space.gradients.high_gradient 19 | color.s = 1.0 20 | color.h += 0.01 21 | 22 | return {'PASS_THROUGH'} 23 | 24 | def execute(self, context): 25 | wm = context.window_manager 26 | self._timer = wm.event_timer_add(0.1, window=context.window) 27 | wm.modal_handler_add(self) 28 | return {'RUNNING_MODAL'} 29 | 30 | def cancel(self, context): 31 | wm = context.window_manager 32 | wm.event_timer_remove(self._timer) 33 | 34 | 35 | def register(): 36 | bpy.utils.register_class(ModalTimerOperator) 37 | 38 | 39 | def unregister(): 40 | bpy.utils.unregister_class(ModalTimerOperator) 41 | 42 | 43 | if __name__ == "__main__": 44 | register() 45 | 46 | # test call 47 | bpy.ops.wm.modal_timer_operator() 48 | -------------------------------------------------------------------------------- /Blender_Python_Templates/operator_modal_view3d.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from mathutils import Vector 3 | from bpy.props import FloatVectorProperty 4 | 5 | 6 | class ViewOperator(bpy.types.Operator): 7 | """Translate the view using mouse events""" 8 | bl_idname = "view3d.modal_operator" 9 | bl_label = "Simple View Operator" 10 | 11 | offset: FloatVectorProperty( 12 | name="Offset", 13 | size=3, 14 | ) 15 | 16 | def execute(self, context): 17 | v3d = context.space_data 18 | rv3d = v3d.region_3d 19 | 20 | rv3d.view_location = self._initial_location + Vector(self.offset) 21 | 22 | def modal(self, context, event): 23 | v3d = context.space_data 24 | rv3d = v3d.region_3d 25 | 26 | if event.type == 'MOUSEMOVE': 27 | self.offset = (self._initial_mouse - Vector((event.mouse_x, event.mouse_y, 0.0))) * 0.02 28 | self.execute(context) 29 | context.area.header_text_set("Offset %.4f %.4f %.4f" % tuple(self.offset)) 30 | 31 | elif event.type == 'LEFTMOUSE': 32 | context.area.header_text_set(None) 33 | return {'FINISHED'} 34 | 35 | elif event.type in {'RIGHTMOUSE', 'ESC'}: 36 | rv3d.view_location = self._initial_location 37 | context.area.header_text_set(None) 38 | return {'CANCELLED'} 39 | 40 | return {'RUNNING_MODAL'} 41 | 42 | def invoke(self, context, event): 43 | 44 | if context.space_data.type == 'VIEW_3D': 45 | v3d = context.space_data 46 | rv3d = v3d.region_3d 47 | 48 | if rv3d.view_perspective == 'CAMERA': 49 | rv3d.view_perspective = 'PERSP' 50 | 51 | self._initial_mouse = Vector((event.mouse_x, event.mouse_y, 0.0)) 52 | self._initial_location = rv3d.view_location.copy() 53 | 54 | context.window_manager.modal_handler_add(self) 55 | return {'RUNNING_MODAL'} 56 | else: 57 | self.report({'WARNING'}, "Active space must be a View3d") 58 | return {'CANCELLED'} 59 | 60 | 61 | def register(): 62 | bpy.utils.register_class(ViewOperator) 63 | 64 | 65 | def unregister(): 66 | bpy.utils.unregister_class(ViewOperator) 67 | 68 | 69 | if __name__ == "__main__": 70 | register() 71 | -------------------------------------------------------------------------------- /Blender_Python_Templates/operator_modal_view3d_raycast.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy_extras import view3d_utils 3 | 4 | 5 | def main(context, event): 6 | """Run this function on left mouse, execute the ray cast""" 7 | # get the context arguments 8 | scene = context.scene 9 | region = context.region 10 | rv3d = context.region_data 11 | coord = event.mouse_region_x, event.mouse_region_y 12 | 13 | # get the ray from the viewport and mouse 14 | view_vector = view3d_utils.region_2d_to_vector_3d(region, rv3d, coord) 15 | ray_origin = view3d_utils.region_2d_to_origin_3d(region, rv3d, coord) 16 | 17 | ray_target = ray_origin + view_vector 18 | 19 | def visible_objects_and_duplis(): 20 | """Loop over (object, matrix) pairs (mesh only)""" 21 | 22 | for obj in context.visible_objects: 23 | if obj.type == 'MESH': 24 | yield (obj, obj.matrix_world.copy()) 25 | 26 | if obj.instance_type != 'NONE': 27 | obj.dupli_list_create(scene) 28 | for dob in obj.dupli_list: 29 | obj_dupli = dob.object 30 | if obj_dupli.type == 'MESH': 31 | yield (obj_dupli, dob.matrix.copy()) 32 | 33 | obj.dupli_list_clear() 34 | 35 | def obj_ray_cast(obj, matrix): 36 | """Wrapper for ray casting that moves the ray into object space""" 37 | 38 | # get the ray relative to the object 39 | matrix_inv = matrix.inverted() 40 | ray_origin_obj = matrix_inv @ ray_origin 41 | ray_target_obj = matrix_inv @ ray_target 42 | ray_direction_obj = ray_target_obj - ray_origin_obj 43 | 44 | # cast the ray 45 | success, location, normal, face_index = obj.ray_cast(ray_origin_obj, ray_direction_obj) 46 | 47 | if success: 48 | return location, normal, face_index 49 | else: 50 | return None, None, None 51 | 52 | # cast rays and find the closest object 53 | best_length_squared = -1.0 54 | best_obj = None 55 | 56 | for obj, matrix in visible_objects_and_duplis(): 57 | if obj.type == 'MESH': 58 | hit, normal, face_index = obj_ray_cast(obj, matrix) 59 | if hit is not None: 60 | hit_world = matrix @ hit 61 | scene.cursor_location = hit_world 62 | length_squared = (hit_world - ray_origin).length_squared 63 | if best_obj is None or length_squared < best_length_squared: 64 | best_length_squared = length_squared 65 | best_obj = obj 66 | 67 | # now we have the object under the mouse cursor, 68 | # we could do lots of stuff but for the example just select. 69 | if best_obj is not None: 70 | best_obj.select_set(True) 71 | context.view_layer.objects.active = best_obj 72 | 73 | 74 | class ViewOperatorRayCast(bpy.types.Operator): 75 | """Modal object selection with a ray cast""" 76 | bl_idname = "view3d.modal_operator_raycast" 77 | bl_label = "RayCast View Operator" 78 | 79 | def modal(self, context, event): 80 | if event.type in {'MIDDLEMOUSE', 'WHEELUPMOUSE', 'WHEELDOWNMOUSE'}: 81 | # allow navigation 82 | return {'PASS_THROUGH'} 83 | elif event.type == 'LEFTMOUSE': 84 | main(context, event) 85 | return {'RUNNING_MODAL'} 86 | elif event.type in {'RIGHTMOUSE', 'ESC'}: 87 | return {'CANCELLED'} 88 | 89 | return {'RUNNING_MODAL'} 90 | 91 | def invoke(self, context, event): 92 | if context.space_data.type == 'VIEW_3D': 93 | context.window_manager.modal_handler_add(self) 94 | return {'RUNNING_MODAL'} 95 | else: 96 | self.report({'WARNING'}, "Active space must be a View3d") 97 | return {'CANCELLED'} 98 | 99 | 100 | def register(): 101 | bpy.utils.register_class(ViewOperatorRayCast) 102 | 103 | 104 | def unregister(): 105 | bpy.utils.unregister_class(ViewOperatorRayCast) 106 | 107 | 108 | if __name__ == "__main__": 109 | register() 110 | -------------------------------------------------------------------------------- /Blender_Python_Templates/operator_node.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | 4 | def main(operator, context): 5 | space = context.space_data 6 | node_tree = space.node_tree 7 | node_active = context.active_node 8 | node_selected = context.selected_nodes 9 | 10 | # now we have the context, perform a simple operation 11 | if node_active in node_selected: 12 | node_selected.remove(node_active) 13 | if len(node_selected) != 1: 14 | operator.report({'ERROR'}, "2 nodes must be selected") 15 | return 16 | 17 | node_other, = node_selected 18 | 19 | # now we have 2 nodes to operate on 20 | if not node_active.inputs: 21 | operator.report({'ERROR'}, "Active node has no inputs") 22 | return 23 | 24 | if not node_other.outputs: 25 | operator.report({'ERROR'}, "Selected node has no outputs") 26 | return 27 | 28 | socket_in = node_active.inputs[0] 29 | socket_out = node_other.outputs[0] 30 | 31 | # add a link between the two nodes 32 | node_link = node_tree.links.new(socket_in, socket_out) 33 | 34 | 35 | class NodeOperator(bpy.types.Operator): 36 | """Tooltip""" 37 | bl_idname = "node.simple_operator" 38 | bl_label = "Simple Node Operator" 39 | 40 | @classmethod 41 | def poll(cls, context): 42 | space = context.space_data 43 | return space.type == 'NODE_EDITOR' 44 | 45 | def execute(self, context): 46 | main(self, context) 47 | return {'FINISHED'} 48 | 49 | 50 | def register(): 51 | bpy.utils.register_class(NodeOperator) 52 | 53 | 54 | def unregister(): 55 | bpy.utils.unregister_class(NodeOperator) 56 | 57 | 58 | if __name__ == "__main__": 59 | register() 60 | -------------------------------------------------------------------------------- /Blender_Python_Templates/operator_simple.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | 4 | def main(context): 5 | for ob in context.scene.objects: 6 | print(ob) 7 | 8 | 9 | class SimpleOperator(bpy.types.Operator): 10 | """Tooltip""" 11 | bl_idname = "object.simple_operator" 12 | bl_label = "Simple Object Operator" 13 | 14 | @classmethod 15 | def poll(cls, context): 16 | return context.active_object is not None 17 | 18 | def execute(self, context): 19 | main(context) 20 | return {'FINISHED'} 21 | 22 | 23 | def register(): 24 | bpy.utils.register_class(SimpleOperator) 25 | 26 | 27 | def unregister(): 28 | bpy.utils.unregister_class(SimpleOperator) 29 | 30 | 31 | if __name__ == "__main__": 32 | register() 33 | 34 | # test call 35 | bpy.ops.object.simple_operator() 36 | -------------------------------------------------------------------------------- /Blender_Python_Templates/ui_list.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | 4 | class MESH_UL_mylist(bpy.types.UIList): 5 | # Constants (flags) 6 | # Be careful not to shadow FILTER_ITEM (i.e. UIList().bitflag_filter_item)! 7 | # E.g. VGROUP_EMPTY = 1 << 0 8 | 9 | # Custom properties, saved with .blend file. E.g. 10 | # use_filter_empty: bpy.props.BoolProperty( 11 | # name="Filter Empty", default=False, options=set(), 12 | # description="Whether to filter empty vertex groups", 13 | # ) 14 | 15 | # Called for each drawn item. 16 | def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index, flt_flag): 17 | # 'DEFAULT' and 'COMPACT' layout types should usually use the same draw code. 18 | if self.layout_type in {'DEFAULT', 'COMPACT'}: 19 | pass 20 | # 'GRID' layout type should be as compact as possible (typically a single icon!). 21 | elif self.layout_type in {'GRID'}: 22 | pass 23 | 24 | # Called once to draw filtering/reordering options. 25 | def draw_filter(self, context, layout): 26 | # Nothing much to say here, it's usual UI code... 27 | pass 28 | 29 | # Called once to filter/reorder items. 30 | def filter_items(self, context, data, propname): 31 | # This function gets the collection property (as the usual tuple (data, propname)), and must return two lists: 32 | # * The first one is for filtering, it must contain 32bit integers were self.bitflag_filter_item marks the 33 | # matching item as filtered (i.e. to be shown), and 31 other bits are free for custom needs. Here we use the 34 | # first one to mark VGROUP_EMPTY. 35 | # * The second one is for reordering, it must return a list containing the new indices of the items (which 36 | # gives us a mapping org_idx -> new_idx). 37 | # Please note that the default UI_UL_list defines helper functions for common tasks (see its doc for more info). 38 | # If you do not make filtering and/or ordering, return empty list(s) (this will be more efficient than 39 | # returning full lists doing nothing!). 40 | 41 | # Default return values. 42 | flt_flags = [] 43 | flt_neworder = [] 44 | 45 | # Do filtering/reordering here... 46 | 47 | return flt_flags, flt_neworder 48 | -------------------------------------------------------------------------------- /Blender_Python_Templates/ui_list_simple.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | 4 | class MATERIAL_UL_matslots_example(bpy.types.UIList): 5 | # The draw_item function is called for each item of the collection that is visible in the list. 6 | # data is the RNA object containing the collection, 7 | # item is the current drawn item of the collection, 8 | # icon is the "computed" icon for the item (as an integer, because some objects like materials or textures 9 | # have custom icons ID, which are not available as enum items). 10 | # active_data is the RNA object containing the active property for the collection (i.e. integer pointing to the 11 | # active item of the collection). 12 | # active_propname is the name of the active property (use 'getattr(active_data, active_propname)'). 13 | # index is index of the current item in the collection. 14 | # flt_flag is the result of the filtering process for this item. 15 | # Note: as index and flt_flag are optional arguments, you do not have to use/declare them here if you don't 16 | # need them. 17 | def draw_item(self, context, layout, data, item, icon, active_data, active_propname): 18 | ob = data 19 | slot = item 20 | ma = slot.material 21 | # draw_item must handle the three layout types... Usually 'DEFAULT' and 'COMPACT' can share the same code. 22 | if self.layout_type in {'DEFAULT', 'COMPACT'}: 23 | # You should always start your row layout by a label (icon + text), or a non-embossed text field, 24 | # this will also make the row easily selectable in the list! The later also enables ctrl-click rename. 25 | # We use icon_value of label, as our given icon is an integer value, not an enum ID. 26 | # Note "data" names should never be translated! 27 | if ma: 28 | layout.prop(ma, "name", text="", emboss=False, icon_value=icon) 29 | else: 30 | layout.label(text="", translate=False, icon_value=icon) 31 | # 'GRID' layout type should be as compact as possible (typically a single icon!). 32 | elif self.layout_type in {'GRID'}: 33 | layout.alignment = 'CENTER' 34 | layout.label(text="", icon_value=icon) 35 | 36 | 37 | # And now we can use this list everywhere in Blender. Here is a small example panel. 38 | class UIListPanelExample(bpy.types.Panel): 39 | """Creates a Panel in the Object properties window""" 40 | bl_label = "UIList Panel" 41 | bl_idname = "OBJECT_PT_ui_list_example" 42 | bl_space_type = 'PROPERTIES' 43 | bl_region_type = 'WINDOW' 44 | bl_context = "object" 45 | 46 | def draw(self, context): 47 | layout = self.layout 48 | 49 | obj = context.object 50 | 51 | # template_list now takes two new args. 52 | # The first one is the identifier of the registered UIList to use (if you want only the default list, 53 | # with no custom draw code, use "UI_UL_list"). 54 | layout.template_list("MATERIAL_UL_matslots_example", "", obj, "material_slots", obj, "active_material_index") 55 | 56 | # The second one can usually be left as an empty string. 57 | # It's an additional ID used to distinguish lists in case you 58 | # use the same list several times in a given area. 59 | layout.template_list("MATERIAL_UL_matslots_example", "compact", obj, "material_slots", 60 | obj, "active_material_index", type='COMPACT') 61 | 62 | 63 | def register(): 64 | bpy.utils.register_class(MATERIAL_UL_matslots_example) 65 | bpy.utils.register_class(UIListPanelExample) 66 | 67 | 68 | def unregister(): 69 | bpy.utils.unregister_class(MATERIAL_UL_matslots_example) 70 | bpy.utils.unregister_class(UIListPanelExample) 71 | 72 | 73 | if __name__ == "__main__": 74 | register() 75 | -------------------------------------------------------------------------------- /Blender_Python_Templates/ui_menu.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | 4 | class CustomMenu(bpy.types.Menu): 5 | bl_label = "Custom Menu" 6 | bl_idname = "OBJECT_MT_custom_menu" 7 | 8 | def draw(self, context): 9 | layout = self.layout 10 | 11 | layout.operator("wm.open_mainfile") 12 | layout.operator("wm.save_as_mainfile").copy = True 13 | 14 | layout.operator("object.shade_smooth") 15 | 16 | layout.label(text="Hello world!", icon='WORLD_DATA') 17 | 18 | # use an operator enum property to populate a sub-menu 19 | layout.operator_menu_enum("object.select_by_type", 20 | property="type", 21 | text="Select All by Type...", 22 | ) 23 | 24 | # call another menu 25 | layout.operator("wm.call_menu", text="Unwrap").name = "VIEW3D_MT_uv_map" 26 | 27 | 28 | def draw_item(self, context): 29 | layout = self.layout 30 | layout.menu(CustomMenu.bl_idname) 31 | 32 | 33 | def register(): 34 | bpy.utils.register_class(CustomMenu) 35 | 36 | # lets add ourselves to the main header 37 | bpy.types.INFO_HT_header.append(draw_item) 38 | 39 | 40 | def unregister(): 41 | bpy.utils.unregister_class(CustomMenu) 42 | 43 | bpy.types.INFO_HT_header.remove(draw_item) 44 | 45 | 46 | if __name__ == "__main__": 47 | register() 48 | 49 | # The menu can also be called from scripts 50 | bpy.ops.wm.call_menu(name=CustomMenu.bl_idname) 51 | -------------------------------------------------------------------------------- /Blender_Python_Templates/ui_menu_simple.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | 4 | class SimpleCustomMenu(bpy.types.Menu): 5 | bl_label = "Simple Custom Menu" 6 | bl_idname = "OBJECT_MT_simple_custom_menu" 7 | 8 | def draw(self, context): 9 | layout = self.layout 10 | 11 | layout.operator("wm.open_mainfile") 12 | layout.operator("wm.save_as_mainfile") 13 | 14 | 15 | def register(): 16 | bpy.utils.register_class(SimpleCustomMenu) 17 | 18 | 19 | def unregister(): 20 | bpy.utils.unregister_class(SimpleCustomMenu) 21 | 22 | 23 | if __name__ == "__main__": 24 | register() 25 | 26 | # The menu can also be called from scripts 27 | bpy.ops.wm.call_menu(name=SimpleCustomMenu.bl_idname) 28 | -------------------------------------------------------------------------------- /Blender_Python_Templates/ui_panel.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | 4 | class LayoutDemoPanel(bpy.types.Panel): #物体属性面板 5 | """Creates a Panel in the scene context of the properties editor 6 | https://docs.blender.org/api/blender_python_api_2_76_release/bpy.types.Panel.html 7 | """ 8 | bl_label = "Layout Demo" 9 | bl_idname = "SCENE_PT_layout" 10 | bl_space_type = 'PROPERTIES' # 枚举('CLIP_EDITOR','CONSOLE','DOPESHEET_EDITOR','FILE_BROWSER','GRAPH_EDITOR','IMAGE_EDITOR','INFO','NLA_EDITOR','NODE_EDITOR','OUTLINER','PREFERENCES','PROPERTIES','SEQUENCE_EDITOR','STATUSBAR','TEXT_EDITOR','TOPBAR','VIEW3D_MT_editor_menus','VIEW_3D',) 11 | bl_region_type = 'WINDOW' # 面板所在区域,枚举 ('EXECUTE','HEADER','NAVIGATION_BAR','TOOL_PROPS','TOOLS','UI','WINDOW') 12 | bl_context = "scene" # 面板作用情境 13 | 14 | def draw(self, context): 15 | layout = self.layout 16 | 17 | scene = context.scene 18 | 19 | # Create a simple row. 20 | layout.label(text=" Simple Row:") 21 | 22 | row = layout.row() 23 | row.prop(scene, "frame_start") 24 | row.prop(scene, "frame_end") 25 | 26 | # Create an row where the buttons are aligned to each other. 27 | layout.label(text=" Aligned Row:") 28 | 29 | row = layout.row(align=True) 30 | row.prop(scene, "frame_start") 31 | row.prop(scene, "frame_end") 32 | 33 | # Create two columns, by using a split layout. 34 | split = layout.split() 35 | 36 | # First column 37 | col = split.column() 38 | col.label(text="Column One:") 39 | col.prop(scene, "frame_end") 40 | col.prop(scene, "frame_start") 41 | 42 | # Second column, aligned 43 | col = split.column(align=True) 44 | col.label(text="Column Two:") 45 | col.prop(scene, "frame_start") 46 | col.prop(scene, "frame_end") 47 | 48 | # Big render button 49 | layout.label(text="Big Button:") 50 | row = layout.row() 51 | row.scale_y = 3.0 52 | row.operator("render.render") 53 | 54 | # Different sizes in a row 55 | layout.label(text="Different button sizes:") 56 | row = layout.row(align=True) 57 | row.operator("render.render") 58 | 59 | sub = row.row() 60 | sub.scale_x = 2.0 61 | sub.operator("render.render") 62 | 63 | row.operator("render.render") 64 | 65 | 66 | def register(): 67 | bpy.utils.register_class(LayoutDemoPanel) 68 | 69 | 70 | def unregister(): 71 | bpy.utils.unregister_class(LayoutDemoPanel) 72 | 73 | 74 | if __name__ == "__main__": 75 | register() 76 | -------------------------------------------------------------------------------- /Blender_Python_Templates/ui_panel_simple.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | 4 | class HelloWorldPanel(bpy.types.Panel): 5 | """Creates a Panel in the Object properties window""" 6 | bl_label = "Hello World Panel" 7 | bl_idname = "OBJECT_PT_hello" 8 | bl_space_type = 'PROPERTIES' 9 | bl_region_type = 'WINDOW' 10 | bl_context = "object" 11 | 12 | def draw(self, context): 13 | layout = self.layout 14 | 15 | obj = context.object 16 | 17 | row = layout.row() 18 | row.label(text="Hello world!", icon='WORLD_DATA') 19 | 20 | row = layout.row() 21 | row.label(text="Active object is: " + obj.name) 22 | row = layout.row() 23 | row.prop(obj, "name") 24 | 25 | row = layout.row() 26 | row.operator("mesh.primitive_cube_add") 27 | 28 | 29 | def register(): 30 | bpy.utils.register_class(HelloWorldPanel) 31 | 32 | 33 | def unregister(): 34 | bpy.utils.unregister_class(HelloWorldPanel) 35 | 36 | 37 | if __name__ == "__main__": 38 | register() 39 | -------------------------------------------------------------------------------- /Blender_Python_Templates/ui_pie_menu.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.types import Menu 3 | 4 | # spawn an edit mode selection pie (run while object is in edit mode to get a valid output) 5 | 6 | 7 | class VIEW3D_PIE_template(Menu): 8 | # label is displayed at the center of the pie menu. 9 | bl_label = "Select Mode" 10 | 11 | def draw(self, context): 12 | layout = self.layout 13 | 14 | pie = layout.menu_pie() 15 | # operator_enum will just spread all available options 16 | # for the type enum of the operator on the pie 17 | pie.operator_enum("mesh.select_mode", "type") 18 | 19 | 20 | def register(): 21 | bpy.utils.register_class(VIEW3D_PIE_template) 22 | 23 | 24 | def unregister(): 25 | bpy.utils.unregister_class(VIEW3D_PIE_template) 26 | 27 | 28 | if __name__ == "__main__": 29 | register() 30 | 31 | bpy.ops.wm.call_menu_pie(name="VIEW3D_PIE_template") 32 | -------------------------------------------------------------------------------- /Blender_Python_Templates/ui_previews_custom_icon.py: -------------------------------------------------------------------------------- 1 | # This sample script demonstrates how to place a custom icon on a button or 2 | # menu entry. 3 | # 4 | # IMPORTANT NOTE: if you run this sample, there will be no icon in the button 5 | # You need to replace the image path with a real existing one. 6 | # For distributable scripts, it is recommended to place the icons inside the 7 | # addon folder and access it relative to the py script file for portability 8 | # 9 | # 10 | # Other use cases for UI-previews: 11 | # - provide a fixed list of previews to select from 12 | # - provide a dynamic list of preview (eg. calculated from reading a directory) 13 | # 14 | # For the above use cases, see the template 'ui_previews_dynamic_enum.py" 15 | 16 | 17 | import os 18 | import bpy 19 | 20 | 21 | class PreviewsExamplePanel(bpy.types.Panel): 22 | """Creates a Panel in the Object properties window""" 23 | bl_label = "Previews Example Panel" 24 | bl_idname = "OBJECT_PT_previews" 25 | bl_space_type = 'PROPERTIES' 26 | bl_region_type = 'WINDOW' 27 | bl_context = "object" 28 | 29 | def draw(self, context): 30 | layout = self.layout 31 | pcoll = preview_collections["main"] 32 | 33 | row = layout.row() 34 | my_icon = pcoll["my_icon"] 35 | row.operator("render.render", icon_value=my_icon.icon_id) 36 | 37 | # my_icon.icon_id can be used in any UI function that accepts 38 | # icon_value # try also setting text="" 39 | # to get an icon only operator button 40 | 41 | 42 | # We can store multiple preview collections here, 43 | # however in this example we only store "main" 44 | preview_collections = {} 45 | 46 | 47 | def register(): 48 | 49 | # Note that preview collections returned by bpy.utils.previews 50 | # are regular py objects - you can use them to store custom data. 51 | import bpy.utils.previews 52 | pcoll = bpy.utils.previews.new() 53 | 54 | # path to the folder where the icon is 55 | # the path is calculated relative to this py file inside the addon folder 56 | my_icons_dir = os.path.join(os.path.dirname(__file__), "icons") 57 | 58 | # load a preview thumbnail of a file and store in the previews collection 59 | pcoll.load("my_icon", os.path.join(my_icons_dir, "icon-image.png"), 'IMAGE') 60 | 61 | preview_collections["main"] = pcoll 62 | 63 | bpy.utils.register_class(PreviewsExamplePanel) 64 | 65 | 66 | def unregister(): 67 | 68 | for pcoll in preview_collections.values(): 69 | bpy.utils.previews.remove(pcoll) 70 | preview_collections.clear() 71 | 72 | bpy.utils.unregister_class(PreviewsExamplePanel) 73 | 74 | 75 | if __name__ == "__main__": 76 | register() 77 | -------------------------------------------------------------------------------- /Blender_Python_Templates/ui_previews_dynamic_enum.py: -------------------------------------------------------------------------------- 1 | # This sample script demonstrates a dynamic EnumProperty with custom icons. 2 | # The EnumProperty is populated dynamically with thumbnails of the contents of 3 | # a chosen directory in 'enum_previews_from_directory_items'. 4 | # Then, the same enum is displayed with different interfaces. Note that the 5 | # generated icon previews do not have Blender IDs, which means that they can 6 | # not be used with UILayout templates that require IDs, 7 | # such as template_list and template_ID_preview. 8 | # 9 | # Other use cases: 10 | # - make a fixed list of enum_items instead of calculating them in a function 11 | # - generate isolated thumbnails to use as custom icons in buttons 12 | # and menu items 13 | # 14 | # For custom icons, see the template "ui_previews_custom_icon.py". 15 | # 16 | # For distributable scripts, it is recommended to place the icons inside the 17 | # script directory and access it relative to the py script file for portability: 18 | # 19 | # os.path.join(os.path.dirname(__file__), "images") 20 | 21 | 22 | import os 23 | import bpy 24 | 25 | 26 | def enum_previews_from_directory_items(self, context): 27 | """EnumProperty callback""" 28 | enum_items = [] 29 | 30 | if context is None: 31 | return enum_items 32 | 33 | wm = context.window_manager 34 | directory = wm.my_previews_dir 35 | 36 | # Get the preview collection (defined in register func). 37 | pcoll = preview_collections["main"] 38 | 39 | if directory == pcoll.my_previews_dir: 40 | return pcoll.my_previews 41 | 42 | print("Scanning directory: %s" % directory) 43 | 44 | if directory and os.path.exists(directory): 45 | # Scan the directory for png files 46 | image_paths = [] 47 | for fn in os.listdir(directory): 48 | if fn.lower().endswith(".png"): 49 | image_paths.append(fn) 50 | 51 | for i, name in enumerate(image_paths): 52 | # generates a thumbnail preview for a file. 53 | filepath = os.path.join(directory, name) 54 | thumb = pcoll.load(filepath, filepath, 'IMAGE') 55 | enum_items.append((name, name, "", thumb.icon_id, i)) 56 | 57 | pcoll.my_previews = enum_items 58 | pcoll.my_previews_dir = directory 59 | return pcoll.my_previews 60 | 61 | 62 | class PreviewsExamplePanel(bpy.types.Panel): 63 | """Creates a Panel in the Object properties window""" 64 | bl_label = "Previews Example Panel" 65 | bl_idname = "OBJECT_PT_previews" 66 | bl_space_type = 'PROPERTIES' 67 | bl_region_type = 'WINDOW' 68 | bl_context = "object" 69 | 70 | def draw(self, context): 71 | layout = self.layout 72 | wm = context.window_manager 73 | 74 | row = layout.row() 75 | row.prop(wm, "my_previews_dir") 76 | 77 | row = layout.row() 78 | row.template_icon_view(wm, "my_previews") 79 | 80 | row = layout.row() 81 | row.prop(wm, "my_previews") 82 | 83 | 84 | # We can store multiple preview collections here, 85 | # however in this example we only store "main" 86 | preview_collections = {} 87 | 88 | 89 | def register(): 90 | from bpy.types import WindowManager 91 | from bpy.props import ( 92 | StringProperty, 93 | EnumProperty, 94 | ) 95 | 96 | WindowManager.my_previews_dir = StringProperty( 97 | name="Folder Path", 98 | subtype='DIR_PATH', 99 | default="" 100 | ) 101 | 102 | WindowManager.my_previews = EnumProperty( 103 | items=enum_previews_from_directory_items, 104 | ) 105 | 106 | # Note that preview collections returned by bpy.utils.previews 107 | # are regular Python objects - you can use them to store custom data. 108 | # 109 | # This is especially useful here, since: 110 | # - It avoids us regenerating the whole enum over and over. 111 | # - It can store enum_items' strings 112 | # (remember you have to keep those strings somewhere in py, 113 | # else they get freed and Blender references invalid memory!). 114 | import bpy.utils.previews 115 | pcoll = bpy.utils.previews.new() 116 | pcoll.my_previews_dir = "" 117 | pcoll.my_previews = () 118 | 119 | preview_collections["main"] = pcoll 120 | 121 | bpy.utils.register_class(PreviewsExamplePanel) 122 | 123 | 124 | def unregister(): 125 | from bpy.types import WindowManager 126 | 127 | del WindowManager.my_previews 128 | 129 | for pcoll in preview_collections.values(): 130 | bpy.utils.previews.remove(pcoll) 131 | preview_collections.clear() 132 | 133 | bpy.utils.unregister_class(PreviewsExamplePanel) 134 | 135 | 136 | if __name__ == "__main__": 137 | register() 138 | -------------------------------------------------------------------------------- /Blender_python_sample/README.md: -------------------------------------------------------------------------------- 1 | # for 2.8+ 2 | 3 | [钟表](https://github.com/BlenderCN/BlenderPython/blob/master/Blender_python_sample/clock.py) 4 | 5 | ![clock](https://github.com/BlenderCN/BlenderPython/blob/master/mDrivEngine/clock.gif) 6 | 7 | # for 2.7 8 | 9 | [2.7钟表](https://github.com/BlenderCN/BlenderPython/blob/master/Blender_python_sample/clock_27.py) 10 | 11 | 12 | [相似link](https://github.com/njanakiev/blender-scripting) 13 | -------------------------------------------------------------------------------- /Blender_python_sample/clock.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import datetime 3 | 4 | now = datetime.datetime.now() #Get the current date/time 5 | 6 | hour = now.hour #Get hours, 7 | min = now.minute #minutes 8 | sec = now.second #and seconds 9 | 10 | 11 | bpy.ops.object.select_all(action='TOGGLE') #Deselects any objects which may be selected 12 | bpy.ops.object.select_all(action='TOGGLE') #Selects all objects 13 | bpy.ops.object.delete() #Deletes all objects to allow for new clockface (will delete old one) 14 | 15 | 16 | #Clock hand creation 17 | 18 | 19 | bpy.ops.mesh.primitive_cube_add(location=(0,0,1)) 20 | bpy.context.active_object.name = "hour hand" 21 | bpy.ops.object.origin_set(type='ORIGIN_CURSOR') 22 | bpy.ops.transform.resize(value=(0.2,0.2,2.5)) 23 | 24 | bpy.context.active_object.rotation_euler = (-0.523599*hour)+(-min*0.008727),0,0 # 30 degrees*hour to get the correct placement on clock. But at 25 | # half past the hour the hour hand should be placed somewhere between 26 | # two hour numbers, hence the min*0.5 (*0.5 converts 60 minutes to 30 degrees) 27 | 28 | bpy.ops.mesh.primitive_cube_add(location=(0,0,1)) 29 | bpy.context.active_object.name = "min hand" 30 | bpy.ops.object.origin_set(type='ORIGIN_CURSOR') 31 | bpy.ops.transform.resize(value=(0.2,0.2,3)) 32 | 33 | bpy.context.active_object.rotation_euler = (-0.104720*min)+(-sec*0.001745),0,0 # Same as the hour hand except using the seconds to offset the minutes 34 | 35 | 36 | 37 | bpy.ops.mesh.primitive_cube_add(location=(0,0,1)) 38 | bpy.context.active_object.name = "sec hand" 39 | bpy.ops.object.origin_set(type='ORIGIN_CURSOR') 40 | bpy.ops.transform.resize(value=(0.1,0.1,3.5)) 41 | 42 | bpy.context.active_object.rotation_euler = -0.104720*sec,0,0 43 | 44 | #Clock rim creation 45 | 46 | for i in range(0,60): 47 | 48 | 49 | bpy.ops.mesh.primitive_cube_add(location=(0,0,8)) #add cube 50 | 51 | bpy.ops.transform.resize(value=(0.1,0.1,0.5)) #resize it 52 | 53 | bpy.context.scene.cursor_location = 0,0,0 #Set cursor to origin of scene 54 | bpy.ops.object.origin_set(type='ORIGIN_CURSOR') #Set objects origin to cursor 55 | 56 | bpy.context.active_object.rotation_euler = 0.104720*i,0,0 #rotate object with radians (6 degrees) 57 | 58 | 59 | #Clock numbers creation 60 | 61 | for i in range(1,13): 62 | 63 | bpy.ops.object.text_add(rotation=(1.570796,0,1.5707960)) #Create text object 64 | bpy.ops.object.editmode_toggle() #Go to edit mode to allow text editing 65 | bpy.ops.font.delete(type='PREVIOUS_WORD') #Delete text 66 | bpy.ops.font.text_insert(text=str(i), accent=False) #Make the text a number (i) 67 | bpy.ops.object.editmode_toggle() #Back to object mode to allow object manipulation 68 | 69 | bpy.context.active_object.name = "Text" +str(i) #Give a name to text of 'Texti' so they can be accessed later 70 | 71 | bpy.context.active_object.location = 0,0,6.5 #Move text up to clock face edge 72 | 73 | bpy.ops.object.origin_set(type='GEOMETRY_ORIGIN') 74 | 75 | bpy.ops.object.origin_set(type='ORIGIN_CURSOR') #Set pivot point to origin of scene 76 | 77 | bpy.ops.object.convert(target='MESH', keep_original=False) #Convert text to mesh so rotation can be applied 78 | 79 | bpy.ops.object.transform_apply(rotation=True) #Apply rotation 80 | 81 | bpy.context.active_object.rotation_euler =-0.523599*i,0,0 #Rotate numbers around clock face 82 | 83 | bpy.ops.object.transform_apply(rotation=True) #Apply rotation 84 | 85 | bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY') #Set origins back to center or geometry (needed for correction, below) 86 | 87 | 88 | #Corrects rotation for numbers on bottom half of clockface 89 | 90 | for i in range(4,9): #Loop through numbers 4-9 91 | 92 | currentText = bpy.data.objects['Text'+str(i)] #Get the object 93 | 94 | bpy.context.view_layer.objects.active = currentText #Set the object to selected 95 | 96 | bpy.context.active_object.rotation_euler = 3.141593,0,0 #Rotate number to right way up (180 degrees) 97 | 98 | 99 | bpy.context.scene.frame_current = 1 100 | 101 | #Insert keyframe 1 102 | 103 | currentObject = bpy.data.objects['hour hand'] 104 | bpy.context.view_layer.objects.active= currentObject 105 | bpy.context.view_layer.objects.active.select_set(True) 106 | bpy.ops.anim.keyframe_insert(type='Rotation', confirm_success=False) #Inserts first keyframe for rotation 107 | 108 | currentObject = bpy.data.objects['min hand'] 109 | bpy.context.view_layer.objects.active= currentObject 110 | bpy.context.view_layer.objects.active.select_set(True) 111 | bpy.ops.anim.keyframe_insert(type='Rotation', confirm_success=False) 112 | 113 | currentObject = bpy.data.objects['sec hand'] 114 | bpy.context.view_layer.objects.active= currentObject 115 | bpy.context.view_layer.objects.active.select_set(True) 116 | bpy.ops.anim.keyframe_insert(type='Rotation', confirm_success=False) 117 | 118 | bpy.context.scene.frame_current = bpy.context.scene.frame_end #Go to the last frame in playback range 119 | 120 | 121 | #Insert Keyframe 2 122 | 123 | currentObject = bpy.data.objects['hour hand'] 124 | bpy.context.view_layer.objects.active= currentObject 125 | bpy.context.view_layer.objects.active.select_set(True) 126 | bpy.context.active_object.rotation_euler.x = bpy.context.active_object.rotation_euler.x+(0.000006*-bpy.context.scene.frame_end) #adds new rotation of 0.00033333*number of frames to get correct position 127 | bpy.ops.anim.keyframe_insert(type='Rotation', confirm_success=False) 128 | 129 | currentObject = bpy.data.objects['min hand'] 130 | bpy.context.view_layer.objects.active= currentObject 131 | bpy.context.view_layer.objects.active.select_set(True) 132 | bpy.context.active_object.rotation_euler.x = bpy.context.active_object.rotation_euler.x+(0.000070*-bpy.context.scene.frame_end) #Same as above except 0.004 degrees 133 | bpy.ops.anim.keyframe_insert(type='Rotation', confirm_success=False) 134 | 135 | currentObject = bpy.data.objects['sec hand'] 136 | bpy.context.view_layer.objects.active= currentObject 137 | bpy.context.view_layer.objects.active.select_set(True) 138 | bpy.context.active_object.rotation_euler.x = bpy.context.active_object.rotation_euler.x+(0.004189*-bpy.context.scene.frame_end) #Same as above except 0.24 degrees 139 | bpy.ops.anim.keyframe_insert(type='Rotation', confirm_success=False) 140 | 141 | bpy.context.area.type = 'GRAPH_EDITOR' #Change screen to graph editor to do the next operation 142 | 143 | bpy.ops.graph.interpolation_type(type='LINEAR') #Set keyframe type to linear to avoid acceleration of the hands animation 144 | 145 | bpy.context.area.type = 'TEXT_EDITOR' #Change back to text editor 146 | 147 | bpy.ops.screen.animation_play() #Play animation 148 | -------------------------------------------------------------------------------- /Blender_python_sample/clock_27.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import datetime 3 | 4 | now = datetime.datetime.now() #Get the current date/time 5 | 6 | hour = now.hour #Get hours, 7 | min = now.minute #minutes 8 | sec = now.second #and seconds 9 | 10 | 11 | bpy.ops.object.select_all(action='TOGGLE') #Deselects any objects which may be selected 12 | bpy.ops.object.select_all(action='TOGGLE') #Selects all objects 13 | bpy.ops.object.delete() #Deletes all objects to allow for new clockface (will delete old one) 14 | 15 | 16 | #Clock hand creation 17 | 18 | 19 | bpy.ops.mesh.primitive_cube_add(location=(0,0,1)) 20 | bpy.context.active_object.name = "hour hand" 21 | bpy.ops.object.origin_set(type='ORIGIN_CURSOR') 22 | bpy.ops.transform.resize(value=(0.2,0.2,2.5)) 23 | 24 | bpy.context.active_object.rotation_euler = (-0.523599*hour)+(-min*0.008727),0,0 # 30 degrees*hour to get the correct placement on clock. But at 25 | # half past the hour the hour hand should be placed somewhere between 26 | # two hour numbers, hence the min*0.5 (*0.5 converts 60 minutes to 30 degrees) 27 | 28 | bpy.ops.mesh.primitive_cube_add(location=(0,0,1)) 29 | bpy.context.active_object.name = "min hand" 30 | bpy.ops.object.origin_set(type='ORIGIN_CURSOR') 31 | bpy.ops.transform.resize(value=(0.2,0.2,3)) 32 | 33 | bpy.context.active_object.rotation_euler = (-0.104720*min)+(-sec*0.001745),0,0 # Same as the hour hand except using the seconds to offset the minutes 34 | 35 | 36 | 37 | bpy.ops.mesh.primitive_cube_add(location=(0,0,1)) 38 | bpy.context.active_object.name = "sec hand" 39 | bpy.ops.object.origin_set(type='ORIGIN_CURSOR') 40 | bpy.ops.transform.resize(value=(0.1,0.1,3.5)) 41 | 42 | bpy.context.active_object.rotation_euler = -0.104720*sec,0,0 43 | 44 | #Clock rim creation 45 | 46 | for i in range(0,60): 47 | 48 | 49 | bpy.ops.mesh.primitive_cube_add(location=(0,0,8)) #add cube 50 | 51 | bpy.ops.transform.resize(value=(0.1,0.1,0.5)) #resize it 52 | 53 | bpy.context.scene.cursor_location = 0,0,0 #Set cursor to origin of scene 54 | bpy.ops.object.origin_set(type='ORIGIN_CURSOR') #Set objects origin to cursor 55 | 56 | bpy.context.active_object.rotation_euler = 0.104720*i,0,0 #rotate object with radians (6 degrees) 57 | 58 | 59 | #Clock numbers creation 60 | 61 | for i in range(1,13): 62 | 63 | bpy.ops.object.text_add(rotation=(1.570796,0,1.5707960)) #Create text object 64 | bpy.ops.object.editmode_toggle() #Go to edit mode to allow text editing 65 | bpy.ops.font.delete(type='ALL') #Delete text 66 | bpy.ops.font.text_insert(text=str(i), accent=False) #Make the text a number (i) 67 | bpy.ops.object.editmode_toggle() #Back to object mode to allow object manipulation 68 | 69 | bpy.context.active_object.name = "Text" +str(i) #Give a name to text of 'Texti' so they can be accessed later 70 | 71 | bpy.context.active_object.location = 0,0,6.5 #Move text up to clock face edge 72 | 73 | bpy.ops.object.origin_set(type='GEOMETRY_ORIGIN') 74 | 75 | bpy.ops.object.origin_set(type='ORIGIN_CURSOR') #Set pivot point to origin of scene 76 | 77 | bpy.ops.object.convert(target='MESH', keep_original=False) #Convert text to mesh so rotation can be applied 78 | 79 | bpy.ops.object.transform_apply(rotation=True) #Apply rotation 80 | 81 | bpy.context.active_object.rotation_euler =-0.523599*i,0,0 #Rotate numbers around clock face 82 | 83 | bpy.ops.object.transform_apply(rotation=True) #Apply rotation 84 | 85 | bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY') #Set origins back to center or geometry (needed for correction, below) 86 | 87 | 88 | #Corrects rotation for numbers on bottom half of clockface 89 | 90 | for i in range(4,9): #Loop through numbers 4-9 91 | 92 | currentText = bpy.data.objects['Text'+str(i)] #Get the object 93 | 94 | bpy.context.scene.objects.active = currentText #Set the object to selected 95 | 96 | bpy.context.active_object.rotation_euler = 3.141593,0,0 #Rotate number to right way up (180 degrees) 97 | 98 | 99 | bpy.context.scene.frame_current = 1 100 | 101 | #Insert keyframe 1 102 | 103 | currentObject = bpy.data.objects['hour hand'] 104 | bpy.context.scene.objects.active= currentObject 105 | bpy.context.scene.objects.active.select= True 106 | bpy.ops.anim.keyframe_insert(type='Rotation', confirm_success=False) #Inserts first keyframe for rotation 107 | 108 | currentObject = bpy.data.objects['min hand'] 109 | bpy.context.scene.objects.active= currentObject 110 | bpy.context.scene.objects.active.select= True 111 | bpy.ops.anim.keyframe_insert(type='Rotation', confirm_success=False) 112 | 113 | currentObject = bpy.data.objects['sec hand'] 114 | bpy.context.scene.objects.active= currentObject 115 | bpy.context.scene.objects.active.select= True 116 | bpy.ops.anim.keyframe_insert(type='Rotation', confirm_success=False) 117 | 118 | bpy.context.scene.frame_current = bpy.context.scene.frame_end #Go to the last frame in playback range 119 | 120 | 121 | #Insert Keyframe 2 122 | 123 | currentObject = bpy.data.objects['hour hand'] 124 | bpy.context.scene.objects.active= currentObject 125 | bpy.context.scene.objects.active.select= True 126 | bpy.context.active_object.rotation_euler.x = bpy.context.active_object.rotation_euler.x+(0.000006*-bpy.context.scene.frame_end) #adds new rotation of 0.00033333*number of frames to get correct position 127 | bpy.ops.anim.keyframe_insert(type='Rotation', confirm_success=False) 128 | 129 | currentObject = bpy.data.objects['min hand'] 130 | bpy.context.scene.objects.active= currentObject 131 | bpy.context.scene.objects.active.select= True 132 | bpy.context.active_object.rotation_euler.x = bpy.context.active_object.rotation_euler.x+(0.000070*-bpy.context.scene.frame_end) #Same as above except 0.004 degrees 133 | bpy.ops.anim.keyframe_insert(type='Rotation', confirm_success=False) 134 | 135 | currentObject = bpy.data.objects['sec hand'] 136 | bpy.context.scene.objects.active= currentObject 137 | bpy.context.scene.objects.active.select= True 138 | bpy.context.active_object.rotation_euler.x = bpy.context.active_object.rotation_euler.x+(0.004189*-bpy.context.scene.frame_end) #Same as above except 0.24 degrees 139 | bpy.ops.anim.keyframe_insert(type='Rotation', confirm_success=False) 140 | 141 | bpy.context.area.type = 'GRAPH_EDITOR' #Change screen to graph editor to do the next operation 142 | 143 | bpy.ops.graph.interpolation_type(type='LINEAR') #Set keyframe type to linear to avoid acceleration of the hands animation 144 | 145 | bpy.context.area.type = 'TEXT_EDITOR' #Change back to text editor 146 | 147 | bpy.ops.screen.animation_play() #Play animation 148 | -------------------------------------------------------------------------------- /Curves.md: -------------------------------------------------------------------------------- 1 | > TODO: show foreach_set for curve data 2 | 3 | Blender has two main type of Curve types. 2D and 3D - and both have a variety of Spline types. Maybe this doesn't mean much to you if you haven't used Blender's Curves much, but it will be apparent after a bit of experimentation. 4 | 5 | Available Spline types: `('POLY', 'BEZIER', 'BSPLINE', 'CARDINAL', 'NURBS')`. Each of these types expects its coordinate information in a different way. 6 | 7 | - POLY: Like a poly line, not smoothed, just a line between each coordinate. 8 | - NURBS: Smoothed lines between coordinates. 9 | - BEZIER: takes a collection Knots and Handles. 10 | - CARDINAL: ... 11 | - BSPLINE: ... 12 | 13 | Sometimes the distinction between 2D and 3D is arbitrary, sometimes you can switch the Curve type to either, it depends on what you need. 14 | 15 | ### 2D (Polyline) 16 | 17 | This snippet lets you create a 2d surface Curve. Think of this as a Filled Polyline. You can pass it any number of sublists and the function will deal with the sublists. If the sublists fall within the perimeter of the largest sublist then it will make holes. Try it. 18 | 19 | ```python 20 | import bpy 21 | 22 | # weight 23 | w = 1 24 | 25 | def MakeFilledPolyLine(objname, curvename, cLists): 26 | curvedata = bpy.data.curves.new(name=curvename, type='CURVE') 27 | curvedata.dimensions = '2D' 28 | 29 | odata = bpy.data.objects.new(objname, curvedata) 30 | odata.location = (0,0,0) # object origin 31 | bpy.context.scene.objects.link(odata) 32 | 33 | for cList in cLists: 34 | polyline = curvedata.splines.new('POLY') 35 | polyline.points.add(len(cList)-1) 36 | for num in range(len(cList)): 37 | # --------------------- = x , y , z, w 38 | polyline.points[num].co = cList[num][0], cList[num][1], 0, w 39 | 40 | polyline.order_u = len(polyline.points)-1 41 | polyline.use_endpoint_u = True 42 | polyline.use_cyclic_u = True # this closes each loop 43 | 44 | 45 | vectors = [ 46 | [[0,0], [10,0], [10,10], [0,10]], 47 | [[1,1], [1,2], [2,2], [2,1]] 48 | ] 49 | MakeFilledPolyLine("NameOfMyCurveObject", "NameOfMyCurve", vectors) 50 | ``` 51 | 52 | makes this: 53 | ![image1](http://i.stack.imgur.com/TuxNP.png) 54 | 55 | 56 | ### 2D (Lettertype, Bezier) 57 | 58 | It helps to see an example with Curves using Beziers and holes. 59 | ```python 60 | import bpy 61 | from mathutils import Vector 62 | 63 | 64 | coordinates = [ 65 | ((-1, 0, 0), (-0.7, 0, 0), (-1, 0.5521, 0)), 66 | ((0, 1, 0), (-0.5521, 1, 0), (0, 0.7, 0)), 67 | ((0, 0, 0), (0, 0.3, 0), (-0.3, 0, 0)) 68 | ] 69 | 70 | 71 | def MakeCurveQuarter(objname, curvename, cList, origin=(0,0,0)): 72 | curvedata = bpy.data.curves.new(name=curvename, type='CURVE') 73 | curvedata.dimensions = '2D' 74 | 75 | objectdata = bpy.data.objects.new(objname, curvedata) 76 | objectdata.location = origin 77 | 78 | bpy.context.scene.objects.link(objectdata) 79 | 80 | polyline = curvedata.splines.new('BEZIER') 81 | polyline.bezier_points.add(len(cList)-1) 82 | 83 | for idx, (knot, h1, h2) in enumerate(cList): 84 | point = polyline.bezier_points[idx] 85 | point.co = knot 86 | point.handle_left = h1 87 | point.handle_right = h2 88 | point.handle_left_type = 'FREE' 89 | point.handle_right_type = 'FREE' 90 | 91 | polyline.use_cyclic_u = True 92 | 93 | MakeCurveQuarter("NameOfMyCurveObject", "NameOfMyCurve", coordinates) 94 | ``` 95 | 96 | ![img4](https://cloud.githubusercontent.com/assets/619340/10614751/41090bfe-775c-11e5-9e72-c33e580512dd.png) 97 | 98 | 99 | ### 3D (smooth path) 100 | 101 | ```python 102 | import bpy 103 | import math 104 | from math import cos, sin, pi 105 | from mathutils import Vector 106 | 107 | tau = 2 * pi 108 | w = 1 109 | 110 | num_points = 30 111 | amp = lambda i: 5 if (i % 2) else 7 112 | pos = lambda f, i: f(i/num_points*tau) * amp(i) 113 | listOfVectors = [(pos(sin, i), pos(cos, i), 0) for i in range(num_points)] 114 | 115 | def MakePolyLine(objname, curvename, cList): 116 | curvedata = bpy.data.curves.new(name=curvename, type='CURVE') 117 | curvedata.dimensions = '3D' 118 | 119 | objectdata = bpy.data.objects.new(objname, curvedata) 120 | objectdata.location = (0, 0, 0) 121 | bpy.context.scene.objects.link(objectdata) 122 | 123 | polyline = curvedata.splines.new('NURBS') 124 | polyline.points.add(len(cList)-1) 125 | for num in range(len(cList)): 126 | polyline.points[num].co = (cList[num])+(w,) 127 | 128 | polyline.order_u = len(polyline.points)-1 129 | polyline.use_endpoint_u = True 130 | polyline.use_cyclic_u = True 131 | 132 | 133 | MakePolyLine("NameOfMyCurveObject", "NameOfMyCurve", listOfVectors) 134 | ``` 135 | ![image2](https://cloud.githubusercontent.com/assets/619340/10515857/abf5258e-7355-11e5-8193-faa6af1f6fa6.png) -------------------------------------------------------------------------------- /Drivers.md: -------------------------------------------------------------------------------- 1 | I don't know a whole lot about these, and [the docs](https://www.blender.org/manual/animation/basics/drivers.html#driver-namespace) seem to do a good job of covering most things. 2 | 3 | Find neat tricks in _TextEditor -> Templates -> Python -> Driver Functions_ 4 | 5 | ### Driver from Python file 6 | 7 | This is the long way, the scenic route. Using a python expression from a file. Sometimes you'll have expressions that are a bit more complicated than what you can fit onto 1 line comfortably. 8 | 9 | - Make a script in the Text Editor with content similar to this, then run it. 10 | 11 | ```python 12 | import math 13 | import bpy 14 | 15 | def driver_func(current_frame): 16 | frames = 120 # <-- period. 17 | theta = math.pi * 2 / frames 18 | final = math.sin(current_frame % frames * theta) 19 | return final 20 | 21 | # add function to driver_namespace 22 | bpy.app.driver_namespace['driver_func'] = driver_func 23 | 24 | ``` 25 | - Right-click the property you want to control (for instance the X rotation of a cube), choose Add Single Driver 26 | - enable `Auto run python scripts` in _User Preferences -> File_. 27 | - Open the Graph Editor, and switch to Drivers view 28 | - select the driven property, and toggle the rightside panel 29 | ![theimage](https://cloud.githubusercontent.com/assets/619340/10715506/092e8798-7b19-11e5-9570-421515d8849f.png) 30 | 31 | If your driver has a python error, you can correct it and run the code again to overwrite 32 | the namespace key 'driver_func`. You can name this function whatever you wish, and can have as many of them as you need in the .blend. 33 | 34 | 35 | 36 | ### example 2 37 | 38 | This might be obvious, but let's be explicit about the possibilities. We can use Empties as dummy objects, and give them custom properties, and assign a Driver to the custom property. Drivers can execute pretty much anything, that means also updating multiple other objects. 39 | 40 | ![img this](https://cloud.githubusercontent.com/assets/619340/10812732/8a912810-7e19-11e5-866c-545b2975189a.png) 41 | 42 | The following code randomizes the location of an object named 'Text' between frame 30 and 60. The return value of the _driver function_ is never used, it's the function body we care about. 43 | 44 | ```python 45 | import math 46 | import bpy 47 | import random 48 | 49 | def driver_func2(current_frame): 50 | 51 | obj = bpy.data.objects.get('Text') 52 | if not obj: 53 | return 0.0 54 | 55 | rnd = random.random 56 | if 30 < current_frame < 60: 57 | obj.location = rnd(), rnd(), rnd() 58 | else: 59 | if obj.location.length > 0.0001: 60 | obj.location = 0, 0, 0 61 | 62 | return 0.0 63 | 64 | # add function to driver_namespace 65 | bpy.app.driver_namespace['driver_func2'] = driver_func2 66 | ``` 67 | 68 | ### example 3 69 | 70 | Change material depending on delta location of keyframed object 71 | 72 | ```python 73 | import bpy 74 | from mathutils import Vector 75 | 76 | def driver_delta_to_RED(frame): 77 | # triggered by a frame change, any code inside here gets run. 78 | 79 | p = bpy.data.objects['Plane'] 80 | current_xyz = p.location 81 | fcurve = p.animation_data.action.fcurves 82 | 83 | x = fcurve[0].evaluate(frame-1) 84 | y = fcurve[1].evaluate(frame-1) 85 | z = fcurve[2].evaluate(frame-1) 86 | 87 | # may want to find the top speed first, and normalize 88 | # this value using that information 89 | delta = (current_xyz - Vector((x, y, z))).length 90 | 91 | nodes = bpy.data.materials[0].node_tree.nodes 92 | nodes[1].inputs[0].default_value = (delta, 0, 0, 1.0) 93 | 94 | # the return value is of no relevance and can be static. 95 | return 0.0 96 | 97 | bpy.app.driver_namespace['driver_delta_to_RED'] = driver_delta_to_RED 98 | ``` -------------------------------------------------------------------------------- /Duplication.md: -------------------------------------------------------------------------------- 1 | The scenario to consider here is as follows: You need to render thousands of spheres and each sphere has a unique center coordinate. Creating thousands of spheres one Object at a time, even when scripted, is a bit slow and becomes progressively slower the more objects you add. 2 | 3 | If you really want to know why that is, it's covered in here: 4 | - [object-creation-slows-over-time](http://blender.stackexchange.com/questions/14814/object-creation-slows-over-time) 5 | - [python-performance-with-blender-operators](http://blender.stackexchange.com/questions/7358/python-performance-with-blender-operators) 6 | 7 | Rather than creating thousands of unique Objects, which 8 | 9 | 1. take up a lot of space in memory, and 10 | 2. require name collision checking 11 | 12 | 13 | ### DupliVerts (duplication on vertices) 14 | ____ 15 | It's possible to do this by making 2 objects. 16 | 17 | - In words: 18 | - child: _1 Cube_ (with verts and faces) 19 | - parent: _1 vertex based mesh Object_ with vertices at the locations where you want the cubes-children to be duplicated. 20 | - In images: this tends to make immediate sense. Here the dots represent the Vertices of the _parent Object_ (A cube without faces/edges) and the small orange Cube in the middle is the child cube. 21 | ![image show](https://cloud.githubusercontent.com/assets/619340/10758302/ecd082b0-7cb0-11e5-9a56-b2b6fdc06093.png) 22 | here with vertex normals: 23 | ![img with normals](https://cloud.githubusercontent.com/assets/619340/10758906/9b2da7c2-7cb4-11e5-9397-8ed8c1028f66.png) 24 | 25 | Things to consider: 26 | 27 | - (pro) it is fast. 28 | - (pro) it is possible to rotate the object's vertex normals and use them as rotation values. 29 | - (con) vertex normals are overwritten by default when you enter edit mode of the parent object. 30 | - (con) no way to scale individual duplicates. 31 | 32 | Reusing code created for the [Mesh](Mesh) page, minus some comments. Read that link if you encounter problems. 33 | 34 | You can execute this code with or without the last `if-statement`. The last if statement shows how to switch on dupli vert rotation and how to set the normal of the vertices. 35 | 36 | ```python 37 | import bpy 38 | import bmesh 39 | import math 40 | import random 41 | 42 | 43 | def create_cube(name, d=1, faces=True): 44 | 45 | Verts = [ 46 | (1.0, 1.0, -1.0), 47 | (1.0, -1.0, -1.0), 48 | (-1.0, -1.0, -1.0), 49 | (-1.0, 1.0, -1.0), 50 | (1.0, 1.0, 1.0), 51 | (1.0, -1.0, 1.0), 52 | (-1.0, -1.0, 1.0), 53 | (-1.0, 1.0, 1.0) 54 | ] 55 | 56 | face_indices = [ 57 | (0, 1, 2, 3), 58 | (4, 7, 6, 5), 59 | (0, 4, 5, 1), 60 | (1, 5, 6, 2), 61 | (2, 6, 7, 3), 62 | (4, 0, 3, 7) 63 | ] 64 | 65 | Faces = face_indices if faces else [] 66 | 67 | if not (d == 1.0): 68 | Verts = [tuple(v*d for v in vert) for vert in Verts] 69 | 70 | mesh = bpy.data.meshes.new(name + "_mesh") 71 | mesh.from_pydata(Verts, [], Faces) 72 | mesh.update() 73 | 74 | obj = bpy.data.objects.new(name + "_Object", mesh) 75 | bpy.context.scene.objects.link(obj) 76 | return obj 77 | 78 | 79 | parent = create_cube('parent_cube', d=1, faces=False) 80 | parent.dupli_type = 'VERTS' 81 | 82 | child = create_cube('child_cube', d=0.2) 83 | child.parent = parent 84 | 85 | # let's rotate the vertex normals of the parent 86 | parent.use_dupli_vertices_rotation = True 87 | if (True): 88 | sin, cos = math.sin, math.cos 89 | for v in parent.data.vertices: 90 | rndx = random.random() * math.pi * 2 91 | rndy = random.random() * math.pi * 2 92 | rndz = random.random() * math.pi * 2 93 | v.normal = sin(rndx), cos(rndy), cos(rndy) 94 | 95 | 96 | ``` 97 | gets you something like this: all that's technically needed for the DupliVert is a _parent_ mesh Object containing verts, and of-course the child object. 98 | 99 | ![img dupliverts](https://cloud.githubusercontent.com/assets/619340/10757990/a6368996-7cae-11e5-8d61-b8908ca7b3ac.png) 100 | 101 | ### DupliFaces (duplication on faces) 102 | ____ 103 | very similar to DupliVerts, but this time it uses the face medians (think of average vertex) as duplication locations. 104 | 105 | Below - using a (parent) disjoint mesh of quads to duplicate the cone (child): 106 | ![img face dupe](https://cloud.githubusercontent.com/assets/619340/10752213/72749cb4-7c87-11e5-9915-f435458937a3.png) 107 | 108 | Things to consider: 109 | 110 | - (pro) it is fast. 111 | - (pro) Faces have a normal and that can be used as an orientation. 112 | - (pro) Faces have an area, this can be used to scale the duplicates individually. 113 | - (con) you have to create 3 or 4 vertices and corresponding face keys for your mesh for each desired duplicate. (This is a bit more work...it's not really a downside, but it needs to be mentioned). 114 | 115 | The code: in order to demonstrate this using an object that has a good number of faces all pointing in different directions and all with different sizes. A UV Sphere. Here you'll notice the angles and scales of the duplicated object correspond to the face areas and face normals. 116 | 117 | Reusing code created for the [bmesh.ops (primitives) page](https://github.com/zeffii/BlenderPythonRecipes/wiki/bmesh_ops_primitives), minus some comments. Read that link if you encounter problems. 118 | 119 | ```python 120 | import bpy 121 | import bmesh 122 | 123 | def create_uv_sphere(name, u=5, v=4, d=1): 124 | bm = bmesh.new() 125 | bmesh.ops.create_uvsphere(bm, u_segments=u, v_segments=v, diameter=d) 126 | 127 | mesh = bpy.data.meshes.new(name + "_mesh") 128 | bm.to_mesh(mesh) 129 | bm.free() 130 | 131 | obj = bpy.data.objects.new(name, mesh) 132 | bpy.context.scene.objects.link(obj) 133 | return obj 134 | 135 | parent = create_uv_sphere('parent_uvsphere', u=5, v=4, d=1) 136 | child = create_uv_sphere('child_uvsphere', u=8, v=8, d=0.2) 137 | 138 | child.parent = parent 139 | parent.dupli_type = 'FACES' 140 | parent.use_dupli_faces_scale = True 141 | 142 | ``` 143 | creates this: 144 | ![image dupli-faces](https://cloud.githubusercontent.com/assets/619340/10755855/96c8a9f2-7ca0-11e5-8748-33b1b321130f.png) -------------------------------------------------------------------------------- /Empty-(null-object).md: -------------------------------------------------------------------------------- 1 | Empties are used for a lot of reasons: 2 | 3 | - as parents 4 | - for Mirror Modifier, as _Mirror Object_ 5 | - for Array Modifier, as _Object Offset_ 6 | - for Screw Modifier, as _Axis Object_ 7 | - etc... 8 | 9 | option 1: 10 | ```python 11 | bpy.ops.object.add(type='EMPTY', location=(0, 2, 1), rotation=(0, 0, 0)) 12 | 13 | # if you need a reference to the newly created Empty, then use the following. 14 | # ( useful for setting a name..or other properties ) 15 | mt = bpy.context.active_object 16 | mt.name = 'empty_name' 17 | mt.empty_draw_size = 2 18 | ``` 19 | option 2 (useful if you want to avoid using bpy.ops): 20 | ```python 21 | scene = bpy.context.scene 22 | objects = bpy.data.objects 23 | mt = objects.new("empty_name", None) 24 | mt.location = (0, 2, 1) 25 | mt.empty_draw_size = 2 26 | scene.objects.link(mt) 27 | scene.update() 28 | ``` 29 | option 2 may seem like more code, but you can set the name on the same line as the creation and you 30 | get the reference at the same time. -------------------------------------------------------------------------------- /GeneralPythonSnippets.md: -------------------------------------------------------------------------------- 1 | This is a crash course in the Python subset. If you already master these you should not waste time reading this, unless you want to check for typos or factual inaccuracies. 2 | 3 | Any combination of the following topics will be present in useful Python scripts. This page hopes to introduce you to the broad outlines of the Python language and will contain links for more detailed information. It makes little sense to get very detailed early in the learning process so i'll cover things quickly. 4 | 5 | It's OK if this doesn't make a lot of sense. If you don't understand something remember that the way we write things is often a convention because a convention had to exist (just like if you look at a map of Africa and see straight line borders which have no relationship to geography, -- what's that all about?? exactly, it's a reality that exists only in the minds of the people who've seen those maps.). 6 | 7 | Follow along in Blender's Python console. Learning to type accurately is incredibly important for programming. 8 | 9 | | basic 1 | basic 2 | intermediate | 10 | |---------|---------|--------------| 11 | | [print](GeneralPythonSnippets#print) | [string manipulation](GeneralPythonSnippets#string-manipulation) | [decorators](GeneralPythonSnippets#decorators) | 12 | | [integer](GeneralPythonSnippets#integer) | [for loop](GeneralPythonSnippets#for-loop) | | 13 | | [float](GeneralPythonSnippets#float) | [list comprehensions](GeneralPythonSnippets#list-comprehensions) | | 14 | | [string](GeneralPythonSnippets#string) | [while loop](GeneralPythonSnippets#while-loop) | | 15 | | [tuple](GeneralPythonSnippets#tuple) | [flow control](GeneralPythonSnippets#flow-control) | | 16 | | [list](GeneralPythonSnippets#list) | [try except](GeneralPythonSnippets#try-except) | | 17 | | [dictionary](GeneralPythonSnippets#dictionary) | [functions](GeneralPythonSnippets#functions) | | 18 | | [boolean](GeneralPythonSnippets#boolean) | [import libraries](GeneralPythonSnippets#import-libraries) | | 19 | | [if statements](GeneralPythonSnippets#if-statements) | [classes](GeneralPythonSnippets#classes) | | 20 | | [standard library](GeneralPythonSnippets#standard-library) | | | 21 | 22 | ### How to run this code 23 | 24 | (elaborate) : Open Blender, and get a Python console view open. You'll notice `>>>` , this is a prompt waiting for you to type stuff. What we'll be doing below is type stuff after the prompt, then hit Enter to run it. 25 | 26 | 27 | ### print 28 | 29 | While you learn Python `print()` is your friend. It will try to display whatever is between the parenthesis `( )`. 30 | 31 | ```python 32 | >>> print("Hello apprentice") 33 | ... Hello apprentice 34 | ``` 35 | 36 | I'll return to `print()` frequently on this page to show how to display information that you will find helpful while coding. 37 | 38 | ### integer 39 | 40 | Whole numbers, positive or negative. You'll see plenty of these as you progress. 41 | ```python 42 | # let's make a variable called hal 43 | >>> hal = 3000 44 | >>> print(hal * 3) 45 | ... 9000 46 | ``` 47 | ### float 48 | 49 | Numbers like these `100.0`, `0.0000003` and `34.4`. etc. You will often see unusual floats like `0.00200023423` when you expect to see just `0.002`, this is normal and will be explained later. 50 | You might also see scientific notation for small numbers namely `2e-7` which is equivalent to `0.0000002`. 51 | 52 | A lot more can be said about _floats_ but it rarely makes sense to talk about them in isolation. You'll see more references to floats further down. 53 | 54 | ### string 55 | 56 | The words you read right now are strings. In Python we express strings by wrapping them in single qoutes, double qoutes or tripple qoutes. 57 | ```python 58 | >>> my_first_string = "I am a HAL 9000 computer" 59 | >>> my_second_string = 'I am a HAL 9000 computer' 60 | >>> my_third_string = """I am a HAL 9000 computer""" 61 | ``` 62 | You can add strings together, we call it 'concatenation': 63 | ```python 64 | # notice the spaces at the end of string, why is that? 65 | >>> var1 = "I am a " 66 | >>> var2 = "HAL " 67 | >>> var3 = "9000 computer" 68 | >>> print(var1 + var2 + var3) 69 | ... I am a HAL 9000 computer 70 | ``` 71 | 72 | to convert a non string to a string is done explicitly using `str()`: 73 | ```python 74 | >>> some_number = 9000 75 | >>> "Hal " + str(some_number) 76 | ... "Hal 9000"` 77 | ``` 78 | 79 | Further _string manipulation_ is discussed at a later stage. Strings are used a lot in Python, but in Blender they're commonly used to name objects, data, or set the properties of data. 80 | 81 | ### tuple 82 | 83 | This is a data type which is used to collect data, it holds any kind of data you want. You'll see a tuple defined in various ways. 84 | 85 | ```python 86 | >>> some_number = 20 87 | >>> tuple_one = ('some_string', some_number, 0.3444) 88 | ... ('some_string', 20, 0.3444) 89 | ``` 90 | but it's OK to drop the surrounding parenthesis, the comma separator is most important 91 | ```python 92 | >>> tuple_two = 'some_string', some_number, 0.3444 93 | >>> tuple_two 94 | ... ('some_string', 20, 0.3444) 95 | ``` 96 | you'll also see it written this way, but it is not common. 97 | ```python 98 | >>> tuple_three = tuple(['some_string', some_number, 0.3444]) 99 | ``` 100 | A tuple is _immutable_. This means you can not change any of the members of the tuple, once it's created it is unchangeable - also if the tuple was made with variables. If you change a variable, the tuple's content is not updated.. see what happens when we update var2 to hold a new string `SAL `. 101 | 102 | ```python 103 | >>> var1 = "I am a " 104 | >>> var2 = "HAL " 105 | >>> var3 = "9000 computer" 106 | >>> tuple_four = var1, var2, var3 107 | >>> tuple_four 108 | ... ('I am a ', 'HAL ', '9000 computer') 109 | >>> var2 = 'SAL ' 110 | >>> var4 111 | ... ('I am a ', 'HAL ', '9000 computer') 112 | ``` 113 | To change a tuple you must assign a new tuple by overwriting the tuple stored in `var4`. 114 | ```python 115 | >>> var4 = (var1, var2, var3) 116 | >>> var4 117 | ... ('I am a ', 'SAL ', '9000 computer') 118 | ``` 119 | If you find the need to update members of a collection you might want to use a `List` instead. 120 | 121 | ### list 122 | 123 | Lists are a very big topic, but you don't need to know much about them to do useful things. Lists are like tuples but with a few important differences. Firstly notation, lists use square brackets `[ ]` to enclose their data. These are _not optional_ as they are with tuples. 124 | ```python 125 | >>> my_list = [0, 1, 2, 30, 34] 126 | >>> my_other_list = [0.9, 1, 2, "30", "thirty nine"] 127 | ``` 128 | lists are mutable, you can change the content of lists without assigning a new list to the variable. A list's _elements_ are also called _members_. I'll use the two terms interchangeably. You can tell Python which element to change by using the element index. First element has index 0 (zero), the second element has index 1 (one). etc...the Tenth element has index 9 (nine). Why the off by one difference? -- because elements or indexed starting from 0, this is called 'zero indexing'. Most programming languages do it this way. It's a convention, you'll make mistakes -- we all make off by one mistakes at some point. 129 | 130 | To tell Python that you want to use or modify an element you use the bracket notation. 131 | 132 | ```python 133 | >>> some_list = [30, 40, 20] 134 | >>> some_list[1] # lets look up the second element, it has index 1. 135 | ... 40 136 | ``` 137 | how about changing the value stored in an element? easy! 138 | 139 | ```python 140 | >>> my_new_list = [30, 40, 50] 141 | >>> my_new_list[0] = 'First Element Changed' 142 | >>> my_new_list 143 | ... ['First Element Changed', 40, 50] 144 | ``` 145 | 146 | **nested lists** 147 | 148 | It's not uncommon to have nested lists, or Multi-Dimensional lists. 149 | 150 | ```python 151 | >>> my_nested_list = [20, 30, my_list] 152 | >>> my_nested_list 153 | [20, 30, [0, 1, 2, 30, 34]] 154 | >>> multi_dim_list = [[1.0, 0.1, 2.0], [0.2, 1.0, 0.3], [1.0, 0.3, 1.0]] 155 | ``` 156 | 157 | **list mutations** 158 | 159 | ```python 160 | test_list = [[],[]] 161 | 162 | def mutate_incoming(test_list, params): 163 | a, b = params 164 | list_a, list_b = test_list 165 | list_a.append(a) 166 | list_b.append(b) 167 | 168 | a_list = [20,30,40] 169 | b_list = ['huh', 'yes', 'ack'] 170 | 171 | for params in zip(a_list, b_list): 172 | mutate_incoming(test_list, params) 173 | 174 | list_a, list_b = test_list 175 | print(list_a, list_b) 176 | 177 | >> [20, 30, 40] ['huh', 'yes', 'ack'] 178 | ``` 179 | In the above snippet we treat the `test_list` like a reference, each time `mutate_incoming` is called it appends new data to the nested list (` [[],[]] `) that the symbol `test_list` represents. This allows for concise zips, abstracting away the .append operation that would be otherwise needed. 180 | 181 | unfinished. 182 | 183 | ### Classes 184 | 185 | basic classes 186 | 187 | 188 | more specific / advanced dynamic class instances 189 | 190 | The following example is not likely to be useful to most use cases. There are scenarios when you don't want (or can't) write a class method for each possible feature of your class. Adding methods (and hey they're handled) dynamically can help you make your classes code easier to use and maintain -- if you know what you're doing. 191 | 192 | ```python 193 | class DemoClass: 194 | 195 | def __init__(self): 196 | self.power = 2 197 | 198 | def __getattr__(self, name): 199 | 200 | def method(*args, **kw): 201 | if name in ['work', 'stereo']: 202 | if isinstance(kw, dict) and kw: 203 | print('a dict', kw) 204 | elif isinstance(args, tuple) and args: 205 | print('a tuple', args) 206 | 207 | return method 208 | 209 | 210 | f = DemoClass() 211 | 212 | f.work(damage=20, reverse=-1) 213 | f.work(20, 1) 214 | 215 | """ 216 | a dict {'damage': 20, 'reverse': -1} 217 | a tuple (20, 1) 218 | """ 219 | ``` 220 | 221 | more!? OK :) what if I want to have attributes and functions with the same name, but have a unique behaviour if I do 222 | ```python 223 | c = NewClass() 224 | c.bar # <-- attr 225 | c.bar(20) # <-- method! 226 | ``` 227 | 228 | the way Python works doesn't allow us to share the names of attributes and methods. for instance the following is not permitted: 229 | ```python 230 | some_class_instance.some_name # <-- attr 231 | some_class_instance.some_name() # <-- method 232 | ``` 233 | ##### Making a class iterable 234 | 235 | sometimes you need to make a wrapper around some datatype, this lets you turn a class instance into something 236 | that you can iterate over. 237 | 238 | ```python 239 | class Emo: 240 | 241 | def __init__(self, data): 242 | self.twoo = [] 243 | self.twoo.extend(data) 244 | 245 | def __iter__(self): 246 | return iter(self.twoo) 247 | 248 | 249 | ak = Emo([20, 30, 40]) 250 | 251 | for g in ak: 252 | print(g) 253 | ``` 254 | 255 | ### Standard Library 256 | 257 | This is a massive topic. but for now.. 258 | 259 | ##### pathlib 260 | 261 | > Iteration over paths.. the easy way 262 | 263 | like this 264 | 265 | 266 | ```python 267 | from pathlib import Path 268 | 269 | dataset_root = Path(r'C:\Users\zeffi\Desktop\GITHUB\sverchok\nodes') 270 | analyzer_path = dataset_root / 'analyzer' 271 | 272 | for node_path in analyzer_path.iterdir(): 273 | print(node_path) 274 | ``` 275 | 276 | ##### Datetime 277 | 278 | I love this module. This example finds all Mondays in the given date range 279 | ```python 280 | from datetime import datetime, date, timedelta 281 | 282 | 283 | def find_mondays_between(start, end, date_format="%m/%d/%Y"): 284 | a = datetime.strptime(start, date_format) 285 | b = datetime.strptime(end, date_format) 286 | delta = b - a 287 | num_days = delta.days 288 | 289 | mondays = [] 290 | for i in range(delta.days + 1): 291 | day = a + timedelta(days=i) 292 | if day.weekday() == 0: 293 | day_str = day.strftime(date_format) 294 | mondays.append(day_str) 295 | 296 | return mondays 297 | 298 | 299 | print(find_mondays_between("01/01/2018", "03/12/2018")) 300 | ``` 301 | 302 | 303 | # Try Except 304 | 305 | what is Try/Except ? a good resource for deeper thought is here: 306 | Personally i tend to use some form of the following. It display the line number and the error. If that isn't enough then it's probably more complicated, and wouldn't likely be conveyed by the error anyway. That's when you switch on your brain again and reason about the line that triggers the error. 307 | 308 | ```python 309 | import sys 310 | 311 | try: 312 | ... 313 | except Exception as err: 314 | print('-----', err, end=' | ') 315 | print('Error on line {}'.format(sys.exc_info()[-1].tb_lineno)) 316 | ``` 317 | 318 | -------------------------------------------------------------------------------- /GreasePencil.md: -------------------------------------------------------------------------------- 1 | resources which cover this: 2 | - [query-grease-pencil-strokes-from-python](http://blender.stackexchange.com/questions/24694/query-grease-pencil-strokes-from-python) 3 | - [convert-curves-to-grease-pencil](http://blender.stackexchange.com/questions/36140/convert-curves-to-grease-pencil) 4 | 5 | ### drawing trigonometry using Grease Pencil. 6 | 7 | ```python 8 | import math 9 | import bpy 10 | 11 | 12 | def get_layer(gdata_owner, layer_name): 13 | 14 | grease_data = bpy.data.grease_pencil 15 | if gdata_owner not in grease_data: 16 | gp = grease_data.new(gdata_owner) 17 | else: 18 | gp = grease_data[gdata_owner] 19 | 20 | # get grease pencil layer 21 | if not (layer_name in gp.layers): 22 | layer = gp.layers.new(layer_name) 23 | layer.frames.new(1) 24 | layer.line_width = 1 25 | else: 26 | layer = gp.layers[layer_name] 27 | layer.frames[0].clear() 28 | 29 | return layer 30 | 31 | 32 | def generate_gp3d_stroke(layer): 33 | layer.show_points = True 34 | layer.color = (0.2, 0.90, .2) 35 | s = layer.frames[0].strokes.new() 36 | s.draw_mode = '3DSPACE' # or '2DSPACE' 37 | 38 | chain = [] 39 | num_verts = 10 40 | r = 2.2 41 | gamma = 2 * math.pi / num_verts 42 | for i in range(num_verts+1): 43 | theta = gamma * i 44 | world_point = (math.sin(theta) * r, math.cos(theta) * r, 1.2) 45 | chain.append(world_point) 46 | 47 | s.points.add(len(chain)) 48 | for idx, p in enumerate(chain): 49 | s.points[idx].co = p 50 | # s.points[idx].pressure = 1.0 # if 2DSPACE 51 | 52 | 53 | class TrigGenerator(bpy.types.Operator): 54 | 55 | bl_idname = 'mesh.trig_generator' 56 | bl_label = 'generated trig with gpencil' 57 | bl_options = {'REGISTER', 'UNDO'} 58 | 59 | def execute(self, context): 60 | obj = bpy.context.object 61 | data_name = 'stack_data' 62 | layer_name = "stack layer" 63 | layer = get_layer(data_name, layer_name) 64 | generate_gp3d_stroke(layer) 65 | context.scene.grease_pencil = bpy.data.grease_pencil[data_name] 66 | return {'FINISHED'} 67 | 68 | 69 | def register(): 70 | bpy.utils.register_class(TrigGenerator) 71 | 72 | 73 | def unregister(): 74 | bpy.utils.unregister_class(TrigGenerator) 75 | 76 | 77 | if __name__ == '__main__': 78 | register() 79 | ``` 80 | 81 | ### Font to grease pencil. 82 | 83 | Font must be converted to Mesh first. Ideally we'd use Ngons to keep the number of strokes down, but GP fills polygons using the Fan method and this doesn't treat irregular concave polygons very well. A way to get the font to display in a usable way is to triangulate or Quad the mesh first. .. then run the script. (or include this as an automated step in the script) 84 | 85 | ```python 86 | import bpy 87 | 88 | def get_layer(gdata_owner, layer_name): 89 | 90 | grease_data = bpy.data.grease_pencil 91 | if gdata_owner not in grease_data: 92 | gp = grease_data.new(gdata_owner) 93 | else: 94 | gp = grease_data[gdata_owner] 95 | 96 | # get grease pencil layer 97 | if not (layer_name in gp.layers): 98 | layer = gp.layers.new(layer_name) 99 | layer.frames.new(1) 100 | layer.line_width = 1 101 | else: 102 | layer = gp.layers[layer_name] 103 | layer.frames[0].clear() 104 | 105 | return layer 106 | 107 | def generate_gp3d_stroke(obj, layer): 108 | layer.show_points = True 109 | layer.color = (0.2, 0.90, .2) 110 | 111 | verts = obj.data.vertices 112 | for f in obj.data.polygons: 113 | 114 | s = layer.frames[0].strokes.new() 115 | s.draw_mode = '3DSPACE' # or '2DSPACE' 116 | s.points.add(len(f.vertices)) 117 | for i, v in enumerate(f.vertices): 118 | p = s.points[i] 119 | p.co = verts[v].co 120 | p.pressure = 1.0 121 | 122 | def main(): 123 | obj = bpy.context.active_object 124 | data_name, layer_name = 'stack_data', "stack layer" 125 | layer = get_layer(data_name, layer_name) 126 | generate_gp3d_stroke(obj, layer) 127 | bpy.context.scene.grease_pencil = bpy.data.grease_pencil[data_name] 128 | 129 | main() 130 | ``` 131 | 132 | ![image](https://cloud.githubusercontent.com/assets/619340/11423648/83f36c28-9446-11e5-9e65-a8c6c59f693c.png) 133 | 134 | 135 | -------------------------------------------------------------------------------- /Home.md: -------------------------------------------------------------------------------- 1 | Welcome to the Blender Python Recipes wiki! 2 | _curated and opinionated snippets for bpy_ 3 | **(Work In Progress until this message goes)** 4 | 5 | Scripting examples are listed in the section to the right. Use the pages dropdown menu (with the arrow) if you need to search by term. 6 | 7 | - :lock: means i've not put much time into it 8 | - :wrench: means some content was added but there's more where that came from 9 | - no emojii means it's OK for the time being. 10 | 11 | **Why Recipes?** 12 | The `bpy` documentation is mostly auto-generated and tends to be (October 2015) rather sparse on details; that's fine for most confident python-heads, but people trying to learn python and `bpy` benefit more from seeing many short samples. A github _markdown based_ wiki is a convenient way to collect snippets and allow people to contribute updates and fixes. 13 | 14 | **Who?** 15 | The snippets assume some familiarity with Python and Blender terminology. If you are new to Python you might pick up a lot of techniques by experimenting with the examples and breaking / fixing / augmenting them. The aim here isn't strictly to teach Python, but Python docs are referenced when it makes sense. 16 | 17 | **What?** 18 | Some of these snippets are written for one off projects, others are taken from code collected at Blenderscripting.blogspot or answers given on Blender.stackexchange. 19 | 20 | **Contribute?** 21 | I have no interest in doing this alone. Make a new issue about a topic if you think it can be augmented with more information. Content will not be included without consent of whoever wrote the code, I will assume any contributors have the right to submit code, submissions found to ignore this request will be rewritten or removed. 22 | -------------------------------------------------------------------------------- /Image_Pixels.md: -------------------------------------------------------------------------------- 1 | Blender comes with numpy built in. yep! 2 | 3 | ### Mix 3 images 4 | 5 | ```python 6 | import math 7 | import numpy as np 8 | import bpy 9 | 10 | def np_array_from_image(img_name): 11 | img = bpy.data.images[img_name] 12 | return np.array(img.pixels[:]) 13 | 14 | pixelsA = np_array_from_image('A') 15 | pixelsB = np_array_from_image('B') 16 | pixelsC = np_array_from_image('C') 17 | pixelsD = (pixelsA + pixelsB + pixelsC) / 3 18 | 19 | image_D = bpy.data.images['D'] 20 | image_D.pixels = pixelsD.tolist() 21 | 22 | # then click in the UV editor / to update the view..to see the pixels of `D` updated 23 | ``` 24 | 25 | ![imge](http://i.stack.imgur.com/rN8tP.png) 26 | 27 | ### Add smallest of each array 28 | ```python 29 | # add smallest element values 30 | interim_1 = np.minimum(pixelsA, pixelsB) 31 | pixelsD = np.minimum(interim_1, pixelsC) 32 | ``` 33 | 34 | ![img2](http://i.stack.imgur.com/4Jf9H.png) 35 | -------------------------------------------------------------------------------- /Keyframes.md: -------------------------------------------------------------------------------- 1 | Keyframing is a massive topic. I fear not everything can be covered explicitly, but implicitly by understanding these examples the rest will also makes sense. 2 | 3 | - "datapath" 4 | - keyframe object properties (like location, scale, rotation, hide_render) 5 | - keyframe mesh content (move vertices by keyframe) 6 | - keyframe miscellaneous 7 | 8 | mention constant interpolation (this avoids the need to place a before and after keyframe) -------------------------------------------------------------------------------- /Layers.md: -------------------------------------------------------------------------------- 1 | **Layers - how to set** 2 | 3 | instead of writing 4 | ```python 5 | .....,layers=[True, False, False ..........False] 6 | ``` 7 | You can write it more Pythonic 8 | ```python 9 | # if you want the object on 1 layer 10 | .....,layers=[i in {0} for i in range(20)] 11 | 12 | # if you want the object on multiple layers 13 | .....,layers=[i in {0,1,4,12} for i in range(20)] 14 | >>> [True, True, False, False, True, False, False, ...., True, ....False, False] 15 | 16 | ``` 17 | 18 | **Moving objects from one layer to another** 19 | 20 | Moving objects that appear in one range of layers, to another range. 21 | 22 | ```python 23 | import bpy 24 | 25 | for obj in bpy.data.objects: 26 | 27 | # let's deal with a simple case: is it only on one layer? 28 | if obj.layers[:].count(True) == 1: 29 | found_index = obj.layers[:].index(True) 30 | if (5 <= found_index <= 10): 31 | new_index = found_index + 5 32 | obj.layers[:] = [i==new_index for i in range(20)] 33 | ``` 34 | 35 | Here restated with a new function called `set_layer`, to encapsulate the behavior. It's less efficient, but arguably easier to read. 36 | 37 | 38 | ```python 39 | import bpy 40 | 41 | def set_layer(obj, idx): 42 | obj.layers[:] = [i==idx for i in range(20)] 43 | 44 | for obj in bpy.data.objects: 45 | if obj.layers[:].count(True) == 1: 46 | found_index = obj.layers[:].index(True) 47 | if found_index in {5,6,7,8,9}: 48 | set_layer(obj, found_index + 5) 49 | ``` 50 | 51 | Here's an alternative, which might be interesting if you're new to python 52 | 53 | ```python 54 | import bpy 55 | 56 | only_on_one_layer = lambda o: o.layers[:].count(True) == 1 57 | 58 | for o in filter(only_on_one_layer, bpy.data.objects): 59 | layer_id = o.layers[:].index(True) 60 | if layer_id in {5,6,7,8,9}: 61 | o.layers[:] = [i == (layer_id + 5) for i in range(20)] 62 | ``` -------------------------------------------------------------------------------- /Layout.md: -------------------------------------------------------------------------------- 1 | The best introduction to UI layout and the templating system used by Blender is [still the UI cookbook](https://wiki.blender.org/index.php/Dev:Py/Scripts/Cookbook/Code_snippets). There's no harm in showing more examples, especially concerning the draw function. 2 | 3 | All examples will use the same template code, unless otherwise specified. Image should show you what's different and then the code will start to make more sense. 4 | 5 | ```python 6 | import bpy 7 | 8 | from bpy.props import * 9 | from bpy.types import Operator, Panel 10 | 11 | class LayoutDemoPanel(Panel): 12 | """Creates a Panel in the scene context of the properties editor""" 13 | bl_label = "Layout Demo" 14 | bl_idname = "SCENE_PT_layout" 15 | bl_space_type = 'PROPERTIES' 16 | bl_region_type = 'WINDOW' 17 | bl_context = "scene" 18 | 19 | def draw(self, context): 20 | ... 21 | 22 | def register(): 23 | bpy.utils.register_class(LayoutDemoPanel) 24 | 25 | def unregister(): 26 | bpy.utils.unregister_class(LayoutDemoPanel) 27 | 28 | 29 | if __name__ == "__main__": 30 | register() 31 | ``` 32 | 33 | ### Responsive layout 34 | 35 | You can get more complex and entirely rearrange layouts depending how many pixels wide the UI panel is. Below this example will reset the percentage of the width that the arrow buttons should occupy when the user resizes the panel horizontally. 36 | 37 | 38 | ```python 39 | def draw(self, context): 40 | 41 | layout = self.layout 42 | scene = context.scene 43 | 44 | w = context.area.width 45 | 46 | row = layout.row(align=True) 47 | pct = 0.5 48 | if w > 300: 49 | pct = 0.3 50 | elif w > 400: 51 | pct = 0.2 52 | elif w < 200: 53 | pct = 0.7 54 | split = row.split(percentage=pct) 55 | 56 | OP = "render.render" 57 | 58 | l_split = split.split() 59 | 60 | left_side = l_split.column() 61 | left_side.operator(OP, text='', icon='RIGHTARROW_THIN') 62 | left_side.operator(OP, text='', icon='TRIA_LEFT') 63 | left_side.operator(OP, text='', icon='RIGHTARROW_THIN') 64 | 65 | mid_side = l_split.column() 66 | mid_side.operator(OP, text='', icon="TRIA_UP") 67 | mid_side.label('') 68 | mid_side.operator(OP, text='', icon="TRIA_DOWN") 69 | 70 | right_side = l_split.column() 71 | right_side.operator(OP, text='', icon='RIGHTARROW_THIN') 72 | right_side.operator(OP, text='', icon="TRIA_RIGHT") 73 | right_side.operator(OP, text='', icon='RIGHTARROW_THIN') 74 | ``` 75 | 76 | ![image](https://cloud.githubusercontent.com/assets/619340/12372505/8304af5c-bc5a-11e5-819c-965c2ca8fee1.png) 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /Mesh.md: -------------------------------------------------------------------------------- 1 | # Mesh 2 | 3 | There are two ways to create `Mesh` data. 4 | 5 | - One relies on first defining all vertices / edges / faces in lists and then sending them to a function called `.from_pydata()`. 6 | - The other grows the mesh in place by adding _vertex coordinates_ and _edge / face indices_ sequentially. 7 | 8 | Both methods have strengths and weaknesses, picking which to use depends largely on what you're doing. 9 | - Are you create a mesh from scratch? 10 | - Are you updating and existing mesh (possibly with new additional geometry) 11 | - Are you purely updating vertex locations of an existing mesh. 12 | - ..etc,.. 13 | 14 | ## from_pydata 15 | 16 | ### snippet 1 (creating: Verts and Edges) 17 | ______ 18 | 19 | Date: October 2015 20 | 21 | This method expects the `Mesh` object to be empty (more about that below). 22 | 23 | ```python 24 | # be in object mode with nothing selected. 25 | 26 | import bpy 27 | 28 | # create 4 verts, string them together to make 4 edges. 29 | coord1 = (-1.0, 1.0, 0.0) 30 | coord2 = (-1.0, -1.0, 0.0) 31 | coord3 = (1.0, -1.0, 0.0) 32 | coord4 = (1.0, 1.0, 0.0) 33 | 34 | Verts = [coord1, coord2, coord3, coord4] 35 | Edges = [[0,1],[1,2],[2,3],[3,0]] 36 | 37 | mesh = bpy.data.meshes.new("Base_Data") 38 | mesh.from_pydata(Verts, Edges, []) 39 | mesh.update() 40 | 41 | obj = bpy.data.objects.new("Base_Object", mesh) 42 | 43 | scene = bpy.context.scene 44 | scene.objects.link(obj) 45 | obj.select = True 46 | ``` 47 | 48 | This snippet 49 | - adds a new empty mesh called 'Base_Data' to the `bpy.data` collection. 50 | - pushes `Verts, Edges, and an empty list` onto the new empty Mesh 51 | - The empty list `[]` is because `from_pydata` expects 3 arguments, none are optional 52 | - You can choose to pass just _verts_, or just _verts + edges_ or just _verts + faces_ like: 53 | 54 | ```python 55 | mesh.from_pydata(Verts, [], []) 56 | mesh.from_pydata(Verts, Edges, []) 57 | mesh.from_pydata(Verts, [], Faces) 58 | ``` 59 | 60 | - In the event you want to add _some faces_ and _some edges_ you can of course do 61 | `mesh.from_pydata(Verts, Edges, Faces)` 62 | 63 | ### snippet 2 (creating: Verts and Quad Faces) 64 | ______ 65 | 66 | Date: October 2015 67 | 68 | This method expects the `Mesh` object to be empty (more about that below). 69 | ```python 70 | import bpy 71 | 72 | verts = [ 73 | (1.0, 1.0, -1.0), 74 | (1.0, -1.0, -1.0), 75 | (-1.0, -1.0, -1.0), 76 | (-1.0, 1.0, -1.0), 77 | (1.0, 1.0, 1.0), 78 | (1.0, -1.0, 1.0), 79 | (-1.0, -1.0, 1.0), 80 | (-1.0, 1.0, 1.0) 81 | ] 82 | 83 | faces = [ 84 | (0, 1, 2, 3), 85 | (4, 7, 6, 5), 86 | (0, 4, 5, 1), 87 | (1, 5, 6, 2), 88 | (2, 6, 7, 3), 89 | (4, 0, 3, 7) 90 | ] 91 | 92 | mesh = bpy.data.meshes.new("cube_mesh_data") 93 | mesh.from_pydata(verts, [], faces) 94 | mesh.update() 95 | 96 | cube_object = bpy.data.objects.new("Cube_Object", mesh) 97 | 98 | scene = bpy.context.scene 99 | scene.objects.link(cube_object) 100 | cube_object.select = True 101 | ``` 102 | 103 | This should make sense, and the way to make Triangles is to simply pass in sub-lists that index 3 vertices instead of 4. 104 | 105 | > You are permitted to mix Triangles and Quads in the Faces list. 106 | 107 | 108 | ## sequentially adding vertices/edges/faces 109 | 110 | In some scenarios you might want to add geometry to existing geometry, use [BMesh](BMesh) for this. 111 | 112 | 113 | ## Not an empty Mesh? 114 | 115 | You will need to get rid of all geometry in the Mesh before you can use `.from_pydata` on it. This is one way to do it without using `bpy.ops`. Effectively using `Bmesh` to overwrite the `Mesh` data. 116 | 117 | ```python 118 | import bpy 119 | import bmesh 120 | 121 | p = bpy.data.objects['Obj2'] 122 | 123 | def clear_mesh(mesh): 124 | bm = bmesh.new() 125 | bm.to_mesh(mesh) 126 | bm.free() 127 | 128 | clear_mesh(p.data) 129 | p.data.update() 130 | ``` 131 | 132 | ## update using `foreach_set` 133 | 134 | ```python 135 | flat_list_of_verts = [x1, y1, z1, x2, y2, z1, x3, y3, z3....xn, yn, zn] 136 | mesh.vertices.foreach_set('co', flat_list_of_verts) 137 | mesh.update() 138 | ``` 139 | 140 | often you can use something like [itertools](https://docs.python.org/3.4/library/itertools.html) to flatten a nested list of coordinates 141 | ```python 142 | flat_list_of_verts = list(itertools.chain.from_iterable(verts)) 143 | ``` 144 | 145 | `foreach_set` will always expect a flat list. -------------------------------------------------------------------------------- /Metaballs.md: -------------------------------------------------------------------------------- 1 | If you need to use metaballs, they recently (October 2015) got approx 10 fold speed increase! 2 | 3 | See this [blender.stackexchange.com](http://blender.stackexchange.com/questions/1349/scripting-metaballs-with-negative-influence) question that spawned this example. There's also more information given by various authors. The snippet below covers the basics that might otherwise not be obvious. 4 | 5 | ```python 6 | import bpy 7 | 8 | scene = bpy.context.scene 9 | 10 | # add metaball object 11 | mball = bpy.data.metaballs.new("MyBall") 12 | obj = bpy.data.objects.new("MyBallObject", mball) 13 | scene.objects.link(obj) 14 | 15 | # test with different values, for viewport do you need ultra high res? 16 | mball.resolution = 0.16 # View resolution 17 | # mball.render_resolution = you decide 18 | 19 | # first metaball element doesn't exist yet - create it. 20 | ele = mball.elements.new() 21 | ele.co = (0.0, 0.0, 0.0) 22 | ele.use_negative = False 23 | ele.radius = 2.0 24 | 25 | # adding extra metaball elements to the object 26 | ele = mball.elements.new() 27 | ele.co = (-0.726, 1.006, 0.559) 28 | ele.use_negative = True 29 | ele.radius = 0.82279 30 | ``` 31 | 32 | ![metaball_img1](https://cloud.githubusercontent.com/assets/619340/10737697/eb99a57c-7c13-11e5-8f81-bde3960f91e4.png) -------------------------------------------------------------------------------- /OperatorTricks.md: -------------------------------------------------------------------------------- 1 | ### reusing functions from other classes / operators 2 | 3 | **One method** 4 | 5 | Most of the time if I have functionality that is shared between Operators, I'll take them out of the class and reference them in the operators. 6 | 7 | ```python 8 | 9 | def some_shared_function(caller, context): 10 | # ... 11 | return 12 | 13 | class A(bpy.types.Operator): 14 | (...) 15 | def execute(self,context): 16 | some_shared_function(self, context) 17 | 18 | class B(bpy.types.Operator): 19 | (...) 20 | def execute(self,context): 21 | # other code here 22 | some_shared_function(self, context) 23 | 24 | ``` 25 | 26 | **Another method** 27 | 28 | Or make the operator behave differently depending on the passed parameters 29 | 30 | ```python 31 | class AB(bpy.types.Operator): 32 | bl_idname = "wm.simple_multi_operator" 33 | bl_label = "Multi Purpose Operator" 34 | 35 | param_one = StringProperty() 36 | # param_two = StringProperty() 37 | 38 | def execute(self,context): 39 | 40 | if self.param_one == 'A': 41 | self.some_functionality(context) 42 | 43 | elif self.param_one == 'B': 44 | # some other code 45 | self.some_functionality(context) 46 | 47 | return {'FINISHED'} 48 | 49 | def some_functionality(self, context): 50 | ... 51 | 52 | ``` 53 | 54 | in your ui code you'd pass parameters like this 55 | 56 | ```python 57 | 58 | row = layout.row() 59 | opname = "wm.simple_multi_operator" 60 | row.operator(opname, text='A').param_one = 'A' 61 | row.operator(opname, text='B').param_one = 'B' 62 | 63 | # if you have more than one property for the operator 64 | op_two = row.operator(opname, text='B / Mode Y') 65 | op_two.param_one = 'B' 66 | op_two.param_two = 'Mode Y' 67 | 68 | ``` 69 | 70 | calling the operator from a script directly works this way 71 | 72 | ```python 73 | 74 | # or calling from a script 75 | bpy.ops.wm.simple_multi_operator(param_one='A') 76 | bpy.ops.wm.simple_multi_operator(param_one='B') 77 | 78 | # with more than one parameter pass the keywords and values 79 | bpy.ops.wm.simple_multi_operator(param_one='B', param_two='Mode Y') 80 | 81 | ``` 82 | There pros and cons with this method worth mentioning. 83 | 84 | - con: If you are in the habit of making tooltips for your Operators, this approach doesn't let you define a unique tooltip for the buttons. 85 | - pro: you can quickly give an Operator new functionality without declaring a whole new Operator 86 | 87 | 88 | **Aother method (using Python's classmethod decorator)** 89 | 90 | ```python 91 | import bpy 92 | 93 | 94 | class A(bpy.types.Operator): 95 | bl_idname = "object.simple_operator_a" 96 | bl_label = "Simple Object Operator A" 97 | 98 | def execute(self,context): 99 | self.some_function() 100 | return {'FINISHED'} 101 | 102 | @classmethod 103 | def some_function(cls, some_parameter='not woop'): 104 | print('some_parameter', some_parameter) 105 | 106 | class B(bpy.types.Operator): 107 | bl_idname = "object.simple_operator_b" 108 | bl_label = "Simple Object Operator B" 109 | 110 | def execute(self,context): 111 | A.some_function('woooop') 112 | return {'FINISHED'} 113 | 114 | 115 | def register(): 116 | bpy.utils.register_module(__name__) 117 | 118 | 119 | def unregister(): 120 | bpy.utils.unregister_module(__name__) 121 | 122 | 123 | if __name__ == "__main__": 124 | register() 125 | 126 | ``` 127 | 128 | then calling them: 129 | 130 | ```python 131 | >>> bpy.ops.object.simple_operator_a() 132 | some_parameter not woop 133 | {'FINISHED'} 134 | 135 | >>> bpy.ops.object.simple_operator_b() 136 | some_parameter woooop 137 | {'FINISHED'} 138 | 139 | ``` 140 | 141 | Not sure if this is helpful, but adding for completeness: 142 | 143 | ```python 144 | # autocomplete from the open parenthesis gives: 145 | >>> bpy.types.OBJECT_OT_simple_operator_a.some_function( 146 | some_function(cls, some_parameter='not woop') 147 | 148 | # calling the function, gives: 149 | >>> bpy.types.OBJECT_OT_simple_operator_a.some_function() 150 | some_parameter not woop 151 | ``` 152 | -------------------------------------------------------------------------------- /Operators.md: -------------------------------------------------------------------------------- 1 | When I try to think back to my earliest ventures in bpy programming, Operators stand out as a sticking point. They took a while to understand because I avoided learning about Python's Classes for a long time. Once I had a working knowledge of Python's Classes `bpy` Operators became a walk in the park. 2 | 3 | If you don't know much about Python Classes [take the tour here](http://www.learnpython.org/en/Classes_and_Objects), then continue on below 4 | 5 | Following that it's worth reading the code of a few add-ons that you have used, that way you'll be able to make connections between how it processes user input into actions. I also encourage you to read as much code in the `/addons` directory as you can stomach, you'll soon become familiar with the patterns and see conventions emerge. The [bpy docs also cover](http://www.blender.org/api/blender_python_api_current/info_quickstart.html?highlight=operator) the various forms of `Operators` in some detail - bookmark that. 6 | 7 | After writing several add-ons I decided to create small templates / snippets for my day-to-day editor SublimeText. Here you'll find a few useful [autocomplete snippets](https://github.com/zeffii/BlenderSublimeSnippets). This helps me get from idea to code faster than loading up Blender's TextEditor Templates. 8 | 9 | More operator Tricks: 10 | 11 | - [Reusing Functions between operators](OperatorTricks) 12 | 13 | ________ 14 | My favorite things about the API might be worth explicitly mentioning. 15 | 16 | ### Registration 17 | 18 | If you have an addon / script with one class (Panel, Operator..etc.), you might register/unregister it using this. 19 | ```python 20 | def register(): 21 | bpy.utils.register_class(YourClassName) 22 | 23 | def unregister(): 24 | bpy.utils.unregister_class(YourClassName) 25 | ``` 26 | 27 | As your add-on becomes more complex you'll want to register more than 1 class, but you probably don't want to be explicit about the class name for each new class. You can avoid writing code like this. 28 | 29 | ```python 30 | def register(): 31 | bpy.utils.register_class(ClassNameOne) 32 | bpy.utils.register_class(ClassNameTwo) 33 | bpy.utils.register_class(ClassNameThree) 34 | 35 | def unregister(): 36 | bpy.utils.unregister_class(ClassNameOne) 37 | bpy.utils.unregister_class(ClassNameTwo) 38 | bpy.utils.unregister_class(ClassNameThree) 39 | 40 | ``` 41 | 42 | Instead you can write something much shorter. This registers all `bpy` classes in the order that they appear in the file. 43 | 44 | ```python 45 | def register(): 46 | bpy.utils.register_module(__name__) 47 | 48 | def unregister(): 49 | bpy.utils.unregister_module(__name__) 50 | ``` 51 | 52 | ### Callbacks. 53 | 54 | When we don't want to write a Class for each Operator, we can write a _delegation class_. Where the `execute` function behaves differently depending on some variable that was passed. 55 | 56 | ```python 57 | def function_one(): 58 | ... 59 | 60 | def function_two(): 61 | ... 62 | 63 | def function_three(): 64 | ... 65 | 66 | 67 | class SomeReusableOperator(bpy.types.Operator): 68 | 69 | bl_idname = "wm.some_reusable_op" 70 | bl_label = "Short Name" 71 | 72 | fn_name = bpy.props.StringProperty(default='') 73 | 74 | def execute(self, context): 75 | exec(self.fn_name + '()') 76 | return {'FINISHED'} 77 | 78 | 79 | # in your ui layout draw code somewhere 80 | 81 | callback = "wm.some_reusable_op" 82 | col.operator(callback, text='function one').fn_name = 'function_one' 83 | col.operator(callback, text='function two').fn_name = 'function_two' 84 | col.operator(callback, text='function three').fn_name = 'function_three' 85 | 86 | ``` 87 | You don't need to use `exec()` in the execute function, for dynamic programming you have many options. Another is finding the function within `globals()`: 88 | 89 | ```python 90 | def execute(self, context): 91 | # self.fn_name stores the string name of the function 92 | # take from globals, the function named the same as self.fn_name 93 | globals()[self.fn_name]() 94 | ``` 95 | using a full if-else would work too.. but do you need to? maybe you do, maybe you don't. 96 | 97 | ```python 98 | def execute(self, context): 99 | if self.fn_name == 'function_one': 100 | function_one() 101 | elif self.fn_name == 'function_two': 102 | function_two() 103 | elif self.fn_name == 'function_three': 104 | function_three() 105 | return {'FINISHED'} 106 | ``` 107 | 108 | Maybe your functions take arguments. Your delegation class can have multiple properties besides `StringProperty`. Imagine the following 109 | 110 | ```python 111 | 112 | def function_one(): return None 113 | def function_two(): return None 114 | def function_three(): return None 115 | def function_four(): return None 116 | 117 | def function_five(a, b): 118 | print(a + b) 119 | 120 | 121 | class SomeReusableOperator(bpy.types.Operator): 122 | 123 | bl_idname = "wm.some_reusable_op" 124 | bl_label = "Short Name" 125 | 126 | param1 = bpy.props.IntProperty(default=20) 127 | param2 = bpy.props.IntProperty(default=20) 128 | fn_name = bpy.props.StringProperty(default='') 129 | 130 | def execute(self, context): 131 | if self.fn_name == 'function_five': 132 | function_five(param1, param2) 133 | else: 134 | exec(self.fn_name + '()') 135 | return {'FINISHED'} 136 | ``` 137 | The ui code to set the operator would then look like 138 | 139 | ```python 140 | callback = "wm.some_reusable_op" 141 | ... 142 | ... 143 | op_five = col.operator(callback, text='function five') 144 | op_five.fn_name = 'function_five' 145 | op_five.param1 = 30 146 | op_five.param2 = 25 147 | ``` 148 | You might not want to hardcode param1 and param2 (but sometimes that's exactly what you want). For dynamic properties accessed via a Panel you need to register them as scene properties 149 | 150 | ```python 151 | scn = bpy.contex.scene 152 | callback = "wm.some_reusable_op" 153 | ... 154 | ... 155 | col.prop(scn, 'scene_param_1', text='param 1') 156 | col.prop(scn, 'scene_param_2', text='param 2') 157 | op_five = col.operator(callback, text='function five') 158 | op_five.fn_name = 'function_five' 159 | op_five.param1 = scn.scene_param_1 160 | op_five.param2 = scn.scene_param_2 161 | 162 | 163 | # in your register / unregister functions (never forget to unregister too) 164 | 165 | def register(): 166 | ... 167 | bpy.types.Scene.scene_param_1 = IntProperty() 168 | bpy.types.Scene.scene_param_2 = IntProperty() 169 | 170 | def register(): 171 | ... 172 | del bpy.types.Scene.scene_param_1 173 | del bpy.types.Scene.scene_param_2 174 | ``` 175 | 176 | -------------------------------------------------------------------------------- /Parenting.md: -------------------------------------------------------------------------------- 1 | More examples of parenting, with [respect to dupliverts / duplifaces](Duplication). 2 | 3 | If you want to parent one object to another, don't use `bpy.ops`, use the `.parent` attribute instead. For example if you have a Cube and want it to be the parent of a Sphere. 4 | 5 | ```python 6 | objects = bpy.data.objects 7 | cube = objects['Cube'] 8 | sphere = objects['Sphere'] 9 | sphere.parent = cube 10 | ``` 11 | 12 | -------------------------------------------------------------------------------- /Particle-Systems.md: -------------------------------------------------------------------------------- 1 | ```python 2 | import bpy 3 | 4 | def quantize(original_obj, numdiv_shortest_side): 5 | 6 | # make a plane 7 | bpy.ops.mesh.primitive_plane_add(radius=0.5) 8 | plane = bpy.context.active_object 9 | 10 | # add particle system to original object 11 | ps = original_obj.modifiers.new("grid particles", type='PARTICLE_SYSTEM') 12 | psettings = ps.particle_system.settings 13 | 14 | # particle settings 15 | psettings.distribution = 'GRID' 16 | psettings.emit_from = 'FACE' 17 | psettings.physics_type = 'NO' 18 | psettings.grid_resolution = numdiv_shortest_side 19 | psettings.use_render_emitter = True 20 | psettings.show_unborn = True 21 | psettings.use_scale_dupli = True 22 | psettings.particle_size = 1.0 23 | psettings.render_type = 'OBJECT' 24 | psettings.dupli_object = plane 25 | 26 | plane.select = False 27 | 28 | original_obj.select = True 29 | bpy.context.scene.objects.active = original_obj 30 | bpy.ops.object.duplicates_make_real() 31 | original_obj.hide = True 32 | original_obj.select = False 33 | 34 | bpy.ops.object.make_local(type='SELECT_OBDATA') 35 | 36 | # find first of the new objects, to join the rest onto 37 | joiner = None 38 | for obj in bpy.data.objects: 39 | if not obj in {plane, original_obj}: 40 | joiner = obj 41 | bpy.context.scene.objects.active = obj 42 | break 43 | 44 | bpy.ops.object.delete(use_global=False) 45 | 46 | 47 | # join, and remove doubles 48 | bpy.ops.object.join() 49 | # bpy.ops.mesh.remove_doubles() 50 | 51 | 52 | numdiv_shortest_side = 21 53 | original_obj = bpy.context.active_object 54 | quantize(original_obj, numdiv_shortest_side) 55 | ``` -------------------------------------------------------------------------------- /Preface.md: -------------------------------------------------------------------------------- 1 | Welcome to this wiki/repository of Blender related python snippets. 2 | 3 | ### First let's get the elephant out of the room 4 | 5 | Are you free to use the code in this wiki for your own projects? Yes. My goal is to show working examples of the current Blender Python API. This wiki is intended to be only part of the learning process. If you already master Python I recommend going straight to [finding the docs](Preface#finding-docs) below. If that's not you and you don't know your way around Python's standard library then please do read on. 6 | 7 | ### OK. I get it, let's rock! 8 | 9 | > Teachers open the door, but you must enter by yourself. 10 | 11 | I've included a section in the Introduction called [General Python Snippets](GeneralPythonSnippets), it covers the most important parts of Python that you need to be comfortable with before doing any Blender scripting. The rest of the _Blender Recipes_ on this wiki assume you know that subset of Python. 12 | 13 | These snippets don't go into details about every line of code. The code hopefully explains itself by having clear variable names and a logical order of execution. I break code out into functions whenever possible to show that certain parts of code are cohesive blocks. I tend mostly to comment on larger parts of the code or those lines that are a little un-intuitive for a beginner. 14 | 15 | ### Finding docs 16 | 17 | I can show you endless examples of how to do things, but eventually you need to 'close the book' and get acquainted with the process of finding documentation and experimenting with Python and Blender's API till you find a workflow that works for you. 18 | 19 | In 2015 we got version independent docs: 20 | 21 | http://www.blender.org/api/blender_python_api_current/ 22 | http://www.blender.org/api/blender_python_api_current/search.html?q=bmesh 23 | http://www.blender.org/api/blender_python_api_current/mathutils.geometry.html 24 | 25 | I found the process of finding docs was a little slow so I coded an addon that uses Blender's Python console to execute non-python commands (Ctrl+Enter vs Enter). -------------------------------------------------------------------------------- /Properties.md: -------------------------------------------------------------------------------- 1 | Properties are what addons and operators use internally and on their UI. They are variables with potential side effects. An important feature of a property is they can call functions when their value is changed. To show what I mean let's take an abstract look. 2 | 3 | ```python 4 | import bpy 5 | from bpy.props import IntProperty 6 | ``` 7 | There are many more property types but the `IntProperty` is a convenient example. Take this hypothetical non-functioning piece of code 8 | 9 | ### Update function 10 | 11 | The panel code for this can be found in _TextEditors -> Templates -> Python -> UI Panel Simple_. 12 | 13 | ```python 14 | import bpy 15 | from bpy.props import IntProperty 16 | 17 | 18 | def my_update(self, context): 19 | print(self.some_int) 20 | 21 | class HelloWorldPanel(bpy.types.Panel): 22 | """Creates a Panel in the Object properties window""" 23 | bl_label = "Hello World Panel" 24 | bl_idname = "OBJECT_PT_hello" 25 | bl_space_type = 'PROPERTIES' 26 | bl_region_type = 'WINDOW' 27 | 28 | def draw(self, context): 29 | layout = self.layout 30 | obj = context.object 31 | row = layout.row() 32 | row.prop(context.scene, 'some_int') 33 | 34 | 35 | def register(): 36 | bpy.types.Scene.some_int = IntProperty(min=0, max=4, update=my_update) 37 | bpy.utils.register_class(HelloWorldPanel) 38 | 39 | 40 | def unregister(): 41 | bpy.utils.unregister_class(HelloWorldPanel) 42 | del bpy.types.Scene.some_int 43 | 44 | 45 | if __name__ == "__main__": 46 | register() 47 | 48 | ``` 49 | If you run the above code in TextEditor, it will add a panel to the Properties window, in the Scene Tab. It will look something like this: 50 | 51 | ![image](https://cloud.githubusercontent.com/assets/619340/11906029/106cc8b8-a5ca-11e5-9b15-25dbad98453d.png) 52 | 53 | It will also print the current value of `some_int` to the console when you change it. It does this because of the update function, it calls `some_update()` with `self, context` being provided for us. Inside the `some_update()` function we are printing `self.some_int`, and self in this case is the same as `bpy.context.scene`. But.. we don't know from the update function which Property was modified to trigger it. Don't let this side track you - There's a solution. 54 | 55 | ### Update function defined inline using lambda 56 | 57 | Now! let's say we want to do more dynamic stuff and want to know where the update came from. The update function can be defined _inline_. 58 | 59 | ```python 60 | import bpy 61 | from bpy.props import IntProperty 62 | 63 | 64 | def my_update(self, context, origin): 65 | print(origin, getattr(self, origin)) 66 | 67 | class HelloWorldPanel(bpy.types.Panel): 68 | """Creates a Panel in the Object properties window""" 69 | bl_label = "Hello World Panel" 70 | bl_idname = "OBJECT_PT_hello" 71 | bl_space_type = 'PROPERTIES' 72 | bl_region_type = 'WINDOW' 73 | 74 | def draw(self, context): 75 | layout = self.layout 76 | obj = context.object 77 | col = layout.column() 78 | col.prop(context.scene, 'some_int') 79 | col.prop(context.scene, 'some_other_int') 80 | 81 | 82 | def register(): 83 | bpy.types.Scene.some_int = IntProperty( 84 | min=0, 85 | update=lambda self, context: my_update(self, context, origin='some_int') 86 | ) 87 | 88 | bpy.types.Scene.some_other_int = IntProperty( 89 | min=0, 90 | update=lambda self, context: my_update(self, context, origin='some_other_int') 91 | ) 92 | 93 | bpy.utils.register_class(HelloWorldPanel) 94 | 95 | 96 | def unregister(): 97 | bpy.utils.unregister_class(HelloWorldPanel) 98 | del bpy.types.Scene.some_int 99 | del bpy.types.Scene.some_other_int 100 | 101 | 102 | if __name__ == "__main__": 103 | register() 104 | 105 | ``` 106 | 107 | ### Organizing properties (avoid namespace pollution) 108 | 109 | What's namespace pollution? It's like a classroom with 20 people, 6 of which are called Peter. Too much confusion regarding who's who - You need to ask their surnames to distinguish one from the other. This is called 'disambiguation'.. 110 | 111 | ### Property Group / Pointer Property 112 | 113 | ```python 114 | import bpy 115 | 116 | ## some panel somewhere 117 | def draw(self, context): 118 | self.layout.prop(context.scene.my_prop_grp, 'custom_1') 119 | 120 | 121 | class MyPropertyGroup(bpy.types.PropertyGroup): 122 | custom_1 = bpy.props.FloatProperty(name="My Float") 123 | custom_2 = bpy.props.IntProperty(name="My Int") 124 | 125 | def register(): 126 | bpy.utils.register_class(MyPropertyGroup) 127 | bpy.types.Scene.my_prop_grp = bpy.props.PointerProperty(type=MyPropertyGroup) 128 | 129 | def unregister(): 130 | bpy.utils.unregister_class(MyPropertyGroup) 131 | 132 | # deleting the group, removes automatically all children. :) 133 | del bpy.types.Scene.my_prop_grp 134 | 135 | 136 | ``` 137 | 138 | or like this.. 139 | 140 | ```python 141 | class SomeAddonProperties(bpy.types.PropertyGroup): 142 | 143 | @classmethod 144 | def register(cls): 145 | Scn = bpy.types.Scene 146 | 147 | Scn.some_addon_props = PointerProperty( 148 | name="some addon's internal global properties", 149 | description="some add-on uses these properties for shared properties between operators", 150 | type=cls, 151 | ) 152 | 153 | cls.custom_1 = bpy.props.FloatProperty(name="My Float") 154 | cls.custom_2 = bpy.props.IntProperty(name="My Int") 155 | 156 | 157 | @classmethod 158 | def unregister(cls): 159 | del bpy.types.Scene.some_addon_props 160 | 161 | 162 | def register(): 163 | ... 164 | bpy.utils.register_class(SomeAddonProperties) 165 | 166 | def unregister(): 167 | ... 168 | bpy.utils.unregister_class(SomeAddonProperties) 169 | ``` 170 | 171 | 172 | ### Iterating over a PropertyGroup (for use in UI for example) 173 | 174 | for UI you really want to do this inline to avoid extra function calls. 175 | 176 | ```python 177 | # imagine we have a PropertyGroup registered on bpy.types.Object 178 | # and called it `parametric_circle` 179 | 180 | props = obj.parametric_circle: 181 | col = l.column() 182 | for propname in props.rna_type.properties.keys(): 183 | if propname in {'rna_type', 'name'}: 184 | continue 185 | col.prop(props, propname) 186 | ``` 187 | 188 | but for one off lookups, it might be nice to have a helper function: 189 | 190 | ```python 191 | def get_props2(consumable): 192 | propnames = consumable.rna_type.properties.keys() 193 | for p in filter(lambda n: not n in {'rna_type', 'name'}, propnames): 194 | yield p 195 | 196 | # usage.. 197 | obj = bpy.context.active_object 198 | for i in get_props2(obj.parametric_circle): 199 | print(i) 200 | ``` 201 | 202 | ### Storing / Importing properties as json or dictionary literal 203 | 204 | Also known as serialization / deserialization. Code for that can be found here: [reading-and-writing-json-or-dict-literal](bpy_data_texts#reading-and-writing-json-or-dict-literal) -------------------------------------------------------------------------------- /PyGeom.md: -------------------------------------------------------------------------------- 1 | This page is mostly a code dump and not arranged for convenient reading. sorry. 2 | 3 | ### Matrix Multiplication 4 | 5 | matrix multiplication , matrix in , vlist in 6 | ```python 7 | def multiply_vectors(M, vlist): 8 | # (4*4 matrix) X (3*1 vector) 9 | 10 | for i, v in enumerate(vlist): 11 | # write _in place_ 12 | vlist[i] = ( 13 | M[0][0]*v[0] + M[0][1]*v[1] + M[0][2]*v[2] + M[0][3]* 1.0, 14 | M[1][0]*v[0] + M[1][1]*v[1] + M[1][2]*v[2] + M[1][3]* 1.0, 15 | M[2][0]*v[0] + M[2][1]*v[1] + M[2][2]*v[2] + M[2][3]* 1.0 16 | ) 17 | 18 | return vlist 19 | ``` 20 | 21 | ### Closest point on triangle 22 | 23 | from http://www.9math.com/book/projection-point-plane 24 | closest point on a triangle , input ( point and 3points ) 25 | ```python 26 | def distance_point_on_tri(point, p1, p2, p3): 27 | n = obtain_normal3(p1, p2, p3) 28 | d = n[0]*p1[0] + n[1]*p1[1] + n[2]*p1[2] 29 | return abs(point[0]*n[0] + point[1]*n[1] + point[2]*n[2] + d) / sqrt(n[0]*n[0] + n[1]*n[1] + n[2]*n[2]) 30 | ``` 31 | or easier to read.. 32 | ```python 33 | def distance_point_on_tri(point, p1, p2, p3): 34 | a, b, c = obtain_normal3(p1, p2, p3) 35 | x, y, z = p1 # pick any point of the triangle 36 | u, v, w = point 37 | d = a*x + b*y + c*z 38 | return abs(a*u + b*v + c*w + d) / sqrt(a*a + b*b + c*c) 39 | ``` 40 | 41 | ### Unsorted 42 | 43 | ```python 44 | import math 45 | 46 | def interp_v3_v3v3(a, b, t=0.5): 47 | if t == 0.0: return a 48 | elif t == 1.0: return b 49 | else: 50 | s = 1.0 - t 51 | return (s * a[0] + t * b[0], s * a[1] + t * b[1], s * a[2] + t * b[2]) 52 | 53 | def length(v): 54 | return math.sqrt((v[0] * v[0]) + (v[1] * v[1]) + (v[2] * v[2])) 55 | 56 | def normalize(v): 57 | l = math.sqrt((v[0] * v[0]) + (v[1] * v[1]) + (v[2] * v[2])) 58 | return [v[0]/l, v[1]/l, v[2]/l] 59 | 60 | def sub_v3_v3v3(a, b): 61 | return a[0]-b[0], a[1]-b[1], a[2]-b[2] 62 | 63 | def madd_v3_v3v3fl(a, b, f=1.0): 64 | return a[0]+b[0]*f, a[1]+b[1]*f, a[2]+b[2]*f 65 | 66 | def dot_v3v3(a, b): 67 | return a[0]*b[0]+a[1]*b[1]+a[2]*b[2] 68 | 69 | def isect_line_plane(l1, l2, plane_co, plane_no): 70 | u = l2[0]-l1[0], l2[1]-l1[1], l2[2]-l1[2] 71 | h = l1[0]-plane_co[0], l1[1]-plane_co[1], l1[2]-plane_co[2] 72 | dot = plane_no[0]*u[0] + plane_no[1]*u[1] + plane_no[2]*u[2] 73 | 74 | if abs(dot) > 1.0e-5: 75 | f = -(plane_no[0]*h[0] + plane_no[1]*h[1] + plane_no[2]*h[2]) / dot 76 | return l1[0]+u[0]*f, l1[1]+u[1]*f, l1[2]+u[2]*f 77 | else: 78 | # parallel to plane 79 | return False 80 | 81 | def obtain_normal3(p1, p2, p3): 82 | # http://stackoverflow.com/a/8135330/1243487 83 | return [ 84 | ((p2[1]-p1[1])*(p3[2]-p1[2]))-((p2[2]-p1[2])*(p3[1]-p1[1])), 85 | ((p2[2]-p1[2])*(p3[0]-p1[0]))-((p2[0]-p1[0])*(p3[2]-p1[2])), 86 | ((p2[0]-p1[0])*(p3[1]-p1[1]))-((p2[1]-p1[1])*(p3[0]-p1[0])) 87 | ] 88 | 89 | def mean(verts): 90 | vx, vy, vz = 0, 0, 0 91 | for v in verts: 92 | vx += v[0] 93 | vy += v[1] 94 | vz += v[2] 95 | numverts = len(verts) 96 | return vx/numverts, vy/numverts, vz/numverts 97 | 98 | 99 | def is_reasonably_opposite(n, normal_one): 100 | return dot_v3v3(normalized(n), normalized(normal_one)) < 0.0 101 | 102 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BlenderPython 2 | 3 | [Blender Python(英文)](https://github.com/BlenderCN/BlenderPython/blob/master/README_en.md) 4 | 5 | [Blender Python 插件开发(中文)](https://github.com/BlenderCN/BlenderPython/blob/master/Blender_Addons_Development.md) 6 | 7 | [Blender python 脚本小示例](https://github.com/BlenderCN/BlenderPython/blob/master/Blender_python_sample/README.md) 8 | 9 | [平时写blender插件时遇到的Blender python Api 2.7到2.8的更改日志记录](https://github.com/BlenderCN/BlenderPython/blob/master/BlenderPythonApiChange.md) 10 | -------------------------------------------------------------------------------- /README_en.md: -------------------------------------------------------------------------------- 1 | # BlenderPython 2 | 3 | - [main](https://github.com/BlenderCN/BlenderPython/blob/master/Home.md) 4 | 5 | Introduction 6 | 7 | - [Preface](https://github.com/BlenderCN/BlenderPython/blob/master/Preface.md) 8 | - [General Python snippets](https://github.com/BlenderCN/BlenderPython/blob/master/GeneralPythonSnippets.md) 9 | - [Python Geometry snippets](https://github.com/BlenderCN/BlenderPython/blob/master/PyGeom.md) 10 | 11 | Objects/Mesh/BMesh 12 | 13 | - [Empty-null object](https://github.com/BlenderCN/BlenderPython/blob/master/Empty-(null-object).md) 14 | - [Mesh](https://github.com/BlenderCN/BlenderPython/blob/master/Mesh.md) 15 | - [Bmesh](https://github.com/BlenderCN/BlenderPython/blob/master/BMesh.md) 16 | - [bmesh.ops-primitives](https://github.com/BlenderCN/BlenderPython/blob/master/bmesh_ops_primitives.md) 17 | - [bmesh.ops-mesh ops](https://github.com/BlenderCN/BlenderPython/blob/master/bmesh_ops_meshops.md) 18 | - [Curves(2d,3d)](https://github.com/BlenderCN/BlenderPython/blob/master/Curves.md) 19 | - [Text(Font Objeccts)](https://github.com/BlenderCN/BlenderPython/blob/master/Text.md) 20 | - [Dupication(instancing)](https://github.com/BlenderCN/BlenderPython/blob/master/Duplication.md) 21 | - [Metaballs](https://github.com/BlenderCN/BlenderPython/blob/master/Metaballs.md) 22 | 23 | Time and Motion 24 | 25 | - [scripted keyframes](https://github.com/BlenderCN/BlenderPython/blob/master/Scripted-Keyframes.md) 26 | - [Event Handlers]() 27 | - [Drives](https://github.com/BlenderCN/BlenderPython/blob/master/Drivers.md) 28 | 29 | Miscellaneous bpy.data.* 30 | 31 | - [.materials(Nodes)]() 32 | - [.texts]() 33 | - [.images(Pixel scripting]() 34 | 35 | Order/Organization 36 | 37 | - [Grouping]() 38 | - [Parenting](https://github.com/BlenderCN/BlenderPython/blob/master/Parenting.md) 39 | - [Layers](https://github.com/BlenderCN/BlenderPython/blob/master/Layers.md) 40 | - [UI/Layout]() 41 | - [Custom Icons]() 42 | 43 | Miscellaneous 44 | 45 | - [Mathutils]() 46 | - [Modifiers]() 47 | - [Particle Systems](https://github.com/BlenderCN/BlenderPython/blob/master/Particle-Systems.md) 48 | - [vertex colors](https://github.com/BlenderCN/BlenderPython/blob/master/Vertex-Colors.md) 49 | - [Textures UV DPI]() 50 | - [Properties](https://github.com/BlenderCN/BlenderPython/blob/master/Properties.md) 51 | - [Operators(and callbacks)]() 52 | - [bgl/blf](https://github.com/BlenderCN/BlenderPython/blob/master/bgl_blf.md) 53 | - [Grease Pencil](https://github.com/BlenderCN/BlenderPython/blob/master/GreasePencil.md) 54 | - [Themes]() 55 | - [Areas](https://github.com/BlenderCN/BlenderPython/blob/master/Areas.md) 56 | 57 | Add-ons 58 | 59 | - [introduction](https://github.com/BlenderCN/BlenderPython/blob/master/Addons_Introduction.md) 60 | - [import/export]() 61 | - [Text Editor]() 62 | - [Custom Nodes]() 63 | 64 | 65 | [link](https://github.com/zeffii/BlenderPythonRecipes/wiki) 66 | -------------------------------------------------------------------------------- /Scripted-Keyframes.md: -------------------------------------------------------------------------------- 1 | ### Snippet 1 (scripted turntable 2 | 3 | Parent camera to an empty, keyframe the rotation of the empty. 4 | 5 | ```python 6 | import math 7 | import bpy 8 | 9 | def add_cam(location, rotation): 10 | bpy.ops.object.camera_add(location=location, rotation=rotation) 11 | return bpy.context.active_object 12 | 13 | def add_empty(location): 14 | bpy.ops.object.empty_add(location=location) 15 | return bpy.context.active_object 16 | 17 | cam = add_cam(location=(0, -5, 0), rotation=(math.pi/2, 0, 0)) 18 | empty = add_empty(location=(0, 0, 0)) 19 | cam.parent = empty 20 | 21 | num_frames = 90 22 | gamma = math.pi * 2 / num_frames 23 | for i in range(1, num_frames+1): 24 | empty.rotation_euler[2] = gamma * i 25 | empty.keyframe_insert(data_path='rotation_euler', frame=i, index=-1) 26 | ``` 27 | 28 | > See: [this page](https://github.com/zeffii/BlenderPythonRecipes/wiki/Empty-(null-object)) for more ways to create an Empty -------------------------------------------------------------------------------- /Text.md: -------------------------------------------------------------------------------- 1 | disambiguation: If you're looking for `bpy.data.texts` _text datablocks_ go [here](bpy_data_texts) 2 | 3 | ### Text Objects / Font Objects 4 | 5 | In Blender _Text Objects_ are `type == 'FONT'`, and are closely related to the Curve data type. The big difference is that the individual glyphs can't be edited. You must convert to `type == 'CURVE'` first. Text Objects have many parameters. The most important parameter of the `'FONT'` type is possibly the font face / family (like Helvetica,..Gill Sans). Confusingly this property is called `.font`. 6 | 7 | ```python 8 | 9 | import bpy 10 | 11 | def make_text_object(name, txt, props): 12 | scene = bpy.context.scene 13 | curves = bpy.data.curves 14 | objects = bpy.data.objects 15 | 16 | name = props.get(name, 'default name') 17 | 18 | # CURVES 19 | if not (name in curves): 20 | f = curves.new(name, 'FONT') 21 | else: 22 | f = curves[name] 23 | 24 | # CONTAINER OBJECTS 25 | if name in objects: 26 | obj = objects[name] 27 | else: 28 | obj = objects.new(name, f) 29 | scene.objects.link(obj) 30 | 31 | # there's two checks, 1) did we pass a font name, 2) is it valid. 32 | default = bpy.data.fonts.get('Bfont') 33 | if props.get('fontname'): 34 | f.font = bpy.data.fonts.get(props['fontname'], default) 35 | else: 36 | f.font = default 37 | 38 | f.body = txt 39 | 40 | setters = [ 41 | 'size', 42 | # space 43 | 'space_character', 44 | 'space_word', 45 | 'space_line', 46 | 'offset_x', 47 | 'offset_y', 48 | # modifications 49 | 'offset', 50 | 'extrude', 51 | # bevel 52 | 'bevel_depth', 53 | 'bevel_resolution', 54 | 'align' 55 | ] 56 | 57 | # some dynamic programming 58 | for propname in setters: 59 | if props.get(propname): 60 | setattr(f, propname, props.get(propname)) 61 | 62 | return obj 63 | 64 | make_text_object('my_text_object', 'far out man', {}) 65 | ``` -------------------------------------------------------------------------------- /Themedata.md: -------------------------------------------------------------------------------- 1 | I like to switch themes for taking screenshots vs doing longer modeling sessions. 2 | 3 | This is definitely a work in progress. Especially 3dview has a number of settings that need special attention for a script to serialize and deserialize the state. 4 | 5 | ```python 6 | import bpy 7 | 8 | 9 | def get_props2(consumable): 10 | propnames = consumable.rna_type.properties.keys() 11 | for p in filter(lambda n: not n in {'rna_type', 'name'}, propnames): 12 | yield p 13 | 14 | current_theme = bpy.context.user_preferences.themes.items()[0][0] 15 | f = bpy.context.user_preferences.themes[current_theme].view_3d 16 | 17 | print('--') 18 | 19 | for prop in get_props2(f): 20 | value = getattr(f, prop) 21 | 22 | try: 23 | can_iterate = iter(value) 24 | print(prop, value[:]) 25 | except TypeError: 26 | if prop == 'space': 27 | print(prop) 28 | else: 29 | print(prop, value) 30 | 31 | 32 | # print(type(value)) 33 | ``` 34 | ### brutal exporter for 3dview theme 35 | 36 | this combines information found in [serializating properties](bpy_data_texts#reading-and-writing-json-or-dict-literal) and [iterating over properties](Properties#iterating-over-a-propertygroup-for-use-in-ui-for-example) 37 | 38 | ```python 39 | import bpy 40 | import json 41 | 42 | def get_props2(consumable): 43 | propnames = consumable.rna_type.properties.keys() 44 | for p in filter(lambda n: not n in {'rna_type', 'name'}, propnames): 45 | yield p 46 | 47 | current_theme = bpy.context.user_preferences.themes.items()[0][0] 48 | view = bpy.context.user_preferences.themes[current_theme].view_3d 49 | 50 | print('--') 51 | theme_dict = {} 52 | 53 | for prop in get_props2(view): 54 | value = getattr(view, prop) 55 | 56 | try: 57 | can_iterate = iter(value) 58 | # print(prop, value[:]) 59 | theme_dict[prop] = [round(v, 5) for v in value[:]] 60 | except TypeError: 61 | 62 | # skip for now.. 63 | if prop == 'space': 64 | space = getattr(view, prop) 65 | # print(prop, dir(space)) 66 | else: 67 | # print(prop, value) 68 | theme_dict[prop] = value 69 | 70 | texts = bpy.data.texts 71 | # dump string 72 | m = json.dumps(theme_dict, sort_keys=True, indent=2) 73 | theme_file_name = 'my_theme.json' 74 | 75 | text_block = texts.get(theme_file_name) 76 | if not text_block: 77 | text_block = texts.new(theme_file_name) 78 | 79 | text_block.from_string(m) 80 | ``` 81 | 82 | 83 | -------------------------------------------------------------------------------- /UV---DPI-(variable-or-homogeneous).md: -------------------------------------------------------------------------------- 1 | some assumptions about the whole thing: 2 | 3 | - square image 4 | - you use Meters of Blender Units. (Else the `texture_dpi` function changes a little) 5 | - you pass the correct image name (or image of equivalent dimensions) 6 | - you have an active uvmap 7 | - you are in edit mode. 8 | 9 | Insight can also be found at _TextEditor -> Templates -> Python -> Operator Mesh UV_ 10 | 11 | ```python 12 | import math 13 | 14 | import bpy 15 | import bmesh 16 | from mathutils import Vector 17 | 18 | 19 | def texture_dpi(polygon_area, uv_area, image_dim): 20 | """ 21 | polygon_area: in (meters or BU) 22 | uv_area: in ratio (0..1) 23 | image_dim: either width or height of image, this assumes 24 | square images anyway, else none of this makes sense 25 | """ 26 | 27 | l1 = math.sqrt(polygon_area) 28 | l2 = math.sqrt(uv_area) 29 | 30 | # assume the units are BU or meter. 31 | inches = l1 * 39.3701 32 | pixels = int(l2 * image_dim) 33 | return int(pixels / inches) 34 | 35 | 36 | def calc_area_from_2d_vectorlist(v): 37 | # http://www.mathopenref.com/coordpolygonarea.html 38 | sum = 0 39 | n = len(v) 40 | for i in range(n - 1): 41 | sum += ((v[i].x * v[i + 1].y) - (v[i].y * v[i + 1].x)) 42 | sum += ((v[n - 1].x * v[0].y) - (v[n - 1].y * v[0].x)) 43 | return abs(sum / 2) 44 | 45 | 46 | def get_bm_from_edit_object(image_name): 47 | obj = bpy.context.edit_object 48 | me = obj.data 49 | bm = bmesh.from_edit_mesh(me) 50 | 51 | uv_layer = bm.loops.layers.uv.verify() 52 | bm.faces.layers.tex.verify() 53 | 54 | totals = [] 55 | for f in bm.faces: 56 | vl = [l[uv_layer].uv for l in f.loops] 57 | fa = calc_area_from_2d_vectorlist(vl) # 2d area of uv loop 58 | totals.append([f.calc_area(), fa]) # 2d area of face (local size) 59 | break # if homogeneous , else comment it out. 60 | 61 | image_dim = bpy.data.images[image_name].size[0] 62 | for polygon_area, uv_area in totals: 63 | dpi = texture_dpi(polygon_area, uv_area, image_dim) 64 | print(dpi) 65 | 66 | get_bm_from_edit_object('some_texture.png') 67 | ``` 68 | 69 | on this line `break # if homogeneous , else comment it out.` I've left the break statement to stop iteration and just use the first polygon. The assumption is that all your polygons have the same amount of area per pixel, and you can calculate DPI once and the value will be the same for all other polygons. 70 | 71 | Sometimes you might have textures and uvmaps that intentionally rescale certain uv_islands to get more pixels in a smaller space, these islands can be calculated independently or simply by the index of one of the faces of the island - exercise for the reader. -------------------------------------------------------------------------------- /Vertex-Colors.md: -------------------------------------------------------------------------------- 1 | more bla -------------------------------------------------------------------------------- /_Sidebar.md: -------------------------------------------------------------------------------- 1 | [main](https://github.com/zeffii/BlenderPythonRecipes/wiki) 2 | 3 | Introduction 4 | 5 | - [Preface](Preface) 6 | - [General Python snippets](GeneralPythonSnippets) 7 | - [Python Geometry snippets](PyGeom) 8 | 9 | ______ 10 | Objects / Mesh / BMesh 11 | 12 | - [Empty - null object](Empty-(null-object)) 13 | - [Mesh](Mesh) 14 | - [Bmesh](BMesh) 15 | - [bmesh.ops - primitives](bmesh_ops_primitives) 16 | - [bmesh.ops - mesh ops](bmesh_ops_meshops):wrench: 17 | - [Curves (2d, 3d)](Curves) 18 | - [Text (Font Objects)](Text) 19 | - [Duplication (instancing)](Duplication) 20 | - [Metaballs](Metaballs) 21 | 22 | Time and Motion 23 | 24 | - [scripted keyframes](Keyframes):lock: 25 | - [Event Handlers](EventHandlers):lock: 26 | - [Drivers](Drivers) 27 | 28 | Miscellaneous `bpy.data.*` 29 | 30 | - [.materials (Nodes)](bpy_data_materials):lock: 31 | - [.texts](bpy_data_texts) 32 | - [.images (Pixel scripting)](Image_Pixels) 33 | 34 | Order / Organization 35 | 36 | - [Grouping](Grouping):lock: 37 | - [Parenting](Parenting):lock: 38 | - [Layers](Layers) 39 | - [UI / Layout](Layout):lock: 40 | - [Custom Icons](icons) 41 | 42 | Miscellaneous 43 | 44 | - [Mathutils](mathutils):lock: 45 | - [Modifiers](Modifiers):lock: 46 | - [Particle Systems](Particle-Systems):lock: 47 | - [vertex colors](VertexColors):lock: 48 | - [Textures UV DPI](UV---DPI-(variable-or-homogeneous)) 49 | - [Properties](Properties):wrench: 50 | - [Operators (and callbacks)](Operators):wrench: 51 | - [bgl / blf](bgl_blf):lock: 52 | - [Grease Pencil](GreasePencil) 53 | - [Themes](Themedata) 54 | - [Areas](Areas) 55 | 56 | Add-ons 57 | 58 | - [introduction](Addons_Introduction):lock: 59 | - [import / export](IO):lock: 60 | - [Text Editor](TextEditor):lock: 61 | - [Custom Nodes](CustomNodes):lock: -------------------------------------------------------------------------------- /bgl_blf.md: -------------------------------------------------------------------------------- 1 | October 2015 2 | 3 | This topic is something people tend to gravitate towards once they've grasped the notions of Meshes: vertices, normals, indices, Vectors and Matrices - and the trigonometry and Linear Algebra used to perform calculations / transformations on them. This page will assume you are at that stage. Blender comes with many convenience functions to get you started. 4 | 5 | Else there is [further reading](Further_Reading_LA) to accompany this if things aren't making sense. 6 | 7 | 2d text in the viewport is drawn using `blf` see below. 8 | 9 | ## BGL 10 | 11 | This is a python wrapper around openGL drawing commands. openGL is a big topic, but once you understand the elementary concepts (which I intend to cover here) the learning curve for new openGL concepts gets less steep. It's possible to accomplish a lot without really knowing much about how openGL or the wrapper works. 12 | 13 | You want to draw stuff. We could start with drawing 3d lines in the viewport with bgl, but Blender comes with a nice template to draw 2d lines. Let's start minimal. 14 | 15 | _TextEditor -> Templates -> Python -> 3dView Modal Draw_ 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /bmesh_ops_meshops.md: -------------------------------------------------------------------------------- 1 | `bmesh.ops` brings a lot of convenience and power to the scripter. All `bmesh.ops` are found in the current API docs: [blender.org/api/blender_python_api_current/search.html?q=bmesh.ops](http://www.blender.org/api/blender_python_api_current/search.html?q=bmesh.ops) 2 | 3 | bmesh.ops [docs have a few good examples](http://www.blender.org/api/blender_python_api_current/bmesh.ops.html?highlight=bmesh.ops#module-bmesh.ops) which are recommended reading. Additionally _TextEditor > Templates > Python_ has two _Bmesh Simple_ templates that show how to get the bmesh representation of an Object's mesh (both in edit mode and object mode). 4 | 5 | ### Example 1 Recalc Normals 6 | 7 | If you need to see them in context think about the following case. You have a bmesh generated procedurally and can't guarantee the direction of the faces (they might be pointing outwards or inwards depending on the order in which you specify the vertex indices for each face). 8 | 9 | This shows how to: 10 | 11 | 1. Mesh to bmesh (bm) 12 | 2. using bmesh.ops on the bm, 13 | 3. pushing the bm back to the Mesh. 14 | 15 | 16 | ```python 17 | import bpy 18 | import bmesh 19 | 20 | verts = [ 21 | (1.0, 1.0, -1.0), 22 | (1.0, -1.0, -1.0), 23 | (-1.0, -1.0, -1.0), 24 | (-1.0, 1.0, -1.0), 25 | (1.0, 1.0, 1.0), 26 | (1.0, -1.0, 1.0), 27 | (-1.0, -1.0, 1.0), 28 | (-1.0, 1.0, 1.0)] 29 | 30 | faces = [ 31 | (0, 1, 2, 3), 32 | (4, 5, 6, 7), # reversed 33 | (0, 4, 5, 1), 34 | (1, 5, 6, 2), 35 | (2, 6, 7, 3), 36 | (4, 0, 3, 7)] 37 | 38 | def make_object(name, verts, faces, normal_recalc=True): 39 | 40 | scn = bpy.context.scene 41 | 42 | mesh = bpy.data.meshes.new(name + "_Mesh") 43 | mesh.from_pydata(verts, [], faces) 44 | mesh.update() 45 | 46 | if normal_recalc: 47 | bm = bmesh.new() 48 | bm.from_mesh(mesh) 49 | bmesh.ops.recalc_face_normals(bm, faces=bm.faces) 50 | bm.to_mesh(mesh) 51 | bm.free() 52 | 53 | ob = bpy.data.objects.new(name, mesh) 54 | scn.objects.link(ob) 55 | 56 | make_object('my_cube', verts, faces) 57 | # make_object(name, verts, faces, normal_recalc=False) 58 | ``` 59 | 60 | The extra bmesh operation is what recalculates the face normals. This does a good job of making the face directions consistent with surrounding faces. 61 | 62 | ### Example 2 Doubles and Normals (chained bmesh.ops) 63 | 64 | ```python 65 | import bpy 66 | import bmesh 67 | 68 | for obj in bpy.context.selected_objects: 69 | 70 | bm = bmesh.new() # create an empty BMesh 71 | bm.from_mesh(obj.data) # fill it in from a Mesh 72 | 73 | d = 0.0001 74 | bm_verts = bm.verts[:] 75 | bmesh.ops.remove_doubles(bm, verts=bm_verts, dist=d) 76 | 77 | bm_faces = bm.faces[:] 78 | bmesh.ops.recalc_face_normals(bm, faces=bm_faces) 79 | 80 | bm.to_mesh(obj.data) 81 | bm.free() 82 | ``` 83 | 84 | ### Example 3 equivalent of screw modifier using bmesh.ops 85 | 86 | ```python 87 | import bpy 88 | import bmesh 89 | from bmesh.ops import spin 90 | import math 91 | 92 | 93 | def lathe_geometry(bm, cent, axis, dvec, angle, steps, remove_doubles=True, dist=0.0001): 94 | geom = bm.verts[:] + bm.edges[:] 95 | 96 | # super verbose explanation. 97 | spin( 98 | bm, 99 | geom=geom, # geometry to use for the spin 100 | cent=cent, # center point of the spin world 101 | axis=axis, # axis, a (x, y, z) spin axis 102 | dvec=dvec, # offset for the center point 103 | angle=angle, # how much of the unit circle to rotate around 104 | steps=steps, # spin subdivision level 105 | use_duplicate=0) # include existing geometry in returned content 106 | 107 | if remove_doubles: 108 | bmesh.ops.remove_doubles(bm, verts=bm.verts[:], dist=dist) 109 | 110 | obj = bpy.data.objects['Graph'] 111 | bm = bmesh.new() 112 | bm.from_mesh(obj.data) 113 | 114 | axis = (0,0,1) 115 | dvec = (0,0,0) 116 | angle = 2*math.pi 117 | steps = 20 118 | cent = obj.location 119 | 120 | lathe_geometry(bm, cent, axis, dvec, angle, steps, remove_doubles=True, dist=0.0001) 121 | 122 | bm.to_mesh(obj.data) 123 | obj.data.update() # <--- not strictly needed, Blender will update itself often anyway. 124 | bm.free() 125 | ``` 126 | In the code I reference a Mesh object called 'Graph'. The name is arbitrary, the function of the mesh is to act as the spin profile. In other applications this is called 'Lathe'. 127 | 128 | ![image spin](https://cloud.githubusercontent.com/assets/619340/11323690/afde11f6-9119-11e5-95de-6e57bb4c71c3.png) 129 | 130 | ### bmesh.ops.bevel example 131 | 132 | ![image](https://cloud.githubusercontent.com/assets/619340/12142838/78cfd854-b47b-11e5-99e0-a93edf8aae83.png) 133 | 134 | herer using a snippet of scripted node, (Sverchok) 135 | 136 | ```python 137 | import bmesh 138 | import sverchok 139 | from sverchok.utils.sv_bmesh_utils import bmesh_from_pydata, pydata_from_bmesh 140 | 141 | def do_bevel(idx, radius, segments, profile, verts, edges): 142 | 143 | if idx > (len(verts) - 1): 144 | return [], [] 145 | 146 | print(' ----', verts, edges) 147 | 148 | bm = bmesh_from_pydata(verts, edges, []) 149 | 150 | bmesh.ops.bevel( 151 | bm, 152 | geom=[v for v in bm.verts if v.index==idx], 153 | offset=radius, 154 | offset_type=0, 155 | segments=segments, 156 | vertex_only=True, 157 | profile=profile, 158 | material=0, 159 | loop_slide=True, 160 | clamp_overlap=True 161 | ) 162 | 163 | verts, edges, _ = pydata_from_bmesh(bm) 164 | return [verts], [edges] 165 | 166 | 167 | def sv_main(verts=[[]], edges=[[]], radius=0.3, index=1, segments=5, profile=0.2): 168 | verts_out = [] 169 | edges_out = [] 170 | 171 | in_sockets = [ 172 | ['v', 'some_socket_name', verts], 173 | ['s', 'edges', edges], 174 | ['s', 'radius', radius], 175 | ['s', 'index', index], 176 | ['s', 'segments', segments], 177 | ['s', 'profile', profile] 178 | ] 179 | 180 | if verts and verts[0]: 181 | if edges and edges[0]: 182 | verts, edges = do_bevel(index, radius, segments, profile, verts[0], edges[0]) 183 | verts_out.extend(verts) 184 | edges_out.extend(edges) 185 | 186 | out_sockets = [ 187 | ['v', 'verts', [verts_out]], 188 | ['s', 'edges', [edges_out]] 189 | ] 190 | print(out_sockets) 191 | 192 | return in_sockets, out_sockets 193 | ``` 194 | 195 | -------------------------------------------------------------------------------- /bmesh_ops_primitives.md: -------------------------------------------------------------------------------- 1 | ### bmesh.ops primitives 2 | ____ 3 | 4 | - bmesh.ops.create_grid 5 | - bmesh.ops.create_uvsphere 6 | - bmesh.ops.create_icosphere 7 | - bmesh.ops.create_monkey 8 | - bmesh.ops.create_cone 9 | - bmesh.ops.create_circle 10 | - bmesh.ops.create_cube 11 | 12 | Currently bpy docs lists the above primitives, but search the docs with `bmesh.ops.create` for the full list of primitives and their parameter lists. 13 | 14 | To create a uv sphere with `bmesh.ops`: 15 | 16 | ```python 17 | import bpy 18 | import bmesh 19 | 20 | def create_uv_sphere(name, u=5, v=4, d=1): 21 | # bmesh.ops.create_uvsphere also accepts a matrix keyword argument, 22 | # which i've dropped from the example. 23 | bm = bmesh.new() 24 | bmesh.ops.create_uvsphere(bm, u_segments=u, v_segments=v, diameter=d) 25 | 26 | # create new empty mesh and fill with uvsphere 27 | mesh = bpy.data.meshes.new(name + "_mesh") 28 | bm.to_mesh(mesh) 29 | bm.free() 30 | 31 | # create object and link to scene 32 | obj = bpy.data.objects.new(name, mesh) 33 | bpy.context.scene.objects.link(obj) 34 | return obj 35 | 36 | create_uv_sphere('my_uvsphere', u=5, v=4, d=1) 37 | 38 | ``` 39 | 40 | During your experimentation you might encounter errors when you pick and mix which arguments to use. 41 | If you get `SyntaxError: non-keyword arg after keyword arg` it's [explained here in the python docs](https://docs.python.org/3.4/tutorial/controlflow.html#keyword-arguments). Reading that will increase your Python-fu! 42 | -------------------------------------------------------------------------------- /bpy_data_materials.md: -------------------------------------------------------------------------------- 1 | Cycles Materials (Nodes, no Nodes) 2 | Internal Materials (Nodes, no Nodes) 3 | 4 | ## Cycles Materials - scripted 5 | 6 | ### Node Groups 7 | 8 | Perhaps you want to manually define materials and node groups, and import ('append') them from another `.blend` via a script. 9 | ```python 10 | todo 11 | 12 | ``` 13 | 14 | ### Nodes 15 | 16 | Scripting a Cycles node tree is quite easy. The only thing I find moderately annoying about it is that if I want the node tree to be user-ready (spread out a little) then I must pass the locations of the nodes explicitly. If you don't pass locations all nodes are added to `(x=0, y=0)`. You might not care, and if you don't expect any user interaction with the nodes then you certainly don't need to add locations. 17 | 18 | ```python 19 | todo 20 | ``` 21 | 22 | 23 | **NodeArrange to the rescue** 24 | 25 | Luckily there's a small [addon by JuhaW called NodeArrange](https://github.com/JuhaW/NodeArrange) that is able to spread the nodes out in a tree form. 26 | 27 | ```python 28 | import bpy 29 | 30 | import addon_utils 31 | addon_utils.enable("NodeArrange-master") 32 | 33 | mat = bpy.data.materials.new('Demo2') 34 | mat.use_nodes = True 35 | nodes = mat.node_tree.nodes 36 | 37 | # remove default diffuse node, get output node 38 | nodes.remove(nodes.get('Diffuse BSDF')) 39 | material_out = nodes.get('Material Output') 40 | 41 | bsdf1 = nodes.new(type='ShaderNodeBsdfDiffuse') 42 | bsdf1.inputs[0].default_value = 0, 0.2, 0.9, 1.0 43 | 44 | bsdf2 = nodes.new(type='ShaderNodeBsdfDiffuse') 45 | bsdf2.inputs[0].default_value = 0.8, 0.2, 0.9, 1.0 46 | 47 | value1 = nodes.new(type='ShaderNodeValue') 48 | mixer1 = nodes.new(type='ShaderNodeMixShader') 49 | 50 | links = mat.node_tree.links 51 | links.new(value1.outputs[0], mixer1.inputs[0]) 52 | links.new(bsdf1.outputs[0], mixer1.inputs[1]) 53 | links.new(bsdf2.outputs[0], mixer1.inputs[2]) 54 | links.new(mixer1.outputs[0], material_out.inputs[0]) 55 | 56 | bpy.ops.node.arrange_nodetree(mat_name=material_name) 57 | 58 | # -- or even -- 59 | # bpy.ops.node.arrange_nodetree( 60 | # mat_name=material_name, margin_x=140, margin_y=120 61 | # ) 62 | ``` 63 | 64 | ### No Nodes 65 | 66 | 67 | -------------------------------------------------------------------------------- /bpy_data_texts.md: -------------------------------------------------------------------------------- 1 | disambiguation: If you're looking for `Font / Text Object` go [here](Text) 2 | 3 | ### Text DataBlock (text files inside Blender) 4 | ____ 5 | Text data-blocks are used for storing all kinds of things. 6 | 7 | - notes 8 | - scripts 9 | - data (coordinates, data structures, relationships, etc.,) 10 | - essentially anything that can be stored as a string(*). 11 | 12 | Creating a new text datablock with content is as simple as this: 13 | 14 | ```python 15 | some_str = "Hello\nMy name is\ngobbledigook" 16 | f = bpy.data.texts.new('new_text.txt') 17 | f.from_string(some_str) 18 | ``` 19 | 20 | You might find that it can be useful to indicate what kind of data the _text datablock_ stores. Just like variables, it's useful to name the text datablocks in a meaningful way. The addition of the file-type extension is not mandatory, but often it can describe quickly how the data is arranged. 21 | 22 | ### reading and writing _json_ or _dict literal_ 23 | 24 | Both methods below work only on dicts that can be stringified, so they can't contain Objects. 25 | 26 | **json** 27 | 28 | JSON can easily be constructed from a dictionary and written to `bpy.data.texts` as a string. Imagine you have some dict of information, call it `my_dict`. To write that dict as a JSON to the .blend file you do: 29 | 30 | ```python 31 | import bpy 32 | import json 33 | 34 | my_dict = { 35 | 'key1': 'some string storage', 36 | 'key2': ['some', 'list', 'storage'], 37 | 'key3': 5 38 | } 39 | 40 | # dump string 41 | m = json.dumps(my_dict, sort_keys=True, indent=2) 42 | text_block = bpy.data.texts.new('my_storage.json') 43 | text_block.from_string(m) 44 | ``` 45 | 46 | `bpy.data.texts['my_storage.json']` would then contain: 47 | 48 | ```python 49 | 50 | { 51 | "key1": "some string storage", 52 | "key2": [ 53 | "some", 54 | "list", 55 | "storage" 56 | ], 57 | "key3": 5 58 | } 59 | ``` 60 | to read this back in at a later stage: 61 | 62 | ```python 63 | import bpy 64 | import json 65 | 66 | text_obj = bpy.data.texts['my_storage.json'] 67 | text_str = text_obj.as_string() 68 | 69 | my_json = json.loads(text_str) 70 | print(my_json['key1']) 71 | ``` 72 | ____ 73 | 74 | **dict and ast.literal_eval** 75 | 76 | here we write the string representation of the dict to an existing text datablock called `'dict_storage'`. I've shown in previous examples how to create text datablocks, this shows how to reference one that already exists. Notice i've omitted the file-extention, you can decide whether that's good convention or not. hint: probably not. 77 | 78 | ```python 79 | import bpy 80 | import ast 81 | 82 | mydict = dict(deb=["two", "three"], far="booo", foo=34) 83 | 84 | d = bpy.data.texts['dict_storage'] 85 | d.from_string(str(mydict)) 86 | 87 | # read from the datablock 88 | d = bpy.data.texts['dict_storage'] 89 | stringified_dict = d.as_string() 90 | my_read_dict = ast.literal_eval(stringified_dict) 91 | for k, v in my_read_dict.items(): 92 | print(k, v) 93 | ``` -------------------------------------------------------------------------------- /icons.md: -------------------------------------------------------------------------------- 1 | ## custom icons for EnumProperty 2 | 3 | ```python 4 | # ##### BEGIN GPL LICENSE BLOCK ##### 5 | # 6 | # This program is free software; you can redistribute it and/or 7 | # modify it under the terms of the GNU General Public License 8 | # as published by the Free Software Foundation; either version 2 9 | # of the License, or (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program; if not, write to the Free Software Foundation, 18 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | # 20 | # ##### END GPL LICENSE BLOCK ##### 21 | 22 | # 23 | 24 | 25 | bl_info = { 26 | "name": "ploot", 27 | "author": "zeffii (aka Dealga McArdle)", 28 | "version": (1, 3, 1), 29 | "blender": (2, 7, 7), 30 | "category": "Mesh", 31 | "location": "View3D > EditMode > (w) Specials" 32 | } 33 | 34 | import os 35 | import bpy 36 | 37 | 38 | 39 | icon_names = ['BIX', 'CCEN', 'E2F'] 40 | icon_collection = {} 41 | icon_dict = {} 42 | 43 | 44 | class DudePanel(bpy.types.Panel): 45 | """Creates a Panel in the Object properties window""" 46 | bl_label = "Hello World Panel" 47 | bl_idname = "OBJECT_PT_hello" 48 | bl_space_type = 'PROPERTIES' 49 | bl_region_type = 'WINDOW' 50 | bl_context = "object" 51 | 52 | def draw(self, context): 53 | layout = self.layout 54 | row = layout.row() 55 | row.prop(context.scene, 'enumlumpa', text='') 56 | 57 | 58 | def register_icons(): 59 | import bpy.utils.previews 60 | pcoll = bpy.utils.previews.new() 61 | icons_dir = os.path.join(os.path.dirname(__file__), "icons") 62 | for icon_name in icon_names: 63 | pcoll.load(icon_name, os.path.join(icons_dir, icon_name + '.png'), 'IMAGE') 64 | 65 | icon_collection["main"] = pcoll 66 | 67 | 68 | def unregister_icons(): 69 | for pcoll in icon_collection.values(): 70 | bpy.utils.previews.remove(pcoll) 71 | icon_collection.clear() 72 | 73 | 74 | def register(): 75 | register_icons() 76 | 77 | icon_dict.update({name: icon_collection["main"][name].icon_id for name in icon_names}) 78 | bpy.types.Scene.enumlumpa = bpy.props.EnumProperty( 79 | default='BIX', 80 | items=[(name, name.title(), '', icon_dict.get(name), idx) for idx, name in enumerate(icon_names)] 81 | ) 82 | bpy.utils.register_module(__name__) 83 | 84 | 85 | def unregister(): 86 | bpy.utils.unregister_module(__name__) 87 | unregister_icons() 88 | del bpy.types.Scene.enumlumpa 89 | 90 | ``` -------------------------------------------------------------------------------- /mDrivEngine/README.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /mDrivEngine/clock.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlenderCN/BlenderPython/07b3e010e15271b8ed0d2741c017e9e16216c9f0/mDrivEngine/clock.gif -------------------------------------------------------------------------------- /mathutils.md: -------------------------------------------------------------------------------- 1 | The mathutils.geometry submodule has a collection of methods that perform common geometric tasks. [Listed here](http://www.blender.org/api/blender_python_api_current/search.html?q=mathutils.geometry&check_keywords=yes&area=default) 2 | 3 | These functions tend to be self explanatory, and if you're stuck you can google them and i'm sure you'll get several code snippets to digest. Some functions accept a Matrix so you don't have to do extra multiplication and inverting, others are less sophisticated and let you take care of having the right transforms :) 4 | 5 | My personal favourites are 6 | 7 | - interpolate_bezier 8 | - intersect_line_line 9 | - intersect_line_plane 10 | - intersect_ray_tri 11 | 12 | Some food for thought. 13 | 14 | **Example 1. intersect_line_plane** 15 | 16 | This function expects (vector_a, vector_b, plane_coordinate, plane_normal, flip) 17 | 18 | ```python 19 | import bpy 20 | import bmesh 21 | import mathutils 22 | 23 | def extend_vertex(limit_axis='x', coordinate=4.2, system='local'): 24 | 25 | obj = bpy.context.edit_object 26 | me = obj.data 27 | bm = bmesh.from_edit_mesh(me) 28 | verts = bm.verts 29 | try: 30 | v1, v2 = [v for v in bm.verts if v.select] 31 | except: 32 | print('need two vertices selected, or one edge') 33 | bm.free() 34 | return 35 | 36 | plane_idx = {'x': 0, 'y': 1, 'z': 2}.get(limit_axis) 37 | plane_co, plane_no = [0,0,0], [0,0,0] 38 | plane_no[plane_idx] = 1 39 | plane_co[plane_idx] = coordinate 40 | 41 | intersect_l_p = mathutils.geometry.intersect_line_plane 42 | 43 | if system == 'local': 44 | new_co = intersect_l_p(v1.co, v2.co, plane_co, plane_no, False) 45 | A_len = (v1.co-new_co).length 46 | B_len = (v2.co-new_co).length 47 | else: 48 | mw = obj.matrix_world 49 | new_co = intersect_l_p(mw * v1.co, mw * v2.co, plane_co, plane_no, False) 50 | A_len = ((mw*v1.co)-new_co).length 51 | B_len = ((mw*v2.co)-new_co).length 52 | new_co = mw.inverted() * new_co 53 | 54 | if A_len < B_len: 55 | v1.co = new_co 56 | else: 57 | v2.co = new_co 58 | 59 | bmesh.update_edit_mesh(me, True) 60 | 61 | 62 | extend_vertex(limit_axis='x', coordinate=-2, system='global') 63 | ``` --------------------------------------------------------------------------------