new addon simple_deform_helper #104464

Closed
EMM wants to merge 29 commits from Guai_Wo_Ge_EMM/blender-addons:simple_deform_helper into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
10 changed files with 469 additions and 278 deletions
Showing only changes of commit 7cedf3a159 - Show all commits

View File

@ -5,22 +5,23 @@ import gpu
from gpu_extras.batch import batch_for_shader from gpu_extras.batch import batch_for_shader
from mathutils import Vector from mathutils import Vector
from .update import change_active_object, simple_update
from .utils import GizmoUtils from .utils import GizmoUtils
class Handler: class Handler:
@classmethod @classmethod
def add_handler(cls): def add_handler(cls):
if 'handler' not in cls.G_GizmoData: if 'handler' not in cls.G_HandleData:
cls.G_GizmoData['handler'] = bpy.types.SpaceView3D.draw_handler_add( cls.G_HandleData['handler'] = bpy.types.SpaceView3D.draw_handler_add(
Draw3D().draw, (), 'WINDOW', 'POST_VIEW') Draw3D().draw, (), 'WINDOW', 'POST_VIEW')
@classmethod @classmethod
def del_handler_text(cls): def del_handler_text(cls):
if 'scale_text' in cls.G_GizmoData: if 'scale_text' in cls.G_HandleData:
bpy.types.SpaceView3D.draw_handler_remove( bpy.types.SpaceView3D.draw_handler_remove(
cls.G_GizmoData['scale_text'], 'WINDOW') cls.G_HandleData['scale_text'], 'WINDOW')
cls.G_GizmoData.pop('scale_text') cls.G_HandleData.pop('scale_text')
@classmethod @classmethod
def del_handler(cls): def del_handler(cls):
@ -33,10 +34,10 @@ class Handler:
cls.del_handler_text() cls.del_handler_text()
if 'handler' in cls.G_GizmoData: if 'handler' in cls.G_HandleData:
bpy.types.SpaceView3D.draw_handler_remove( bpy.types.SpaceView3D.draw_handler_remove(
cls.G_GizmoData['handler'], 'WINDOW') cls.G_HandleData['handler'], 'WINDOW')
cls.G_GizmoData.clear() cls.G_HandleData.clear()
class DrawPublic: class DrawPublic:
@ -94,12 +95,14 @@ class Draw3D(GizmoUtils, DrawPublic, DrawText, Handler):
gpu.state.depth_test_set('ALWAYS') gpu.state.depth_test_set('ALWAYS')
context = bpy.context context = bpy.context
if self.simple_deform_public_poll(context): if simple_update.timers_update_poll():
self.draw_3d(context) is_switch_obj = change_active_object.is_change_active_object(False)
if self.simple_deform_public_poll(context) and not is_switch_obj:
self.draw_3d(context)
def draw_3d(self, context): def draw_3d(self, context):
self.draw_scale_text(self.obj) self.draw_scale_text(self.obj)
if not self.modifier_origin_angle_is_available: if not self.modifier_origin_is_available:
self.draw_bound_box() self.draw_bound_box()
elif self.simple_deform_show_gizmo_poll(context): elif self.simple_deform_show_gizmo_poll(context):
# draw bound box # draw bound box
@ -136,20 +139,21 @@ class Draw3D(GizmoUtils, DrawPublic, DrawText, Handler):
def draw_deform_mesh(self): def draw_deform_mesh(self):
ob = self.obj ob = self.obj
handler_dit = self.G_GizmoData deform_data = self.G_DeformDrawData
active = self.modifier active = self.modifier
# draw deform mesh # draw deform mesh
if 'simple_deform_box_data' in handler_dit and self.pref.update_deform_wireframe: if 'simple_deform_bound_data' in deform_data and self.pref.update_deform_wireframe:
pos, indices, mat, mod_data, limits = handler_dit['simple_deform_box_data'] modifiers = self.get_modifiers_parameter(self.modifier)
if ([getattr(active, i) for i in self.G_MODIFIERS_PROPERTY] == mod_data) and ( pos, indices, mat, mod_data, limits = deform_data['simple_deform_bound_data']
ob.matrix_world == mat) and limits == active.limits[:]: is_limits = limits == active.limits[:]
self.draw_3d_shader( is_mat = (ob.matrix_world == mat)
pos, indices, self.pref.deform_wireframe_color) if modifiers == mod_data and is_mat and is_limits:
self.draw_3d_shader(pos, indices, self.pref.deform_wireframe_color)
def draw_scale_text(self, ob): def draw_scale_text(self, ob):
scale_error = (ob.scale != Vector((1, 1, 1))) scale_error = (ob.scale != Vector((1, 1, 1)))
if scale_error and ('scale_text' not in self.G_GizmoData): if scale_error and ('scale_text' not in self.G_HandleData):
self.G_GizmoData['scale_text'] = bpy.types.SpaceView3D.draw_handler_add( self.G_HandleData['scale_text'] = bpy.types.SpaceView3D.draw_handler_add(
self.draw_str, (), 'WINDOW', 'POST_PIXEL') self.draw_str, (), 'WINDOW', 'POST_PIXEL')
elif not scale_error: elif not scale_error:
self.del_handler_text() self.del_handler_text()

View File

@ -6,6 +6,7 @@ from bpy.types import (
GizmoGroup, GizmoGroup,
) )
from ..update import change_active_modifier_parameter
from ..utils import GizmoUtils, GizmoGroupUtils from ..utils import GizmoUtils, GizmoGroupUtils
@ -109,10 +110,13 @@ class AngleGizmo(Gizmo, AngleUpdate):
return {'RUNNING_MODAL'} return {'RUNNING_MODAL'}
def modal(self, context, event, tweak): def modal(self, context, event, tweak):
self.clear_cache() self.clear_point_cache()
self.update_prop_value(event, tweak) self.update_prop_value(event, tweak)
self.update_deform_wireframe()
self.update_header_text(context) self.update_header_text(context)
change_active_modifier_parameter.update_modifier_parameter()
self.tag_redraw(context)
return self.event_handle(event) return self.event_handle(event)
def exit(self, context, cancel): def exit(self, context, cancel):

View File

@ -14,12 +14,7 @@ class CustomGizmo(Gizmo, GizmoUtils):
custom_shape: dict custom_shape: dict
def setup(self): def setup(self):
self.draw_type = 'None_GizmoGroup_' self.init_setup()
if not hasattr(self, 'custom_shape'):
self.custom_shape = {}
for i in self.G_CustomShape:
self.custom_shape[i] = self.new_custom_shape(
'TRIS', self.G_CustomShape[i])
def draw(self, context): def draw(self, context):
self.draw_custom_shape(self.custom_shape[self.draw_type]) self.draw_custom_shape(self.custom_shape[self.draw_type])
@ -29,6 +24,7 @@ class CustomGizmo(Gizmo, GizmoUtils):
self.custom_shape[self.draw_type], select_id=select_id) self.custom_shape[self.draw_type], select_id=select_id)
def invoke(self, context, event): def invoke(self, context, event):
self.init_invoke(context, event)
return {'RUNNING_MODAL'} return {'RUNNING_MODAL'}
def modal(self, context, event, tweak): def modal(self, context, event, tweak):

View File

@ -34,17 +34,18 @@ class SetDeformGizmoGroup(GizmoGroup, GizmoGroupUtils):
setattr(self, f'deform_axis_{axis.lower()}', gizmo) setattr(self, f'deform_axis_{axis.lower()}', gizmo)
def draw_prepare(self, context): def draw_prepare(self, context):
bound = self.modifier_bound_co
if 'co' in self.G_GizmoData: if bound:
obj = self.get_depsgraph(self.obj) obj = self.get_depsgraph(self.obj)
dimensions = obj.dimensions dimensions = obj.dimensions
def _mat(f): def mat(f):
co = self.G_GizmoData['co'][0] b = bound[0]
co = (co[0] + (max(dimensions) * f), co[1], co = (b[0] + (max(dimensions) * f),
co[2] - (min(dimensions) * 0.3)) b[1],
b[2] - (min(dimensions) * 0.3))
return self.obj_matrix_world @ Vector(co) return self.obj_matrix_world @ Vector(co)
self.deform_axis_x.matrix_basis.translation = _mat(0) self.deform_axis_x.matrix_basis.translation = mat(0)
self.deform_axis_y.matrix_basis.translation = _mat(0.3) self.deform_axis_y.matrix_basis.translation = mat(0.3)
self.deform_axis_z.matrix_basis.translation = _mat(0.6) self.deform_axis_z.matrix_basis.translation = mat(0.6)

View File

@ -6,6 +6,7 @@ from bpy.types import Gizmo, GizmoGroup
from bpy_extras import view3d_utils from bpy_extras import view3d_utils
from mathutils import Vector from mathutils import Vector
from ..update import change_active_modifier_parameter
from ..utils import GizmoUtils, GizmoGroupUtils from ..utils import GizmoUtils, GizmoGroupUtils
@ -201,22 +202,26 @@ class UpDownLimitsGizmo(Gizmo, GizmoUpdate):
'down_limits', self.int_value_down_limits) 'down_limits', self.int_value_down_limits)
def modal(self, context, event, tweak): def modal(self, context, event, tweak):
st = time() self.clear_point_cache()
self.clear_cache()
if self.modifier_is_use_origin_axis: if self.modifier_is_use_origin_axis:
self.new_origin_empty_object() self.new_origin_empty_object()
# return {'RUNNING_MODAL'}
self.difference_value = self.modifier_up_limits - self.modifier_down_limits self.difference_value = self.modifier_up_limits - self.modifier_down_limits
self.middle_limits_value = (self.modifier_up_limits + self.modifier_down_limits) / 2 self.middle_limits_value = (self.modifier_up_limits + self.modifier_down_limits) / 2
self.set_prop_value(event) try:
self.clear_cache() self.set_prop_value(event)
self.update_object_origin_matrix() self.clear_point_cache()
# self.update_deform_wireframe(self.get_depsgraph(origin_object)) self.update_object_origin_matrix()
except Exception:
...
# return {'FINISHED'}
self.update_header_text(context) self.update_header_text(context)
return_handle = self.event_handle(event) return_handle = self.event_handle(event)
print('modal time sum ', time() - st) change_active_modifier_parameter.update_modifier_parameter()
self.update_deform_wireframe()
return return_handle return return_handle

View File

@ -26,7 +26,7 @@ class DeformAxisOperator(Operator, GizmoUtils):
return {'RUNNING_MODAL'} return {'RUNNING_MODAL'}
def modal(self, context, event): def modal(self, context, event):
self.clear_cache() self.clear_point_cache()
mod = context.object.modifiers.active mod = context.object.modifiers.active
mod.deform_axis = self.Deform_Axis mod.deform_axis = self.Deform_Axis
empty = self.new_origin_empty_object() empty = self.new_origin_empty_object()
@ -39,7 +39,7 @@ class DeformAxisOperator(Operator, GizmoUtils):
('max_z', self.Z_Value), ('max_z', self.Z_Value),
('min_z', self.Z_Value), ('min_z', self.Z_Value),
): ):
setattr(empty.constraints[self.G_CON_LIMIT_NAME], limit, value) setattr(empty.constraints[self.G_NAME_CON_LIMIT], limit, value)
if ((not is_positive) and self.Is_Positive) or (is_positive and (not self.Is_Positive)): if ((not is_positive) and self.Is_Positive) or (is_positive and (not self.Is_Positive)):
mod.angle = mod.angle * -1 mod.angle = mod.angle * -1

View File

@ -41,7 +41,7 @@ class SimpleDeformHelperToolPanel(Panel, GizmoUtils):
'show_set_axis_button', 'show_set_axis_button',
icon='EMPTY_AXIS', icon='EMPTY_AXIS',
text='') text='')
if mod.deform_method == 'BEND': if pref.modifier_deform_method_is_bend:
layout.prop(pref, layout.prop(pref,
'display_bend_axis_switch_gizmo', 'display_bend_axis_switch_gizmo',
toggle=1) toggle=1)

View File

@ -96,12 +96,10 @@ class SimpleDeformGizmoAddonPreferences(AddonPreferences, GizmoUtils):
row.prop(mod, show_type) row.prop(mod, show_type)
class SimpleDeformGizmoObjectPropertyGroup(PropertyGroup): class SimpleDeformGizmoObjectPropertyGroup(PropertyGroup, GizmoUtils):
def _limits_up(self, context): def _limits_up(self, context):
mod = context.object.modifiers if self.active_modifier_is_simple_deform:
if mod and (mod.active.type == 'SIMPLE_DEFORM'): self.modifier.limits[1] = self.up_limits
mod = mod.active
mod.limits[1] = self.up_limits
up_limits: FloatProperty(name='up', up_limits: FloatProperty(name='up',
description='UP Limits(Red)', description='UP Limits(Red)',
@ -111,10 +109,8 @@ class SimpleDeformGizmoObjectPropertyGroup(PropertyGroup):
min=0) min=0)
def _limits_down(self, context): def _limits_down(self, context):
mod = context.object.modifiers if self.active_modifier_is_simple_deform:
if mod and (mod.active.type == 'SIMPLE_DEFORM'): self.modifier.limits[0] = self.down_limits
mod = mod.active
mod.limits[0] = self.down_limits
down_limits: FloatProperty(name='down', down_limits: FloatProperty(name='down',
description='Lower limit(Green)', description='Lower limit(Green)',

View File

@ -1,78 +1,147 @@
# SPDX-License-Identifier: GPL-2.0-or-later # SPDX-License-Identifier: GPL-2.0-or-later
from functools import cache
import bpy
from .utils import GizmoUpdate 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: class update_public:
_event_func_list = {} _events_func_list = {}
update_func: 'function'
tmp_save_data = {}
run_time = 0.2 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 @classmethod
def register(cls): def register(cls):
import bpy from bpy.app import timers
bpy.app.timers.register(cls.update_func, persistent=True) 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 @classmethod
def unregister(cls): def unregister(cls):
from bpy.app import timers from bpy.app import timers
func = cls.update_func func = cls._update_func_call_timer
if timers.is_registered(func): if timers.is_registered(func):
timers.unregister(func) timers.unregister(func)
else:
@classmethod print('cls timers is not registered', cls)
def _update_call(cls): cls._events_func_list.clear()
for i in cls._event_func_list[cls]:
i()
@classmethod
def append(cls, item):
if cls not in cls._event_func_list:
cls._event_func_list[cls] = []
cls._event_func_list[cls].append(item)
@classmethod
def remove(cls, item):
if item in cls._event_func_list[cls]:
cls._event_func_list[cls].remove(item)
class change_active_object(update_public): class simple_update(update_public, GizmoUpdate):
tmp_save_data = {} tmp_save_data = {}
from bpy.app.handlers import depsgraph_update_post, persistent
handler_type = depsgraph_update_post
@classmethod @classmethod
def update_func(cls): def timers_update_poll(cls):
import bpy obj = bpy.context.object
name = bpy.context.object.name if not cls.context_mode_is_object():
key = 'active_object' ...
if key not in cls.tmp_save_data or cls.tmp_save_data[key] != name: elif not obj:
cls._update_call() ...
cls.tmp_save_data[key] = name elif not cls.obj_type_is_mesh_or_lattice(obj):
return cls.run_time ...
elif cls.mod_is_simple_deform_type(obj.modifiers.active):
return True
return False
class change_active_simple_deform_modifier(update_public): class change_active_object(simple_update):
@classmethod
@cache
def update_poll(cls):
return cls.is_change_active_object()
@classmethod @classmethod
def update_func(cls): def is_change_active_object(cls, change_data=True):
import bpy import bpy
obj = bpy.context.object obj = bpy.context.object
if not obj or obj.type != 'MESH':
return cls.run_time
name = obj.name name = obj.name
key = 'active_object' key = 'active_object'
if key not in cls.tmp_save_data:
if change_data:
cls.tmp_save_data[key] = name
return True
elif cls.tmp_save_data[key] != name:
if change_data:
cls.tmp_save_data[key] = name
return True
return False
class change_active_simple_deform_modifier(simple_update):
@classmethod
@cache
def update_poll(cls):
return cls.is_change_active_simple_deform()
@classmethod
def is_change_active_simple_deform(cls) -> bool:
import bpy
obj = bpy.context.object
modifiers = cls.get_modifiers_data(obj) modifiers = cls.get_modifiers_data(obj)
change_modifiers = 'modifiers' not in cls.tmp_save_data or cls.tmp_save_data['modifiers'] != modifiers
if key not in cls.tmp_save_data or cls.tmp_save_data[key] != name: def update():
cls.tmp_save_data['modifiers'] = modifiers cls.tmp_save_data['modifiers'] = modifiers
cls.tmp_save_data[key] = name
elif change_modifiers: if change_active_object.update_poll():
cls.tmp_save_data['modifiers'] = modifiers update()
cls._update_call() elif 'modifiers' not in cls.tmp_save_data:
return cls.run_time update()
elif cls.tmp_save_data['modifiers'] != modifiers:
update()
return True
return False
@classmethod @classmethod
def get_modifiers_data(cls, obj): def get_modifiers_data(cls, obj):
@ -81,20 +150,52 @@ class change_active_simple_deform_modifier(update_public):
'modifiers': list(i.name for i in obj.modifiers)} 'modifiers': list(i.name for i in obj.modifiers)}
gizmo = GizmoUpdate() class change_active_modifier_parameter(simple_update):
key = 'active_modifier_parameter'
@classmethod
@cache
def update_poll(cls):
return gizmo.active_modifier_is_simple_deform and cls.is_change_active_simple_parameter()
@classmethod
def update_modifier_parameter(cls, modifier_parameter=None):
"""Run this function when the gizmo is updated to avoid duplicate updates
"""
if not modifier_parameter:
modifier_parameter = cls.get_modifiers_parameter(gizmo.modifier)
cls.tmp_save_data[cls.key] = modifier_parameter
@classmethod
def change_modifier_parameter(cls) -> bool:
mod_data = cls.get_modifiers_parameter(gizmo.modifier)
return cls.key in cls.tmp_save_data and cls.tmp_save_data[cls.key] == mod_data
@classmethod
def is_change_active_simple_parameter(cls):
parameter = cls.get_modifiers_parameter(gizmo.modifier)
if change_active_object.update_poll():
cls.update_modifier_parameter(parameter)
elif change_active_simple_deform_modifier.update_poll():
cls.update_modifier_parameter(parameter)
elif cls.key not in cls.tmp_save_data:
cls.update_modifier_parameter(parameter)
elif cls.tmp_save_data[cls.key] != parameter:
cls.update_modifier_parameter(parameter)
return True
return False
def register(): def register():
change_active_object.register() simple_update.register()
change_active_simple_deform_modifier.register()
change_active_object.append(gizmo.update_multiple_modifiers_data) def p():
change_active_simple_deform_modifier.append(gizmo.update_multiple_modifiers_data) gizmo.update_multiple_modifiers_data()
change_active_object.append(p)
change_active_modifier_parameter.append(p)
change_active_simple_deform_modifier.append(p)
def unregister(): def unregister():
change_active_object.remove(gizmo.update_multiple_modifiers_data) simple_update.unregister()
change_active_simple_deform_modifier.remove(gizmo.update_multiple_modifiers_data)
change_active_object.unregister()
change_active_simple_deform_modifier.unregister()

View File

@ -4,7 +4,6 @@ import math
import uuid import uuid
from functools import cache from functools import cache
from os.path import dirname, basename, realpath from os.path import dirname, basename, realpath
from time import time
import bpy import bpy
import numpy as np import numpy as np
@ -13,27 +12,38 @@ from mathutils import Vector, Matrix, Euler
class PublicData: class PublicData:
"""Public data class, all fixed data will be placed here """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_CustomShape = {} #
G_GizmoData = {} G_HandleData = {} # Save draw Handle
G_Modifiers_Data = {}
G_DeformDrawData = {} # Save Deform Vertex And Indices,Update data only when updating deformation boxes
G_MultipleModifiersBoundData = {}
G_INDICES = ( G_INDICES = (
(0, 1), (0, 2), (1, 3), (2, 3), (0, 1), (0, 2), (1, 3), (2, 3),
(4, 5), (4, 6), (5, 7), (6, 7), (4, 5), (4, 6), (5, 7), (6, 7),
(0, 4), (1, 5), (2, 6), (3, 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_NAME = 'ViewSimpleDeformGizmo_' # Temporary use files prefix
G_ADDON_NAME = basename(dirname(realpath(__file__))) # "simple_deform_helper"
G_CON_LIMIT_NAME = G_NAME + 'constraints_limit_rotation' # constraints name
G_MODIFIERS_PROPERTY = [ # copy modifier data 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', 'angle',
'deform_axis', 'deform_axis',
'deform_method', 'deform_method',
'factor', 'factor',
'invert_vertex_group', 'invert_vertex_group',
'limits', 'limits', # bpy.types.bpy_prop_array
'lock_x', 'lock_x',
'lock_y', 'lock_y',
'lock_z', 'lock_z',
@ -72,10 +82,11 @@ class PublicData:
def from_selected_obj_generate_json(cls): def from_selected_obj_generate_json(cls):
"""Export selected object vertex data as gizmo custom paint data """Export selected object vertex data as gizmo custom paint data
The output file should be in the blender folder The output file should be in the blender folder
gizmo.json
""" """
import json import json
data = {} data = {}
for obj in bpy.context.selected_object: for obj in bpy.context.selected_objects:
data[obj.name] = cls.from_mesh_get_triangle_face_co(obj.data) data[obj.name] = cls.from_mesh_get_triangle_face_co(obj.data)
print(data) print(data)
with open('gizmo.json', 'w+') as f: with open('gizmo.json', 'w+') as f:
@ -96,10 +107,14 @@ class PublicClass(PublicData):
class PublicPoll(PublicClass): class PublicPoll(PublicClass):
@classmethod
def context_mode_is_object(cls) -> bool:
return bpy.context.mode == 'OBJECT'
@classmethod @classmethod
def simple_deform_modifier_is_simple(cls, context): def simple_deform_modifier_is_simple(cls, context):
""" """
Active Object in ('MESH', 'LATTICE') Active Object in ('MESH', 'LATTICE')
Active Modifier Type Is 'SIMPLE_DEFORM' and show_viewport Active Modifier Type Is 'SIMPLE_DEFORM' and show_viewport
:param context:bpy.types.Object :param context:bpy.types.Object
:return: :return:
@ -112,10 +127,9 @@ class PublicPoll(PublicClass):
if not mod: if not mod:
return False return False
available_obj_type = obj and (obj.type in ('MESH', 'LATTICE')) available_obj_type = cls.obj_type_is_mesh_or_lattice(obj)
available_modifiers_type = mod and (mod.type == 'SIMPLE_DEFORM') is_available_obj = cls.mod_is_simple_deform_type(mod) and available_obj_type
is_available_obj = available_modifiers_type and available_obj_type is_obj_mode = cls.context_mode_is_object()
is_obj_mode = context.mode == 'OBJECT'
show_mod = mod.show_viewport show_mod = mod.show_viewport
not_is_self_mesh = obj.name != cls.G_NAME 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 return is_available_obj and is_obj_mode and show_mod and not_is_self_mesh
@ -159,7 +173,51 @@ class PublicPoll(PublicClass):
return poll and not_switch return poll and not_switch
class PublicUtils(PublicPoll): 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 @classmethod
def value_limit(cls, value, max_value=1, min_value=0): def value_limit(cls, value, max_value=1, min_value=0):
""" """
@ -183,18 +241,6 @@ class PublicUtils(PublicPoll):
""" """
return number == abs(number) return number == abs(number)
@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 @classmethod
def link_obj_to_active_collection(cls, obj: 'bpy.types.Object'): def link_obj_to_active_collection(cls, obj: 'bpy.types.Object'):
context = bpy.context context = bpy.context
@ -203,22 +249,6 @@ class PublicUtils(PublicPoll):
objects.link( objects.link(
obj) obj)
@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
@classmethod
def bound_box_to_list(cls, obj: 'bpy.types.Object'):
return tuple(i[:] for i in obj.bound_box)
@classmethod @classmethod
def get_mesh_max_min_co(cls, obj: 'bpy.context.object') -> '[Vector,Vector]': def get_mesh_max_min_co(cls, obj: 'bpy.context.object') -> '[Vector,Vector]':
if obj.type == 'MESH': if obj.type == 'MESH':
@ -271,36 +301,6 @@ class PublicUtils(PublicPoll):
return list((aa + bb) / 2 for (aa, bb) in point_list) return list((aa + bb) / 2 for (aa, bb) in point_list)
@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(PublicUtils):
@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_bound_co_data(cls):
if 'co' not in cls.G_GizmoData:
cls.G_GizmoData['co'] = cls.get_mesh_max_min_co(
bpy.context.object)
return cls.G_GizmoData['co']
@classmethod @classmethod
def tow_co_to_coordinate(cls, data): def tow_co_to_coordinate(cls, data):
((min_x, min_y, min_z), (max_x, max_y, max_z)) = data ((min_x, min_y, min_z), (max_x, max_y, max_z)) = data
@ -315,6 +315,29 @@ class GizmoClassMethod(PublicUtils):
Vector((min_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): class PublicProperty(GizmoClassMethod):
@ -332,15 +355,17 @@ class PublicProperty(GizmoClassMethod):
top, bottom, left, right, front, back = self.modifier_bound_box_pos top, bottom, left, right, front, back = self.modifier_bound_box_pos
mod = self.modifier mod = self.modifier
g_l = self.__from_up_down_point_get_limits_point g_l = self.__from_up_down_point_get_limits_point
if self.modifier.origin: origin = self.modifier.origin
if origin:
vector_axis = self.get_vector_axis(mod) vector_axis = self.get_vector_axis(mod)
origin_mat = mod.origin.matrix_basis.to_3x3() matrix = self.modifier.origin.matrix_local
axis_ = origin_mat @ vector_axis origin_mat = matrix.to_3x3()
axis = origin_mat @ vector_axis
point_lit = [[top, bottom], [left, right], [front, back]] point_lit = [[top, bottom], [left, right], [front, back]]
for f in range(point_lit.__len__()): for f in range(point_lit.__len__()):
i = point_lit[f][0] i = point_lit[f][0]
j = point_lit[f][1] j = point_lit[f][1]
angle = self.point_to_angle(i, j, f, axis_) angle = self.point_to_angle(i, j, f, axis)
if abs(angle - 180) < 0.00001: if abs(angle - 180) < 0.00001:
up_point, down_point = j, i up_point, down_point = j, i
up_limits, down_limits = g_l(j, i) up_limits, down_limits = g_l(j, i)
@ -349,7 +374,6 @@ class PublicProperty(GizmoClassMethod):
up_point, down_point = i, j up_point, down_point = i, j
up_limits, down_limits = g_l(i, j) up_limits, down_limits = g_l(i, j)
point_lit[f][0], point_lit[f][1] = up_limits, down_limits point_lit[f][0], point_lit[f][1] = up_limits, down_limits
[[top, bottom], [left, right], [front, back]] = point_lit [[top, bottom], [left, right], [front, back]] = point_lit
else: else:
axis = self.modifier_deform_axis axis = self.modifier_deform_axis
@ -383,28 +407,31 @@ class PublicProperty(GizmoClassMethod):
@classmethod @classmethod
def clear_cache(cls): def clear_cache(cls):
cls._each_face_pos.cache_clear() cls.clear_point_cache()
cls.clear_modifiers_data()
@classmethod
def clear_point_cache(cls):
cls._get_limits_point_and_bound_box_co.cache_clear() cls._get_limits_point_and_bound_box_co.cache_clear()
@classmethod @classmethod
def clear_data(cls): def clear_modifiers_data(cls):
cls.G_GizmoData.clear() cls.G_MultipleModifiersBoundData.clear()
@classmethod @classmethod
def clear_modifiers_data(cls): def clear_deform_data(cls):
cls.G_Modifiers_Data.clear() cls.G_DeformDrawData.clear()
# --------------- Cache Data ---------------------- # --------------- Cache Data ----------------------
@property
def each_face_pos(self):
matrix = Matrix()
matrix.freeze()
return self._each_face_pos(matrix, self.get_bound_co_data())
@property @property
def modifier_bound_co(self): def modifier_bound_co(self):
return self.G_Modifiers_Data.get(self.modifier.name, self.get_bound_co_data()) 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 @property
def modifier_bound_box_pos(self): def modifier_bound_box_pos(self):
@ -423,13 +450,12 @@ class PublicProperty(GizmoClassMethod):
return self.matrix_calculation(self.obj_matrix_world, bound) return self.matrix_calculation(self.obj_matrix_world, bound)
@property @property
def modifier_origin_angle_is_available(self): def modifier_origin_is_available(self):
try: try:
self._get_limits_point_and_bound_box_co() self._get_limits_point_and_bound_box_co()
return True return True
except UnboundLocalError: except UnboundLocalError:
print('modifier_origin_angle_is_available') self.clear_point_cache()
self.clear_cache()
return False return False
# --------------- Compute Data ---------------------- # --------------- Compute Data ----------------------
@ -471,6 +497,11 @@ class PublicProperty(GizmoClassMethod):
if self.active_modifier_is_simple_deform: if self.active_modifier_is_simple_deform:
return self.modifier.deform_method in ('TWIST', 'BEND') return self.modifier.deform_method in ('TWIST', 'BEND')
@property
def modifier_deform_method_is_bend(self):
if self.active_modifier_is_simple_deform:
return self.modifier.deform_method == 'BEND'
@property @property
def modifier_up_limits(self): def modifier_up_limits(self):
if self.modifier: if self.modifier:
@ -483,7 +514,7 @@ class PublicProperty(GizmoClassMethod):
@property @property
def active_modifier_is_simple_deform(self): def active_modifier_is_simple_deform(self):
return self.modifier and self.modifier.type == 'SIMPLE_DEFORM' return self.mod_is_simple_deform_type(self.modifier)
# ----- point # ----- point
@property @property
@ -526,7 +557,11 @@ class PublicProperty(GizmoClassMethod):
@property @property
def modifier_is_use_origin_axis(self): def modifier_is_use_origin_axis(self):
return self.obj_origin_property_group.origin_mode != 'NOT' and not self.modifier.origin 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): class GizmoUpdate(PublicProperty):
@ -543,7 +578,7 @@ class GizmoUpdate(PublicProperty):
if origin.parent != obj: if origin.parent != obj:
origin.parent = obj origin.parent = obj
origin.rotation_euler.zero() origin.rotation_euler.zero()
if not self.modifier_origin_angle_is_available: if not self.modifier_origin_is_available:
origin.location.zero() origin.location.zero()
origin.scale = 1, 1, 1 origin.scale = 1, 1, 1
@ -552,7 +587,7 @@ class GizmoUpdate(PublicProperty):
obj = self.obj obj = self.obj
origin = mod.origin origin = mod.origin
if not origin: if not origin:
new_name = self.G_NAME + '_Empty_' + str(uuid.uuid4()) new_name = self.G_NAME_EMPTY_AXIS + str(uuid.uuid4())
origin_object = bpy.data.objects.new(new_name, None) origin_object = bpy.data.objects.new(new_name, None)
self.link_obj_to_active_collection(origin_object) self.link_obj_to_active_collection(origin_object)
origin_object.hide_set(True) origin_object.hide_set(True)
@ -564,7 +599,7 @@ class GizmoUpdate(PublicProperty):
if origin_object == obj: if origin_object == obj:
return return
# add constraints # add constraints
name = self.G_CON_LIMIT_NAME name = self.G_NAME_CON_LIMIT
if origin_object.constraints.keys().__len__() > 2: if origin_object.constraints.keys().__len__() > 2:
origin_object.constraints.clear() origin_object.constraints.clear()
if name in origin_object.constraints.keys(): if name in origin_object.constraints.keys():
@ -579,7 +614,7 @@ class GizmoUpdate(PublicProperty):
limit_constraints.use_limit_x = True limit_constraints.use_limit_x = True
limit_constraints.use_limit_y = True limit_constraints.use_limit_y = True
limit_constraints.use_limit_z = True limit_constraints.use_limit_z = True
con_copy_name = self.G_NAME + 'constraints_copy_rotation' con_copy_name = self.G_NAME_CON_COPY_ROTATION
if con_copy_name in origin_object.constraints.keys(): if con_copy_name in origin_object.constraints.keys():
copy_constraints = origin.constraints.get(con_copy_name) copy_constraints = origin.constraints.get(con_copy_name)
else: else:
@ -590,16 +625,15 @@ class GizmoUpdate(PublicProperty):
copy_constraints.mix_mode = 'BEFORE' copy_constraints.mix_mode = 'BEFORE'
copy_constraints.target_space = 'WORLD' copy_constraints.target_space = 'WORLD'
copy_constraints.owner_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() self.fix_origin_parent_and_angle()
return origin_object return origin_object
def update_object_origin_matrix(self): def update_object_origin_matrix(self):
st = time() if self.modifier_is_have_origin:
origin_mode = self.origin_mode origin_mode = self.origin_mode
origin_object = self.modifier.origin origin_object = self.modifier.origin
is_use = self.modifier_is_use_origin_axis
if origin_object and is_use:
if origin_mode == 'UP_LIMITS': if origin_mode == 'UP_LIMITS':
origin_object.matrix_world.translation = Vector(self.point_limits_up) origin_object.matrix_world.translation = Vector(self.point_limits_up)
elif origin_mode == 'DOWN_LIMITS': elif origin_mode == 'DOWN_LIMITS':
@ -610,80 +644,106 @@ class GizmoUpdate(PublicProperty):
elif origin_mode == 'MIDDLE': elif origin_mode == 'MIDDLE':
translation = (self.point_up + self.point_down) / 2 translation = (self.point_up + self.point_down) / 2
origin_object.matrix_world.translation = translation origin_object.matrix_world.translation = translation
print('update_object_origin_matrix', time() - st)
def update_multiple_modifiers_data(self): def update_multiple_modifiers_data(self):
print('update_multiple_modifiers_data', self)
st = time()
obj = self.obj obj = self.obj
context = bpy.context context = bpy.context
if obj.type not in ('MESH', 'LATTICE') or not self.simple_deform_public_poll(context): if not self.obj_type_is_mesh_or_lattice(obj) or not self.simple_deform_modifier_is_simple(context):
return return
self.clear_cache() self.clear_point_cache()
self.clear_modifiers_data()
data = bpy.data data = bpy.data
name = self.G_NAME name = self.G_TMP_MULTIPLE_MODIFIERS_MESH
origin_object = data.objects.get(name)
# update multiple simple_deform bound data # del old tmp object
if origin_object: old_object = data.objects.get(name)
data.objects.remove(origin_object) if old_object:
data.objects.remove(old_object)
if data.meshes.get(name): if data.meshes.get(name):
data.meshes.remove(data.meshes.get(name)) data.meshes.remove(data.meshes.get(name))
vertices = self.tow_co_to_coordinate(self.get_bound_co_data()) """get origin mesh bound box as multiple basic mesh
new_mesh = data.meshes.new(name) add multiple modifiers and get depsgraph obj bound box
new_mesh.from_pydata(vertices, self.G_INDICES, []) """
new_mesh.update() vertices = self.tow_co_to_coordinate(self.get_mesh_max_min_co(self.obj))
deform_obj = data.objects.new(name, new_mesh) new_mesh = self.from_vertices_new_mesh(name, vertices)
modifiers_obj = data.objects.new(name, new_mesh)
self.link_obj_to_active_collection(deform_obj) self.link_obj_to_active_collection(modifiers_obj)
if deform_obj == obj: if modifiers_obj == obj: # is cycles
return return
if deform_obj.parent != obj: if modifiers_obj.parent != obj:
deform_obj.parent = obj modifiers_obj.parent = obj
deform_obj.modifiers.clear() modifiers_obj.modifiers.clear()
subdivision = deform_obj.modifiers.new('1', 'SUBSURF') subdivision = modifiers_obj.modifiers.new('1', 'SUBSURF')
subdivision.levels = 7 subdivision.levels = self.G_SUB_LEVELS
self.G_GizmoData['co'] = self.get_bound_co_data()
for mo in context.object.modifiers: for mod in context.object.modifiers:
if mo.type == 'SIMPLE_DEFORM': if self.mod_is_simple_deform_type(mod):
obj = self.get_depsgraph(deform_obj) dep_bound_tow_co = self.get_mesh_max_min_co(self.get_depsgraph(modifiers_obj))
self.G_Modifiers_Data[mo.name] = self.get_mesh_max_min_co(obj) self.G_MultipleModifiersBoundData[mod.name] = dep_bound_tow_co
simple_deform = deform_obj.modifiers.new( new_mod = modifiers_obj.modifiers.new(mod.name, 'SIMPLE_DEFORM')
mo.name, 'SIMPLE_DEFORM') self.copy_modifier_parameter(mod, new_mod)
simple_deform.deform_method = mo.deform_method data.objects.remove(modifiers_obj)
simple_deform.deform_axis = mo.deform_axis
simple_deform.lock_x = mo.lock_x
simple_deform.lock_y = mo.lock_y
simple_deform.lock_z = mo.lock_z
simple_deform.origin = mo.origin
simple_deform.limits[1] = mo.limits[1]
simple_deform.limits[0] = mo.limits[0]
simple_deform.angle = mo.angle
simple_deform.show_viewport = mo.show_viewport
deform_obj.hide_select = True
deform_obj.hide_set(True)
deform_obj.hide_viewport = False
deform_obj.hide_render = True
deform_obj.hide_viewport = True
deform_obj.hide_set(True)
print('multiple_modifiers', time() - st)
def update_deform_wireframe(self, obj): def update_deform_wireframe(self):
if not self.pref.update_deform_wireframe: if not self.pref.update_deform_wireframe:
return 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 context = bpy.context
matrix = self.obj_matrix_world.copy() obj = self.get_depsgraph(deform_obj)
matrix = deform_obj.matrix_world.copy()
ver_len = obj.data.vertices.__len__() ver_len = obj.data.vertices.__len__()
edge_len = obj.data.edges.__len__() edge_len = obj.data.edges.__len__()
if 'numpy_data' not in self.G_DeformDrawData:
if 'numpy_data' not in self.G_GizmoData: self.G_DeformDrawData['numpy_data'] = {}
self.G_GizmoData['numpy_data'] = {} numpy_data = self.G_DeformDrawData['numpy_data']
numpy_data = self.G_GizmoData['numpy_data']
key = (ver_len, edge_len) key = (ver_len, edge_len)
if key in numpy_data: if key in numpy_data:
list_edges, list_vertices = numpy_data[key] list_edges, list_vertices = numpy_data[key]
@ -702,10 +762,13 @@ class GizmoUpdate(PublicProperty):
obj.data.edges.foreach_get('vertices', list_edges) obj.data.edges.foreach_get('vertices', list_edges)
indices = list_edges.reshape((edge_len, 2)) indices = list_edges.reshape((edge_len, 2))
modifiers = self.get_modifiers_parameter(self.modifier)
limits = context.object.modifiers.active.limits[:] limits = context.object.modifiers.active.limits[:]
modifiers = [getattr(context.object.modifiers.active, i)
for i in self.G_MODIFIERS_PROPERTY] deform_obj.hide_viewport = tmv
self.G_GizmoData['simple_deform_box_data'] = (ver, indices, matrix, modifiers, limits[:]) deform_obj.hide_set(tmh)
self.G_DeformDrawData['simple_deform_bound_data'] = (ver, indices, self.obj_matrix_world, modifiers, limits[:])
class GizmoUtils(GizmoUpdate): class GizmoUtils(GizmoUpdate):
@ -748,16 +811,16 @@ class GizmoUtils(GizmoUpdate):
def __update_matrix_func(self, context): def __update_matrix_func(self, context):
func = getattr(self, 'update_gizmo_matrix', None) func = getattr(self, 'update_gizmo_matrix', None)
if func and self.modifier_origin_angle_is_available: if func and self.modifier_origin_is_available:
func(context) func(context)
def draw(self, context): def draw(self, context):
if self.modifier_origin_angle_is_available: if self.modifier_origin_is_available:
self.draw_custom_shape(self.custom_shape[self.draw_type]) self.draw_custom_shape(self.custom_shape[self.draw_type])
self.__update_matrix_func(context) self.__update_matrix_func(context)
def draw_select(self, context, select_id): def draw_select(self, context, select_id):
if self.modifier_origin_angle_is_available: if self.modifier_origin_is_available:
self.draw_custom_shape( self.draw_custom_shape(
self.custom_shape[self.draw_type], select_id=select_id) self.custom_shape[self.draw_type], select_id=select_id)
self.__update_matrix_func(context) self.__update_matrix_func(context)
@ -788,6 +851,11 @@ class GizmoUtils(GizmoUpdate):
self.pref.update_deform_wireframe = self.pref.update_deform_wireframe ^ True self.pref.update_deform_wireframe = self.pref.update_deform_wireframe ^ True
return {'RUNNING_MODAL'} return {'RUNNING_MODAL'}
@staticmethod
def tag_redraw(context):
if context.area:
context.area.tag_redraw()
class GizmoGroupUtils(GizmoUtils): class GizmoGroupUtils(GizmoUtils):
bl_space_type = 'VIEW_3D' bl_space_type = 'VIEW_3D'
@ -848,6 +916,22 @@ class Tmp:
rot = rot.to_matrix() rot = rot.to_matrix()
self.matrix_basis = self.matrix_basis @ rot.to_4x4() 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(): def register():
PublicData.load_gizmo_data() PublicData.load_gizmo_data()