new addon simple_deform_helper #104464
90
simple_deform_helper/__init__.py
Normal file
90
simple_deform_helper/__init__.py
Normal 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()
|
181
simple_deform_helper/draw.py
Normal file
181
simple_deform_helper/draw.py
Normal 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):
|
||||||
|
...
|
1
simple_deform_helper/gizmo.json
Normal file
1
simple_deform_helper/gizmo.json
Normal file
File diff suppressed because one or more lines are too long
32
simple_deform_helper/gizmo/__init__.py
Normal file
32
simple_deform_helper/gizmo/__init__.py
Normal 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()
|
161
simple_deform_helper/gizmo/angle_and_factor.py
Normal file
161
simple_deform_helper/gizmo/angle_and_factor.py
Normal 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')
|
115
simple_deform_helper/gizmo/bend_axis.py
Normal file
115
simple_deform_helper/gizmo/bend_axis.py
Normal 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)
|
51
simple_deform_helper/gizmo/set_deform_axis.py
Normal file
51
simple_deform_helper/gizmo/set_deform_axis.py
Normal 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)
|
274
simple_deform_helper/gizmo/up_down_limits_point.py
Normal file
274
simple_deform_helper/gizmo/up_down_limits_point.py
Normal 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)
|
64
simple_deform_helper/operators.py
Normal file
64
simple_deform_helper/operators.py
Normal 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()
|
77
simple_deform_helper/panel.py
Normal file
77
simple_deform_helper/panel.py
Normal 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)
|
176
simple_deform_helper/preferences.py
Normal file
176
simple_deform_helper/preferences.py
Normal 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)
|
73
simple_deform_helper/translate.py
Normal file
73
simple_deform_helper/translate.py
Normal 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__)
|
201
simple_deform_helper/update.py
Normal file
201
simple_deform_helper/update.py
Normal 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()
|
964
simple_deform_helper/utils.py
Normal file
964
simple_deform_helper/utils.py
Normal 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():
|
||||||
|
...
|
Loading…
Reference in New Issue
Block a user