├── .gitattributes ├── .gitignore ├── RA_draw_ui.py └── __init__.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .vscode/settings.json 3 | .vscode/launch.json 4 | __pycache__/__init__.cpython-37.pyc 5 | __pycache__/RA_draw_ui.cpython-37.pyc 6 | -------------------------------------------------------------------------------- /RA_draw_ui.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import gpu 3 | import blf 4 | from gpu_extras.batch import batch_for_shader 5 | 6 | 7 | def RA_modal_Draw(self, context, prefs): 8 | height = bpy.context.region.height 9 | width = bpy.context.region.width 10 | CO = context.object 11 | 12 | font_id = 0 13 | 14 | #+ text 15 | if CO.RA_Unq_mode == True: 16 | blf.color (font_id,0.9,0.32,0.35,1) 17 | else: 18 | blf.color (font_id,0.85,0.85,0.85,1) 19 | #* Offset 20 | blf.position(font_id, (width/2) - 200, (height/2) - 250, 0) 21 | blf.size(font_id, 20, 60) 22 | blf.draw(font_id, ("{} {}".format("Offset: ",str(round(CO.RA_Offset, 2)))) ) 23 | 24 | #* Object Selectable 25 | blf.position(font_id, (width/2) + 50, (height/2) - 250, 0) 26 | 27 | blf.draw(font_id, ("{} {}".format("Selectable: ",str(CO.RA_Sel_Status))) ) 28 | 29 | #* Object Number "Count" 30 | blf.position(font_id, (width/2) - 50, (height/2) - 250, 0) 31 | if CO.RA_Unq_mode == True: 32 | blf.color (font_id,0.5,0.5,0.5,1) 33 | else: 34 | blf.color (font_id,0.85,0.85,0.85,1) 35 | 36 | blf.draw(font_id, ("{} {}".format("Count: ",str(round(CO.RA_ObjNum, 2)))) ) 37 | #* Show/Hide Help 38 | blf.color (font_id,1,1,1,1) 39 | text = "Show/Hide Help 'H'" 40 | blf.position(font_id, (width/2 - blf.dimensions(font_id, text)[0] / 2), (height/2) - 230, 0) 41 | 42 | blf.draw(font_id, text) 43 | #+--------------------------------------------------------------+# 44 | #* Unique Mode 45 | blf.color (font_id,0.8,0.4,0.0,1) 46 | text = "Unique Mode: " 47 | blf.position(font_id, (width/2 - 84), (height/2) - 270, 0) 48 | blf.draw(font_id, text) 49 | #-------------------------# 50 | if CO.RA_Unq_mode == True: 51 | blf.color (font_id,0.1,0.94,0.4,1) 52 | unq_text = "Active" 53 | else: 54 | blf.color (font_id,0.6,0.1,0.0,1) 55 | unq_text = "--------" 56 | blf.position(font_id, (width/2 + 34), (height/2) - 270, 0) 57 | blf.draw(font_id, unq_text) 58 | #+--------------------------------------------------------------+# 59 | #* Help 60 | blf.color (font_id,0.6,1,0.6,1) 61 | if prefs.modal_help == True: 62 | lines = ["Reset 'R'", 63 | "Apply 'A'", 64 | "Join 'J' ends radial mode and merges all objects", 65 | "Grab 'G'", 66 | "Unique Mode 'Q' unlinks objects data block", 67 | "'RMB' and Esc to Cancel", 68 | "'Shift' to snap offset", 69 | "'Mouse Wheel' Increase/Decrease Count" 70 | ] 71 | for index, l in enumerate(lines): 72 | text = l 73 | blf.position(font_id, (width/2) - 200, (height/2 -200) + 20 * index, 0) 74 | 75 | blf.draw(font_id, text) 76 | 77 | def RA_draw_B(self, context, prefs): 78 | height = bpy.context.region.height 79 | width = bpy.context.region.width 80 | CO = bpy.context.object 81 | #+-----------------------------------------------------------------------+# 82 | vertices = ( 83 | (width/2 - 80 , height/2 - 215),(width/2 + 80, height/2 - 215), 84 | (width/2 - 90, height/2 - 233),( width/2 + 90, height/2 - 233) ) 85 | 86 | indices = ( 87 | (0, 1, 2), (2, 1, 3)) 88 | 89 | shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR') 90 | batch = batch_for_shader(shader, 'TRIS', {"pos": vertices}, indices=indices) 91 | 92 | shader.bind() 93 | 94 | shader.uniform_float("color", (0.8,0.4,0.0,1)) 95 | batch.draw(shader) 96 | #+-----------------------------------------------------------------------+# 97 | vertices = ( 98 | (width/2 - 216 , height/2 - 234),(width/2 + 206, height/2 - 234), 99 | (width/2 - 220, height/2 - 254),( width/2 + 200, height/2 - 254) ) 100 | 101 | indices = ( 102 | (0, 1, 2), (2, 1, 3)) 103 | 104 | shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR') 105 | batch = batch_for_shader(shader, 'TRIS', {"pos": vertices}, indices=indices) 106 | 107 | 108 | 109 | shader.bind() 110 | shader.uniform_float("color", (0.15,0.15,0.15,1)) 111 | batch.draw(shader) 112 | #+-----------------------------------------------------------------------+# 113 | vertices = ( 114 | (width/2 - 96 , height/2 - 253),(width/2 + 96, height/2 - 253), 115 | (width/2 - 86, height/2 - 274),( width/2 + 86, height/2 - 274) ) 116 | 117 | indices = ( 118 | (0, 1, 2), (2, 1, 3)) 119 | 120 | shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR') 121 | batch = batch_for_shader(shader, 'TRIS', {"pos": vertices}, indices=indices) 122 | 123 | shader.bind() 124 | shader.uniform_float("color", (0.15,0.15,0.15,1)) 125 | batch.draw(shader) -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | 2 | import bpy,math,mathutils,blf,rna_keymap_ui 3 | 4 | from .RA_draw_ui import * 5 | from mathutils import Matrix 6 | from bpy.types import ( 7 | PropertyGroup, 8 | Menu 9 | ) 10 | from bpy.props import ( 11 | IntProperty, 12 | FloatProperty, 13 | BoolProperty 14 | ) 15 | #// join objects option in modal operator 16 | #// Reset array option in modal operator 17 | #// Modal operator Ui 18 | #// add Radial Array hotkey 19 | #// preferences add hotkey in addon preferences menu 20 | #// addon menu ui 21 | #// add modal selectable toggle 22 | #// add modal apply option 23 | #// add modal ui tooltips 24 | #// add make unique 25 | #// add create collection toggle 26 | 27 | 28 | bl_info = { 29 | "name" : "R.Array", 30 | "author" : "Syler", 31 | "version": (0, 0, 1, 2), 32 | "description": "Adds Radial Array Operator", 33 | "blender" : (2, 80, 0), 34 | "category" : "Object" 35 | } 36 | #+ handle the keymap 37 | addon_keymaps = [] 38 | 39 | def add_hotkey(): 40 | #* Ctrl Q call R_Array 41 | wm = bpy.context.window_manager 42 | km = wm.keyconfigs.addon.keymaps.new(name='Object Mode', space_type='EMPTY') 43 | kmi = km.keymap_items.new(R_Array.bl_idname, 'Q', 'PRESS', ctrl=True) 44 | addon_keymaps.append(km) 45 | 46 | def remove_hotkey(): 47 | wm = bpy.context.window_manager 48 | for km in addon_keymaps: 49 | wm.keyconfigs.addon.keymaps.remove(km) 50 | # clear the list 51 | del addon_keymaps[:] 52 | #--------------------------------------------------------------------------------------# 53 | def RA_Update_Sel_Status(self, context): 54 | if self.RA_Sel_Status == True: 55 | for ob in self.RA_Parent.children: 56 | ob.hide_select = False 57 | if self.RA_Sel_Status == False: 58 | for ob in self.RA_Parent.children: 59 | ob.hide_select = True 60 | 61 | def RA_Update_ObjNum(self, context): 62 | 63 | if self.RA_Status == True: 64 | 65 | if len(self.RA_Parent.children) == self.RA_ObjNum: 66 | pass 67 | 68 | #+ Add Objects 69 | if len(self.RA_Parent.children) < self.RA_ObjNum: 70 | object_list = [] 71 | object_to_copy = self.RA_Parent.children[0] 72 | # append already existing objects to object list 73 | for c in self.RA_Parent.children: 74 | object_list.append(c) 75 | 76 | 77 | for i in range (len(self.RA_Parent.children), self.RA_ObjNum): 78 | object_list.append(object_to_copy.copy()) 79 | 80 | 81 | 82 | # Add Objects To Collection 83 | for index, ob in enumerate(object_list): 84 | 85 | # Reset Matrix 86 | ob.matrix_basis = mathutils.Matrix() 87 | 88 | # set object location to RA_Parent + RA_Offset 89 | ob.location[1] = self.RA_Parent.location[1] + self.RA_Parent.RA_Offset 90 | # create angle variable 91 | angle = math.radians(360/self.RA_Parent.RA_ObjNum) 92 | 93 | # rotate object 94 | R = mathutils.Matrix.Rotation(angle * (index), 4, 'Z') 95 | T = mathutils.Matrix.Translation([0, 0, 0]) 96 | M = T @ R @ T.inverted() 97 | ob.location = M @ ob.location 98 | ob.rotation_euler.rotate(M) 99 | 100 | 101 | # Parent Object 102 | ob.parent = self.RA_Parent 103 | self.RA_Parent.matrix_parent_inverse = ob.matrix_world.inverted() 104 | ob.RA_Parent = self.RA_Parent 105 | 106 | # make objects selectable/unselectable 107 | if self.RA_Sel_Status == True: 108 | ob.hide_select = False 109 | if self.RA_Sel_Status == False: 110 | ob.hide_select = True 111 | 112 | # Change Object Name 113 | ob.name = "RA - " + self.RA_Name + " - " + str(index) 114 | # set RA Status 115 | ob.RA_Status = True 116 | # Link object 117 | try: 118 | self.RA_Parent.users_collection[0].objects.link(ob) 119 | #print ("For LINK") 120 | except: 121 | #print ("PASS Linking object to collection failed") 122 | pass 123 | 124 | #+ Remove Objects 125 | if len(self.RA_Parent.children) > self.RA_ObjNum: 126 | 127 | # deselect all objects 128 | for d in bpy.context.view_layer.objects: 129 | d.select_set(False) 130 | bpy.context.view_layer.objects.active = None 131 | 132 | # Make selectable and Select all objects that will be deleted 133 | for i in range (self.RA_ObjNum, len(self.RA_Parent.children)): 134 | self.RA_Parent.children[i].hide_select = False 135 | self.RA_Parent.children[i].select_set(True) 136 | # Delete Objects 137 | bpy.ops.object.delete() 138 | # select control Object 139 | bpy.context.view_layer.objects.active = self.RA_Parent 140 | self.RA_Parent.select_set(True) 141 | for index, ob in enumerate(self.RA_Parent.children): 142 | # Reset Matrix 143 | ob.matrix_basis = mathutils.Matrix() 144 | 145 | # set object location to RA_Parent + RA_Offset 146 | ob.location[1] = self.RA_Parent.location[1] + self.RA_Parent.RA_Offset 147 | # create angle variable 148 | angle = math.radians(360/self.RA_Parent.RA_ObjNum) 149 | 150 | # rotate object 151 | R = mathutils.Matrix.Rotation(angle * (index), 4, 'Z') 152 | T = mathutils.Matrix.Translation([0, 0, 0]) 153 | M = T @ R @ T.inverted() 154 | ob.location = M @ ob.location 155 | ob.rotation_euler.rotate(M) 156 | 157 | def RA_Update_Offset(self, context): 158 | 159 | if self.RA_Status == True: 160 | for ob in self.RA_Parent.children: 161 | # define variables 162 | loc = mathutils.Vector((0.0, self.RA_Offset, 0.0)) 163 | rot = ob.rotation_euler 164 | # rotate location 165 | loc.rotate(rot) 166 | # apply rotation 167 | ob.location = loc 168 | else: 169 | pass 170 | #--------------------------------------------------------------------------------------# 171 | class R_Array(bpy.types.Operator): 172 | bl_idname = 'sop.r_array' 173 | bl_label = 'Radial Array' 174 | bl_description = 'Radial Array S.Operator' 175 | bl_options = {'REGISTER', 'UNDO'} 176 | 177 | 178 | 179 | 180 | #?Useless !? 181 | @classmethod 182 | def poll(cls, context): 183 | return True 184 | 185 | def execute(self, context): 186 | 187 | #Create Bpy.context Variable 188 | C = bpy.context 189 | active_object = C.active_object 190 | 191 | 192 | # call modal if RA_Status = True 193 | try: 194 | if active_object.RA_Status == True: 195 | bpy.ops.sop.ra_modal('INVOKE_DEFAULT') 196 | return {'FINISHED'} 197 | except: 198 | pass 199 | # Check Selected Cancel if NOT Mesh 200 | if C.selected_objects == [] or C.active_object.type != 'MESH': 201 | self.report({'INFO'}, "No Mesh Selected") 202 | return {'CANCELLED'} 203 | 204 | 205 | # Create Variables 206 | L_Objects = [] # object list 207 | ob = active_object # active object reference 208 | ob_collections = ob.users_collection # active Object collections 209 | f_name = ob.name # Object Name 210 | point = ob.location.copy() # Middle point 211 | is_col_new = True 212 | 213 | 214 | # Create New Collection 215 | if bpy.context.preferences.addons[__name__].preferences.col_toggle == True: 216 | for q in bpy.data.collections: 217 | if q.name == "RA -" + f_name: 218 | collection = q 219 | is_col_new = False 220 | try: 221 | for col in ob_collections: 222 | col.objects.unlink(ob) 223 | collection.objects.link(ob) 224 | except: 225 | pass 226 | 227 | 228 | if is_col_new == True: 229 | # create and link new collection 230 | collection = bpy.data.collections.new(name="RA -" + f_name) 231 | bpy.context.scene.collection.children.link(collection) 232 | print ("NEW") 233 | # Move Object to collection 234 | for col in ob_collections: 235 | col.objects.unlink(ob) 236 | collection.objects.link(ob) 237 | else: 238 | collection = ob_collections[0] 239 | 240 | # Create/Location/Name/Status/set RA_Parent/Link Empty and other memery 241 | empty = bpy.data.objects.new( "empty", None ) 242 | empty.location = point 243 | empty.name = ".RA - " + ob.name + " - Control Empty" 244 | empty.RA_Status = True 245 | empty.RA_Parent = empty 246 | empty.RA_Name = f_name 247 | empty.RA_Sel_Status = bpy.context.preferences.addons[__name__].preferences.selectable 248 | collection.objects.link(empty) 249 | 250 | # Move object 251 | ob.location[1] = ob.location[1] + ob.RA_Offset 252 | 253 | # Deselect Active Object and select Control Object 254 | ob.select_set(False) 255 | empty.select_set(True) 256 | 257 | # set empty as active object 258 | bpy.context.view_layer.objects.active = empty 259 | 260 | # create duplicate objects 261 | for o in range(0, empty.RA_ObjNum): 262 | 263 | if o == 0: 264 | L_Objects.append(ob) 265 | if o != 0: 266 | L_Objects.append(ob.copy()) 267 | # Add Objects To Collection 268 | for index, ob in enumerate(L_Objects): 269 | # create angle variable 270 | angle = math.radians(360/empty.RA_ObjNum) 271 | 272 | 273 | # rotate object 274 | R = mathutils.Matrix.Rotation(angle * (index), 4, 'Z') 275 | T = mathutils.Matrix.Translation([0, 0, 0]) 276 | M = T @ R @ T.inverted() 277 | ob.location = M @ ob.location 278 | ob.rotation_euler.rotate(M) 279 | 280 | # Parent Object 281 | ob.parent = empty 282 | empty.matrix_parent_inverse = ob.matrix_world.inverted() 283 | ob.RA_Parent = empty 284 | 285 | # make objects selectable/unselectable 286 | if empty.RA_Sel_Status == True: 287 | ob.hide_select = False 288 | if empty.RA_Sel_Status == False: 289 | ob.hide_select = True 290 | 291 | # Change Object Name 292 | ob.name = "RA - " + str(f_name) + " - " + str(index) 293 | # Set RA Status 294 | ob.RA_Status = True 295 | 296 | # Link object 297 | try: 298 | collection.objects.link(ob) 299 | #print ("For LINK") 300 | except: 301 | #print ("PASS Linking object to collection failed") 302 | pass 303 | bpy.ops.sop.ra_modal('INVOKE_DEFAULT') 304 | 305 | return {'FINISHED'} 306 | #--------------------------------------------------------------------------------------# 307 | class RA_Modal(bpy.types.Operator): 308 | # Change Radial Array 309 | bl_idname = "sop.ra_modal" 310 | bl_label = "Radial Array Modal" 311 | bl_options = {"REGISTER", "UNDO", "BLOCKING", "GRAB_CURSOR", "INTERNAL"} #- add later!? 312 | 313 | first_mouse_x: IntProperty() 314 | I_RA_Offset: FloatProperty() 315 | I_RA_ObjNum: IntProperty() 316 | unq_mode: BoolProperty() 317 | 318 | 319 | def modal(self, context, event): 320 | 321 | # context shortcut 322 | C = context 323 | OB = C.object 324 | context.area.tag_redraw() #? 325 | prefs = bpy.context.preferences.addons[__name__].preferences 326 | # -------------------------------------------------------------# 327 | #+ change offset 328 | if event.type == 'MOUSEMOVE' : 329 | delta = self.first_mouse_x - event.mouse_x 330 | if event.shift: 331 | C.object.RA_Offset = round((self.I_RA_Offset + delta * 0.01)) 332 | else: 333 | C.object.RA_Offset = self.I_RA_Offset + delta * 0.01 334 | # -------------------------------------------------------------# 335 | #+ add/remove Objects 336 | if event.type == 'WHEELUPMOUSE' and OB.RA_Unq_mode == False: 337 | OB.RA_ObjNum = OB.RA_ObjNum + 1 338 | 339 | if event.type == 'WHEELDOWNMOUSE' and OB.RA_Unq_mode == False: 340 | OB.RA_ObjNum = OB.RA_ObjNum - 1 341 | # -------------------------------------------------------------# 342 | #+ call the tarnslation operator 343 | if event.type == 'G' and event.value == "PRESS": 344 | 345 | C.tool_settings.use_snap = True 346 | C.tool_settings.snap_elements = {'FACE'} 347 | C.tool_settings.use_snap_align_rotation = True 348 | 349 | bpy.ops.transform.translate('INVOKE_DEFAULT') 350 | bpy.types.SpaceView3D.draw_handler_remove(self.ra_draw_b, 'WINDOW') 351 | bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') 352 | return {'FINISHED'} 353 | # -------------------------------------------------------------# 354 | 355 | #+ join objects 356 | if event.type == 'J' and event.value == "PRESS": 357 | objects = OB.RA_Parent.children 358 | location = OB.RA_Parent.location 359 | cursor_location = bpy.context.scene.cursor.location.copy() 360 | 361 | # deselect objects and select control object 362 | for o in C.selected_objects: 363 | o.select_set(False) 364 | C.object.RA_Parent.hide_select = False 365 | bpy.context.view_layer.objects.active = C.object.RA_Parent 366 | C.object.RA_Parent.select_set(True) 367 | 368 | # Delete control object 369 | bpy.ops.object.delete() 370 | 371 | for ob in objects: 372 | ob.hide_select = False 373 | ob.select_set(True) 374 | bpy.context.view_layer.objects.active = objects[0] 375 | 376 | 377 | bpy.context.scene.cursor.location = location 378 | bpy.ops.view3d.snap_selected_to_cursor(use_offset=True) 379 | bpy.ops.object.parent_clear(type='CLEAR_KEEP_TRANSFORM') 380 | bpy.ops.object.join() 381 | bpy.ops.object.origin_set(type='ORIGIN_CURSOR') 382 | bpy.context.scene.cursor.location = cursor_location 383 | bpy.types.SpaceView3D.draw_handler_remove(self.ra_draw_b, 'WINDOW') 384 | bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') 385 | return {'FINISHED'} 386 | # -------------------------------------------------------------# 387 | 388 | #+ Reset 389 | if event.type == 'R' and event.value == "PRESS": 390 | 391 | objects = OB.RA_Parent.children 392 | name = OB.RA_Parent.RA_Name 393 | # deslect all objects 394 | for o in C.selected_objects: 395 | o.select_set(False) 396 | # select objects 397 | for ob in objects: 398 | if ob != objects[0]: 399 | ob.hide_select = False 400 | ob.select_set(True) 401 | # delete objects 402 | bpy.ops.object.delete() 403 | 404 | # select object and clear parent and other memery 405 | objects[0].location = objects[0].RA_Parent.location 406 | objects[0].RA_Parent.select_set(True) 407 | bpy.ops.object.delete() 408 | objects[0].hide_select = False 409 | bpy.context.view_layer.objects.active = objects[0] 410 | objects[0].select_set(True) 411 | objects[0].parent = None 412 | objects[0].name = name 413 | try: 414 | del objects[0]["RA_Parent"] 415 | del objects[0]["RA_Status"] 416 | except: 417 | pass 418 | 419 | bpy.types.SpaceView3D.draw_handler_remove(self.ra_draw_b, 'WINDOW') 420 | bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') 421 | return {'FINISHED'} 422 | #+ Apply 423 | if event.type == 'A' and event.value == "PRESS": 424 | 425 | objects = OB.RA_Parent.children 426 | # deslect all objects 427 | for o in C.selected_objects: 428 | o.select_set(False) 429 | # select and delete control object 430 | objects[0].RA_Parent.select_set(True) 431 | bpy.ops.object.delete() 432 | # select objects 433 | for ob in objects: 434 | 435 | ob.hide_select = False 436 | ob.select_set(True) 437 | ob.RA_Status = False 438 | ob.parent = None 439 | 440 | bpy.context.view_layer.objects.active = objects[0] 441 | bpy.types.SpaceView3D.draw_handler_remove(self.ra_draw_b, 'WINDOW') 442 | bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') 443 | return {'FINISHED'} 444 | #+ Make Unique Mode toggle 445 | if event.type == 'Q' and event.value == "PRESS": 446 | objects = OB.RA_Parent.children 447 | if OB.RA_Unq_mode == True: 448 | for ob in objects: 449 | ob.data = objects[0].data 450 | OB.RA_Unq_mode = False 451 | else: 452 | #* make unique data 453 | for ob in objects: 454 | ob.data = ob.data.copy() 455 | OB.RA_Unq_mode = True 456 | #+ Selectable toggle 457 | if event.type == 'S' and event.value == "PRESS": 458 | if OB.RA_Sel_Status == True: 459 | OB.RA_Sel_Status = False 460 | else: 461 | OB.RA_Sel_Status = True 462 | #+ Help Mode toggle 463 | if event.type == 'H' and event.value == "PRESS": 464 | if prefs.modal_help == True: 465 | prefs.modal_help = False 466 | else: 467 | prefs.modal_help = True 468 | # -------------------------------------------------------------# 469 | #+ Finish/Cancel Modal 470 | elif event.type == 'LEFTMOUSE': 471 | bpy.types.SpaceView3D.draw_handler_remove(self.ra_draw_b, 'WINDOW') 472 | bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') 473 | return {'FINISHED'} 474 | 475 | elif event.type in {'RIGHTMOUSE', 'ESC'}: 476 | C.object.RA_Offset = self.I_RA_Offset 477 | C.object.RA_ObjNum = self.I_RA_ObjNum 478 | bpy.types.SpaceView3D.draw_handler_remove(self.ra_draw_b, 'WINDOW') 479 | bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') 480 | return {'CANCELLED'} 481 | 482 | return {'RUNNING_MODAL'} 483 | 484 | def invoke(self, context, event): 485 | # context shortcut 486 | C = context 487 | if C.object.RA_Status == True: 488 | for o in C.selected_objects: 489 | o.select_set(False) 490 | bpy.context.view_layer.objects.active = C.object.RA_Parent 491 | C.object.RA_Parent.select_set(True) 492 | 493 | 494 | 495 | if C.object: 496 | # set initial Variable values 497 | self.first_mouse_x = event.mouse_x 498 | self.I_RA_Offset = C.object.RA_Offset 499 | self.I_RA_ObjNum = C.object.RA_ObjNum 500 | self.unq_mode = C.object.RA_Unq_mode 501 | self.prefs = bpy.context.preferences.addons[__name__].preferences 502 | ###-------------------------------------------### 503 | args = (self, context, self.prefs) 504 | 505 | 506 | self.ra_draw_b = bpy.types.SpaceView3D.draw_handler_add(RA_draw_B, args, 'WINDOW', 'POST_PIXEL') 507 | self._handle = bpy.types.SpaceView3D.draw_handler_add(RA_modal_Draw, args, 'WINDOW', 'POST_PIXEL') 508 | 509 | self.mouse_path = [] 510 | 511 | context.window_manager.modal_handler_add(self) 512 | return {'RUNNING_MODAL'} 513 | else: 514 | self.report({'WARNING'}, "No active object, could not finish") 515 | return {'CANCELLED'} 516 | #--------------------------------------------------------------------------------------# 517 | class RA_Prefs(bpy.types.AddonPreferences): 518 | bl_idname = __name__ 519 | # here you define the addons customizable props 520 | offset: bpy.props.FloatProperty(default=5) 521 | objnum: bpy.props.IntProperty(default=6) 522 | selectable: bpy.props.BoolProperty(default= True, description="False = Only Control Object is selectable") 523 | modal_help: bpy.props.BoolProperty(default= False, description="True = Display Help text in modal") 524 | col_toggle: bpy.props.BoolProperty(default= False, description="True = Create New Collection") 525 | # here you specify how they are drawn 526 | def draw(self, context): 527 | layout = self.layout 528 | box = layout.box() 529 | split = box.split() 530 | col = split.column() 531 | # Layout ---------------------------------------------------------------- # 532 | col.label(text="Default Values:") 533 | col.prop(self, "offset",text="Default Offset") 534 | col.prop(self, "objnum",text="Default Count") 535 | col.prop(self, "selectable",text="Selectable") 536 | col.prop(self, "modal_help",text="Modal Help") 537 | col.label(text ="Options:") 538 | col.prop(self, "col_toggle",text="Create New Collection") 539 | col.label(text="Keymap:") 540 | 541 | 542 | wm = bpy.context.window_manager 543 | kc = wm.keyconfigs.user 544 | km = kc.keymaps['Object Mode'] 545 | #kmi = km.keymap_items[0] 546 | kmi = get_hotkey_entry_item(km, 'sop.r_array', 'sop.r_array') 547 | 548 | if addon_keymaps: 549 | km = addon_keymaps[0].active() 550 | col.context_pointer_set("keymap", km) 551 | rna_keymap_ui.draw_kmi([], kc, km, kmi, col, 0) 552 | 553 | 554 | 555 | def get_addon_preferences(): 556 | ''' quick wrapper for referencing addon preferences ''' 557 | addon_preferences = bpy.context.user_preferences.addons[__name__].preferences 558 | return addon_preferences 559 | def get_hotkey_entry_item(km, kmi_name, kmi_value): 560 | ''' 561 | returns hotkey of specific type, with specific properties.name (keymap is not a dict, so referencing by keys is not enough 562 | if there are multiple hotkeys!) 563 | ''' 564 | for i, km_item in enumerate(km.keymap_items): 565 | if km.keymap_items.keys()[i] == kmi_name: 566 | if km.keymap_items[i].idname == kmi_value: 567 | return km_item 568 | return None 569 | 570 | classes = ( 571 | RA_Prefs, 572 | R_Array, 573 | RA_Modal, 574 | ) 575 | 576 | 577 | def register(): 578 | print ("----------------------------------") 579 | print ("S.Ops Init") 580 | print ("----------------------------------") 581 | 582 | #+ add hotkey 583 | add_hotkey() 584 | 585 | from bpy.utils import register_class 586 | for cls in classes: 587 | register_class(cls) 588 | # Init Props 589 | 590 | bpy.types.Object.RA_Parent = bpy.props.PointerProperty( 591 | name="RA Parent", 592 | description="RA Parent Object Reference", 593 | type=bpy.types.Object 594 | ) 595 | 596 | bpy.types.Object.RA_ObjNum = bpy.props.IntProperty( 597 | name="RA ObjNum", 598 | description="RA Object Number", 599 | default = bpy.context.preferences.addons[__name__].preferences.objnum, 600 | min = 1, 601 | update = RA_Update_ObjNum 602 | ) 603 | 604 | bpy.types.Object.RA_Offset = bpy.props.FloatProperty( 605 | name="Offset", 606 | description="Radial Array Offset", 607 | default = bpy.context.preferences.addons[__name__].preferences.offset, 608 | update = RA_Update_Offset 609 | ) 610 | 611 | bpy.types.Object.RA_Status = bpy.props.BoolProperty( 612 | name="Status", 613 | description="Radial Array Status", 614 | default = False 615 | ) 616 | 617 | bpy.types.Object.RA_Sel_Status = bpy.props.BoolProperty( 618 | name="Selectable", 619 | description="False = Only Control Object is selectable", 620 | default = bpy.context.preferences.addons[__name__].preferences.selectable, 621 | update = RA_Update_Sel_Status 622 | ) 623 | 624 | bpy.types.Object.RA_Unq_mode = bpy.props.BoolProperty( 625 | name="Unique Mode", 626 | description="True = all objects have a unique data block(Disables Count in Modal)", 627 | default = False 628 | ) 629 | bpy.types.Object.RA_Name = bpy.props.StringProperty( 630 | name="Name", 631 | description="Radial Array Name", 632 | default = "Nameing Error" 633 | ) 634 | 635 | 636 | print ("----------------------------------") 637 | print ("S.Ops Register End") 638 | print ("----------------------------------") 639 | 640 | 641 | def unregister(): 642 | print ("----------------------------------") 643 | print ("S.Ops unRegister Start") 644 | print ("----------------------------------") 645 | #+ remove hotkey 646 | remove_hotkey() 647 | 648 | from bpy.utils import unregister_class 649 | for cls in classes: 650 | unregister_class(cls) 651 | 652 | 653 | 654 | 655 | 656 | print ("----------------------------------") 657 | print ("S.Ops unRegister End") 658 | print ("----------------------------------") 659 | 660 | if __name__ == "__main__": 661 | register() 662 | 663 | 664 | 665 | 666 | 667 | --------------------------------------------------------------------------------