├── .gitattributes ├── .gitignore └── pivotPro.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear on external disk 35 | .Spotlight-V100 36 | .Trashes 37 | 38 | # Directories potentially created on remote AFP share 39 | .AppleDB 40 | .AppleDesktop 41 | Network Trash Folder 42 | Temporary Items 43 | .apdisk 44 | -------------------------------------------------------------------------------- /pivotPro.py: -------------------------------------------------------------------------------- 1 | # ##### BEGIN GPL LICENSE BLOCK ##### 2 | # 3 | # This program is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU General Public License 5 | # as published by the Free Software Foundation; either version 2 6 | # of the License, or (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software Foundation, 15 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 16 | # 17 | # ##### END GPL LICENSE BLOCK ##### 18 | 19 | 20 | bl_info = { 21 | "name": "PivotPro", 22 | "author": "Jose Conseco", 23 | "version": (1, 0), 24 | "blender": (2, 75, 0), 25 | "location": "3D view", 26 | "description": "Gives ability to snap pivot", 27 | "warning": "", 28 | "wiki_url": "https://github.com/JoseConseco/PivotPro", 29 | "category": "3D View", 30 | } 31 | 32 | 33 | import bpy 34 | from bpy_extras.view3d_utils import region_2d_to_vector_3d, region_2d_to_location_3d 35 | import bgl 36 | 37 | 38 | class PivotProSettings(bpy.types.PropertyGroup): # seems fucked upp 39 | activeObject = bpy.props.StringProperty() 40 | snap_target = bpy.props.StringProperty() 41 | pivot_point = bpy.props.StringProperty() 42 | bpy.utils.register_class(PivotProSettings) 43 | bpy.types.Scene.PPSettings = bpy.props.CollectionProperty(type=PivotProSettings) 44 | 45 | 46 | class SelectedObjects(): 47 | storedSelectedObjects = [] 48 | 49 | 50 | def UpdatePivotPro(self, context): # just when enabling disablig button 'pivotPro' 51 | if self.pivot_pro_enabled: 52 | context.scene.PPSettings.add() 53 | RegisterHotkeys() 54 | else: 55 | UnRegisterHotkeys() 56 | context.scene.PPSettings.remove(0) 57 | disablePivot(context) 58 | 59 | 60 | bpy.types.Scene.pivot_pro_enabled = bpy.props.BoolProperty(name="Enable PivotPro", description="Turns on/off pivot", default=False,update=UpdatePivotPro) 61 | 62 | 63 | def enablePivot(context): # unhides pivot (or create if dosn't exist) 64 | try: 65 | pivot = bpy.data.objects['PivotPro'] 66 | except: 67 | print('No pivot found! Creating New one') 68 | createPivot(context) 69 | try: 70 | context.scene.objects.link(pivot) 71 | except: 72 | pass 73 | pivot = bpy.data.objects['PivotPro'] # now pivot should be created 74 | layers = [False]*20 75 | layers[context.scene.active_layer] = True 76 | pivot.layers = layers 77 | 78 | pivot.hide_select = False 79 | pivot.select = True 80 | context.scene.objects.active = pivot 81 | 82 | 83 | def disablePivot(context): # hides pivot but do not deletes it 84 | pivot = bpy.data.objects['PivotPro'] 85 | pivot.hide_select = True 86 | pivot.select = False 87 | try: 88 | context.scene.objects.unlink(pivot) 89 | except: 90 | pass 91 | 92 | 93 | def createPivot(context): # just when enabling addon 94 | newEmpty = bpy.data.objects.new('PivotPro', None) 95 | context.scene.objects.link(newEmpty) 96 | layers = [False]*20 97 | layers[context.scene.active_layer] = True 98 | newEmpty.layers = layers 99 | newEmpty.empty_draw_type = "PLAIN_AXES" 100 | newEmpty.empty_draw_size = 0.01 101 | bpy.ops.object.select_all(action='DESELECT') 102 | newEmpty.select = True 103 | context.scene.objects.active = newEmpty 104 | 105 | 106 | def setSnapping(context): 107 | TempStorage = context.scene.PPSettings[0] 108 | TempStorage.snap_target = context.scene.tool_settings.snap_target 109 | TempStorage.pivot_point = context.space_data.pivot_point 110 | TempStorage.activeObject = context.scene.objects.active.name 111 | context.scene.tool_settings.snap_target = 'ACTIVE' 112 | context.space_data.pivot_point = 'CURSOR' 113 | context.scene.cursor_location = bpy.data.objects['PivotPro'].location 114 | 115 | 116 | def resetSnapping(context): 117 | TempStorage = context.scene.PPSettings[0] 118 | context.scene.tool_settings.snap_target = TempStorage.snap_target 119 | context.space_data.pivot_point = TempStorage.pivot_point 120 | context.scene.objects.active = bpy.data.objects[TempStorage.activeObject] 121 | context.scene.cursor_location = bpy.data.objects['PivotPro'].location 122 | 123 | 124 | class PivotMacro(bpy.types.Macro): 125 | """Overall macro declaration - knife then delete""" 126 | bl_idname = "object.pivot_macro" 127 | bl_label = "Pivot Macro" 128 | bl_options = {'REGISTER', "UNDO"} 129 | 130 | @classmethod 131 | def poll(cls, context): 132 | return context.mode == 'OBJECT' 133 | 134 | 135 | class PivotInit(bpy.types.Operator): # sets pivot location after double click (create pivot if first run) 136 | """Move an object with the mouse, example""" 137 | bl_idname = "object.pivot_init" 138 | bl_label = "Ini Pivot" 139 | bl_options = {'REGISTER', 'UNDO'} 140 | 141 | def invoke(self, context, event): 142 | if context.mode == 'OBJECT': 143 | 144 | coord = event.mouse_region_x, event.mouse_region_y 145 | region = context.region 146 | rv3d = context.space_data.region_3d 147 | vec = region_2d_to_vector_3d(region, rv3d, coord) 148 | loc = region_2d_to_location_3d(region, rv3d, coord, vec) 149 | 150 | SelectedObjects.storedSelectedObjects = [obj for obj in context.visible_objects if obj.select] 151 | for obj in SelectedObjects.storedSelectedObjects: 152 | obj.select = False 153 | 154 | oldPivot = bpy.data.objects.get('PivotPro', None) 155 | context.scene.tool_settings.use_snap = True 156 | if oldPivot is not None: 157 | enablePivot(context) 158 | oldPivot.location = loc 159 | else: 160 | createPivot(context) 161 | pivot = bpy.data.objects.get('PivotPro', None) # it exist now after CreatePivot 162 | pivot.location = loc # so put pivot under cursor 163 | return {'FINISHED'} 164 | 165 | 166 | class PivotHide(bpy.types.Operator): 167 | bl_idname = "object.pivot_hide" 168 | bl_label = "Hide Pivot" 169 | bl_options = {'REGISTER', 'UNDO'} 170 | 171 | @classmethod 172 | def poll(cls, context): 173 | return context.mode == 'OBJECT' 174 | 175 | def execute(self, context): 176 | context.scene.tool_settings.use_snap = False 177 | if context.mode == 'OBJECT': 178 | for obj in SelectedObjects.storedSelectedObjects: 179 | obj.select = True 180 | SelectedObjects.storedSelectedObjects.clear() 181 | context.scene.cursor_location = bpy.data.objects['PivotPro'].location 182 | disablePivot(context) 183 | return {'FINISHED'} 184 | 185 | 186 | class PivotTransform(bpy.types.Operator): 187 | """Enable Fast Transform""" 188 | bl_idname = "object.pivot_transform" 189 | bl_label = "Pivot Transform" 190 | 191 | operator = bpy.props.StringProperty("") 192 | 193 | count = 0 194 | 195 | def modal(self, context, event): 196 | self.count += 1 197 | 198 | if self.count == 1: 199 | if self.operator == "Translate": 200 | bpy.ops.transform.translate('INVOKE_DEFAULT') 201 | if self.operator == "Rotate": 202 | bpy.ops.transform.rotate('INVOKE_DEFAULT') 203 | if self.operator == "Scale": 204 | bpy.ops.transform.resize('INVOKE_DEFAULT') 205 | 206 | if event.type in {'X', 'Y', 'Z'}: 207 | return {'PASS_THROUGH'} 208 | 209 | if event.type in {'RIGHTMOUSE', 'ESC', 'LEFTMOUSE'}: 210 | disablePivot(context) 211 | resetSnapping(context) 212 | return {'FINISHED'} 213 | 214 | return {'PASS_THROUGH'} # was running modal 215 | 216 | def invoke(self, context, event): 217 | if bpy.context.scene.pivot_pro_enabled: 218 | enablePivot(context) 219 | setSnapping(context) 220 | context.window_manager.modal_handler_add(self) 221 | return {'RUNNING_MODAL'} 222 | else: 223 | return {'PASS_THROUGH'} # was CANCELL but broke edit mesh G, R, S 224 | 225 | 226 | def drawPivotRed(): 227 | if bpy.context.scene.pivot_pro_enabled: 228 | oldPivot = bpy.data.objects.get('PivotPro', None) 229 | if oldPivot is not None: 230 | 231 | bgl.glEnable(bgl.GL_BLEND) 232 | 233 | bgl.glColor3f(1, 0, 0) 234 | bgl.glPointSize(10) 235 | bgl.glBegin(bgl.GL_POINTS) 236 | bgl.glVertex3f(*oldPivot.location) 237 | bgl.glEnd() 238 | bgl.glDisable(bgl.GL_BLEND) 239 | 240 | # restore defaults 241 | bgl.glPointSize(1) 242 | bgl.glDisable(bgl.GL_BLEND) 243 | bgl.glColor3f(0.0, 0.0, 0.0) 244 | 245 | 246 | def addon_button(self, context): 247 | layout = self.layout 248 | if bpy.context.scene.pivot_pro_enabled: 249 | layout.prop(context.scene, "pivot_pro_enabled", text='PivotPro', icon='OUTLINER_OB_EMPTY') 250 | else: 251 | layout.prop(context.scene, "pivot_pro_enabled", text='PivotPro', icon='OUTLINER_OB_EMPTY') 252 | 253 | 254 | addon_keymaps = [] # put on out of register() 255 | handleDrawPivot = [] 256 | 257 | 258 | def RegisterHotkeys(): 259 | wm = bpy.context.window_manager 260 | km = wm.keyconfigs.addon.keymaps.new(name='Object Mode', space_type='EMPTY') 261 | kmi = km.keymap_items.new(PivotMacro.bl_idname, 'LEFTMOUSE', 'DOUBLE_CLICK') 262 | #kmi.properties.my_prop = 'some' 263 | addon_keymaps.append((km, kmi)) 264 | 265 | kmi = km.keymap_items.new(PivotTransform.bl_idname, 'G', 'PRESS', shift=True) 266 | kmi.properties.operator = "Translate" 267 | addon_keymaps.append((km, kmi)) 268 | 269 | kmi = km.keymap_items.new(PivotTransform.bl_idname, 'R', 'PRESS', shift=True) 270 | kmi.properties.operator = "Rotate" 271 | addon_keymaps.append((km, kmi)) 272 | 273 | kmi = km.keymap_items.new(PivotTransform.bl_idname, 'S', 'PRESS', shift=True) 274 | kmi.properties.operator = "Scale" 275 | addon_keymaps.append((km, kmi)) 276 | 277 | 278 | def UnRegisterHotkeys(): 279 | for km, kmi in addon_keymaps: 280 | km.keymap_items.remove(kmi) 281 | addon_keymaps.clear() 282 | 283 | 284 | def register(): 285 | bpy.utils.register_module(__name__) 286 | PivotMacro.define("OBJECT_OT_pivot_init") 287 | PivotMacro.define("TRANSFORM_OT_translate") 288 | PivotMacro.define("OBJECT_OT_pivot_hide") 289 | 290 | if handleDrawPivot: 291 | bpy.types.SpaceView3D.draw_handler_remove(handleDrawPivot[0], 'WINDOW') 292 | handleDrawPivot[:] = [bpy.types.SpaceView3D.draw_handler_add(drawPivotRed, (), 'WINDOW', 'POST_VIEW')] 293 | 294 | bpy.types.VIEW3D_HT_header.append(addon_button) 295 | 296 | try: 297 | if bpy.context.scene.pivot_pro_enabled: 298 | RegisterHotkeys() 299 | else: 300 | UnRegisterHotkeys() 301 | except: 302 | pass 303 | #RegisterHotkeys() #if there is no property pivot_pro_enabled means we run it first time. By default is is disabled 304 | 305 | 306 | def unregister(): 307 | bpy.utils.unregister_module(__name__) 308 | 309 | if handleDrawPivot: 310 | bpy.types.SpaceView3D.draw_handler_remove(handleDrawPivot[0], 'WINDOW') 311 | handleDrawPivot[:] = [] 312 | 313 | bpy.types.VIEW3D_HT_header.remove(addon_button) 314 | 315 | bpy.utils.unregister_class(PivotProSettings) 316 | UnRegisterHotkeys() 317 | 318 | 319 | if __name__ == "__main__": 320 | register() 321 | --------------------------------------------------------------------------------