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

View File

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

View File

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

View File

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

View File

@ -6,6 +6,7 @@ from bpy.types import Gizmo, GizmoGroup
from bpy_extras import view3d_utils
from mathutils import Vector
from ..update import change_active_modifier_parameter
from ..utils import GizmoUtils, GizmoGroupUtils
@ -201,22 +202,26 @@ class UpDownLimitsGizmo(Gizmo, GizmoUpdate):
'down_limits', self.int_value_down_limits)
def modal(self, context, event, tweak):
st = time()
self.clear_cache()
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
self.set_prop_value(event)
self.clear_cache()
self.update_object_origin_matrix()
# self.update_deform_wireframe(self.get_depsgraph(origin_object))
try:
self.set_prop_value(event)
self.clear_point_cache()
self.update_object_origin_matrix()
except Exception:
...
# return {'FINISHED'}
self.update_header_text(context)
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

View File

@ -26,7 +26,7 @@ class DeformAxisOperator(Operator, GizmoUtils):
return {'RUNNING_MODAL'}
def modal(self, context, event):
self.clear_cache()
self.clear_point_cache()
mod = context.object.modifiers.active
mod.deform_axis = self.Deform_Axis
empty = self.new_origin_empty_object()
@ -39,7 +39,7 @@ class DeformAxisOperator(Operator, GizmoUtils):
('max_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)):
mod.angle = mod.angle * -1

View File

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

View File

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

View File

@ -1,78 +1,147 @@
# 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:
_event_func_list = {}
update_func: 'function'
tmp_save_data = {}
_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):
import bpy
bpy.app.timers.register(cls.update_func, persistent=True)
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
func = cls._update_func_call_timer
if timers.is_registered(func):
timers.unregister(func)
@classmethod
def _update_call(cls):
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)
else:
print('cls timers is not registered', cls)
cls._events_func_list.clear()
class change_active_object(update_public):
class simple_update(update_public, GizmoUpdate):
tmp_save_data = {}
from bpy.app.handlers import depsgraph_update_post, persistent
handler_type = depsgraph_update_post
@classmethod
def update_func(cls):
import bpy
name = bpy.context.object.name
key = 'active_object'
if key not in cls.tmp_save_data or cls.tmp_save_data[key] != name:
cls._update_call()
cls.tmp_save_data[key] = name
return cls.run_time
def timers_update_poll(cls):
obj = bpy.context.object
if not cls.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 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
def update_func(cls):
def is_change_active_object(cls, change_data=True):
import bpy
obj = bpy.context.object
if not obj or obj.type != 'MESH':
return cls.run_time
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 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)
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[key] = name
elif change_modifiers:
cls.tmp_save_data['modifiers'] = modifiers
cls._update_call()
return cls.run_time
if change_active_object.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):
@ -81,20 +150,52 @@ class change_active_simple_deform_modifier(update_public):
'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():
change_active_object.register()
change_active_simple_deform_modifier.register()
simple_update.register()
change_active_object.append(gizmo.update_multiple_modifiers_data)
change_active_simple_deform_modifier.append(gizmo.update_multiple_modifiers_data)
def p():
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():
change_active_object.remove(gizmo.update_multiple_modifiers_data)
change_active_simple_deform_modifier.remove(gizmo.update_multiple_modifiers_data)
change_active_object.unregister()
change_active_simple_deform_modifier.unregister()
simple_update.unregister()

View File

@ -4,7 +4,6 @@ import math
import uuid
from functools import cache
from os.path import dirname, basename, realpath
from time import time
import bpy
import numpy as np
@ -13,27 +12,38 @@ from mathutils import Vector, Matrix, Euler
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_GizmoData = {}
G_Modifiers_Data = {}
G_CustomShape = {} #
G_HandleData = {} # Save draw Handle
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))
(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_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',
'deform_axis',
'deform_method',
'factor',
'invert_vertex_group',
'limits',
'limits', # bpy.types.bpy_prop_array
'lock_x',
'lock_y',
'lock_z',
@ -72,10 +82,11 @@ class PublicData:
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_object:
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:
@ -96,10 +107,14 @@ class PublicClass(PublicData):
class PublicPoll(PublicClass):
@classmethod
def context_mode_is_object(cls) -> bool:
return bpy.context.mode == 'OBJECT'
@classmethod
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
:param context:bpy.types.Object
:return:
@ -112,10 +127,9 @@ class PublicPoll(PublicClass):
if not mod:
return False
available_obj_type = obj and (obj.type in ('MESH', 'LATTICE'))
available_modifiers_type = mod and (mod.type == 'SIMPLE_DEFORM')
is_available_obj = available_modifiers_type and available_obj_type
is_obj_mode = context.mode == 'OBJECT'
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.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
@ -159,7 +173,51 @@ class PublicPoll(PublicClass):
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
def value_limit(cls, value, max_value=1, min_value=0):
"""
@ -183,18 +241,6 @@ class PublicUtils(PublicPoll):
"""
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
def link_obj_to_active_collection(cls, obj: 'bpy.types.Object'):
context = bpy.context
@ -203,22 +249,6 @@ class PublicUtils(PublicPoll):
objects.link(
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
def get_mesh_max_min_co(cls, obj: 'bpy.context.object') -> '[Vector,Vector]':
if obj.type == 'MESH':
@ -271,36 +301,6 @@ class PublicUtils(PublicPoll):
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
def tow_co_to_coordinate(cls, 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))
)
@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):
@ -332,15 +355,17 @@ class PublicProperty(GizmoClassMethod):
top, bottom, left, right, front, back = self.modifier_bound_box_pos
mod = self.modifier
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)
origin_mat = mod.origin.matrix_basis.to_3x3()
axis_ = origin_mat @ vector_axis
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_)
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)
@ -349,7 +374,6 @@ class PublicProperty(GizmoClassMethod):
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
@ -383,28 +407,31 @@ class PublicProperty(GizmoClassMethod):
@classmethod
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()
@classmethod
def clear_data(cls):
cls.G_GizmoData.clear()
def clear_modifiers_data(cls):
cls.G_MultipleModifiersBoundData.clear()
@classmethod
def clear_modifiers_data(cls):
cls.G_Modifiers_Data.clear()
def clear_deform_data(cls):
cls.G_DeformDrawData.clear()
# --------------- Cache Data ----------------------
@property
def each_face_pos(self):
matrix = Matrix()
matrix.freeze()
return self._each_face_pos(matrix, self.get_bound_co_data())
@property
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
def modifier_bound_box_pos(self):
@ -423,13 +450,12 @@ class PublicProperty(GizmoClassMethod):
return self.matrix_calculation(self.obj_matrix_world, bound)
@property
def modifier_origin_angle_is_available(self):
def modifier_origin_is_available(self):
try:
self._get_limits_point_and_bound_box_co()
return True
except UnboundLocalError:
print('modifier_origin_angle_is_available')
self.clear_cache()
self.clear_point_cache()
return False
# --------------- Compute Data ----------------------
@ -471,6 +497,11 @@ class PublicProperty(GizmoClassMethod):
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:
@ -483,7 +514,7 @@ class PublicProperty(GizmoClassMethod):
@property
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
@property
@ -526,7 +557,11 @@ class PublicProperty(GizmoClassMethod):
@property
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):
@ -543,7 +578,7 @@ class GizmoUpdate(PublicProperty):
if origin.parent != obj:
origin.parent = obj
origin.rotation_euler.zero()
if not self.modifier_origin_angle_is_available:
if not self.modifier_origin_is_available:
origin.location.zero()
origin.scale = 1, 1, 1
@ -552,7 +587,7 @@ class GizmoUpdate(PublicProperty):
obj = self.obj
origin = mod.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)
self.link_obj_to_active_collection(origin_object)
origin_object.hide_set(True)
@ -564,7 +599,7 @@ class GizmoUpdate(PublicProperty):
if origin_object == obj:
return
# add constraints
name = self.G_CON_LIMIT_NAME
name = self.G_NAME_CON_LIMIT
if origin_object.constraints.keys().__len__() > 2:
origin_object.constraints.clear()
if name in origin_object.constraints.keys():
@ -579,7 +614,7 @@ class GizmoUpdate(PublicProperty):
limit_constraints.use_limit_x = True
limit_constraints.use_limit_y = 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():
copy_constraints = origin.constraints.get(con_copy_name)
else:
@ -590,16 +625,15 @@ class GizmoUpdate(PublicProperty):
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):
st = time()
origin_mode = self.origin_mode
origin_object = self.modifier.origin
is_use = self.modifier_is_use_origin_axis
if origin_object and is_use:
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':
@ -610,80 +644,106 @@ class GizmoUpdate(PublicProperty):
elif origin_mode == 'MIDDLE':
translation = (self.point_up + self.point_down) / 2
origin_object.matrix_world.translation = translation
print('update_object_origin_matrix', time() - st)
def update_multiple_modifiers_data(self):
print('update_multiple_modifiers_data', self)
st = time()
obj = self.obj
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
self.clear_cache()
self.clear_point_cache()
self.clear_modifiers_data()
data = bpy.data
name = self.G_NAME
origin_object = data.objects.get(name)
name = self.G_TMP_MULTIPLE_MODIFIERS_MESH
# update multiple simple_deform bound data
if origin_object:
data.objects.remove(origin_object)
# 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))
vertices = self.tow_co_to_coordinate(self.get_bound_co_data())
new_mesh = data.meshes.new(name)
new_mesh.from_pydata(vertices, self.G_INDICES, [])
new_mesh.update()
deform_obj = data.objects.new(name, new_mesh)
"""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(deform_obj)
if deform_obj == obj:
self.link_obj_to_active_collection(modifiers_obj)
if modifiers_obj == obj: # is cycles
return
if deform_obj.parent != obj:
deform_obj.parent = obj
if modifiers_obj.parent != obj:
modifiers_obj.parent = obj
deform_obj.modifiers.clear()
subdivision = deform_obj.modifiers.new('1', 'SUBSURF')
subdivision.levels = 7
self.G_GizmoData['co'] = self.get_bound_co_data()
modifiers_obj.modifiers.clear()
subdivision = modifiers_obj.modifiers.new('1', 'SUBSURF')
subdivision.levels = self.G_SUB_LEVELS
for mo in context.object.modifiers:
if mo.type == 'SIMPLE_DEFORM':
obj = self.get_depsgraph(deform_obj)
self.G_Modifiers_Data[mo.name] = self.get_mesh_max_min_co(obj)
simple_deform = deform_obj.modifiers.new(
mo.name, 'SIMPLE_DEFORM')
simple_deform.deform_method = mo.deform_method
simple_deform.deform_axis = mo.deform_axis
simple_deform.lock_x = mo.lock_x
simple_deform.lock_y = mo.lock_y
simple_deform.lock_z = mo.lock_z
simple_deform.origin = mo.origin
simple_deform.limits[1] = mo.limits[1]
simple_deform.limits[0] = mo.limits[0]
simple_deform.angle = mo.angle
simple_deform.show_viewport = mo.show_viewport
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)
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, 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
matrix = self.obj_matrix_world.copy()
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_GizmoData:
self.G_GizmoData['numpy_data'] = {}
numpy_data = self.G_GizmoData['numpy_data']
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]
@ -702,10 +762,13 @@ class GizmoUpdate(PublicProperty):
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[:]
modifiers = [getattr(context.object.modifiers.active, i)
for i in self.G_MODIFIERS_PROPERTY]
self.G_GizmoData['simple_deform_box_data'] = (ver, indices, matrix, modifiers, 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):
@ -748,16 +811,16 @@ class GizmoUtils(GizmoUpdate):
def __update_matrix_func(self, context):
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)
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.__update_matrix_func(context)
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.custom_shape[self.draw_type], select_id=select_id)
self.__update_matrix_func(context)
@ -788,6 +851,11 @@ class GizmoUtils(GizmoUpdate):
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'
@ -848,6 +916,22 @@ class Tmp:
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()