new addon simple_deform_helper #104464

Open
EMM wants to merge 29 commits from Guai_Wo_Ge_EMM/blender-addons:simple_deform_helper into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
16 changed files with 1836 additions and 1243 deletions
Showing only changes of commit 5a1c553931 - Show all commits

View File

@ -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()

View File

@ -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

View File

@ -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):
...

File diff suppressed because one or more lines are too long

View File

@ -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()

View File

@ -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()

View File

@ -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')

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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'}

View File

@ -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 = (

View File

@ -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')

View File

@ -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"): "绘制网格上限下限边界线框的颜色",

View File

@ -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()

File diff suppressed because it is too large Load Diff