new addon simple_deform_helper #104464

Closed
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.
10 changed files with 492 additions and 376 deletions
Showing only changes of commit af50b2f258 - Show all commits

View File

@ -5,7 +5,7 @@ import gpu
from gpu_extras.batch import batch_for_shader from gpu_extras.batch import batch_for_shader
from mathutils import Vector from mathutils import Vector
from .utils import PublicUtils from .utils import GizmoUtils
class Handler: class Handler:
@ -39,7 +39,48 @@ class Handler:
cls.G_SimpleDeformGizmoHandlerDit.clear() cls.G_SimpleDeformGizmoHandlerDit.clear()
class Draw3D(PublicUtils): class Draw3D(GizmoUtils):
@classmethod
def draw_bound_box(cls):
gpu.state.blend_set('ALPHA')
gpu.state.line_width_set(1)
gpu.state.blend_set('ALPHA')
gpu.state.depth_test_set('ALWAYS')
context = bpy.context
if cls.simple_deform_public_poll(context):
cls.is_draw_box(context)
else:
Handler.del_handler()
@classmethod
def is_draw_box(cls, context):
obj = context.object # 活动物体
matrix = obj.matrix_world # 活动物体矩阵
modifier = context.object.modifiers.active # 活动修改器
pref = cls.pref_()
simple_poll = cls.simple_deform_public_poll(context)
bend = modifier and (modifier.deform_method == 'BEND')
display_switch_axis = not pref.display_bend_axis_switch_gizmo
cls.draw_scale_text(obj)
cls.update_co_data(obj, modifier)
co_data = cls.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)
cls.new_empty(obj, modifier)
@classmethod @classmethod
def draw_3d_shader(cls, pos, indices, color=None, *, shader_name='3D_UNIFORM_COLOR', draw_type='LINES'): def draw_3d_shader(cls, pos, indices, color=None, *, shader_name='3D_UNIFORM_COLOR', draw_type='LINES'):
shader = gpu.shader.from_builtin(shader_name) shader = gpu.shader.from_builtin(shader_name)
@ -86,8 +127,8 @@ class Draw3D(PublicUtils):
@classmethod @classmethod
def draw_box(cls, data, mat): def draw_box(cls, data, mat):
pref = cls.pref_() pref = cls.pref_()
coords = PublicUtils.matrix_calculation(mat, coords = cls.matrix_calculation(mat,
cls.data_to_calculation(data)) cls.data_to_calculation(data))
cls.draw_3d_shader(coords, cls.G_INDICES, pref.bound_box_color) cls.draw_3d_shader(coords, cls.G_INDICES, pref.bound_box_color)
@classmethod @classmethod
@ -111,7 +152,7 @@ class Draw3D(PublicUtils):
if 'draw_limits_bound_box' in handler_dit: if 'draw_limits_bound_box' in handler_dit:
# draw limits_bound_box # draw limits_bound_box
mat, data = handler_dit['draw_limits_bound_box'] mat, data = handler_dit['draw_limits_bound_box']
coords = PublicUtils.matrix_calculation(mat, cls.data_to_calculation(data)) coords = cls.matrix_calculation(mat, cls.data_to_calculation(data))
cls.draw_3d_shader(coords, cls.draw_3d_shader(coords,
cls.G_INDICES, cls.G_INDICES,
pref.limits_bound_box_color) pref.limits_bound_box_color)
@ -147,43 +188,3 @@ class Draw3D(PublicUtils):
if (ob.scale != Vector((1, 1, 1))) and ('handler_text' not in cls.G_SimpleDeformGizmoHandlerDit): 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.G_SimpleDeformGizmoHandlerDit['handler_text'] = bpy.types.SpaceView3D.draw_handler_add(
cls.draw_str, (), 'WINDOW', 'POST_PIXEL') 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 = PublicUtils.simple_deform_public_poll(context)
bend = modifier and (modifier.deform_method == 'BEND')
display_switch_axis = not pref.display_bend_axis_switch_gizmo
cls.draw_scale_text(obj)
PublicUtils.update_co_data(obj, modifier)
co_data = PublicUtils.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)
PublicUtils.new_empty(obj, modifier)
@classmethod
def draw_bound_box(cls):
gpu.state.blend_set('ALPHA')
gpu.state.line_width_set(1)
gpu.state.blend_set('ALPHA')
gpu.state.depth_test_set('ALWAYS')
context = bpy.context
if PublicUtils.simple_deform_public_poll(context):
cls.is_draw_box(context)
else:
Handler.del_handler()

View File

@ -1,87 +1,20 @@
import bpy import bpy
from bpy_types import Gizmo from bpy_types import Gizmo
from draw import Handler from .bend_axis import SimpleDeformGizmoGroupDisplayBendAxiSwitchGizmo, CustomGizmo
from utils import PublicUtils from .up_down_limits_point import GizmoProperty, UpDownLimitsGizmo
from .angle_and_factor import AngleGizmoGroup, AngleGizmo
from ..draw import Handler
class GizmoProperty:
@property
def obj(self):
return bpy.context.object
@property
def modifier(self):
obj = self.obj
if not obj:
return
return obj.modifiers.active
@property
def active_modifier_is_simple_deform(self):
return self.modifier and self.modifier.type == 'SIMPLE_DEFORM'
@property
def is_use_angle_value(self):
if self.active_modifier_is_simple_deform:
return self.modifier.deform_method in ('TWIST', 'BEND')
class GizmoPublic(GizmoProperty, PublicUtils, Handler):
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])
class CustomGizmo(Gizmo, PublicUtils, Handler):
"""绘制自定义Gizmo"""
bl_idname = '_Custom_Gizmo'
draw_type: str
custom_shape: dict
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_list = ( class_list = (
ViewSimpleDeformGizmo, # GizmoProperty,
SimpleDeformGizmoGroup, # UpDownLimitsGizmo,
AngleGizmo,
AngleGizmoGroup,
CustomGizmo,
SimpleDeformGizmoGroupDisplayBendAxiSwitchGizmo,
) )
register_class, unregister_class = bpy.utils.register_classes_factory(class_list) register_class, unregister_class = bpy.utils.register_classes_factory(class_list)

View File

@ -0,0 +1,163 @@
# SPDX-License-Identifier: GPL-2.0-or-later
import math
from bpy.types import (
GizmoGroup,
)
from bpy.types import Gizmo
from mathutils import Vector, Euler, Matrix
from ..utils import GizmoUtils
from ..draw import Handler
class AngleGizmo(Gizmo, GizmoUtils):
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',
'init_mouse_y',
'init_mouse_x',
'custom_shape',
'int_value_angle',
'rotate_follow_modifier',
)
int_value_angle: float
rotate_follow_modifier: bool
def setup(self):
self.mouse_dpi = 2
self.rotate_follow_modifier = True
self.init_setup()
def invoke(self, context, event):
self.init_invoke(context, event)
self.int_value_angle = self.target_get_value('angle')
return {'RUNNING_MODAL'}
def modal(self, context, event, tweak):
self.update_header_text(context)
self.update_prop_value(event, tweak)
return {'RUNNING_MODAL'}
def exit(self, context, cancel):
context.area.header_text_set(None)
if cancel:
self.target_set_value('angle', self.int_value_angle)
def update_prop_value(self, event, tweak):
# radians 弧度
# degrees 角度
delta = self.get_delta(event)
value = math.degrees(self.int_value_angle - delta)
new_value = (self.get_snap(value, tweak))
old_value = math.degrees(self.target_get_value('angle'))
print(new_value, old_value)
self.target_set_value('angle', math.radians(new_value))
def update_gizmo_matrix(self, context):
matrix = context.object.matrix_world
self.matrix_basis.translation = matrix @ Vector((self.generate_co_data()[1]))
def update_header_text(self, context):
text = self.translate_header_text('Angle', round(math.degrees(self.modifier_angle), 3))
context.area.header_text_set(text)
class AngleGizmoGroup(GizmoGroup, GizmoUtils, Handler):
"""ShowGizmo
"""
bl_idname = 'OBJECT_GGT_SimpleDeformGizmoGroup'
bl_label = 'AngleGizmoGroup'
bl_space_type = 'VIEW_3D'
bl_region_type = 'WINDOW'
bl_options = {'3D', 'PERSISTENT', }
@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': 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)
def refresh(self, context):
self.angle.target_set_prop('angle',
context.object.modifiers.active,
'angle')
# pro = context.object.SimpleDeformGizmo_PropertyGroup
# 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')
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)

View File

@ -1,13 +1,46 @@
import math import math
from bpy.types import GizmoGroup from bpy.types import GizmoGroup
from bpy_types import Gizmo
from mathutils import Euler, Vector from mathutils import Euler, Vector
from ..draw import Handler from ..draw import Handler
from ..utils import PublicUtils from ..utils import GizmoUtils
class SimpleDeformGizmoGroupDisplayBendAxiSwitchGizmo(GizmoGroup, PublicUtils, Handler): class CustomGizmo(Gizmo, GizmoUtils, Handler):
"""绘制自定义Gizmo"""
bl_idname = '_Custom_Gizmo'
draw_type: str
custom_shape: dict
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 SimpleDeformGizmoGroupDisplayBendAxiSwitchGizmo(GizmoGroup, GizmoUtils):
"""绘制切换变型轴的 """绘制切换变型轴的
变换方向 变换方向
""" """
@ -23,21 +56,15 @@ class SimpleDeformGizmoGroupDisplayBendAxiSwitchGizmo(GizmoGroup, PublicUtils, H
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
pref = cls.pref_() return cls.simple_deform_show_bend_axis_witch_poll(context)
simple = cls.simple_deform_public_poll(context)
bend = simple and (
context.object.modifiers.active.deform_method == 'BEND')
switch_axis = pref.display_bend_axis_switch_gizmo
return switch_axis and bend
def setup(self, context): def setup(self, context):
_draw_type = 'SimpleDeform_Bend_Direction_' _draw_type = 'SimpleDeform_Bend_Direction_'
_color_a = 1, 0, 0 _color_a = 1, 0, 0
_color_b = 0, 1, 0 _color_b = 0, 1, 0
self.add_handler()
for na, axis, rot, positive in ( for na, axis, rot, positive in (
('top_a', 'X', (math.radians(90), 0, math.radians(90)), True), ('top_a', 'X', (math.radians(90), 0, math.radians(9 - 0)), True),
('top_b', 'X', (math.radians(90), 0, 0), True), ('top_b', 'X', (math.radians(90), 0, 0), True),
('bottom_a', 'X', (math.radians(90), 0, math.radians(90)), False), ('bottom_a', 'X', (math.radians(90), 0, math.radians(90)), False),
@ -102,6 +129,5 @@ class SimpleDeformGizmoGroupDisplayBendAxiSwitchGizmo(GizmoGroup, PublicUtils, H
for i, j, w, in for_list: for i, j, w, in for_list:
gizmo = getattr(self, i, False) gizmo = getattr(self, i, False)
rot = Euler(w, 'XYZ').to_matrix().to_4x4() rot = Euler(w, 'XYZ').to_matrix().to_4x4()
gizmo.matrix_basis = mat.to_euler().to_matrix().to_4x4() @ rot gizmo.matrix_basis = mat.to_euler().to_matrix().to_4x4() @ rot
gizmo.matrix_basis.translation = Vector(j) gizmo.matrix_basis.translation = Vector(j)

View File

@ -1,132 +0,0 @@
# SPDX-License-Identifier: GPL-2.0-or-later
from bpy.types import (
GizmoGroup,
)
from mathutils import Vector
from utils import PublicUtils
class SimpleDeformGizmoGroup(GizmoGroup, PublicUtils):
"""显示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_public_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 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()

View File

@ -1,7 +1,16 @@
import math
from typing import Callable, Any
import bpy import bpy
from bpy_extras import view3d_utils
from bpy.types import Gizmo
from mathutils import Euler, Vector
from ..draw import Handler
from ..utils import GizmoUtils
class GizmoProperty(Gizmo, PublicUtils, Handler): class GizmoProperty(GizmoUtils, Handler):
@property @property
def is_angle_mode(self): def is_angle_mode(self):
return self.ctrl_mode == 'angle' return self.ctrl_mode == 'angle'
@ -15,15 +24,14 @@ class GizmoProperty(Gizmo, PublicUtils, Handler):
return self.ctrl_mode == 'down_limits' return self.ctrl_mode == 'down_limits'
class ViewSimpleDeformGizmo(GizmoProperty): class UpDownLimitsGizmo(GizmoProperty, Gizmo):
"""显示轴向切换拖动点Gizmo(两个点) """显示轴向切换拖动点Gizmo(两个点)
""" """
bl_idname = 'ViewSimpleDeformGizmo' bl_idname = 'UpDownLimitsGizmo'
bl_target_properties = ( bl_target_properties = (
{'id': 'up_limits', 'type': 'FLOAT', 'array_length': 1}, {'id': 'up_limits', 'type': 'FLOAT', 'array_length': 1},
{'id': 'down_limits', 'type': 'FLOAT', 'array_length': 1}, {'id': 'down_limits', 'type': 'FLOAT', 'array_length': 1},
{'id': 'angle', 'type': 'FLOAT', 'array_length': 1},
) )
__slots__ = ( __slots__ = (
@ -46,27 +54,6 @@ class ViewSimpleDeformGizmo(GizmoProperty):
'rotate_follow_modifier', '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_): def update_draw_limits_bound_box(self, data, mod, axis, mat, up_, down_):
top, bottom, left, right, front, back = data top, bottom, left, right, front, back = data
if mod.origin: if mod.origin:
@ -111,7 +98,6 @@ class ViewSimpleDeformGizmo(GizmoProperty):
self.matrix_basis = ob.matrix_world.normalized() self.matrix_basis = ob.matrix_world.normalized()
co = self.generate_co_data() co = self.generate_co_data()
self.update_gizmo_rotate(axis, mod)
# calculation limits position # calculation limits position
top, bottom, left, right, front, back = self.each_face_pos(mat) top, bottom, left, right, front, back = self.each_face_pos(mat)
(up, down), (up_, down_) = self.get_limits_pos( (up, down), (up_, down_) = self.get_limits_pos(
@ -130,38 +116,19 @@ class ViewSimpleDeformGizmo(GizmoProperty):
def setup(self): def setup(self):
self.generate_co_data() self.generate_co_data()
self.draw_type = 'None_GizmoGroup_' self.draw_type = 'None_GizmoGroup_'
self.ctrl_mode = 'angle' # up_limits , down_limits self.ctrl_mode = 'up_limits' # up_limits , down_limits
self.mouse_dpi = 10 self.mouse_dpi = 10
self.rotate_follow_modifier = True 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() 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): def invoke(self, context, event):
self.init_mouse_y = event.mouse_y self.init_invoke(context, event)
self.init_mouse_x = event.mouse_x
mod = context.object.modifiers.active mod = context.object.modifiers.active
limits = mod.limits limits = mod.limits
up_limits = limits[1] up_limits = limits[1]
down_limits = limits[0] down_limits = limits[0]
if 'angle' == self.ctrl_mode: if 'up_limits' == 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.int_value_up_limits = up_limits
self.target_set_value('up_limits', self.int_value_up_limits) self.target_set_value('up_limits', self.int_value_up_limits)
elif 'down_limits' == self.ctrl_mode: elif 'down_limits' == self.ctrl_mode:
@ -179,7 +146,6 @@ class ViewSimpleDeformGizmo(GizmoProperty):
self.target_set_value('deform_axis', self.value_deform_axis) self.target_set_value('deform_axis', self.value_deform_axis)
elif 'up_limits' == self.ctrl_mode: elif 'up_limits' == self.ctrl_mode:
self.target_set_value('up_limits', self.int_value_up_limits) self.target_set_value('up_limits', self.int_value_up_limits)
elif 'down_limits' == self.ctrl_mode: elif 'down_limits' == self.ctrl_mode:
self.target_set_value( self.target_set_value(
'down_limits', self.int_value_down_limits) 'down_limits', self.int_value_down_limits)
@ -256,14 +222,6 @@ class ViewSimpleDeformGizmo(GizmoProperty):
elif self.is_down_limits_mode: elif self.is_down_limits_mode:
self.set_down_value(data, mu) self.set_down_value(data, mu)
@staticmethod
def snap_value(value, event: 'bpy.types.Event'):
if event.ctrl:
value //= 5
elif event.ctrl and event.shift:
value //= 1
return value
def update_header_text(self, context, mod, origin, up_limits, down_limits): def update_header_text(self, context, mod, origin, up_limits, down_limits):
translate: Callable[[Any], str] = lambda t: bpy.app.translations.pgettext(t) translate: Callable[[Any], str] = lambda t: bpy.app.translations.pgettext(t)
mode = origin.bl_rna.properties['origin_mode'].enum_items[origin.origin_mode].name mode = origin.bl_rna.properties['origin_mode'].enum_items[origin.origin_mode].name
@ -274,7 +232,7 @@ class ViewSimpleDeformGizmo(GizmoProperty):
text = translate(mode) + ' ' text = translate(mode) + ' '
if self.is_use_angle_value and self.is_angle_mode: if self.is_use_angle_value and self.is_angle_mode:
text += t_('Angle', math.degrees(mod.angle)) text += t_()
elif self.is_up_limits_mode: elif self.is_up_limits_mode:
text += t_('Upper limit', up_limits) text += t_('Upper limit', up_limits)
elif self.is_down_limits_mode: elif self.is_down_limits_mode:
@ -306,7 +264,6 @@ class ViewSimpleDeformGizmo(GizmoProperty):
def modal(self, context, event, tweak): def modal(self, context, event, tweak):
self.update_bound_box(context.object) self.update_bound_box(context.object)
delta = (self.init_mouse_x - event.mouse_x) / self.mouse_dpi
ob = context.object ob = context.object
mod = ob.modifiers.active mod = ob.modifiers.active
limits = mod.limits limits = mod.limits
@ -320,10 +277,7 @@ class ViewSimpleDeformGizmo(GizmoProperty):
min_value = down_limits + limit_scope min_value = down_limits + limit_scope
difference_value = up_limits - down_limits difference_value = up_limits - down_limits
if 'SNAP' in tweak: delta = self.get_delta(event, tweak)
delta = round(delta)
if 'PRECISE' in tweak:
delta /= self.mouse_dpi
delta = self.delta_update(context, event, delta) delta = self.delta_update(context, event, delta)
if origin_mode != 'NOT' and ('draw_line' in self.G_SimpleDeformGizmoHandlerDit): if origin_mode != 'NOT' and ('draw_line' in self.G_SimpleDeformGizmoHandlerDit):

View File

@ -4,10 +4,10 @@ import bpy
from bpy.types import Operator from bpy.types import Operator
from bpy.props import FloatProperty, StringProperty, BoolProperty from bpy.props import FloatProperty, StringProperty, BoolProperty
from .utils import PublicUtils from .utils import GizmoUtils
class DeformAxisOperator(Operator, PublicUtils): class DeformAxisOperator(Operator, GizmoUtils):
bl_idname = 'simple_deform_gizmo.deform_axis' bl_idname = 'simple_deform_gizmo.deform_axis'
bl_label = 'deform_axis' bl_label = 'deform_axis'
bl_description = 'deform_axis operator' bl_description = 'deform_axis operator'
@ -26,12 +26,12 @@ class DeformAxisOperator(Operator, PublicUtils):
return {'RUNNING_MODAL'} return {'RUNNING_MODAL'}
def modal(self, context, event): def modal(self, context, event):
from gizmo.ctrl_value_and_factor import PublicUtils from gizmo.angle_and_factor import GizmoUtils
mod = context.object.modifiers.active mod = context.object.modifiers.active
mod.deform_axis = self.Deform_Axis mod.deform_axis = self.Deform_Axis
empty, con_limit_name = PublicUtils.new_empty(context.object, mod) empty, con_limit_name = GizmoUtils.new_empty(context.object, mod)
is_positive = PublicUtils.is_positive(mod.angle) is_positive = GizmoUtils.is_positive(mod.angle)
for limit, value in (('max_x', self.X_Value), for limit, value in (('max_x', self.X_Value),
('min_x', self.X_Value), ('min_x', self.X_Value),
@ -48,7 +48,7 @@ class DeformAxisOperator(Operator, PublicUtils):
if not event.ctrl: if not event.ctrl:
self.pref.display_bend_axis_switch_gizmo = False self.pref.display_bend_axis_switch_gizmo = False
PublicUtils.update_bound_box(context.object) GizmoUtils.update_bound_box(context.object)
return {'FINISHED'} return {'FINISHED'}

View File

@ -2,10 +2,10 @@
import bpy import bpy
from bpy.types import Panel, VIEW3D_HT_tool_header from bpy.types import Panel, VIEW3D_HT_tool_header
from .utils import PublicUtils from .utils import GizmoUtils
class SimpleDeformHelperToolPanel(Panel, PublicUtils): class SimpleDeformHelperToolPanel(Panel, GizmoUtils):
bl_space_type = 'VIEW_3D' bl_space_type = 'VIEW_3D'
bl_region_type = 'UI' bl_region_type = 'UI'
bl_category = 'Tool' bl_category = 'Tool'
@ -16,7 +16,7 @@ class SimpleDeformHelperToolPanel(Panel, PublicUtils):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
return PublicUtils.simple_deform_public_poll(context) return cls.simple_deform_public_poll(context)
def draw(self, context): def draw(self, context):
cls = SimpleDeformHelperToolPanel cls = SimpleDeformHelperToolPanel

View File

@ -11,11 +11,11 @@ from bpy.types import (
PropertyGroup, PropertyGroup,
) )
from .utils import PublicUtils, GizmoUtils from .utils import GizmoUtils
class SimpleDeformGizmoAddonPreferences(AddonPreferences, PublicUtils): class SimpleDeformGizmoAddonPreferences(AddonPreferences, GizmoUtils):
bl_idname = PublicUtils.G_ADDON_NAME bl_idname = GizmoUtils.G_ADDON_NAME
deform_wireframe_color: FloatVectorProperty( deform_wireframe_color: FloatVectorProperty(
name='Deform Wireframe', name='Deform Wireframe',
@ -141,7 +141,7 @@ register_class, unregister_class = bpy.utils.register_classes_factory(class_list
def register(): def register():
register_class() register_class()
PublicUtils.pref_().display_bend_axis_switch_gizmo = False GizmoUtils.pref_().display_bend_axis_switch_gizmo = False
bpy.types.Object.SimpleDeformGizmo_PropertyGroup = PointerProperty( bpy.types.Object.SimpleDeformGizmo_PropertyGroup = PointerProperty(
type=SimpleDeformGizmoObjectPropertyGroup, type=SimpleDeformGizmoObjectPropertyGroup,
name='SimpleDeformGizmo_PropertyGroup') name='SimpleDeformGizmo_PropertyGroup')

View File

@ -8,7 +8,7 @@ from typing import Callable, Any
import bpy import bpy
import numpy as np import numpy as np
from bpy.types import AddonPreferences from bpy.types import AddonPreferences
from mathutils import Vector, Matrix from mathutils import Vector, Matrix, Euler
class PublicData: class PublicData:
@ -93,7 +93,61 @@ class PublicClass(PublicData):
return PublicClass.pref_() return PublicClass.pref_()
class PublicUtils(PublicClass): class PublicPoll(PublicClass):
@classmethod
def simple_deform_public_poll(cls, context: 'bpy.types.context') -> bool:
"""Public poll
In 3D View
Active Object in ('MESH', 'LATTICE')
Active Modifier Type Is 'SIMPLE_DEFORM' and show_viewport
return True
"""
obj = context.object
if not obj:
return False
mod = obj.modifiers.active
if not mod:
return False
space = context.space_data
show_gizmo = space.show_gizmo if space.type == 'VIEW_3D' else True
available_obj_type = obj and (obj.type in ('MESH', 'LATTICE'))
available_modifiers_type = mod and (mod.type == 'SIMPLE_DEFORM')
is_available_obj = available_modifiers_type and available_obj_type
is_obj_mode = context.mode == 'OBJECT'
show_mod = mod.show_viewport
return is_available_obj and is_obj_mode and show_gizmo and show_mod
@classmethod
def _simple_deform_modifier_is_bend_poll(cls, context):
"""
Public poll
active modifier deform_method =='BEND'
"""
simple = cls.simple_deform_public_poll(context)
is_bend = (context.object.modifiers.active.deform_method == 'BEND')
return simple and is_bend
@classmethod
def simple_deform_show_bend_axis_witch_poll(cls, context):
"""
Show D
"""
switch_axis = cls.pref_().display_bend_axis_switch_gizmo
bend = cls._simple_deform_modifier_is_bend_poll(context)
return switch_axis and bend
@classmethod
def simple_deform_show_gizmo_poll(cls, context):
poll = cls.simple_deform_public_poll(context)
not_switch = (not cls.simple_deform_show_bend_axis_witch_poll(context))
return poll and not_switch
class PublicUtils(PublicPoll):
@classmethod @classmethod
def value_limit(cls, value, max_value=1, min_value=0): def value_limit(cls, value, max_value=1, min_value=0):
""" """
@ -154,7 +208,7 @@ class PublicUtils(PublicClass):
return tuple(i[:] for i in obj.bound_box) return tuple(i[:] for i in obj.bound_box)
@classmethod @classmethod
def get_mesh_max_min_co(cls, obj: 'bpy.context.object') -> tuple: def get_mesh_max_min_co(cls, obj: 'bpy.context.object') -> '[Vector,Vector]':
if obj.type == 'MESH': if obj.type == 'MESH':
ver_len = obj.data.vertices.__len__() ver_len = obj.data.vertices.__len__()
list_vertices = np.zeros(ver_len * 3, dtype=np.float32) list_vertices = np.zeros(ver_len * 3, dtype=np.float32)
@ -165,7 +219,7 @@ class PublicUtils(PublicClass):
list_vertices = np.zeros(ver_len * 3, dtype=np.float32) list_vertices = np.zeros(ver_len * 3, dtype=np.float32)
obj.data.points.foreach_get('co', list_vertices) obj.data.points.foreach_get('co', list_vertices)
list_vertices = list_vertices.reshape(ver_len, 3) list_vertices = list_vertices.reshape(ver_len, 3)
return tuple(list_vertices.min(axis=0)), tuple(list_vertices.max(axis=0)) return Vector(list_vertices.min(axis=0)), Vector(list_vertices.max(axis=0))
@classmethod @classmethod
def matrix_calculation(cls, mat: 'Matrix', calculation_list: 'list') -> list: def matrix_calculation(cls, mat: 'Matrix', calculation_list: 'list') -> list:
@ -203,9 +257,51 @@ class PublicUtils(PublicClass):
(d, b) (d, b)
(c, a))) (c, a)))
@classmethod
def translate_text(cls, text):
return bpy.app.translations.pgettext(text)
class GizmoUtils(PublicUtils): @classmethod
def translate_header_text(cls, mode, value):
return cls.translate_text(mode) + ':{}'.format(value)
class GizmoProperty:
@property
def obj(self):
return bpy.context.object
@property
def modifier(self):
obj = self.obj
if not obj:
return
return obj.modifiers.active
@property
def modifier_deform_axis(self):
mod = self.modifier
if mod:
return mod.deform_axis
@property
def modifier_angle(self):
mod = self.modifier
if mod:
return mod.angle
@property
def active_modifier_is_simple_deform(self):
return self.modifier and self.modifier.type == 'SIMPLE_DEFORM'
@property
def is_use_angle_value(self):
if self.active_modifier_is_simple_deform:
return self.modifier.deform_method in ('TWIST', 'BEND')
class GizmoClassMethod(GizmoProperty, PublicUtils):
@classmethod @classmethod
def each_face_pos(cls, mat: 'Matrix' = None): def each_face_pos(cls, mat: 'Matrix' = None):
if mat is None: if mat is None:
@ -245,32 +341,6 @@ class GizmoUtils(PublicUtils):
else: else:
return ob.SimpleDeformGizmo_PropertyGroup return ob.SimpleDeformGizmo_PropertyGroup
@classmethod
def simple_deform_public_poll(cls, context: 'bpy.types.context') -> bool:
"""Public poll
In 3D View
Active Object in ('MESH', 'LATTICE')
Active Modifier Type Is 'SIMPLE_DEFORM' and show_viewport
return True
"""
obj = context.object
if not obj:
return False
mod = obj.modifiers.active
if not mod:
return False
space = context.space_data
show_gizmo = space.show_gizmo if space.type == 'VIEW_3D' else True
available_obj_type = obj and (obj.type in ('MESH', 'LATTICE'))
available_modifiers_type = mod and (mod.type == 'SIMPLE_DEFORM')
is_available_obj = available_modifiers_type and available_obj_type
is_obj_mode = context.mode == 'OBJECT'
show_mod = mod.show_viewport
return is_available_obj and is_obj_mode and show_gizmo and show_mod
@classmethod @classmethod
def get_up_down_return_list(cls, mod, axis, up_, down_, data): def get_up_down_return_list(cls, mod, axis, up_, down_, data):
top, bottom, left, right, front, back = data top, bottom, left, right, front, back = data
@ -418,7 +488,7 @@ class GizmoUtils(PublicUtils):
matrix = obj.matrix_world.copy() # 物体矩阵 matrix = obj.matrix_world.copy() # 物体矩阵
# add simple_deform mesh # add simple_deform mesh
(min_x, min_y, min_z), (max_x, max_y, (min_x, min_y, min_z), (max_x, max_y,
max_z) = cls.get_mesh_max_min_co(object) max_z) = cls.get_mesh_max_min_co(obj)
vertexes = ((max_x, min_y, min_z), vertexes = ((max_x, min_y, min_z),
(min_x, min_y, min_z), (min_x, min_y, min_z),
(max_x, max_y, min_z), (max_x, max_y, min_z),
@ -526,7 +596,85 @@ class GizmoUtils(PublicUtils):
modifiers_co['co'] modifiers_co['co']
class tmp: class GizmoUtils(GizmoClassMethod):
custom_shape: dict
init_mouse_y: float
init_mouse_x: float
mouse_dpi: int
matrix_basis: Matrix
draw_type: str
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 init_shape(self):
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)
def init_setup(self):
self.init_shape()
def init_invoke(self, context, event):
self.init_mouse_y = event.mouse_y
self.init_mouse_x = event.mouse_x
def _update_matrix(self, context):
func = getattr(self, 'update_gizmo_matrix', None)
if func:
func(context)
def draw(self, context):
self.draw_custom_shape(self.custom_shape[self.draw_type])
self._update_matrix(context)
def draw_select(self, context, select_id):
self.draw_custom_shape(
self.custom_shape[self.draw_type], select_id=select_id)
self._update_matrix(context)
def get_delta(self, event):
delta = (self.init_mouse_x - event.mouse_x) / self.mouse_dpi
return delta
def get_snap(self, delta, tweak):
# ctrl SNAP
# shift PRECISE
is_snap = 'SNAP' in tweak
is_precise = 'PRECISE' in tweak
if is_snap and is_precise:
delta = round(delta)
# delta /= self.mouse_dpi
elif is_snap:
delta //= 5
delta *= 5
elif is_precise:
delta //= 0.01
delta *= 0.01
print('tweak', delta, tweak)
return delta
def update_gizmo_matrix(self):
...
class Tmp:
@classmethod @classmethod
def get_origin_bounds(cls, obj: 'bpy.types.Object') -> list: def get_origin_bounds(cls, obj: 'bpy.types.Object') -> list:
modifiers_dict = {} modifiers_dict = {}
@ -546,6 +694,29 @@ class tmp:
mod.show_viewport = show_viewport mod.show_viewport = show_viewport
return list(bound) return list(bound)
def update_gizmo_rotate(self):
mod = self.modifier
axis = self.modifier_deform_axis
if self.rotate_follow_modifier:
rot = Euler()
if axis == 'X' and (not self.is_positive(mod.angle)):
rot.z = math.pi
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 register(): def register():
PublicData.load_gizmo_data() PublicData.load_gizmo_data()