├── README.md ├── circle_02.zip ├── circle_02 ├── __init__.py └── icons │ └── circle32.png ├── circle_03_options.zip ├── circle_03_options ├── __init__.py └── icons │ └── circle32.png ├── circle_03_scale.zip ├── circle_03_scale ├── __init__.py └── icons │ └── circle32.png ├── ladder_04.py ├── ladder_05.py ├── move_01.py ├── random_vcolors.py └── select_connect.py /README.md: -------------------------------------------------------------------------------- 1 | # CreatingAdd-onsForBlender 2 | Example code for my book Creating add-ons for Blender, a practical primer 3 | -------------------------------------------------------------------------------- /circle_02.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/varkenvarken/CreatingAdd-onsForBlender/3be28fbe28553d284b8a7925ef920814a7d0f6d2/circle_02.zip -------------------------------------------------------------------------------- /circle_02/__init__.py: -------------------------------------------------------------------------------- 1 | # ##### BEGIN GPL LICENSE BLOCK ##### 2 | # 3 | # Circle, a Blender addon 4 | # (c) 2016 Michel J. Anders (varkenvarken) 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 | bl_info = { 23 | "name": "CircleObjects", 24 | "author": "Michel Anders (varkenvarken)", 25 | "version": (0, 0, 201601061418), 26 | "blender": (2, 76, 0), 27 | "location": "View3D > Object > Circle", 28 | "description": "Arranges selected objects in a circle", 29 | "warning": "", 30 | "wiki_url": "", 31 | "tracker_url": "", 32 | "category": "Object"} 33 | 34 | import bpy 35 | from mathutils import Vector 36 | from bpy.props import FloatProperty 37 | from bpy.props import FloatVectorProperty 38 | from bpy.props import EnumProperty 39 | 40 | class CircleObjects(bpy.types.Operator): 41 | """Arrange selected objects in a circle in the xy plane""" 42 | bl_idname = "object.circle_objects" 43 | bl_label = "Circle objects" 44 | bl_options = {'REGISTER', 'UNDO'} 45 | 46 | scale = 100 47 | 48 | @classmethod 49 | def poll(cls, context): 50 | return ( (len(context.selected_objects) > 2) 51 | and (context.mode == 'OBJECT') ) 52 | 53 | def execute(self, context): 54 | xyz = [ob.location for ob in context.selected_objects] 55 | center = sum(xyz, Vector()) / len(xyz) 56 | radius = sum((loc.xy - center.xy).length for loc in xyz) 57 | radius /= len(xyz) 58 | for loc, ob in zip(xyz, context.selected_objects): 59 | direction = (loc.xy - center.xy).normalized().to_3d() 60 | ob.location = center + self.scale * 0.01 * radius * direction 61 | return {'FINISHED'} 62 | 63 | preview_collections = {} 64 | 65 | def load_icon(): 66 | import os 67 | import bpy 68 | import bpy.utils 69 | 70 | try: # if anything goes wrong, for example because we are not running 2.75+ we just ignore it 71 | import bpy.utils.previews 72 | pcoll = bpy.utils.previews.new() 73 | # the path is calculated relative to this py file inside the addon folder 74 | my_icons_dir = os.path.join(os.path.dirname(__file__), "icons") 75 | # load a preview thumbnail of the circle icon 76 | pcoll.load("circle_icon", os.path.join(my_icons_dir, "circle32.png"), 'IMAGE') 77 | preview_collections['icons'] = pcoll 78 | except Exception as e: 79 | pass 80 | 81 | def register(): 82 | load_icon() 83 | bpy.utils.register_module(__name__) 84 | bpy.types.VIEW3D_MT_object.append(menu_func) 85 | 86 | def unregister(): 87 | bpy.utils.unregister_module(__name__) 88 | bpy.types.VIEW3D_MT_object.remove(menu_func) 89 | for pcoll in preview_collections.values(): 90 | bpy.utils.previews.remove(pcoll) 91 | preview_collections.clear() 92 | 93 | def menu_func(self, context): 94 | if 'icons' in preview_collections: 95 | self.layout.operator(CircleObjects.bl_idname, 96 | icon_value=preview_collections['icons']['circle_icon'].icon_id) 97 | else: 98 | self.layout.operator(CircleObjects.bl_idname, icon='PLUGIN') 99 | 100 | if __name__ == "__main__": 101 | register() 102 | -------------------------------------------------------------------------------- /circle_02/icons/circle32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/varkenvarken/CreatingAdd-onsForBlender/3be28fbe28553d284b8a7925ef920814a7d0f6d2/circle_02/icons/circle32.png -------------------------------------------------------------------------------- /circle_03_options.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/varkenvarken/CreatingAdd-onsForBlender/3be28fbe28553d284b8a7925ef920814a7d0f6d2/circle_03_options.zip -------------------------------------------------------------------------------- /circle_03_options/__init__.py: -------------------------------------------------------------------------------- 1 | # ##### BEGIN GPL LICENSE BLOCK ##### 2 | # 3 | # Circle, a Blender addon 4 | # (c) 2016 Michel J. Anders (varkenvarken) 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 | bl_info = { 23 | "name": "CircleObjects", 24 | "author": "Michel Anders (varkenvarken)", 25 | "version": (0, 0, 201601071058), 26 | "blender": (2, 76, 0), 27 | "location": "View3D > Object > Circle", 28 | "description": "Arranges selected objects in a circle", 29 | "warning": "", 30 | "wiki_url": "", 31 | "tracker_url": "", 32 | "category": "Object"} 33 | 34 | import bpy 35 | from mathutils import Vector 36 | from bpy.props import FloatProperty 37 | from bpy.props import FloatVectorProperty 38 | from bpy.props import EnumProperty 39 | 40 | class CircleObjects(bpy.types.Operator): 41 | """Arrange selected objects in a circle""" 42 | bl_idname = "object.circle_objects" 43 | bl_label = "Circle objects" 44 | bl_options = {'REGISTER', 'UNDO', 'PRESET'} 45 | 46 | scale = FloatProperty( name="Scale", 47 | description="Scale the radius of the circle", 48 | default=100, 49 | min=0, 50 | subtype='PERCENTAGE') 51 | 52 | axis = FloatVectorProperty( name="Axis", 53 | description="Arbitrary circle orientation", 54 | default=(0,0,1), 55 | subtype='DIRECTION') 56 | 57 | orientation = EnumProperty( name="Orientation", 58 | description="Circle orientation", 59 | items=[ 60 | ('X','X-Axis','X-Axis'), 61 | ('Y','Y-Axis','Y-Axis'), 62 | ('Z','Z-Axis','Z-Axis'), 63 | ('A','Arbitrary Axis','Arbitrary Axis')], 64 | default='Z') 65 | 66 | cardinals = { 'X' : Vector((1,0,0)), 67 | 'Y' : Vector((0,1,0)), 68 | 'Z' : Vector((0,0,1)) } 69 | 70 | @classmethod 71 | def poll(cls, context): 72 | return ( (len(context.selected_objects) > 2) 73 | and (context.mode == 'OBJECT') ) 74 | 75 | def execute(self, context): 76 | xyz = [ob.location for ob in context.selected_objects] 77 | center = sum(xyz, Vector()) / len(xyz) 78 | radius = sum((loc.xy - center.xy).length for loc in xyz) 79 | radius /= len(xyz) 80 | if self.orientation in self.cardinals: 81 | orientation = self.cardinals[self.orientation] 82 | else: 83 | orientation = self.axis 84 | rotation = self.cardinals['Z'].rotation_difference(orientation) 85 | for loc, ob in zip(xyz, context.selected_objects): 86 | direction = (loc.xy - center.xy).normalized().to_3d() 87 | direction = rotation * direction 88 | ob.location = center + self.scale * 0.01 * radius * direction 89 | return {'FINISHED'} 90 | 91 | def draw(self, context): 92 | layout = self.layout 93 | layout.prop(self, 'scale') 94 | layout.prop(self, 'orientation') 95 | if self.orientation == 'A': 96 | layout.prop(self, 'axis', text="") 97 | 98 | preview_collections = {} 99 | 100 | def load_icon(): 101 | import os 102 | import bpy 103 | import bpy.utils 104 | 105 | try: # if anything goes wrong, for example because we are not running 2.75+ we just ignore it 106 | import bpy.utils.previews 107 | pcoll = bpy.utils.previews.new() 108 | # the path is calculated relative to this py file inside the addon folder 109 | my_icons_dir = os.path.join(os.path.dirname(__file__), "icons") 110 | # load a preview thumbnail of the circle icon 111 | pcoll.load("circle_icon", os.path.join(my_icons_dir, "circle32.png"), 'IMAGE') 112 | preview_collections['icons'] = pcoll 113 | except Exception as e: 114 | pass 115 | 116 | def register(): 117 | load_icon() 118 | bpy.utils.register_module(__name__) 119 | bpy.types.VIEW3D_MT_object.append(menu_func) 120 | 121 | def unregister(): 122 | bpy.utils.unregister_module(__name__) 123 | bpy.types.VIEW3D_MT_object.remove(menu_func) 124 | for pcoll in preview_collections.values(): 125 | bpy.utils.previews.remove(pcoll) 126 | preview_collections.clear() 127 | 128 | def menu_func(self, context): 129 | if 'icons' in preview_collections: 130 | self.layout.operator(CircleObjects.bl_idname, 131 | icon_value=preview_collections['icons']['circle_icon'].icon_id) 132 | else: 133 | self.layout.operator(CircleObjects.bl_idname, icon='PLUGIN') 134 | 135 | if __name__ == "__main__": 136 | register() 137 | -------------------------------------------------------------------------------- /circle_03_options/icons/circle32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/varkenvarken/CreatingAdd-onsForBlender/3be28fbe28553d284b8a7925ef920814a7d0f6d2/circle_03_options/icons/circle32.png -------------------------------------------------------------------------------- /circle_03_scale.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/varkenvarken/CreatingAdd-onsForBlender/3be28fbe28553d284b8a7925ef920814a7d0f6d2/circle_03_scale.zip -------------------------------------------------------------------------------- /circle_03_scale/__init__.py: -------------------------------------------------------------------------------- 1 | # ##### BEGIN GPL LICENSE BLOCK ##### 2 | # 3 | # Circle, a Blender addon 4 | # (c) 2016 Michel J. Anders (varkenvarken) 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 | bl_info = { 23 | "name": "CircleObjects", 24 | "author": "Michel Anders (varkenvarken)", 25 | "version": (0, 0, 201601071109), 26 | "blender": (2, 76, 0), 27 | "location": "View3D > Object > Circle", 28 | "description": "Arranges selected objects in a circle", 29 | "warning": "", 30 | "wiki_url": "", 31 | "tracker_url": "", 32 | "category": "Object"} 33 | 34 | import bpy 35 | from mathutils import Vector 36 | from bpy.props import FloatProperty 37 | 38 | class CircleObjects(bpy.types.Operator): 39 | """Arrange selected objects in a circle in the xy plane""" 40 | bl_idname = "object.circle_objects" 41 | bl_label = "Circle objects" 42 | bl_options = {'REGISTER', 'UNDO'} 43 | 44 | scale = FloatProperty( name="Scale", 45 | description="Scale the radius of the circle", 46 | default=100, 47 | min=0, 48 | subtype='PERCENTAGE') 49 | 50 | @classmethod 51 | def poll(cls, context): 52 | return ( (len(context.selected_objects) > 2) 53 | and (context.mode == 'OBJECT') ) 54 | 55 | def execute(self, context): 56 | xyz = [ob.location for ob in context.selected_objects] 57 | center = sum(xyz, Vector()) / len(xyz) 58 | radius = sum((loc.xy - center.xy).length for loc in xyz) 59 | radius /= len(xyz) 60 | for loc, ob in zip(xyz, context.selected_objects): 61 | direction = (loc.xy - center.xy).normalized().to_3d() 62 | ob.location = center + self.scale * 0.01 * radius * direction 63 | return {'FINISHED'} 64 | 65 | preview_collections = {} 66 | 67 | def load_icon(): 68 | import os 69 | import bpy 70 | import bpy.utils 71 | 72 | try: # if anything goes wrong, for example because we are not running 2.75+ we just ignore it 73 | import bpy.utils.previews 74 | pcoll = bpy.utils.previews.new() 75 | # the path is calculated relative to this py file inside the addon folder 76 | my_icons_dir = os.path.join(os.path.dirname(__file__), "icons") 77 | # load a preview thumbnail of the circle icon 78 | pcoll.load("circle_icon", os.path.join(my_icons_dir, "circle32.png"), 'IMAGE') 79 | preview_collections['icons'] = pcoll 80 | except Exception as e: 81 | pass 82 | 83 | def register(): 84 | load_icon() 85 | bpy.utils.register_module(__name__) 86 | bpy.types.VIEW3D_MT_object.append(menu_func) 87 | 88 | def unregister(): 89 | bpy.utils.unregister_module(__name__) 90 | bpy.types.VIEW3D_MT_object.remove(menu_func) 91 | for pcoll in preview_collections.values(): 92 | bpy.utils.previews.remove(pcoll) 93 | preview_collections.clear() 94 | 95 | def menu_func(self, context): 96 | if 'icons' in preview_collections: 97 | self.layout.operator(CircleObjects.bl_idname, 98 | icon_value=preview_collections['icons']['circle_icon'].icon_id) 99 | else: 100 | self.layout.operator(CircleObjects.bl_idname, icon='PLUGIN') 101 | 102 | if __name__ == "__main__": 103 | register() 104 | -------------------------------------------------------------------------------- /circle_03_scale/icons/circle32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/varkenvarken/CreatingAdd-onsForBlender/3be28fbe28553d284b8a7925ef920814a7d0f6d2/circle_03_scale/icons/circle32.png -------------------------------------------------------------------------------- /ladder_04.py: -------------------------------------------------------------------------------- 1 | # ##### BEGIN GPL LICENSE BLOCK ##### 2 | # 3 | # Ladder, a Blender addon 4 | # (c) 2016 Michel J. Anders (varkenvarken) 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 | bl_info = { 23 | "name": "Ladder", 24 | "author": "Michel Anders (varkenvarken)", 25 | "version": (0, 0, 201601071058), 26 | "blender": (2, 76, 0), 27 | "location": "View3D > Add > Mesh > Ladder", 28 | "description": "Adds a ladder mesh object to the scene", 29 | "warning": "", 30 | "wiki_url": "", 31 | "tracker_url": "", 32 | "category": "Add Mesh"} 33 | 34 | import bpy 35 | import bmesh 36 | from bpy.props import FloatProperty, IntProperty 37 | from mathutils import Vector 38 | 39 | verts = [ 40 | (1.0000, -2.0000, -3.0000), 41 | (1.0000, -4.0000, -3.0000), 42 | (-1.0000, -4.0000, -3.0000), 43 | (-1.0000, -2.0000, -3.0000), 44 | (1.0000, -2.0000, -1.0000), 45 | (1.0000, -4.0000, -1.0000), 46 | (-1.0000, -4.0000, -1.0000), 47 | (-1.0000, -2.0000, -1.0000), 48 | (1.0000, -4.0000, 1.0000), 49 | (1.0000, -2.0000, 1.0000), 50 | (-1.0000, -2.0000, 1.0000), 51 | (-1.0000, -4.0000, 1.0000), 52 | (1.0000, -4.0000, 3.0000), 53 | (1.0000, -2.0000, 3.0000), 54 | (-1.0000, -2.0000, 3.0000), 55 | (-1.0000, -4.0000, 3.0000), 56 | (0.0000, -2.0000, -3.0000), 57 | (0.0000, -4.0000, -3.0000), 58 | (0.0000, -2.0000, -1.0000), 59 | (0.0000, -4.0000, -1.0000), 60 | (0.0000, -2.0000, 1.0000), 61 | (0.0000, -4.0000, 1.0000), 62 | (0.0000, -2.0000, 3.0000), 63 | (0.0000, -4.0000, 3.0000), 64 | (1.0000, -4.0000, 0.0000), 65 | (-1.0000, -4.0000, 0.0000), 66 | (-1.0000, -2.0000, 0.0000), 67 | (1.0000, -2.0000, 0.0000), 68 | (0.0000, -4.0000, 0.0000), 69 | (1.0000, 0.0000, 0.0000), 70 | (0.0000, 0.0000, 1.0000), 71 | (0.0000, 0.0000, -1.0000), 72 | (-1.0000, 0.0000, 0.0000)] 73 | 74 | faces = [( 28,24,8,21 ), 75 | ( 0,4,5,1 ), 76 | ( 17,19,6,2 ), 77 | ( 2,6,7,3 ), 78 | ( 18,16,3,7 ), 79 | ( 10,11,15,14 ), 80 | ( 27,4,18 ), 81 | ( 26,25,11,10 ), 82 | ( 24,27,9,8 ), 83 | ( 8,9,13,12 ), 84 | ( 21,8,12,23 ), 85 | ( 20,10,14,22 ), 86 | ( 9,20,22,13 ), 87 | ( 11,21,23,15 ), 88 | ( 10,20,26 ), 89 | ( 4,0,16,18 ), 90 | ( 1,5,19,17 ), 91 | ( 25,28,21,11 ), 92 | ( 6,19,28,25 ), 93 | ( 20,9,27 ), 94 | ( 5,4,27,24 ), 95 | ( 7,6,25,26 ), 96 | ( 26,20,30,32 ), 97 | ( 19,5,24,28 ), 98 | ( 18,7,26 ), 99 | ( 20,27,29,30 ), 100 | ( 27,18,31,29 ), 101 | ( 18,26,32,31 ) ] 102 | 103 | def geometry(verts, faces, unit_height, unit_width, repetitions, taper): 104 | # create a new bmesh 105 | bm = bmesh.new() 106 | 107 | width = max(v[1] for v in verts) - min(v[1] for v in verts) 108 | height = max(v[2] for v in verts) - min(v[2] for v in verts) 109 | max_height = repetitions * unit_height 110 | wscale = unit_width / (width/2) 111 | hscale = unit_height / height 112 | 113 | start_index = 0 114 | for rep in range(repetitions): 115 | for v_co in verts: 116 | h = (v_co[2] + rep * height) * hscale 117 | vtaper = 1 - (h / max_height) * (taper * 0.01) 118 | bm.verts.new((v_co[0]*hscale, v_co[1]*wscale*vtaper, h)) 119 | 120 | bm.verts.ensure_lookup_table() 121 | for f_idx in faces: 122 | bm.faces.new([bm.verts[i + start_index] for i in f_idx]) 123 | 124 | start_index = len(bm.verts) 125 | 126 | return bm 127 | 128 | class Ladder(bpy.types.Operator): 129 | """Add a ladder mesh object to the scene""" 130 | bl_idname = "mesh.ladder" 131 | bl_label = "Ladder" 132 | bl_options = {'REGISTER', 'UNDO', 'PRESET'} 133 | 134 | taper = FloatProperty(name="Taper", description="Perc. tapering towards top", default=0, min=0, max=100, subtype='PERCENTAGE') 135 | width = FloatProperty(name="Width", description="Width of the ladder", default=0.5, min=0.3, soft_max=.6, unit='LENGTH') 136 | height= FloatProperty(name="Height", description="Step height", default=0.3, min=0.1, soft_max=.5, unit='LENGTH') 137 | rungs = IntProperty(name="# Rungs", description="Number of rungs", default=12, min=1, soft_max=30) 138 | 139 | @classmethod 140 | def poll(cls, context): 141 | return context.mode == 'OBJECT' 142 | 143 | def execute(self, context): 144 | # create a new empty mesh 145 | me = bpy.data.meshes.new(name='Ladder') 146 | 147 | # create a new object 148 | ob = bpy.data.objects.new('Ladder', me) 149 | 150 | # add some geometry 151 | bm = geometry( verts, faces, 152 | self.height, 153 | self.width, 154 | self.rungs, 155 | self.taper) 156 | 157 | # remove doubles (using a 'bmop' saves us from having to switch to edit mode and back later on) 158 | # we pass a list of all vertices here 159 | bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.001) 160 | 161 | # TODO add a uv-map (two ways: mark seams and unwrap, or DIY layer 162 | 163 | # write the bmesh to the mesh 164 | bm.to_mesh(me) 165 | me.update() 166 | bm.free() # free and prevent further access 167 | 168 | # associate the mesh with the object 169 | ob.data = me 170 | 171 | # link the object to the scene & make it active and selected 172 | context.scene.objects.link(ob) 173 | context.scene.update() 174 | context.scene.objects.active = ob 175 | ob.select = True 176 | 177 | # mark object as smooth (example of using ops on active object) 178 | bpy.ops.object.shade_smooth() 179 | 180 | # add mirror modifier and subsurface modifier 181 | mods = ob.modifiers 182 | m = mods.new('Mirror','MIRROR') 183 | m.use_x = False 184 | m.use_y = True 185 | m.use_z = False 186 | m = mods.new('Subsurf','SUBSURF') 187 | m.levels = 2 188 | m.render_levels = 2 189 | 190 | # TODO add material 191 | 192 | return {'FINISHED'} 193 | 194 | def register(): 195 | bpy.utils.register_module(__name__) 196 | bpy.types.INFO_MT_mesh_add.append(menu_func) 197 | 198 | def unregister(): 199 | bpy.utils.unregister_module(__name__) 200 | bpy.types.INFO_MT_mesh_add.remove(menu_func) 201 | 202 | def menu_func(self, context): 203 | self.layout.operator(Ladder.bl_idname, icon='PLUGIN') 204 | -------------------------------------------------------------------------------- /ladder_05.py: -------------------------------------------------------------------------------- 1 | # ##### BEGIN GPL LICENSE BLOCK ##### 2 | # 3 | # Ladder, a Blender addon 4 | # (c) 2016 Michel J. Anders (varkenvarken) 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 | bl_info = { 23 | "name": "Ladder", 24 | "author": "Michel Anders (varkenvarken)", 25 | "version": (0, 0, 201607271133), 26 | "blender": (2, 76, 0), 27 | "location": "View3D > Add > Mesh > Ladder", 28 | "description": "Adds a ladder mesh object to the scene", 29 | "warning": "", 30 | "wiki_url": "", 31 | "tracker_url": "", 32 | "category": "Add Mesh"} 33 | 34 | import bpy 35 | import bmesh 36 | from bpy.props import FloatProperty, IntProperty, BoolProperty 37 | from bpy.app.handlers import persistent 38 | 39 | from mathutils import Vector 40 | 41 | vertsStile = [(-0.07573,0.03,-0.05),(-0.07573,0.03,0.05),(-0.04975,0.015,-0.05),(-0.04975,0.015,0.05),(-0.04975,-0.015,-0.05),(-0.04975,-0.015,0.05),(-0.07573,-0.03,-0.05),(-0.07573,-0.03,0.05),(-0.1017,-0.015,-0.05),(-0.1017,-0.015,0.05),(-0.1017,0.015,-0.05),(-0.1017,0.015,0.05),(-0.07573,0.03,-0.01667),(-0.07573,0.03,0.01667),(-0.04975,0.015,0.01667),(-0.04975,0.015,-0.01667),(-0.04975,-0.015,0.01667),(-0.04975,-0.015,-0.01667),(-0.07573,-0.03,0.01667),(-0.07573,-0.03,-0.01667),(-0.1017,-0.015,0.01667),(-0.1017,-0.015,-0.01667),(-0.1017,0.015,0.01667),(-0.1017,0.015,-0.01667),] 42 | 43 | facesStile = [(13, 1, 3, 14),(14, 3, 5, 16),(16, 5, 7, 18),(18, 7, 9, 20),(20, 9, 11, 22),(22, 11, 1, 13),(10, 23, 12, 0),(23, 22, 13, 12),(8, 21, 23, 10),(21, 20, 22, 23),(6, 19, 21, 8),(19, 18, 20, 21),(4, 17, 19, 6),(17, 16, 18, 19),(2, 15, 17, 4),(0, 12, 15, 2),(12, 13, 14, 15),] 44 | 45 | edgesStile = [(2, 0),(13, 1),(1, 3),(15, 2),(4, 2),(3, 5),(17, 4),(6, 4),(5, 7),(19, 6),(8, 6),(7, 9),(21, 8),(10, 8),(9, 11),(23, 10),(0, 10),(11, 1),(0, 12),(12, 13),(3, 14),(14, 15),(5, 16),(16, 17),(7, 18),(18, 19),(9, 20),(20, 21),(11, 22),(22, 23),(23, 12),(22, 13),(21, 23),(20, 22),(19, 21),(18, 20),(17, 19),(16, 18),(15, 17),(14, 16),(12, 15),(13, 14),] 46 | 47 | creaseStile = {0: 0.0,1: 0.0,2: 0.0,3: 0.0,4: 0.0,5: 0.0,6: 0.0,7: 0.0,8: 0.0,9: 0.0,10: 0.0,11: 0.0,12: 0.0,13: 0.0,14: 0.0,15: 0.0,16: 0.0,17: 0.0,18: 0.0,19: 0.0,20: 0.0,21: 1.0,22: 0.0,23: 1.0,24: 0.0,25: 0.0,26: 0.0,27: 0.0,28: 0.0,29: 0.0,30: 0.0,31: 0.0,32: 0.0,33: 0.0,34: 0.0,35: 0.0,36: 0.0,37: 0.0,38: 1.0,39: 1.0,40: 0.0,41: 0.0,} 48 | 49 | selectedStile = {0: True,1: False,2: True,3: False,4: True,5: True,6: False,7: True,8: True,9: False,10: True,11: True,12: False,13: True,14: True,15: False,16: True,17: True,18: False,19: False,20: False,21: False,22: False,23: False,24: False,25: False,26: False,27: False,28: False,29: False,30: False,31: False,32: False,33: False,34: False,35: False,36: False,37: False,38: False,39: False,40: False,41: False,} 50 | 51 | vertsRung = [(0,0.0187,0.01781),(0,0.0187,-0.01552),(0,-0.0113,0.01781),(0,-0.0113,-0.01552),] 52 | 53 | edgesRung = [(0, 1),(2, 3),(1, 3),(0, 2),] 54 | 55 | def geometry(unit_height, unit_width, repetitions, taper): 56 | # create a new bmesh 57 | bm = bmesh.new() 58 | 59 | maxx = max(v[0] for v in vertsStile) 60 | offset = unit_width/2 - abs(maxx) 61 | height = max(v[2] for v in vertsStile) - min(v[2] for v in vertsStile) 62 | max_height = repetitions * unit_height 63 | hscale = unit_height / height 64 | 65 | # first the stile segment 66 | edge_loops = [] 67 | stile_select = set() 68 | for v in vertsStile: 69 | bm.verts.new((v[0] - offset, v[1], v[2])) 70 | bm.verts.ensure_lookup_table() 71 | bm.verts.index_update() 72 | 73 | for n,e in enumerate(edgesStile): 74 | edge = bm.edges.new([bm.verts[e[0]],bm.verts[e[1]]]) 75 | edge.select = selectedStile[n] # actually there's no need to keep these edges selected 76 | if edge.select: 77 | stile_select.add(e[0]) 78 | stile_select.add(e[1]) 79 | if creaseStile[n]>0 : 80 | edge_loops.append(n) 81 | bm.edges.ensure_lookup_table() 82 | bm.edges.index_update() 83 | 84 | # add a crease layer 85 | cl = bm.edges.layers.crease.new() 86 | for n,e in enumerate(bm.edges): 87 | e[cl] = creaseStile[n] # note the counter intuitive indexing the edge is indexed by the layer 88 | 89 | for f in facesStile: 90 | bm.faces.new([bm.verts[fi] for fi in f]) 91 | 92 | for v in stile_select: 93 | bm.verts[v].co.z *= hscale 94 | 95 | # then the rung segment, which is just a square of 4 edges with no face 96 | start_rung_verts = len(bm.verts) 97 | start_rung_edges = len(bm.edges) 98 | 99 | for v in vertsRung: 100 | bm.verts.new(v) 101 | bm.verts.ensure_lookup_table() 102 | bm.verts.index_update() 103 | 104 | for n,e in enumerate(edgesRung): 105 | edge = bm.edges.new([bm.verts[e[0]+start_rung_verts], bm.verts[e[1]+start_rung_verts]]) 106 | edge_loops.append(n+start_rung_edges) 107 | bm.edges.ensure_lookup_table() 108 | bm.edges.index_update() 109 | 110 | # bridge the 4 edges on the stile with those on the run 111 | bmesh.ops.bridge_loops(bm, edges=[bm.edges[e] for e in edge_loops]) 112 | 113 | bm.verts.ensure_lookup_table() 114 | bm.edges.ensure_lookup_table() 115 | bm.faces.ensure_lookup_table() 116 | 117 | # copying will copy creases as well so we don't have to look at that 118 | geom_orig = bm.verts[:] + bm.edges[:] + bm.faces[:] 119 | for rep in range(1,repetitions): 120 | ret = bmesh.ops.duplicate(bm, geom=geom_orig) # dest param not implemented in 2.76 121 | for ele in ret["geom"]: 122 | if isinstance(ele, bmesh.types.BMVert): 123 | ele.co.z += unit_height * rep 124 | 125 | # I am pretty sure that the duplicate bmop has taken care of this but just to be sure 126 | bm.verts.ensure_lookup_table() 127 | bm.edges.ensure_lookup_table() 128 | bm.faces.ensure_lookup_table() 129 | 130 | # make all faces appear smooth 131 | for f in bm.faces: 132 | f.smooth = True 133 | 134 | # finally we skew the vertices, but only those that belong to the stiles 135 | for v in bm.verts: 136 | vtaper = (v.co.z / max_height) * (taper * 0.01) 137 | if v.co.x < -0.001: 138 | v.co.x += vtaper * unit_width/2 139 | 140 | return bm 141 | 142 | def updateLadderObject(ob): 143 | 144 | me = ob.data 145 | 146 | # add some geometry. we refer to different prop locations now! 147 | bm = geometry( ob.ladder.height, 148 | ob.ladder.width, 149 | ob.ladder.rungs, 150 | ob.ladder.taper) 151 | 152 | # remove doubles (using a 'bmop' saves us from having to switch to edit mode and back later on) 153 | # we pass a list of all vertices here 154 | bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.001) 155 | 156 | # write the bmesh to the mesh 157 | bm.to_mesh(me) 158 | me.update() 159 | bm.free() # free and prevent further access 160 | 161 | # mark object as smooth (example of using ops on active object) 162 | #bpy.ops.object.shade_smooth() 163 | 164 | 165 | # add mirror modifier and subsurface modifier if needed 166 | mods = ob.modifiers 167 | if 'Mirror' not in mods: 168 | m = mods.new('Mirror','MIRROR') 169 | m.use_x = True 170 | m.use_y = False 171 | m.use_z = False 172 | if 'Subsurf' not in mods: 173 | m = mods.new('Subsurf','SUBSURF') 174 | m.levels = 2 175 | m.render_levels = 2 176 | 177 | def updateLadder(self, context): 178 | # this function is called from the operator or it is called if the redraw value changes in the panel 179 | # when called from the panel, self is the active object, otherwise self is the operator but to prevent confusion 180 | # we explicitely retrieve the active object 181 | 182 | ob = context.active_object 183 | updateLadderObject(ob) 184 | 185 | class LadderPropertyGroup(bpy.types.PropertyGroup): 186 | ladder= BoolProperty( name="Ladder", default=False) 187 | 188 | taper = FloatProperty( name="Taper", 189 | description="Perc. tapering towards top", 190 | default=0, min=0, max=100, 191 | subtype='PERCENTAGE', 192 | update=updateLadder) 193 | width = FloatProperty( name="Width", 194 | description="Width of the ladder", 195 | default=0.5, min=0.3, soft_max=.6, 196 | unit='LENGTH', 197 | update=updateLadder) 198 | height= FloatProperty( name="Height", 199 | description="Step height", 200 | default=0.3, min=0.1, soft_max=.5, 201 | unit='LENGTH', 202 | update=updateLadder) 203 | rungs = IntProperty( name="# Rungs", 204 | description="Number of rungs", 205 | default=12, min=1, soft_max=30, 206 | update=updateLadder) 207 | 208 | class LadderPropsPanel(bpy.types.Panel): 209 | bl_label = "Ladder" 210 | bl_space_type = "VIEW_3D" 211 | bl_region_type = "TOOLS" 212 | bl_category = "Ladder" 213 | bl_options = set() 214 | 215 | @classmethod 216 | def poll(self, context): 217 | # Check if we are in object mode (and we are dealing with a ladder object) 218 | return ( context.mode == 'OBJECT' 219 | and (context.active_object is not None) 220 | and (context.active_object.ladder is not None) 221 | and context.active_object.ladder.ladder ) 222 | 223 | def draw(self, context): 224 | layout = self.layout 225 | ob = context.active_object.ladder 226 | layout.prop(ob, 'width') 227 | layout.prop(ob, 'height') 228 | layout.prop(ob, 'rungs') 229 | layout.prop(ob, 'taper') 230 | 231 | 232 | class Ladder(bpy.types.Operator): 233 | """Add a ladder mesh object to the scene""" 234 | bl_idname = "mesh.ladder05" 235 | bl_label = "Ladder" 236 | bl_options = {'REGISTER', 'UNDO', 'PRESET'} 237 | 238 | @classmethod 239 | def poll(cls, context): 240 | return context.mode == 'OBJECT' 241 | 242 | def execute(self, context): 243 | # create a new empty mesh 244 | me = bpy.data.meshes.new(name='Ladder') 245 | 246 | # create a new object and identify it as a ladder object 247 | ob = bpy.data.objects.new('Ladder', me) 248 | ob.ladder.ladder = True 249 | 250 | # associate the mesh with the object 251 | ob.data = me 252 | 253 | # link the object to the scene & make it active and selected 254 | context.scene.objects.link(ob) 255 | context.scene.update() 256 | context.scene.objects.active = ob 257 | ob.select = True 258 | 259 | # create the geometry based on properties in the Ladder panel 260 | updateLadder(self, context) 261 | 262 | return {'FINISHED'} 263 | 264 | @persistent 265 | def update_ladders(scene): 266 | print("update_ladders", scene, scene.frame_current) 267 | for ob in scene.objects: 268 | if hasattr(ob,'ladder'): 269 | if ob.ladder.ladder: 270 | print(ob) 271 | updateLadderObject(ob) 272 | scene.update() 273 | 274 | def register(): 275 | bpy.utils.register_module(__name__) 276 | # adding a pointer to a propertygroup will cause *every* object in the scene to have a pointer to a possibly empty propertygroup 277 | # only when assigning to a member of this property group will this propertygroup itself be instantiated 278 | bpy.types.Object.ladder = bpy.props.PointerProperty(type=LadderPropertyGroup) 279 | bpy.types.INFO_MT_mesh_add.append(menu_func) 280 | # add a post frame change handler to update all ladder objects. 281 | # w.o. it object properties can be animated but will not result in actual changes to the object. 282 | # (this is a known issue https://developer.blender.org/T48285 ) 283 | bpy.app.handlers.frame_change_post.append(update_ladders) 284 | 285 | def unregister(): 286 | bpy.utils.unregister_module(__name__) 287 | bpy.types.INFO_MT_mesh_add.remove(menu_func) 288 | bpy.app.handlers.frame_change_post.remove(update_ladders) 289 | bpy.utils.unregister_module(__name__) 290 | 291 | def menu_func(self, context): 292 | self.layout.operator(Ladder.bl_idname, icon='PLUGIN') 293 | -------------------------------------------------------------------------------- /move_01.py: -------------------------------------------------------------------------------- 1 | # ##### BEGIN GPL LICENSE BLOCK ##### 2 | # 3 | # Move, a Blender addon 4 | # (c) 2016 Michel J. Anders (varkenvarken) 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 | bl_info = { 23 | "name": "MoveObject", 24 | "author": "Michel Anders (varkenvarken)", 25 | "version": (0, 0, 20160104121212), 26 | "blender": (2, 76, 0), 27 | "location": "View3D > Object > Move", 28 | "description": "Moves and object", 29 | "warning": "", 30 | "wiki_url": "", 31 | "tracker_url": "", 32 | "category": "Object"} 33 | 34 | import bpy 35 | 36 | 37 | class MoveObject(bpy.types.Operator): 38 | """Moves an object""" 39 | bl_idname = "object.move_object" 40 | bl_label = "Move an object" 41 | bl_options = {'REGISTER', 'UNDO'} 42 | 43 | def execute(self, context): 44 | context.active_object.location.x += 1 45 | return {'FINISHED'} 46 | 47 | 48 | # registering an operator is necessary so the user can find it 49 | # we create a wrapper function here so that we have a single 50 | # point where we can register multiple operators if needed and 51 | # and add operator to menus if desired. 52 | 53 | def register(): 54 | bpy.utils.register_module(__name__) 55 | bpy.types.VIEW3D_MT_object.append(menu_func) 56 | 57 | # the unregister() function is needed for deinstalling add-ons 58 | def unregister(): 59 | bpy.utils.unregister_module(__name__) 60 | bpy.types.VIEW3D_MT_object.remove(menu_func) 61 | 62 | def menu_func(self, context): 63 | self.layout.operator(MoveObject.bl_idname, icon='MESH_CUBE') 64 | 65 | if __name__ == "__main__": 66 | register() 67 | -------------------------------------------------------------------------------- /random_vcolors.py: -------------------------------------------------------------------------------- 1 | from random import random 2 | import bpy 3 | import bmesh 4 | 5 | C = bpy.context 6 | 7 | bm = bmesh.new() 8 | 9 | bm.from_mesh(C.object.data) 10 | bm.faces.ensure_lookup_table() 11 | 12 | vcolor = bm.loops.layers.color.active 13 | if vcolor is None: 14 | vcolor = bm.loops.layers.color.new() 15 | 16 | for face in bm.faces: 17 | color = (random(), random(), random()) 18 | for loop in face.loops: 19 | loop[vcolor] = color 20 | 21 | bm.to_mesh(C.object.data) 22 | bm.free() 23 | 24 | for w in C.window_manager.windows: 25 | for a in w.screen.areas: 26 | if a.type == 'VIEW_3D': 27 | a.tag_redraw() -------------------------------------------------------------------------------- /select_connect.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import bmesh 3 | 4 | C = bpy.context 5 | 6 | bm = bmesh.from_edit_mesh(C.object.data) 7 | 8 | to_visit = [] 9 | 10 | for v in bm.verts: 11 | v.tag = False 12 | if v.select : 13 | to_visit.append(v) 14 | 15 | while to_visit: 16 | v = to_visit.pop() 17 | if v.tag : continue 18 | v.tag = True 19 | for e in v.link_edges: 20 | v2 = e.other_vert(v) 21 | if not v2.tag : 22 | v2.select = True 23 | to_visit.append(v2) 24 | 25 | bmesh.update_edit_mesh(C.object.data) --------------------------------------------------------------------------------