tmp_simple_deform_helper_refactor #1

Merged
EMM merged 12 commits from tmp_simple_deform_helper_refactor into simple_deform_helper 2023-04-02 21:37:42 +02:00
16 changed files with 1836 additions and 1243 deletions

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