diff --git a/simple_deform_helper/__init__.py b/simple_deform_helper/__init__.py index 8b94dbebf..ce0ca4cf9 100644 --- a/simple_deform_helper/__init__.py +++ b/simple_deform_helper/__init__.py @@ -1,5 +1,13 @@ # SPDX-License-Identifier: GPL-2.0-or-later -from . import gizmo, operators, preferences, data, update, translate, panel +from . import ( + panel, # + gizmo, + utils, + update, + translate, + operators, + preferences, +) bl_info = { "name": "SimpleDeformHelper", @@ -12,9 +20,59 @@ bl_info = { "category": "3D View" } +""" +# ------------------------- +__init__.py: + Register All Module + +gizmo/__init__.py: + Register All Gizmo + + /angle_and_factor.py: + Ctrl Modifier Angle + + /bend_axis.py: + Bend Method Switch Direction Gizmo + + /set_deform_axis.py: + Three Switch Deform Axis Operator Gizmo + + /up_down_limits_point.py: + Main control part + use utils.py PublicProperty._get_limits_point_and_bound_box_co + Obtain and calculate boundary box and limit point data + + +draw.py: + Draw 3D Bound And Line + +gizmo.json: + Draw Custom Shape Vertex Data + +operator.py: + Set Deform Axis Operator + +panel.py: + Draw Gizmo Tool Property in Options and Tool Settings Right + +preferences.py: + Addon Preferences + +translate.py: + temporary only Cn translate + +update.py: + In Change Depsgraph When Update Addon Data And Del Redundant Empty + +utils.py: + Main documents used + Most computing operations are placed in classes GizmoUtils +# ------------------------- +""" module_tuple = ( panel, gizmo, + utils, update, translate, operators, @@ -23,8 +81,6 @@ module_tuple = ( def register(): - data.Data.load_gizmo_data() - for item in module_tuple: item.register() diff --git a/simple_deform_helper/data.py b/simple_deform_helper/data.py deleted file mode 100644 index 4da383ab7..000000000 --- a/simple_deform_helper/data.py +++ /dev/null @@ -1,58 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-or-later -from os.path import dirname, basename, realpath - -G_MODIFIERS_PROPERTY = [ # copy modifier data - 'angle', - 'deform_axis', - 'deform_method', - 'factor', - 'invert_vertex_group', - 'limits', - 'lock_x', - 'lock_y', - 'lock_z', - 'origin', - 'show_expanded', - 'show_in_editmode', - 'vertex_group', -] - -G_INDICES = ( - (0, 1), (0, 2), (1, 3), (2, 3), - (4, 5), (4, 6), (5, 7), (6, 7), - (0, 4), (1, 5), (2, 6), (3, 7)) - -G_NAME = 'ViewSimpleDeformGizmo_' # Temporary use files -G_CON_LIMIT_NAME = G_NAME + 'constraints_limit_rotation' # 约束名称 -G_ADDON_NAME = basename(dirname(realpath(__file__))) # "simple_deform_helper" - - -class Data: - G_GizmoCustomShapeDict = {} - G_SimpleDeformGizmoHandlerDit = {} - - @classmethod - def load_gizmo_data(cls) -> None: - import json - import os - json_path = os.path.join(os.path.dirname(__file__), "gizmo.json") - with open(json_path, "r") as file: - cls.G_GizmoCustomShapeDict = json.load(file) - - @staticmethod - def from_bmesh_get_triangle_face_co(mesh: 'bpy.types.Mesh') -> list: - """ - :param mesh: 输入一个网格数据 - :type mesh: bpy.data.meshes - :return list: 反回顶点列表[[co1,co2,co3],[co1,co2,co3]...] - """ - import bmesh - - bm = bmesh.new() - bm.from_mesh(mesh) - bm.faces.ensure_lookup_table() - bm.verts.ensure_lookup_table() - bmesh.ops.triangulate(bm, faces=bm.faces) - co_list = [list(float(format(j, ".4f")) for j in vert.co) for face in bm.faces for vert in face.verts] - bm.free() - return co_list diff --git a/simple_deform_helper/draw.py b/simple_deform_helper/draw.py index 9fb5a674b..d8b9c5b88 100644 --- a/simple_deform_helper/draw.py +++ b/simple_deform_helper/draw.py @@ -5,62 +5,44 @@ import gpu from gpu_extras.batch import batch_for_shader from mathutils import Vector -from .data import G_INDICES, G_MODIFIERS_PROPERTY, G_NAME, Data -from .utils import PublicClass, Utils +from .update import change_active_object, simple_update +from .utils import GizmoUtils -class Handler(Data): +class Handler: @classmethod def add_handler(cls): - """向3d视图添加绘制handler - 并将其存储下来 - """ - if 'handler' not in cls.G_SimpleDeformGizmoHandlerDit: - cls.G_SimpleDeformGizmoHandlerDit['handler'] = bpy.types.SpaceView3D.draw_handler_add( - Draw3D.draw_bound_box, (), 'WINDOW', 'POST_VIEW') + if 'handler' not in cls.G_HandleData: + cls.G_HandleData['handler'] = bpy.types.SpaceView3D.draw_handler_add( + Draw3D().draw, (), 'WINDOW', 'POST_VIEW') @classmethod def del_handler_text(cls): - - if 'handler_text' in cls.G_SimpleDeformGizmoHandlerDit: + if 'scale_text' in cls.G_HandleData: bpy.types.SpaceView3D.draw_handler_remove( - cls.G_SimpleDeformGizmoHandlerDit['handler_text'], 'WINDOW') - cls.G_SimpleDeformGizmoHandlerDit.pop('handler_text') + cls.G_HandleData['scale_text'], 'WINDOW') + cls.G_HandleData.pop('scale_text') @classmethod def del_handler(cls): data = bpy.data - if data.meshes.get(G_NAME): - data.meshes.remove(data.meshes.get(G_NAME)) + if data.meshes.get(cls.G_NAME): + data.meshes.remove(data.meshes.get(cls.G_NAME)) - if data.objects.get(G_NAME): - data.objects.remove(data.objects.get(G_NAME)) + if data.objects.get(cls.G_NAME): + data.objects.remove(data.objects.get(cls.G_NAME)) cls.del_handler_text() - if 'handler' in cls.G_SimpleDeformGizmoHandlerDit: + if 'handler' in cls.G_HandleData: bpy.types.SpaceView3D.draw_handler_remove( - cls.G_SimpleDeformGizmoHandlerDit['handler'], 'WINDOW') - cls.G_SimpleDeformGizmoHandlerDit.clear() + cls.G_HandleData['handler'], 'WINDOW') + cls.G_HandleData.clear() -class Draw3D(PublicClass, Data): - +class DrawPublic: @classmethod def draw_3d_shader(cls, pos, indices, color=None, *, shader_name='3D_UNIFORM_COLOR', draw_type='LINES'): - """ - :param draw_type: - :param shader_name: - :param color: - :param indices: - :param pos: - :type pos:list ((0,0,0),(1,1,1)) - 2D_FLAT_COLOR - 2D_IMAGE - 2D_SMOOTH_COLOR - 2D_UNIFORM_COLOR - 3D_FLAT_COLOR - 3D_SMOOTH_COLOR - 3D_UNIFORM_COLOR - 3D_POLYLINE_FLAT_COLOR - 3D_POLYLINE_SMOOTH_COLOR - 3D_POLYLINE_UNIFORM_COLOR - ('POINTS', 'LINES', 'TRIS', 'LINE_STRIP', 'LINE_LOOP','TRI_STRIP', - 'TRI_FAN', 'LINES_ADJ', 'TRIS_ADJ', 'LINE_STRIP_ADJ') - `NONE`, `ALWAYS`, `LESS`, `LESS_EQUAL`, `EQUAL`, `GREATER` and `GREATER_EQUAL` - """ - shader = gpu.shader.from_builtin(shader_name) if draw_type == 'POINTS': batch = batch_for_shader(shader, draw_type, {'pos': pos}) @@ -74,6 +56,8 @@ class Draw3D(PublicClass, Data): batch.draw(shader) + +class DrawText: font_info = { 'font_id': 0, 'handler': None, @@ -92,8 +76,6 @@ class Draw3D(PublicClass, Data): f'The scaling value of the object {obj.name_full} is not 1,' f' which will cause the deformation of the simple deformation modifier.' f' Please apply the scaling before deformation') - if obj.scale == Vector((1, 1, 1)): - Handler.del_handler_text() @classmethod def draw_text(cls, x, y, text='Hello Word', font_id=0, size=10, *, color=(0.5, 0.5, 0.5, 1), dpi=72, column=0): @@ -102,99 +84,10 @@ class Draw3D(PublicClass, Data): blf.draw(font_id, text) blf.color(font_id, *color) - @classmethod - def draw_box(cls, data, mat): - pref = cls.pref_() - coords = Utils.matrix_calculation(mat, - cls.data_to_calculation(data)) - cls.draw_3d_shader(coords, G_INDICES, pref.bound_box_color) - @classmethod - def data_to_calculation(cls, data): - ((min_x, min_y, min_z), (max_x, max_y, max_z)) = data - return ( - (max_x, min_y, min_z), - (min_x, min_y, min_z), - (max_x, max_y, min_z), - (min_x, max_y, min_z), - (max_x, min_y, max_z), - (min_x, min_y, max_z), - (max_x, max_y, max_z), - (min_x, max_y, max_z)) +class Draw3D(GizmoUtils, DrawPublic, DrawText, Handler): - @classmethod - def draw_limits_bound_box(cls): - - pref = cls.pref_() - handler_dit = cls.G_SimpleDeformGizmoHandlerDit - if 'draw_limits_bound_box' in handler_dit: - # draw limits_bound_box - mat, data = handler_dit['draw_limits_bound_box'] - coords = Utils.matrix_calculation(mat, cls.data_to_calculation(data)) - cls.draw_3d_shader(coords, - G_INDICES, - pref.limits_bound_box_color) - - @classmethod - def draw_limits_line(cls): - handler_dit = cls.G_SimpleDeformGizmoHandlerDit - if 'draw_line' in handler_dit: - line_pos, limits_pos, = handler_dit['draw_line'] - # draw limits line - cls.draw_3d_shader(limits_pos, ((1, 0),), (1, 1, 0, 0.5)) - # draw line - cls.draw_3d_shader(line_pos, ((1, 0),), (1, 1, 0, 0.3)) - # draw pos - cls.draw_3d_shader([line_pos[1]], (), (0, 1, 0, 0.5), - shader_name='3D_UNIFORM_COLOR', draw_type='POINTS') - - @classmethod - def draw_deform_mesh(cls, ob, context): - pref = cls.pref_() - handler_dit = cls.G_SimpleDeformGizmoHandlerDit - active = context.object.modifiers.active - # draw deform mesh - if 'draw' in handler_dit: - pos, indices, mat, mod_data, limits = handler_dit['draw'] - if ([getattr(active, i) for i in G_MODIFIERS_PROPERTY] == mod_data) and ( - ob.matrix_world == mat) and limits == active.limits[:]: - cls.draw_3d_shader( - pos, indices, pref.deform_wireframe_color) - - @classmethod - def draw_scale_text(cls, ob): - if (ob.scale != Vector((1, 1, 1))) and ('handler_text' not in cls.G_SimpleDeformGizmoHandlerDit): - cls.G_SimpleDeformGizmoHandlerDit['handler_text'] = bpy.types.SpaceView3D.draw_handler_add( - cls.draw_str, (), 'WINDOW', 'POST_PIXEL') - - @classmethod - def is_draw_box(cls, context): - obj = context.object # 活动物体 - matrix = obj.matrix_world # 活动物体矩阵 - modifier = context.object.modifiers.active # 活动修改器 - - pref = cls.pref_() - simple_poll = Utils.simple_deform_poll(context) - bend = modifier and (modifier.deform_method == 'BEND') - display_switch_axis = not pref.display_bend_axis_switch_gizmo - - cls.draw_scale_text(obj) - Utils.update_co_data(obj, modifier) - - co_data = Utils.generate_co_data() - - if simple_poll and ((not bend) or display_switch_axis): - # draw bound box - cls.draw_box(co_data, matrix) - cls.draw_deform_mesh(obj, context) - cls.draw_limits_line() - cls.draw_limits_bound_box() - elif simple_poll and (bend and not display_switch_axis): - cls.draw_box(co_data, matrix) - Utils.new_empty(obj, modifier) - - @classmethod - def draw_bound_box(cls): + def draw(self): gpu.state.blend_set('ALPHA') gpu.state.line_width_set(1) @@ -202,7 +95,68 @@ class Draw3D(PublicClass, Data): gpu.state.depth_test_set('ALWAYS') context = bpy.context - if Utils.simple_deform_poll(context): - cls.is_draw_box(context) - else: - Handler.del_handler() + if simple_update.timers_update_poll(): + is_switch_obj = change_active_object.is_change_active_object(False) + if self.simple_deform_public_poll(context) and not is_switch_obj: + self.draw_3d(context) + + def draw_3d(self, context): + self.draw_scale_text(self.obj) + if not self.modifier_origin_is_available: + self.draw_bound_box() + elif self.simple_deform_show_gizmo_poll(context): + # draw bound box + self.draw_bound_box() + self.draw_deform_mesh() + self.draw_limits_line() + self.draw_limits_bound_box() + elif self.simple_deform_show_bend_axis_witch_poll(context): + self.draw_bound_box() + + def draw_bound_box(self): + coords = self.matrix_calculation(self.obj_matrix_world, + self.tow_co_to_coordinate(self.modifier_bound_co)) + self.draw_3d_shader(coords, self.G_INDICES, self.pref.bound_box_color) + + def draw_limits_bound_box(self): + self.draw_3d_shader(self.modifier_limits_bound_box, + self.G_INDICES, + self.pref.limits_bound_box_color, + ) + + def draw_limits_line(self): + up_point, down_point, up_limits, down_limits = self.modifier_limits_point + # draw limits line + self.draw_3d_shader((up_limits, down_limits), ((1, 0),), (1, 1, 0, 0.5)) + # draw line + self.draw_3d_shader((up_point, down_point), ((1, 0),), (1, 1, 0, 0.3)) + + # draw pos + self.draw_3d_shader([down_point], (), (0, 1, 0, 0.5), + shader_name='3D_UNIFORM_COLOR', draw_type='POINTS') + self.draw_3d_shader([up_point], (), (1, 0, 0, 0.5), + shader_name='3D_UNIFORM_COLOR', draw_type='POINTS') + + def draw_deform_mesh(self): + ob = self.obj + deform_data = self.G_DeformDrawData + active = self.modifier + # draw deform mesh + if 'simple_deform_bound_data' in deform_data and self.pref.update_deform_wireframe: + modifiers = self.get_modifiers_parameter(self.modifier) + pos, indices, mat, mod_data, limits = deform_data['simple_deform_bound_data'] + is_limits = limits == active.limits[:] + is_mat = (ob.matrix_world == mat) + if modifiers == mod_data and is_mat and is_limits: + self.draw_3d_shader(pos, indices, self.pref.deform_wireframe_color) + + def draw_scale_text(self, ob): + scale_error = (ob.scale != Vector((1, 1, 1))) + if scale_error and ('scale_text' not in self.G_HandleData): + self.G_HandleData['scale_text'] = bpy.types.SpaceView3D.draw_handler_add( + self.draw_str, (), 'WINDOW', 'POST_PIXEL') + elif not scale_error: + self.del_handler_text() + + def draw_origin_error(self): + ... diff --git a/simple_deform_helper/gizmo.json b/simple_deform_helper/gizmo.json index 5c5387d83..7f0694103 100644 --- a/simple_deform_helper/gizmo.json +++ b/simple_deform_helper/gizmo.json @@ -1 +1 @@ -{"SimpleDeform_GizmoGroup_": [[-0.5, 0.1, -1.0], [-0.5, -0.8, -0.2], [-0.5, 0.2, -0.6], [0.5, 0.4, -0.4], [-0.5, 1.0, -0.1], [0.5, 1.0, -0.1], [0.3, -0.3, 0.5], [0.5, -0.4, 0.4], [0.5, 1.0, 1.0], [0.5, -1.0, -1.0], [-0.5, 0.1, -1.0], [0.5, 0.1, -1.0], [0.5, 1.0, -0.1], [-0.5, 1.0, 1.0], [0.5, 1.0, 1.0], [-0.5, 0.2, -0.6], [-0.5, -0.4, 0.4], [-0.5, 0.4, -0.4], [0.5, 0.2, -0.6], [-0.5, 0.4, -0.4], [0.5, 0.4, -0.4], [0.3, -0.9, -0.7], [-0.3, -0.9, -0.7], [-0.5, -1.0, -1.0], [0.5, 0.1, -1.0], [-0.5, 0.2, -0.6], [0.5, 0.2, -0.6], [0.3, -0.9, 1.0], [0.3, -0.9, -0.7], [0.3, -0.8, -0.2], [0.3, -0.9, -0.7], [0.3, -0.9, 1.0], [-0.3, -0.9, -0.7], [-0.3, -0.3, 0.5], [0.3, -0.3, 1.0], [0.3, -0.3, 0.5], [0.5, -0.4, 0.4], [0.5, -0.8, -0.2], [0.5, 0.4, -0.4], [0.3, -0.8, -0.2], [0.5, -0.4, 0.4], [0.3, -0.3, 0.5], [-0.5, -0.4, 0.4], [-0.3, -0.8, -0.2], [-0.3, -0.3, 0.5], [-0.5, -0.4, 0.4], [-0.5, 1.0, -0.1], [-0.5, 0.4, -0.4], [0.3, -0.9, 1.0], [-0.3, -0.3, 0.5], [-0.3, -0.8, -0.2], [-0.5, 0.1, -1.0], [-0.5, -1.0, -1.0], [-0.5, -0.8, -0.2], [0.5, 0.4, -0.4], [-0.5, 0.4, -0.4], [-0.5, 1.0, -0.1], [0.5, 1.0, 1.0], [-0.5, 1.0, 1.0], [0.3, -0.3, 0.5], [-0.5, 1.0, 1.0], [-0.5, -0.4, 0.4], [-0.3, -0.3, 0.5], [-0.5, 1.0, 1.0], [-0.3, -0.3, 0.5], [0.3, -0.3, 0.5], [0.5, -1.0, -1.0], [-0.5, -1.0, -1.0], [-0.5, 0.1, -1.0], [0.5, 1.0, -0.1], [-0.5, 1.0, -0.1], [-0.5, 1.0, 1.0], [-0.5, 0.2, -0.6], [-0.5, -0.8, -0.2], [-0.5, -0.4, 0.4], [0.5, 0.2, -0.6], [-0.5, 0.2, -0.6], [-0.5, 0.4, -0.4], [0.5, -1.0, -1.0], [0.5, -0.8, -0.2], [0.3, -0.9, -0.7], [0.5, -0.8, -0.2], [0.3, -0.8, -0.2], [0.3, -0.9, -0.7], [-0.3, -0.8, -0.2], [-0.5, -0.8, -0.2], [-0.3, -0.9, -0.7], [-0.5, -0.8, -0.2], [-0.5, -1.0, -1.0], [-0.3, -0.9, -0.7], [0.5, -1.0, -1.0], [0.3, -0.9, -0.7], [-0.5, -1.0, -1.0], [0.5, 0.1, -1.0], [-0.5, 0.1, -1.0], [-0.5, 0.2, -0.6], [0.3, -0.8, -0.2], [0.3, -0.3, 0.5], [0.3, -0.9, 1.0], [0.3, -0.3, 0.5], [0.3, -0.3, 1.0], [0.3, -0.9, 1.0], [0.5, -1.0, -1.0], [0.5, 0.1, -1.0], [0.5, 0.2, -0.6], [0.5, 0.4, -0.4], [0.5, 1.0, -0.1], [0.5, -0.4, 0.4], [0.5, 1.0, -0.1], [0.5, 1.0, 1.0], [0.5, -0.4, 0.4], [0.5, -1.0, -1.0], [0.5, 0.2, -0.6], [0.5, -0.8, -0.2], [0.5, 0.2, -0.6], [0.5, 0.4, -0.4], [0.5, -0.8, -0.2], [0.3, -0.8, -0.2], [0.5, -0.8, -0.2], [0.5, -0.4, 0.4], [-0.5, -0.4, 0.4], [-0.5, -0.8, -0.2], [-0.3, -0.8, -0.2], [-0.5, -0.4, 0.4], [-0.5, 1.0, 1.0], [-0.5, 1.0, -0.1], [-0.3, -0.8, -0.2], [-0.3, -0.9, -0.7], [0.3, -0.9, 1.0], [0.3, -0.9, 1.0], [0.3, -0.3, 1.0], [-0.3, -0.3, 0.5]], "None_GizmoGroup_": [[1.0, -0.0, 0.2], [1.0, -0.0, -0.2], [1.0, -0.0, -0.0], [0.5, -0.0, 0.2], [0.2, -0.0, 0.2], [0.2, -0.0, -0.2], [-0.6, -0.0, 0.2], [-1.0, -0.0, 0.2], [-0.9, -0.0, -0.2], [-0.2, -0.0, 0.3], [-0.1, -0.0, 0.2], [-0.1, -0.0, 0.2], [-0.4, -0.0, 0.1], [-0.3, -0.0, 0.2], [-0.3, -0.0, 0.2], [0.1, -0.0, -0.0], [-0.0, -0.0, -0.1], [0.0, -0.0, -0.1], [-0.2, -0.0, -0.3], [-0.3, 0.0, -0.2], [-0.3, 0.0, -0.2], [-0.4, -0.0, 0.1], [-0.4, -0.0, -0.0], [-0.4, -0.0, 0.1], [0.1, -0.0, -0.0], [-0.0, -0.0, 0.1], [0.0, -0.0, -0.0], [-0.1, 0.0, -0.2], [-0.2, -0.0, -0.2], [-0.2, -0.0, -0.3], [-0.4, -0.0, -0.1], [-0.4, -0.0, -0.0], [-0.4, -0.0, -0.0], [-0.1, -0.0, 0.2], [-0.0, -0.0, 0.1], [0.0, -0.0, 0.1], [-0.3, -0.0, 0.2], [-0.2, -0.0, 0.2], [-0.2, -0.0, 0.3], [0.0, -0.0, -0.1], [-0.1, 0.0, -0.2], [-0.1, 0.0, -0.2], [-0.4, -0.0, -0.1], [-0.3, 0.0, -0.2], [-0.4, -0.0, -0.1], [-0.2, -0.0, 0.3], [-0.2, -0.0, 0.2], [-0.1, -0.0, 0.2], [-0.4, -0.0, 0.1], [-0.4, -0.0, 0.1], [-0.3, -0.0, 0.2], [0.1, -0.0, -0.0], [0.0, -0.0, -0.0], [-0.0, -0.0, -0.1], [-0.2, -0.0, -0.3], [-0.2, -0.0, -0.2], [-0.3, 0.0, -0.2], [-0.4, -0.0, 0.1], [-0.4, -0.0, -0.0], [-0.4, -0.0, -0.0], [0.1, -0.0, -0.0], [0.0, -0.0, 0.1], [-0.0, -0.0, 0.1], [-0.1, 0.0, -0.2], [-0.1, 0.0, -0.2], [-0.2, -0.0, -0.2], [-0.4, -0.0, -0.1], [-0.4, -0.0, -0.1], [-0.4, -0.0, -0.0], [-0.1, -0.0, 0.2], [-0.1, -0.0, 0.2], [-0.0, -0.0, 0.1], [-0.3, -0.0, 0.2], [-0.3, -0.0, 0.2], [-0.2, -0.0, 0.2], [0.0, -0.0, -0.1], [-0.0, -0.0, -0.1], [-0.1, 0.0, -0.2], [-0.4, -0.0, -0.1], [-0.3, 0.0, -0.2], [-0.3, 0.0, -0.2]], "SimpleDeform_Bend_Direction_": [[-2.5, 0.4, 1.0], [-3.0, -0.0, 1.7], [-2.5, -0.4, 1.0], [2.5, 0.4, 1.0], [2.5, -0.4, 1.0], [3.0, -0.0, 1.7], [1.3, 0.1, 0.4], [1.9, -0.1, 0.7], [1.9, 0.1, 0.7], [-2.5, -0.4, 1.0], [-1.3, 0.1, 0.4], [-2.5, 0.4, 1.0], [1.9, -0.1, 0.7], [2.5, 0.4, 1.0], [1.9, 0.1, 0.7], [0.0, 0.1, 0.3], [-1.3, -0.1, 0.4], [0.0, -0.1, 0.3], [1.3, -0.1, 0.4], [0.0, 0.1, 0.3], [0.0, -0.1, 0.3], [1.3, 0.1, 0.4], [1.3, -0.1, 0.4], [1.9, -0.1, 0.7], [-2.5, -0.4, 1.0], [-1.3, -0.1, 0.4], [-1.3, 0.1, 0.4], [1.9, -0.1, 0.7], [2.5, -0.4, 1.0], [2.5, 0.4, 1.0], [0.0, 0.1, 0.3], [-1.3, 0.1, 0.4], [-1.3, -0.1, 0.4], [1.3, -0.1, 0.4], [1.3, 0.1, 0.4], [0.0, 0.1, 0.3]], "Sphere_GizmoGroup_": [[0.7, -0.7, 0.0], [-0.7, 0.7, 0.0], [-0.7, -0.7, 0.0], [0.7, -0.7, 0.0], [0.7, 0.7, 0.0], [-0.7, 0.7, 0.0]]} \ No newline at end of file +{"SimpleDeform_GizmoGroup_": [[-0.54, 0.12, -0.96], [-0.54, -0.82, -0.23], [-0.54, 0.19, -0.64], [0.54, 0.37, -0.37], [-0.54, 0.96, -0.12], [0.54, 0.96, -0.12], [0.32, -0.29, 0.5], [0.54, -0.4, 0.4], [0.55, 0.96, 0.96], [0.54, -0.96, -0.96], [-0.54, 0.12, -0.96], [0.54, 0.12, -0.96], [0.54, 0.96, -0.12], [-0.54, 0.96, 0.96], [0.55, 0.96, 0.96], [-0.54, 0.19, -0.64], [-0.54, -0.4, 0.4], [-0.54, 0.37, -0.37], [0.54, 0.19, -0.64], [-0.54, 0.37, -0.37], [0.54, 0.37, -0.37], [0.32, -0.94, -0.69], [-0.33, -0.94, -0.69], [-0.54, -0.96, -0.96], [0.54, 0.12, -0.96], [-0.54, 0.19, -0.64], [0.54, 0.19, -0.64], [0.32, -0.94, 0.95], [0.32, -0.94, -0.69], [0.32, -0.81, -0.23], [0.32, -0.94, -0.69], [0.32, -0.94, 0.95], [-0.33, -0.94, -0.69], [-0.33, -0.29, 0.5], [0.32, -0.29, 0.95], [0.32, -0.29, 0.5], [0.54, -0.4, 0.4], [0.54, -0.81, -0.23], [0.54, 0.37, -0.37], [0.32, -0.81, -0.23], [0.54, -0.4, 0.4], [0.32, -0.29, 0.5], [-0.54, -0.4, 0.4], [-0.33, -0.81, -0.23], [-0.33, -0.29, 0.5], [-0.54, -0.4, 0.4], [-0.54, 0.96, -0.12], [-0.54, 0.37, -0.37], [0.32, -0.94, 0.95], [-0.33, -0.29, 0.5], [-0.33, -0.81, -0.23], [-0.54, 0.12, -0.96], [-0.54, -0.96, -0.96], [-0.54, -0.82, -0.23], [0.54, 0.37, -0.37], [-0.54, 0.37, -0.37], [-0.54, 0.96, -0.12], [0.55, 0.96, 0.96], [-0.54, 0.96, 0.96], [0.32, -0.29, 0.5], [-0.54, 0.96, 0.96], [-0.54, -0.4, 0.4], [-0.33, -0.29, 0.5], [-0.54, 0.96, 0.96], [-0.33, -0.29, 0.5], [0.32, -0.29, 0.5], [0.54, -0.96, -0.96], [-0.54, -0.96, -0.96], [-0.54, 0.12, -0.96], [0.54, 0.96, -0.12], [-0.54, 0.96, -0.12], [-0.54, 0.96, 0.96], [-0.54, 0.19, -0.64], [-0.54, -0.82, -0.23], [-0.54, -0.4, 0.4], [0.54, 0.19, -0.64], [-0.54, 0.19, -0.64], [-0.54, 0.37, -0.37], [0.54, -0.96, -0.96], [0.54, -0.81, -0.23], [0.32, -0.94, -0.69], [0.54, -0.81, -0.23], [0.32, -0.81, -0.23], [0.32, -0.94, -0.69], [-0.33, -0.81, -0.23], [-0.54, -0.82, -0.23], [-0.33, -0.94, -0.69], [-0.54, -0.82, -0.23], [-0.54, -0.96, -0.96], [-0.33, -0.94, -0.69], [0.54, -0.96, -0.96], [0.32, -0.94, -0.69], [-0.54, -0.96, -0.96], [0.54, 0.12, -0.96], [-0.54, 0.12, -0.96], [-0.54, 0.19, -0.64], [0.32, -0.81, -0.23], [0.32, -0.29, 0.5], [0.32, -0.94, 0.95], [0.32, -0.29, 0.5], [0.32, -0.29, 0.95], [0.32, -0.94, 0.95], [0.54, -0.96, -0.96], [0.54, 0.12, -0.96], [0.54, 0.19, -0.64], [0.54, 0.37, -0.37], [0.54, 0.96, -0.12], [0.54, -0.4, 0.4], [0.54, 0.96, -0.12], [0.55, 0.96, 0.96], [0.54, -0.4, 0.4], [0.54, -0.96, -0.96], [0.54, 0.19, -0.64], [0.54, -0.81, -0.23], [0.54, 0.19, -0.64], [0.54, 0.37, -0.37], [0.54, -0.81, -0.23], [0.32, -0.81, -0.23], [0.54, -0.81, -0.23], [0.54, -0.4, 0.4], [-0.54, -0.4, 0.4], [-0.54, -0.82, -0.23], [-0.33, -0.81, -0.23], [-0.54, -0.4, 0.4], [-0.54, 0.96, 0.96], [-0.54, 0.96, -0.12], [-0.33, -0.81, -0.23], [-0.33, -0.94, -0.69], [0.32, -0.94, 0.95], [0.32, -0.94, 0.95], [0.32, -0.29, 0.95], [-0.33, -0.29, 0.5]], "None_GizmoGroup_": [[0.97, -0.01, 0.18], [0.98, -0.01, -0.24], [0.96, -0.01, -0.03], [0.53, -0.01, 0.24], [0.15, -0.01, 0.24], [0.22, -0.01, -0.24], [-0.59, -0.01, 0.24], [-0.97, -0.01, 0.24], [-0.9, -0.01, -0.24], [-0.19, -0.0, 0.25], [-0.09, -0.0, 0.17], [-0.06, -0.0, 0.22], [-0.4, -0.0, 0.13], [-0.28, -0.0, 0.17], [-0.31, -0.0, 0.22], [0.07, -0.0, -0.0], [-0.02, -0.0, -0.1], [0.03, -0.0, -0.13], [-0.19, -0.0, -0.25], [-0.28, 0.0, -0.17], [-0.31, 0.0, -0.22], [-0.4, -0.0, 0.13], [-0.38, -0.0, -0.0], [-0.35, -0.0, 0.1], [0.07, -0.0, -0.0], [-0.02, -0.0, 0.1], [0.01, -0.0, -0.0], [-0.06, 0.0, -0.22], [-0.19, -0.0, -0.19], [-0.19, -0.0, -0.25], [-0.4, -0.0, -0.13], [-0.38, -0.0, -0.0], [-0.44, -0.0, -0.0], [-0.06, -0.0, 0.22], [-0.02, -0.0, 0.1], [0.03, -0.0, 0.13], [-0.31, -0.0, 0.22], [-0.19, -0.0, 0.19], [-0.19, -0.0, 0.25], [0.03, -0.0, -0.13], [-0.09, 0.0, -0.17], [-0.06, 0.0, -0.22], [-0.4, -0.0, -0.13], [-0.28, 0.0, -0.17], [-0.35, -0.0, -0.1], [-0.19, -0.0, 0.25], [-0.19, -0.0, 0.19], [-0.09, -0.0, 0.17], [-0.4, -0.0, 0.13], [-0.35, -0.0, 0.1], [-0.28, -0.0, 0.17], [0.07, -0.0, -0.0], [0.01, -0.0, -0.0], [-0.02, -0.0, -0.1], [-0.19, -0.0, -0.25], [-0.19, -0.0, -0.19], [-0.28, 0.0, -0.17], [-0.4, -0.0, 0.13], [-0.44, -0.0, -0.0], [-0.38, -0.0, -0.0], [0.07, -0.0, -0.0], [0.03, -0.0, 0.13], [-0.02, -0.0, 0.1], [-0.06, 0.0, -0.22], [-0.09, 0.0, -0.17], [-0.19, -0.0, -0.19], [-0.4, -0.0, -0.13], [-0.35, -0.0, -0.1], [-0.38, -0.0, -0.0], [-0.06, -0.0, 0.22], [-0.09, -0.0, 0.17], [-0.02, -0.0, 0.1], [-0.31, -0.0, 0.22], [-0.28, -0.0, 0.17], [-0.19, -0.0, 0.19], [0.03, -0.0, -0.13], [-0.02, -0.0, -0.1], [-0.09, 0.0, -0.17], [-0.4, -0.0, -0.13], [-0.31, 0.0, -0.22], [-0.28, 0.0, -0.17]], "SimpleDeform_Bend_Direction_": [[-3.04, -0.0, 1.68], [-2.79, -0.0, 1.35], [-2.79, 0.1, 1.35], [0.0, 0.0, 0.28], [-0.36, -0.03, 0.31], [0.0, -0.03, 0.28], [-2.22, 0.0, 0.87], [-1.9, 0.04, 0.71], [-2.22, 0.12, 0.87], [-2.22, 0.0, 0.87], [-1.9, -0.04, 0.71], [-1.9, 0.0, 0.71], [-3.04, -0.0, 1.68], [-2.79, -0.1, 1.35], [-2.79, -0.0, 1.35], [-2.79, 0.1, 1.35], [-2.51, 0.0, 1.06], [-2.51, 0.13, 1.06], [0.0, 0.03, 0.28], [-0.36, 0.0, 0.31], [0.0, 0.0, 0.28], [-1.9, 0.0, 0.71], [-1.43, -0.04, 0.53], [-1.43, 0.0, 0.53], [-1.43, 0.0, 0.53], [-0.86, -0.04, 0.38], [-0.86, 0.0, 0.38], [-0.86, 0.0, 0.38], [-0.36, -0.03, 0.31], [-0.36, 0.0, 0.31], [-1.9, 0.0, 0.71], [-1.43, 0.04, 0.53], [-1.9, 0.04, 0.71], [-1.43, 0.0, 0.53], [-0.86, 0.04, 0.38], [-1.43, 0.04, 0.53], [-0.86, 0.0, 0.38], [-0.36, 0.03, 0.31], [-0.86, 0.04, 0.38], [-2.22, 0.12, 0.87], [-2.51, 0.0, 1.06], [-2.22, 0.0, 0.87], [-2.79, -0.1, 1.35], [-2.51, 0.0, 1.06], [-2.79, -0.0, 1.35], [-2.22, -0.12, 0.87], [-2.51, 0.0, 1.06], [-2.51, -0.13, 1.06], [3.04, -0.0, 1.68], [2.79, 0.1, 1.35], [2.79, -0.0, 1.35], [0.36, -0.03, 0.31], [0.0, 0.0, 0.28], [0.0, -0.03, 0.28], [1.9, 0.04, 0.71], [2.22, 0.0, 0.87], [2.22, 0.12, 0.87], [2.22, 0.0, 0.87], [1.9, -0.04, 0.71], [2.22, -0.12, 0.87], [3.04, -0.0, 1.68], [2.79, -0.0, 1.35], [2.79, -0.1, 1.35], [2.79, 0.1, 1.35], [2.51, 0.0, 1.06], [2.79, -0.0, 1.35], [0.36, 0.0, 0.31], [0.0, 0.03, 0.28], [0.0, 0.0, 0.28], [1.9, 0.0, 0.71], [1.43, -0.04, 0.53], [1.9, -0.04, 0.71], [1.43, 0.0, 0.53], [0.86, -0.04, 0.38], [1.43, -0.04, 0.53], [0.86, 0.0, 0.38], [0.36, -0.03, 0.31], [0.86, -0.04, 0.38], [1.43, 0.04, 0.53], [1.9, 0.0, 0.71], [1.9, 0.04, 0.71], [0.86, 0.04, 0.38], [1.43, 0.0, 0.53], [1.43, 0.04, 0.53], [0.36, 0.03, 0.31], [0.86, 0.0, 0.38], [0.86, 0.04, 0.38], [2.51, 0.0, 1.06], [2.22, 0.12, 0.87], [2.22, 0.0, 0.87], [2.51, 0.0, 1.06], [2.79, -0.1, 1.35], [2.79, -0.0, 1.35], [2.22, -0.12, 0.87], [2.51, 0.0, 1.06], [2.22, 0.0, 0.87], [0.0, 0.0, 0.28], [-0.36, 0.0, 0.31], [-0.36, -0.03, 0.31], [-2.22, 0.0, 0.87], [-1.9, 0.0, 0.71], [-1.9, 0.04, 0.71], [-2.22, 0.0, 0.87], [-2.22, -0.12, 0.87], [-1.9, -0.04, 0.71], [-2.79, 0.1, 1.35], [-2.79, -0.0, 1.35], [-2.51, 0.0, 1.06], [0.0, 0.03, 0.28], [-0.36, 0.03, 0.31], [-0.36, 0.0, 0.31], [-1.9, 0.0, 0.71], [-1.9, -0.04, 0.71], [-1.43, -0.04, 0.53], [-1.43, 0.0, 0.53], [-1.43, -0.04, 0.53], [-0.86, -0.04, 0.38], [-0.86, 0.0, 0.38], [-0.86, -0.04, 0.38], [-0.36, -0.03, 0.31], [-1.9, 0.0, 0.71], [-1.43, 0.0, 0.53], [-1.43, 0.04, 0.53], [-1.43, 0.0, 0.53], [-0.86, 0.0, 0.38], [-0.86, 0.04, 0.38], [-0.86, 0.0, 0.38], [-0.36, 0.0, 0.31], [-0.36, 0.03, 0.31], [-2.22, 0.12, 0.87], [-2.51, 0.13, 1.06], [-2.51, 0.0, 1.06], [-2.79, -0.1, 1.35], [-2.51, -0.13, 1.06], [-2.51, 0.0, 1.06], [-2.22, -0.12, 0.87], [-2.22, 0.0, 0.87], [-2.51, 0.0, 1.06], [0.36, -0.03, 0.31], [0.36, 0.0, 0.31], [0.0, 0.0, 0.28], [1.9, 0.04, 0.71], [1.9, 0.0, 0.71], [2.22, 0.0, 0.87], [2.22, 0.0, 0.87], [1.9, 0.0, 0.71], [1.9, -0.04, 0.71], [2.79, 0.1, 1.35], [2.51, 0.13, 1.06], [2.51, 0.0, 1.06], [0.36, 0.0, 0.31], [0.36, 0.03, 0.31], [0.0, 0.03, 0.28], [1.9, 0.0, 0.71], [1.43, 0.0, 0.53], [1.43, -0.04, 0.53], [1.43, 0.0, 0.53], [0.86, 0.0, 0.38], [0.86, -0.04, 0.38], [0.86, 0.0, 0.38], [0.36, 0.0, 0.31], [0.36, -0.03, 0.31], [1.43, 0.04, 0.53], [1.43, 0.0, 0.53], [1.9, 0.0, 0.71], [0.86, 0.04, 0.38], [0.86, 0.0, 0.38], [1.43, 0.0, 0.53], [0.36, 0.03, 0.31], [0.36, 0.0, 0.31], [0.86, 0.0, 0.38], [2.51, 0.0, 1.06], [2.51, 0.13, 1.06], [2.22, 0.12, 0.87], [2.51, 0.0, 1.06], [2.51, -0.13, 1.06], [2.79, -0.1, 1.35], [2.22, -0.12, 0.87], [2.51, -0.13, 1.06], [2.51, 0.0, 1.06]], "Sphere_GizmoGroup_": [[-0.0, -0.71, 0.0], [0.61, 0.35, 0.0], [-0.61, 0.35, 0.0], [-0.61, 0.35, 0.0], [-0.71, -0.0, 0.0], [-0.0, -0.71, 0.0], [-0.71, -0.0, 0.0], [-0.61, -0.35, 0.0], [-0.0, -0.71, 0.0], [-0.61, -0.35, 0.0], [-0.35, -0.61, 0.0], [-0.0, -0.71, 0.0], [-0.0, -0.71, 0.0], [0.35, -0.61, 0.0], [0.61, -0.35, 0.0], [0.61, -0.35, 0.0], [0.71, -0.0, 0.0], [0.61, 0.35, 0.0], [0.61, 0.35, 0.0], [0.35, 0.61, 0.0], [-0.0, 0.71, 0.0], [-0.0, 0.71, 0.0], [-0.35, 0.61, 0.0], [-0.61, 0.35, 0.0], [-0.0, -0.71, 0.0], [0.61, -0.35, 0.0], [0.61, 0.35, 0.0], [0.61, 0.35, 0.0], [-0.0, 0.71, 0.0], [-0.61, 0.35, 0.0]]} \ No newline at end of file diff --git a/simple_deform_helper/gizmo.py b/simple_deform_helper/gizmo.py deleted file mode 100644 index 15d03cd99..000000000 --- a/simple_deform_helper/gizmo.py +++ /dev/null @@ -1,620 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-or-later -import bpy -import math -from bpy_extras import view3d_utils -from mathutils import Vector, Euler -from bpy.types import ( - Gizmo, - GizmoGroup, -) - -from .draw import Handler -from .utils import Utils, PublicClass -from .data import Data - - -class CustomGizmo(Gizmo, Utils, Handler, Data): - """绘制自定义Gizmo""" - bl_idname = '_Custom_Gizmo' - - def setup(self): - self.draw_type = 'None_GizmoGroup_' - if not hasattr(self, 'custom_shape'): - self.custom_shape = {} - for i in self.G_GizmoCustomShapeDict: - self.custom_shape[i] = self.new_custom_shape( - 'TRIS', self.G_GizmoCustomShapeDict[i]) - self.add_handler() - - def draw(self, context): - self.draw_custom_shape(self.custom_shape[self.draw_type]) - - def draw_select(self, context, select_id): - self.draw_custom_shape( - self.custom_shape[self.draw_type], select_id=select_id) - - def invoke(self, context, event): - return {'RUNNING_MODAL'} - - def modal(self, context, event, tweak): - self.add_handler() - - self.update_bound_box(context.object) - self.update_empty_matrix() - return {'RUNNING_MODAL'} - - -class ViewSimpleDeformGizmo(Gizmo, Utils, Handler, Data, PublicClass): - """显示轴向切换拖动点Gizmo(两个点) - """ - bl_idname = 'ViewSimpleDeformGizmo' - - bl_target_properties = ( - {'id': 'up_limits', 'type': 'FLOAT', 'array_length': 1}, - {'id': 'down_limits', 'type': 'FLOAT', 'array_length': 1}, - {'id': 'angle', 'type': 'FLOAT', 'array_length': 1}, - ) - - __slots__ = ( - 'mod', - 'up', - 'down', - 'up_', - 'down_', - 'draw_type', - 'mouse_dpi', - 'ctrl_mode', - 'empty_object', - 'init_mouse_y', - 'init_mouse_x', - 'custom_shape', - 'int_value_angle', - 'value_deform_axis', - 'int_value_up_limits', - 'int_value_down_limits', - 'rotate_follow_modifier', - ) - - def update_gizmo_rotate(self, axis, mod): - if self.rotate_follow_modifier: - rot = Euler() - if axis == 'X' and (not self.is_positive(mod.angle)): - rot.z = math.pi - - elif axis == 'Y': - if self.is_positive(mod.angle): - rot.z = -(math.pi / 2) - else: - rot.z = math.pi / 2 - elif axis == 'Z': - if self.is_positive(mod.angle): - rot.x = rot.z = rot.y = math.pi / 2 - else: - rot.z = rot.y = math.pi / 2 - rot.x = -(math.pi / 2) - - rot = rot.to_matrix() - self.matrix_basis = self.matrix_basis @ rot.to_4x4() - - def update_draw_limits_bound_box(self, data, mod, axis, mat, up_, down_): - top, bottom, left, right, front, back = data - if mod.origin: - vector_axis = self.get_vector_axis(mod) - origin_mat = mod.origin.matrix_world.to_3x3() - axis_ = origin_mat @ vector_axis - point_lit = [[top, bottom], [left, right], [front, back]] - for f in range(point_lit.__len__()): - i = point_lit[f][0] - j = point_lit[f][1] - angle = self.point_to_angle(i, j, f, axis_) - if abs(angle - 180) < 0.00001: - point_lit[f][1], point_lit[f][0] = up_, down_ - elif abs(angle) < 0.00001: - point_lit[f][0], point_lit[f][1] = up_, down_ - [[top, bottom], [left, right], [front, back]] = point_lit - else: - top, bottom, left, right, front, back = self.get_up_down_return_list( - mod, axis, up_, down_, data) - data = top, bottom, left, right, front, back - (top, bottom, left, right, front, - back) = self.matrix_calculation(mat.inverted(), data) - self.G_SimpleDeformGizmoHandlerDit['draw_limits_bound_box'] = ( - mat, ((right[0], back[1], top[2]), (left[0], front[1], bottom[2],))) - - def update_matrix_basis_translation(self, co, mat, up_, down_): - if 'angle' == self.ctrl_mode: - self.matrix_basis.translation = mat @ Vector((co[1])) - elif 'up_limits' == self.ctrl_mode: - self.matrix_basis.translation = up_ - elif 'down_limits' == self.ctrl_mode: - self.matrix_basis.translation = down_ - - def update_gizmo_matrix(self, context): - ob = context.object - mat = ob.matrix_world - mod = context.object.modifiers.active - axis = mod.deform_axis - if mod.origin: - self.matrix_basis = mod.origin.matrix_world.normalized() - else: - self.matrix_basis = ob.matrix_world.normalized() - - co = self.generate_co_data() - self.update_gizmo_rotate(axis, mod) - # calculation limits position - top, bottom, left, right, front, back = self.each_face_pos(mat) - (up, down), (up_, down_) = self.get_limits_pos( - mod, (top, bottom, left, right, front, back)) - self.update_matrix_basis_translation(co, mat, up_, down_) - - self.up = up - self.down = down - self.up_ = up_ - self.down_ = down_ - self.G_SimpleDeformGizmoHandlerDit['draw_line'] = ( - (up, down), (up_, down_)) - data = top, bottom, left, right, front, back - self.update_draw_limits_bound_box(data, mod, axis, mat, up_, down_) - - def setup(self): - self.generate_co_data() - self.draw_type = 'None_GizmoGroup_' - self.ctrl_mode = 'angle' # up_limits , down_limits - self.mouse_dpi = 10 - self.rotate_follow_modifier = True - if not hasattr(self, 'custom_shape'): - self.custom_shape = {} - for i in self.G_GizmoCustomShapeDict: - item = self.G_GizmoCustomShapeDict[i] - self.custom_shape[i] = self.new_custom_shape('TRIS', item) - self.add_handler() - - def draw(self, context): - self.add_handler() - - self.update_gizmo_matrix(context) - self.draw_custom_shape(self.custom_shape[self.draw_type]) - - def draw_select(self, context, select_id): - self.update_gizmo_matrix(context) - self.draw_custom_shape( - self.custom_shape[self.draw_type], select_id=select_id) - - def invoke(self, context, event): - self.init_mouse_y = event.mouse_y - self.init_mouse_x = event.mouse_x - mod = context.object.modifiers.active - limits = mod.limits - up_limits = limits[1] - down_limits = limits[0] - - if 'angle' == self.ctrl_mode: - self.int_value_angle = self.target_get_value('angle') - elif 'up_limits' == self.ctrl_mode: - self.int_value_up_limits = up_limits - self.target_set_value('up_limits', self.int_value_up_limits) - elif 'down_limits' == self.ctrl_mode: - self.int_value_down_limits = down_limits - self.target_set_value('down_limits', self.int_value_down_limits) - return {'RUNNING_MODAL'} - - def exit(self, context, cancel): - context.area.header_text_set(None) - - if cancel: - if 'angle' == self.ctrl_mode: - self.target_set_value('angle', self.int_value_angle) - elif 'deform_axis' == self.ctrl_mode: - self.target_set_value('deform_axis', self.value_deform_axis) - elif 'up_limits' == self.ctrl_mode: - self.target_set_value('up_limits', self.int_value_up_limits) - - elif 'down_limits' == self.ctrl_mode: - self.target_set_value( - 'down_limits', self.int_value_down_limits) - - def delta_update(self, context, event, delta): - if ('draw_line' in self.G_SimpleDeformGizmoHandlerDit) and (self.ctrl_mode in ('up_limits', 'down_limits')): - x, y = view3d_utils.location_3d_to_region_2d( - context.region, context.space_data.region_3d, self.up) - x2, y2 = view3d_utils.location_3d_to_region_2d( - context.region, context.space_data.region_3d, self.down) - - mouse_line_distance = math.sqrt(((event.mouse_region_x - x2) ** 2) + - ((event.mouse_region_y - y2) ** 2)) - straight_line_distance = math.sqrt(((x2 - x) ** 2) + - ((y2 - y) ** 2)) - delta = mouse_line_distance / \ - straight_line_distance + 0 - - v_up = Vector((x, y)) - v_down = Vector((x2, y2)) - limits_angle = v_up - v_down - - mouse_v = Vector((event.mouse_region_x, event.mouse_region_y)) - - mouse_angle = mouse_v - v_down - angle_ = mouse_angle.angle(limits_angle) - if angle_ > (math.pi / 2): - delta = 0 - return delta - - def set_down_value(self, data, mu): - up_limits, down_limits, delta, middle, min_value, max_value, limit_scope, difference_value, event, origin_mode = data - value = self.value_limit(delta, max_value=mu - - limit_scope if middle else max_value) - self.target_set_value('down_limits', value) - if event.ctrl: - self.target_set_value( - 'up_limits', value + difference_value) - elif middle: - if origin_mode == 'LIMITS_MIDDLE': - self.target_set_value('up_limits', mu - (value - mu)) - elif origin_mode == 'MIDDLE': - self.target_set_value('up_limits', 1 - value) - else: - self.target_set_value('up_limits', up_limits) - else: - self.target_set_value('up_limits', up_limits) - - def set_up_value(self, data, mu): - up_limits, down_limits, delta, middle, min_value, max_value, limit_scope, difference_value, event, origin_mode = data - value = self.value_limit(delta, min_value=mu + - limit_scope if middle else min_value) - self.target_set_value('up_limits', value) - if event.ctrl: - self.target_set_value( - 'down_limits', value - difference_value) - elif middle: - if origin_mode == 'LIMITS_MIDDLE': - self.target_set_value('down_limits', mu - (value - mu)) - elif origin_mode == 'MIDDLE': - self.target_set_value('down_limits', 1 - value) - else: - self.target_set_value('down_limits', down_limits) - else: - self.target_set_value('down_limits', down_limits) - - def set_prop_value(self, data): - up_limits, down_limits, delta, middle, min_value, max_value, limit_scope, difference_value, event, origin_mode = data - mu = (up_limits + down_limits) / 2 - if 'angle' == self.ctrl_mode: - value = self.int_value_angle - delta - self.target_set_value('angle', value) - elif 'up_limits' == self.ctrl_mode: - self.set_up_value(data, mu) - elif 'down_limits' == self.ctrl_mode: - self.set_down_value(data, mu) - - def update_header_text(self, context, mod, origin, up_limits, down_limits): - t = lambda a: bpy.app.translations.pgettext(a) - - if (mod.deform_method in ('TWIST', 'BEND')) and (self.ctrl_mode in ('angle',)): - text = t("Angle") + ':{}'.format(math.degrees(mod.angle)) - elif 'up_limits' == self.ctrl_mode: - text = t("Upper limit") + ':{}'.format(up_limits) - elif 'down_limits' == self.ctrl_mode: - text = t("Down limit") + ':{}'.format(down_limits) - else: - text = t("Coefficient") + ':{}'.format(mod.factor) - text += ' ' - text += t(origin.bl_rna.properties[ - 'origin_mode'].enum_items[origin.origin_mode].name) - context.area.header_text_set(text) - - def event_ops(self, event, ob, origin): - """通过输入键位来更改属性""" - # event ctrl - data_path = ('object.SimpleDeformGizmo_PropertyGroup.origin_mode', - 'object.modifiers.active.origin.SimpleDeformGizmo_PropertyGroup.origin_mode') - - if event.type in ('WHEELUPMOUSE', 'WHEELDOWNMOUSE'): - reverse = (event.type == 'WHEELUPMOUSE') - for path in data_path: - bpy.ops.wm.context_cycle_enum( - data_path=path, reverse=reverse, wrap=True) - elif event.type in ('X', 'Y', 'Z'): - ob.modifiers.active.deform_axis = event.type - elif event.type == 'A': - self.pref.display_bend_axis_switch_gizmo = True - return {'FINISHED'} - self.add_handler() - - return {'RUNNING_MODAL'} - - def modal(self, context, event, tweak): - self.update_bound_box(context.object) - - delta = (self.init_mouse_x - event.mouse_x) / self.mouse_dpi - ob = context.object - mod = ob.modifiers.active - limits = mod.limits - up_limits = limits[1] - down_limits = limits[0] - origin = self.get_origin_property_group(mod, ob) - origin_mode = origin.origin_mode - middle = origin_mode in ('LIMITS_MIDDLE', 'MIDDLE') - limit_scope = self.pref.modifiers_limits_tolerance - max_value = up_limits - limit_scope - min_value = down_limits + limit_scope - difference_value = up_limits - down_limits - - if 'SNAP' in tweak: - delta = round(delta) - if 'PRECISE' in tweak: - delta /= self.mouse_dpi - delta = self.delta_update(context, event, delta) - - if origin_mode != 'NOT' and ('draw_line' in self.G_SimpleDeformGizmoHandlerDit): - self.empty_object, _ = self.new_empty(ob, mod) - self.G_SimpleDeformGizmoHandlerDit['empty_object'] = self.empty_object - data = up_limits, down_limits, delta, middle, min_value, max_value, limit_scope, difference_value, event, origin_mode - self.set_prop_value(data) - self.update_gizmo_matrix(context) - self.update_empty_matrix() - self.update_bound_box(context.object) - - self.update_header_text(context, mod, origin, up_limits, down_limits) - self.add_handler() - - return self.event_ops(event, ob, origin) - - -class SimpleDeformGizmoGroup(GizmoGroup, Utils, Handler, PublicClass, Data): - """显示Gizmo - """ - bl_idname = 'OBJECT_GGT_SimpleDeformGizmoGroup' - bl_label = 'SimpleDeformGizmoGroup' - bl_space_type = 'VIEW_3D' - bl_region_type = 'WINDOW' - bl_options = {'3D', 'PERSISTENT', } - - @classmethod - def poll(cls, context): - pol = cls.simple_deform_poll(context) - pref = cls.pref_() - deform_method = ( - pol and (context.object.modifiers.active.deform_method != 'BEND')) - display_gizmo = pref.display_bend_axis_switch_gizmo - switch = (not display_gizmo) - return pol and (deform_method or switch) - - def generate_gizmo_mode(self, gizmo_data): - """生成gizmo的上限下限及角度设置 - - Args: - gizmo_data (_type_): _description_ - """ - for i, j, k in gizmo_data: - setattr(self, i, self.gizmos.new(j)) - gizmo = getattr(self, i) - for f in k: - if f == 'target_set_operator': - gizmo.target_set_operator(k[f]) - elif f == 'target_set_prop': - gizmo.target_set_prop(*k[f]) - else: - setattr(gizmo, f, k[f]) - - def setup(self, context): - sd_name = ViewSimpleDeformGizmo.bl_idname - - add_data = (('up_limits', - sd_name, - {'ctrl_mode': 'up_limits', - 'draw_type': 'Sphere_GizmoGroup_', - 'mouse_dpi': 1000, - 'color': (1.0, 0, 0), - 'alpha': 0.5, - 'color_highlight': (1.0, 1.0, 1.0), - 'alpha_highlight': 0.3, - 'use_draw_modal': True, - 'scale_basis': 0.1, - 'use_draw_value': True, }), - ('down_limits', - sd_name, - {'ctrl_mode': 'down_limits', - 'draw_type': 'Sphere_GizmoGroup_', - 'mouse_dpi': 1000, - 'color': (0, 1.0, 0), - 'alpha': 0.5, - 'color_highlight': (1.0, 1.0, 1.0), - 'alpha_highlight': 0.3, - 'use_draw_modal': True, - 'scale_basis': 0.1, - 'use_draw_value': True, }), - ('angle', - sd_name, - {'ctrl_mode': 'angle', - 'draw_type': 'SimpleDeform_GizmoGroup_', - 'color': (1.0, 0.5, 1.0), - 'alpha': 0.3, - 'color_highlight': (1.0, 1.0, 1.0), - 'alpha_highlight': 0.3, - 'use_draw_modal': True, - 'scale_basis': 0.1, - 'use_draw_value': True, - 'mouse_dpi': 100, - }), - ) - - self.generate_gizmo_mode(add_data) - - data_path = 'object.modifiers.active.deform_axis' - set_enum = 'wm.context_set_enum' - - for axis in ('X', 'Y', 'Z'): - # show toggle axis button - gizmo = self.gizmos.new('GIZMO_GT_button_2d') - gizmo.icon = f'EVENT_{axis.upper()}' - gizmo.draw_options = {'BACKDROP', 'HELPLINE'} - ops = gizmo.target_set_operator(set_enum) - ops.data_path = data_path - ops.value = axis - gizmo.color = (0, 0, 0) - gizmo.alpha = 0.3 - gizmo.color_highlight = 1.0, 1.0, 1.0 - gizmo.alpha_highlight = 0.3 - gizmo.use_draw_modal = True - gizmo.use_draw_value = True - gizmo.scale_basis = 0.1 - setattr(self, f'deform_axis_{axis.lower()}', gizmo) - self.add_handler() - - def refresh(self, context): - pro = context.object.SimpleDeformGizmo_PropertyGroup - - self.angle.target_set_prop('angle', - context.object.modifiers.active, - 'angle') - self.down_limits.target_set_prop('down_limits', - pro, - 'down_limits') - self.down_limits.target_set_prop('up_limits', - pro, - 'up_limits') - self.up_limits.target_set_prop('down_limits', - pro, - 'down_limits') - self.up_limits.target_set_prop('up_limits', - pro, - 'up_limits') - self.add_handler() - - def draw_prepare(self, context): - ob = context.object - mat = ob.matrix_world - - if 'co' in self.G_SimpleDeformGizmoHandlerDit: - def _mat(f): - co = self.G_SimpleDeformGizmoHandlerDit['co'][0] - co = (co[0] + (max(ob.dimensions) * f), co[1], - co[2] - (min(ob.dimensions) * 0.3)) - return mat @ Vector(co) - - self.deform_axis_x.matrix_basis.translation = _mat(0) - self.deform_axis_y.matrix_basis.translation = _mat(0.3) - self.deform_axis_z.matrix_basis.translation = _mat(0.6) - self.add_handler() - - def invoke_prepare(self, context, gizmo): - self.add_handler() - - -class SimpleDeformGizmoGroupDisplayBendAxiSwitchGizmo(GizmoGroup, Utils, Handler, PublicClass): - """绘制切换变型轴的 - 变换方向 - """ - bl_idname = 'OBJECT_GGT_SimpleDeformGizmoGroup_display_bend_axis_switch_gizmo' - bl_label = 'SimpleDeformGizmoGroup_display_bend_axis_switch_gizmo' - - bl_space_type = 'VIEW_3D' - bl_region_type = 'WINDOW' - bl_options = { - '3D', - 'PERSISTENT', - } - - @classmethod - def poll(cls, context): - pref = cls.pref_() - simple = cls.simple_deform_poll(context) - bend = simple and ( - context.object.modifiers.active.deform_method == 'BEND') - switch_axis = (pref.display_bend_axis_switch_gizmo == True) - return switch_axis and bend - - def setup(self, context): - _draw_type = 'SimpleDeform_Bend_Direction_' - _color_a = 1, 0, 0 - _color_b = 0, 1, 0 - self.add_handler() - - for na, axis, rot, positive in ( - ('top_a', 'X', (math.radians(90), 0, math.radians(90)), True), - ('top_b', 'X', (math.radians(90), 0, 0), True), - - ('bottom_a', 'X', (math.radians(90), 0, math.radians(90)), False), - ('bottom_b', 'X', (math.radians(90), 0, 0), False), - - ('left_a', 'Y', (math.radians(90), 0, 0), False), - ('left_b', 'Y', (0, 0, 0), False), - - ('right_a', 'Y', (math.radians(90), 0, 0), True), - ('right_b', 'Y', (0, 0, 0), True), - - ('front_a', 'Z', (0, 0, 0), False), - ('front_b', 'X', (0, 0, 0), False), - - ('back_a', 'Z', (0, 0, 0), True), - ('back_b', 'X', (0, 0, 0), True),): - _a = (na.split('_')[1] == 'a') - setattr(self, na, self.gizmos.new(CustomGizmo.bl_idname)) - gizmo = getattr(self, na) - gizmo.mode = na - gizmo.draw_type = _draw_type - gizmo.color = _color_a if _a else _color_b - gizmo.alpha = 0.3 - gizmo.color_highlight = 1.0, 1.0, 1.0 - gizmo.alpha_highlight = 1 - gizmo.use_draw_modal = True - gizmo.scale_basis = 0.2 - gizmo.use_draw_value = True - ops = gizmo.target_set_operator( - 'simple_deform_gizmo.deform_axis') - ops.Deform_Axis = axis - ops.X_Value = rot[0] - ops.Y_Value = rot[1] - ops.Z_Value = rot[2] - ops.Is_Positive = positive - - def draw_prepare(self, context): - ob = context.object - mat = ob.matrix_world - top, bottom, left, right, front, back = self.each_face_pos(mat) - - rad = math.radians - for_list = ( - ('top_a', top, (0, 0, 0),), - ('top_b', top, (0, 0, rad(90)),), - - ('bottom_a', bottom, (0, rad(180), 0),), - ('bottom_b', bottom, (0, rad(180), rad(90)),), - - ('left_a', left, (rad(-90), 0, rad(90)),), - ('left_b', left, (0, rad(-90), 0),), - - ('right_a', right, (rad(90), 0, rad(90)),), - ('right_b', right, (0, rad(90), 0),), - - ('front_a', front, (rad(90), 0, 0),), - ('front_b', front, (rad(90), rad(90), 0),), - - ('back_a', back, (rad(-90), 0, 0),), - ('back_b', back, (rad(-90), rad(-90), 0),), - ) - for i, j, w, in for_list: - gizmo = getattr(self, i, False) - rot = Euler(w, 'XYZ').to_matrix().to_4x4() - - gizmo.matrix_basis = mat.to_euler().to_matrix().to_4x4() @ rot - gizmo.matrix_basis.translation = Vector(j) - - -class_list = ( - CustomGizmo, - ViewSimpleDeformGizmo, - SimpleDeformGizmoGroup, - SimpleDeformGizmoGroupDisplayBendAxiSwitchGizmo, -) - -register_class, unregister_class = bpy.utils.register_classes_factory(class_list) - - -def register(): - register_class() - - -def unregister(): - Handler.del_handler() - unregister_class() diff --git a/simple_deform_helper/gizmo/__init__.py b/simple_deform_helper/gizmo/__init__.py new file mode 100644 index 000000000..756b41aec --- /dev/null +++ b/simple_deform_helper/gizmo/__init__.py @@ -0,0 +1,32 @@ +import bpy + +from .angle_and_factor import AngleGizmoGroup, AngleGizmo +from .bend_axis import BendAxiSwitchGizmoGroup, CustomGizmo +from .set_deform_axis import SetDeformGizmoGroup +from .up_down_limits_point import UpDownLimitsGizmo, UpDownLimitsGizmoGroup +from ..draw import Draw3D + +class_list = ( + UpDownLimitsGizmo, + UpDownLimitsGizmoGroup, + + AngleGizmo, + AngleGizmoGroup, + + CustomGizmo, + BendAxiSwitchGizmoGroup, + + SetDeformGizmoGroup, +) + +register_class, unregister_class = bpy.utils.register_classes_factory(class_list) + + +def register(): + Draw3D.add_handler() + register_class() + + +def unregister(): + Draw3D.del_handler() + unregister_class() diff --git a/simple_deform_helper/gizmo/angle_and_factor.py b/simple_deform_helper/gizmo/angle_and_factor.py new file mode 100644 index 000000000..56493fee0 --- /dev/null +++ b/simple_deform_helper/gizmo/angle_and_factor.py @@ -0,0 +1,161 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +import math + +from bpy.types import Gizmo +from bpy.types import ( + GizmoGroup, +) + +from ..update import change_active_modifier_parameter +from ..utils import GizmoUtils, GizmoGroupUtils + + +class AngleUpdate(GizmoUtils): + int_value_degrees: float + tmp_value_angle: float + + def get_snap(self, delta, tweak): + is_snap = 'SNAP' in tweak + is_precise = 'PRECISE' in tweak + if is_snap and is_precise: + delta = round(delta) + elif is_snap: + delta //= 5 + delta *= 5 + elif is_precise: + delta /= self.mouse_dpi + delta //= 0.01 + delta *= 0.01 + return delta + + def update_prop_value(self, event, tweak): + def v(va): + self.target_set_value('angle', math.radians(va)) + + not_c_l = not event.alt and not event.ctrl + is_only_shift = event.shift and not_c_l + + change_angle = self.get_delta(event) + if is_only_shift: + change_angle /= 50 + new_value = self.tmp_value_angle - change_angle + old_value = self.target_get_value('angle') + snap_value = self.get_snap(new_value, tweak) + + is_shift = event.type == 'LEFT_SHIFT' + if is_only_shift: + if event.value == 'PRESS': + self.init_mouse_region_x = event.mouse_region_x + self.tmp_value_angle = int(math.degrees(old_value)) + v(self.tmp_value_angle) + return + + value = (self.tmp_value_angle - change_angle) // 0.01 * 0.01 + v(value) + return + + elif not_c_l and not event.shift and is_shift and event.value == 'RELEASE': + self.init_mouse_region_x = event.mouse_region_x + # new_value = self.tmp_value_angle = math.degrees(old_value) + return + v(snap_value) + + def update_gizmo_matrix(self, context): + matrix = context.object.matrix_world + point = self.modifier_bound_co[1] + self.matrix_basis = self.obj_matrix_world + self.matrix_basis.translation = matrix @ point + + def update_header_text(self, context): + te = self.translate_text + text = te(self.modifier.deform_method.title()) + ' ' + + if self.modifier_is_use_angle_value: + value = round(math.degrees(self.modifier_angle), 3) + text += self.translate_header_text('Angle', value) + else: + value = round(self.modifier.factor, 3) + text += self.translate_header_text('Coefficient', value) + context.area.header_text_set(text) + + +class AngleGizmo(Gizmo, AngleUpdate): + bl_idname = 'ViewSimpleAngleGizmo' + + bl_target_properties = ( + {'id': 'up_limits', 'type': 'FLOAT', 'array_length': 1}, + {'id': 'down_limits', 'type': 'FLOAT', 'array_length': 1}, + {'id': 'angle', 'type': 'FLOAT', 'array_length': 1}, + ) + + __slots__ = ( + 'draw_type', + 'mouse_dpi', + 'empty_object', + 'custom_shape', + 'tmp_value_angle', + 'int_value_degrees', + 'init_mouse_region_y', + 'init_mouse_region_x', + ) + + def setup(self): + self.init_setup() + + def invoke(self, context, event): + self.init_invoke(context, event) + self.int_value_degrees = self.target_get_value('angle') + angle = math.degrees(self.int_value_degrees) + self.tmp_value_angle = angle + return {'RUNNING_MODAL'} + + def modal(self, context, event, tweak): + self.clear_point_cache() + + self.update_prop_value(event, tweak) + self.update_deform_wireframe() + self.update_header_text(context) + change_active_modifier_parameter.update_modifier_parameter() + self.tag_redraw(context) + return self.event_handle(event) + + def exit(self, context, cancel): + context.area.header_text_set(None) + if cancel: + self.target_set_value('angle', self.int_value_degrees) + + +class AngleGizmoGroup(GizmoGroup, GizmoGroupUtils): + """ShowGizmo + """ + bl_idname = 'OBJECT_GGT_SimpleDeformGizmoGroup' + bl_label = 'AngleGizmoGroup' + + @classmethod + def poll(cls, context): + return cls.simple_deform_show_gizmo_poll(context) + + def setup(self, context): + sd_name = AngleGizmo.bl_idname + + add_data = [ + ('angle', + sd_name, + {'draw_type': 'SimpleDeform_GizmoGroup_', + 'color': (1.0, 0.5, 1.0), + 'alpha': 0.3, + 'color_highlight': (1.0, 1.0, 1.0), + 'alpha_highlight': 0.3, + 'use_draw_modal': True, + 'scale_basis': 0.1, + 'use_draw_value': True, + 'mouse_dpi': 5, + }), + ] + + self.generate_gizmo(add_data) + + def refresh(self, context): + self.angle.target_set_prop('angle', + context.object.modifiers.active, + 'angle') diff --git a/simple_deform_helper/gizmo/bend_axis.py b/simple_deform_helper/gizmo/bend_axis.py new file mode 100644 index 000000000..a6ee0bd40 --- /dev/null +++ b/simple_deform_helper/gizmo/bend_axis.py @@ -0,0 +1,115 @@ +import math + +from bpy.types import GizmoGroup +from bpy_types import Gizmo +from mathutils import Euler, Vector + +from ..utils import GizmoUtils, GizmoGroupUtils + + +class CustomGizmo(Gizmo, GizmoUtils): + """Draw Custom Gizmo""" + bl_idname = '_Custom_Gizmo' + draw_type: str + custom_shape: dict + + def setup(self): + self.init_setup() + + def draw(self, context): + self.draw_custom_shape(self.custom_shape[self.draw_type]) + + def draw_select(self, context, select_id): + self.draw_custom_shape( + self.custom_shape[self.draw_type], select_id=select_id) + + def invoke(self, context, event): + self.init_invoke(context, event) + return {'RUNNING_MODAL'} + + def modal(self, context, event, tweak): + self.update_empty_matrix() + return {'RUNNING_MODAL'} + + +class BendAxiSwitchGizmoGroup(GizmoGroup, GizmoGroupUtils): + bl_idname = 'OBJECT_GGT_SimpleDeformGizmoGroup_display_bend_axis_switch_gizmo' + bl_label = 'SimpleDeformGizmoGroup_display_bend_axis_switch_gizmo' + + @classmethod + def poll(cls, context): + return cls.simple_deform_show_bend_axis_witch_poll(context) + + def setup(self, context): + _draw_type = 'SimpleDeform_Bend_Direction_' + _color_a = 1, 0, 0 + _color_b = 0, 1, 0 + + for na, axis, rot, positive in ( + ('top_a', 'X', (math.radians(90), 0, math.radians(90)), True), + ('top_b', 'X', (math.radians(90), 0, 0), True), + + ('bottom_a', 'X', (math.radians(90), 0, math.radians(90)), False), + ('bottom_b', 'X', (math.radians(90), 0, 0), False), + + ('left_a', 'Y', (math.radians(90), 0, 0), False), + ('left_b', 'Y', (0, 0, 0), False), + + ('right_a', 'Y', (math.radians(90), 0, 0), True), + ('right_b', 'Y', (0, 0, 0), True), + + ('front_a', 'Z', (0, 0, 0), False), + ('front_b', 'X', (0, 0, 0), False), + + ('back_a', 'Z', (0, 0, 0), True), + ('back_b', 'X', (0, 0, 0), True),): + _a = (na.split('_')[1] == 'a') + setattr(self, na, self.gizmos.new(CustomGizmo.bl_idname)) + gizmo = getattr(self, na) + gizmo.mode = na + gizmo.draw_type = _draw_type + gizmo.color = _color_a if _a else _color_b + gizmo.alpha = 0.3 + gizmo.color_highlight = 1.0, 1.0, 1.0 + gizmo.alpha_highlight = 1 + gizmo.use_draw_modal = True + gizmo.scale_basis = 0.2 + gizmo.use_draw_value = True + ops = gizmo.target_set_operator( + 'simple_deform_gizmo.deform_axis') + ops.Deform_Axis = axis + ops.X_Value = rot[0] + ops.Y_Value = rot[1] + ops.Z_Value = rot[2] + ops.Is_Positive = positive + + def draw_prepare(self, context): + ob = context.object + mat = ob.matrix_world + top, bottom, left, right, front, back = self.modifier_bound_box_pos + + rad = math.radians + for_list = ( + ('top_a', top, (0, 0, 0),), + ('top_b', top, (0, 0, rad(90)),), + + ('bottom_a', bottom, (0, rad(180), 0),), + ('bottom_b', bottom, (0, rad(180), rad(90)),), + + ('left_a', left, (rad(-90), 0, rad(90)),), + ('left_b', left, (0, rad(-90), 0),), + + ('right_a', right, (rad(90), 0, rad(90)),), + ('right_b', right, (0, rad(90), 0),), + + ('front_a', front, (rad(90), 0, 0),), + ('front_b', front, (rad(90), rad(90), 0),), + + ('back_a', back, (rad(-90), 0, 0),), + ('back_b', back, (rad(-90), rad(-90), 0),), + ) + for i, j, w, in for_list: + gizmo = getattr(self, i, False) + rot = Euler(w, 'XYZ').to_matrix().to_4x4() + gizmo.matrix_basis = mat.to_euler().to_matrix().to_4x4() @ rot + gizmo.matrix_basis.translation = self.obj_matrix_world @ Vector(j) diff --git a/simple_deform_helper/gizmo/set_deform_axis.py b/simple_deform_helper/gizmo/set_deform_axis.py new file mode 100644 index 000000000..34671b0b8 --- /dev/null +++ b/simple_deform_helper/gizmo/set_deform_axis.py @@ -0,0 +1,51 @@ +from bpy.types import GizmoGroup +from mathutils import Vector + +from ..utils import GizmoGroupUtils + + +class SetDeformGizmoGroup(GizmoGroup, GizmoGroupUtils): + bl_idname = 'OBJECT_GGT_SetDeformGizmoGroup' + bl_label = 'SetDeformGizmoGroup' + + @classmethod + def poll(cls, context): + return cls.simple_deform_show_gizmo_poll(context) and cls.pref_().show_set_axis_button + + def setup(self, context): + data_path = 'object.modifiers.active.deform_axis' + set_enum = 'wm.context_set_enum' + + for axis in ('X', 'Y', 'Z'): + # show toggle axis button + gizmo = self.gizmos.new('GIZMO_GT_button_2d') + gizmo.icon = f'EVENT_{axis.upper()}' + gizmo.draw_options = {'BACKDROP', 'HELPLINE'} + ops = gizmo.target_set_operator(set_enum) + ops.data_path = data_path + ops.value = axis + gizmo.color = (0, 0, 0) + gizmo.alpha = 0.3 + gizmo.color_highlight = 1.0, 1.0, 1.0 + gizmo.alpha_highlight = 0.3 + gizmo.use_draw_modal = True + gizmo.use_draw_value = True + gizmo.scale_basis = 0.1 + setattr(self, f'deform_axis_{axis.lower()}', gizmo) + + def draw_prepare(self, context): + bound = self.modifier_bound_co + if bound: + obj = self.get_depsgraph(self.obj) + dimensions = obj.dimensions + + def mat(f): + b = bound[0] + co = (b[0] + (max(dimensions) * f), + b[1], + b[2] - (min(dimensions) * 0.3)) + return self.obj_matrix_world @ Vector(co) + + self.deform_axis_x.matrix_basis.translation = mat(0) + self.deform_axis_y.matrix_basis.translation = mat(0.3) + self.deform_axis_z.matrix_basis.translation = mat(0.6) diff --git a/simple_deform_helper/gizmo/up_down_limits_point.py b/simple_deform_helper/gizmo/up_down_limits_point.py new file mode 100644 index 000000000..c472bf7ef --- /dev/null +++ b/simple_deform_helper/gizmo/up_down_limits_point.py @@ -0,0 +1,270 @@ +import math +from time import time + +import bpy +from bpy.types import Gizmo, GizmoGroup +from bpy_extras import view3d_utils +from mathutils import Vector + +from ..update import change_active_modifier_parameter +from ..utils import GizmoUtils, GizmoGroupUtils + + +class GizmoProperty(GizmoUtils): + ctrl_mode: str + int_value_up_limits: int + int_value_down_limits: int + + @property + def is_up_limits_mode(self): + return self.ctrl_mode == 'up_limits' + + @property + def is_down_limits_mode(self): + return self.ctrl_mode == 'down_limits' + + @property + def limit_scope(self): + return self.pref.modifiers_limits_tolerance + + @property + def limits_min_value(self): + return self.modifier_down_limits + self.limit_scope + + @property + def limits_max_value(self): + return self.modifier_up_limits - self.limit_scope + + # ----get func + + def get_up_limits_value(self, event): + delta = self.get_delta(event) + mid = self.middle_limits_value + self.limit_scope + min_value = mid if self.is_middle_mode else self.limits_min_value + return self.value_limit(delta, min_value=min_value) + + def get_down_limits_value(self, event): + delta = self.get_delta(event) + mid = self.middle_limits_value - self.limit_scope + max_value = mid if self.is_middle_mode else self.limits_max_value + return self.value_limit(delta, max_value=max_value) + + def get_delta(self, event): + context = bpy.context + x, y = view3d_utils.location_3d_to_region_2d( + context.region, context.space_data.region_3d, self.point_up) + x2, y2 = view3d_utils.location_3d_to_region_2d( + context.region, context.space_data.region_3d, self.point_down) + + mouse_line_distance = math.sqrt(((event.mouse_region_x - x2) ** 2) + + ((event.mouse_region_y - y2) ** 2)) + straight_line_distance = math.sqrt(((x2 - x) ** 2) + + ((y2 - y) ** 2)) + delta = mouse_line_distance / straight_line_distance + 0 + + v_up = Vector((x, y)) + v_down = Vector((x2, y2)) + limits_angle = v_up - v_down + + mouse_v = Vector((event.mouse_region_x, event.mouse_region_y)) + + mouse_angle = mouse_v - v_down + angle_ = mouse_angle.angle(limits_angle) + if angle_ > (math.pi / 2): + delta = 0 + return delta + + +class GizmoUpdate(GizmoProperty): + # ---update gizmo matrix + def update_gizmo_matrix(self, context): + self.align_orientation_to_user_perspective(context) + self.align_point_to_limits_point() + + def align_orientation_to_user_perspective(self, context): + rotation = context.space_data.region_3d.view_matrix.inverted().to_quaternion() + matrix = rotation.to_matrix().to_4x4() + self.matrix_basis = matrix + + def align_point_to_limits_point(self): + if self.is_up_limits_mode: + self.matrix_basis.translation = self.point_limits_up + elif self.is_down_limits_mode: + self.matrix_basis.translation = self.point_limits_down + + # ---- set prop + def set_prop_value(self, event): + if self.is_up_limits_mode: + self.set_up_value(event) + elif self.is_down_limits_mode: + self.set_down_value(event) + + def set_down_value(self, event): + value = self.get_down_limits_value(event) + self.target_set_value('down_limits', value) + if event.ctrl: + self.target_set_value('up_limits', value + self.difference_value) + elif self.is_middle_mode: + if self.origin_mode == 'LIMITS_MIDDLE': + mu = self.middle_limits_value + v = mu - (value - mu) + self.target_set_value('up_limits', v) + elif self.origin_mode == 'MIDDLE': + self.target_set_value('up_limits', 1 - value) + else: + self.target_set_value('up_limits', self.modifier_up_limits) + else: + self.target_set_value('up_limits', self.modifier_up_limits) + + def set_up_value(self, event): + value = self.get_up_limits_value(event) + self.target_set_value('up_limits', value) + if event.ctrl: + self.target_set_value('down_limits', value - self.difference_value) + elif self.is_middle_mode: + if self.origin_mode == 'LIMITS_MIDDLE': + mu = self.middle_limits_value + value = mu - (value - mu) + self.target_set_value('down_limits', value) + elif self.origin_mode == 'MIDDLE': + self.target_set_value('down_limits', 1 - value) + else: + self.target_set_value('down_limits', self.modifier_down_limits) + else: + self.target_set_value('down_limits', self.modifier_down_limits) + + # ------- + def update_header_text(self, context): + origin = self.obj_origin_property_group + mode = origin.bl_rna.properties['origin_mode'].enum_items[origin.origin_mode].name + + te = self.translate_text + t = self.translate_header_text + text = te(self.modifier.deform_method.title()) + ' ' + te(mode) + ' ' + if self.is_up_limits_mode: + value = round(self.modifier_up_limits, 3) + text += t('Up limit', value) + elif self.is_down_limits_mode: + value = round(self.modifier_down_limits, 3) + text += t('Down limit', value) + context.area.header_text_set(text) + + +class UpDownLimitsGizmo(Gizmo, GizmoUpdate): + bl_idname = 'UpDownLimitsGizmo' + bl_label = 'UpDownLimitsGizmo' + bl_target_properties = ( + {'id': 'up_limits', 'type': 'FLOAT', 'array_length': 1}, + {'id': 'down_limits', 'type': 'FLOAT', 'array_length': 1}, + ) + bl_options = {'UNDO', 'GRAB_CURSOR'} + + __slots__ = ( + 'mod', + 'up_limits', + 'down_limits', + 'draw_type', + 'mouse_dpi', + 'ctrl_mode', + 'difference_value', + 'middle_limits_value', + 'init_mouse_region_y', + 'init_mouse_region_x', + 'custom_shape', + 'int_value_up_limits', + 'int_value_down_limits', + ) + difference_value: float + middle_limits_value: float + + def setup(self): + self.mouse_dpi = 10 + self.init_setup() + + def invoke(self, context, event): + self.init_invoke(context, event) + + if self.is_up_limits_mode: + self.int_value_up_limits = up_limits = self.modifier_up_limits + self.target_set_value('up_limits', up_limits) + elif self.is_down_limits_mode: + self.int_value_down_limits = down_limits = self.modifier_down_limits + self.target_set_value('down_limits', down_limits) + return {'RUNNING_MODAL'} + + def exit(self, context, cancel): + context.area.header_text_set(None) + if cancel: + if self.is_up_limits_mode: + self.target_set_value('up_limits', self.int_value_up_limits) + elif self.is_down_limits_mode: + self.target_set_value( + 'down_limits', self.int_value_down_limits) + + def modal(self, context, event, tweak): + self.clear_point_cache() + + if self.modifier_is_use_origin_axis: + self.new_origin_empty_object() + # return {'RUNNING_MODAL'} + + self.difference_value = self.modifier_up_limits - self.modifier_down_limits + self.middle_limits_value = (self.modifier_up_limits + self.modifier_down_limits) / 2 + + try: + self.set_prop_value(event) + self.clear_point_cache() + self.update_object_origin_matrix() + except Exception: + ... + # return {'FINISHED'} + self.update_header_text(context) + return_handle = self.event_handle(event) + change_active_modifier_parameter.update_modifier_parameter() + self.update_deform_wireframe() + return return_handle + + +class UpDownLimitsGizmoGroup(GizmoGroup, GizmoGroupUtils): + bl_idname = 'OBJECT_GGT_UpDownLimitsGizmoGroup' + bl_label = 'UpDownLimitsGizmoGroup' + + @classmethod + def poll(cls, context): + return cls.simple_deform_show_gizmo_poll(context) + + def setup(self, context): + sd_name = UpDownLimitsGizmo.bl_idname + gizmo_data = [ + ('up_limits', + sd_name, + {'ctrl_mode': 'up_limits', + 'draw_type': 'Sphere_GizmoGroup_', + 'mouse_dpi': 1000, + 'color': (1.0, 0, 0), + 'alpha': 0.5, + 'color_highlight': (1.0, 1.0, 1.0), + 'alpha_highlight': 0.3, + 'use_draw_modal': True, + 'scale_basis': 0.1, + 'use_draw_value': True, }), + ('down_limits', + sd_name, + {'ctrl_mode': 'down_limits', + 'draw_type': 'Sphere_GizmoGroup_', + 'mouse_dpi': 1000, + 'color': (0, 1.0, 0), + 'alpha': 0.5, + 'color_highlight': (1.0, 1.0, 1.0), + 'alpha_highlight': 0.3, + 'use_draw_modal': True, + 'scale_basis': 0.1, + 'use_draw_value': True, }), + ] + self.generate_gizmo(gizmo_data) + + def refresh(self, context): + pro = context.object.SimpleDeformGizmo_PropertyGroup + for i in (self.down_limits, self.up_limits): + for j in ('down_limits', 'up_limits'): + i.target_set_prop(j, pro, j) diff --git a/simple_deform_helper/operators.py b/simple_deform_helper/operators.py index f933085ac..0601f9882 100644 --- a/simple_deform_helper/operators.py +++ b/simple_deform_helper/operators.py @@ -4,10 +4,10 @@ import bpy from bpy.types import Operator from bpy.props import FloatProperty, StringProperty, BoolProperty -from .utils import PublicClass +from .utils import GizmoUtils -class DeformAxisOperator(Operator, PublicClass): +class DeformAxisOperator(Operator, GizmoUtils): bl_idname = 'simple_deform_gizmo.deform_axis' bl_label = 'deform_axis' bl_description = 'deform_axis operator' @@ -26,12 +26,11 @@ class DeformAxisOperator(Operator, PublicClass): return {'RUNNING_MODAL'} def modal(self, context, event): - from .gizmo import Utils - + self.clear_point_cache() mod = context.object.modifiers.active mod.deform_axis = self.Deform_Axis - empty, con_limit_name = Utils.new_empty(context.object, mod) - is_positive = Utils.is_positive(mod.angle) + empty = self.new_origin_empty_object() + is_positive = self.is_positive(mod.angle) for limit, value in (('max_x', self.X_Value), ('min_x', self.X_Value), @@ -40,15 +39,13 @@ class DeformAxisOperator(Operator, PublicClass): ('max_z', self.Z_Value), ('min_z', self.Z_Value), ): - setattr(empty.constraints[con_limit_name], limit, value) + setattr(empty.constraints[self.G_NAME_CON_LIMIT], limit, value) if ((not is_positive) and self.Is_Positive) or (is_positive and (not self.Is_Positive)): mod.angle = mod.angle * -1 if not event.ctrl: self.pref.display_bend_axis_switch_gizmo = False - - Utils.update_bound_box(context.object) return {'FINISHED'} diff --git a/simple_deform_helper/panel.py b/simple_deform_helper/panel.py index 512017b0e..2d86f08c3 100644 --- a/simple_deform_helper/panel.py +++ b/simple_deform_helper/panel.py @@ -2,10 +2,10 @@ import bpy from bpy.types import Panel, VIEW3D_HT_tool_header -from .utils import PublicClass, Utils +from .utils import GizmoUtils -class SimpleDeformHelperToolPanel(Panel, PublicClass): +class SimpleDeformHelperToolPanel(Panel, GizmoUtils): bl_space_type = 'VIEW_3D' bl_region_type = 'UI' bl_category = 'Tool' @@ -16,7 +16,7 @@ class SimpleDeformHelperToolPanel(Panel, PublicClass): @classmethod def poll(cls, context): - return Utils.simple_deform_poll(context) + return cls.simple_deform_public_poll(context) def draw(self, context): cls = SimpleDeformHelperToolPanel @@ -34,13 +34,20 @@ class SimpleDeformHelperToolPanel(Panel, PublicClass): 'origin_mode', text='') layout.prop(pref, - 'modifiers_limits_tolerance', + 'update_deform_wireframe', + icon='MOD_WIREFRAME', text='') - - if mod.deform_method == 'BEND': + layout.prop(pref, + 'show_set_axis_button', + icon='EMPTY_AXIS', + text='') + if pref.modifier_deform_method_is_bend: layout.prop(pref, 'display_bend_axis_switch_gizmo', toggle=1) + layout.prop(pref, + 'modifiers_limits_tolerance', + text='') class_list = ( diff --git a/simple_deform_helper/preferences.py b/simple_deform_helper/preferences.py index 90835854e..473566b50 100644 --- a/simple_deform_helper/preferences.py +++ b/simple_deform_helper/preferences.py @@ -1,6 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-or-later -import os import bpy from bpy.props import (FloatProperty, PointerProperty, @@ -12,12 +11,11 @@ from bpy.types import ( PropertyGroup, ) -from .data import G_ADDON_NAME -from .utils import PublicClass, Utils +from .utils import GizmoUtils -class SimpleDeformGizmoAddonPreferences(AddonPreferences, PublicClass): - bl_idname = G_ADDON_NAME +class SimpleDeformGizmoAddonPreferences(AddonPreferences, GizmoUtils): + bl_idname = GizmoUtils.G_ADDON_NAME deform_wireframe_color: FloatVectorProperty( name='Deform Wireframe', @@ -29,7 +27,7 @@ class SimpleDeformGizmoAddonPreferences(AddonPreferences, PublicClass): bound_box_color: FloatVectorProperty( name='Bound Box', description='Draw Bound Box Color', - default=(1, 0, 0, 0.1), + default=(1, 0, 0, 0.5), soft_max=1, soft_min=0, size=4, @@ -50,20 +48,38 @@ class SimpleDeformGizmoAddonPreferences(AddonPreferences, PublicClass): min=0.0001 ) display_bend_axis_switch_gizmo: BoolProperty( - name='Show Toggle Axis Gizmo', + name='Show Toggle Bend Axis Gizmo', default=False, options={'SKIP_SAVE'}) + update_deform_wireframe: BoolProperty( + name='Show Deform Wireframe', + default=False) + + show_set_axis_button: BoolProperty( + name='Show Set Axis Button', + default=False) + def draw(self, context): col = self.layout.column() + box = col.box() + for text in ("You can press the following shortcut keys when dragging values", + " Wheel: Switch Origin Ctrl Mode", + " X、Y、Z: Switch Modifier Deform Axis", + " W: Switch Deform Wireframe Show", + " A: Switch To Select Bend Axis Mode(deform_method=='BEND')",): + box.label(text=self.translate_text(text)) + col.prop(self, 'deform_wireframe_color') col.prop(self, 'bound_box_color') col.prop(self, 'limits_bound_box_color') col.prop(self, 'modifiers_limits_tolerance') col.prop(self, 'display_bend_axis_switch_gizmo') + col.prop(self, 'update_deform_wireframe', icon='MOD_WIREFRAME', ) + col.prop(self, 'show_set_axis_button', icon='EMPTY_AXIS', ) def draw_header_tool_settings(self, context): - if Utils.simple_deform_poll(context): + if GizmoUtils.simple_deform_public_poll(context): row = self.layout.row() obj = context.object mod = obj.modifiers.active @@ -80,13 +96,10 @@ class SimpleDeformGizmoAddonPreferences(AddonPreferences, PublicClass): row.prop(mod, show_type) -class SimpleDeformGizmoObjectPropertyGroup(PropertyGroup): - +class SimpleDeformGizmoObjectPropertyGroup(PropertyGroup, GizmoUtils): def _limits_up(self, context): - mod = context.object.modifiers - if mod and (mod.active.type == 'SIMPLE_DEFORM'): - mod = mod.active - mod.limits[1] = self.up_limits + if self.active_modifier_is_simple_deform: + self.modifier.limits[1] = self.up_limits up_limits: FloatProperty(name='up', description='UP Limits(Red)', @@ -96,10 +109,8 @@ class SimpleDeformGizmoObjectPropertyGroup(PropertyGroup): min=0) def _limits_down(self, context): - mod = context.object.modifiers - if mod and (mod.active.type == 'SIMPLE_DEFORM'): - mod = mod.active - mod.limits[0] = self.down_limits + if self.active_modifier_is_simple_deform: + self.modifier.limits[0] = self.down_limits down_limits: FloatProperty(name='down', description='Lower limit(Green)', @@ -144,7 +155,7 @@ register_class, unregister_class = bpy.utils.register_classes_factory(class_list def register(): register_class() - PublicClass.pref_().display_bend_axis_switch_gizmo = False + GizmoUtils.pref_().display_bend_axis_switch_gizmo = False bpy.types.Object.SimpleDeformGizmo_PropertyGroup = PointerProperty( type=SimpleDeformGizmoObjectPropertyGroup, name='SimpleDeformGizmo_PropertyGroup') diff --git a/simple_deform_helper/translate.py b/simple_deform_helper/translate.py index 72e94e2a5..d2701d272 100644 --- a/simple_deform_helper/translate.py +++ b/simple_deform_helper/translate.py @@ -11,12 +11,28 @@ def origin_text(a, b): translations_dict = { "zh_CN": { ("上下文", "原文"): "翻译文字", - ("*", "Show Toggle Axis Gizmo"): "显示切换轴向Gizmo", + ("*", "Show Toggle Bend Axis Gizmo"): "显示切换弯曲轴向Gizmo", + + ("*", "You can press the following shortcut keys when dragging values"): + "拖动值时可以按以下快捷键", + ("*", " Wheel: Switch Origin Ctrl Mode"): + " 滚轮: 切换原点控制模式", + ("*", " X、Y、Z: Switch Modifier Deform Axis"): + " X、Y、Z: 切换修改器型变轴", + ("*", " W: Switch Deform Wireframe Show"): + " W: 切换形变线框显示", + ("*", + " A: Switch To Select Bend Axis Mode(deform_method=='BEND')"): + " A: 切换到选择弯曲轴模式(形变方法='弯曲')", + ("*", "Show Set Axis Button"): "显示设置轴向Gizmo", ("*", "Follow Upper Limit(Red)"): "跟随上限(红色)", ("*", "Follow Lower Limit(Green)"): "跟随下限(绿色)", ("*", "Lower limit(Green)"): "下限(绿色)", ("*", "UP Limits(Red)"): "上限(红色)", + ("*", "Down limit"): "下限", + ("*", "Up limit"): "上限", + ("*", "Show Deform Wireframe"): "显示形变线框", ("*", "Minimum value between upper and lower limits"): "上限与下限之间的最小值", ("*", "Upper and lower limit tolerance"): "上下限容差", ("*", "Draw Upper and lower limit Bound Box Color"): "绘制网格上限下限边界线框的颜色", diff --git a/simple_deform_helper/update.py b/simple_deform_helper/update.py index fb61082be..d6fb78fca 100644 --- a/simple_deform_helper/update.py +++ b/simple_deform_helper/update.py @@ -1,25 +1,201 @@ # SPDX-License-Identifier: GPL-2.0-or-later +from functools import cache import bpy -from bpy.app.handlers import depsgraph_update_post, persistent + +from .utils import GizmoUpdate + +gizmo = GizmoUpdate() + +"""depsgraph_update_post cannot listen to users modifying modifier parameters +Use timers to watch and use cache +""" -@persistent -def remove_not_use_empty(scene, dep): - """循环场景内的所有物体,找出没用的空物体并删掉 - """ - remove_name: str = "ViewSimpleDeformGizmo__Empty_" - context = bpy.context - for obj in context.scene.objects: - is_empty = obj.type == "EMPTY" - not_parent = not obj.parent - if remove_name in obj.name and not_parent and is_empty: - bpy.data.objects.remove(obj) # remove object +class update_public: + _events_func_list = {} + run_time = 0.2 + + @classmethod + def timers_update_poll(cls) -> bool: + return True + + @classmethod + @cache + def update_poll(cls) -> bool: + return True + + @classmethod + def _update_func_call_timer(cls): + if cls.timers_update_poll(): + for c, func_list in cls._events_func_list.items(): + if func_list and c.update_poll(): + for func in func_list: + func() + cls.clear_cache_events() + return cls.run_time + + @classmethod + def clear_cache_events(cls): + for cl in cls._events_func_list.keys(): + if getattr(cl, 'clear_cache', False): + cl.clear_cache() + + @classmethod + def clear_cache(cls): + cls.update_poll.cache_clear() + + @classmethod + def append(cls, item): + if cls not in cls._events_func_list: + cls._events_func_list[cls] = [] + cls._events_func_list[cls].append(item) + + @classmethod + def remove(cls, item): + if item in cls._events_func_list[cls]: + cls._events_func_list[cls].remove(item) + + # --------------- reg and unreg + @classmethod + def register(cls): + from bpy.app import timers + func = cls._update_func_call_timer + if not timers.is_registered(func): + timers.register(func, persistent=True) + else: + print('cls timers is registered', cls) + + @classmethod + def unregister(cls): + from bpy.app import timers + func = cls._update_func_call_timer + if timers.is_registered(func): + timers.unregister(func) + else: + print('cls timers is not registered', cls) + cls._events_func_list.clear() + + +class simple_update(update_public, GizmoUpdate): + tmp_save_data = {} + + @classmethod + def timers_update_poll(cls): + obj = bpy.context.object + if not cls.context_mode_is_object(): + ... + elif not obj: + ... + elif not cls.obj_type_is_mesh_or_lattice(obj): + ... + elif cls.mod_is_simple_deform_type(obj.modifiers.active): + return True + return False + + +class change_active_object(simple_update): + @classmethod + @cache + def update_poll(cls): + return cls.is_change_active_object() + + @classmethod + def is_change_active_object(cls, change_data=True): + import bpy + obj = bpy.context.object + name = obj.name + key = 'active_object' + if key not in cls.tmp_save_data: + if change_data: + cls.tmp_save_data[key] = name + return True + + elif cls.tmp_save_data[key] != name: + if change_data: + cls.tmp_save_data[key] = name + return True + return False + + +class change_active_simple_deform_modifier(simple_update): + + @classmethod + @cache + def update_poll(cls): + return cls.is_change_active_simple_deform() + + @classmethod + def is_change_active_simple_deform(cls) -> bool: + import bpy + obj = bpy.context.object + modifiers = cls.get_modifiers_data(obj) + + def update(): + cls.tmp_save_data['modifiers'] = modifiers + + if change_active_object.update_poll(): + update() + elif 'modifiers' not in cls.tmp_save_data: + update() + elif cls.tmp_save_data['modifiers'] != modifiers: + update() + return True + return False + + @classmethod + def get_modifiers_data(cls, obj): + return {'obj': obj.name, + 'active_modifier': getattr(obj.modifiers.active, 'name', None), + 'modifiers': list(i.name for i in obj.modifiers)} + + +class change_active_modifier_parameter(simple_update): + key = 'active_modifier_parameter' + + @classmethod + @cache + def update_poll(cls): + return gizmo.active_modifier_is_simple_deform and cls.is_change_active_simple_parameter() + + @classmethod + def update_modifier_parameter(cls, modifier_parameter=None): + """Run this function when the gizmo is updated to avoid duplicate updates + """ + if not modifier_parameter: + modifier_parameter = cls.get_modifiers_parameter(gizmo.modifier) + cls.tmp_save_data[cls.key] = modifier_parameter + + @classmethod + def change_modifier_parameter(cls) -> bool: + mod_data = cls.get_modifiers_parameter(gizmo.modifier) + return cls.key in cls.tmp_save_data and cls.tmp_save_data[cls.key] == mod_data + + @classmethod + def is_change_active_simple_parameter(cls): + parameter = cls.get_modifiers_parameter(gizmo.modifier) + if change_active_object.update_poll(): + cls.update_modifier_parameter(parameter) + elif change_active_simple_deform_modifier.update_poll(): + cls.update_modifier_parameter(parameter) + elif cls.key not in cls.tmp_save_data: + cls.update_modifier_parameter(parameter) + elif cls.tmp_save_data[cls.key] != parameter: + cls.update_modifier_parameter(parameter) + return True + return False def register(): - depsgraph_update_post.append(remove_not_use_empty) + simple_update.register() + + def p(): + gizmo.update_multiple_modifiers_data() + + change_active_object.append(p) + change_active_modifier_parameter.append(p) + change_active_simple_deform_modifier.append(p) def unregister(): - depsgraph_update_post.remove(remove_not_use_empty) + simple_update.unregister() diff --git a/simple_deform_helper/utils.py b/simple_deform_helper/utils.py index ae9df8ca8..f7f9092ff 100644 --- a/simple_deform_helper/utils.py +++ b/simple_deform_helper/utils.py @@ -2,19 +2,101 @@ import math import uuid +from functools import cache +from os.path import dirname, basename, realpath import bpy import numpy as np from bpy.types import AddonPreferences -from mathutils import Vector, Matrix - -from .data import G_ADDON_NAME, G_NAME, G_INDICES, G_MODIFIERS_PROPERTY, G_CON_LIMIT_NAME, Data +from mathutils import Vector, Matrix, Euler -class PublicClass: +class PublicData: + """Public data class, where all fixed data will be placed +Classify each different type of data separately and cache it to avoid getting stuck due to excessive update frequency + """ + G_CustomShape = {} # + G_HandleData = {} # Save draw Handle + + G_DeformDrawData = {} # Save Deform Vertex And Indices,Update data only when updating deformation boxes + + G_MultipleModifiersBoundData = {} + + G_INDICES = ( + (0, 1), (0, 2), (1, 3), (2, 3), + (4, 5), (4, 6), (5, 7), (6, 7), + (0, 4), (1, 5), (2, 6), (3, 7)) # The order in which the 8 points of the bounding box are drawn + G_NAME = 'ViewSimpleDeformGizmo_' # Temporary use files prefix + + G_DEFORM_MESH_NAME = G_NAME + 'DeformMesh' + G_TMP_MULTIPLE_MODIFIERS_MESH = 'TMP_' + G_NAME + 'MultipleModifiersMesh' + G_SUB_LEVELS = 7 + + G_NAME_EMPTY_AXIS = G_NAME + '_Empty_' + G_NAME_CON_LIMIT = G_NAME + 'ConstraintsLimitRotation' # constraints name + G_NAME_CON_COPY_ROTATION = G_NAME + 'ConstraintsCopyRotation' + G_ADDON_NAME = basename(dirname(realpath(__file__))) # "simple_deform_helper" + + G_MODIFIERS_PROPERTY = [ # Copy modifier data + 'angle', + 'deform_axis', + 'deform_method', + 'factor', + 'invert_vertex_group', + 'limits', # bpy.types.bpy_prop_array + 'lock_x', + 'lock_y', + 'lock_z', + 'origin', + 'show_expanded', + 'show_in_editmode', + 'vertex_group', + ] + + @classmethod + def load_gizmo_data(cls) -> None: + import json + import os + json_path = os.path.join(os.path.dirname(__file__), "gizmo.json") + with open(json_path, "r") as file: + cls.G_CustomShape = json.load(file) + + @staticmethod + def from_mesh_get_triangle_face_co(mesh: 'bpy.types.Mesh') -> list: + """ + :param mesh: input mesh read vertex + :type mesh: bpy.data.meshes + :return list: vertex coordinate list[[cox,coy,coz],[cox,coy,coz]...] + """ + import bmesh + bm = bmesh.new() + bm.from_mesh(mesh) + bm.faces.ensure_lookup_table() + bm.verts.ensure_lookup_table() + bmesh.ops.triangulate(bm, faces=bm.faces) + co_list = [list(float(format(j, ".3f")) for j in vert.co) for face in bm.faces for vert in face.verts] + bm.free() + return co_list + + @classmethod + def from_selected_obj_generate_json(cls): + """Export selected object vertex data as gizmo custom paint data + The output file should be in the blender folder + gizmo.json + """ + import json + data = {} + for obj in bpy.context.selected_objects: + data[obj.name] = cls.from_mesh_get_triangle_face_co(obj.data) + print(data) + with open('gizmo.json', 'w+') as f: + f.write(json.dumps(data)) + + +class PublicClass(PublicData): @staticmethod def pref_() -> "AddonPreferences": - return bpy.context.preferences.addons[G_ADDON_NAME].preferences + return bpy.context.preferences.addons[PublicData.G_ADDON_NAME].preferences @property def pref(self=None) -> 'AddonPreferences': @@ -24,37 +106,126 @@ class PublicClass: return PublicClass.pref_() -class Utils(Data): +class PublicPoll(PublicClass): @classmethod - def set_reduce(cls, list_a, list_b, operation_type='-') -> list: - """ - :param list_a: 列表a - :type list_a: list or set - :param list_b: 列表b - :type list_b:list or set - :param operation_type :运算方法Enumerator in ['+','-','*','/']. - :type operation_type :str - :return list: 反回运算后的列表 - """ - if operation_type == '-': - return [list_a[i] - list_b[i] for i in range(0, len(list_a))] - elif operation_type == '+': - return [list_a[i] + list_b[i] for i in range(0, len(list_a))] - elif operation_type == '/': - return [list_a[i] / list_b[i] for i in range(0, len(list_a))] - elif operation_type == '*': - return [list_a[i] * list_b[i] for i in range(0, len(list_a))] + def context_mode_is_object(cls) -> bool: + return bpy.context.mode == 'OBJECT' @classmethod - def value_limit(cls, value, max_value=1, min_value=0) -> float: + def simple_deform_modifier_is_simple(cls, context): """ - :param value: 输入值 - :type value: float - :param max_value: 允许的最大值 - :type max_value: float - :param min_value: 允许的最小值 - :type min_value: float - :return float: 反回小于最大值及大于最小值的浮点数 + Active Object in ('MESH', 'LATTICE') + Active Modifier Type Is 'SIMPLE_DEFORM' and show_viewport + :param context:bpy.types.Object + :return: + """ + + obj = context.object + if not obj: + return False + mod = obj.modifiers.active + if not mod: + return False + + available_obj_type = cls.obj_type_is_mesh_or_lattice(obj) + is_available_obj = cls.mod_is_simple_deform_type(mod) and available_obj_type + is_obj_mode = cls.context_mode_is_object() + show_mod = mod.show_viewport + not_is_self_mesh = obj.name != cls.G_NAME + return is_available_obj and is_obj_mode and show_mod and not_is_self_mesh + + @classmethod + def simple_deform_public_poll(cls, context: 'bpy.types.context') -> bool: + """Public poll + In 3D View + return True + """ + space = context.space_data + if not space: + return False + show_gizmo = space.show_gizmo if space.type == 'VIEW_3D' else True + obj = cls.simple_deform_modifier_is_simple(context) + return obj and show_gizmo + + @classmethod + def _simple_deform_modifier_is_bend_poll(cls, context): + """ + Public poll + active modifier deform_method =='BEND' + """ + simple = cls.simple_deform_public_poll(context) + is_bend = simple and (context.object.modifiers.active.deform_method == 'BEND') + return simple and is_bend + + @classmethod + def simple_deform_show_bend_axis_witch_poll(cls, context): + """ + Show D + """ + switch_axis = cls.pref_().display_bend_axis_switch_gizmo + bend = cls._simple_deform_modifier_is_bend_poll(context) + return switch_axis and bend + + @classmethod + def simple_deform_show_gizmo_poll(cls, context): + poll = cls.simple_deform_public_poll(context) + not_switch = (not cls.simple_deform_show_bend_axis_witch_poll(context)) + return poll and not_switch + + +class PublicTranslate(PublicPoll): + @classmethod + def translate_text(cls, text): + return bpy.app.translations.pgettext(text) + + @classmethod + def translate_header_text(cls, mode, value): + return cls.translate_text(mode) + ':{}'.format(value) + + +class GizmoClassMethod(PublicTranslate): + + @classmethod + def get_depsgraph(cls, obj: 'bpy.types.Object'): + """ + @param obj: dep obj + @return: If there is no input obj, reverse the active object evaluated + """ + context = bpy.context + if obj is None: + obj = context.object + dep = context.evaluated_depsgraph_get() + return obj.evaluated_get(dep) + + @classmethod + def get_vector_axis(cls, mod): + axis = mod.deform_axis + if 'BEND' == mod.deform_method: + vector_axis = Vector((0, 0, 1)) if axis in ( + 'Y', 'X') else Vector((1, 0, 0)) + else: + vector = (Vector((1, 0, 0)) if ( + axis == 'X') else Vector((0, 1, 0))) + vector_axis = Vector((0, 0, 1)) if ( + axis == 'Z') else vector + return vector_axis + + @classmethod + def get_modifiers_parameter(cls, modifier): + prop = bpy.types.bpy_prop_array + return list( + getattr(modifier, i)[:] if type(getattr(modifier, i)) == prop else getattr(modifier, i) + for i in cls.G_MODIFIERS_PROPERTY + ) + + @classmethod + def value_limit(cls, value, max_value=1, min_value=0): + """ + @param value: limit value + @param max_value: Maximum allowed + @param min_value: Minimum allowed + @return: If the input value is greater than the maximum value or less than the minimum value + it will be limited to the maximum or minimum value """ if value > max_value: return max_value @@ -71,92 +242,15 @@ class Utils(Data): return number == abs(number) @classmethod - def get_depsgraph(cls, obj: 'bpy.context.object'): - """ - :param obj: 要被评估的物体 - :type obj: bpy.types.Object - :return bpy.types.Object: 反回评估后的物体,计算应用修改器和实例化的数据 - 如果未输入物休将会评估活动物体 - """ + def link_obj_to_active_collection(cls, obj: 'bpy.types.Object'): context = bpy.context - if obj is None: - obj = context.object - depsgraph = context.evaluated_depsgraph_get() - return obj.evaluated_get(depsgraph) - - @classmethod - def link_active_collection(cls, - obj: 'bpy.context.object') -> \ - 'bpy.context.view_layer.active_layer_collection.collection.objects': - context = bpy.context - if obj.name not in context.view_layer.active_layer_collection.collection.objects: - context.view_layer.active_layer_collection.collection.objects.link( + objects = context.view_layer.active_layer_collection.collection.objects + if obj.name not in objects: + objects.link( obj) - return context.view_layer.active_layer_collection.collection.objects @classmethod - def properties_is_modifier(cls) -> bool: - """ - 反回活动窗口内是否有修改器属性面板被打开,如果打开则反回True else False - """ - for area in bpy.context.screen.areas: - if area.type == 'PROPERTIES': - for space in area.spaces: - if space.type == 'PROPERTIES' and space.context == 'MODIFIER': - return True - return False - - @classmethod - def simple_deform_poll(cls, context: 'bpy.context') -> bool: - """Public poll""" - obj = context.object - if not obj: - return False - - mod = obj.modifiers.active - if not mod: - return False - - space = context.space_data - show_gizmo = space.show_gizmo if space.type == 'VIEW_3D' else True - - available_obj_type = obj and (obj.type in ('MESH', 'LATTICE')) - available_modifiers_type = mod and (mod.type == 'SIMPLE_DEFORM') - is_available_obj = available_modifiers_type and available_obj_type - is_obj_mode = context.mode == 'OBJECT' - show_mod = mod.show_viewport - return is_available_obj and is_obj_mode and show_gizmo and show_mod - - @classmethod - def bound_box_to_list(cls, obj: 'bpy.context.object') -> tuple: - """ - :param obj:输入一个物体,反回物体的边界框列表 - :type obj:bpy.types.Object - :return tuple: - """ - return tuple(i[:] for i in obj.bound_box) - - @classmethod - def get_origin_bounds(cls, obj: 'bpy.context.object') -> list: - modifiers_list = {} - for mod in obj.modifiers: - if (mod == obj.modifiers.active) or (modifiers_list != {}): - modifiers_list[mod] = (mod.show_render, mod.show_viewport) - mod.show_viewport = False - mod.show_render = False - matrix_obj = obj.matrix_world.copy() - obj.matrix_world.zero() - obj.scale = (1, 1, 1) - bound = cls.bound_box_to_list(obj) - obj.matrix_world = matrix_obj - for mod in modifiers_list: - show_render, show_viewport = modifiers_list[mod] - mod.show_render = show_render - mod.show_viewport = show_viewport - return list(bound) - - @classmethod - def get_mesh_max_min_co(cls, obj: 'bpy.context.object') -> tuple: + def get_mesh_max_min_co(cls, obj: 'bpy.context.object') -> '[Vector,Vector]': if obj.type == 'MESH': ver_len = obj.data.vertices.__len__() list_vertices = np.zeros(ver_len * 3, dtype=np.float32) @@ -167,47 +261,12 @@ class Utils(Data): list_vertices = np.zeros(ver_len * 3, dtype=np.float32) obj.data.points.foreach_get('co', list_vertices) list_vertices = list_vertices.reshape(ver_len, 3) - return tuple(list_vertices.min(axis=0)), tuple(list_vertices.max(axis=0)) + return Vector(list_vertices.min(axis=0)).freeze(), Vector(list_vertices.max(axis=0)).freeze() @classmethod def matrix_calculation(cls, mat: 'Matrix', calculation_list: 'list') -> list: return [mat @ Vector(i) for i in calculation_list] - @classmethod - def get_origin_property_group(cls, mod, ob): - if mod.origin: - return mod.origin.SimpleDeformGizmo_PropertyGroup - else: - return ob.SimpleDeformGizmo_PropertyGroup - - @classmethod - def set_empty_obj_matrix(cls, origin_mode, empty_object, up_, down_, up, down): - tow = (2, 2, 2) - if origin_mode == 'UP_LIMITS': - empty_object.matrix_world.translation = Vector(up_) - elif origin_mode == 'DOWN_LIMITS': - empty_object.matrix_world.translation = Vector( - down_) - elif origin_mode == 'LIMITS_MIDDLE': - empty_object.matrix_world.translation = cls.set_reduce( - cls.set_reduce(up_, down_, '+'), tow, '/') - elif origin_mode == 'MIDDLE': - empty_object.matrix_world.translation = cls.set_reduce( - cls.set_reduce(up, down, '+'), tow, '/') - - @classmethod - def get_vector_axis(cls, mod): - axis = mod.deform_axis - if 'BEND' == mod.deform_method: - vector_axis = Vector((0, 0, 1)) if axis in ( - 'Y', 'X') else Vector((1, 0, 0)) - else: - vector = (Vector((1, 0, 0)) if ( - axis == 'X') else Vector((0, 1, 0))) - vector_axis = Vector((0, 0, 1)) if ( - axis == 'Z') else vector - return vector_axis - @classmethod def point_to_angle(cls, i, j, f, axis_): if i == j: @@ -226,123 +285,465 @@ class Utils(Data): return angle @classmethod - def get_up_down(cls, mod, axis, top, bottom, left, right, front, back): - if 'BEND' == mod.deform_method: - if axis in ('X', 'Y'): - return top, bottom - elif axis == 'Z': - return right, left - else: - if axis == 'X': - return right, left - elif axis == 'Y': - return back, front - elif axis == 'Z': - return top, bottom + def co_to_direction(cls, mat, data): + (min_x, min_y, min_z), (max_x, max_y, + max_z) = data + a = mat @ Vector((max_x, max_y, max_z)) + b = mat @ Vector((max_x, min_y, min_z)) + c = mat @ Vector((min_x, max_y, min_z)) + d = mat @ Vector((min_x, min_y, max_z)) + point_list = ((a, d), + (c, b), + (c, d), + (a, b), + (d, b), + (c, a),) + + return list((aa + bb) / 2 for (aa, bb) in point_list) @classmethod - def get_limits_pos(cls, mod, data): - top, bottom, left, right, front, back = data - up_limits = mod.limits[1] - down_limits = mod.limits[0] - axis = mod.deform_axis + def tow_co_to_coordinate(cls, data): + ((min_x, min_y, min_z), (max_x, max_y, max_z)) = data + return ( + Vector((max_x, min_y, min_z)), + Vector((min_x, min_y, min_z)), + Vector((max_x, max_y, min_z)), + Vector((min_x, max_y, min_z)), + Vector((max_x, min_y, max_z)), + Vector((min_x, min_y, max_z)), + Vector((max_x, max_y, max_z)), + Vector((min_x, max_y, max_z)) + ) - if mod.origin: - vector_axis = cls.get_vector_axis(mod) - origin_mat = mod.origin.matrix_world.to_3x3() - axis_ = origin_mat @ vector_axis + @classmethod + def mod_is_simple_deform_type(cls, mod): + return mod and mod.type == 'SIMPLE_DEFORM' + + @classmethod + def obj_type_is_mesh_or_lattice(cls, obj: 'bpy.types.Object'): + return obj and (obj.type in ('MESH', 'LATTICE')) + + @classmethod + def from_vertices_new_mesh(cls, name, vertices): + new_mesh = bpy.data.meshes.new(name) + new_mesh.from_pydata(vertices, cls.G_INDICES, []) + new_mesh.update() + return new_mesh + + @classmethod + def copy_modifier_parameter(cls, old_mod, new_mod): + for prop_name in cls.G_MODIFIERS_PROPERTY: + origin_value = getattr(old_mod, prop_name, None) + is_array_prop = type(origin_value) == bpy.types.bpy_prop_array + value = origin_value[:] if is_array_prop else origin_value + setattr(new_mod, prop_name, value) + + +class PublicProperty(GizmoClassMethod): + + def __from_up_down_point_get_limits_point(self, up_point, down_point): + + def ex(a): + return down_point + ((up_point - down_point) * Vector((a, a, a))) + + up_limits = ex(self.modifier_up_limits) + down_limits = ex(self.modifier_down_limits) + return up_limits, down_limits + + @cache + def _get_limits_point_and_bound_box_co(self): + top, bottom, left, right, front, back = self.modifier_bound_box_pos + mod = self.modifier + g_l = self.__from_up_down_point_get_limits_point + origin = self.modifier.origin + if origin: + vector_axis = self.get_vector_axis(mod) + matrix = self.modifier.origin.matrix_local + origin_mat = matrix.to_3x3() + axis = origin_mat @ vector_axis point_lit = [[top, bottom], [left, right], [front, back]] - for f in range(point_lit.__len__()): i = point_lit[f][0] j = point_lit[f][1] - angle = cls.point_to_angle(i, j, f, axis_) + angle = self.point_to_angle(i, j, f, axis) if abs(angle - 180) < 0.00001: - up, down = j, i + up_point, down_point = j, i + up_limits, down_limits = g_l(j, i) + point_lit[f][1], point_lit[f][0] = up_limits, down_limits elif abs(angle) < 0.00001: - up, down = i, j + up_point, down_point = i, j + up_limits, down_limits = g_l(i, j) + point_lit[f][0], point_lit[f][1] = up_limits, down_limits + [[top, bottom], [left, right], [front, back]] = point_lit else: - up, down = cls.get_up_down(mod, axis, top, bottom, - left, right, front, back) + axis = self.modifier_deform_axis + if 'BEND' == self.modifier.deform_method: + if axis in ('X', 'Y'): + up_point, down_point = top, bottom + top, bottom = up_limits, down_limits = g_l(top, bottom) + elif axis == 'Z': + up_point, down_point = right, left + right, left = up_limits, down_limits = g_l(right, left) + else: + if axis == 'X': + up_point, down_point = right, left + right, left = up_limits, down_limits = g_l(right, left) + elif axis == 'Y': + up_point, down_point = back, front + back, front = up_limits, down_limits = g_l(back, front) - ex = lambda a: cls.set_reduce(down, cls.set_reduce(cls.set_reduce( - up, down, '-'), (a, a, a), '*'), '+') + elif axis == 'Z': + up_point, down_point = top, bottom + top, bottom = up_limits, down_limits = g_l(top, bottom) - up_ = ex(up_limits) - down_ = ex(down_limits) - return (up, down), (up_, down_) + points = (up_point, down_point, up_limits, down_limits) + each_point = ((right[0], back[1], top[2]), (left[0], front[1], bottom[2],)) + return points, self.tow_co_to_coordinate(each_point) + + # ---------------------- + @cache + def _each_face_pos(self, mat, co): + return self.co_to_direction(mat, co) @classmethod - def update_bound_box(cls, object): + def clear_cache(cls): + cls.clear_point_cache() + cls.clear_modifiers_data() + @classmethod + def clear_point_cache(cls): + cls._get_limits_point_and_bound_box_co.cache_clear() + + @classmethod + def clear_modifiers_data(cls): + cls.G_MultipleModifiersBoundData.clear() + + @classmethod + def clear_deform_data(cls): + cls.G_DeformDrawData.clear() + + # --------------- Cache Data ---------------------- + @property + def modifier_bound_co(self): + def get_bound_co_data(): + key = 'self.modifier.name' + if key not in self.G_MultipleModifiersBoundData: + self.G_MultipleModifiersBoundData[key] = self.get_mesh_max_min_co(self.obj) + return self.G_MultipleModifiersBoundData[key] + + return self.G_MultipleModifiersBoundData.get(self.modifier.name, get_bound_co_data()) + + @property + def modifier_bound_box_pos(self): + matrix = Matrix() + matrix.freeze() + return self.co_to_direction(matrix, self.modifier_bound_co) + + @property + def modifier_limits_point(self): + points, _ = self._get_limits_point_and_bound_box_co() + return self.matrix_calculation(self.obj_matrix_world, points) + + @property + def modifier_limits_bound_box(self): + _, bound = self._get_limits_point_and_bound_box_co() + return self.matrix_calculation(self.obj_matrix_world, bound) + + @property + def modifier_origin_is_available(self): + try: + self._get_limits_point_and_bound_box_co() + return True + except UnboundLocalError: + self.clear_point_cache() + return False + + # --------------- Compute Data ---------------------- + @property + def obj(self): + return bpy.context.object + + @property + def obj_matrix_world(self): + if self.obj: + mat = self.obj.matrix_world.copy() + mat.freeze() + return mat + mat = Matrix() + mat.freeze() + return mat + + @property + def modifier(self): + obj = self.obj + if not obj: + return + return obj.modifiers.active + + @property + def modifier_deform_axis(self): + mod = self.modifier + if mod: + return mod.deform_axis + + @property + def modifier_angle(self): + mod = self.modifier + if mod: + return mod.angle + + @property + def modifier_is_use_angle_value(self): + if self.active_modifier_is_simple_deform: + return self.modifier.deform_method in ('TWIST', 'BEND') + + @property + def modifier_deform_method_is_bend(self): + if self.active_modifier_is_simple_deform: + return self.modifier.deform_method == 'BEND' + + @property + def modifier_up_limits(self): + if self.modifier: + return self.modifier.limits[1] + + @property + def modifier_down_limits(self): + if self.modifier: + return self.modifier.limits[0] + + @property + def active_modifier_is_simple_deform(self): + return self.mod_is_simple_deform_type(self.modifier) + + # ----- point + @property + def point_up(self): + return self.modifier_limits_point[0] + + @property + def point_down(self): + return self.modifier_limits_point[1] + + @property + def point_limits_up(self): + return self.modifier_limits_point[2] + + @property + def point_limits_down(self): + return self.modifier_limits_point[3] + + # ------ + + @property + def obj_origin_property_group(self): + mod = self.modifier + if mod.origin: + return mod.origin.SimpleDeformGizmo_PropertyGroup + else: + return self.obj.SimpleDeformGizmo_PropertyGroup + + @property + def origin_mode(self): + return self.obj_origin_property_group.origin_mode + + @property + def is_limits_middle_mode(self): + return self.origin_mode == 'LIMITS_MIDDLE' + + @property + def is_middle_mode(self): + return self.origin_mode in ('LIMITS_MIDDLE', 'MIDDLE') + + @property + def modifier_is_use_origin_axis(self): + return self.obj_origin_property_group.origin_mode != 'NOT' + + @property + def modifier_is_have_origin(self): + return self.modifier_is_use_origin_axis and self.modifier.origin + + +class GizmoUpdate(PublicProperty): + def fix_origin_parent_and_angle(self): + obj = self.obj + mod = self.modifier + if not obj or not mod or not getattr(mod, 'origin', False): + return + + origin = mod.origin + if not origin: + return + + if origin.parent != obj: + origin.parent = obj + origin.rotation_euler.zero() + if not self.modifier_origin_is_available: + origin.location.zero() + origin.scale = 1, 1, 1 + + def new_origin_empty_object(self): + mod = self.modifier + obj = self.obj + origin = mod.origin + if not origin: + new_name = self.G_NAME_EMPTY_AXIS + str(uuid.uuid4()) + origin_object = bpy.data.objects.new(new_name, None) + self.link_obj_to_active_collection(origin_object) + origin_object.hide_set(True) + origin_object.empty_display_size = min(obj.dimensions) + mod.origin = origin_object + else: + origin_object = mod.origin + origin_object.hide_viewport = False + if origin_object == obj: + return + # add constraints + name = self.G_NAME_CON_LIMIT + if origin_object.constraints.keys().__len__() > 2: + origin_object.constraints.clear() + if name in origin_object.constraints.keys(): + limit_constraints = origin.constraints.get(name) + else: + limit_constraints = origin_object.constraints.new( + 'LIMIT_ROTATION') + limit_constraints.name = name + limit_constraints.owner_space = 'WORLD' + limit_constraints.space_object = obj + limit_constraints.use_transform_limit = True + limit_constraints.use_limit_x = True + limit_constraints.use_limit_y = True + limit_constraints.use_limit_z = True + con_copy_name = self.G_NAME_CON_COPY_ROTATION + if con_copy_name in origin_object.constraints.keys(): + copy_constraints = origin.constraints.get(con_copy_name) + else: + copy_constraints = origin_object.constraints.new( + 'COPY_ROTATION') + copy_constraints.name = con_copy_name + copy_constraints.target = obj + copy_constraints.mix_mode = 'BEFORE' + copy_constraints.target_space = 'WORLD' + copy_constraints.owner_space = 'WORLD' + origin_mode = self.obj.SimpleDeformGizmo_PropertyGroup.origin_mode + origin_object.SimpleDeformGizmo_PropertyGroup.origin_mode = origin_mode + self.fix_origin_parent_and_angle() + return origin_object + + def update_object_origin_matrix(self): + if self.modifier_is_have_origin: + origin_mode = self.origin_mode + origin_object = self.modifier.origin + if origin_mode == 'UP_LIMITS': + origin_object.matrix_world.translation = Vector(self.point_limits_up) + elif origin_mode == 'DOWN_LIMITS': + origin_object.matrix_world.translation = Vector(self.point_limits_down) + elif origin_mode == 'LIMITS_MIDDLE': + translation = (self.point_limits_up + self.point_limits_down) / 2 + origin_object.matrix_world.translation = translation + elif origin_mode == 'MIDDLE': + translation = (self.point_up + self.point_down) / 2 + origin_object.matrix_world.translation = translation + + def update_multiple_modifiers_data(self): + obj = self.obj context = bpy.context + if not self.obj_type_is_mesh_or_lattice(obj) or not self.simple_deform_modifier_is_simple(context): + return + self.clear_point_cache() + self.clear_modifiers_data() data = bpy.data - obj = object - matrix = obj.matrix_world.copy() # 物体矩阵 + name = self.G_TMP_MULTIPLE_MODIFIERS_MESH - # add simple_deform mesh - (min_x, min_y, min_z), (max_x, max_y, - max_z) = cls.get_mesh_max_min_co(object) - vertexes = ((max_x, min_y, min_z), - (min_x, min_y, min_z), - (max_x, max_y, min_z), - (min_x, max_y, min_z), - (max_x, min_y, max_z), - (min_x, min_y, max_z), - (max_x, max_y, max_z), - (min_x, max_y, max_z)) - if data.objects.get(G_NAME): - data.objects.remove(data.objects.get(G_NAME)) + # del old tmp object + old_object = data.objects.get(name) + if old_object: + data.objects.remove(old_object) - if data.meshes.get(G_NAME): - data.meshes.remove(data.meshes.get(G_NAME)) - mesh = data.meshes.new(G_NAME) - mesh.from_pydata(vertexes, G_INDICES, []) - mesh.update() + if data.meshes.get(name): + data.meshes.remove(data.meshes.get(name)) - new_object = data.objects.new(G_NAME, mesh) + """get origin mesh bound box as multiple basic mesh + add multiple modifiers and get depsgraph obj bound box + """ + vertices = self.tow_co_to_coordinate(self.get_mesh_max_min_co(self.obj)) + new_mesh = self.from_vertices_new_mesh(name, vertices) + modifiers_obj = data.objects.new(name, new_mesh) - cls.link_active_collection(new_object) + self.link_obj_to_active_collection(modifiers_obj) + if modifiers_obj == obj: # is cycles + return + if modifiers_obj.parent != obj: + modifiers_obj.parent = obj - if new_object.parent != obj: - new_object.parent = obj + modifiers_obj.modifiers.clear() + subdivision = modifiers_obj.modifiers.new('1', 'SUBSURF') + subdivision.levels = self.G_SUB_LEVELS - new_object.modifiers.clear() - subdivision = new_object.modifiers.new('1', 'SUBSURF') - subdivision.levels = 7 - cls.G_SimpleDeformGizmoHandlerDit['modifiers_co'] = {} - cls.G_SimpleDeformGizmoHandlerDit['modifiers_co']['co'] = ( - min_x, min_y, min_z), (max_x, max_y, max_z) - for mo in context.object.modifiers: - if mo.type == 'SIMPLE_DEFORM': - simple_deform = new_object.modifiers.new( - mo.name, 'SIMPLE_DEFORM') - simple_deform.deform_method = mo.deform_method - simple_deform.deform_axis = mo.deform_axis - simple_deform.lock_x = mo.lock_x - simple_deform.lock_y = mo.lock_y - simple_deform.lock_z = mo.lock_z - simple_deform.origin = mo.origin - simple_deform.limits[1] = mo.limits[1] - simple_deform.limits[0] = mo.limits[0] - simple_deform.angle = mo.angle - simple_deform.show_viewport = mo.show_viewport - obj = Utils.get_depsgraph(new_object) - cls.G_SimpleDeformGizmoHandlerDit['modifiers_co'][mo.name] = cls.get_mesh_max_min_co( - obj) - new_object.hide_set(True) - new_object.hide_viewport = False - new_object.hide_select = True - new_object.hide_render = True - new_object.hide_viewport = True - new_object.hide_set(True) + for mod in context.object.modifiers: + if self.mod_is_simple_deform_type(mod): + dep_bound_tow_co = self.get_mesh_max_min_co(self.get_depsgraph(modifiers_obj)) + self.G_MultipleModifiersBoundData[mod.name] = dep_bound_tow_co + new_mod = modifiers_obj.modifiers.new(mod.name, 'SIMPLE_DEFORM') + self.copy_modifier_parameter(mod, new_mod) + data.objects.remove(modifiers_obj) + + def update_deform_wireframe(self): + if not self.pref.update_deform_wireframe: + return + # obj = self.obj + name = self.modifier.name + deform_name = self.G_DEFORM_MESH_NAME + + co = self.G_MultipleModifiersBoundData[name] + + deform_obj = bpy.data.objects.get(deform_name, None) + + if not deform_obj: + a, b = 0.5, -0.5 + vertices = self.tow_co_to_coordinate(((b, b, b), (a, a, a))) + new_mesh = self.from_vertices_new_mesh(name, vertices) + deform_obj = bpy.data.objects.new(deform_name, new_mesh) + deform_obj.hide_select = True + # deform_obj.hide_set(True) + deform_obj.hide_render = True + deform_obj.hide_viewport = True + + self.link_obj_to_active_collection(deform_obj) + + deform_obj.parent = self.obj + + tmv = deform_obj.hide_viewport + tmh = deform_obj.hide_get() + deform_obj.hide_viewport = False + deform_obj.hide_set(False) + + # Update Matrix + deform_obj.matrix_world = Matrix() + center = (co[0] + co[1]) / 2 + scale = co[1] - co[0] + deform_obj.matrix_world = self.obj_matrix_world @ deform_obj.matrix_world + deform_obj.location = center + deform_obj.scale = scale + + # Update Modifier data + mods = deform_obj.modifiers + mods.clear() + subdivision = mods.new('1', 'SUBSURF') + subdivision.levels = self.G_SUB_LEVELS + + new_mod = mods.new(name, 'SIMPLE_DEFORM') + self.copy_modifier_parameter(self.modifier, new_mod) + + # Get vertices data + context = bpy.context + obj = self.get_depsgraph(deform_obj) + matrix = deform_obj.matrix_world.copy() ver_len = obj.data.vertices.__len__() edge_len = obj.data.edges.__len__() - - if 'numpy_data' not in cls.G_SimpleDeformGizmoHandlerDit: - cls.G_SimpleDeformGizmoHandlerDit['numpy_data'] = {} - - numpy_data = cls.G_SimpleDeformGizmoHandlerDit['numpy_data'] + if 'numpy_data' not in self.G_DeformDrawData: + self.G_DeformDrawData['numpy_data'] = {} + numpy_data = self.G_DeformDrawData['numpy_data'] key = (ver_len, edge_len) if key in numpy_data: list_edges, list_vertices = numpy_data[key] @@ -361,155 +762,179 @@ class Utils(Data): obj.data.edges.foreach_get('vertices', list_edges) indices = list_edges.reshape((edge_len, 2)) + modifiers = self.get_modifiers_parameter(self.modifier) limits = context.object.modifiers.active.limits[:] - modifiers = [getattr(context.object.modifiers.active, i) - for i in G_MODIFIERS_PROPERTY] - cls.G_SimpleDeformGizmoHandlerDit['draw'] = (ver, indices, matrix, modifiers, limits) + deform_obj.hide_viewport = tmv + deform_obj.hide_set(tmh) + self.G_DeformDrawData['simple_deform_bound_data'] = (ver, indices, self.obj_matrix_world, modifiers, limits[:]) + + +class GizmoUtils(GizmoUpdate): + custom_shape: dict + init_mouse_region_y: float + init_mouse_region_x: float + mouse_dpi: int + matrix_basis: Matrix + draw_type: str + + def generate_gizmo(self, gizmo_data): + """Generate Gizmo From Input Data + Args: + gizmo_data (_type_): _description_ + """ + for i, j, k in gizmo_data: + setattr(self, i, self.gizmos.new(j)) + gizmo = getattr(self, i) + for f in k: + if f == 'target_set_operator': + gizmo.target_set_operator(k[f]) + elif f == 'target_set_prop': + gizmo.target_set_prop(*k[f]) + else: + setattr(gizmo, f, k[f]) + + def init_shape(self): + if not hasattr(self, 'custom_shape'): + self.custom_shape = {} + for i in self.G_CustomShape: + item = self.G_CustomShape[i] + self.custom_shape[i] = self.new_custom_shape('TRIS', item) + + def init_setup(self): + self.init_shape() + + def init_invoke(self, context, event): + self.init_mouse_region_y = event.mouse_region_y + self.init_mouse_region_x = event.mouse_region_x + + def __update_matrix_func(self, context): + func = getattr(self, 'update_gizmo_matrix', None) + if func and self.modifier_origin_is_available: + func(context) + + def draw(self, context): + if self.modifier_origin_is_available: + self.draw_custom_shape(self.custom_shape[self.draw_type]) + self.__update_matrix_func(context) + + def draw_select(self, context, select_id): + if self.modifier_origin_is_available: + self.draw_custom_shape( + self.custom_shape[self.draw_type], select_id=select_id) + self.__update_matrix_func(context) + + def get_delta(self, event): + delta = (self.init_mouse_region_x - event.mouse_region_x) / self.mouse_dpi + return delta + + def update_gizmo_matrix(self): + ... + + def event_handle(self, event): + """General event triggering""" + data_path = ('object.SimpleDeformGizmo_PropertyGroup.origin_mode', + 'object.modifiers.active.origin.SimpleDeformGizmo_PropertyGroup.origin_mode') + + if event.type in ('WHEELUPMOUSE', 'WHEELDOWNMOUSE'): + reverse = (event.type == 'WHEELUPMOUSE') + for path in data_path: + bpy.ops.wm.context_cycle_enum( + data_path=path, reverse=reverse, wrap=True) + elif event.type in ('X', 'Y', 'Z'): + self.obj.modifiers.active.deform_axis = event.type + elif event.type == 'A' and 'BEND' == self.modifier.deform_method: + self.pref.display_bend_axis_switch_gizmo = True + return {'FINISHED'} + elif event.type == 'W' and event.value == 'RELEASE': + self.pref.update_deform_wireframe = self.pref.update_deform_wireframe ^ True + return {'RUNNING_MODAL'} + + @staticmethod + def tag_redraw(context): + if context.area: + context.area.tag_redraw() + + +class GizmoGroupUtils(GizmoUtils): + bl_space_type = 'VIEW_3D' + bl_region_type = 'WINDOW' + bl_options = {'3D', + 'PERSISTENT', + # 'SCALE', + # 'DEPTH_3D', + # 'SELECT', + # 'SHOW_MODAL_ALL', + # 'EXCLUDE_MODAL', + # 'TOOL_INIT', # not show + # 'TOOL_FALLBACK_KEYMAP', + # 'VR_REDRAWS' + } + + +class Tmp: @classmethod - def update_co_data(cls, ob, mod): - handler_dit = cls.G_SimpleDeformGizmoHandlerDit + def get_origin_bounds(cls, obj: 'bpy.types.Object') -> list: + modifiers_dict = {} + for mod in obj.modifiers: + if (mod == obj.modifiers.active) or (modifiers_dict != {}): + modifiers_dict[mod] = (mod.show_render, mod.show_viewport) + mod.show_viewport = False + mod.show_render = False + matrix_obj = obj.matrix_world.copy() + obj.matrix_world.zero() + obj.scale = (1, 1, 1) + bound = cls.bound_box_to_list(obj) + obj.matrix_world = matrix_obj + for mod in modifiers_dict: + show_render, show_viewport = modifiers_dict[mod] + mod.show_render = show_render + mod.show_viewport = show_viewport + return list(bound) - if 'modifiers_co' in cls.G_SimpleDeformGizmoHandlerDit and ob.type in ('MESH', 'LATTICE'): - modifiers_co = cls.G_SimpleDeformGizmoHandlerDit['modifiers_co'] - for index, mod_name in enumerate(modifiers_co): - co_items = list(modifiers_co.items()) - if mod.name == mod_name: - cls.G_SimpleDeformGizmoHandlerDit['co'] = co_items[index - 1][1] if (index or (index != 1)) else \ - modifiers_co['co'] + def update_gizmo_rotate(self): + mod = self.modifier + axis = self.modifier_deform_axis + if self.rotate_follow_modifier: + rot = Euler() + if axis == 'X' and (not self.is_positive(mod.angle)): + rot.z = math.pi - @classmethod - def generate_co_data(cls): - handler_dit = cls.G_SimpleDeformGizmoHandlerDit - - if 'co' not in handler_dit: - handler_dit['co'] = cls.get_mesh_max_min_co( - bpy.context.object) - return handler_dit['co'] - - @classmethod - def new_empty(cls, obj, mod): - origin = mod.origin - if origin is None: - new_name = G_NAME + '_Empty_' + str(uuid.uuid4()) - origin_object = bpy.data.objects.new(new_name, None) - cls.link_active_collection(origin_object) - origin_object.hide_set(True) - origin_object.empty_display_size = min(obj.dimensions) - mod.origin = origin_object - else: - origin_object = mod.origin - origin_object.hide_viewport = False - - if origin_object.parent != obj: - origin_object.parent = obj - - # add constraints - if G_CON_LIMIT_NAME in origin_object.constraints.keys(): - limit_constraints = origin.constraints.get(G_CON_LIMIT_NAME) - else: - limit_constraints = origin_object.constraints.new( - 'LIMIT_ROTATION') - limit_constraints.name = G_CON_LIMIT_NAME - limit_constraints.owner_space = 'WORLD' - limit_constraints.space_object = obj - limit_constraints.use_transform_limit = True - limit_constraints.use_limit_x = True - limit_constraints.use_limit_y = True - limit_constraints.use_limit_z = True - con_copy_name = G_NAME + 'constraints_copy_rotation' - if con_copy_name in origin_object.constraints.keys(): - copy_constraints = origin.constraints.get(con_copy_name) - else: - copy_constraints = origin_object.constraints.new( - 'COPY_ROTATION') - copy_constraints.name = con_copy_name - copy_constraints.target = obj - copy_constraints.mix_mode = 'BEFORE' - copy_constraints.target_space = 'WORLD' - copy_constraints.owner_space = 'WORLD' - origin_object.rotation_euler.zero() - origin_object.scale = 1, 1, 1 - return origin_object, G_CON_LIMIT_NAME - - @classmethod - def co_to_direction(cls, mat, data): - (min_x, min_y, min_z), (max_x, max_y, - max_z) = data - a = mat @ Vector((max_x, max_y, max_z)) - b = mat @ Vector((max_x, min_y, min_z)) - c = mat @ Vector((min_x, max_y, min_z)) - d = mat @ Vector((min_x, min_y, max_z)) - - def pos_get(a, b): - return cls.set_reduce(cls.set_reduce(a, b, '+'), (2, 2, 2), '/') - - top = Vector(pos_get(a, d)) - bottom = Vector(pos_get(c, b)) - left = Vector(pos_get(c, d)) - right = Vector(pos_get(a, b)) - front = Vector(pos_get(d, b)) - back = Vector(pos_get(c, a)) - return top, bottom, left, right, front, back - - @classmethod - def each_face_pos(cls, mat: 'Matrix' = None): - if mat is None: - mat = Matrix() - return cls.co_to_direction(mat, cls.G_SimpleDeformGizmoHandlerDit['co']) - - @classmethod - def update_matrix(cls, mod, ob): - if mod.deform_method == 'BEND': - cls.new_empty(ob, mod) - if mod.origin: - empty_object = mod.origin - modifiers_co = cls.G_SimpleDeformGizmoHandlerDit['modifiers_co'] - for index, mod_name in enumerate(modifiers_co): - co_items = list(modifiers_co.items()) - if mod.name == mod_name: - data = co_items[index - 1][1] if ( - index or (index != 1)) else modifiers_co['co'] - (up, down), (up_, down_) = cls.get_limits_pos( - mod, cls.co_to_direction(ob.matrix_world.copy(), data)) - origin_mode = cls.get_origin_property_group( - mod, ob).origin_mode - cls.set_empty_obj_matrix( - origin_mode, empty_object, up_, down_, up, down) - - @classmethod - def update_empty_matrix(cls): - ob = bpy.context.object - for mod in ob.modifiers: - if mod.type == 'SIMPLE_DEFORM': - cls.update_matrix(mod, ob) - - @classmethod - def get_up_down_return_list(cls, mod, axis, up_, down_, data): - top, bottom, left, right, front, back = data - if 'BEND' == mod.deform_method: - if axis in ('X', 'Y'): - top = up_ - bottom = down_ - elif axis == 'Z': - right = up_ - left = down_ - else: - if axis == 'X': - right = up_ - left = down_ elif axis == 'Y': - back = up_ - front = down_ + if self.is_positive(mod.angle): + rot.z = -(math.pi / 2) + else: + rot.z = math.pi / 2 elif axis == 'Z': - top = up_ - bottom = down_ - return top, bottom, left, right, front, back + if self.is_positive(mod.angle): + rot.x = rot.z = rot.y = math.pi / 2 + else: + rot.z = rot.y = math.pi / 2 + rot.x = -(math.pi / 2) + + rot = rot.to_matrix() + self.matrix_basis = self.matrix_basis @ rot.to_4x4() + + @classmethod + def bound_box_to_list(cls, obj: 'bpy.types.Object'): + return tuple(i[:] for i in obj.bound_box) + + @classmethod + def properties_is_modifier(cls) -> bool: + """Returns whether there is a modifier property panel open in the active window. + If it is open, it returns to True else False + """ + for area in bpy.context.screen.areas: + if area.type == 'PROPERTIES': + for space in area.spaces: + if space.type == 'PROPERTIES' and space.context == 'MODIFIER': + return True + return False def register(): - PublicClass.load_gizmo_data() + PublicData.load_gizmo_data() def unregister():