| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666 |
-
- import bpy,math,mathutils,blf,rna_keymap_ui
-
- from .RA_draw_ui import *
- from mathutils import Matrix
- from bpy.types import (
- PropertyGroup,
- Menu
- )
- from bpy.props import (
- IntProperty,
- FloatProperty,
- BoolProperty
- )
- #// join objects option in modal operator
- #// Reset array option in modal operator
- #// Modal operator Ui
- #// add Radial Array hotkey
- #// preferences add hotkey in addon preferences menu
- #// addon menu ui
- #// add modal selectable toggle
- #// add modal apply option
- #// add modal ui tooltips
- #// add make unique
- #// add create collection toggle
-
-
- bl_info = {
- "name" : "R.Array",
- "author" : "Syler",
- "version": (0, 0, 1, 2),
- "description": "Adds Radial Array Operator",
- "blender" : (2, 80, 0),
- "category" : "Object"
- }
- #+ handle the keymap
- addon_keymaps = []
-
- def add_hotkey():
- #* Ctrl Q call R_Array
- wm = bpy.context.window_manager
- km = wm.keyconfigs.addon.keymaps.new(name='Object Mode', space_type='EMPTY')
- kmi = km.keymap_items.new(R_Array.bl_idname, 'Q', 'PRESS', ctrl=True)
- addon_keymaps.append(km)
-
- def remove_hotkey():
- wm = bpy.context.window_manager
- for km in addon_keymaps:
- wm.keyconfigs.addon.keymaps.remove(km)
- # clear the list
- del addon_keymaps[:]
- #--------------------------------------------------------------------------------------#
- def RA_Update_Sel_Status(self, context):
- if self.RA_Sel_Status == True:
- for ob in self.RA_Parent.children:
- ob.hide_select = False
- if self.RA_Sel_Status == False:
- for ob in self.RA_Parent.children:
- ob.hide_select = True
-
- def RA_Update_ObjNum(self, context):
-
- if self.RA_Status == True:
-
- if len(self.RA_Parent.children) == self.RA_ObjNum:
- pass
-
- #+ Add Objects
- if len(self.RA_Parent.children) < self.RA_ObjNum:
- object_list = []
- object_to_copy = self.RA_Parent.children[0]
- # append already existing objects to object list
- for c in self.RA_Parent.children:
- object_list.append(c)
-
-
- for i in range (len(self.RA_Parent.children), self.RA_ObjNum):
- object_list.append(object_to_copy.copy())
-
-
-
- # Add Objects To Collection
- for index, ob in enumerate(object_list):
-
- # Reset Matrix
- ob.matrix_basis = mathutils.Matrix()
-
- # set object location to RA_Parent + RA_Offset
- ob.location[1] = self.RA_Parent.location[1] + self.RA_Parent.RA_Offset
- # create angle variable
- angle = math.radians(360/self.RA_Parent.RA_ObjNum)
-
- # rotate object
- R = mathutils.Matrix.Rotation(angle * (index), 4, 'Z')
- T = mathutils.Matrix.Translation([0, 0, 0])
- M = T @ R @ T.inverted()
- ob.location = M @ ob.location
- ob.rotation_euler.rotate(M)
-
-
- # Parent Object
- ob.parent = self.RA_Parent
- self.RA_Parent.matrix_parent_inverse = ob.matrix_world.inverted()
- ob.RA_Parent = self.RA_Parent
-
- # make objects selectable/unselectable
- if self.RA_Sel_Status == True:
- ob.hide_select = False
- if self.RA_Sel_Status == False:
- ob.hide_select = True
-
- # Change Object Name
- ob.name = "RA - " + self.RA_Name + " - " + str(index)
- # set RA Status
- ob.RA_Status = True
- # Link object
- try:
- self.RA_Parent.users_collection[0].objects.link(ob)
- #print ("For LINK")
- except:
- #print ("PASS Linking object to collection failed")
- pass
-
- #+ Remove Objects
- if len(self.RA_Parent.children) > self.RA_ObjNum:
-
- # deselect all objects
- for d in bpy.context.view_layer.objects:
- d.select_set(False)
- bpy.context.view_layer.objects.active = None
-
- # Make selectable and Select all objects that will be deleted
- for i in range (self.RA_ObjNum, len(self.RA_Parent.children)):
- self.RA_Parent.children[i].hide_select = False
- self.RA_Parent.children[i].select_set(True)
- # Delete Objects
- bpy.ops.object.delete()
- # select control Object
- bpy.context.view_layer.objects.active = self.RA_Parent
- self.RA_Parent.select_set(True)
- for index, ob in enumerate(self.RA_Parent.children):
- # Reset Matrix
- ob.matrix_basis = mathutils.Matrix()
-
- # set object location to RA_Parent + RA_Offset
- ob.location[1] = self.RA_Parent.location[1] + self.RA_Parent.RA_Offset
- # create angle variable
- angle = math.radians(360/self.RA_Parent.RA_ObjNum)
-
- # rotate object
- R = mathutils.Matrix.Rotation(angle * (index), 4, 'Z')
- T = mathutils.Matrix.Translation([0, 0, 0])
- M = T @ R @ T.inverted()
- ob.location = M @ ob.location
- ob.rotation_euler.rotate(M)
-
- def RA_Update_Offset(self, context):
-
- if self.RA_Status == True:
- for ob in self.RA_Parent.children:
- # define variables
- loc = mathutils.Vector((0.0, self.RA_Offset, 0.0))
- rot = ob.rotation_euler
- # rotate location
- loc.rotate(rot)
- # apply rotation
- ob.location = loc
- else:
- pass
- #--------------------------------------------------------------------------------------#
- class R_Array(bpy.types.Operator):
- bl_idname = 'sop.r_array'
- bl_label = 'Radial Array'
- bl_description = 'Radial Array S.Operator'
- bl_options = {'REGISTER', 'UNDO'}
-
-
-
-
- #?Useless !?
- @classmethod
- def poll(cls, context):
- return True
-
- def execute(self, context):
-
- #Create Bpy.context Variable
- C = bpy.context
- active_object = C.active_object
-
-
- # call modal if RA_Status = True
- try:
- if active_object.RA_Status == True:
- bpy.ops.sop.ra_modal('INVOKE_DEFAULT')
- return {'FINISHED'}
- except:
- pass
- # Check Selected Cancel if NOT Mesh
- if C.selected_objects == [] or C.active_object.type != 'MESH':
- self.report({'INFO'}, "No Mesh Selected")
- return {'CANCELLED'}
-
-
- # Create Variables
- L_Objects = [] # object list
- ob = active_object # active object reference
- ob_collections = ob.users_collection # active Object collections
- f_name = ob.name # Object Name
- point = ob.location.copy() # Middle point
- is_col_new = True
-
-
- # Create New Collection
- if bpy.context.preferences.addons[__name__].preferences.col_toggle == True:
- for q in bpy.data.collections:
- if q.name == "RA -" + f_name:
- collection = q
- is_col_new = False
- try:
- for col in ob_collections:
- col.objects.unlink(ob)
- collection.objects.link(ob)
- except:
- pass
-
-
- if is_col_new == True:
- # create and link new collection
- collection = bpy.data.collections.new(name="RA -" + f_name)
- bpy.context.scene.collection.children.link(collection)
- print ("NEW")
- # Move Object to collection
- for col in ob_collections:
- col.objects.unlink(ob)
- collection.objects.link(ob)
- else:
- collection = ob_collections[0]
-
- # Create/Location/Name/Status/set RA_Parent/Link Empty and other memery
- empty = bpy.data.objects.new( "empty", None )
- empty.location = point
- empty.name = ".RA - " + ob.name + " - Control Empty"
- empty.RA_Status = True
- empty.RA_Parent = empty
- empty.RA_Name = f_name
- empty.RA_Sel_Status = bpy.context.preferences.addons[__name__].preferences.selectable
- collection.objects.link(empty)
-
- # Move object
- ob.location[1] = ob.location[1] + ob.RA_Offset
-
- # Deselect Active Object and select Control Object
- ob.select_set(False)
- empty.select_set(True)
-
- # set empty as active object
- bpy.context.view_layer.objects.active = empty
-
- # create duplicate objects
- for o in range(0, empty.RA_ObjNum):
-
- if o == 0:
- L_Objects.append(ob)
- if o != 0:
- L_Objects.append(ob.copy())
- # Add Objects To Collection
- for index, ob in enumerate(L_Objects):
- # create angle variable
- angle = math.radians(360/empty.RA_ObjNum)
-
-
- # rotate object
- R = mathutils.Matrix.Rotation(angle * (index), 4, 'Z')
- T = mathutils.Matrix.Translation([0, 0, 0])
- M = T @ R @ T.inverted()
- ob.location = M @ ob.location
- ob.rotation_euler.rotate(M)
-
- # Parent Object
- ob.parent = empty
- empty.matrix_parent_inverse = ob.matrix_world.inverted()
- ob.RA_Parent = empty
-
- # make objects selectable/unselectable
- if empty.RA_Sel_Status == True:
- ob.hide_select = False
- if empty.RA_Sel_Status == False:
- ob.hide_select = True
-
- # Change Object Name
- ob.name = "RA - " + str(f_name) + " - " + str(index)
- # Set RA Status
- ob.RA_Status = True
-
- # Link object
- try:
- collection.objects.link(ob)
- #print ("For LINK")
- except:
- #print ("PASS Linking object to collection failed")
- pass
- bpy.ops.sop.ra_modal('INVOKE_DEFAULT')
-
- return {'FINISHED'}
- #--------------------------------------------------------------------------------------#
- class RA_Modal(bpy.types.Operator):
- # Change Radial Array
- bl_idname = "sop.ra_modal"
- bl_label = "Radial Array Modal"
- bl_options = {"REGISTER", "UNDO", "BLOCKING", "GRAB_CURSOR", "INTERNAL"} #- add later!?
-
- first_mouse_x: IntProperty()
- I_RA_Offset: FloatProperty()
- I_RA_ObjNum: IntProperty()
- unq_mode: BoolProperty()
-
-
- def modal(self, context, event):
-
- # context shortcut
- C = context
- OB = C.object
- context.area.tag_redraw() #?
- prefs = bpy.context.preferences.addons[__name__].preferences
- # -------------------------------------------------------------#
- #+ change offset
- if event.type == 'MOUSEMOVE' :
- delta = self.first_mouse_x - event.mouse_x
- if event.shift:
- C.object.RA_Offset = round((self.I_RA_Offset + delta * 0.01))
- else:
- C.object.RA_Offset = self.I_RA_Offset + delta * 0.01
- # -------------------------------------------------------------#
- #+ add/remove Objects
- if event.type == 'WHEELUPMOUSE' and OB.RA_Unq_mode == False:
- OB.RA_ObjNum = OB.RA_ObjNum + 1
-
- if event.type == 'WHEELDOWNMOUSE' and OB.RA_Unq_mode == False:
- OB.RA_ObjNum = OB.RA_ObjNum - 1
- # -------------------------------------------------------------#
- #+ call the tarnslation operator
- if event.type == 'G' and event.value == "PRESS":
-
- C.tool_settings.use_snap = True
- C.tool_settings.snap_elements = {'FACE'}
- C.tool_settings.use_snap_align_rotation = True
-
- bpy.ops.transform.translate('INVOKE_DEFAULT')
- bpy.types.SpaceView3D.draw_handler_remove(self.ra_draw_b, 'WINDOW')
- bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
- return {'FINISHED'}
- # -------------------------------------------------------------#
-
- #+ join objects
- if event.type == 'J' and event.value == "PRESS":
- objects = OB.RA_Parent.children
- location = OB.RA_Parent.location
- cursor_location = bpy.context.scene.cursor.location.copy()
-
- # deselect objects and select control object
- for o in C.selected_objects:
- o.select_set(False)
- C.object.RA_Parent.hide_select = False
- bpy.context.view_layer.objects.active = C.object.RA_Parent
- C.object.RA_Parent.select_set(True)
-
- # Delete control object
- bpy.ops.object.delete()
-
- for ob in objects:
- ob.hide_select = False
- ob.select_set(True)
- bpy.context.view_layer.objects.active = objects[0]
-
-
- bpy.context.scene.cursor.location = location
- bpy.ops.view3d.snap_selected_to_cursor(use_offset=True)
- bpy.ops.object.parent_clear(type='CLEAR_KEEP_TRANSFORM')
- bpy.ops.object.join()
- bpy.ops.object.origin_set(type='ORIGIN_CURSOR')
- bpy.context.scene.cursor.location = cursor_location
- bpy.types.SpaceView3D.draw_handler_remove(self.ra_draw_b, 'WINDOW')
- bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
- return {'FINISHED'}
- # -------------------------------------------------------------#
-
- #+ Reset
- if event.type == 'R' and event.value == "PRESS":
-
- objects = OB.RA_Parent.children
- name = OB.RA_Parent.RA_Name
- # deslect all objects
- for o in C.selected_objects:
- o.select_set(False)
- # select objects
- for ob in objects:
- if ob != objects[0]:
- ob.hide_select = False
- ob.select_set(True)
- # delete objects
- bpy.ops.object.delete()
-
- # select object and clear parent and other memery
- objects[0].location = objects[0].RA_Parent.location
- objects[0].RA_Parent.select_set(True)
- bpy.ops.object.delete()
- objects[0].hide_select = False
- bpy.context.view_layer.objects.active = objects[0]
- objects[0].select_set(True)
- objects[0].parent = None
- objects[0].name = name
- try:
- del objects[0]["RA_Parent"]
- del objects[0]["RA_Status"]
- except:
- pass
-
- bpy.types.SpaceView3D.draw_handler_remove(self.ra_draw_b, 'WINDOW')
- bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
- return {'FINISHED'}
- #+ Apply
- if event.type == 'A' and event.value == "PRESS":
-
- objects = OB.RA_Parent.children
- # deslect all objects
- for o in C.selected_objects:
- o.select_set(False)
- # select and delete control object
- objects[0].RA_Parent.select_set(True)
- bpy.ops.object.delete()
- # select objects
- for ob in objects:
-
- ob.hide_select = False
- ob.select_set(True)
- ob.RA_Status = False
- ob.parent = None
-
- bpy.context.view_layer.objects.active = objects[0]
- bpy.types.SpaceView3D.draw_handler_remove(self.ra_draw_b, 'WINDOW')
- bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
- return {'FINISHED'}
- #+ Make Unique Mode toggle
- if event.type == 'Q' and event.value == "PRESS":
- objects = OB.RA_Parent.children
- if OB.RA_Unq_mode == True:
- for ob in objects:
- ob.data = objects[0].data
- OB.RA_Unq_mode = False
- else:
- #* make unique data
- for ob in objects:
- ob.data = ob.data.copy()
- OB.RA_Unq_mode = True
- #+ Selectable toggle
- if event.type == 'S' and event.value == "PRESS":
- if OB.RA_Sel_Status == True:
- OB.RA_Sel_Status = False
- else:
- OB.RA_Sel_Status = True
- #+ Help Mode toggle
- if event.type == 'H' and event.value == "PRESS":
- if prefs.modal_help == True:
- prefs.modal_help = False
- else:
- prefs.modal_help = True
- # -------------------------------------------------------------#
- #+ Finish/Cancel Modal
- elif event.type == 'LEFTMOUSE':
- bpy.types.SpaceView3D.draw_handler_remove(self.ra_draw_b, 'WINDOW')
- bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
- return {'FINISHED'}
-
- elif event.type in {'RIGHTMOUSE', 'ESC'}:
- C.object.RA_Offset = self.I_RA_Offset
- C.object.RA_ObjNum = self.I_RA_ObjNum
- bpy.types.SpaceView3D.draw_handler_remove(self.ra_draw_b, 'WINDOW')
- bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
- return {'CANCELLED'}
-
- return {'RUNNING_MODAL'}
-
- def invoke(self, context, event):
- # context shortcut
- C = context
- if C.object.RA_Status == True:
- for o in C.selected_objects:
- o.select_set(False)
- bpy.context.view_layer.objects.active = C.object.RA_Parent
- C.object.RA_Parent.select_set(True)
-
-
-
- if C.object:
- # set initial Variable values
- self.first_mouse_x = event.mouse_x
- self.I_RA_Offset = C.object.RA_Offset
- self.I_RA_ObjNum = C.object.RA_ObjNum
- self.unq_mode = C.object.RA_Unq_mode
- self.prefs = bpy.context.preferences.addons[__name__].preferences
- ###-------------------------------------------###
- args = (self, context, self.prefs)
-
-
- self.ra_draw_b = bpy.types.SpaceView3D.draw_handler_add(RA_draw_B, args, 'WINDOW', 'POST_PIXEL')
- self._handle = bpy.types.SpaceView3D.draw_handler_add(RA_modal_Draw, args, 'WINDOW', 'POST_PIXEL')
-
- self.mouse_path = []
-
- context.window_manager.modal_handler_add(self)
- return {'RUNNING_MODAL'}
- else:
- self.report({'WARNING'}, "No active object, could not finish")
- return {'CANCELLED'}
- #--------------------------------------------------------------------------------------#
- class RA_Prefs(bpy.types.AddonPreferences):
- bl_idname = __name__
- # here you define the addons customizable props
- offset: bpy.props.FloatProperty(default=5)
- objnum: bpy.props.IntProperty(default=6)
- selectable: bpy.props.BoolProperty(default= True, description="False = Only Control Object is selectable")
- modal_help: bpy.props.BoolProperty(default= False, description="True = Display Help text in modal")
- col_toggle: bpy.props.BoolProperty(default= False, description="True = Create New Collection")
- # here you specify how they are drawn
- def draw(self, context):
- layout = self.layout
- box = layout.box()
- split = box.split()
- col = split.column()
- # Layout ---------------------------------------------------------------- #
- col.label(text="Default Values:")
- col.prop(self, "offset",text="Default Offset")
- col.prop(self, "objnum",text="Default Count")
- col.prop(self, "selectable",text="Selectable")
- col.prop(self, "modal_help",text="Modal Help")
- col.label(text ="Options:")
- col.prop(self, "col_toggle",text="Create New Collection")
- col.label(text="Keymap:")
-
-
- wm = bpy.context.window_manager
- kc = wm.keyconfigs.user
- km = kc.keymaps['Object Mode']
- #kmi = km.keymap_items[0]
- kmi = get_hotkey_entry_item(km, 'sop.r_array', 'sop.r_array')
-
- if addon_keymaps:
- km = addon_keymaps[0].active()
- col.context_pointer_set("keymap", km)
- rna_keymap_ui.draw_kmi([], kc, km, kmi, col, 0)
-
-
-
- def get_addon_preferences():
- ''' quick wrapper for referencing addon preferences '''
- addon_preferences = bpy.context.user_preferences.addons[__name__].preferences
- return addon_preferences
- def get_hotkey_entry_item(km, kmi_name, kmi_value):
- '''
- returns hotkey of specific type, with specific properties.name (keymap is not a dict, so referencing by keys is not enough
- if there are multiple hotkeys!)
- '''
- for i, km_item in enumerate(km.keymap_items):
- if km.keymap_items.keys()[i] == kmi_name:
- if km.keymap_items[i].idname == kmi_value:
- return km_item
- return None
-
- classes = (
- RA_Prefs,
- R_Array,
- RA_Modal,
- )
-
-
- def register():
- print ("----------------------------------")
- print ("S.Ops Init")
- print ("----------------------------------")
-
- #+ add hotkey
- add_hotkey()
-
- from bpy.utils import register_class
- for cls in classes:
- register_class(cls)
- # Init Props
-
- bpy.types.Object.RA_Parent = bpy.props.PointerProperty(
- name="RA Parent",
- description="RA Parent Object Reference",
- type=bpy.types.Object
- )
-
- bpy.types.Object.RA_ObjNum = bpy.props.IntProperty(
- name="RA ObjNum",
- description="RA Object Number",
- default = bpy.context.preferences.addons[__name__].preferences.objnum,
- min = 1,
- update = RA_Update_ObjNum
- )
-
- bpy.types.Object.RA_Offset = bpy.props.FloatProperty(
- name="Offset",
- description="Radial Array Offset",
- default = bpy.context.preferences.addons[__name__].preferences.offset,
- update = RA_Update_Offset
- )
-
- bpy.types.Object.RA_Status = bpy.props.BoolProperty(
- name="Status",
- description="Radial Array Status",
- default = False
- )
-
- bpy.types.Object.RA_Sel_Status = bpy.props.BoolProperty(
- name="Selectable",
- description="False = Only Control Object is selectable",
- default = bpy.context.preferences.addons[__name__].preferences.selectable,
- update = RA_Update_Sel_Status
- )
-
- bpy.types.Object.RA_Unq_mode = bpy.props.BoolProperty(
- name="Unique Mode",
- description="True = all objects have a unique data block(Disables Count in Modal)",
- default = False
- )
- bpy.types.Object.RA_Name = bpy.props.StringProperty(
- name="Name",
- description="Radial Array Name",
- default = "Nameing Error"
- )
-
-
- print ("----------------------------------")
- print ("S.Ops Register End")
- print ("----------------------------------")
-
-
- def unregister():
- print ("----------------------------------")
- print ("S.Ops unRegister Start")
- print ("----------------------------------")
- #+ remove hotkey
- remove_hotkey()
-
- from bpy.utils import unregister_class
- for cls in classes:
- unregister_class(cls)
-
-
-
-
-
- print ("----------------------------------")
- print ("S.Ops unRegister End")
- print ("----------------------------------")
-
- if __name__ == "__main__":
- register()
-
-
-
-
|