diff --git a/__init__.py b/__init__.py index 5aaab05..70c5c21 100644 --- a/__init__.py +++ b/__init__.py @@ -64,6 +64,44 @@ class NDPreferences(AddonPreferences): default=True, ) + enable_axis_helper: BoolProperty( + name="Enable Axis Visualization", + default=True, + ) + + axis_base_thickness: FloatProperty( + name="Axis Base Thickness", + default=2, + min=0, + max=100, + step=1, + ) + + axis_active_thickness: FloatProperty( + name="Axis Active Thickness", + default=4, + min=0, + max=100, + step=1, + ) + + axis_inactive_opacity: FloatProperty( + name="Axis Inactive Opacity", + default=0.2, + min=0, + max=1, + step=0.1, + ) + + mouse_value_scalar: FloatProperty( + name="Mouse Value Scalar", + default=0.0025, + min=0.0001, + max=10, + precision=4, + step=0.01, + ) + mouse_value_scalar: FloatProperty( name="Mouse Value Scalar", default=0.0025, @@ -172,6 +210,16 @@ def draw_ui(self, box): column = box.column(align=True) row = column.row() row.prop(self, "enable_quick_favourites") + + column = box.column(align=True) + row = column.row() + row.prop(self, "enable_axis_helper") + + column = box.column(align=True) + row = column.row() + row.prop(self, "axis_base_thickness") + row.prop(self, "axis_active_thickness") + row.prop(self, "axis_inactive_opacity") def draw_keymap(self, box): diff --git a/lib/__init__.py b/lib/__init__.py index 4ca5305..b271229 100644 --- a/lib/__init__.py +++ b/lib/__init__.py @@ -3,6 +3,7 @@ from . import math from . import objects from . import overlay +from . import axis from . import viewport from . import assets from . import updates @@ -16,6 +17,7 @@ def reload(): importlib.reload(math) importlib.reload(objects) importlib.reload(overlay) + importlib.reload(axis) importlib.reload(viewport) importlib.reload(assets) importlib.reload(updates) @@ -24,3 +26,4 @@ def reload(): importlib.reload(overlay_keys) overlay.unregister_draw_handler() + axis.unregister_axis_handler() diff --git a/lib/axis.py b/lib/axis.py new file mode 100644 index 0000000..4fa46c2 --- /dev/null +++ b/lib/axis.py @@ -0,0 +1,91 @@ +import bpy +import gpu +from mathutils import Vector, Matrix +from gpu_extras.batch import batch_for_shader +from . preferences import get_preferences + + +def register_axis_handler(cls): + if cls.axis_obj is None: + return + + if not get_preferences().enable_axis_helper: + return + + handler = bpy.app.driver_namespace.get('nd.axis') + + if not handler: + handler = bpy.types.SpaceView3D.draw_handler_add(update_axis, (cls, ), 'WINDOW', 'POST_VIEW') + dns = bpy.app.driver_namespace + dns['nd.axis'] = handler + + redraw_regions() + + +def unregister_axis_handler(): + handler = bpy.app.driver_namespace.get('nd.axis') + + if handler: + bpy.types.SpaceView3D.draw_handler_remove(handler, 'WINDOW') + del bpy.app.driver_namespace['nd.axis'] + + redraw_regions() + + +def redraw_regions(): + for area in bpy.context.window.screen.areas: + if area.type == 'VIEW_3D': + for region in area.regions: + if region.type == 'WINDOW': + region.tag_redraw() + + +def init_axis(cls, axis_obj = None, axis = 0): + cls.axis_obj = axis_obj + cls.axis = axis + cls.axis_base_thickness = get_preferences().axis_base_thickness + cls.axis_active_thickness = get_preferences().axis_active_thickness + cls.axis_inactive_opacity = get_preferences().axis_inactive_opacity + + +def update_axis(cls): + if cls.axis_obj is None: + return + + axes = [ + (Vector((1, 0, 0)), (226/255, 54/255, 54/255)), + (Vector((0, 1, 0)), (130/255, 221/255, 85/255)), + (Vector((0, 0, 1)), (74/255, 144/255, 226/255)), + ] + + for counter, conf in enumerate(axes): + axis, color = conf + coords = [] + + mx = cls.axis_obj.matrix_world + origin = mx.decompose()[0] + + # Draw the axis through the origin point. + coords.append(origin + mx.to_3x3() @ axis * -1000) + coords.append(origin + mx.to_3x3() @ axis * 1000) + + shader = gpu.shader.from_builtin('3D_UNIFORM_COLOR') + shader.bind() + shader.uniform_float("color", (*color, 1 if counter == cls.axis else cls.axis_inactive_opacity)) + + gpu.state.depth_test_set('NONE') + gpu.state.blend_set('ALPHA') + gpu.state.line_width_set( + cls.axis_active_thickness if counter == cls.axis else cls.axis_base_thickness + ) + + batch = batch_for_shader( + shader, + 'LINES', + {"pos": coords}, + indices=[(i, i + 1) for i in range(0, len(coords), 2)] + ) + + batch.draw(shader) + + redraw_regions() diff --git a/power_mods/array_cubed.py b/power_mods/array_cubed.py index ea30bb7..827e18a 100644 --- a/power_mods/array_cubed.py +++ b/power_mods/array_cubed.py @@ -3,6 +3,7 @@ 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 from .. lib.preferences import get_preferences +from .. lib.axis import init_axis, register_axis_handler, unregister_axis_handler mod_array_x = "Array³ X — ND" @@ -49,21 +50,21 @@ def modal(self, context, event): elif self.key_step_up: if self.key_no_modifiers: - self.current_axis = (self.current_axis + 1) % 3 + self.axis = (self.axis + 1) % 3 elif self.key_alt: - self.axes[self.current_axis][1] += 1 + self.axes[self.axis][1] += 1 elif self.key_ctrl: - self.axes[self.current_axis][2] += offset_factor + self.axes[self.axis][2] += offset_factor self.dirty = True elif self.key_step_down: if self.key_no_modifiers: - self.current_axis = (self.current_axis - 1) % 3 + self.axis = (self.axis - 1) % 3 elif self.key_alt: - self.axes[self.current_axis][1] = max(1, self.axes[self.current_axis][1] - 1) + self.axes[self.axis][1] = max(1, self.axes[self.axis][1] - 1) elif self.key_ctrl: - self.axes[self.current_axis][2] -= offset_factor + self.axes[self.axis][2] -= offset_factor self.dirty = True @@ -77,7 +78,7 @@ def modal(self, context, event): if get_preferences().enable_mouse_values: if self.key_ctrl: - self.axes[self.current_axis][2] += self.mouse_value + self.axes[self.axis][2] += self.mouse_value self.dirty = True @@ -93,11 +94,9 @@ def invoke(self, context, event): self.dirty = False self.base_offset_factor = 0.01 - self.current_axis = 0 + self.axis = 0 self.axes = [None, None, None] - context.active_object.show_axis = True - mods = context.active_object.modifiers mod_names = list(map(lambda x: x.name, mods)) previous_op = all(m in mod_names for m in mod_summon_list) @@ -114,6 +113,9 @@ def invoke(self, context, event): init_overlay(self, event) register_draw_handler(self, draw_text_callback) + init_axis(self, context.active_object, self.axis) + register_axis_handler(self) + context.window_manager.modal_handler_add(self) return {'RUNNING_MODAL'} @@ -168,14 +170,11 @@ def operate(self, context): def finish(self, context): - context.active_object.show_axis = False - unregister_draw_handler() + unregister_axis_handler() def revert(self, context): - context.active_object.show_axis = False - if not self.summoned: for mod in mod_summon_list: bpy.ops.object.modifier_remove(modifier=mod) @@ -187,6 +186,7 @@ def revert(self, context): array.relative_offset_displace = [offset if i == axis else 0 for i in range(3)] unregister_draw_handler() + unregister_axis_handler() def draw_text_callback(self): @@ -194,21 +194,21 @@ def draw_text_callback(self): draw_property( self, - "Axis: {0}".format(['X', 'Y', 'Z'][self.current_axis]), + "Axis: {0}".format(['X', 'Y', 'Z'][self.axis]), "(X, Y, Z)", active=self.key_no_modifiers, alt_mode=False) draw_property( self, - "Count: {0}".format(self.axes[self.current_axis][1]), + "Count: {0}".format(self.axes[self.axis][1]), "Alt (±1)", active=self.key_alt, alt_mode=self.key_shift_alt) draw_property( self, - "Offset: {0:.3f}".format(self.axes[self.current_axis][2]), + "Offset: {0:.3f}".format(self.axes[self.axis][2]), "Ctrl (±{0:.1f}) | Shift + Ctrl (±{1:.1f})".format(self.base_offset_factor * 1000, (self.base_offset_factor / 10) * 1000), active=self.key_ctrl, alt_mode=self.key_shift_ctrl, diff --git a/power_mods/mirror.py b/power_mods/mirror.py index ae7d579..d05378b 100644 --- a/power_mods/mirror.py +++ b/power_mods/mirror.py @@ -3,6 +3,7 @@ from math import radians 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 +from .. lib.axis import init_axis, register_axis_handler, unregister_axis_handler class ND_OT_mirror(bpy.types.Operator): @@ -83,6 +84,9 @@ def invoke(self, context, event): init_overlay(self, event) register_draw_handler(self, draw_text_callback) + init_axis(self, self.reference_obj if self.mirror_obj is None else self.mirror_obj, self.axis) + register_axis_handler(self) + context.window_manager.modal_handler_add(self) return {'RUNNING_MODAL'} @@ -125,13 +129,15 @@ def finish(self, context): self.select_reference_obj(context) unregister_draw_handler() + unregister_axis_handler() def revert(self, context): self.select_reference_obj(context) bpy.ops.object.modifier_remove(modifier=self.mirror.name) - + unregister_draw_handler() + unregister_axis_handler() def draw_text_callback(self): diff --git a/power_mods/profile_extrude.py b/power_mods/profile_extrude.py index afaface..6ad33fa 100644 --- a/power_mods/profile_extrude.py +++ b/power_mods/profile_extrude.py @@ -3,6 +3,7 @@ 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 from .. lib.preferences import get_preferences +from .. lib.axis import init_axis, register_axis_handler, unregister_axis_handler mod_screw = "Extrusion — ND PE" @@ -109,6 +110,9 @@ def invoke(self, context, event): init_overlay(self, event) register_draw_handler(self, draw_text_callback) + init_axis(self, context.active_object, self.axis) + register_axis_handler(self) + context.window_manager.modal_handler_add(self) return {'RUNNING_MODAL'} @@ -190,6 +194,7 @@ def operate(self, context): def finish(self, context): unregister_draw_handler() + unregister_axis_handler() def revert(self, context): @@ -206,6 +211,7 @@ def revert(self, context): self.offset.strength = self.calculate_offset_strength() unregister_draw_handler() + unregister_axis_handler() def draw_text_callback(self): diff --git a/power_mods/screw.py b/power_mods/screw.py index 3bafd83..2feafe7 100644 --- a/power_mods/screw.py +++ b/power_mods/screw.py @@ -4,6 +4,7 @@ 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 from .. lib.preferences import get_preferences +from .. lib.axis import init_axis, register_axis_handler, unregister_axis_handler mod_displace = "Offset — ND SCR" @@ -56,7 +57,7 @@ def modal(self, context, event): if self.key_shift: self.offset_axis = (self.offset_axis + 1) % 3 else: - self.screw_axis = (self.screw_axis + 1) % 3 + self.axis = (self.axis + 1) % 3 elif self.key_ctrl: self.angle = min(360, self.angle + angle_factor) else: @@ -71,7 +72,7 @@ def modal(self, context, event): if self.key_shift: self.offset_axis = (self.offset_axis + 1) % 3 else: - self.screw_axis = (self.screw_axis + 1) % 3 + self.axis = (self.axis + 1) % 3 elif self.key_ctrl: self.angle = max(0, self.angle - angle_factor) else: @@ -126,6 +127,9 @@ def invoke(self, context, event): init_overlay(self, event) register_draw_handler(self, draw_text_callback) + init_axis(self, context.active_object, self.axis) + register_axis_handler(self) + context.window_manager.modal_handler_add(self) return {'RUNNING_MODAL'} @@ -140,7 +144,7 @@ def poll(cls, context): def prepare_new_operator(self, context): self.summoned = False - self.screw_axis = 2 # X (0), Y (1), Z (2) + self.axis = 2 # X (0), Y (1), Z (2) self.offset_axis = 1 # X (0), Y (1), Z (2) self.segments = 3 self.angle = 360 @@ -159,7 +163,7 @@ def summon_old_operator(self, context, mods): self.offset_prev = self.offset = self.displace.strength self.offset_axis_prev = self.offset_axis = {'X': 0, 'Y': 1, 'Z': 2}[self.displace.direction] - self.screw_axis_prev = self.screw_axis = {'X': 0, 'Y': 1, 'Z': 2}[self.screw.axis] + self.axis_prev = self.axis = {'X': 0, 'Y': 1, 'Z': 2}[self.screw.axis] self.segments_prev = self.segments = self.screw.steps self.segments_prev = self.segments = self.screw.render_steps self.angle_prev = self.angle = degrees(self.screw.angle) @@ -192,7 +196,7 @@ def add_screw_modifier(self, context): def operate(self, context): self.displace.strength = self.offset self.displace.direction = ['X', 'Y', 'Z'][self.offset_axis] - self.screw.axis = ['X', 'Y', 'Z'][self.screw_axis] + self.screw.axis = ['X', 'Y', 'Z'][self.axis] self.screw.steps = self.segments self.screw.render_steps = self.segments self.screw.angle = radians(self.angle) @@ -202,6 +206,7 @@ def operate(self, context): def finish(self, context): unregister_draw_handler() + unregister_axis_handler() def revert(self, context): @@ -212,12 +217,13 @@ def revert(self, context): if self.summoned: self.displace.strength = self.offset_prev self.displace.direction = ['X', 'Y', 'Z'][self.offset_axis_prev] - self.screw.axis = ['X', 'Y', 'Z'][self.screw_axis_prev] + self.screw.axis = ['X', 'Y', 'Z'][self.axis_prev] self.screw.steps = self.segments_prev self.screw.render_steps = self.segments_prev self.screw.angle = radians(self.angle_prev) unregister_draw_handler() + unregister_axis_handler() def draw_text_callback(self): @@ -232,7 +238,7 @@ def draw_text_callback(self): draw_property( self, - "Screw Axis: {} / Offset Axis: {}".format(['X', 'Y', 'Z'][self.screw_axis], ['X', 'Y', 'Z'][self.offset_axis]), + "Screw Axis: {} / Offset Axis: {}".format(['X', 'Y', 'Z'][self.axis], ['X', 'Y', 'Z'][self.offset_axis]), "Alt (Screw X, Y, Z) | Shift + Alt (Offset X, Y, Z)", active=self.key_alt, alt_mode=self.key_shift_alt)