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.
14 changed files with 2460 additions and 0 deletions

View File

@ -0,0 +1,90 @@
# SPDX-License-Identifier: GPL-2.0-or-later
from . import (
panel, #
gizmo,
utils,
update,
translate,
operators,
preferences,
)
bl_info = {
"name": "Simple Deform Helper",
"author": "AIGODLIKE Community(BlenderCN辣椒,小萌新)",
"version": (0, 2, 0),
"blender": (3, 0, 0),
"location": "3D View -> Select an object and the active modifier is simple deformation",
"description": "Simple Deform visualization adjustment tool",
"doc_url": "https://github.com/AIGODLIKE/simple_deform_helper/blob/main/README.md",
"category": "3D View"
}
"""
# -------------------------
__init__.py:
Register All Module
gizmo/__init__.py:
Register All Gizmo
/angle_and_factor.py:
Ctrl Modifier Angle
/bend_axis.py:
Bend Method Switch Direction Gizmo
/set_deform_axis.py:
Three Switch Deform Axis Operator Gizmo
/up_down_limits_point.py:
Main control part
use utils.py PublicProperty._get_limits_point_and_bound_box_co
Obtain and calculate boundary box and limit point data
draw.py:
Draw 3D Bound And Line
gizmo.json:
Draw Custom Shape Vertex Data
operator.py:
Set Deform Axis Operator
panel.py:
Draw Gizmo Tool Property in Options and Tool Settings Right
preferences.py:
Addon Preferences
translate.py:
temporary only Cn translate
update.py:
In Change Depsgraph When Update Addon Data And Del Redundant Empty
utils.py:
Main documents used
Most computing operations are placed in classes GizmoUtils
# -------------------------
"""
module_tuple = (
panel,
gizmo,
utils,
update,
translate,
operators,
preferences,
)
def register():
for item in module_tuple:
item.register()
def unregister():
for item in module_tuple:
item.unregister()

View File

@ -0,0 +1,181 @@
# SPDX-License-Identifier: GPL-2.0-or-later
import blf
import bpy
import gpu
from gpu_extras.batch import batch_for_shader
from mathutils import Vector
from .update import ChangeActiveObject, simple_update
from .utils import GizmoUtils
class DrawPublic(GizmoUtils):
G_HandleData = {} # Save draw Handle
@classmethod
def draw_3d_shader(cls, pos, indices, color=None, *, shader_name='3D_UNIFORM_COLOR', draw_type='LINES'):
shader = gpu.shader.from_builtin(shader_name)
if draw_type == 'POINTS':
batch = batch_for_shader(shader, draw_type, {'pos': pos})
else:
batch = batch_for_shader(
shader, draw_type, {'pos': pos}, indices=indices)
shader.bind()
if color:
shader.uniform_float('color', color)
batch.draw(shader)
@property
def draw_poll(self) -> bool:
if simple_update.timers_update_poll():
is_switch_obj = ChangeActiveObject.is_change_active_object(False)
if self.poll_simple_deform_public(bpy.context) and not is_switch_obj:
return True
return False
class DrawText(DrawPublic):
font_info = {
'font_id': 0,
'handler': None,
}
text_key = 'handler_text'
@classmethod
def add_text_handler(cls):
key = cls.text_key
if key not in cls.G_HandleData:
cls.G_HandleData[key] = bpy.types.SpaceView3D.draw_handler_add(
DrawText().draw_text_handler, (), 'WINDOW', 'POST_PIXEL')
@classmethod
def del_text_handler(cls):
key = cls.text_key
if key in cls.G_HandleData:
bpy.types.SpaceView3D.draw_handler_remove(
cls.G_HandleData[key], 'WINDOW')
cls.G_HandleData.pop(key)
@classmethod
def obj_is_scale(cls) -> bool:
ob = bpy.context.object
scale_error = ob and (ob.scale != Vector((1, 1, 1)))
return scale_error
def draw_text_handler(self):
if self.draw_poll and self.obj_is_scale():
self.draw_scale_text()
def draw_scale_text(self):
obj = bpy.context.object
font_id = self.font_info['font_id']
blf.position(font_id, 200, 80, 0)
blf.size(font_id, 15, 72)
blf.color(font_id, 1, 1, 1, 1)
blf.draw(
font_id,
f'The scaling value of the object {obj.name_full} is not 1,'
f' which will cause the deformation of the simple deformation modifier.'
f' Please apply the scaling before deformation')
@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):
blf.position(font_id, x, y - (size * (column + 1)), 0)
blf.size(font_id, size, dpi)
blf.draw(font_id, text)
blf.color(font_id, *color)
class DrawHandler(DrawText):
@classmethod
def add_handler(cls):
if 'handler' not in cls.G_HandleData:
cls.G_HandleData['handler'] = bpy.types.SpaceView3D.draw_handler_add(
Draw3D().draw, (), 'WINDOW', 'POST_VIEW')
cls.add_text_handler()
@classmethod
def del_handler(cls):
data = bpy.data
if data.meshes.get(cls.G_NAME):
data.meshes.remove(data.meshes.get(cls.G_NAME))
if data.objects.get(cls.G_NAME):
data.objects.remove(data.objects.get(cls.G_NAME))
if 'handler' in cls.G_HandleData:
bpy.types.SpaceView3D.draw_handler_remove(
cls.G_HandleData['handler'], 'WINDOW')
cls.G_HandleData.clear()
cls.del_text_handler()
class Draw3D(DrawHandler):
def draw(self):
gpu.state.blend_set('ALPHA')
gpu.state.line_width_set(1)
gpu.state.blend_set('ALPHA')
gpu.state.depth_test_set('ALWAYS')
if self.draw_poll:
self.draw_3d(bpy.context)
def draw_3d(self, context):
if not self.modifier_origin_is_available:
self.draw_bound_box()
elif self.simple_deform_show_gizmo_poll(context):
# draw bound box
self.draw_bound_box()
self.draw_deform_mesh()
self.draw_limits_line()
self.draw_limits_bound_box()
self.draw_text_handler()
elif self.poll_simple_deform_show_bend_axis_witch(context):
self.draw_bound_box()
def draw_bound_box(self):
coords = self.matrix_calculation(self.obj_matrix_world,
self.tow_co_to_coordinate(self.modifier_bound_co))
self.draw_3d_shader(coords, self.G_INDICES, self.pref.bound_box_color)
def draw_limits_bound_box(self):
self.draw_3d_shader(self.modifier_limits_bound_box,
self.G_INDICES,
self.pref.limits_bound_box_color,
)
def draw_limits_line(self):
up_point, down_point, up_limits, down_limits = self.modifier_limits_point
# draw limits line
self.draw_3d_shader((up_limits, down_limits), ((1, 0),), (1, 1, 0, 0.5))
# draw line
self.draw_3d_shader((up_point, down_point), ((1, 0),), (1, 1, 0, 0.3))
# draw pos
self.draw_3d_shader([down_point], (), (0, 1, 0, 0.5),
shader_name='3D_UNIFORM_COLOR', draw_type='POINTS')
self.draw_3d_shader([up_point], (), (1, 0, 0, 0.5),
shader_name='3D_UNIFORM_COLOR', draw_type='POINTS')
def draw_deform_mesh(self):
ob = self.obj
deform_data = self.G_DeformDrawData
active = self.modifier
# draw deform mesh
if 'simple_deform_bound_data' in deform_data and self.pref.update_deform_wireframe:
modifiers = self.get_modifiers_parameter(self.modifier)
pos, indices, mat, mod_data, limits = deform_data['simple_deform_bound_data']
is_limits = limits == active.limits[:]
is_mat = (ob.matrix_world == mat)
if modifiers == mod_data and is_mat and is_limits:
self.draw_3d_shader(pos, indices, self.pref.deform_wireframe_color)
def draw_origin_error(self):
...

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,32 @@
import bpy
from .angle_and_factor import AngleGizmoGroup, AngleGizmo
from .bend_axis import BendAxiSwitchGizmoGroup, CustomGizmo
from .set_deform_axis import SetDeformGizmoGroup
from .up_down_limits_point import UpDownLimitsGizmo, UpDownLimitsGizmoGroup
from ..draw import Draw3D
class_list = (
UpDownLimitsGizmo,
UpDownLimitsGizmoGroup,
AngleGizmo,
AngleGizmoGroup,
CustomGizmo,
BendAxiSwitchGizmoGroup,
SetDeformGizmoGroup,
)
register_class, unregister_class = bpy.utils.register_classes_factory(class_list)
def register():
Draw3D.add_handler()
register_class()
def unregister():
Draw3D.del_handler()
unregister_class()

View File

@ -0,0 +1,161 @@
# SPDX-License-Identifier: GPL-2.0-or-later
import math
from bpy.types import Gizmo
from bpy.types import (
GizmoGroup,
)
from ..update import ChangeActiveModifierParameter
from ..utils import GizmoUtils, GizmoGroupUtils
class AngleUpdate(GizmoUtils):
int_value_degrees: float
tmp_value_angle: float
def get_snap(self, delta, tweak):
is_snap = 'SNAP' in tweak
is_precise = 'PRECISE' in tweak
if is_snap and is_precise:
delta = round(delta)
elif is_snap:
delta //= 5
delta *= 5
elif is_precise:
delta /= self.mouse_dpi
delta //= 0.01
delta *= 0.01
return delta
def update_prop_value(self, event, tweak):
def v(va):
self.target_set_value('angle', math.radians(va))
not_c_l = not event.alt and not event.ctrl
is_only_shift = event.shift and not_c_l
change_angle = self.get_delta(event)
if is_only_shift:
change_angle /= 50
new_value = self.tmp_value_angle - change_angle
old_value = self.target_get_value('angle')
snap_value = self.get_snap(new_value, tweak)
is_shift = event.type == 'LEFT_SHIFT'
if is_only_shift:
if event.value == 'PRESS':
self.init_mouse_region_x = event.mouse_region_x
self.tmp_value_angle = int(math.degrees(old_value))
v(self.tmp_value_angle)
return
value = (self.tmp_value_angle - change_angle) // 0.01 * 0.01
v(value)
return
elif not_c_l and not event.shift and is_shift and event.value == 'RELEASE':
self.init_mouse_region_x = event.mouse_region_x
# new_value = self.tmp_value_angle = math.degrees(old_value)
return
v(snap_value)
def update_gizmo_matrix(self, context):
matrix = context.object.matrix_world
point = self.modifier_bound_co[1]
self.matrix_basis = self.obj_matrix_world
self.matrix_basis.translation = matrix @ point
def update_header_text(self, context):
te = self.translate_text
text = te(self.modifier.deform_method.title()) + ' '
if self.modifier_is_use_angle_value:
value = round(math.degrees(self.modifier_angle), 3)
text += self.translate_header_text('Angle', value)
else:
value = round(self.modifier.factor, 3)
text += self.translate_header_text('Coefficient', value)
context.area.header_text_set(text)
class AngleGizmo(Gizmo, AngleUpdate):
bl_idname = 'ViewSimpleAngleGizmo'
bl_target_properties = (
{'id': 'up_limits', 'type': 'FLOAT', 'array_length': 1},
{'id': 'down_limits', 'type': 'FLOAT', 'array_length': 1},
{'id': 'angle', 'type': 'FLOAT', 'array_length': 1},
)
__slots__ = (
'draw_type',
'mouse_dpi',
'empty_object',
'custom_shape',
'tmp_value_angle',
'int_value_degrees',
'init_mouse_region_y',
'init_mouse_region_x',
)
def setup(self):
self.init_setup()
def invoke(self, context, event):
self.init_invoke(context, event)
self.int_value_degrees = self.target_get_value('angle')
angle = math.degrees(self.int_value_degrees)
self.tmp_value_angle = angle
return {'RUNNING_MODAL'}
def modal(self, context, event, tweak):
self.clear_point_cache()
self.update_prop_value(event, tweak)
self.update_deform_wireframe()
self.update_header_text(context)
ChangeActiveModifierParameter.update_modifier_parameter()
self.tag_redraw(context)
return self.event_handle(event)
def exit(self, context, cancel):
context.area.header_text_set(None)
if cancel:
self.target_set_value('angle', self.int_value_degrees)
class AngleGizmoGroup(GizmoGroup, GizmoGroupUtils):
"""ShowGizmo
"""
bl_idname = 'OBJECT_GGT_SimpleDeformGizmoGroup'
bl_label = 'AngleGizmoGroup'
@classmethod
def poll(cls, context):
return cls.simple_deform_show_gizmo_poll(context)
def setup(self, context):
sd_name = AngleGizmo.bl_idname
add_data = [
('angle',
sd_name,
{'draw_type': 'SimpleDeform_GizmoGroup_',
'color': (1.0, 0.5, 1.0),
'alpha': 0.3,
'color_highlight': (1.0, 1.0, 1.0),
'alpha_highlight': 0.3,
'use_draw_modal': True,
'scale_basis': 0.1,
'use_draw_value': True,
'mouse_dpi': 5,
}),
]
self.generate_gizmo(add_data)
def refresh(self, context):
self.angle.target_set_prop('angle',
context.object.modifiers.active,
'angle')

View File

@ -0,0 +1,115 @@
import math
from bpy.types import GizmoGroup
from bpy_types import Gizmo
from mathutils import Euler, Vector
from ..utils import GizmoUtils, GizmoGroupUtils
class CustomGizmo(Gizmo, GizmoUtils):
"""Draw Custom Gizmo"""
bl_idname = '_Custom_Gizmo'
draw_type: str
custom_shape: dict
def setup(self):
self.init_setup()
def draw(self, context):
self.draw_custom_shape(self.custom_shape[self.draw_type])
def draw_select(self, context, select_id):
self.draw_custom_shape(
self.custom_shape[self.draw_type], select_id=select_id)
def invoke(self, context, event):
self.init_invoke(context, event)
return {'RUNNING_MODAL'}
def modal(self, context, event, tweak):
self.update_empty_matrix()
return {'RUNNING_MODAL'}
class BendAxiSwitchGizmoGroup(GizmoGroup, GizmoGroupUtils):
bl_idname = 'OBJECT_GGT_SimpleDeformGizmoGroup_display_bend_axis_switch_gizmo'
bl_label = 'SimpleDeformGizmoGroup_display_bend_axis_switch_gizmo'
@classmethod
def poll(cls, context):
return cls.poll_simple_deform_show_bend_axis_witch(context)
def setup(self, context):
_draw_type = 'SimpleDeform_Bend_Direction_'
_color_a = 1, 0, 0
_color_b = 0, 1, 0
for na, axis, rot, positive in (
('top_a', 'X', (math.radians(90), 0, math.radians(90)), True),
('top_b', 'X', (math.radians(90), 0, 0), True),
('bottom_a', 'X', (math.radians(90), 0, math.radians(90)), False),
('bottom_b', 'X', (math.radians(90), 0, 0), False),
('left_a', 'Y', (math.radians(90), 0, 0), False),
('left_b', 'Y', (0, 0, 0), False),
('right_a', 'Y', (math.radians(90), 0, 0), True),
('right_b', 'Y', (0, 0, 0), True),
('front_a', 'Z', (0, 0, 0), False),
('front_b', 'X', (0, 0, 0), False),
('back_a', 'Z', (0, 0, 0), True),
('back_b', 'X', (0, 0, 0), True),):
_a = (na.split('_')[1] == 'a')
setattr(self, na, self.gizmos.new(CustomGizmo.bl_idname))
gizmo = getattr(self, na)
gizmo.mode = na
gizmo.draw_type = _draw_type
gizmo.color = _color_a if _a else _color_b
gizmo.alpha = 0.3
gizmo.color_highlight = 1.0, 1.0, 1.0
gizmo.alpha_highlight = 1
gizmo.use_draw_modal = True
gizmo.scale_basis = 0.2
gizmo.use_draw_value = True
ops = gizmo.target_set_operator(
'simple_deform_gizmo.deform_axis')
ops.Deform_Axis = axis
ops.X_Value = rot[0]
ops.Y_Value = rot[1]
ops.Z_Value = rot[2]
ops.Is_Positive = positive
def draw_prepare(self, context):
ob = context.object
mat = ob.matrix_world
top, bottom, left, right, front, back = self.modifier_bound_box_pos
rad = math.radians
for_list = (
('top_a', top, (0, 0, 0),),
('top_b', top, (0, 0, rad(90)),),
('bottom_a', bottom, (0, rad(180), 0),),
('bottom_b', bottom, (0, rad(180), rad(90)),),
('left_a', left, (rad(-90), 0, rad(90)),),
('left_b', left, (0, rad(-90), 0),),
('right_a', right, (rad(90), 0, rad(90)),),
('right_b', right, (0, rad(90), 0),),
('front_a', front, (rad(90), 0, 0),),
('front_b', front, (rad(90), rad(90), 0),),
('back_a', back, (rad(-90), 0, 0),),
('back_b', back, (rad(-90), rad(-90), 0),),
)
for i, j, w, in for_list:
gizmo = getattr(self, i, False)
rot = Euler(w, 'XYZ').to_matrix().to_4x4()
gizmo.matrix_basis = mat.to_euler().to_matrix().to_4x4() @ rot
gizmo.matrix_basis.translation = self.obj_matrix_world @ Vector(j)

View File

@ -0,0 +1,51 @@
from bpy.types import GizmoGroup
from mathutils import Vector
from ..utils import GizmoGroupUtils
class SetDeformGizmoGroup(GizmoGroup, GizmoGroupUtils):
bl_idname = 'OBJECT_GGT_SetDeformGizmoGroup'
bl_label = 'SetDeformGizmoGroup'
@classmethod
def poll(cls, context):
return cls.simple_deform_show_gizmo_poll(context) and cls.pref_().show_set_axis_button
def setup(self, context):
data_path = 'object.modifiers.active.deform_axis'
set_enum = 'wm.context_set_enum'
for axis in ('X', 'Y', 'Z'):
# show toggle axis button
gizmo = self.gizmos.new('GIZMO_GT_button_2d')
gizmo.icon = f'EVENT_{axis.upper()}'
gizmo.draw_options = {'BACKDROP', 'HELPLINE'}
ops = gizmo.target_set_operator(set_enum)
ops.data_path = data_path
ops.value = axis
gizmo.color = (0, 0, 0)
gizmo.alpha = 0.3
gizmo.color_highlight = 1.0, 1.0, 1.0
gizmo.alpha_highlight = 0.3
gizmo.use_draw_modal = True
gizmo.use_draw_value = True
gizmo.scale_basis = 0.1
setattr(self, f'deform_axis_{axis.lower()}', gizmo)
def draw_prepare(self, context):
bound = self.modifier_bound_co
if bound:
obj = self.get_depsgraph(self.obj)
dimensions = obj.dimensions
def mat(f):
b = bound[0]
co = (b[0] + (max(dimensions) * f),
b[1],
b[2] - (min(dimensions) * 0.3))
return self.obj_matrix_world @ Vector(co)
self.deform_axis_x.matrix_basis.translation = mat(0)
self.deform_axis_y.matrix_basis.translation = mat(0.3)
self.deform_axis_z.matrix_basis.translation = mat(0.6)

View File

@ -0,0 +1,274 @@
import math
from time import time
import bpy
from bpy.types import Gizmo, GizmoGroup
from bpy_extras import view3d_utils
from mathutils import Vector
from ..update import ChangeActiveModifierParameter
from ..utils import GizmoUtils, GizmoGroupUtils
class GizmoProperty(GizmoUtils):
ctrl_mode: str
int_value_up_limits: int
int_value_down_limits: int
@property
def is_up_limits_mode(self):
return self.ctrl_mode == 'up_limits'
@property
def is_down_limits_mode(self):
return self.ctrl_mode == 'down_limits'
@property
def limit_scope(self):
return self.pref.modifiers_limits_tolerance
@property
def limits_min_value(self):
return self.modifier_down_limits + self.limit_scope
@property
def limits_max_value(self):
return self.modifier_up_limits - self.limit_scope
# ----get func
def get_up_limits_value(self, event):
delta = self.get_delta(event)
mid = self.middle_limits_value + self.limit_scope
min_value = mid if self.is_middle_mode else self.limits_min_value
return self.value_limit(delta, min_value=min_value)
def get_down_limits_value(self, event):
delta = self.get_delta(event)
mid = self.middle_limits_value - self.limit_scope
max_value = mid if self.is_middle_mode else self.limits_max_value
return self.value_limit(delta, max_value=max_value)
def get_delta(self, event):
context = bpy.context
x, y = view3d_utils.location_3d_to_region_2d(
context.region, context.space_data.region_3d, self.point_up)
x2, y2 = view3d_utils.location_3d_to_region_2d(
context.region, context.space_data.region_3d, self.point_down)
mouse_line_distance = math.sqrt(((event.mouse_region_x - x2) ** 2) +
((event.mouse_region_y - y2) ** 2))
straight_line_distance = math.sqrt(((x2 - x) ** 2) +
((y2 - y) ** 2))
delta = mouse_line_distance / straight_line_distance + 0
v_up = Vector((x, y))
v_down = Vector((x2, y2))
limits_angle = v_up - v_down
mouse_v = Vector((event.mouse_region_x, event.mouse_region_y))
mouse_angle = mouse_v - v_down
angle_ = mouse_angle.angle(limits_angle)
if angle_ > (math.pi / 2):
delta = 0
return delta
class GizmoUpdate(GizmoProperty):
# ---update gizmo matrix
def update_gizmo_matrix(self, context):
self.align_orientation_to_user_perspective(context)
self.align_point_to_limits_point()
def align_orientation_to_user_perspective(self, context):
rotation = context.space_data.region_3d.view_matrix.inverted().to_quaternion()
matrix = rotation.to_matrix().to_4x4()
self.matrix_basis = matrix
def align_point_to_limits_point(self):
if self.is_up_limits_mode:
self.matrix_basis.translation = self.point_limits_up
elif self.is_down_limits_mode:
self.matrix_basis.translation = self.point_limits_down
# ---- set prop
def set_prop_value(self, event):
if self.is_up_limits_mode:
self.set_up_value(event)
elif self.is_down_limits_mode:
self.set_down_value(event)
def set_down_value(self, event):
value = self.get_down_limits_value(event)
self.target_set_value('down_limits', value)
if event.ctrl:
self.target_set_value('up_limits', value + self.difference_value)
elif self.is_middle_mode:
if self.origin_mode == 'LIMITS_MIDDLE':
mu = self.middle_limits_value
v = mu - (value - mu)
self.target_set_value('up_limits', v)
elif self.origin_mode == 'MIDDLE':
self.target_set_value('up_limits', 1 - value)
else:
self.target_set_value('up_limits', self.modifier_up_limits)
else:
self.target_set_value('up_limits', self.modifier_up_limits)
def set_up_value(self, event):
value = self.get_up_limits_value(event)
self.target_set_value('up_limits', value)
if event.ctrl:
self.target_set_value('down_limits', value - self.difference_value)
elif self.is_middle_mode:
if self.origin_mode == 'LIMITS_MIDDLE':
mu = self.middle_limits_value
value = mu - (value - mu)
self.target_set_value('down_limits', value)
elif self.origin_mode == 'MIDDLE':
self.target_set_value('down_limits', 1 - value)
else:
self.target_set_value('down_limits', self.modifier_down_limits)
else:
self.target_set_value('down_limits', self.modifier_down_limits)
# -------
def update_header_text(self, context):
origin = self.obj_origin_property_group
mode = origin.bl_rna.properties['origin_mode'].enum_items[origin.origin_mode].name
te = self.translate_text
t = self.translate_header_text
text = te(self.modifier.deform_method.title()) + ' ' + te(mode) + ' '
if self.is_up_limits_mode:
value = round(self.modifier_up_limits, 3)
text += t('Up limit', value)
elif self.is_down_limits_mode:
value = round(self.modifier_down_limits, 3)
text += t('Down limit', value)
context.area.header_text_set(text)
class UpDownLimitsGizmo(Gizmo, GizmoUpdate):
bl_idname = 'UpDownLimitsGizmo'
bl_label = 'UpDownLimitsGizmo'
bl_target_properties = (
{'id': 'up_limits', 'type': 'FLOAT', 'array_length': 1},
{'id': 'down_limits', 'type': 'FLOAT', 'array_length': 1},
)
bl_options = {'UNDO', 'GRAB_CURSOR'}
__slots__ = (
'mod',
'up_limits',
'down_limits',
'draw_type',
'mouse_dpi',
'ctrl_mode',
'difference_value',
'middle_limits_value',
'init_mouse_region_y',
'init_mouse_region_x',
'custom_shape',
'int_value_up_limits',
'int_value_down_limits',
)
difference_value: float
middle_limits_value: float
def setup(self):
self.mouse_dpi = 10
self.init_setup()
def invoke(self, context, event):
self.init_invoke(context, event)
if self.is_up_limits_mode:
self.int_value_up_limits = up_limits = self.modifier_up_limits
self.target_set_value('up_limits', up_limits)
elif self.is_down_limits_mode:
self.int_value_down_limits = down_limits = self.modifier_down_limits
self.target_set_value('down_limits', down_limits)
return {'RUNNING_MODAL'}
def exit(self, context, cancel):
context.area.header_text_set(None)
if cancel:
if self.is_up_limits_mode:
self.target_set_value('up_limits', self.int_value_up_limits)
elif self.is_down_limits_mode:
self.target_set_value(
'down_limits', self.int_value_down_limits)
def modal(self, context, event, tweak):
st = time()
self.clear_point_cache()
if self.modifier_is_use_origin_axis:
self.new_origin_empty_object()
# return {'RUNNING_MODAL'}
self.difference_value = self.modifier_up_limits - self.modifier_down_limits
self.middle_limits_value = (self.modifier_up_limits + self.modifier_down_limits) / 2
try:
self.set_prop_value(event)
self.clear_point_cache()
self.update_object_origin_matrix()
except Exception as e:
print(e.args)
# ...
# return {'FINISHED'}
self.update_header_text(context)
return_handle = self.event_handle(event)
ChangeActiveModifierParameter.update_modifier_parameter()
self.update_deform_wireframe()
print('run modal time:', time() - st)
return return_handle
class UpDownLimitsGizmoGroup(GizmoGroup, GizmoGroupUtils):
bl_idname = 'OBJECT_GGT_UpDownLimitsGizmoGroup'
bl_label = 'UpDownLimitsGizmoGroup'
@classmethod
def poll(cls, context):
return cls.simple_deform_show_gizmo_poll(context)
def setup(self, context):
sd_name = UpDownLimitsGizmo.bl_idname
gizmo_data = [
('up_limits',
sd_name,
{'ctrl_mode': 'up_limits',
'draw_type': 'Sphere_GizmoGroup_',
'mouse_dpi': 1000,
'color': (1.0, 0, 0),
'alpha': 0.5,
'color_highlight': (1.0, 1.0, 1.0),
'alpha_highlight': 0.3,
'use_draw_modal': True,
'scale_basis': 0.1,
'use_draw_value': True, }),
('down_limits',
sd_name,
{'ctrl_mode': 'down_limits',
'draw_type': 'Sphere_GizmoGroup_',
'mouse_dpi': 1000,
'color': (0, 1.0, 0),
'alpha': 0.5,
'color_highlight': (1.0, 1.0, 1.0),
'alpha_highlight': 0.3,
'use_draw_modal': True,
'scale_basis': 0.1,
'use_draw_value': True, }),
]
self.generate_gizmo(gizmo_data)
def refresh(self, context):
pro = context.object.SimpleDeformGizmo_PropertyGroup
for i in (self.down_limits, self.up_limits):
for j in ('down_limits', 'up_limits'):
i.target_set_prop(j, pro, j)

View File

@ -0,0 +1,64 @@
# SPDX-License-Identifier: GPL-2.0-or-later
import bpy
from bpy.types import Operator
from bpy.props import FloatProperty, StringProperty, BoolProperty
from .utils import GizmoUtils
class DeformAxisOperator(Operator, GizmoUtils):
bl_idname = 'simple_deform_gizmo.deform_axis'
bl_label = 'deform_axis'
bl_description = 'deform_axis operator'
bl_options = {'REGISTER'}
Deform_Axis: StringProperty(default='X', options={'SKIP_SAVE'})
X_Value: FloatProperty(default=-0, options={'SKIP_SAVE'})
Y_Value: FloatProperty(default=-0, options={'SKIP_SAVE'})
Z_Value: FloatProperty(default=-0, options={'SKIP_SAVE'})
Is_Positive: BoolProperty(default=True, options={'SKIP_SAVE'})
def invoke(self, context, event):
context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'}
def modal(self, context, event):
self.clear_point_cache()
mod = context.object.modifiers.active
mod.deform_axis = self.Deform_Axis
empty = self.new_origin_empty_object()
is_positive = self.is_positive(mod.angle)
for limit, value in (('max_x', self.X_Value),
('min_x', self.X_Value),
('max_y', self.Y_Value),
('min_y', self.Y_Value),
('max_z', self.Z_Value),
('min_z', self.Z_Value),
):
setattr(empty.constraints[self.G_NAME_CON_LIMIT], limit, value)
if ((not is_positive) and self.Is_Positive) or (is_positive and (not self.Is_Positive)):
mod.angle = mod.angle * -1
if not event.ctrl:
self.pref.display_bend_axis_switch_gizmo = False
return {'FINISHED'}
class_list = (
DeformAxisOperator,
)
register_class, unregister_class = bpy.utils.register_classes_factory(class_list)
def register():
register_class()
def unregister():
unregister_class()

View File

@ -0,0 +1,77 @@
# SPDX-License-Identifier: GPL-2.0-or-later
import bpy
from bpy.types import Panel, VIEW3D_HT_tool_header
from .utils import GizmoUtils
class SimpleDeformHelperToolPanel(Panel, GizmoUtils):
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'Tool'
bl_context = '.objectmode'
bl_label = 'Simple Deform Helper'
bl_idname = 'VIEW3D_PT_simple_deform_helper'
bl_parent_id = 'VIEW3D_PT_tools_object_options'
@classmethod
def poll(cls, context):
show_in_tool_options = GizmoUtils.pref_().show_gizmo_property_location == 'ToolOptions'
return cls.poll_simple_deform_public(context) and show_in_tool_options
def draw(self, context):
if self.poll(context):
self.draw_property(self.layout, context)
@staticmethod
def draw_property(layout, context):
if GizmoUtils.poll_simple_deform_public(context):
cls = SimpleDeformHelperToolPanel
pref = cls.pref_()
obj = context.object
mod = obj.modifiers.active
prop = obj.SimpleDeformGizmo_PropertyGroup
ctrl_obj = mod.origin.SimpleDeformGizmo_PropertyGroup if mod.origin else prop
layout.prop(ctrl_obj,
'origin_mode',
text='')
layout.prop(pref,
'update_deform_wireframe',
icon='MOD_WIREFRAME',
text='')
layout.prop(pref,
'show_set_axis_button',
icon='EMPTY_AXIS',
text='')
if pref.modifier_deform_method_is_bend:
layout.prop(pref,
'display_bend_axis_switch_gizmo',
toggle=1)
layout.prop(pref,
'modifiers_limits_tolerance',
text='')
def draw_settings(self, context):
show_in_settings = GizmoUtils.pref_().show_gizmo_property_location == 'ToolSettings'
if show_in_settings:
SimpleDeformHelperToolPanel.draw_property(self.layout, context)
class_list = (
SimpleDeformHelperToolPanel,
)
register_class, unregister_class = bpy.utils.register_classes_factory(class_list)
def register():
register_class()
VIEW3D_HT_tool_header.append(SimpleDeformHelperToolPanel.draw_settings)
def unregister():
unregister_class()
VIEW3D_HT_tool_header.remove(SimpleDeformHelperToolPanel.draw_settings)

View File

@ -0,0 +1,176 @@
# SPDX-License-Identifier: GPL-2.0-or-later
import bpy
from bpy.props import (FloatProperty,
PointerProperty,
FloatVectorProperty,
EnumProperty,
BoolProperty)
from bpy.types import (
AddonPreferences,
PropertyGroup,
)
from .utils import GizmoUtils
class SimpleDeformGizmoAddonPreferences(AddonPreferences, GizmoUtils):
bl_idname = GizmoUtils.G_ADDON_NAME
deform_wireframe_color: FloatVectorProperty(
name='Deform Wireframe',
description='Draw Deform Wireframe Color',
default=(1, 1, 1, 0.3),
soft_max=1,
soft_min=0,
size=4, subtype='COLOR')
bound_box_color: FloatVectorProperty(
name='Bound Box',
description='Draw Bound Box Color',
default=(1, 0, 0, 0.5),
soft_max=1,
soft_min=0,
size=4,
subtype='COLOR')
limits_bound_box_color: FloatVectorProperty(
name='Upper and lower limit Bound Box Color',
description='Draw Upper and lower limit Bound Box Color',
default=(0.3, 1, 0.2, 0.5),
soft_max=1,
soft_min=0,
size=4,
subtype='COLOR')
modifiers_limits_tolerance: FloatProperty(
name='Upper and lower limit tolerance',
description='Minimum value between upper and lower limits',
default=0.05,
max=1,
min=0.0001
)
display_bend_axis_switch_gizmo: BoolProperty(
name='Show Toggle Bend Axis Gizmo',
default=False,
options={'SKIP_SAVE'})
update_deform_wireframe: BoolProperty(
name='Show Deform Wireframe',
default=False)
show_set_axis_button: BoolProperty(
name='Show Set Axis Button',
default=False)
show_gizmo_property_location: EnumProperty(
name='Gizmo Property Show Location',
items=[('ToolSettings', 'Tool Settings', ''),
('ToolOptions', 'Tool Options', ''),
],
default='ToolSettings'
)
def draw(self, context):
col = self.layout.column()
box = col.box()
for text in ("You can press the following shortcut keys when dragging values",
" Wheel: Switch Origin Ctrl Mode",
" X,Y,Z: Switch Modifier Deform Axis",
" W: Switch Deform Wireframe Show",
" A: Switch To Select Bend Axis Mode(deform_method=='BEND')",):
box.label(text=self.translate_text(text))
col.prop(self, 'deform_wireframe_color')
col.prop(self, 'bound_box_color')
col.prop(self, 'limits_bound_box_color')
col.label(text='Gizmo Property Show Location')
col.prop(self, 'show_gizmo_property_location', expand=True)
def draw_header_tool_settings(self, context):
if GizmoUtils.poll_simple_deform_public(context):
row = self.layout.row()
obj = context.object
mod = obj.modifiers.active
row.separator(factor=0.2)
row.prop(mod,
'deform_method',
expand=True)
row.prop(mod,
'deform_axis',
expand=True)
show_type = 'angle' if mod.deform_method in ('BEND', 'TWIST') else 'factor'
row.prop(mod, show_type)
class SimpleDeformGizmoObjectPropertyGroup(PropertyGroup, GizmoUtils):
def _limits_up(self, context):
if self.active_modifier_is_simple_deform:
self.modifier.limits[1] = self.up_limits
up_limits: FloatProperty(name='up',
description='UP Limits(Red)',
default=1,
update=_limits_up,
max=1,
min=0)
def _limits_down(self, context):
if self.active_modifier_is_simple_deform:
self.modifier.limits[0] = self.down_limits
down_limits: FloatProperty(name='down',
description='Lower limit(Green)',
default=0,
update=_limits_down,
max=1,
min=0)
origin_mode_items = (
('UP_LIMITS',
'Follow Upper Limit(Red)',
'Add an empty object origin as the rotation axis (if there is an origin, do not add it), and set the origin '
'position as the upper limit during operation'),
('DOWN_LIMITS',
'Follow Lower Limit(Green)',
'Add an empty object origin as the rotation axis (if there is an origin, do not add it), and set the origin '
'position as the lower limit during operation'),
('LIMITS_MIDDLE',
'Middle',
'Add an empty object origin as the rotation axis (if there is an origin, do not add it), and set the '
'origin position between the upper and lower limits during operation'),
('MIDDLE',
'Bound Middle',
'Add an empty object origin as the rotation axis (if there is an origin, do not add it), and set the origin '
'position as the position between the bounding boxes during operation'),
('NOT', 'No origin operation', ''),
)
origin_mode: EnumProperty(name='Origin control mode',
default='NOT',
items=origin_mode_items)
class_list = (
SimpleDeformGizmoAddonPreferences,
SimpleDeformGizmoObjectPropertyGroup,
)
register_class, unregister_class = bpy.utils.register_classes_factory(class_list)
def register():
register_class()
GizmoUtils.pref_().display_bend_axis_switch_gizmo = False
bpy.types.Object.SimpleDeformGizmo_PropertyGroup = PointerProperty(
type=SimpleDeformGizmoObjectPropertyGroup,
name='SimpleDeformGizmo_PropertyGroup')
bpy.types.VIEW3D_MT_editor_menus.append(
SimpleDeformGizmoAddonPreferences.draw_header_tool_settings)
def unregister():
unregister_class()
bpy.types.VIEW3D_MT_editor_menus.remove(
SimpleDeformGizmoAddonPreferences.draw_header_tool_settings)

View File

@ -0,0 +1,73 @@
# SPDX-License-Identifier: GPL-2.0-or-later
import bpy
def origin_text(a, b):
return "Add an empty object origin as the rotation axis (if there is an origin, " + a + \
"), and set the origin position " + b + " during operation"
translations_dict = {
"zh_CN": {
("上下文", "原文"): "翻译文字",
("*", "Show Toggle Bend Axis Gizmo"): "显示切换弯曲轴向Gizmo",
("*", "Gizmo Property Show Location"): "Gizmo属性显示位置",
("*", "You can press the following shortcut keys when dragging values"):
"拖动值时可以按以下快捷键",
("*", " Wheel: Switch Origin Ctrl Mode"):
" 滚轮: 切换原点控制模式",
("*", " X,Y,Z: Switch Modifier Deform Axis"):
" X,Y,Z: 切换修改器型变轴",
("*", " W: Switch Deform Wireframe Show"):
" W: 切换形变线框显示",
("*",
" A: Switch To Select Bend Axis Mode(deform_method=='BEND')"):
" A: 切换到选择弯曲轴模式(形变方法='弯曲')",
("*", "Show Set Axis Button"): "显示设置轴向Gizmo",
("*", "Follow Upper Limit(Red)"): "跟随上限(红色)",
("*", "Follow Lower Limit(Green)"): "跟随下限(绿色)",
("*", "Lower limit(Green)"): "下限(绿色)",
("*", "UP Limits(Red)"): "上限(红色)",
("*", "Down limit"): "下限",
("*", "Up limit"): "上限",
("*", "Show Deform Wireframe"): "显示形变线框",
("*", "Minimum value between upper and lower limits"): "上限与下限之间的最小值",
("*", "Upper and lower limit tolerance"): "上下限容差",
("*", "Draw Upper and lower limit Bound Box Color"): "绘制网格上限下限边界线框的颜色",
("*", "Upper and lower limit Bound Box Color"): "上限下限边界框颜色",
("*", "Draw Bound Box Color"): "绘制网格边界框的颜色",
("*", "Bound Box"): "边界框颜色",
("*", "Draw Deform Wireframe Color"): "绘制网格形变形状线框的颜色",
("*", "Deform Wireframe"): "形变线框颜色",
("*", "Simple Deform visualization adjustment tool"): "简易形变可视化工具",
("*", "Select an object and the active modifier is Simple Deform"): "选择物体并且活动修改器为简易形变",
("*", "Bound Middle"): "边界框中心",
("*", origin_text("do not add it", "as the lower limit")):
"添加一个空物体原点作为旋转轴(如果已有原点则不添加),并在操作时设置原点位置为下限位置",
("*", origin_text("do not add it", "as the upper limit")):
"添加一个空物体原点作为旋转轴(如果已有原点则不添加),并在操作时设置原点位置为上限位置",
("*", origin_text("it will not be added", "between the upper and lower limits")):
"添加一个空物体原点作为旋转轴(如果已有原点则不添加),并在操作时设置原点位置为上下限之间的位置",
("*", origin_text("do not add it", "as the position between the bounding boxes")):
"添加一个空物体原点作为旋转轴(如果已有原点则不添加),并在操作时设置原点位置为边界框之间的位置",
("*", "No origin operation"): "不进行原点操作",
("*", "Origin control mode"): "原点控制模式",
("*", "Down limit"): "下限",
("*", "Coefficient"): "系数",
("*", "Upper limit"): "上限",
("*", "3D View -> Select an object and the active modifier is simple deformation"): "3D视图 -> 选择一个物体,"
"并且活动修改器为简易形修改器",
("*", "3D View: Simple Deform Helper"): "3D 视图: Simple Deform Helper 简易形变助手",
("*", "Simple Deform Helper"): "简易形变助手",
("*", ""): "",
}
}
def register():
bpy.app.translations.register(__name__, translations_dict)
def unregister():
bpy.app.translations.unregister(__name__)

View File

@ -0,0 +1,201 @@
# SPDX-License-Identifier: GPL-2.0-or-later
from functools import cache
import bpy
from .utils import GizmoUpdate
gizmo = GizmoUpdate()
"""depsgraph_update_post cannot listen to users modifying modifier parameters
Use timers to watch and use cache
"""
class update_public:
_events_func_list = {}
run_time = 0.2
@classmethod
def timers_update_poll(cls) -> bool:
return True
@classmethod
@cache
def update_poll(cls) -> bool:
return True
@classmethod
def _update_func_call_timer(cls):
if cls.timers_update_poll():
for c, func_list in cls._events_func_list.items():
if func_list and c.update_poll():
for func in func_list:
func()
cls.clear_cache_events()
return cls.run_time
@classmethod
def clear_cache_events(cls):
for cl in cls._events_func_list.keys():
if getattr(cl, 'clear_cache', False):
cl.clear_cache()
@classmethod
def clear_cache(cls):
cls.update_poll.cache_clear()
@classmethod
def append(cls, item):
if cls not in cls._events_func_list:
cls._events_func_list[cls] = []
cls._events_func_list[cls].append(item)
@classmethod
def remove(cls, item):
if item in cls._events_func_list[cls]:
cls._events_func_list[cls].remove(item)
# --------------- reg and unreg
@classmethod
def register(cls):
from bpy.app import timers
func = cls._update_func_call_timer
if not timers.is_registered(func):
timers.register(func, persistent=True)
else:
print('cls timers is registered', cls)
@classmethod
def unregister(cls):
from bpy.app import timers
func = cls._update_func_call_timer
if timers.is_registered(func):
timers.unregister(func)
else:
print('cls timers is not registered', cls)
cls._events_func_list.clear()
class simple_update(update_public, GizmoUpdate):
tmp_save_data = {}
@classmethod
def timers_update_poll(cls):
obj = bpy.context.object
if not cls.poll_context_mode_is_object():
...
elif not obj:
...
elif not cls.obj_type_is_mesh_or_lattice(obj):
...
elif cls.mod_is_simple_deform_type(obj.modifiers.active):
return True
return False
class ChangeActiveObject(simple_update):
@classmethod
@cache
def update_poll(cls):
return cls.is_change_active_object()
@classmethod
def is_change_active_object(cls, change_data=True):
import bpy
obj = bpy.context.object
name = obj.name
key = 'active_object'
if key not in cls.tmp_save_data:
if change_data:
cls.tmp_save_data[key] = name
return True
elif cls.tmp_save_data[key] != name:
if change_data:
cls.tmp_save_data[key] = name
return True
return False
class ChangeActiveSimpleDeformModifier(simple_update):
@classmethod
@cache
def update_poll(cls):
return cls.is_change_active_simple_deform()
@classmethod
def is_change_active_simple_deform(cls) -> bool:
import bpy
obj = bpy.context.object
modifiers = cls.get_modifiers_data(obj)
def update():
cls.tmp_save_data['modifiers'] = modifiers
if ChangeActiveObject.update_poll():
update()
elif 'modifiers' not in cls.tmp_save_data:
update()
elif cls.tmp_save_data['modifiers'] != modifiers:
update()
return True
return False
@classmethod
def get_modifiers_data(cls, obj):
return {'obj': obj.name,
'active_modifier': getattr(obj.modifiers.active, 'name', None),
'modifiers': list(i.name for i in obj.modifiers)}
class ChangeActiveModifierParameter(simple_update):
key = 'active_modifier_parameter'
@classmethod
@cache
def update_poll(cls):
return gizmo.active_modifier_is_simple_deform and cls.is_change_active_simple_parameter()
@classmethod
def update_modifier_parameter(cls, modifier_parameter=None):
"""Run this function when the gizmo is updated to avoid duplicate updates
"""
if not modifier_parameter:
modifier_parameter = cls.get_modifiers_parameter(gizmo.modifier)
cls.tmp_save_data[cls.key] = modifier_parameter
@classmethod
def change_modifier_parameter(cls) -> bool:
mod_data = cls.get_modifiers_parameter(gizmo.modifier)
return cls.key in cls.tmp_save_data and cls.tmp_save_data[cls.key] == mod_data
@classmethod
def is_change_active_simple_parameter(cls):
parameter = cls.get_modifiers_parameter(gizmo.modifier)
if ChangeActiveObject.update_poll():
cls.update_modifier_parameter(parameter)
elif ChangeActiveSimpleDeformModifier.update_poll():
cls.update_modifier_parameter(parameter)
elif cls.key not in cls.tmp_save_data:
cls.update_modifier_parameter(parameter)
elif cls.tmp_save_data[cls.key] != parameter:
cls.update_modifier_parameter(parameter)
return True
return False
def register():
simple_update.register()
def p():
gizmo.update_multiple_modifiers_data()
ChangeActiveObject.append(p)
ChangeActiveModifierParameter.append(p)
ChangeActiveSimpleDeformModifier.append(p)
def unregister():
simple_update.unregister()

View File

@ -0,0 +1,964 @@
# SPDX-License-Identifier: GPL-2.0-or-later
import math
import uuid
from functools import cache
from os.path import dirname, basename, realpath
import bpy
import numpy as np
from bpy.types import AddonPreferences
from mathutils import Vector, Matrix, Euler
class PublicData:
"""Public data class, where all fixed data will be placed
Classify each different type of data separately and cache it to avoid getting stuck due to excessive update frequency
"""
G_CustomShape = {} #
G_DeformDrawData = {} # Save Deform Vertex And Indices,Update data only when updating deformation boxes
G_MultipleModifiersBoundData = {}
G_INDICES = (
(0, 1), (0, 2), (1, 3), (2, 3),
(4, 5), (4, 6), (5, 7), (6, 7),
(0, 4), (1, 5), (2, 6), (3, 7)) # The order in which the 8 points of the bounding box are drawn
G_NAME = 'ViewSimpleDeformGizmo_' # Temporary use files prefix
G_DEFORM_MESH_NAME = G_NAME + 'DeformMesh'
G_TMP_MULTIPLE_MODIFIERS_MESH = 'TMP_' + G_NAME + 'MultipleModifiersMesh'
G_SUB_LEVELS = 7
G_NAME_EMPTY_AXIS = G_NAME + '_Empty_'
G_NAME_CON_LIMIT = G_NAME + 'ConstraintsLimitRotation' # constraints name
G_NAME_CON_COPY_ROTATION = G_NAME + 'ConstraintsCopyRotation'
G_ADDON_NAME = basename(dirname(realpath(__file__))) # "simple_deform_helper"
G_MODIFIERS_PROPERTY = [ # Copy modifier data
'angle',
'deform_axis',
'deform_method',
'factor',
'invert_vertex_group',
'limits', # bpy.types.bpy_prop_array
'lock_x',
'lock_y',
'lock_z',
'origin',
# 'show_expanded',
# 'show_in_editmode',
'vertex_group',
]
@classmethod
def load_gizmo_data(cls) -> None:
import json
import os
json_path = os.path.join(os.path.dirname(__file__), "gizmo.json")
with open(json_path, "r") as file:
cls.G_CustomShape = json.load(file)
@staticmethod
def from_mesh_get_triangle_face_co(mesh: 'bpy.types.Mesh') -> list:
"""
:param mesh: input mesh read vertex
:type mesh: bpy.data.meshes
:return list: vertex coordinate list[[cox,coy,coz],[cox,coy,coz]...]
"""
import bmesh
bm = bmesh.new()
bm.from_mesh(mesh)
bm.faces.ensure_lookup_table()
bm.verts.ensure_lookup_table()
bmesh.ops.triangulate(bm, faces=bm.faces)
co_list = [list(float(format(j, ".3f")) for j in vert.co) for face in bm.faces for vert in face.verts]
bm.free()
return co_list
@classmethod
def from_selected_obj_generate_json(cls):
"""Export selected object vertex data as gizmo custom paint data
The output file should be in the blender folder
gizmo.json
"""
import json
data = {}
for obj in bpy.context.selected_objects:
data[obj.name] = cls.from_mesh_get_triangle_face_co(obj.data)
print(data)
with open('gizmo.json', 'w+') as f:
f.write(json.dumps(data))
class PublicClass(PublicData):
@staticmethod
def pref_() -> "AddonPreferences":
return bpy.context.preferences.addons[PublicData.G_ADDON_NAME].preferences
@property
def pref(self=None) -> 'AddonPreferences':
"""
:return: AddonPreferences
"""
return PublicClass.pref_()
class PublicPoll(PublicClass):
@classmethod
def poll_context_mode_is_object(cls) -> bool:
return bpy.context.mode == 'OBJECT'
@classmethod
def poll_modifier_type_is_simple(cls, context):
"""
Active Object in ('MESH', 'LATTICE')
Active Modifier Type Is 'SIMPLE_DEFORM' and show_viewport
:param context:bpy.types.Object
:return:
"""
obj = context.object
if not obj:
return False
mod = obj.modifiers.active
if not mod:
return False
available_obj_type = cls.obj_type_is_mesh_or_lattice(obj)
is_available_obj = cls.mod_is_simple_deform_type(mod) and available_obj_type
is_obj_mode = cls.poll_context_mode_is_object()
show_mod = mod.show_viewport
not_is_self_mesh = obj.name != cls.G_NAME
return is_available_obj and is_obj_mode and show_mod and not_is_self_mesh
@classmethod
def poll_object_is_show(cls, context: 'bpy.types.Context') -> bool:
"""
hava active object and object is show
:param context:
:return:
"""
obj = context.object
return obj and (not obj.hide_viewport) and (not obj.hide_get())
@classmethod
def poll_simple_deform_public(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
is_simple = cls.poll_modifier_type_is_simple(context)
is_show = cls.poll_object_is_show(context)
return is_simple and show_gizmo and is_show
@classmethod
def poll_simple_deform_modifier_is_bend(cls, context):
"""
Public poll
active modifier deform_method =='BEND'
"""
simple = cls.poll_simple_deform_public(context)
is_bend = simple and (context.object.modifiers.active.deform_method == 'BEND')
return simple and is_bend
@classmethod
def poll_simple_deform_show_bend_axis_witch(cls, context):
"""
Show D
"""
switch_axis = cls.pref_().display_bend_axis_switch_gizmo
bend = cls.poll_simple_deform_modifier_is_bend(context)
return switch_axis and bend
@classmethod
def simple_deform_show_gizmo_poll(cls, context):
poll = cls.poll_simple_deform_public(context)
not_switch = (not cls.poll_simple_deform_show_bend_axis_witch(context))
return poll and not_switch
class PublicTranslate(PublicPoll):
@classmethod
def translate_text(cls, text):
return bpy.app.translations.pgettext(text)
@classmethod
def translate_header_text(cls, mode, value):
return cls.translate_text(mode) + ':{}'.format(value)
class GizmoClassMethod(PublicTranslate):
@classmethod
def get_depsgraph(cls, obj: 'bpy.types.Object'):
"""
@param obj: dep obj
@return: If there is no input obj, reverse the active object evaluated
"""
context = bpy.context
if obj is None:
obj = context.object
dep = context.evaluated_depsgraph_get()
return obj.evaluated_get(dep)
@classmethod
def get_vector_axis(cls, mod):
axis = mod.deform_axis
if 'BEND' == mod.deform_method:
vector_axis = Vector((0, 0, 1)) if axis in (
'Y', 'X') else Vector((1, 0, 0))
else:
vector = (Vector((1, 0, 0)) if (
axis == 'X') else Vector((0, 1, 0)))
vector_axis = Vector((0, 0, 1)) if (
axis == 'Z') else vector
return vector_axis
@classmethod
def get_modifiers_parameter(cls, modifier):
prop = bpy.types.bpy_prop_array
return list(
getattr(modifier, i)[:] if type(getattr(modifier, i)) == prop else getattr(modifier, i)
for i in cls.G_MODIFIERS_PROPERTY
)
@classmethod
def value_limit(cls, value, max_value=1, min_value=0):
"""
@param value: limit value
@param max_value: Maximum allowed
@param min_value: Minimum allowed
@return: If the input value is greater than the maximum value or less than the minimum value
it will be limited to the maximum or minimum value
"""
if value > max_value:
return max_value
elif value < min_value:
return min_value
else:
return value
@classmethod
def is_positive(cls, number: 'int') -> bool:
"""return bool value
if number is positive return True else return False
"""
return number == abs(number)
@classmethod
def _link_obj(cls, obj, link):
context = bpy.context
objects = context.view_layer.active_layer_collection.collection.objects
if obj.name not in objects:
if link:
objects.link(
obj)
else:
objects.unlink(
obj)
@classmethod
def link_obj_to_active_collection(cls, obj: 'bpy.types.Object'):
cls._link_obj(obj, True)
@classmethod
def unlink_obj_to_active_collection(cls, obj: 'bpy.types.Object'):
cls._link_obj(obj, False)
@classmethod
def get_mesh_max_min_co(cls, obj: 'bpy.context.object') -> '[Vector,Vector]':
if obj.type == 'MESH':
ver_len = obj.data.vertices.__len__()
list_vertices = np.zeros(ver_len * 3, dtype=np.float32)
obj.data.vertices.foreach_get('co', list_vertices)
list_vertices = list_vertices.reshape(ver_len, 3)
elif obj.type == 'LATTICE':
ver_len = obj.data.points.__len__()
list_vertices = np.zeros(ver_len * 3, dtype=np.float32)
obj.data.points.foreach_get('co_deform', list_vertices)
list_vertices = list_vertices.reshape(ver_len, 3)
else:
list_vertices = np.zeros((3, 3), dtype=np.float32)
return Vector(list_vertices.min(axis=0)).freeze(), Vector(list_vertices.max(axis=0)).freeze()
@classmethod
def matrix_calculation(cls, mat: 'Matrix', calculation_list: 'list') -> list:
return [mat @ Vector(i) for i in calculation_list]
@classmethod
def point_to_angle(cls, i, j, f, axis_):
if i == j:
if f == 0:
i[0] += 0.1
j[0] -= 0.1
elif f == 1:
i[1] -= 0.1
j[1] += 0.1
else:
i[2] -= 0.1
j[2] += 0.1
vector_value = i - j
angle = (180 * vector_value.angle(axis_) / math.pi)
return angle
@classmethod
def co_to_direction(cls, mat, data):
(min_x, min_y, min_z), (max_x, max_y,
max_z) = data
a = mat @ Vector((max_x, max_y, max_z))
b = mat @ Vector((max_x, min_y, min_z))
c = mat @ Vector((min_x, max_y, min_z))
d = mat @ Vector((min_x, min_y, max_z))
point_list = ((a, d),
(c, b),
(c, d),
(a, b),
(d, b),
(c, a),)
return list((aa + bb) / 2 for (aa, bb) in point_list)
@classmethod
def tow_co_to_coordinate(cls, data):
((min_x, min_y, min_z), (max_x, max_y, max_z)) = data
return (
Vector((max_x, min_y, min_z)),
Vector((min_x, min_y, min_z)),
Vector((max_x, max_y, min_z)),
Vector((min_x, max_y, min_z)),
Vector((max_x, min_y, max_z)),
Vector((min_x, min_y, max_z)),
Vector((max_x, max_y, max_z)),
Vector((min_x, max_y, max_z))
)
@classmethod
def mod_is_simple_deform_type(cls, mod):
return mod and mod.type == 'SIMPLE_DEFORM'
@classmethod
def obj_type_is_mesh_or_lattice(cls, obj: 'bpy.types.Object'):
return obj and (obj.type in ('MESH', 'LATTICE'))
@classmethod
def from_vertices_new_mesh(cls, name, vertices):
new_mesh = bpy.data.meshes.new(name)
new_mesh.from_pydata(vertices, cls.G_INDICES, [])
new_mesh.update()
return new_mesh
@classmethod
def copy_modifier_parameter(cls, old_mod, new_mod):
for prop_name in cls.G_MODIFIERS_PROPERTY:
origin_value = getattr(old_mod, prop_name, None)
is_array_prop = type(origin_value) == bpy.types.bpy_prop_array
value = origin_value[:] if is_array_prop else origin_value
setattr(new_mod, prop_name, value)
class PublicProperty(GizmoClassMethod):
def __from_up_down_point_get_limits_point(self, up_point, down_point):
def ex(a):
return down_point + ((up_point - down_point) * Vector((a, a, a)))
up_limits = ex(self.modifier_up_limits)
down_limits = ex(self.modifier_down_limits)
return up_limits, down_limits
@cache
def _get_limits_point_and_bound_box_co(self):
top, bottom, left, right, front, back = self.modifier_bound_box_pos
mod = self.modifier
g_l = self.__from_up_down_point_get_limits_point
origin = self.modifier.origin
if origin:
vector_axis = self.get_vector_axis(mod)
matrix = self.modifier.origin.matrix_local
origin_mat = matrix.to_3x3()
axis = origin_mat @ vector_axis
point_lit = [[top, bottom], [left, right], [front, back]]
for f in range(point_lit.__len__()):
i = point_lit[f][0]
j = point_lit[f][1]
angle = self.point_to_angle(i, j, f, axis)
if abs(angle - 180) < 0.00001:
up_point, down_point = j, i
up_limits, down_limits = g_l(j, i)
point_lit[f][1], point_lit[f][0] = up_limits, down_limits
elif abs(angle) < 0.00001:
up_point, down_point = i, j
up_limits, down_limits = g_l(i, j)
point_lit[f][0], point_lit[f][1] = up_limits, down_limits
[[top, bottom], [left, right], [front, back]] = point_lit
else:
axis = self.modifier_deform_axis
if 'BEND' == self.modifier.deform_method:
if axis in ('X', 'Y'):
up_point, down_point = top, bottom
top, bottom = up_limits, down_limits = g_l(top, bottom)
elif axis == 'Z':
up_point, down_point = right, left
right, left = up_limits, down_limits = g_l(right, left)
else:
if axis == 'X':
up_point, down_point = right, left
right, left = up_limits, down_limits = g_l(right, left)
elif axis == 'Y':
up_point, down_point = back, front
back, front = up_limits, down_limits = g_l(back, front)
elif axis == 'Z':
up_point, down_point = top, bottom
top, bottom = up_limits, down_limits = g_l(top, bottom)
points = (up_point, down_point, up_limits, down_limits)
each_point = ((right[0], back[1], top[2]), (left[0], front[1], bottom[2],))
return points, self.tow_co_to_coordinate(each_point)
# ----------------------
@cache
def _each_face_pos(self, mat, co):
return self.co_to_direction(mat, co)
@classmethod
def clear_cache(cls):
cls.clear_point_cache()
cls.clear_modifiers_data()
@classmethod
def clear_point_cache(cls):
cls._get_limits_point_and_bound_box_co.cache_clear()
@classmethod
def clear_modifiers_data(cls):
cls.G_MultipleModifiersBoundData.clear()
@classmethod
def clear_deform_data(cls):
cls.G_DeformDrawData.clear()
# --------------- Cache Data ----------------------
@property
def modifier_bound_co(self):
def get_bound_co_data():
key = 'self.modifier.name'
if key not in self.G_MultipleModifiersBoundData:
self.G_MultipleModifiersBoundData[key] = self.get_mesh_max_min_co(self.obj)
return self.G_MultipleModifiersBoundData[key]
return self.G_MultipleModifiersBoundData.get(self.modifier.name, get_bound_co_data())
@property
def modifier_bound_box_pos(self):
matrix = Matrix()
matrix.freeze()
return self.co_to_direction(matrix, self.modifier_bound_co)
@property
def modifier_limits_point(self):
points, _ = self._get_limits_point_and_bound_box_co()
return self.matrix_calculation(self.obj_matrix_world, points)
@property
def modifier_limits_bound_box(self):
_, bound = self._get_limits_point_and_bound_box_co()
return self.matrix_calculation(self.obj_matrix_world, bound)
@property
def modifier_origin_is_available(self):
try:
self._get_limits_point_and_bound_box_co()
return True
except UnboundLocalError:
self.clear_point_cache()
return False
# --------------- Compute Data ----------------------
@property
def obj(self):
return bpy.context.object
@property
def obj_matrix_world(self):
if self.obj:
mat = self.obj.matrix_world.copy()
mat.freeze()
return mat
mat = Matrix()
mat.freeze()
return mat
@property
def modifier(self):
obj = self.obj
if not obj:
return
return obj.modifiers.active
@property
def modifier_deform_axis(self):
mod = self.modifier
if mod:
return mod.deform_axis
@property
def modifier_angle(self):
mod = self.modifier
if mod:
return mod.angle
@property
def modifier_is_use_angle_value(self):
if self.active_modifier_is_simple_deform:
return self.modifier.deform_method in ('TWIST', 'BEND')
@property
def modifier_deform_method_is_bend(self):
if self.active_modifier_is_simple_deform:
return self.modifier.deform_method == 'BEND'
@property
def modifier_up_limits(self):
if self.modifier:
return self.modifier.limits[1]
@property
def modifier_down_limits(self):
if self.modifier:
return self.modifier.limits[0]
@property
def active_modifier_is_simple_deform(self):
return self.mod_is_simple_deform_type(self.modifier)
# ----- point
@property
def point_up(self):
return self.modifier_limits_point[0]
@property
def point_down(self):
return self.modifier_limits_point[1]
@property
def point_limits_up(self):
return self.modifier_limits_point[2]
@property
def point_limits_down(self):
return self.modifier_limits_point[3]
# ------
@property
def obj_origin_property_group(self):
mod = self.modifier
if mod.origin:
return mod.origin.SimpleDeformGizmo_PropertyGroup
else:
return self.obj.SimpleDeformGizmo_PropertyGroup
@property
def origin_mode(self):
return self.obj_origin_property_group.origin_mode
@property
def is_limits_middle_mode(self):
return self.origin_mode == 'LIMITS_MIDDLE'
@property
def is_middle_mode(self):
return self.origin_mode in ('LIMITS_MIDDLE', 'MIDDLE')
@property
def modifier_is_use_origin_axis(self):
return self.obj_origin_property_group.origin_mode != 'NOT'
@property
def modifier_is_have_origin(self):
return self.modifier_is_use_origin_axis and self.modifier.origin
class GizmoUpdate(PublicProperty):
def fix_origin_parent_and_angle(self):
obj = self.obj
mod = self.modifier
if not obj or not mod or not getattr(mod, 'origin', False):
return
origin = mod.origin
if not origin:
return
if origin.parent != obj:
origin.parent = obj
origin.rotation_euler.zero()
if not self.modifier_origin_is_available:
origin.location.zero()
origin.scale = 1, 1, 1
def new_origin_empty_object(self):
mod = self.modifier
obj = self.obj
origin = mod.origin
if not origin:
new_name = self.G_NAME_EMPTY_AXIS + str(uuid.uuid4())
origin_object = bpy.data.objects.new(new_name, None)
self.link_obj_to_active_collection(origin_object)
origin_object.hide_set(True)
origin_object.empty_display_size = min(obj.dimensions)
mod.origin = origin_object
else:
origin_object = mod.origin
origin_object.hide_viewport = False
if origin_object == obj:
return
# add constraints
name = self.G_NAME_CON_LIMIT
if origin_object.constraints.keys().__len__() > 2:
origin_object.constraints.clear()
if name in origin_object.constraints.keys():
limit_constraints = origin.constraints.get(name)
else:
limit_constraints = origin_object.constraints.new(
'LIMIT_ROTATION')
limit_constraints.name = name
limit_constraints.owner_space = 'WORLD'
limit_constraints.space_object = obj
limit_constraints.use_transform_limit = True
limit_constraints.use_limit_x = True
limit_constraints.use_limit_y = True
limit_constraints.use_limit_z = True
con_copy_name = self.G_NAME_CON_COPY_ROTATION
if con_copy_name in origin_object.constraints.keys():
copy_constraints = origin.constraints.get(con_copy_name)
else:
copy_constraints = origin_object.constraints.new(
'COPY_ROTATION')
copy_constraints.name = con_copy_name
copy_constraints.target = obj
copy_constraints.mix_mode = 'BEFORE'
copy_constraints.target_space = 'WORLD'
copy_constraints.owner_space = 'WORLD'
origin_mode = self.obj.SimpleDeformGizmo_PropertyGroup.origin_mode
origin_object.SimpleDeformGizmo_PropertyGroup.origin_mode = origin_mode
self.fix_origin_parent_and_angle()
return origin_object
def update_object_origin_matrix(self):
if self.modifier_is_have_origin:
origin_mode = self.origin_mode
origin_object = self.modifier.origin
if origin_mode == 'UP_LIMITS':
origin_object.matrix_world.translation = Vector(self.point_limits_up)
elif origin_mode == 'DOWN_LIMITS':
origin_object.matrix_world.translation = Vector(self.point_limits_down)
elif origin_mode == 'LIMITS_MIDDLE':
translation = (self.point_limits_up + self.point_limits_down) / 2
origin_object.matrix_world.translation = translation
elif origin_mode == 'MIDDLE':
translation = (self.point_up + self.point_down) / 2
origin_object.matrix_world.translation = translation
def update_multiple_modifiers_data(self):
obj = self.obj
context = bpy.context
if not self.obj_type_is_mesh_or_lattice(obj) or not self.poll_modifier_type_is_simple(context):
return
self.clear_point_cache()
self.clear_modifiers_data()
data = bpy.data
name = self.G_TMP_MULTIPLE_MODIFIERS_MESH
# del old tmp object
old_object = data.objects.get(name)
if old_object:
data.objects.remove(old_object)
if data.meshes.get(name):
data.meshes.remove(data.meshes.get(name))
"""get origin mesh bound box as multiple basic mesh
add multiple modifiers and get depsgraph obj bound box
"""
vertices = self.tow_co_to_coordinate(self.get_mesh_max_min_co(self.obj))
new_mesh = self.from_vertices_new_mesh(name, vertices)
modifiers_obj = data.objects.new(name, new_mesh)
self.link_obj_to_active_collection(modifiers_obj)
if modifiers_obj == obj: # is cycles
return
if modifiers_obj.parent != obj:
modifiers_obj.parent = obj
modifiers_obj.modifiers.clear()
subdivision = modifiers_obj.modifiers.new('1', 'SUBSURF')
subdivision.levels = self.G_SUB_LEVELS
for mod in context.object.modifiers:
if self.mod_is_simple_deform_type(mod):
dep_bound_tow_co = self.get_mesh_max_min_co(self.get_depsgraph(modifiers_obj))
self.G_MultipleModifiersBoundData[mod.name] = dep_bound_tow_co
new_mod = modifiers_obj.modifiers.new(mod.name, 'SIMPLE_DEFORM')
self.copy_modifier_parameter(mod, new_mod)
data.objects.remove(modifiers_obj)
def update_deform_wireframe(self):
if not self.pref.update_deform_wireframe:
return
# obj = self.obj
name = self.modifier.name
deform_name = self.G_DEFORM_MESH_NAME
co = self.G_MultipleModifiersBoundData[name]
deform_obj = bpy.data.objects.get(deform_name, None)
if not deform_obj:
a, b = 0.5, -0.5
vertices = self.tow_co_to_coordinate(((b, b, b), (a, a, a)))
new_mesh = self.from_vertices_new_mesh(name, vertices)
deform_obj = bpy.data.objects.new(deform_name, new_mesh)
deform_obj.hide_select = True
# deform_obj.hide_set(True)
deform_obj.hide_render = True
deform_obj.hide_viewport = True
self.link_obj_to_active_collection(deform_obj)
deform_obj.parent = self.obj
tmv = deform_obj.hide_viewport
tmh = deform_obj.hide_get()
deform_obj.hide_viewport = False
deform_obj.hide_set(False)
# Update Matrix
deform_obj.matrix_world = Matrix()
center = (co[0] + co[1]) / 2
scale = co[1] - co[0]
deform_obj.matrix_world = self.obj_matrix_world @ deform_obj.matrix_world
deform_obj.location = center
deform_obj.scale = scale
# Update Modifier data
mods = deform_obj.modifiers
mods.clear()
subdivision = mods.new('1', 'SUBSURF')
subdivision.levels = self.G_SUB_LEVELS
new_mod = mods.new(name, 'SIMPLE_DEFORM')
self.copy_modifier_parameter(self.modifier, new_mod)
# Get vertices data
context = bpy.context
obj = self.get_depsgraph(deform_obj)
matrix = deform_obj.matrix_world.copy()
ver_len = obj.data.vertices.__len__()
edge_len = obj.data.edges.__len__()
if 'numpy_data' not in self.G_DeformDrawData:
self.G_DeformDrawData['numpy_data'] = {}
numpy_data = self.G_DeformDrawData['numpy_data']
key = (ver_len, edge_len)
if key in numpy_data:
list_edges, list_vertices = numpy_data[key]
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))
modifiers = self.get_modifiers_parameter(self.modifier)
limits = context.object.modifiers.active.limits[:]
deform_obj.hide_viewport = tmv
deform_obj.hide_set(tmh)
self.G_DeformDrawData['simple_deform_bound_data'] = (ver, indices, self.obj_matrix_world, modifiers, limits[:])
class GizmoUtils(GizmoUpdate):
custom_shape: dict
init_mouse_region_y: float
init_mouse_region_x: float
mouse_dpi: int
matrix_basis: Matrix
draw_type: str
def generate_gizmo(self, gizmo_data):
"""Generate Gizmo From Input Data
Args:
gizmo_data (_type_): _description_
"""
for i, j, k in gizmo_data:
setattr(self, i, self.gizmos.new(j))
gizmo = getattr(self, i)
for f in k:
if f == 'target_set_operator':
gizmo.target_set_operator(k[f])
elif f == 'target_set_prop':
gizmo.target_set_prop(*k[f])
else:
setattr(gizmo, f, k[f])
def init_shape(self):
if not hasattr(self, 'custom_shape'):
self.custom_shape = {}
for i in self.G_CustomShape:
item = self.G_CustomShape[i]
self.custom_shape[i] = self.new_custom_shape('TRIS', item)
def init_setup(self):
self.init_shape()
def init_invoke(self, context, event):
self.init_mouse_region_y = event.mouse_region_y
self.init_mouse_region_x = event.mouse_region_x
def __update_matrix_func(self, context):
func = getattr(self, 'update_gizmo_matrix', None)
if func and self.modifier_origin_is_available:
func(context)
def draw(self, context):
if self.modifier_origin_is_available:
self.draw_custom_shape(self.custom_shape[self.draw_type])
self.__update_matrix_func(context)
def draw_select(self, context, select_id):
if self.modifier_origin_is_available:
self.draw_custom_shape(
self.custom_shape[self.draw_type], select_id=select_id)
self.__update_matrix_func(context)
def get_delta(self, event):
delta = (self.init_mouse_region_x - event.mouse_region_x) / self.mouse_dpi
return delta
def update_gizmo_matrix(self):
...
def event_handle(self, event):
"""General event triggering"""
data_path = ('object.SimpleDeformGizmo_PropertyGroup.origin_mode',
'object.modifiers.active.origin.SimpleDeformGizmo_PropertyGroup.origin_mode')
if event.type in ('WHEELUPMOUSE', 'WHEELDOWNMOUSE'):
reverse = (event.type == 'WHEELUPMOUSE')
for path in data_path:
bpy.ops.wm.context_cycle_enum(
data_path=path, reverse=reverse, wrap=True)
elif event.type in ('X', 'Y', 'Z'):
self.obj.modifiers.active.deform_axis = event.type
elif event.type == 'A' and 'BEND' == self.modifier.deform_method:
self.pref.display_bend_axis_switch_gizmo = True
return {'FINISHED'}
elif event.type == 'W' and event.value == 'RELEASE':
self.pref.update_deform_wireframe = self.pref.update_deform_wireframe ^ True
return {'RUNNING_MODAL'}
@staticmethod
def tag_redraw(context):
if context.area:
context.area.tag_redraw()
class GizmoGroupUtils(GizmoUtils):
bl_space_type = 'VIEW_3D'
bl_region_type = 'WINDOW'
bl_options = {'3D',
'PERSISTENT',
# 'SCALE',
# 'DEPTH_3D',
# 'SELECT',
# 'SHOW_MODAL_ALL',
# 'EXCLUDE_MODAL',
# 'TOOL_INIT', # not show
# 'TOOL_FALLBACK_KEYMAP',
# 'VR_REDRAWS'
}
class Tmp:
@classmethod
def get_origin_bounds(cls, obj: 'bpy.types.Object') -> list:
modifiers_dict = {}
for mod in obj.modifiers:
if (mod == obj.modifiers.active) or (modifiers_dict != {}):
modifiers_dict[mod] = (mod.show_render, mod.show_viewport)
mod.show_viewport = False
mod.show_render = False
matrix_obj = obj.matrix_world.copy()
obj.matrix_world.zero()
obj.scale = (1, 1, 1)
bound = cls.bound_box_to_list(obj)
obj.matrix_world = matrix_obj
for mod in modifiers_dict:
show_render, show_viewport = modifiers_dict[mod]
mod.show_render = show_render
mod.show_viewport = show_viewport
return list(bound)
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()
@classmethod
def bound_box_to_list(cls, obj: 'bpy.types.Object'):
return tuple(i[:] for i in obj.bound_box)
@classmethod
def properties_is_modifier(cls) -> bool:
"""Returns whether there is a modifier property panel open in the active window.
If it is open, it returns to True else False
"""
for area in bpy.context.screen.areas:
if area.type == 'PROPERTIES':
for space in area.spaces:
if space.type == 'PROPERTIES' and space.context == 'MODIFIER':
return True
return False
def register():
PublicData.load_gizmo_data()
def unregister():
...