From 8d6d20eb21d6a73370a0770342bedbc1539d5177 Mon Sep 17 00:00:00 2001 From: Tristan Strathearn Date: Mon, 4 Apr 2022 22:23:34 +1000 Subject: [PATCH] feat: add circular_array operator --- interface/main_menu.py | 1 + interface/main_ui_panel.py | 4 + power_mods/__init__.py | 4 + power_mods/circular_array.py | 173 +++++++++++++++++++++++++++++++++++ 4 files changed, 182 insertions(+) create mode 100644 power_mods/circular_array.py diff --git a/interface/main_menu.py b/interface/main_menu.py index fcb3d27..23f34ac 100644 --- a/interface/main_menu.py +++ b/interface/main_menu.py @@ -24,6 +24,7 @@ def draw(self, context): layout.operator("nd.screw", icon='MOD_SCREW') layout.operator("nd.mirror", icon='MOD_MIRROR') layout.operator("nd.profile_extrude", icon='EMPTY_SINGLE_ARROW') + layout.operator("nd.circular_array", icon='DRIVER_ROTATIONAL_DIFFERENCE') layout.separator() layout.operator("nd.recon_poly", icon='SURFACE_NCURVE') layout.operator("nd.screw_head", icon='CANCEL') diff --git a/interface/main_ui_panel.py b/interface/main_ui_panel.py index 1e6d2fd..e0fa7fa 100644 --- a/interface/main_ui_panel.py +++ b/interface/main_ui_panel.py @@ -85,6 +85,10 @@ def draw(self, context): row = column.row(align=True) row.scale_y = 1.2 row.operator("nd.profile_extrude", icon='EMPTY_SINGLE_ARROW') + + row = column.row(align=True) + row.scale_y = 1.2 + row.operator("nd.circular_array", icon='DRIVER_ROTATIONAL_DIFFERENCE') box = layout.box() box.label(text="Generators", icon='GHOST_ENABLED') diff --git a/power_mods/__init__.py b/power_mods/__init__.py index 7575a08..f77c44d 100644 --- a/power_mods/__init__.py +++ b/power_mods/__init__.py @@ -5,6 +5,7 @@ from . import vertex_bevel from . import mirror from . import profile_extrude +from . import circular_array def reload(): @@ -14,6 +15,7 @@ def reload(): importlib.reload(vertex_bevel) importlib.reload(mirror) importlib.reload(profile_extrude) + importlib.reload(circular_array) def register(): @@ -23,6 +25,7 @@ def register(): vertex_bevel.register() mirror.register() profile_extrude.register() + circular_array.register() def unregister(): @@ -32,5 +35,6 @@ def unregister(): vertex_bevel.unregister() mirror.unregister() profile_extrude.unregister() + circular_array.unregister() \ No newline at end of file diff --git a/power_mods/circular_array.py b/power_mods/circular_array.py new file mode 100644 index 0000000..213107d --- /dev/null +++ b/power_mods/circular_array.py @@ -0,0 +1,173 @@ +import bpy +import bmesh +from math import radians +from mathutils import Euler +from .. lib.overlay import update_overlay, init_overlay, toggle_pin_overlay, toggle_operator_passthrough, register_draw_handler, unregister_draw_handler, draw_header, draw_property +from .. lib.events import capture_modifier_keys + + +class ND_OT_circular_array(bpy.types.Operator): + bl_idname = "nd.circular_array" + bl_label = "Circular Array" + bl_description = "Array an object around another in a circular fashion" + bl_options = {'UNDO'} + + + def modal(self, context, event): + capture_modifier_keys(self, event) + + angle_factor = 1 if self.key_shift else 15 + + if self.key_toggle_operator_passthrough: + toggle_operator_passthrough(self) + + elif self.key_toggle_pin_overlay: + toggle_pin_overlay(self, event) + + elif self.operator_passthrough: + update_overlay(self, context, event) + + return {'PASS_THROUGH'} + + elif self.key_cancel: + self.revert(context) + + return {'CANCELLED'} + + elif self.key_step_up: + if self.key_alt: + self.count = self.count + 1 + elif self.key_ctrl: + self.angle = min(360, self.angle + angle_factor) + else: + self.axis = (self.axis + 1) % 3 + + elif self.key_step_down: + if self.key_alt: + self.count = max(2, self.count - 1) + elif self.key_ctrl: + self.angle = max(0, self.angle - angle_factor) + else: + self.axis = (self.axis - 1) % 3 + + elif self.key_confirm: + self.finish(context) + + return {'FINISHED'} + + elif self.key_movement_passthrough: + return {'PASS_THROUGH'} + + self.operate(context) + update_overlay(self, context, event) + + return {'RUNNING_MODAL'} + + + def invoke(self, context, event): + self.axis = 2 + self.count = 2 + self.angle = 360 + + a, b = context.selected_objects + self.reference_obj = a if a.name != context.object.name else b + self.rotator_obj = context.active_object + + self.rotation_snapshot = self.rotator_obj.rotation_euler.copy() + self.add_array_modifier() + + capture_modifier_keys(self) + + init_overlay(self, event) + register_draw_handler(self, draw_text_callback) + + context.window_manager.modal_handler_add(self) + + return {'RUNNING_MODAL'} + + + @classmethod + def poll(cls, context): + if context.mode == 'OBJECT': + return len(context.selected_objects) == 2 + + + def add_array_modifier(self): + array = self.reference_obj.modifiers.new('Circular Array — ND', 'ARRAY') + array.count = self.count + array.use_relative_offset = False + array.use_object_offset = True + array.offset_object = self.rotator_obj + + self.array = array + + + def operate(self, context): + count = self.count if self.angle == 360 else self.count - 1 + final_rotation = radians(self.angle / count) + rotation_axis = ['X', 'Y', 'Z'][self.axis] + + self.rotator_obj.rotation_euler = self.rotation_snapshot + self.rotator_obj.rotation_euler.rotate_axis(rotation_axis, final_rotation) + + self.array.count = self.count + + + def select_reference_obj(self, context): + bpy.ops.object.select_all(action='DESELECT') + self.reference_obj.select_set(True) + bpy.context.view_layer.objects.active = self.reference_obj + + + def finish(self, context): + self.select_reference_obj(context) + + unregister_draw_handler() + + + def revert(self, context): + self.rotator_obj.rotation_euler = self.rotation_snapshot + self.select_reference_obj(context) + bpy.ops.object.modifier_remove(modifier=self.array.name) + + unregister_draw_handler() + + +def draw_text_callback(self): + draw_header(self) + + draw_property( + self, + "Axis: {}".format(['X', 'Y', 'Z'][self.axis]), + "X, Y, Z", + active=self.key_no_modifiers, + alt_mode=False) + + draw_property( + self, + "Count: {}".format(self.count), + "Alt (±1)", + active=self.key_alt, + alt_mode=False) + + draw_property( + self, + "{}".format('Full (360°)' if self.angle == 360 else "Angle: {}°".format(self.angle)), + "Ctrl (±15) | Shift (±1)", + active=self.key_ctrl, + alt_mode=self.key_shift_ctrl) + + +def menu_func(self, context): + self.layout.operator(ND_OT_circular_array.bl_idname, text=ND_OT_circular_array.bl_label) + + +def register(): + bpy.utils.register_class(ND_OT_circular_array) + bpy.types.VIEW3D_MT_object.append(menu_func) + + +def unregister(): + bpy.utils.unregister_class(ND_OT_circular_array) + bpy.types.VIEW3D_MT_object.remove(menu_func) + unregister_draw_handler()