├── .gitignore ├── LICENSE ├── README ├── auto_blend_save.py ├── change_keying_set.py ├── create_bound_box.py ├── create_multi_bound_box.py ├── duplicate_without_parent.py ├── file_prefix.py ├── mesh_select_cycle.py ├── mesh_summary.py ├── node_colours.py ├── presets └── interface_theme │ └── myenergy.xml ├── sequencer_alignment.py ├── set_smooth.py └── textToFile.py /.gitignore: -------------------------------------------------------------------------------- 1 | # common files we don't want to archive 2 | *.blend* 3 | *~ 4 | 5 | # python temp paths 6 | __pycache__/ 7 | *.py[cod] 8 | 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | All work here will be released under the 2-clause BSD license. 2 | 3 | Some file may have other restrictions, this would be if some code is 4 | used from another project. Check each file for exceptions. 5 | 6 | # 7 | # Copyright (c) 2014 Shane Ambler 8 | # 9 | # All rights reserved. 10 | # Redistribution and use in source and binary forms, with or without 11 | # modification, are permitted provided that the following conditions are met: 12 | # 13 | # 1. Redistributions of source code must retain the above copyright 14 | # notice, this list of conditions and the following disclaimer. 15 | # 2. Redistributions in binary form must reproduce the above copyright 16 | # notice, this list of conditions and the following disclaimer in the 17 | # documentation and/or other materials provided with the distribution. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 23 | # OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 24 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 25 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 26 | # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 27 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 28 | # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | # 31 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | addonsByMe is a collection of blender addons that I have created. 2 | This allows better tracking of my work and issues to be reported 3 | separate from the official contrib addons. 4 | 5 | Some of these were originally added to https://github.com/sambler/myblendercontrib 6 | All of these files will be merged into myblendercontrib to keep it 7 | as one folder that gets used during the blender build process. 8 | 9 | To get official blender source or binaries see http://blender.org 10 | For my blender repo see https://github.com/sambler/myblender 11 | -------------------------------------------------------------------------------- /auto_blend_save.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2017 Shane Ambler 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are 6 | # met: 7 | # 8 | # * Redistributions of source code must retain the above copyright 9 | # notice, this list of conditions and the following disclaimer. 10 | # * Redistributions in binary form must reproduce the above 11 | # copyright notice, this list of conditions and the following disclaimer 12 | # in the documentation and/or other materials provided with the 13 | # distribution. 14 | # 15 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 16 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 17 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 18 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 19 | # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 21 | # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | # 27 | 28 | # made in response to BSE question - 29 | # https://blender.stackexchange.com/q/95070/935 30 | 31 | bl_info = { 32 | "name": "Auto Blend Save", 33 | "author": "sambler", 34 | "version": (1, 2), 35 | "blender": (2, 80, 0), 36 | "location": "blender", 37 | "description": "Automatically save multiple copies of a blend file", 38 | "warning": "Deletes old files - check your settings.", 39 | "wiki_url": "https://github.com/sambler/addonsByMe/blob/master/auto_blend_save.py", 40 | "tracker_url": "https://github.com/sambler/addonsByMe/issues", 41 | "category": "System", 42 | } 43 | 44 | import bpy 45 | from bpy.app.handlers import persistent 46 | import datetime as dt 47 | import os 48 | 49 | ## TIME_FMT_STR is used for filename prefix so keep path friendly 50 | ## deleting old files relies on this format - CHANGE WITH CAUTION 51 | TIME_FMT_STR = '%Y_%m_%d_%H_%M_%S' 52 | 53 | last_saved = None 54 | 55 | class AutoBlendSavePreferences(bpy.types.AddonPreferences): 56 | bl_idname = __name__ 57 | 58 | save_after_open : bpy.props.BoolProperty(name='Save on open', 59 | description='Save a copy of file after opening it', 60 | default=True) 61 | save_before_close : bpy.props.BoolProperty(name='Save before close', 62 | description='Save the current file before opening another file', 63 | default=True) 64 | save_on_interval : bpy.props.BoolProperty(name='Save at intervals', 65 | description='Save the file at timed intervals', 66 | default=True) 67 | save_interval : bpy.props.IntProperty(name='Save interval', 68 | description='Number of minutes between each save', 69 | default=5, min=1, max=120, soft_max=30) 70 | save_to_path : bpy.props.StringProperty(name='Save to path', 71 | description='Path to auto save files into', 72 | default='//AutoSave') 73 | max_save_files : bpy.props.IntProperty(name='Max save files', 74 | description='Maximum number of copies to save, 0 means unlimited', 75 | default=10, min=0, max=100) 76 | compress_backups : bpy.props.BoolProperty(name='Compress backups', 77 | description='Save backups with compression enabled', 78 | default=True) 79 | 80 | def draw(self, context): 81 | layout = self.layout 82 | col = layout.column() 83 | 84 | row = col.row() 85 | row.prop(self,'save_after_open') 86 | row = col.row() 87 | row.prop(self,'save_before_close') 88 | row = col.row() 89 | row.prop(self,'save_on_interval') 90 | if self.save_on_interval: 91 | row.prop(self,'save_interval') 92 | row = col.row() 93 | row.prop(self,'save_to_path') 94 | row = col.row() 95 | if bpy.data.filepath == '': 96 | par = os.getcwd() 97 | else: 98 | par = None 99 | row.label(text='Current file will save to: '+bpy.path.abspath(self.save_to_path, start=par)) 100 | row = col.row() 101 | row.prop(self,'max_save_files') 102 | row = col.row() 103 | row.prop(self,'compress_backups') 104 | 105 | def prefs(): 106 | user_preferences = bpy.context.preferences 107 | return user_preferences.addons[__name__].preferences 108 | 109 | def time_since_save(): 110 | '''Minutes since last saved''' 111 | global last_saved 112 | if last_saved is None: 113 | last_saved = dt.datetime.now() 114 | now = dt.datetime.now() 115 | elapsed = now - last_saved 116 | return elapsed.seconds // 60 #minutes 117 | 118 | def save_file(): 119 | global last_saved 120 | last_saved = dt.datetime.now() 121 | p = prefs() 122 | 123 | basename = bpy.data.filepath 124 | if basename == '': 125 | basename = 'Unsaved.blend' 126 | else: 127 | basename = bpy.path.basename(basename) 128 | 129 | try: 130 | save_dir = bpy.path.abspath(p.save_to_path) 131 | if not os.path.isdir(save_dir): 132 | os.mkdir(save_dir) 133 | except: 134 | print("Error creating auto save directory.") 135 | return 136 | 137 | # delete old files if we want to limit the number of saves 138 | if p.max_save_files > 0: 139 | try: 140 | # as we prefix saved blends with a timestamp 141 | # sorted puts the oldest prefix at the start of the list 142 | # this should be quicker than getting system timestamps for each file 143 | otherfiles = sorted([name for name in os.listdir(save_dir) if name.endswith(basename)]) 144 | if len(otherfiles) >= p.max_save_files: 145 | while len(otherfiles) >= p.max_save_files: 146 | old_file = os.path.join(save_dir,otherfiles[0]) 147 | os.remove(old_file) 148 | otherfiles.pop(0) 149 | except: 150 | print('Unable to remove old files.') 151 | 152 | # save the copy 153 | filename = last_saved.strftime(TIME_FMT_STR) + '_' + basename 154 | backup_file = os.path.join(save_dir,filename) 155 | try: 156 | bpy.ops.wm.save_as_mainfile(filepath=backup_file, copy=True, 157 | compress=p.compress_backups) 158 | except: 159 | print('Error auto saving file.') 160 | 161 | @persistent 162 | def save_post_open(scn): 163 | if prefs().save_after_open: 164 | save_file() 165 | 166 | @persistent 167 | def save_pre_close(scn): 168 | # is_dirty means there are changes that haven't been saved to disk 169 | if bpy.data.is_dirty and prefs().save_before_close: 170 | save_file() 171 | 172 | @persistent 173 | def timed_save(scn): 174 | if bpy.data.is_dirty and prefs().save_on_interval \ 175 | and time_since_save() >= prefs().save_interval: 176 | save_file() 177 | 178 | def register(): 179 | bpy.utils.register_class(AutoBlendSavePreferences) 180 | bpy.app.handlers.load_pre.append(save_pre_close) 181 | bpy.app.handlers.load_post.append(save_post_open) 182 | bpy.app.handlers.depsgraph_update_post.append(timed_save) 183 | 184 | def unregister(): 185 | bpy.app.handlers.load_pre.remove(save_pre_close) 186 | bpy.app.handlers.load_post.remove(save_post_open) 187 | bpy.app.handlers.depsgraph_update_post.remove(timed_save) 188 | bpy.utils.unregister_class(AutoBlendSavePreferences) 189 | 190 | if __name__ == "__main__": 191 | register() 192 | -------------------------------------------------------------------------------- /change_keying_set.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2016 Shane Ambler 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are 6 | # met: 7 | # 8 | # * Redistributions of source code must retain the above copyright 9 | # notice, this list of conditions and the following disclaimer. 10 | # * Redistributions in binary form must reproduce the above 11 | # copyright notice, this list of conditions and the following disclaimer 12 | # in the documentation and/or other materials provided with the 13 | # distribution. 14 | # 15 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 16 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 17 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 18 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 19 | # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 21 | # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | # 27 | 28 | # made in response to 29 | # http://blender.stackexchange.com/q/49541/935 30 | 31 | bl_info = { 32 | "version": (1, 2), 33 | "blender": (2, 80, 0), 34 | "author": "sambler", 35 | "name": "Change Keying Sets", 36 | "location": "3D view -> properties region", 37 | "description": "A panel of buttons and shortcuts that change the active keying set.", 38 | "warning": "", 39 | "wiki_url": "https://github.com/sambler/addonsByMe/blob/master/change_keying_set.py", 40 | "tracker_url": "https://github.com/sambler/addonsByMe/issues", 41 | "category": "Animation", 42 | } 43 | 44 | import bpy 45 | 46 | class SetKeyingSetOperator(bpy.types.Operator): 47 | """Set the active keying set""" 48 | bl_idname = 'anim.set_keyingset' 49 | bl_label = 'Set the active keying set' 50 | 51 | type : bpy.props.StringProperty(default='LocRotScale') 52 | 53 | def execute(self, context): 54 | ks = context.scene.keying_sets_all 55 | if self.type == '': 56 | ks.active = None 57 | else: 58 | ks.active = ks[self.type] 59 | return {'FINISHED'} 60 | 61 | class KeyingsetsPanel(bpy.types.Panel): 62 | """Display some butons to set the active keying set""" 63 | bl_label = 'Keying Set' 64 | bl_idname = 'ANIM_PT_set_keyingset' 65 | bl_space_type = 'VIEW_3D' 66 | bl_region_type = 'UI' 67 | 68 | def draw(self, context): 69 | scn = context.scene 70 | layout = self.layout 71 | row = layout.row() 72 | col = row.column() 73 | col.label(text='Active Set:') 74 | col = row.column() 75 | if scn.keying_sets_all.active: 76 | col.label(text=scn.keying_sets_all.active.bl_label) 77 | else: 78 | col.label(text='None') 79 | 80 | # some preset buttons - add or adjust to what you use 81 | row = layout.row() 82 | row.operator('anim.set_keyingset', text='None').type = '' 83 | 84 | row = layout.row() 85 | row.operator('anim.set_keyingset', text='Location').type = 'Location' 86 | 87 | row = layout.row() 88 | row.operator('anim.set_keyingset', text='Rotation').type = 'Rotation' 89 | 90 | row = layout.row() 91 | row.operator('anim.set_keyingset', text='LocRot').type = 'LocRot' 92 | 93 | row = layout.row() 94 | row.operator('anim.set_keyingset', text='LocRotScale').type = 'LocRotScale' 95 | 96 | # note that the bl_label of the keying set is used for the type 97 | # for a list of available type values - paste this in blenders python console 98 | # [k.bl_label for k in bpy.context.scene.keying_sets_all] 99 | row = layout.row() 100 | row.operator('anim.set_keyingset', text='WholeCharacter').type = 'Whole Character' 101 | 102 | # store keymaps here to access after registration 103 | addon_keymaps = [] 104 | 105 | def register(): 106 | bpy.utils.register_class(SetKeyingSetOperator) 107 | bpy.utils.register_class(KeyingsetsPanel) 108 | 109 | # some sample shortcuts - adjust to personal taste. 110 | # As most keys have been assigned to various tasks I have 111 | # chosen to setup modifier keys - hold K and press the shortcut key 112 | wm = bpy.context.window_manager 113 | kc = wm.keyconfigs.addon 114 | if kc: 115 | # add keys for object mode 116 | km = wm.keyconfigs.addon.keymaps.new(name='Object Mode', space_type='EMPTY') 117 | 118 | kmi = km.keymap_items.new(SetKeyingSetOperator.bl_idname, 'Y', 119 | 'PRESS', key_modifier='K') 120 | kmi.properties.type = 'Location' 121 | addon_keymaps.append((km, kmi)) 122 | 123 | kmi = km.keymap_items.new(SetKeyingSetOperator.bl_idname, 'U', 124 | 'PRESS', key_modifier='K') 125 | kmi.properties.type = 'Rotation' 126 | addon_keymaps.append((km, kmi)) 127 | 128 | kmi = km.keymap_items.new(SetKeyingSetOperator.bl_idname, 'I', 129 | 'PRESS', key_modifier='K') 130 | kmi.properties.type = 'LocRotScale' 131 | addon_keymaps.append((km, kmi)) 132 | 133 | 134 | # add keys for pose mode 135 | km = wm.keyconfigs.addon.keymaps.new(name='Pose', space_type='EMPTY') 136 | 137 | kmi = km.keymap_items.new(SetKeyingSetOperator.bl_idname, 'Y', 138 | 'PRESS', key_modifier='K') 139 | kmi.properties.type = 'Location' 140 | addon_keymaps.append((km, kmi)) 141 | 142 | kmi = km.keymap_items.new(SetKeyingSetOperator.bl_idname, 'U', 143 | 'PRESS', key_modifier='K') 144 | kmi.properties.type = 'Rotation' 145 | addon_keymaps.append((km, kmi)) 146 | 147 | kmi = km.keymap_items.new(SetKeyingSetOperator.bl_idname, 'I', 148 | 'PRESS', key_modifier='K') 149 | kmi.properties.type = 'LocRotScale' 150 | addon_keymaps.append((km, kmi)) 151 | 152 | 153 | def unregister(): 154 | for km, kmi in addon_keymaps: 155 | km.keymap_items.remove(kmi) 156 | addon_keymaps.clear() 157 | 158 | bpy.utils.unregister_class(KeyingsetsPanel) 159 | bpy.utils.unregister_class(SetKeyingSetOperator) 160 | 161 | 162 | if __name__ == "__main__": 163 | register() 164 | 165 | -------------------------------------------------------------------------------- /create_bound_box.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2014 Shane Ambler 3 | # 4 | # All rights reserved. 5 | # Redistribution and use in source and binary forms, with or without 6 | # modification, are permitted provided that the following conditions are met: 7 | # 8 | # 1. Redistributions of source code must retain the above copyright 9 | # notice, this list of conditions and the following disclaimer. 10 | # 2. Redistributions in binary form must reproduce the above copyright 11 | # notice, this list of conditions and the following disclaimer in the 12 | # documentation and/or other materials provided with the distribution. 13 | # 14 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 15 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 16 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 17 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 18 | # OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 19 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 20 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 21 | # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 22 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 23 | # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | # 26 | 27 | # made in response to -- 28 | # http://blender.stackexchange.com/q/14072/935 29 | # 30 | # NOTE: 31 | # Non-render objects have a zero size bounding box 32 | # when only one of these objects is selected you will get 33 | # a zero sized cube. Such as camera, lamp, lattice, armature, empty 34 | 35 | bl_info = { 36 | "name": "Create Bounding Box", 37 | "author": "sambler", 38 | "version": (1,4), 39 | "blender": (2, 80, 0), 40 | "location": "View3D > Add > Mesh > Create Bounding Box", 41 | "description": "Create a mesh cube that encompasses all selected objects", 42 | "warning": "", 43 | "wiki_url": "https://github.com/sambler/addonsByMe/blob/master/create_bound_box.py", 44 | "tracker_url": "https://github.com/sambler/addonsByMe/issues", 45 | "category": "Add Mesh", 46 | } 47 | 48 | import bpy 49 | import bmesh 50 | from bpy.props import BoolProperty, FloatVectorProperty 51 | import mathutils 52 | from bpy_extras import object_utils 53 | 54 | # from blender templates 55 | def add_box(width, height, depth): 56 | """ 57 | This function takes inputs and returns vertex and face arrays. 58 | no actual mesh data creation is done here. 59 | """ 60 | 61 | verts = [(+1.0, +1.0, -1.0), 62 | (+1.0, -1.0, -1.0), 63 | (-1.0, -1.0, -1.0), 64 | (-1.0, +1.0, -1.0), 65 | (+1.0, +1.0, +1.0), 66 | (+1.0, -1.0, +1.0), 67 | (-1.0, -1.0, +1.0), 68 | (-1.0, +1.0, +1.0), 69 | ] 70 | 71 | faces = [(0, 1, 2, 3), 72 | (4, 7, 6, 5), 73 | (0, 4, 5, 1), 74 | (1, 5, 6, 2), 75 | (2, 6, 7, 3), 76 | (4, 0, 3, 7), 77 | ] 78 | 79 | # apply size 80 | for i, v in enumerate(verts): 81 | verts[i] = v[0] * width, v[1] * depth, v[2] * height 82 | 83 | return verts, faces 84 | 85 | class CreateBoundingBox(bpy.types.Operator, object_utils.AddObjectHelper): 86 | """Create a mesh cube that encompasses all selected objects""" 87 | bl_idname = "mesh.boundbox_add" 88 | bl_label = "Create Bounding Box" 89 | bl_description = "Create a bounding box around selected objects" 90 | bl_options = {'REGISTER', 'UNDO'} 91 | 92 | # generic transform props 93 | view_align : BoolProperty( 94 | name="Align to View", 95 | default=False, 96 | ) 97 | location : FloatVectorProperty( 98 | name="Location", 99 | subtype='TRANSLATION', 100 | ) 101 | rotation : FloatVectorProperty( 102 | name="Rotation", 103 | subtype='EULER', 104 | ) 105 | 106 | @classmethod 107 | def poll(cls, context): 108 | if len(context.selected_objects) == 0: 109 | return False 110 | return True 111 | 112 | def execute(self, context): 113 | minx, miny, minz = (999999.0,)*3 114 | maxx, maxy, maxz = (-999999.0,)*3 115 | for obj in context.selected_objects: 116 | for v in obj.bound_box: 117 | v_world = obj.matrix_world @ mathutils.Vector((v[0],v[1],v[2])) 118 | 119 | if v_world[0] < minx: 120 | minx = v_world[0] 121 | if v_world[0] > maxx: 122 | maxx = v_world[0] 123 | 124 | if v_world[1] < miny: 125 | miny = v_world[1] 126 | if v_world[1] > maxy: 127 | maxy = v_world[1] 128 | 129 | if v_world[2] < minz: 130 | minz = v_world[2] 131 | if v_world[2] > maxz: 132 | maxz = v_world[2] 133 | 134 | verts_loc, faces = add_box((maxx-minx)/2, (maxz-minz)/2, (maxy-miny)/2) 135 | mesh = bpy.data.meshes.new("BoundingBox") 136 | bm = bmesh.new() 137 | for v_co in verts_loc: 138 | bm.verts.new(v_co) 139 | 140 | bm.verts.ensure_lookup_table() 141 | 142 | for f_idx in faces: 143 | bm.faces.new([bm.verts[i] for i in f_idx]) 144 | 145 | bm.to_mesh(mesh) 146 | mesh.update() 147 | self.location[0] = minx+((maxx-minx)/2) 148 | self.location[1] = miny+((maxy-miny)/2) 149 | self.location[2] = minz+((maxz-minz)/2) 150 | bbox = object_utils.object_data_add(context, mesh, operator=self) 151 | # does a bounding box need to display more than the bounds?? 152 | bbox.display_type = 'BOUNDS' 153 | bbox.hide_render = True 154 | 155 | return {'FINISHED'} 156 | 157 | def menu_boundbox(self, context): 158 | self.layout.operator(CreateBoundingBox.bl_idname, text=CreateBoundingBox.bl_label, icon="PLUGIN") 159 | 160 | def register(): 161 | bpy.utils.register_class(CreateBoundingBox) 162 | bpy.types.VIEW3D_MT_mesh_add.append(menu_boundbox) 163 | 164 | def unregister(): 165 | bpy.utils.unregister_class(CreateBoundingBox) 166 | bpy.types.VIEW3D_MT_mesh_add.remove(menu_boundbox) 167 | 168 | if __name__ == "__main__": 169 | register() 170 | -------------------------------------------------------------------------------- /create_multi_bound_box.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2014 Shane Ambler 3 | # 4 | # All rights reserved. 5 | # Redistribution and use in source and binary forms, with or without 6 | # modification, are permitted provided that the following conditions are met: 7 | # 8 | # 1. Redistributions of source code must retain the above copyright 9 | # notice, this list of conditions and the following disclaimer. 10 | # 2. Redistributions in binary form must reproduce the above copyright 11 | # notice, this list of conditions and the following disclaimer in the 12 | # documentation and/or other materials provided with the distribution. 13 | # 14 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 15 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 16 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 17 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 18 | # OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 19 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 20 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 21 | # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 22 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 23 | # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | # 26 | 27 | # made in response to -- 28 | # http://blender.stackexchange.com/q/14070/935 29 | # 30 | # NOTE: 31 | # Non-render objects have a zero size bounding box 32 | # when only one of these objects is selected you will get 33 | # a zero sized cube. Such as camera, lamp, lattice, armature, empty 34 | 35 | bl_info = { 36 | "name": "Create multi Bounding Box", 37 | "author": "sambler", 38 | "version": (1,2), 39 | "blender": (2, 80, 0), 40 | "location": "View3D > Add > Mesh > Create Bounding Box", 41 | "description": "Create an individual mesh cube matching the bounding box of each selected object", 42 | "warning": "", 43 | "wiki_url": "https://github.com/sambler/addonsByMe/blob/master/create_multi_bound_box.py", 44 | "tracker_url": "https://github.com/sambler/addonsByMe/issues", 45 | "category": "Add Mesh", 46 | } 47 | 48 | import bpy 49 | import bmesh 50 | from bpy.props import BoolProperty, FloatVectorProperty 51 | import mathutils 52 | from bpy_extras import object_utils 53 | 54 | class CreateMultiBoundingBox(bpy.types.Operator, object_utils.AddObjectHelper): 55 | """Create a mesh cube that encompasses all selected objects""" 56 | bl_idname = "mesh.multi_boundbox_add" 57 | bl_label = "Create Multi Bounding Box" 58 | bl_description = "Create a bounding box around each selected object" 59 | bl_options = {'REGISTER', 'UNDO'} 60 | 61 | # generic transform props 62 | view_align : BoolProperty( 63 | name="Align to View", 64 | default=False, 65 | ) 66 | location : FloatVectorProperty( 67 | name="Location", 68 | subtype='TRANSLATION', 69 | ) 70 | rotation : FloatVectorProperty( 71 | name="Rotation", 72 | subtype='EULER', 73 | ) 74 | 75 | @classmethod 76 | def poll(cls, context): 77 | if len(context.selected_objects) == 0: 78 | return False 79 | return True 80 | 81 | def execute(self, context): 82 | 83 | workobjs = [o.name for o in bpy.context.selected_objects] 84 | faces = [(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 | for objname in workobjs: 93 | obj = bpy.data.objects[objname] 94 | mesh = bpy.data.meshes.new("BoundingBox") 95 | bm = bmesh.new() 96 | for v_co in obj.bound_box: 97 | bm.verts.new(v_co) 98 | 99 | bm.verts.ensure_lookup_table() 100 | 101 | for f_idx in faces: 102 | bm.faces.new([bm.verts[i] for i in f_idx]) 103 | 104 | bm.to_mesh(mesh) 105 | mesh.update() 106 | 107 | self.location = obj.location 108 | self.rotation = obj.rotation_euler 109 | bbox = object_utils.object_data_add(context, mesh, operator=self) 110 | # does a bounding box need to display more than the bounds?? 111 | bbox.display_type = 'BOUNDS' 112 | bbox.scale = obj.scale 113 | bbox.hide_render = True 114 | 115 | return {'FINISHED'} 116 | 117 | def menu_boundbox(self, context): 118 | self.layout.operator(CreateMultiBoundingBox.bl_idname, text=CreateMultiBoundingBox.bl_label, icon="PLUGIN") 119 | 120 | def register(): 121 | bpy.utils.register_class(CreateMultiBoundingBox) 122 | bpy.types.VIEW3D_MT_mesh_add.append(menu_boundbox) 123 | 124 | def unregister(): 125 | bpy.utils.unregister_class(CreateMultiBoundingBox) 126 | bpy.types.VIEW3D_MT_mesh_add.remove(menu_boundbox) 127 | 128 | if __name__ == "__main__": 129 | register() 130 | -------------------------------------------------------------------------------- /duplicate_without_parent.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2015 Shane Ambler 3 | # 4 | # All rights reserved. 5 | # Redistribution and use in source and binary forms, with or without 6 | # modification, are permitted provided that the following conditions are met: 7 | # 8 | # 1. Redistributions of source code must retain the above copyright 9 | # notice, this list of conditions and the following disclaimer. 10 | # 2. Redistributions in binary form must reproduce the above copyright 11 | # notice, this list of conditions and the following disclaimer in the 12 | # documentation and/or other materials provided with the distribution. 13 | # 14 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 15 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 16 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 17 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 18 | # OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 19 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 20 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 21 | # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 22 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 23 | # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | # 26 | 27 | # made in response to 28 | # http://blender.stackexchange.com/q/42365/935 29 | 30 | import bpy 31 | 32 | bl_info = { 33 | "name": "Duplicate object(s) without parenting", 34 | "author": "sambler", 35 | "version": (1,1), 36 | "blender": (2, 80, 0), 37 | "location": "Shift-Alt-D", 38 | "description": "Duplicate selected objects or bones without copying the parent connection", 39 | "warning": "", 40 | "wiki_url": "https://github.com/sambler/addonsByMe/blob/master/duplicate_without_parent.py", 41 | "tracker_url": "https://github.com/sambler/addonsByMe/issues", 42 | "category": "Object", 43 | } 44 | 45 | class DuplicateWithoutParent(bpy.types.Operator): 46 | """Duplicate selection without parenting""" 47 | bl_idname = "object.duplicate_no_parent" 48 | bl_label = "Duplicate without parenting" 49 | 50 | @classmethod 51 | def poll(cls, context): 52 | return context.active_object is not None 53 | 54 | def execute(self, context): 55 | if context.active_object.type == 'ARMATURE' \ 56 | and context.active_object.mode == 'EDIT': 57 | bpy.ops.armature.duplicate() 58 | for b in context.selected_bones: 59 | b.parent = None 60 | else: 61 | bpy.ops.object.duplicate() 62 | bpy.ops.object.parent_clear(type='CLEAR') 63 | return bpy.ops.transform.translate('INVOKE_DEFAULT') 64 | 65 | addon_keymaps = [] 66 | 67 | def register(): 68 | bpy.utils.register_class(DuplicateWithoutParent) 69 | 70 | if bpy.app.background: return 71 | 72 | wm = bpy.context.window_manager 73 | kc = wm.keyconfigs.addon 74 | if kc: 75 | km = kc.keymaps.new('Armature', space_type='EMPTY') 76 | kmi = km.keymap_items.new(DuplicateWithoutParent.bl_idname, 77 | 'D', 'PRESS', alt=True, shift=True) 78 | addon_keymaps.append((km, kmi)) 79 | 80 | km = kc.keymaps.new('Object Mode', space_type='EMPTY') 81 | kmi = km.keymap_items.new(DuplicateWithoutParent.bl_idname, 82 | 'D', 'PRESS', alt=True, shift=True) 83 | addon_keymaps.append((km, kmi)) 84 | 85 | def unregister(): 86 | for km, kmi in addon_keymaps: 87 | km.keymap_items.remove(kmi) 88 | addon_keymaps.clear() 89 | 90 | bpy.utils.unregister_class(DuplicateWithoutParent) 91 | 92 | if __name__ == "__main__": 93 | register() 94 | -------------------------------------------------------------------------------- /file_prefix.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2015 Shane Ambler 3 | # 4 | # All rights reserved. 5 | # Redistribution and use in source and binary forms, with or without 6 | # modification, are permitted provided that the following conditions are met: 7 | # 8 | # 1. Redistributions of source code must retain the above copyright 9 | # notice, this list of conditions and the following disclaimer. 10 | # 2. Redistributions in binary form must reproduce the above copyright 11 | # notice, this list of conditions and the following disclaimer in the 12 | # documentation and/or other materials provided with the distribution. 13 | # 14 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 15 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 16 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 17 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 18 | # OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 19 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 20 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 21 | # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 22 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 23 | # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | # 26 | 27 | # made in response to - 28 | # http://blender.stackexchange.com/q/40436/935 29 | 30 | bl_info = { 31 | "name": "Save File Prefix", 32 | "author": "sambler", 33 | "version": (1,1), 34 | "blender": (2, 80, 0), 35 | "location": "File->Save Prefixed Blendfile", 36 | "description": "Add a prefix to the filename before saving.", 37 | "warning": "Runs user specified python code", 38 | "wiki_url": "https://github.com/sambler/addonsByMe/blob/master/file_prefix.py", 39 | "tracker_url": "https://github.com/sambler/addonsByMe/issues", 40 | "category": "System", 41 | } 42 | 43 | import bpy 44 | import os 45 | import time, datetime 46 | 47 | class PrefixSavePreferences(bpy.types.AddonPreferences): 48 | bl_idname = __name__ 49 | 50 | prefix : bpy.props.StringProperty(name="Prefix calculation", 51 | description="Python string that calculates the file prefix", 52 | default="timestamp().strftime('%Y_%m_%d_%H_%M_%S') + '_'") 53 | 54 | copies : bpy.props.BoolProperty(name="Save as copies", 55 | description="Save prefixed copies instead of renaming the existing file", 56 | default=True) 57 | 58 | def draw(self, context): 59 | layout = self.layout 60 | col = layout.column() 61 | 62 | row = col.row() 63 | row.prop(self,"copies") 64 | row = col.row() 65 | row.prop(self,"prefix") 66 | 67 | def timestamp(): 68 | # convienience function that is available to the user in their calculations 69 | return datetime.datetime.fromtimestamp(time.time()) 70 | 71 | class PrefixFileSave(bpy.types.Operator): 72 | """Set a filename prefix before saving the file""" 73 | bl_idname = "wm.save_prefix" 74 | bl_label = "Save Prefixed Blendfile" 75 | 76 | def execute(self, context): 77 | user_preferences = context.preferences 78 | addon_prefs = user_preferences.addons[__name__].preferences 79 | outname = eval(addon_prefs.prefix) + bpy.path.basename(bpy.data.filepath) 80 | outpath = os.path.dirname(bpy.path.abspath(bpy.data.filepath)) 81 | print(os.path.join(outpath, outname)) 82 | if addon_prefs.copies: 83 | return bpy.ops.wm.save_as_mainfile(filepath=os.path.join(outpath, outname), 84 | check_existing=True, copy=True) 85 | return bpy.ops.wm.save_mainfile(filepath=os.path.join(outpath, outname), 86 | check_existing=True) 87 | 88 | def menu_save_prefix(self, context): 89 | self.layout.operator(PrefixFileSave.bl_idname, text=PrefixFileSave.bl_label, icon="FILE_TICK") 90 | 91 | def register(): 92 | bpy.utils.register_class(PrefixSavePreferences) 93 | bpy.utils.register_class(PrefixFileSave) 94 | 95 | # add the menuitem to the top of the file menu 96 | bpy.types.TOPBAR_MT_file.prepend(menu_save_prefix) 97 | 98 | wm = bpy.context.window_manager 99 | win_keymaps = wm.keyconfigs.user.keymaps.get('Window') 100 | if win_keymaps: 101 | # disable standard save file keymaps 102 | for kmi in win_keymaps.keymap_items: 103 | if kmi.idname == 'wm.save_mainfile': 104 | kmi.active = False 105 | 106 | # add a keymap for our save operator 107 | kmi = win_keymaps.keymap_items.new(PrefixFileSave.bl_idname, 'S', 'PRESS', ctrl=True) 108 | 109 | def unregister(): 110 | 111 | wm = bpy.context.window_manager 112 | win_keymaps = wm.keyconfigs.user.keymaps.get('Window') 113 | if win_keymaps: 114 | for kmi in win_keymaps.keymap_items: 115 | # re-enable standard save file 116 | if kmi.idname == 'wm.save_mainfile': 117 | kmi.active = True 118 | if kmi.idname == PrefixFileSave.bl_idname: 119 | win_keymaps.keymap_items.remove(kmi) 120 | 121 | bpy.types.TOPBAR_MT_file.remove(menu_save_prefix) 122 | 123 | bpy.utils.unregister_class(PrefixFileSave) 124 | bpy.utils.unregister_class(PrefixSavePreferences) 125 | 126 | if __name__ == "__main__": 127 | register() 128 | 129 | -------------------------------------------------------------------------------- /mesh_select_cycle.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2018 Shane Ambler 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are 6 | # met: 7 | # 8 | # * Redistributions of source code must retain the above copyright 9 | # notice, this list of conditions and the following disclaimer. 10 | # * Redistributions in binary form must reproduce the above 11 | # copyright notice, this list of conditions and the following disclaimer 12 | # in the documentation and/or other materials provided with the 13 | # distribution. 14 | # 15 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 16 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 17 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 18 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 19 | # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 21 | # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | # 27 | 28 | # made in response to https://blender.stackexchange.com/q/97746/935 29 | 30 | bl_info = { 31 | "name": "Cycle mesh select", 32 | "author": "sambler", 33 | "version": (1, 1), 34 | "blender": (2, 80, 0), 35 | "location": "blender", 36 | "description": "Cycle through mesh selection modes.", 37 | "wiki_url": "https://github.com/sambler/addonsByMe/blob/master/mesh_select_cycle.py", 38 | "tracker_url": "https://github.com/sambler/addonsByMe/issues", 39 | "category": "Mesh", 40 | } 41 | 42 | import bpy 43 | 44 | class SelectCycle(bpy.types.Operator): 45 | bl_idname = 'mesh.select_cycle' 46 | bl_label = 'Cycles through selection modes.' 47 | 48 | @classmethod 49 | def poll(cls, context): 50 | return context.mode == 'EDIT_MESH' 51 | 52 | def execute(self, context): 53 | ts = context.tool_settings 54 | if ts.mesh_select_mode[0]: 55 | ts.mesh_select_mode = [False,True,False] 56 | elif ts.mesh_select_mode[1]: 57 | ts.mesh_select_mode = [False,False,True] 58 | else: 59 | ts.mesh_select_mode = [True,False,False] 60 | return {'FINISHED'} 61 | 62 | def register(): 63 | bpy.utils.register_class(SelectCycle) 64 | 65 | def unregister(): 66 | bpy.utils.unregister_class(SelectCycle) 67 | 68 | if __name__ == "__main__": 69 | register() 70 | -------------------------------------------------------------------------------- /mesh_summary.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2014 Shane Ambler 3 | # 4 | # All rights reserved. 5 | # Redistribution and use in source and binary forms, with or without 6 | # modification, are permitted provided that the following conditions are met: 7 | # 8 | # 1. Redistributions of source code must retain the above copyright 9 | # notice, this list of conditions and the following disclaimer. 10 | # 2. Redistributions in binary form must reproduce the above copyright 11 | # notice, this list of conditions and the following disclaimer in the 12 | # documentation and/or other materials provided with the distribution. 13 | # 14 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 15 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 16 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 17 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 18 | # OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 19 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 20 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 21 | # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 22 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 23 | # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | # 26 | 27 | # made in response to -- 28 | # from http://blender.stackexchange.com/q/13757/935 29 | 30 | bl_info = { 31 | "name": "Mesh Summary", 32 | "author": "sambler", 33 | "version": (1,3), 34 | "blender": (2, 80, 0), 35 | "location": "Properties > Scene > Object Info Panel", 36 | "description": "Summarize details about the mesh objects in this file.", 37 | "warning": "", 38 | "wiki_url": "https://github.com/sambler/addonsByMe/blob/master/mesh_summary.py", 39 | "tracker_url": "https://github.com/sambler/addonsByMe/issues", 40 | "category": "Scene", 41 | } 42 | 43 | import bpy 44 | import bmesh 45 | from bpy.props import IntProperty, BoolProperty 46 | from operator import itemgetter 47 | 48 | class MeshSummaryPreferences(bpy.types.AddonPreferences): 49 | bl_idname = __name__ 50 | 51 | display_limit : IntProperty(name="Display limit", 52 | description="Maximum number of items to list", 53 | default=5, min=2, max=20) 54 | calculate_modifier_verts : BoolProperty(name="Calculate mod. vertices", 55 | description="Calculate vertex count after applying modifiers.", 56 | default=False) 57 | 58 | def draw(self, context): 59 | layout = self.layout 60 | col = layout.column() 61 | 62 | row = col.row() 63 | row.prop(self,"calculate_modifier_verts") 64 | row = col.row() 65 | row.prop(self, "display_limit") 66 | col = row.column() # this stops the button stretching 67 | 68 | def us(qty): 69 | """ 70 | Convert qty to truncated string with unit suffixes. 71 | eg turn 12345678 into 12.3M 72 | """ 73 | 74 | if qty<1000: 75 | return str(qty) 76 | 77 | for suf in ['K','M','G','T','P','E']: 78 | qty /= 1000 79 | if qty<1000: 80 | return "%3.1f%s" % (qty, suf) 81 | 82 | choice_types = [ 83 | ('ALL','All','Show information for all mesh objects',1), 84 | ('SELECTED','Selected','Show information for selected mesh objects',2), 85 | ('VISIBLE','Visible','Show information for visible mesh objects',3), 86 | ] 87 | 88 | class Properties_meshinfo(bpy.types.Panel): 89 | bl_label = "Mesh Information" 90 | bl_space_type = "PROPERTIES" 91 | bl_region_type = "WINDOW" 92 | bl_context = "scene" 93 | 94 | def draw(self, context): 95 | prefs = context.preferences.addons[__name__].preferences 96 | layout = self.layout 97 | 98 | row = layout.row() 99 | row.prop(context.scene, 'show_choice', text='Show') 100 | 101 | if context.scene.show_choice == 'SELECTED': 102 | meshes = [o for o in context.scene.objects 103 | if o.type == 'MESH' and o.select_get() == True] 104 | choice_desc = 'selected ' 105 | total_desc = 'Selected Totals:' 106 | elif context.scene.show_choice == 'VISIBLE': 107 | meshes = [o for o in context.scene.objects 108 | if o.type == 'MESH' and o.hide_viewport == False] 109 | choice_desc = 'visible ' 110 | total_desc = 'Visible Totals:' 111 | else: 112 | meshes = [o for o in context.scene.objects if o.type == 'MESH'] 113 | choice_desc = '' 114 | total_desc = 'Scene Totals:' 115 | 116 | row = layout.row() 117 | if len(meshes) == 1: 118 | row.label(text="1 {}mesh object in this scene.".format(choice_desc), icon='OBJECT_DATA') 119 | else: 120 | row.label(text=us(len(meshes))+" {}mesh objects in this scene.".format(choice_desc), icon='OBJECT_DATA') 121 | row = layout.row() 122 | if len(meshes) > prefs.display_limit: 123 | row.label(text="Top {} {}mesh objects.".format(prefs.display_limit,choice_desc)) 124 | else: 125 | row.label(text="Top {} {}mesh objects.".format(len(meshes), choice_desc)) 126 | 127 | row = layout.row() 128 | row.prop(prefs,"calculate_modifier_verts") 129 | 130 | if len(meshes) > 0: 131 | dataCols = [] 132 | row = layout.row() 133 | dataCols.append(row.column()) # name 134 | dataCols.append(row.column()) # verts 135 | dataCols.append(row.column()) # verts after modifiers 136 | dataCols.append(row.column()) # edges 137 | dataCols.append(row.column()) # faces 138 | 139 | topMeshes = [(o, o.name, len(o.data.vertices), len(o.data.edges), len(o.data.polygons)) for o in meshes] 140 | topMeshes = sorted(topMeshes, key=itemgetter(2), reverse=True)[:prefs.display_limit] 141 | 142 | headRow = dataCols[0].row() 143 | headRow.label(text="Name") 144 | headRow = dataCols[1].row() 145 | headRow.label(text="Verts") 146 | headRow = dataCols[2].row() 147 | headRow.label(text="(mod.)") 148 | headRow = dataCols[3].row() 149 | headRow.label(text="Edges") 150 | headRow = dataCols[4].row() 151 | headRow.label(text="Faces") 152 | 153 | for mo in topMeshes: 154 | detailRow = dataCols[0].row() 155 | detailRow.label(text=mo[1]) 156 | detailRow = dataCols[1].row() 157 | detailRow.label(text=us(mo[2])) 158 | if prefs.calculate_modifier_verts: 159 | detailRow = dataCols[2].row() 160 | bm = bmesh.new() 161 | bm.from_object(mo[0], context.evaluated_depsgraph_get()) 162 | detailRow.label(text="("+us(len(bm.verts))+")") 163 | bm.free() 164 | detailRow = dataCols[3].row() 165 | detailRow.label(text=us(mo[3])) 166 | detailRow = dataCols[4].row() 167 | detailRow.label(text=us(mo[4])) 168 | 169 | vTotal = sum([len(o.data.vertices) for o in meshes]) 170 | eTotal = sum([len(o.data.edges) for o in meshes]) 171 | fTotal = sum([len(o.data.polygons) for o in meshes]) 172 | 173 | totRow = dataCols[0].row() 174 | totRow.label(text=total_desc) 175 | totRow = dataCols[1].row() 176 | totRow.label(text=us(vTotal)) 177 | totRow = dataCols[3].row() 178 | totRow.label(text=us(eTotal)) 179 | totRow = dataCols[4].row() 180 | totRow.label(text=us(fTotal)) 181 | 182 | def register(): 183 | bpy.utils.register_class(MeshSummaryPreferences) 184 | bpy.utils.register_class(Properties_meshinfo) 185 | bpy.types.Scene.show_choice = bpy.props.EnumProperty(items=choice_types, default='ALL') 186 | 187 | def unregister(): 188 | bpy.utils.unregister_class(MeshSummaryPreferences) 189 | bpy.utils.unregister_class(Properties_meshinfo) 190 | del bpy.types.Scene.show_choice 191 | 192 | if __name__ == "__main__": 193 | register() 194 | -------------------------------------------------------------------------------- /node_colours.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2014 Shane Ambler 3 | # 4 | # All rights reserved. 5 | # Redistribution and use in source and binary forms, with or without 6 | # modification, are permitted provided that the following conditions are met: 7 | # 8 | # 1. Redistributions of source code must retain the above copyright 9 | # notice, this list of conditions and the following disclaimer. 10 | # 2. Redistributions in binary form must reproduce the above copyright 11 | # notice, this list of conditions and the following disclaimer in the 12 | # documentation and/or other materials provided with the distribution. 13 | # 14 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 15 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 16 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 17 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 18 | # OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 19 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 20 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 21 | # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 22 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 23 | # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | # 26 | 27 | bl_info = { 28 | "name": "Node Colours", 29 | "author": "sambler", 30 | "version": (1, 1), 31 | "blender": (2, 80, 0), 32 | "location": "SpaceBar Search -> Turn on/off all node colours", 33 | "description": "Turn on/off all custom node colours. Currently only for custom node trees.", 34 | "warning": "", 35 | "wiki_url": "https://github.com/sambler/addonsByMe/blob/master/node_colours.py", 36 | "tracker_url": "https://github.com/sambler/addonsByMe/issues", 37 | "category": "Node"} 38 | 39 | import bpy 40 | from bpy.props import BoolProperty 41 | 42 | class NodeColourPreferences(bpy.types.AddonPreferences): 43 | bl_idname = __name__ 44 | 45 | set_custom_nodes : BoolProperty(name="Set custom nodes", 46 | description="Include custom nodes", 47 | default=True) 48 | 49 | set_compositing_nodes : BoolProperty(name="Set compositing nodes", 50 | description="Include compositing nodes", 51 | default=True) 52 | 53 | set_material_nodes : BoolProperty(name="Set material nodes", 54 | description="Include material nodes", 55 | default=True) 56 | 57 | set_texture_nodes : BoolProperty(name="Set texture nodes", 58 | description="Include texture nodes", 59 | default=True) 60 | 61 | def draw(self, context): 62 | layout = self.layout 63 | col = layout.column() 64 | 65 | row = col.row() 66 | row.prop(self, "set_custom_nodes") 67 | row = col.row() 68 | row.prop(self, "set_compositing_nodes") 69 | row = col.row() 70 | row.prop(self, "set_material_nodes") 71 | row = col.row() 72 | row.prop(self, "set_texture_nodes") 73 | 74 | def setNodeColourOption(setOption): 75 | """Enable/Disable the custom node colour for all nodes""" 76 | 77 | prefs = bpy.context.preferences.addons[__name__].preferences 78 | 79 | if prefs.set_compositing_nodes: 80 | for n in bpy.context.scene.node_tree.nodes: 81 | n.use_custom_color = setOption 82 | 83 | if prefs.set_custom_nodes: 84 | for ng in bpy.data.node_groups: 85 | for n in ng.nodes: 86 | n.use_custom_color = setOption 87 | 88 | if prefs.set_material_nodes: 89 | for mat in bpy.data.materials: 90 | if mat.node_tree and mat.node_tree.nodes: 91 | for n in mat.node_tree.nodes: 92 | n.use_custom_color = setOption 93 | 94 | if prefs.set_texture_nodes: 95 | for tex in bpy.data.textures: 96 | if tex.node_tree and tex.node_tree.nodes: 97 | for n in tex.node_tree.nodes: 98 | n.use_custom_color = setOption 99 | 100 | class NodeColourOn(bpy.types.Operator): 101 | """Turn on custom node colour for all nodes""" 102 | bl_idname = "node.node_colour_on" 103 | bl_label = "Turn on all node colours" 104 | 105 | @classmethod 106 | def poll(cls, context): 107 | return context.area.type == 'NODE_EDITOR' 108 | 109 | def execute(self, context): 110 | setNodeColourOption(True) 111 | return {'FINISHED'} 112 | 113 | class NodeColourOff(bpy.types.Operator): 114 | """Turn off custom node colour for all nodes""" 115 | bl_idname = "node.node_colour_off" 116 | bl_label = "Turn off all node colours" 117 | 118 | @classmethod 119 | def poll(cls, context): 120 | return context.area.type == 'NODE_EDITOR' 121 | 122 | def execute(self, context): 123 | setNodeColourOption(False) 124 | return {'FINISHED'} 125 | 126 | class NodeColourPanel(bpy.types.Panel): 127 | bl_idname = "node_colour_panel" 128 | bl_label = "Node Colours" 129 | bl_space_type = 'NODE_EDITOR' 130 | bl_region_type = 'UI' 131 | use_pin = True 132 | 133 | @classmethod 134 | def poll(cls, context): 135 | return context.area.type == 'NODE_EDITOR' 136 | 137 | def draw(self, context): 138 | layout = self.layout 139 | col = layout.column() 140 | 141 | row = col.row() 142 | row.operator(NodeColourOn.bl_idname) 143 | row.operator(NodeColourOff.bl_idname) 144 | 145 | def register(): 146 | bpy.utils.register_class(NodeColourPreferences) 147 | bpy.utils.register_class(NodeColourOn) 148 | bpy.utils.register_class(NodeColourOff) 149 | bpy.utils.register_class(NodeColourPanel) 150 | 151 | def unregister(): 152 | bpy.utils.unregister_class(NodeColourPreferences) 153 | bpy.utils.unregister_class(NodeColourOn) 154 | bpy.utils.unregister_class(NodeColourOff) 155 | bpy.utils.unregister_class(NodeColourPanel) 156 | 157 | if __name__ == "__main__": 158 | register() 159 | 160 | -------------------------------------------------------------------------------- /presets/interface_theme/myenergy.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 24 | 25 | 35 | 36 | 37 | 38 | 48 | 49 | 50 | 51 | 61 | 62 | 63 | 64 | 74 | 75 | 76 | 77 | 87 | 88 | 89 | 90 | 100 | 101 | 102 | 103 | 113 | 114 | 115 | 116 | 126 | 127 | 128 | 129 | 139 | 140 | 141 | 142 | 152 | 153 | 154 | 155 | 165 | 166 | 167 | 168 | 178 | 179 | 180 | 181 | 191 | 192 | 193 | 194 | 204 | 205 | 206 | 207 | 217 | 218 | 219 | 220 | 230 | 231 | 232 | 233 | 243 | 244 | 245 | 246 | 256 | 257 | 258 | 259 | 269 | 270 | 271 | 272 | 281 | 282 | 283 | 284 | 294 | 295 | 296 | 297 | 298 | 299 | 365 | 366 | 381 | 382 | 385 | 386 | 387 | 388 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 426 | 427 | 443 | 444 | 447 | 448 | 449 | 450 | 451 | 452 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 479 | 480 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 508 | 509 | 525 | 526 | 529 | 530 | 531 | 532 | 533 | 534 | 538 | 539 | 540 | 541 | 542 | 543 | 571 | 572 | 588 | 589 | 592 | 593 | 594 | 595 | 596 | 597 | 601 | 602 | 603 | 604 | 605 | 606 | 647 | 648 | 664 | 665 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 695 | 696 | 712 | 713 | 716 | 717 | 718 | 719 | 720 | 721 | 722 | 723 | 724 | 725 | 741 | 742 | 745 | 746 | 747 | 748 | 749 | 750 | 751 | 752 | 763 | 764 | 780 | 781 | 784 | 785 | 786 | 787 | 788 | 789 | 790 | 791 | 818 | 819 | 835 | 836 | 839 | 840 | 841 | 842 | 843 | 844 | 848 | 849 | 850 | 851 | 852 | 853 | 855 | 856 | 872 | 873 | 876 | 877 | 878 | 879 | 880 | 881 | 882 | 883 | 893 | 894 | 910 | 911 | 914 | 915 | 916 | 917 | 918 | 919 | 920 | 921 | 922 | 923 | 939 | 940 | 943 | 944 | 945 | 946 | 947 | 948 | 949 | 950 | 956 | 957 | 973 | 974 | 977 | 978 | 979 | 980 | 981 | 982 | 983 | 984 | 1009 | 1010 | 1026 | 1027 | 1030 | 1031 | 1032 | 1033 | 1034 | 1035 | 1039 | 1040 | 1041 | 1042 | 1043 | 1044 | 1045 | 1046 | 1062 | 1063 | 1066 | 1067 | 1068 | 1069 | 1070 | 1071 | 1072 | 1073 | 1074 | 1075 | 1091 | 1092 | 1095 | 1096 | 1097 | 1098 | 1099 | 1100 | 1101 | 1102 | 1106 | 1107 | 1111 | 1112 | 1116 | 1117 | 1121 | 1122 | 1126 | 1127 | 1131 | 1132 | 1136 | 1137 | 1141 | 1142 | 1146 | 1147 | 1151 | 1152 | 1156 | 1157 | 1161 | 1162 | 1166 | 1167 | 1171 | 1172 | 1176 | 1177 | 1181 | 1182 | 1186 | 1187 | 1191 | 1192 | 1196 | 1197 | 1201 | 1202 | 1203 | 1204 | 1205 | 1206 | 1213 | 1214 | 1215 | 1216 | 1223 | 1224 | 1225 | 1226 | 1233 | 1234 | 1235 | 1236 | 1237 | -------------------------------------------------------------------------------- /sequencer_alignment.py: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # Copyright (c) 2018 Shane Ambler 4 | # 5 | # Redistribution and use in source and binary forms, with or without 6 | # modification, are permitted provided that the following conditions are 7 | # met: 8 | # 9 | # * Redistributions of source code must retain the above copyright 10 | # notice, this list of conditions and the following disclaimer. 11 | # * Redistributions in binary form must reproduce the above 12 | # copyright notice, this list of conditions and the following disclaimer 13 | # in the documentation and/or other materials provided with the 14 | # distribution. 15 | # 16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20 | # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 | # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | # 28 | 29 | # made in response to https://blender.stackexchange.com/q/106986/935 30 | # provide a way to align strips to edge of frame 31 | 32 | bl_info = { 33 | 'name': 'Sequencer Alignment', 34 | 'author': 'sambler', 35 | 'version': (1, 2), 36 | 'blender': (2, 80, 0), 37 | 'location': 'Video Sequencer', 38 | 'description': 'Align sequencer strips.', 39 | 'wiki_url': 'https://github.com/sambler/addonsByMe/blob/master/sequencer_alignment.py', 40 | 'tracker_url': 'https://github.com/sambler/addonsByMe/issues', 41 | 'category': 'Sequencer', 42 | } 43 | 44 | import bpy 45 | 46 | alignment = [ 47 | ('TOP', 'Top', 'Align top edges', 1), 48 | ('LEFT', 'Left', 'Align left edges', 2), 49 | ('BOTTOM', 'Bottom', 'Align bottom edges', 3), 50 | ('RIGHT', 'Right', 'Align right edges', 4), 51 | ('VERT','Centre Height','Align vertical centre',5), 52 | ('HORIZ','Centre Width','Align horizontal centre',6), 53 | ] 54 | 55 | class VSEStripAlignment(bpy.types.Operator): 56 | bl_idname = 'sequencer.alignment' 57 | bl_label = 'Align sequencer strips.' 58 | bl_options = {'REGISTER','UNDO'} 59 | 60 | direction : bpy.props.EnumProperty(items=alignment) 61 | 62 | def execute(self, context): 63 | scene = context.scene 64 | if scene.vse_align_active: 65 | active_strip = scene.sequence_editor.active_strip 66 | ast = active_strip.transform 67 | ase = active_strip.strip_elem_from_frame(active_strip.frame_start) 68 | asw = ase.orig_width 69 | ash = ase.orig_height 70 | for s in context.selected_sequences: 71 | if s == active_strip: continue 72 | s.use_translation = True 73 | st = s.transform 74 | se = s.strip_elem_from_frame(s.frame_start) 75 | if self.direction == 'TOP': 76 | st.offset_y = ast.offset_y + ash 77 | elif self.direction == 'BOTTOM': 78 | st.offset_y = ast.offset_y - se.orig_height 79 | elif self.direction == 'LEFT': 80 | st.offset_x = ast.offset_x - se.orig_width 81 | elif self.direction == 'RIGHT': 82 | st.offset_x = ast.offset_x + asw 83 | elif self.direction == 'VERT': 84 | st.offset_y = ast.offset_y + (ash/2) - (se.orig_height/2) 85 | elif self.direction == 'HORIZ': 86 | st.offset_x = ast.offset_x + (asw/2) - (se.orig_width/2) 87 | 88 | if s.use_crop: 89 | if self.direction == 'TOP': 90 | st.offset_y += s.crop.min_y + s.crop.max_y 91 | if self.direction == 'RIGHT': 92 | st.offset_x += s.crop.min_x + s.crop.max_x 93 | if self.direction == 'VERT': 94 | st.offset_y += (s.crop.min_y + s.crop.max_y) / 2 95 | if self.direction == 'HORIZ': 96 | st.offset_x += (s.crop.min_x + s.crop.max_x) / 2 97 | else: 98 | width = scene.render.resolution_x 99 | height = scene.render.resolution_y 100 | for s in context.selected_sequences: 101 | s.use_translation = True 102 | st = s.transform 103 | se = s.strip_elem_from_frame(s.frame_start) 104 | if self.direction == 'TOP': 105 | st.offset_y = height - se.orig_height 106 | elif self.direction == 'BOTTOM': 107 | st.offset_y = 0 108 | elif self.direction == 'LEFT': 109 | st.offset_x = 0 110 | elif self.direction == 'RIGHT': 111 | st.offset_x = width - se.orig_width 112 | elif self.direction == 'VERT': 113 | st.offset_y = (height/2) - (se.orig_height/2) 114 | elif self.direction == 'HORIZ': 115 | st.offset_x = (width/2) - (se.orig_width/2) 116 | 117 | if s.use_crop: 118 | if self.direction == 'TOP': 119 | st.offset_y += s.crop.min_y + s.crop.max_y 120 | if self.direction == 'RIGHT': 121 | st.offset_x += s.crop.min_x + s.crop.max_x 122 | if self.direction == 'VERT': 123 | st.offset_y += (s.crop.min_y + s.crop.max_y) / 2 124 | if self.direction == 'HORIZ': 125 | st.offset_x += (s.crop.min_x + s.crop.max_x) / 2 126 | 127 | return {'FINISHED'} 128 | 129 | 130 | class VSEAlignmentPanel(bpy.types.Panel): 131 | bl_idname = 'SEQUENCER_PT_Alignment' 132 | bl_label = 'Align Strips' 133 | bl_space_type = 'SEQUENCE_EDITOR' 134 | bl_region_type = 'UI' 135 | 136 | def draw(self, context): 137 | row = self.layout.row() 138 | row.prop(context.scene, 'vse_align_active', text='Align to active.') 139 | if context.scene.vse_align_active and context.scene.sequence_editor.active_strip: 140 | row = self.layout.row() 141 | row.label(text=context.scene.sequence_editor.active_strip.name) 142 | row = self.layout.row() 143 | row.operator(VSEStripAlignment.bl_idname, text='Top').direction = 'TOP' 144 | row = self.layout.row() 145 | row.operator(VSEStripAlignment.bl_idname, text='Left').direction = 'LEFT' 146 | row.operator(VSEStripAlignment.bl_idname, text='Right').direction = 'RIGHT' 147 | row = self.layout.row() 148 | row.operator(VSEStripAlignment.bl_idname, text='Bottom').direction = 'BOTTOM' 149 | row = self.layout.row() 150 | row.alignment = 'CENTER' 151 | row.label(text='Centre') 152 | row = self.layout.row() 153 | row.operator(VSEStripAlignment.bl_idname, text='^Height^').direction = 'VERT' 154 | row = self.layout.row() 155 | row.operator(VSEStripAlignment.bl_idname, text='<-Width->').direction = 'HORIZ' 156 | 157 | def register(): 158 | bpy.types.Scene.vse_align_active = bpy.props.BoolProperty() 159 | bpy.utils.register_class(VSEStripAlignment) 160 | bpy.utils.register_class(VSEAlignmentPanel) 161 | 162 | def unregister(): 163 | bpy.utils.unregister_class(VSEAlignmentPanel) 164 | bpy.utils.unregister_class(VSEStripAlignment) 165 | del bpy.types.Scene.vse_align_active 166 | 167 | if __name__ == '__main__': 168 | register() 169 | -------------------------------------------------------------------------------- /set_smooth.py: -------------------------------------------------------------------------------- 1 | # Redistribution and use in source and binary forms, with or without 2 | # modification, are permitted provided that the following conditions are 3 | # met: 4 | # 5 | # * Redistributions of source code must retain the above copyright 6 | # notice, this list of conditions and the following disclaimer. 7 | # * Redistributions in binary form must reproduce the above 8 | # copyright notice, this list of conditions and the following disclaimer 9 | # in the documentation and/or other materials provided with the 10 | # distribution. 11 | # 12 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 13 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 14 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 15 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 16 | # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 17 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 18 | # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 19 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 20 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 22 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | # 24 | 25 | # made for https://blender.stackexchange.com/q/85049/935 26 | 27 | bl_info = { 28 | "name": "Set Smooth", 29 | "author": "sambler", 30 | "version": (1, 2), 31 | "blender": (2, 80, 0), 32 | "location": "Render panel", 33 | "description": "Easily set smooth/flat shading to all objects.", 34 | "wiki_url": "https://github.com/sambler/addonsByMe/blob/master/set_smooth.py", 35 | "tracker_url": "https://github.com/sambler/addonsByMe/issues", 36 | "category": "Render", 37 | } 38 | 39 | import bpy 40 | 41 | class SetShading(bpy.types.Operator): 42 | bl_idname = 'object.set_all_shading' 43 | bl_label = 'Set shading of all objects' 44 | 45 | smooth : bpy.props.BoolProperty() 46 | 47 | def execute(self, context): 48 | for o in context.scene.objects: 49 | if o.type == 'MESH': 50 | for f in o.data.polygons: 51 | f.use_smooth = self.smooth 52 | return {'FINISHED'} 53 | 54 | def add_change_shading(self, context): 55 | self.layout.operator(SetShading.bl_idname, text='All smooth').smooth = True 56 | self.layout.operator(SetShading.bl_idname, text='All flat').smooth = False 57 | 58 | def register(): 59 | bpy.utils.register_class(SetShading) 60 | bpy.types.TOPBAR_MT_render.prepend(add_change_shading) 61 | 62 | wm = bpy.context.window_manager 63 | kc = wm.keyconfigs.addon 64 | if kc: 65 | km = kc.keymaps.new('Window', space_type='EMPTY') 66 | kmi = km.keymap_items.new(SetShading.bl_idname, 'F12', 'PRESS', alt=True) 67 | kmi.properties.smooth = True 68 | 69 | kmi = km.keymap_items.new(SetShading.bl_idname, 'F12', 'PRESS', alt=True, shift=True) 70 | kmi.properties.smooth = False 71 | 72 | def unregister(): 73 | wm = bpy.context.window_manager 74 | kc = wm.keyconfigs.addon 75 | if kc: 76 | km = kc.keymaps['Window'] 77 | for kmi in km.keymap_items: 78 | if kmi.idname == SetShading.bl_idname: 79 | km.keymap_items.remove(kmi) 80 | 81 | bpy.types.TOPBAR_MT_render.remove(add_change_shading) 82 | bpy.utils.unregister_class(SetShading) 83 | 84 | if __name__ == "__main__": 85 | register() 86 | -------------------------------------------------------------------------------- /textToFile.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2015 Shane Ambler 3 | # 4 | # All rights reserved. 5 | # Redistribution and use in source and binary forms, with or without 6 | # modification, are permitted provided that the following conditions are met: 7 | # 8 | # 1. Redistributions of source code must retain the above copyright 9 | # notice, this list of conditions and the following disclaimer. 10 | # 2. Redistributions in binary form must reproduce the above copyright 11 | # notice, this list of conditions and the following disclaimer in the 12 | # documentation and/or other materials provided with the distribution. 13 | # 14 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 15 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 16 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 17 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 18 | # OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 19 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 20 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 21 | # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 22 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 23 | # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | # 26 | 27 | # based on response to -- 28 | # http://blender.stackexchange.com/a/26722/935 29 | # by Chebhou 30 | # 31 | # My version splits into two operators to work without a dialog 32 | # this allows use of buttons without a dialog 33 | # in my opinion this is a better user experience. 34 | 35 | bl_info = { 36 | "name": "Text conversion", 37 | "author": "sambler", 38 | "version": (1,1), 39 | "blender": (2, 80, 0), 40 | "location": "View3D > Object > Convert text object", 41 | "description": "Create a text file from the contents of a text object, or set the object contents from a file.", 42 | "warning": "", 43 | "wiki_url": "https://github.com/sambler/addonsByMe/blob/master/textToFile.py", 44 | "tracker_url": "https://github.com/sambler/addonsByMe/issues", 45 | "category": "3D View", 46 | } 47 | 48 | import bpy 49 | 50 | class textToFile(bpy.types.Operator): 51 | """text file to text object""" 52 | bl_idname = "object.text_to_file" 53 | bl_label = "Convert text object to file" 54 | bl_options = {'REGISTER', 'UNDO'} 55 | 56 | def execute(self, context): 57 | obj = bpy.context.active_object 58 | if obj.type == 'FONT': 59 | if None == bpy.data.texts.get(obj.name): 60 | bpy.data.texts.new(name = obj.name) 61 | 62 | text_file = bpy.data.texts[obj.name] 63 | text_file.clear() 64 | text = obj.data.body 65 | text_file.write(text) 66 | return {'FINISHED'} 67 | 68 | class fileToText(bpy.types.Operator): 69 | """text object from text file""" 70 | bl_idname = "object.file_to_text" 71 | bl_label = "Convert text file to object" 72 | bl_options = {'REGISTER', 'UNDO'} 73 | 74 | def execute(self, context): 75 | obj = bpy.context.active_object 76 | if obj.type == 'FONT': 77 | if None == bpy.data.texts.get(obj.name): 78 | bpy.data.texts.new(name = obj.name) 79 | 80 | text_file = bpy.data.texts[obj.name] 81 | obj.data.body = text_file.as_string() 82 | return {'FINISHED'} 83 | 84 | class textConversion(bpy.types.Panel): 85 | """Panel for quick text conversion""" 86 | bl_label = "Convert Text" 87 | bl_idname = "SCENE_PT_conversion" 88 | bl_space_type = 'PROPERTIES' 89 | bl_region_type = 'WINDOW' 90 | bl_context = "data" 91 | 92 | @classmethod 93 | def poll(cls, context): 94 | return (context.object is not None and 95 | context.object.type == 'FONT') 96 | 97 | def draw(self, context): 98 | layout = self.layout 99 | scene = context.scene 100 | 101 | row = layout.row() 102 | row.operator("object.text_to_file") 103 | row = layout.row() 104 | row.operator("object.file_to_text") 105 | 106 | class textSubMenu(bpy.types.Menu): 107 | bl_label = "Convert text" 108 | bl_idname = "OBJECT_MT_convert_text" 109 | 110 | def draw(self, context): 111 | self.layout.operator(textToFile.bl_idname) 112 | self.layout.operator(fileToText.bl_idname) 113 | 114 | def convertMenu(self, context): 115 | self.layout.menu(textSubMenu.bl_idname, icon = 'OUTLINER_DATA_FONT') 116 | 117 | def register(): 118 | bpy.utils.register_class(textToFile) 119 | bpy.utils.register_class(fileToText) 120 | bpy.utils.register_class(textConversion) 121 | bpy.utils.register_class(textSubMenu) 122 | bpy.types.VIEW3D_MT_object.append(convertMenu) 123 | 124 | def unregister(): 125 | bpy.types.VIEW3D_MT_object.remove(convertMenu) 126 | bpy.utils.unregister_class(textToFile) 127 | bpy.utils.unregister_class(fileToText) 128 | bpy.utils.unregister_class(textConversion) 129 | bpy.utils.unregister_class(textSubMenu) 130 | 131 | if __name__ == "__main__": 132 | register() 133 | 134 | --------------------------------------------------------------------------------