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 554 additions and 598 deletions
Showing only changes of commit 5beacd168a - Show all commits

View File

@ -11,16 +11,16 @@ from .utils import GizmoUtils
class Handler: class Handler:
@classmethod @classmethod
def add_handler(cls): def add_handler(cls):
if 'handler' not in cls.G_SimpleDeformGizmoHandlerDit: if 'handler' not in cls.G_GizmoData:
cls.G_SimpleDeformGizmoHandlerDit['handler'] = bpy.types.SpaceView3D.draw_handler_add( cls.G_GizmoData['handler'] = bpy.types.SpaceView3D.draw_handler_add(
Draw3D.draw_bound_box, (), 'WINDOW', 'POST_VIEW') Draw3D().draw, (), 'WINDOW', 'POST_VIEW')
@classmethod @classmethod
def del_handler_text(cls): def del_handler_text(cls):
if 'handler_text' in cls.G_SimpleDeformGizmoHandlerDit: if 'handler_text' in cls.G_GizmoData:
bpy.types.SpaceView3D.draw_handler_remove( bpy.types.SpaceView3D.draw_handler_remove(
cls.G_SimpleDeformGizmoHandlerDit['handler_text'], 'WINDOW') cls.G_GizmoData['handler_text'], 'WINDOW')
cls.G_SimpleDeformGizmoHandlerDit.pop('handler_text') cls.G_GizmoData.pop('handler_text')
@classmethod @classmethod
def del_handler(cls): def del_handler(cls):
@ -33,54 +33,13 @@ class Handler:
cls.del_handler_text() cls.del_handler_text()
if 'handler' in cls.G_SimpleDeformGizmoHandlerDit: if 'handler' in cls.G_GizmoData:
bpy.types.SpaceView3D.draw_handler_remove( bpy.types.SpaceView3D.draw_handler_remove(
cls.G_SimpleDeformGizmoHandlerDit['handler'], 'WINDOW') cls.G_GizmoData['handler'], 'WINDOW')
cls.G_SimpleDeformGizmoHandlerDit.clear() cls.G_GizmoData.clear()
class Draw3D(GizmoUtils): class DrawPublic:
@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)
@ -96,6 +55,8 @@ class Draw3D(GizmoUtils):
batch.draw(shader) batch.draw(shader)
class DrawText:
font_info = { font_info = {
'font_id': 0, 'font_id': 0,
'handler': None, 'handler': None,
@ -115,7 +76,7 @@ class Draw3D(GizmoUtils):
f' which will cause the deformation of the simple deformation modifier.' f' which will cause the deformation of the simple deformation modifier.'
f' Please apply the scaling before deformation') f' Please apply the scaling before deformation')
if obj.scale == Vector((1, 1, 1)): if obj.scale == Vector((1, 1, 1)):
Handler.del_handler_text() cls.del_handler_text()
@classmethod @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): 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):
@ -124,56 +85,62 @@ class Draw3D(GizmoUtils):
blf.draw(font_id, text) blf.draw(font_id, text)
blf.color(font_id, *color) blf.color(font_id, *color)
@classmethod
def draw_box(cls, data, mat):
pref = cls.pref_()
coords = cls.matrix_calculation(mat,
cls.data_to_calculation(data))
cls.draw_3d_shader(coords, cls.G_INDICES, pref.bound_box_color)
@classmethod class Draw3D(GizmoUtils, DrawPublic, DrawText, Handler):
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))
@classmethod def draw(self):
def draw_limits_bound_box(cls): gpu.state.blend_set('ALPHA')
gpu.state.line_width_set(1)
pref = cls.pref_() gpu.state.blend_set('ALPHA')
handler_dit = cls.G_SimpleDeformGizmoHandlerDit gpu.state.depth_test_set('ALWAYS')
if 'draw_limits_bound_box' in handler_dit:
# draw limits_bound_box
mat, data = handler_dit['draw_limits_bound_box']
coords = cls.matrix_calculation(mat, cls.data_to_calculation(data))
cls.draw_3d_shader(coords,
cls.G_INDICES,
pref.limits_bound_box_color)
@classmethod context = bpy.context
def draw_limits_line(cls): if self.simple_deform_public_poll(context):
handler_dit = cls.G_SimpleDeformGizmoHandlerDit self.draw_3d(context)
if 'draw_line' in handler_dit:
line_pos, limits_pos, = handler_dit['draw_line'] def draw_3d(self, context):
# draw limits line obj = context.object # 活动物体
cls.draw_3d_shader(limits_pos, ((1, 0),), (1, 1, 0, 0.5))
# draw line self.draw_scale_text(obj)
cls.draw_3d_shader(line_pos, ((1, 0),), (1, 1, 0, 0.3)) if not self.modifier_origin_angle_is_available:
# draw pos self.draw_bound_box()
cls.draw_3d_shader([line_pos[1]], (), (0, 1, 0, 0.5), ...
shader_name='3D_UNIFORM_COLOR', draw_type='POINTS') if self.simple_deform_show_gizmo_poll(context):
# draw bound box
self.draw_bound_box()
# cls.draw_deform_mesh(obj, context)
self.draw_limits_line()
self.draw_limits_bound_box()
elif self.simple_deform_show_bend_axis_witch_poll(context):
self.draw_bound_box()
# self.new_empty(obj, modifier)
def draw_bound_box(self):
coords = self.matrix_calculation(self.obj_matrix_world,
self.tow_co_to_coordinate(self.get_bound_co_data()))
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):
line_pos, limits_pos, = self.modifier_limits_point
# draw limits line
self.draw_3d_shader(limits_pos, ((1, 0),), (1, 1, 0, 0.5))
# draw line
self.draw_3d_shader(line_pos, ((1, 0),), (1, 1, 0, 0.3))
# draw pos
self.draw_3d_shader([line_pos[1]], (), (0, 1, 0, 0.5),
shader_name='3D_UNIFORM_COLOR', draw_type='POINTS')
@classmethod @classmethod
def draw_deform_mesh(cls, ob, context): def draw_deform_mesh(cls, ob, context):
pref = cls.pref_() pref = cls.pref_()
handler_dit = cls.G_SimpleDeformGizmoHandlerDit handler_dit = cls.G_GizmoData
active = context.object.modifiers.active active = context.object.modifiers.active
# draw deform mesh # draw deform mesh
if 'draw' in handler_dit: if 'draw' in handler_dit:
@ -183,8 +150,9 @@ class Draw3D(GizmoUtils):
cls.draw_3d_shader( cls.draw_3d_shader(
pos, indices, pref.deform_wireframe_color) pos, indices, pref.deform_wireframe_color)
@classmethod def draw_scale_text(self, ob):
def draw_scale_text(cls, ob): ob = self.obj
if (ob.scale != Vector((1, 1, 1))) and ('handler_text' not in cls.G_SimpleDeformGizmoHandlerDit): dit = self.G_GizmoData
cls.G_SimpleDeformGizmoHandlerDit['handler_text'] = bpy.types.SpaceView3D.draw_handler_add( if (ob.scale != Vector((1, 1, 1))) and ('handler_text' not in dit):
cls.draw_str, (), 'WINDOW', 'POST_PIXEL') dit['handler_text'] = bpy.types.SpaceView3D.draw_handler_add(
self.draw_str, (), 'WINDOW', 'POST_PIXEL')

View File

@ -4,7 +4,7 @@ from .angle_and_factor import AngleGizmoGroup, AngleGizmo
from .bend_axis import BendAxiSwitchGizmoGroup, CustomGizmo from .bend_axis import BendAxiSwitchGizmoGroup, CustomGizmo
from .set_deform_axis import SetDeformGizmoGroup from .set_deform_axis import SetDeformGizmoGroup
from .up_down_limits_point import UpDownLimitsGizmo, UpDownLimitsGizmoGroup from .up_down_limits_point import UpDownLimitsGizmo, UpDownLimitsGizmoGroup
from ..draw import Handler from ..draw import Draw3D
class_list = ( class_list = (
UpDownLimitsGizmo, UpDownLimitsGizmo,
@ -23,9 +23,10 @@ register_class, unregister_class = bpy.utils.register_classes_factory(class_list
def register(): def register():
Draw3D.add_handler()
register_class() register_class()
def unregister(): def unregister():
Handler.del_handler() Draw3D.del_handler()
unregister_class() unregister_class()

View File

@ -5,10 +5,8 @@ from bpy.types import Gizmo
from bpy.types import ( from bpy.types import (
GizmoGroup, GizmoGroup,
) )
from mathutils import Vector
from ..draw import Handler from ..utils import GizmoUtils, GizmoGroupUtils
from ..utils import GizmoUtils
class AngleUpdate(GizmoUtils): class AngleUpdate(GizmoUtils):
@ -26,10 +24,16 @@ class AngleUpdate(GizmoUtils):
def update_gizmo_matrix(self, context): def update_gizmo_matrix(self, context):
matrix = context.object.matrix_world matrix = context.object.matrix_world
self.matrix_basis.translation = matrix @ Vector((self.generate_co_data()[1])) point = self.get_bound_co_data()[1]
self.matrix_basis = self.obj_matrix_world
self.matrix_basis.translation = matrix @ point
def update_header_text(self, context): def update_header_text(self, context):
text = self.translate_header_text('Angle', round(math.degrees(self.modifier_angle), 3)) if self.modifier_origin_angle_is_available:
value = round(math.degrees(self.modifier_angle), 3)
text = self.translate_header_text('Angle', value)
else:
text = self.translate_header_text('Coefficient', self.modifier.factor)
context.area.header_text_set(text) context.area.header_text_set(text)
@ -46,8 +50,8 @@ class AngleGizmo(Gizmo, AngleUpdate):
'draw_type', 'draw_type',
'mouse_dpi', 'mouse_dpi',
'empty_object', 'empty_object',
'init_mouse_y', 'init_mouse_region_y',
'init_mouse_x', 'init_mouse_region_x',
'custom_shape', 'custom_shape',
'int_value_angle', 'int_value_angle',
) )
@ -64,6 +68,8 @@ class AngleGizmo(Gizmo, AngleUpdate):
return {'RUNNING_MODAL'} return {'RUNNING_MODAL'}
def modal(self, context, event, tweak): def modal(self, context, event, tweak):
self.clear_cache()
self.update_header_text(context) self.update_header_text(context)
self.update_prop_value(event, tweak) self.update_prop_value(event, tweak)
return {'RUNNING_MODAL'} return {'RUNNING_MODAL'}
@ -74,24 +80,11 @@ class AngleGizmo(Gizmo, AngleUpdate):
self.target_set_value('angle', self.int_value_angle) self.target_set_value('angle', self.int_value_angle)
class AngleGizmoGroup(GizmoGroup, GizmoUtils, Handler): class AngleGizmoGroup(GizmoGroup, GizmoGroupUtils):
"""ShowGizmo """ShowGizmo
""" """
bl_idname = 'OBJECT_GGT_SimpleDeformGizmoGroup' bl_idname = 'OBJECT_GGT_SimpleDeformGizmoGroup'
bl_label = 'AngleGizmoGroup' bl_label = 'AngleGizmoGroup'
bl_space_type = 'VIEW_3D'
bl_region_type = 'WINDOW'
bl_options = {'3D',
# 'SCALE',
# 'DEPTH_3D',
# 'SELECT',
'PERSISTENT',
'SHOW_MODAL_ALL',
# 'EXCLUDE_MODAL',
# 'TOOL_INIT', # not show
# 'TOOL_FALLBACK_KEYMAP',
# 'VR_REDRAWS'
}
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
@ -118,7 +111,6 @@ class AngleGizmoGroup(GizmoGroup, GizmoUtils, Handler):
self.generate_gizmo_mode(add_data) self.generate_gizmo_mode(add_data)
def refresh(self, context): def refresh(self, context):
self.angle.target_set_prop('angle', self.angle.target_set_prop('angle',
context.object.modifiers.active, context.object.modifiers.active,
'angle') 'angle')

View File

@ -4,11 +4,10 @@ from bpy.types import GizmoGroup
from bpy_types import Gizmo from bpy_types import Gizmo
from mathutils import Euler, Vector from mathutils import Euler, Vector
from ..draw import Handler from ..utils import GizmoUtils, GizmoGroupUtils
from ..utils import GizmoUtils
class CustomGizmo(Gizmo, GizmoUtils, Handler): class CustomGizmo(Gizmo, GizmoUtils):
"""绘制自定义Gizmo""" """绘制自定义Gizmo"""
bl_idname = '_Custom_Gizmo' bl_idname = '_Custom_Gizmo'
draw_type: str draw_type: str
@ -18,10 +17,9 @@ class CustomGizmo(Gizmo, GizmoUtils, Handler):
self.draw_type = 'None_GizmoGroup_' self.draw_type = 'None_GizmoGroup_'
if not hasattr(self, 'custom_shape'): if not hasattr(self, 'custom_shape'):
self.custom_shape = {} self.custom_shape = {}
for i in self.G_GizmoCustomShapeDict: for i in self.G_CustomShape:
self.custom_shape[i] = self.new_custom_shape( self.custom_shape[i] = self.new_custom_shape(
'TRIS', self.G_GizmoCustomShapeDict[i]) 'TRIS', self.G_CustomShape[i])
self.add_handler()
def draw(self, context): def draw(self, context):
self.draw_custom_shape(self.custom_shape[self.draw_type]) self.draw_custom_shape(self.custom_shape[self.draw_type])
@ -34,26 +32,17 @@ class CustomGizmo(Gizmo, GizmoUtils, Handler):
return {'RUNNING_MODAL'} return {'RUNNING_MODAL'}
def modal(self, context, event, tweak): def modal(self, context, event, tweak):
self.add_handler()
self.update_bound_box(context.object)
self.update_empty_matrix() self.update_empty_matrix()
return {'RUNNING_MODAL'} return {'RUNNING_MODAL'}
class BendAxiSwitchGizmoGroup(GizmoGroup, GizmoUtils): class BendAxiSwitchGizmoGroup(GizmoGroup, GizmoGroupUtils):
"""绘制切换变型轴的 """绘制切换变型轴的
变换方向 变换方向
""" """
bl_idname = 'OBJECT_GGT_SimpleDeformGizmoGroup_display_bend_axis_switch_gizmo' bl_idname = 'OBJECT_GGT_SimpleDeformGizmoGroup_display_bend_axis_switch_gizmo'
bl_label = '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 @classmethod
def poll(cls, context): def poll(cls, context):
return cls.simple_deform_show_bend_axis_witch_poll(context) return cls.simple_deform_show_bend_axis_witch_poll(context)
@ -104,7 +93,7 @@ class BendAxiSwitchGizmoGroup(GizmoGroup, GizmoUtils):
def draw_prepare(self, context): def draw_prepare(self, context):
ob = context.object ob = context.object
mat = ob.matrix_world mat = ob.matrix_world
top, bottom, left, right, front, back = self.each_face_pos(mat) top, bottom, left, right, front, back = self.each_face_pos
rad = math.radians rad = math.radians
for_list = ( for_list = (

View File

@ -1,18 +1,12 @@
from bpy.types import GizmoGroup from bpy.types import GizmoGroup
from mathutils import Vector from mathutils import Vector
from utils import GizmoUtils from ..utils import GizmoGroupUtils
class SetDeformGizmoGroup(GizmoGroup, GizmoUtils): class SetDeformGizmoGroup(GizmoGroup, GizmoGroupUtils):
bl_idname = 'OBJECT_GGT_SetDeformGizmoGroup' bl_idname = 'OBJECT_GGT_SetDeformGizmoGroup'
bl_label = 'SetDeformGizmoGroup' bl_label = 'SetDeformGizmoGroup'
bl_space_type = 'VIEW_3D'
bl_region_type = 'WINDOW'
bl_options = {'3D',
'PERSISTENT',
'SHOW_MODAL_ALL',
}
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
@ -41,9 +35,9 @@ class SetDeformGizmoGroup(GizmoGroup, GizmoUtils):
def draw_prepare(self, context): def draw_prepare(self, context):
if 'co' in self.G_SimpleDeformGizmoHandlerDit: if 'co' in self.G_GizmoData:
def _mat(f): def _mat(f):
co = self.G_SimpleDeformGizmoHandlerDit['co'][0] co = self.G_GizmoData['co'][0]
co = (co[0] + (max(self.obj.dimensions) * f), co[1], co = (co[0] + (max(self.obj.dimensions) * f), co[1],
co[2] - (min(self.obj.dimensions) * 0.3)) co[2] - (min(self.obj.dimensions) * 0.3))
return self.obj_matrix_world @ Vector(co) return self.obj_matrix_world @ Vector(co)

View File

@ -6,11 +6,14 @@ from bpy.types import Gizmo, GizmoGroup
from bpy_extras import view3d_utils from bpy_extras import view3d_utils
from mathutils import Vector from mathutils import Vector
from ..draw import Handler from ..utils import GizmoUtils, GizmoGroupUtils
from ..utils import GizmoUtils
class GizmoProperty(GizmoUtils, Handler): class GizmoProperty(GizmoUtils):
ctrl_mode: str
int_value_up_limits: int
int_value_down_limits: int
@property @property
def is_up_limits_mode(self): def is_up_limits_mode(self):
return self.ctrl_mode == 'up_limits' return self.ctrl_mode == 'up_limits'
@ -19,130 +22,145 @@ class GizmoProperty(GizmoUtils, Handler):
def is_down_limits_mode(self): def is_down_limits_mode(self):
return self.ctrl_mode == 'down_limits' return self.ctrl_mode == 'down_limits'
@property
def origin_mode(self):
return self.SimpleDeformGizmo_origin_property_group.origin_mode
@property
def is_middle_mode(self):
return self.origin_mode in ('LIMITS_MIDDLE', 'MIDDLE')
@property
def limit_scope(self):
return self.pref.modifiers_limits_tolerance
@property
def difference_value(self):
return self.modifier_up_limits - self.modifier_down_limits
@property
def middle_value(self):
return (self.modifier_up_limits + self.modifier_down_limits) / 2
@property
def limits_min_value(self):
return self.modifier_up_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_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_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)
class GizmoUpdate(GizmoProperty): class GizmoUpdate(GizmoProperty):
def update_gizmo_matrix(self, context): def update_gizmo_matrix(self, context):
self._update_matrix_basis_to_obj() self.align_orientation_to_user_perspective(context)
self.align_point_to_limits_point()
co = self.generate_co_data() def align_orientation_to_user_perspective(self, context):
# calculation limits position rotation = context.space_data.region_3d.view_matrix.inverted().to_quaternion()
top, bottom, left, right, front, back = self.each_face_pos(mat) matrix = rotation.to_matrix().to_4x4()
(up, down), (up_, down_) = self.get_limits_pos( self.matrix_basis = matrix
mod, (top, bottom, left, right, front, back))
self._update_matrix_basis_translation(co, mat, up_, down_)
self.up = up def align_point_to_limits_point(self):
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 _update_matrix_basis_to_obj(self):
origin = self.modifier.origin
if origin:
self.matrix_basis = origin.matrix_world.normalized()
else:
self.matrix_basis = self.obj_matrix_world.normalized()
def _update_matrix_basis_translation(self, co, mat, up_, down_):
if self.is_up_limits_mode: if self.is_up_limits_mode:
self.matrix_basis.translation = up_ self.matrix_basis.translation = self.point_limits_up
elif self.is_down_limits_mode: elif self.is_down_limits_mode:
self.matrix_basis.translation = down_ self.matrix_basis.translation = self.point_limits_down
def delta_update(self, context, event, delta): 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(
x, y = view3d_utils.location_3d_to_region_2d( context.region, context.space_data.region_3d, self.up)
context.region, context.space_data.region_3d, self.up) x2, y2 = view3d_utils.location_3d_to_region_2d(
x2, y2 = view3d_utils.location_3d_to_region_2d( context.region, context.space_data.region_3d, self.down)
context.region, context.space_data.region_3d, self.down)
mouse_line_distance = math.sqrt(((event.mouse_region_x - x2) ** 2) + mouse_line_distance = math.sqrt(((event.mouse_region_x - x2) ** 2) +
((event.mouse_region_y - y2) ** 2)) ((event.mouse_region_y - y2) ** 2))
straight_line_distance = math.sqrt(((x2 - x) ** 2) + straight_line_distance = math.sqrt(((x2 - x) ** 2) +
((y2 - y) ** 2)) ((y2 - y) ** 2))
delta = mouse_line_distance / straight_line_distance + 0 delta = mouse_line_distance / straight_line_distance + 0
v_up = Vector((x, y)) v_up = Vector((x, y))
v_down = Vector((x2, y2)) v_down = Vector((x2, y2))
limits_angle = v_up - v_down limits_angle = v_up - v_down
mouse_v = Vector((event.mouse_region_x, event.mouse_region_y)) 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
mouse_angle = mouse_v - v_down
angle_ = mouse_angle.angle(limits_angle)
if angle_ > (math.pi / 2):
delta = 0
return delta return delta
def set_down_value(self, data, mu): def set_down_value(self, event):
up_limits, down_limits, delta, middle, min_value, max_value, limit_scope, difference_value, event, origin_mode = data value = self.get_down_limits_value(event)
value = self.value_limit(delta, max_value=mu - limit_scope if middle else max_value)
self.target_set_value('down_limits', value) self.target_set_value('down_limits', value)
if event.ctrl: if event.ctrl:
self.target_set_value( self.target_set_value(
'up_limits', value + difference_value) 'up_limits', value + self.difference_value)
elif middle:
if origin_mode == 'LIMITS_MIDDLE': elif self.is_middle_mode:
if self.origin_mode == 'LIMITS_MIDDLE':
mu = self.middle_value
self.target_set_value('up_limits', mu - (value - mu)) self.target_set_value('up_limits', mu - (value - mu))
elif origin_mode == 'MIDDLE': elif self.origin_mode == 'MIDDLE':
self.target_set_value('up_limits', 1 - value) self.target_set_value('up_limits', 1 - value)
else: else:
self.target_set_value('up_limits', up_limits) self.target_set_value('up_limits', self.modifier_up_limits)
else: else:
self.target_set_value('up_limits', up_limits) self.target_set_value('up_limits', self.modifier_up_limits)
def set_up_value(self, data, mu): def set_up_value(self, event):
up_limits, down_limits, delta, middle, min_value, max_value, limit_scope, difference_value, event, origin_mode = data value = self.get_up_limits_value(event)
value = self.value_limit(delta, min_value=mu + limit_scope if middle else min_value)
self.target_set_value('up_limits', value) self.target_set_value('up_limits', value)
if event.ctrl: if event.ctrl:
self.target_set_value( self.target_set_value(
'down_limits', value - difference_value) 'down_limits', value - self.difference_value)
elif middle: elif self.is_middle_mode:
if origin_mode == 'LIMITS_MIDDLE': if self.origin_mode == 'LIMITS_MIDDLE':
mu = self.middle_value
self.target_set_value('down_limits', mu - (value - mu)) self.target_set_value('down_limits', mu - (value - mu))
elif origin_mode == 'MIDDLE': elif self.origin_mode == 'MIDDLE':
self.target_set_value('down_limits', 1 - value) self.target_set_value('down_limits', 1 - value)
else: else:
self.target_set_value('down_limits', down_limits) self.target_set_value('down_limits', self.modifier_down_limits)
else: else:
self.target_set_value('down_limits', down_limits) self.target_set_value('down_limits', self.modifier_down_limits)
def set_prop_value(self, data): def set_prop_value(self, event):
up_limits, down_limits, delta, middle, min_value, max_value, limit_scope, difference_value, event, origin_mode = data if self.is_up_limits_mode:
mu = (up_limits + down_limits) / 2 self.set_up_value(event)
if self.is_angle_mode:
value = self.int_value_angle - delta
v = self.snap_value(value, event)
print(v)
self.target_set_value('angle', v)
elif self.is_up_limits_mode:
self.set_up_value(data, mu)
elif self.is_down_limits_mode: elif self.is_down_limits_mode:
self.set_down_value(data, mu) self.set_down_value(event)
def update_header_text(self, context, mod, origin, up_limits, down_limits): def update_header_text(self, context):
translate: Callable[[Any], str] = lambda t: bpy.app.translations.pgettext(t) origin = self.SimpleDeformGizmo_origin_property_group
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
def t_(a, b): t = self.translate_header_text
return translate(a) + ':{}'.format(round(b, 3)) text = self.translate_text(mode) + ' '
text = translate(mode) + ' ' if self.is_up_limits_mode:
value = round(self.modifier_up_limits, 3)
if self.modifier_is_use_angle_value and self.is_angle_mode: text += t('Upper limit', value)
text += t_()
elif self.is_up_limits_mode:
text += t_('Upper limit', up_limits)
elif self.is_down_limits_mode: elif self.is_down_limits_mode:
text += t_('Down limit', down_limits) value = round(self.modifier_down_limits, 3)
else: text += t('Down limit', value)
text += t_('Coefficient', mod.factor)
context.area.header_text_set(text) context.area.header_text_set(text)
def event_ops(self, event, ob, origin): def event_ops(self, event, ob, origin):
@ -161,16 +179,13 @@ class GizmoUpdate(GizmoProperty):
elif event.type == 'A': elif event.type == 'A':
self.pref.display_bend_axis_switch_gizmo = True self.pref.display_bend_axis_switch_gizmo = True
return {'FINISHED'} return {'FINISHED'}
self.add_handler()
return {'RUNNING_MODAL'} return {'RUNNING_MODAL'}
class UpDownLimitsGizmo(Gizmo, GizmoUpdate): class UpDownLimitsGizmo(Gizmo, GizmoUpdate):
"""显示轴向切换拖动点Gizmo(两个点)
"""
bl_idname = 'UpDownLimitsGizmo' bl_idname = 'UpDownLimitsGizmo'
bl_label = '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},
@ -183,84 +198,54 @@ class UpDownLimitsGizmo(Gizmo, GizmoUpdate):
'draw_type', 'draw_type',
'mouse_dpi', 'mouse_dpi',
'ctrl_mode', 'ctrl_mode',
'empty_object', 'init_mouse_region_y',
'init_mouse_y', 'init_mouse_region_x',
'init_mouse_x',
'custom_shape', 'custom_shape',
'value_deform_axis',
'int_value_up_limits', 'int_value_up_limits',
'int_value_down_limits', 'int_value_down_limits',
) )
def setup(self): def setup(self):
self.generate_co_data()
self.draw_type = 'None_GizmoGroup_'
self.ctrl_mode = 'up_limits' # up_limits , down_limits
self.mouse_dpi = 10 self.mouse_dpi = 10
self.init_setup()
def invoke(self, context, event): def invoke(self, context, event):
self.init_invoke(context, event) self.init_invoke(context, event)
mod = context.object.modifiers.active
limits = mod.limits
up_limits = limits[1]
down_limits = limits[0]
if 'up_limits' == self.ctrl_mode: if self.is_up_limits_mode:
self.int_value_up_limits = up_limits self.int_value_up_limits = up_limits = self.modifier_up_limits
self.target_set_value('up_limits', self.int_value_up_limits) self.target_set_value('up_limits', up_limits)
elif 'down_limits' == self.ctrl_mode: elif self.is_down_limits_mode:
self.int_value_down_limits = down_limits self.int_value_down_limits = down_limits = self.modifier_down_limits
self.target_set_value('down_limits', self.int_value_down_limits) self.target_set_value('down_limits', down_limits)
return {'RUNNING_MODAL'} return {'RUNNING_MODAL'}
def exit(self, context, cancel): def exit(self, context, cancel):
context.area.header_text_set(None) context.area.header_text_set(None)
if cancel: if cancel:
if 'deform_axis' == self.ctrl_mode: if self.is_up_limits_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) self.target_set_value('up_limits', self.int_value_up_limits)
elif 'down_limits' == self.ctrl_mode: elif self.is_down_limits_mode:
self.target_set_value( self.target_set_value(
'down_limits', self.int_value_down_limits) 'down_limits', self.int_value_down_limits)
def modal(self, context, event, tweak): def modal(self, context, event, tweak):
# self.update_bound_box(context.object) self.clear_cache()
#
# ob = context.object self.set_prop_value(event)
# mod = ob.modifiers.active if self.SimpleDeformGizmo_is_use_empty_as_axis:
# limits = mod.limits self.new_empty(self.obj, self.modifier)
# up_limits = limits[1] # self.update_empty_matrix()
# 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
#
# delta = self.get_delta(event, tweak)
# 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.update_header_text(context, mod, origin, up_limits, down_limits)
# self.add_handler()
# return self.event_ops(event, ob, origin) # return self.event_ops(event, ob, origin)
return {'RUNNING_MODAL'} return {'RUNNING_MODAL'}
class UpDownLimitsGizmoGroup(GizmoGroup, GizmoUtils, Handler): class UpDownLimitsGizmoGroup(GizmoGroup, GizmoGroupUtils):
bl_idname = 'OBJECT_GGT_SimpleDeformGizmoGroup' bl_idname = 'OBJECT_GGT_UpDownLimitsGizmoGroup'
bl_label = 'UpDownLimitsGizmoGroup'
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
@ -268,8 +253,7 @@ class UpDownLimitsGizmoGroup(GizmoGroup, GizmoUtils, Handler):
def setup(self, context): def setup(self, context):
sd_name = UpDownLimitsGizmo.bl_idname sd_name = UpDownLimitsGizmo.bl_idname
gizmo_data = [
add_data = (
('up_limits', ('up_limits',
sd_name, sd_name,
{'ctrl_mode': 'up_limits', {'ctrl_mode': 'up_limits',
@ -294,10 +278,14 @@ class UpDownLimitsGizmoGroup(GizmoGroup, GizmoUtils, Handler):
'use_draw_modal': True, 'use_draw_modal': True,
'scale_basis': 0.1, 'scale_basis': 0.1,
'use_draw_value': True, }), 'use_draw_value': True, }),
) ]
self.generate_gizmo_mode(gizmo_data)
def refresh(self, context): def refresh(self, context):
pro = context.object.SimpleDeformGizmo_PropertyGroup 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)
self.down_limits.target_set_prop('down_limits', self.down_limits.target_set_prop('down_limits',
pro, pro,
'down_limits') 'down_limits')

View File

@ -47,8 +47,6 @@ class DeformAxisOperator(Operator, GizmoUtils):
if not event.ctrl: if not event.ctrl:
self.pref.display_bend_axis_switch_gizmo = False self.pref.display_bend_axis_switch_gizmo = False
GizmoUtils.update_bound_box(context.object)
return {'FINISHED'} return {'FINISHED'}

View File

@ -27,7 +27,7 @@ class SimpleDeformGizmoAddonPreferences(AddonPreferences, GizmoUtils):
bound_box_color: FloatVectorProperty( bound_box_color: FloatVectorProperty(
name='Bound Box', name='Bound Box',
description='Draw Bound Box Color', description='Draw Bound Box Color',
default=(1, 0, 0, 0.1), default=(1, 0, 0, 0.5),
soft_max=1, soft_max=1,
soft_min=0, soft_min=0,
size=4, size=4,

View File

@ -3,18 +3,22 @@
import bpy import bpy
from bpy.app.handlers import depsgraph_update_post, persistent from bpy.app.handlers import depsgraph_update_post, persistent
from .utils import GizmoUpdate
@persistent @persistent
def remove_not_use_empty(scene, dep): def remove_not_use_empty(scene, dep):
"""循环场景内的所有物体,找出没用的空物体并删掉 """循环场景内的所有物体,找出没用的空物体并删掉
""" """
GizmoUpdate.clear_cache()
remove_name: str = "ViewSimpleDeformGizmo__Empty_" remove_name: str = "ViewSimpleDeformGizmo__Empty_"
context = bpy.context context = bpy.context
for obj in context.scene.objects: if GizmoUpdate.simple_deform_modifier_is_simple(context):
is_empty = obj.type == "EMPTY" for obj in context.scene.objects:
not_parent = not obj.parent is_empty = obj.type == "EMPTY"
if remove_name in obj.name and not_parent and is_empty: not_parent = not obj.parent
bpy.data.objects.remove(obj) # remove object if remove_name in obj.name and not_parent and is_empty:
bpy.data.objects.remove(obj) # remove object
def register(): def register():

View File

@ -2,8 +2,8 @@
import math import math
import uuid import uuid
from functools import cache
from os.path import dirname, basename, realpath from os.path import dirname, basename, realpath
from typing import Callable, Any
import bpy import bpy
import numpy as np import numpy as np
@ -14,8 +14,8 @@ from mathutils import Vector, Matrix, Euler
class PublicData: class PublicData:
"""Public data class, all fixed data will be placed here """Public data class, all fixed data will be placed here
""" """
G_GizmoCustomShapeDict = {} G_CustomShape = {}
G_SimpleDeformGizmoHandlerDit = {} G_GizmoData = {}
G_INDICES = ( G_INDICES = (
(0, 1), (0, 2), (1, 3), (2, 3), (0, 1), (0, 2), (1, 3), (2, 3),
(4, 5), (4, 6), (5, 7), (6, 7), (4, 5), (4, 6), (5, 7), (6, 7),
@ -47,7 +47,7 @@ class PublicData:
import os import os
json_path = os.path.join(os.path.dirname(__file__), "gizmo.json") json_path = os.path.join(os.path.dirname(__file__), "gizmo.json")
with open(json_path, "r") as file: with open(json_path, "r") as file:
cls.G_GizmoCustomShapeDict = json.load(file) cls.G_CustomShape = json.load(file)
@staticmethod @staticmethod
def from_mesh_get_triangle_face_co(mesh: 'bpy.types.Mesh') -> list: def from_mesh_get_triangle_face_co(mesh: 'bpy.types.Mesh') -> list:
@ -94,32 +94,41 @@ class PublicClass(PublicData):
class PublicPoll(PublicClass): class PublicPoll(PublicClass):
@classmethod @classmethod
def simple_deform_public_poll(cls, context: 'bpy.types.context') -> bool: def simple_deform_modifier_is_simple(cls, context):
"""Public poll """
In 3D View
Active Object in ('MESH', 'LATTICE') Active Object in ('MESH', 'LATTICE')
Active Modifier Type Is 'SIMPLE_DEFORM' and show_viewport Active Modifier Type Is 'SIMPLE_DEFORM' and show_viewport
return True :param context:bpy.types.Object
:return:
""" """
obj = context.object obj = context.object
if not obj: if not obj:
return False return False
mod = obj.modifiers.active mod = obj.modifiers.active
if not mod: if not mod:
return False 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_obj_type = obj and (obj.type in ('MESH', 'LATTICE'))
available_modifiers_type = mod and (mod.type == 'SIMPLE_DEFORM') available_modifiers_type = mod and (mod.type == 'SIMPLE_DEFORM')
is_available_obj = available_modifiers_type and available_obj_type is_available_obj = available_modifiers_type and available_obj_type
is_obj_mode = context.mode == 'OBJECT' is_obj_mode = context.mode == 'OBJECT'
show_mod = mod.show_viewport show_mod = mod.show_viewport
return is_available_obj and is_obj_mode and show_gizmo and show_mod return is_available_obj and is_obj_mode and show_mod
@classmethod
def simple_deform_public_poll(cls, context: 'bpy.types.context') -> bool:
"""Public poll
In 3D View
return True
"""
space = context.space_data
if not space:
return False
show_gizmo = space.show_gizmo if space.type == 'VIEW_3D' else True
obj = cls.simple_deform_modifier_is_simple(context)
return obj and show_gizmo
@classmethod @classmethod
def _simple_deform_modifier_is_bend_poll(cls, context): def _simple_deform_modifier_is_bend_poll(cls, context):
@ -250,12 +259,14 @@ class PublicUtils(PublicPoll):
b = mat @ Vector((max_x, min_y, min_z)) b = mat @ Vector((max_x, min_y, min_z))
c = mat @ Vector((min_x, max_y, min_z)) c = mat @ Vector((min_x, max_y, min_z))
d = mat @ Vector((min_x, min_y, max_z)) d = mat @ Vector((min_x, min_y, max_z))
return ((aa + bb) / Vector((2, 2, 2)) for aa, bb in ((a, d) point_list = ((a, d),
(c, b) (c, b),
(c, d) (c, d),
(a, b) (a, b),
(d, b) (d, b),
(c, a))) (c, a),)
return list((aa + bb) / 2 for (aa, bb) in point_list)
@classmethod @classmethod
def translate_text(cls, text): def translate_text(cls, text):
@ -267,35 +278,6 @@ class PublicUtils(PublicPoll):
class GizmoClassMethod(PublicUtils): class GizmoClassMethod(PublicUtils):
@classmethod
def get_origin_property_group(cls, mod, ob):
if mod.origin:
return mod.origin.SimpleDeformGizmo_PropertyGroup
else:
return ob.SimpleDeformGizmo_PropertyGroup
@classmethod
def get_up_down_return_list(cls, mod, axis, up_, down_, data):
top, bottom, left, right, front, back = data
if 'BEND' == mod.deform_method:
if axis in ('X', 'Y'):
top = up_
bottom = down_
elif axis == 'Z':
right = up_
left = down_
else:
if axis == 'X':
right = up_
left = down_
elif axis == 'Y':
back = up_
front = down_
elif axis == 'Z':
top = up_
bottom = down_
return top, bottom, left, right, front, back
@classmethod @classmethod
def new_empty(cls, obj, mod): def new_empty(cls, obj, mod):
origin = mod.origin origin = mod.origin
@ -355,52 +337,6 @@ class GizmoClassMethod(PublicUtils):
elif origin_mode == 'MIDDLE': elif origin_mode == 'MIDDLE':
empty_object.matrix_world.translation = (up + down) / tow empty_object.matrix_world.translation = (up + down) / tow
@classmethod
def get_up_down(cls, mod, axis, top, bottom, left, right, front, back):
if 'BEND' == mod.deform_method:
if axis in ('X', 'Y'):
return top, bottom
elif axis == 'Z':
return right, left
else:
if axis == 'X':
return right, left
elif axis == 'Y':
return back, front
elif axis == 'Z':
return top, bottom
@classmethod
def get_limits_pos(cls, mod, data):
top, bottom, left, right, front, back = data
up_limits = mod.limits[1]
down_limits = mod.limits[0]
axis = mod.deform_axis
if mod.origin:
vector_axis = cls.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 = cls.point_to_angle(i, j, f, axis_)
if abs(angle - 180) < 0.00001:
up, down = j, i
elif abs(angle) < 0.00001:
up, down = i, j
else:
up, down = cls.get_up_down(mod, axis, top, bottom,
left, right, front, back)
ex = lambda a: down + ((up - down) * Vector((a, a, a)))
up_ = ex(up_limits)
down_ = ex(down_limits)
return (up, down), (up_, down_)
@classmethod @classmethod
def get_vector_axis(cls, mod): def get_vector_axis(cls, mod):
axis = mod.deform_axis axis = mod.deform_axis
@ -415,151 +351,44 @@ class GizmoClassMethod(PublicUtils):
return vector_axis return vector_axis
@classmethod @classmethod
def generate_co_data(cls): def get_bound_co_data(cls):
handler_dit = cls.G_SimpleDeformGizmoHandlerDit if 'co' not in cls.G_GizmoData:
cls.G_GizmoData['co'] = cls.get_mesh_max_min_co(
if 'co' not in handler_dit:
handler_dit['co'] = cls.get_mesh_max_min_co(
bpy.context.object) bpy.context.object)
return handler_dit['co'] return cls.G_GizmoData['co']
class GizmoUpdate(GizmoClassMethod):
@classmethod @classmethod
def update_matrix(cls, mod, ob): def tow_co_to_coordinate(cls, data):
if mod.deform_method == 'BEND': ((min_x, min_y, min_z), (max_x, max_y, max_z)) = data
cls.new_empty(ob, mod) return (
if mod.origin: Vector((max_x, min_y, min_z)),
empty_object = mod.origin Vector((min_x, min_y, min_z)),
modifiers_co = cls.G_SimpleDeformGizmoHandlerDit['modifiers_co'] Vector((max_x, max_y, min_z)),
for index, mod_name in enumerate(modifiers_co): Vector((min_x, max_y, min_z)),
co_items = list(modifiers_co.items()) Vector((max_x, min_y, max_z)),
if mod.name == mod_name: Vector((min_x, min_y, max_z)),
data = co_items[index - 1][1] if ( Vector((max_x, max_y, max_z)),
index or (index != 1)) else modifiers_co['co'] Vector((min_x, max_y, max_z))
(up, down), (up_, down_) = cls.get_limits_pos( )
mod, cls.co_to_direction(ob.matrix_world.copy(), data))
origin_mode = cls.get_origin_property_group(
mod, ob).origin_mode
cls.set_empty_obj_matrix(
origin_mode, empty_object, up_, down_, up, down)
@classmethod
def update_empty_matrix(cls):
ob = bpy.context.object
for mod in ob.modifiers:
if mod.type == 'SIMPLE_DEFORM':
cls.update_matrix(mod, ob)
@classmethod class PublicProperty(GizmoClassMethod):
def update_bound_box(cls, obj: 'bpy.types.Object'):
context = bpy.context
data = bpy.data
matrix = obj.matrix_world.copy() # 物体矩阵
# add simple_deform mesh
(min_x, min_y, min_z), (max_x, max_y,
max_z) = cls.get_mesh_max_min_co(obj)
vertexes = ((max_x, min_y, min_z),
(min_x, min_y, min_z),
(max_x, max_y, min_z),
(min_x, max_y, min_z),
(max_x, min_y, max_z),
(min_x, min_y, max_z),
(max_x, max_y, max_z),
(min_x, max_y, max_z))
name = cls.G_NAME
if data.objects.get(name):
data.objects.remove(data.objects.get(name))
if data.meshes.get(name): def __from_up_down_point_get_limits_point(self, up_point, down_point):
data.meshes.remove(data.meshes.get(name))
mesh = data.meshes.new(name)
mesh.from_pydata(vertexes, cls.G_INDICES, [])
mesh.update()
new_object = data.objects.new(name, mesh) def ex(a):
return down_point + ((up_point - down_point) * Vector((a, a, a)))
cls.link_obj_to_active_collection(new_object) up_limits = ex(self.modifier_up_limits)
down_limits = ex(self.modifier_down_limits)
return up_limits, down_limits
if new_object.parent != obj: @cache
new_object.parent = obj def _get_limits_point_and_bound_box_co(self):
top, bottom, left, right, front, back = self.each_face_pos
new_object.modifiers.clear() mod = self.modifier
subdivision = new_object.modifiers.new('1', 'SUBSURF') g_l = self.__from_up_down_point_get_limits_point
subdivision.levels = 7 if self.modifier.origin:
cls.G_SimpleDeformGizmoHandlerDit['modifiers_co'] = {}
cls.G_SimpleDeformGizmoHandlerDit['modifiers_co']['co'] = (
min_x, min_y, min_z), (max_x, max_y, max_z)
for mo in context.object.modifiers:
if mo.type == 'SIMPLE_DEFORM':
simple_deform = new_object.modifiers.new(
mo.name, 'SIMPLE_DEFORM')
simple_deform.deform_method = mo.deform_method
simple_deform.deform_axis = mo.deform_axis
simple_deform.lock_x = mo.lock_x
simple_deform.lock_y = mo.lock_y
simple_deform.lock_z = mo.lock_z
simple_deform.origin = mo.origin
simple_deform.limits[1] = mo.limits[1]
simple_deform.limits[0] = mo.limits[0]
simple_deform.angle = mo.angle
simple_deform.show_viewport = mo.show_viewport
obj = PublicUtils.get_depsgraph(new_object)
cls.G_SimpleDeformGizmoHandlerDit['modifiers_co'][mo.name] = cls.get_mesh_max_min_co(
obj)
new_object.hide_set(True)
new_object.hide_viewport = False
new_object.hide_select = True
new_object.hide_render = True
new_object.hide_viewport = True
new_object.hide_set(True)
ver_len = obj.data.vertices.__len__()
edge_len = obj.data.edges.__len__()
if 'numpy_data' not in cls.G_SimpleDeformGizmoHandlerDit:
cls.G_SimpleDeformGizmoHandlerDit['numpy_data'] = {}
numpy_data = cls.G_SimpleDeformGizmoHandlerDit['numpy_data']
key = (ver_len, edge_len)
if key in numpy_data:
list_edges, list_vertices = numpy_data[key]
else:
list_edges = np.zeros(edge_len * 2, dtype=np.int32)
list_vertices = np.zeros(ver_len * 3, dtype=np.float32)
numpy_data[key] = (list_edges, list_vertices)
obj.data.vertices.foreach_get('co', list_vertices)
ver = list_vertices.reshape((ver_len, 3))
ver = np.insert(ver, 3, 1, axis=1).T
ver[:] = np.dot(matrix, ver)
ver /= ver[3, :]
ver = ver.T
ver = ver[:, :3]
obj.data.edges.foreach_get('vertices', list_edges)
indices = list_edges.reshape((edge_len, 2))
limits = context.object.modifiers.active.limits[:]
modifiers = [getattr(context.object.modifiers.active, i)
for i in cls.G_MODIFIERS_PROPERTY]
cls.G_SimpleDeformGizmoHandlerDit['draw'] = (ver, indices, matrix, modifiers, limits)
@classmethod
def update_co_data(cls, ob, mod):
handler_dit = cls.G_SimpleDeformGizmoHandlerDit
if 'modifiers_co' in cls.G_SimpleDeformGizmoHandlerDit and ob.type in ('MESH', 'LATTICE'):
modifiers_co = cls.G_SimpleDeformGizmoHandlerDit['modifiers_co']
for index, mod_name in enumerate(modifiers_co):
co_items = list(modifiers_co.items())
if mod.name == mod_name:
cls.G_SimpleDeformGizmoHandlerDit['co'] = co_items[index - 1][1] if (index or (index != 1)) else \
modifiers_co['co']
def update_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) vector_axis = self.get_vector_axis(mod)
origin_mat = mod.origin.matrix_world.to_3x3() origin_mat = mod.origin.matrix_world.to_3x3()
axis_ = origin_mat @ vector_axis axis_ = origin_mat @ vector_axis
@ -569,37 +398,86 @@ class GizmoUpdate(GizmoClassMethod):
j = point_lit[f][1] j = point_lit[f][1]
angle = self.point_to_angle(i, j, f, axis_) angle = self.point_to_angle(i, j, f, axis_)
if abs(angle - 180) < 0.00001: if abs(angle - 180) < 0.00001:
point_lit[f][1], point_lit[f][0] = up_, down_ up_point, down_point = j, i
up_limits, down_limits = g_l(j, i)
point_lit[f][1], point_lit[f][0] = up_limits, down_limits
elif abs(angle) < 0.00001: elif abs(angle) < 0.00001:
point_lit[f][0], point_lit[f][1] = up_, down_ up_point, down_point = i, j
up_limits, down_limits = g_l(i, j)
point_lit[f][0], point_lit[f][1] = up_limits, down_limits
[[top, bottom], [left, right], [front, back]] = point_lit [[top, bottom], [left, right], [front, back]] = point_lit
else: else:
top, bottom, left, right, front, back = self.get_up_down_return_list( axis = self.modifier_deform_axis
mod, axis, up_, down_, data) if 'BEND' == self.modifier.deform_method:
data = top, bottom, left, right, front, back if axis in ('X', 'Y'):
(top, bottom, left, right, front, up_point, down_point = top, bottom
back) = self.matrix_calculation(mat.inverted(), data) top, bottom = up_limits, down_limits = g_l(top, bottom)
self.G_SimpleDeformGizmoHandlerDit['draw_limits_bound_box'] = ( elif axis == 'Z':
mat, ((right[0], back[1], top[2]), (left[0], front[1], bottom[2],))) up_point, down_point = right, left
right, left = up_limits, down_limits = g_l(right, left)
else:
if axis == 'X':
up_point, down_point = right, left
right, left = up_limits, down_limits = g_l(right, left)
elif axis == 'Y':
up_point, down_point = back, front
back, front = up_limits, down_limits = g_l(back, front)
def update_bound_box_wireframe(self): elif axis == 'Z':
... up_point, down_point = top, bottom
top, bottom = up_limits, down_limits = g_l(top, bottom)
def update_up_down_limits_wireframe(self): (top, bottom, left,
... right, front, back) = self.matrix_calculation(self.obj_matrix_world.inverted(),
(top, bottom, left, right, front, back))
def update_deform_wireframe(self): points = ((up_point, down_point), (up_limits, down_limits))
... each_point = ((right[0], back[1], top[2]), (left[0], front[1], bottom[2],))
box_bound_point = self.matrix_calculation(self.obj_matrix_world, self.tow_co_to_coordinate(each_point))
return points, box_bound_point
# ----------------------
class PublicProperty(GizmoUpdate): @cache
def _each_face_pos(self, mat):
return self.co_to_direction(mat, self.get_bound_co_data())
@classmethod @classmethod
def each_face_pos(cls, mat: 'Matrix' = None): def clear_cache(cls):
if mat is None: cls._each_face_pos.cache_clear()
mat = Matrix() cls._get_limits_point_and_bound_box_co.cache_clear()
return cls.co_to_direction(mat, cls.G_SimpleDeformGizmoHandlerDit['co']) cls.clear_data()
@classmethod
def clear_data(cls):
cls.G_GizmoData.clear()
# --------------- Cache Data ----------------------
@property
def each_face_pos(self):
return self._each_face_pos(self.obj_matrix_world)
@property
def modifier_limits_point(self):
points, _ = self._get_limits_point_and_bound_box_co()
return points
@property
def modifier_limits_bound_box(self):
_, bound = self._get_limits_point_and_bound_box_co()
return bound
@property
def modifier_origin_angle_is_available(self):
try:
self._get_limits_point_and_bound_box_co()
return True
except UnboundLocalError:
return False
# --------------- Compute Data ----------------------
@property @property
def obj(self): def obj(self):
return bpy.context.object return bpy.context.object
@ -607,7 +485,12 @@ class PublicProperty(GizmoUpdate):
@property @property
def obj_matrix_world(self): def obj_matrix_world(self):
if self.obj: if self.obj:
return self.obj.matrix_world mat = self.obj.matrix_world.copy()
mat.freeze()
return mat
mat = Matrix()
mat.freeze()
return mat
@property @property
def modifier(self): def modifier(self):
@ -628,64 +511,185 @@ class PublicProperty(GizmoUpdate):
if mod: if mod:
return mod.angle return mod.angle
@property
def active_modifier_is_simple_deform(self):
return self.modifier and self.modifier.type == 'SIMPLE_DEFORM'
@property @property
def modifier_is_use_angle_value(self): def modifier_is_use_angle_value(self):
if self.active_modifier_is_simple_deform: if self.active_modifier_is_simple_deform:
return self.modifier.deform_method in ('TWIST', 'BEND') return self.modifier.deform_method in ('TWIST', 'BEND')
@property
def modifier_up_limits(self):
if self.modifier:
return self.modifier.limits[1]
@property
def modifier_down_limits(self):
if self.modifier:
return self.modifier.limits[0]
@property
def active_modifier_is_simple_deform(self):
return self.modifier and self.modifier.type == 'SIMPLE_DEFORM'
# ----- point
@property @property
def point_up(self): def point_up(self):
... return self.modifier_limits_point[0][0]
@property @property
def point_down(self): def point_down(self):
... return self.modifier_limits_point[0][1]
@property @property
def point_limits_up(self): def point_limits_up(self):
... return self.modifier_limits_point[1][0]
@property @property
def point_limits_down(self): def point_limits_down(self):
... return self.modifier_limits_point[1][1]
# ------
@property @property
def point_top(self): def SimpleDeformGizmo_origin_property_group(self):
... mod = self.modifier
if mod.origin:
return mod.origin.SimpleDeformGizmo_PropertyGroup
else:
return self.obj.SimpleDeformGizmo_PropertyGroup
@property @property
def point_bottom(self): def SimpleDeformGizmo_is_use_empty_as_axis(self):
... return self.SimpleDeformGizmo_origin_property_group.origin_mode != 'NOT'
@property
def point_left(self):
...
@property class GizmoUpdate(PublicProperty):
def point_right(self):
...
@property
def point_front(self):
...
@property
def point_back(self):
...
@classmethod @classmethod
def clear_cache(cls): def update_matrix(cls, mod, ob):
if mod.deform_method == 'BEND':
cls.new_empty(ob, mod)
if mod.origin:
empty_object = mod.origin
modifiers_co = cls.G_GizmoData['modifiers_co']
for index, mod_name in enumerate(modifiers_co):
co_items = list(modifiers_co.items())
if mod.name == mod_name:
data = co_items[index - 1][1] if (
index or (index != 1)) else modifiers_co['co']
(up, down), (up_, down_) = cls.get_limits_pos(
mod, cls.co_to_direction(ob.matrix_world.copy(), data))
origin_mode = cls.SimpleDeformGizmo_origin_property_group(
mod, ob).origin_mode
cls.set_empty_obj_matrix(
origin_mode, empty_object, up_, down_, up, down)
# @classmethod
# def update_empty_matrix(cls):
# ob = bpy.context.object
# for mod in ob.modifiers:
# if mod.type == 'SIMPLE_DEFORM':
# cls.update_matrix(mod, ob)
#
# @classmethod
# context = bpy.context
# data = bpy.data
# matrix = obj.matrix_world.copy() # 物体矩阵
# # add simple_deform mesh
# (min_x, min_y, min_z), (max_x, max_y,
# max_z) = cls.get_mesh_max_min_co(obj)
# vertexes = ((max_x, min_y, min_z),
# (min_x, min_y, min_z),
# (max_x, max_y, min_z),
# (min_x, max_y, min_z),
# (max_x, min_y, max_z),
# (min_x, min_y, max_z),
# (max_x, max_y, max_z),
# (min_x, max_y, max_z))
# name = cls.G_NAME
# if data.objects.get(name):
# data.objects.remove(data.objects.get(name))
#
# if data.meshes.get(name):
# data.meshes.remove(data.meshes.get(name))
# mesh = data.meshes.new(name)
# mesh.from_pydata(vertexes, cls.G_INDICES, [])
# mesh.update()
#
# new_object = data.objects.new(name, mesh)
#
# cls.link_obj_to_active_collection(new_object)
#
# if new_object.parent != obj:
# new_object.parent = obj
#
# new_object.modifiers.clear()
# subdivision = new_object.modifiers.new('1', 'SUBSURF')
# subdivision.levels = 7
# cls.G_GizmoData['modifiers_co'] = {}
# cls.G_GizmoData['modifiers_co']['co'] = (
# min_x, min_y, min_z), (max_x, max_y, max_z)
# for mo in context.object.modifiers:
# if mo.type == 'SIMPLE_DEFORM':
# simple_deform = new_object.modifiers.new(
# mo.name, 'SIMPLE_DEFORM')
# simple_deform.deform_method = mo.deform_method
# simple_deform.deform_axis = mo.deform_axis
# simple_deform.lock_x = mo.lock_x
# simple_deform.lock_y = mo.lock_y
# simple_deform.lock_z = mo.lock_z
# simple_deform.origin = mo.origin
# simple_deform.limits[1] = mo.limits[1]
# simple_deform.limits[0] = mo.limits[0]
# simple_deform.angle = mo.angle
# simple_deform.show_viewport = mo.show_viewport
# obj = PublicUtils.get_depsgraph(new_object)
# cls.G_GizmoData['modifiers_co'][mo.name] = cls.get_mesh_max_min_co(
# obj)
# new_object.hide_set(True)
# new_object.hide_viewport = False
# new_object.hide_select = True
# new_object.hide_render = True
# new_object.hide_viewport = True
# new_object.hide_set(True)
# ver_len = obj.data.vertices.__len__()
# edge_len = obj.data.edges.__len__()
#
# if 'numpy_data' not in cls.G_GizmoData:
# cls.G_GizmoData['numpy_data'] = {}
#
# numpy_data = cls.G_GizmoData['numpy_data']
# key = (ver_len, edge_len)
# if key in numpy_data:
# list_edges, list_vertices = numpy_data[key]
# else:
# list_edges = np.zeros(edge_len * 2, dtype=np.int32)
# list_vertices = np.zeros(ver_len * 3, dtype=np.float32)
# numpy_data[key] = (list_edges, list_vertices)
# obj.data.vertices.foreach_get('co', list_vertices)
# ver = list_vertices.reshape((ver_len, 3))
# ver = np.insert(ver, 3, 1, axis=1).T
# ver[:] = np.dot(matrix, ver)
#
# ver /= ver[3, :]
# ver = ver.T
# ver = ver[:, :3]
# obj.data.edges.foreach_get('vertices', list_edges)
# indices = list_edges.reshape((edge_len, 2))
#
# limits = context.object.modifiers.active.limits[:]
# modifiers = [getattr(context.object.modifiers.active, i)
# for i in cls.G_MODIFIERS_PROPERTY]
#
# cls.G_GizmoData['draw'] = (ver, indices, matrix, modifiers, limits)
def update_deform_wireframe(self):
... ...
class GizmoUtils(PublicProperty): class GizmoUtils(GizmoUpdate):
custom_shape: dict custom_shape: dict
init_mouse_y: float init_mouse_region_y: float
init_mouse_x: float init_mouse_region_x: float
mouse_dpi: int mouse_dpi: int
matrix_basis: Matrix matrix_basis: Matrix
draw_type: str draw_type: str
@ -710,33 +714,35 @@ class GizmoUtils(PublicProperty):
def init_shape(self): def init_shape(self):
if not hasattr(self, 'custom_shape'): if not hasattr(self, 'custom_shape'):
self.custom_shape = {} self.custom_shape = {}
for i in self.G_GizmoCustomShapeDict: for i in self.G_CustomShape:
item = self.G_GizmoCustomShapeDict[i] item = self.G_CustomShape[i]
self.custom_shape[i] = self.new_custom_shape('TRIS', item) self.custom_shape[i] = self.new_custom_shape('TRIS', item)
def init_setup(self): def init_setup(self):
self.init_shape() self.init_shape()
def init_invoke(self, context, event): def init_invoke(self, context, event):
self.init_mouse_y = event.mouse_y self.init_mouse_region_y = event.mouse_region_y
self.init_mouse_x = event.mouse_x self.init_mouse_region_x = event.mouse_region_x
def _update_matrix(self, context): def _update_matrix(self, context):
func = getattr(self, 'update_gizmo_matrix', None) func = getattr(self, 'update_gizmo_matrix', None)
if func: if func and self.modifier_origin_angle_is_available:
func(context) func(context)
def draw(self, context): def draw(self, context):
self.draw_custom_shape(self.custom_shape[self.draw_type]) if self.modifier_origin_angle_is_available:
self._update_matrix(context) self.draw_custom_shape(self.custom_shape[self.draw_type])
self._update_matrix(context)
def draw_select(self, context, select_id): def draw_select(self, context, select_id):
self.draw_custom_shape( if self.modifier_origin_angle_is_available:
self.custom_shape[self.draw_type], select_id=select_id) self.draw_custom_shape(
self._update_matrix(context) self.custom_shape[self.draw_type], select_id=select_id)
self._update_matrix(context)
def get_delta(self, event): def get_delta(self, event):
delta = (self.init_mouse_x - event.mouse_x) / self.mouse_dpi delta = (self.init_mouse_region_x - event.mouse_region_x) / self.mouse_dpi
return delta return delta
def get_snap(self, delta, tweak): def get_snap(self, delta, tweak):
@ -759,6 +765,22 @@ class GizmoUtils(PublicProperty):
... ...
class GizmoGroupUtils(GizmoUtils):
bl_space_type = 'VIEW_3D'
bl_region_type = 'WINDOW'
bl_options = {'3D',
'PERSISTENT',
# 'SCALE',
# 'DEPTH_3D',
# 'SELECT',
# 'SHOW_MODAL_ALL',
# 'EXCLUDE_MODAL',
# 'TOOL_INIT', # not show
# 'TOOL_FALLBACK_KEYMAP',
# 'VR_REDRAWS'
}
class Tmp: class Tmp:
@classmethod @classmethod
def get_origin_bounds(cls, obj: 'bpy.types.Object') -> list: def get_origin_bounds(cls, obj: 'bpy.types.Object') -> list: