Add Lattice Magic
to Addons
#48
366
tweak_lattice.py
366
tweak_lattice.py
@ -11,115 +11,8 @@ from rna_prop_ui import rna_idprop_ui_create
|
|||||||
|
|
||||||
from .utils import clamp, get_lattice_vertex_index, simple_driver, bounding_box_center_of_objects
|
from .utils import clamp, get_lattice_vertex_index, simple_driver, bounding_box_center_of_objects
|
||||||
|
|
||||||
coll_name = 'Tweak Lattices'
|
TWEAKLAT_COLL_NAME = 'Tweak Lattices'
|
||||||
|
|
||||||
def ensure_tweak_lattice_collection(scene: bpy.types.Scene) -> bpy.types.Collection:
|
|
||||||
coll = bpy.data.collections.get(coll_name)
|
|
||||||
if not coll:
|
|
||||||
coll = bpy.data.collections.new(coll_name)
|
|
||||||
scene.collection.children.link(coll)
|
|
||||||
|
|
||||||
return coll
|
|
||||||
|
|
||||||
def ensure_falloff_vgroup(
|
|
||||||
lattice_ob: bpy.types.Object,
|
|
||||||
vg_name="Group", multiplier=1, expression="x") -> bpy.types.VertexGroup:
|
|
||||||
lattice = lattice_ob.data
|
|
||||||
res_x, res_y, res_z = lattice.points_u, lattice.points_v, lattice.points_w
|
|
||||||
|
|
||||||
vg = lattice_ob.vertex_groups.get(vg_name)
|
|
||||||
|
|
||||||
center = Vector((res_x-1, res_y-1, res_z-1))/2
|
|
||||||
max_res = max(res_x, res_y, res_z)
|
|
||||||
|
|
||||||
if not vg:
|
|
||||||
vg = lattice_ob.vertex_groups.new(name=vg_name)
|
|
||||||
for x in range(res_x-4):
|
|
||||||
for y in range(res_y-4):
|
|
||||||
for z in range(res_z-4):
|
|
||||||
index = get_lattice_vertex_index(lattice, (x+2, y+2, z+2))
|
|
||||||
|
|
||||||
coord = Vector((x+2, y+2, z+2))
|
|
||||||
distance_from_center = (coord-center).length
|
|
||||||
distance_factor = 1 - (distance_from_center / max_res * 2)
|
|
||||||
try:
|
|
||||||
influence = eval(expression.replace("x", "distance_factor"))
|
|
||||||
except:
|
|
||||||
return None
|
|
||||||
|
|
||||||
vg.add([index], influence * multiplier, 'REPLACE')
|
|
||||||
return vg
|
|
||||||
|
|
||||||
def add_radius_constraint(obj, hook, target):
|
|
||||||
trans_con = obj.constraints.new(type='TRANSFORM')
|
|
||||||
trans_con.target = target
|
|
||||||
trans_con.map_to = 'SCALE'
|
|
||||||
trans_con.mix_mode_scale = 'MULTIPLY'
|
|
||||||
for prop in ['to_min_x_scale', 'to_min_y_scale', 'to_min_z_scale']:
|
|
||||||
simple_driver(trans_con, prop, hook, '["Radius"]')
|
|
||||||
return trans_con
|
|
||||||
|
|
||||||
def get_objects_of_lattice(hook: bpy.types.Object) -> List[bpy.types.Object]:
|
|
||||||
objs = []
|
|
||||||
ob_count = 0
|
|
||||||
ob_prop_name = "object_"+str(ob_count)
|
|
||||||
while ob_prop_name in hook:
|
|
||||||
objs.append(hook[ob_prop_name])
|
|
||||||
return objs
|
|
||||||
|
|
||||||
def get_lattice_modifier_of_object(obj, lattice) -> bpy.types.Modifier:
|
|
||||||
"""Find the lattice modifier on the object that uses this lattice"""
|
|
||||||
for m in obj.modifiers:
|
|
||||||
if m.type == 'LATTICE' and m.object == lattice:
|
|
||||||
return m
|
|
||||||
|
|
||||||
def add_objects_to_lattice(
|
|
||||||
hook: bpy.types.Object,
|
|
||||||
objects: List[bpy.types.Object]):
|
|
||||||
lattice_ob = hook['Lattice']
|
|
||||||
|
|
||||||
# Check for existing
|
|
||||||
offset = 0
|
|
||||||
while "object_"+str(offset) in hook:
|
|
||||||
offset += 1
|
|
||||||
|
|
||||||
for i, o in enumerate(objects):
|
|
||||||
o.select_set(False)
|
|
||||||
if o.type!='MESH' or o in hook.values():
|
|
||||||
offset -= 1
|
|
||||||
continue
|
|
||||||
m = o.modifiers.new(name="Tweak Lattice", type='LATTICE')
|
|
||||||
m.object = lattice_ob
|
|
||||||
hook["object_"+str(i+offset)] = o
|
|
||||||
# Add driver to the modifier influence
|
|
||||||
simple_driver(m, 'strength', hook, '["Tweak Lattice"]')
|
|
||||||
|
|
||||||
def remove_all_objects_from_lattice(hook: bpy.types.Object) -> List[bpy.types.Object]:
|
|
||||||
lattice_ob = hook['Lattice']
|
|
||||||
objs = []
|
|
||||||
ob_count = 0
|
|
||||||
ob_prop_name = "object_"+str(ob_count)
|
|
||||||
while ob_prop_name in hook:
|
|
||||||
ob = hook[ob_prop_name]
|
|
||||||
for m in ob.modifiers:
|
|
||||||
if m.type!='LATTICE': continue
|
|
||||||
if m.object == lattice_ob:
|
|
||||||
m.driver_remove('strength')
|
|
||||||
ob.modifiers.remove(m)
|
|
||||||
break
|
|
||||||
ob_count += 1
|
|
||||||
objs.append(ob)
|
|
||||||
del hook[ob_prop_name]
|
|
||||||
ob_prop_name = "object_"+str(ob_count)
|
|
||||||
return objs
|
|
||||||
|
|
||||||
def remove_objects_from_lattice(hook, objects):
|
|
||||||
new_objs = []
|
|
||||||
prev_objs = remove_all_objects_from_lattice(hook)
|
|
||||||
for o in prev_objs:
|
|
||||||
if o not in objects:
|
|
||||||
new_objs.append(o)
|
|
||||||
add_objects_to_lattice(hook, new_objs)
|
|
||||||
|
|
||||||
class TWEAKLAT_OT_Create(bpy.types.Operator):
|
class TWEAKLAT_OT_Create(bpy.types.Operator):
|
||||||
"""Create a lattice setup to deform selected objects"""
|
"""Create a lattice setup to deform selected objects"""
|
||||||
@ -135,9 +28,9 @@ class TWEAKLAT_OT_Create(bpy.types.Operator):
|
|||||||
)
|
)
|
||||||
|
|
||||||
location: EnumProperty(name="Location", items=[
|
location: EnumProperty(name="Location", items=[
|
||||||
('CURSOR', "3D Cursor", "Create at the location and orientation of the 3D cursor.")
|
('CURSOR', "3D Cursor", "Create at the location and orientation of the 3D cursor."),
|
||||||
,('CENTER', "Center", "Create at the bounding box center of all selected objects.")
|
('CENTER', "Center", "Create at the bounding box center of all selected objects."),
|
||||||
,('PARENT', "Parent", "Create at the location of the parent object or bone.")
|
('PARENT', "Parent", "Create at the location of the parent object or bone.")
|
||||||
])
|
])
|
||||||
radius: FloatProperty(
|
radius: FloatProperty(
|
||||||
name="Radius",
|
name="Radius",
|
||||||
@ -147,12 +40,13 @@ class TWEAKLAT_OT_Create(bpy.types.Operator):
|
|||||||
max=1000,
|
max=1000,
|
||||||
soft_max=2
|
soft_max=2
|
||||||
)
|
)
|
||||||
parent_bone: StringProperty(name="Bone", description="Bone to use as parent")
|
parent_bone: StringProperty(
|
||||||
|
name="Bone", description="Bone to use as parent")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
for ob in context.selected_objects:
|
for ob in context.selected_objects:
|
||||||
if ob.type=='MESH':
|
if ob.type == 'MESH':
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -183,9 +77,9 @@ class TWEAKLAT_OT_Create(bpy.types.Operator):
|
|||||||
col.prop(context.scene, 'tweak_lattice_parent_ob')
|
col.prop(context.scene, 'tweak_lattice_parent_ob')
|
||||||
|
|
||||||
scene = context.scene
|
scene = context.scene
|
||||||
if scene.tweak_lattice_parent_ob and scene.tweak_lattice_parent_ob.type=='ARMATURE':
|
if scene.tweak_lattice_parent_ob and scene.tweak_lattice_parent_ob.type == 'ARMATURE':
|
||||||
col.prop_search(self, 'parent_bone', scene.tweak_lattice_parent_ob.data, 'bones')
|
col.prop_search(self, 'parent_bone',
|
||||||
|
scene.tweak_lattice_parent_ob.data, 'bones')
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
scene = context.scene
|
scene = context.scene
|
||||||
@ -222,14 +116,14 @@ class TWEAKLAT_OT_Create(bpy.types.Operator):
|
|||||||
hook['Expression'] = 'x'
|
hook['Expression'] = 'x'
|
||||||
|
|
||||||
rna_idprop_ui_create(
|
rna_idprop_ui_create(
|
||||||
hook, "Tweak Lattice", default = 1.0,
|
hook, "Tweak Lattice", default=1.0,
|
||||||
min = 0, max = 1,
|
min=0, max=1,
|
||||||
description = "Influence of this lattice on all of its target objects",
|
description="Influence of this lattice on all of its target objects",
|
||||||
)
|
)
|
||||||
rna_idprop_ui_create(
|
rna_idprop_ui_create(
|
||||||
hook, "Radius", default = self.radius,
|
hook, "Radius", default=self.radius,
|
||||||
min = 0, soft_max = 0.2, max=100,
|
min=0, soft_max=0.2, max=100,
|
||||||
description = "Size of the influenced area",
|
description="Size of the influenced area",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create a Root Empty to parent both the hook and the lattice to.
|
# Create a Root Empty to parent both the hook and the lattice to.
|
||||||
@ -239,14 +133,16 @@ class TWEAKLAT_OT_Create(bpy.types.Operator):
|
|||||||
root.empty_display_type = 'CUBE'
|
root.empty_display_type = 'CUBE'
|
||||||
root.empty_display_size = 0.5
|
root.empty_display_size = 0.5
|
||||||
if self.location == 'CENTER':
|
if self.location == 'CENTER':
|
||||||
meshes = [o for o in context.selected_objects if o.type=='MESH']
|
meshes = [o for o in context.selected_objects if o.type == 'MESH']
|
||||||
root.matrix_world.translation = bounding_box_center_of_objects(meshes)
|
root.matrix_world.translation = bounding_box_center_of_objects(
|
||||||
|
meshes)
|
||||||
elif self.location == 'CURSOR':
|
elif self.location == 'CURSOR':
|
||||||
root.matrix_world = context.scene.cursor.matrix
|
root.matrix_world = context.scene.cursor.matrix
|
||||||
elif self.location == 'PARENT':
|
elif self.location == 'PARENT':
|
||||||
matrix_of_parent = scene.tweak_lattice_parent_ob.matrix_world
|
matrix_of_parent = scene.tweak_lattice_parent_ob.matrix_world
|
||||||
if self.parent_bone:
|
if self.parent_bone:
|
||||||
matrix_of_parent = scene.tweak_lattice_parent_ob.matrix_world @ scene.tweak_lattice_parent_ob.pose.bones[self.parent_bone].matrix
|
matrix_of_parent = scene.tweak_lattice_parent_ob.matrix_world @ scene.tweak_lattice_parent_ob.pose.bones[
|
||||||
|
self.parent_bone].matrix
|
||||||
root.matrix_world = matrix_of_parent.copy()
|
root.matrix_world = matrix_of_parent.copy()
|
||||||
coll.objects.link(root)
|
coll.objects.link(root)
|
||||||
root.hide_viewport = True
|
root.hide_viewport = True
|
||||||
@ -256,7 +152,7 @@ class TWEAKLAT_OT_Create(bpy.types.Operator):
|
|||||||
scene = context.scene
|
scene = context.scene
|
||||||
matrix_backup = root.matrix_world.copy()
|
matrix_backup = root.matrix_world.copy()
|
||||||
root.parent = scene.tweak_lattice_parent_ob
|
root.parent = scene.tweak_lattice_parent_ob
|
||||||
if root.parent and root.parent.type=='ARMATURE':
|
if root.parent and root.parent.type == 'ARMATURE':
|
||||||
bone = root.parent.pose.bones.get(self.parent_bone)
|
bone = root.parent.pose.bones.get(self.parent_bone)
|
||||||
if bone:
|
if bone:
|
||||||
root.parent_type = 'BONE'
|
root.parent_type = 'BONE'
|
||||||
@ -280,7 +176,8 @@ class TWEAKLAT_OT_Create(bpy.types.Operator):
|
|||||||
add_radius_constraint(hook, hook, root)
|
add_radius_constraint(hook, hook, root)
|
||||||
add_radius_constraint(lattice_ob, hook, root)
|
add_radius_constraint(lattice_ob, hook, root)
|
||||||
|
|
||||||
root_drv = simple_driver(root, 'empty_display_size', hook, '["Radius"]')
|
root_drv = simple_driver(
|
||||||
|
root, 'empty_display_size', hook, '["Radius"]')
|
||||||
root_drv.expression = 'var/2'
|
root_drv.expression = 'var/2'
|
||||||
|
|
||||||
# Deselect everything, select the hook and make it active
|
# Deselect everything, select the hook and make it active
|
||||||
@ -291,6 +188,7 @@ class TWEAKLAT_OT_Create(bpy.types.Operator):
|
|||||||
scene.tweak_lattice_parent_ob = None
|
scene.tweak_lattice_parent_ob = None
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
class TWEAKLAT_OT_Falloff(bpy.types.Operator):
|
class TWEAKLAT_OT_Falloff(bpy.types.Operator):
|
||||||
"""Adjust falloff of the hook vertex group of a Tweak Lattice"""
|
"""Adjust falloff of the hook vertex group of a Tweak Lattice"""
|
||||||
bl_idname = "lattice.tweak_lattice_adjust_falloww"
|
bl_idname = "lattice.tweak_lattice_adjust_falloww"
|
||||||
@ -298,24 +196,26 @@ class TWEAKLAT_OT_Falloff(bpy.types.Operator):
|
|||||||
bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
|
bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
|
||||||
|
|
||||||
def update_falloff(self, context):
|
def update_falloff(self, context):
|
||||||
if self.doing_invoke: return
|
if self.doing_invoke:
|
||||||
|
return
|
||||||
hook = context.object
|
hook = context.object
|
||||||
lattice_ob = hook['Lattice']
|
lattice_ob = hook['Lattice']
|
||||||
ret = ensure_falloff_vgroup(lattice_ob, 'Hook', multiplier=self.multiplier, expression=self.expression)
|
ret = ensure_falloff_vgroup(
|
||||||
|
lattice_ob, 'Hook', multiplier=self.multiplier, expression=self.expression)
|
||||||
self.is_expression_valid = ret != None
|
self.is_expression_valid = ret != None
|
||||||
if ret:
|
if ret:
|
||||||
hook['Expression'] = self.expression
|
hook['Expression'] = self.expression
|
||||||
hook['Multiplier'] = self.multiplier
|
hook['Multiplier'] = self.multiplier
|
||||||
|
|
||||||
is_expression_valid: BoolProperty(
|
is_expression_valid: BoolProperty(
|
||||||
name = "Error",
|
name="Error",
|
||||||
description = "Used to notify user if their expression is invalid",
|
description="Used to notify user if their expression is invalid",
|
||||||
default = True
|
default=True
|
||||||
)
|
)
|
||||||
# Actual parameters
|
# Actual parameters
|
||||||
multiplier: FloatProperty(
|
multiplier: FloatProperty(
|
||||||
name="Multiplier",
|
name="Multiplier",
|
||||||
description = "Multiplier on the weight values",
|
description="Multiplier on the weight values",
|
||||||
default=1,
|
default=1,
|
||||||
update=update_falloff,
|
update=update_falloff,
|
||||||
min=0,
|
min=0,
|
||||||
@ -324,7 +224,7 @@ class TWEAKLAT_OT_Falloff(bpy.types.Operator):
|
|||||||
expression: StringProperty(
|
expression: StringProperty(
|
||||||
name="Expression",
|
name="Expression",
|
||||||
default="x",
|
default="x",
|
||||||
description = "Expression to calculate the weight values where 'x' is a 0-1 value representing a point's closeness to the lattice center",
|
description="Expression to calculate the weight values where 'x' is a 0-1 value representing a point's closeness to the lattice center",
|
||||||
update=update_falloff,
|
update=update_falloff,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -336,7 +236,7 @@ class TWEAKLAT_OT_Falloff(bpy.types.Operator):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
ob = context.object
|
ob = context.object
|
||||||
return ob.type=='EMPTY' and 'Tweak Lattice' in ob
|
return ob.type == 'EMPTY' and 'Tweak Lattice' in ob
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
hook = context.object
|
hook = context.object
|
||||||
@ -368,6 +268,7 @@ class TWEAKLAT_OT_Falloff(bpy.types.Operator):
|
|||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
class TWEAKLAT_OT_Delete(bpy.types.Operator):
|
class TWEAKLAT_OT_Delete(bpy.types.Operator):
|
||||||
"""Delete a tweak lattice setup with all its helper objects, drivers, etc"""
|
"""Delete a tweak lattice setup with all its helper objects, drivers, etc"""
|
||||||
bl_idname = "lattice.delete_tweak_lattice"
|
bl_idname = "lattice.delete_tweak_lattice"
|
||||||
@ -377,7 +278,7 @@ class TWEAKLAT_OT_Delete(bpy.types.Operator):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
ob = context.object
|
ob = context.object
|
||||||
return ob and ob.type=='EMPTY' and 'Tweak Lattice' in ob
|
return ob and ob.type == 'EMPTY' and 'Tweak Lattice' in ob
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
hook = context.object
|
hook = context.object
|
||||||
@ -399,13 +300,13 @@ class TWEAKLAT_OT_Delete(bpy.types.Operator):
|
|||||||
bpy.data.objects.remove(root)
|
bpy.data.objects.remove(root)
|
||||||
|
|
||||||
# Remove the collection if it's empty.
|
# Remove the collection if it's empty.
|
||||||
coll = bpy.data.collections.get(coll_name)
|
coll = bpy.data.collections.get(TWEAKLAT_COLL_NAME)
|
||||||
if coll and len(coll.all_objects)==0:
|
if coll and len(coll.all_objects) == 0:
|
||||||
bpy.data.collections.remove(coll)
|
bpy.data.collections.remove(coll)
|
||||||
|
|
||||||
|
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
class TWEAKLAT_OT_Add_Objects(bpy.types.Operator):
|
class TWEAKLAT_OT_Add_Objects(bpy.types.Operator):
|
||||||
"""Add selected objects to this tweak lattice"""
|
"""Add selected objects to this tweak lattice"""
|
||||||
bl_idname = "lattice.add_selected_objects"
|
bl_idname = "lattice.add_selected_objects"
|
||||||
@ -415,10 +316,12 @@ class TWEAKLAT_OT_Add_Objects(bpy.types.Operator):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
ob = context.object
|
ob = context.object
|
||||||
if ob.type!='EMPTY' or 'Tweak Lattice' not in ob: return False
|
if ob.type != 'EMPTY' or 'Tweak Lattice' not in ob:
|
||||||
|
return False
|
||||||
values = ob.values()
|
values = ob.values()
|
||||||
for sel_o in context.selected_objects:
|
for sel_o in context.selected_objects:
|
||||||
if sel_o==ob or sel_o.type!='MESH': continue
|
if sel_o == ob or sel_o.type != 'MESH':
|
||||||
|
continue
|
||||||
if sel_o not in values:
|
if sel_o not in values:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
@ -431,6 +334,7 @@ class TWEAKLAT_OT_Add_Objects(bpy.types.Operator):
|
|||||||
|
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
class TWEAKLAT_OT_Remove_Selected_Objects(bpy.types.Operator):
|
class TWEAKLAT_OT_Remove_Selected_Objects(bpy.types.Operator):
|
||||||
"""Remove selected objects from this tweak lattice"""
|
"""Remove selected objects from this tweak lattice"""
|
||||||
bl_idname = "lattice.remove_selected_objects"
|
bl_idname = "lattice.remove_selected_objects"
|
||||||
@ -440,10 +344,12 @@ class TWEAKLAT_OT_Remove_Selected_Objects(bpy.types.Operator):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
ob = context.object
|
ob = context.object
|
||||||
if ob.type!='EMPTY' or 'Tweak Lattice' not in ob: return False
|
if ob.type != 'EMPTY' or 'Tweak Lattice' not in ob:
|
||||||
|
return False
|
||||||
values = ob.values()
|
values = ob.values()
|
||||||
for sel_o in context.selected_objects:
|
for sel_o in context.selected_objects:
|
||||||
if sel_o==ob or sel_o.type!='MESH': continue
|
if sel_o == ob or sel_o.type != 'MESH':
|
||||||
|
continue
|
||||||
if sel_o in values:
|
if sel_o in values:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
@ -456,13 +362,15 @@ class TWEAKLAT_OT_Remove_Selected_Objects(bpy.types.Operator):
|
|||||||
|
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
class TWEAKLAT_OT_Remove_Object(bpy.types.Operator):
|
class TWEAKLAT_OT_Remove_Object(bpy.types.Operator):
|
||||||
"""Remove this object from the tweak lattice"""
|
"""Remove this object from the tweak lattice"""
|
||||||
bl_idname = "lattice.remove_object"
|
bl_idname = "lattice.remove_object"
|
||||||
bl_label = "Remove Object"
|
bl_label = "Remove Object"
|
||||||
bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
|
bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
|
||||||
|
|
||||||
ob_pointer_prop_name: StringProperty(description="Name of the custom property that references the object that should be removed")
|
ob_pointer_prop_name: StringProperty(
|
||||||
|
description="Name of the custom property that references the object that should be removed")
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
hook = context.object
|
hook = context.object
|
||||||
@ -473,6 +381,7 @@ class TWEAKLAT_OT_Remove_Object(bpy.types.Operator):
|
|||||||
|
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
class TWEAKLAT_PT_Main(bpy.types.Panel):
|
class TWEAKLAT_PT_Main(bpy.types.Panel):
|
||||||
bl_space_type = 'VIEW_3D'
|
bl_space_type = 'VIEW_3D'
|
||||||
bl_region_type = 'UI'
|
bl_region_type = 'UI'
|
||||||
@ -491,7 +400,8 @@ class TWEAKLAT_PT_Main(bpy.types.Panel):
|
|||||||
hook = context.object
|
hook = context.object
|
||||||
layout = layout.column()
|
layout = layout.column()
|
||||||
if hook.type != 'EMPTY' or 'Tweak Lattice' not in hook:
|
if hook.type != 'EMPTY' or 'Tweak Lattice' not in hook:
|
||||||
layout.operator(TWEAKLAT_OT_Create.bl_idname, icon='OUTLINER_OB_LATTICE')
|
layout.operator(TWEAKLAT_OT_Create.bl_idname,
|
||||||
|
icon='OUTLINER_OB_LATTICE')
|
||||||
return
|
return
|
||||||
|
|
||||||
layout.prop(hook, '["Tweak Lattice"]', slider=True, text="Influence")
|
layout.prop(hook, '["Tweak Lattice"]', slider=True, text="Influence")
|
||||||
@ -499,13 +409,15 @@ class TWEAKLAT_PT_Main(bpy.types.Panel):
|
|||||||
layout.operator(TWEAKLAT_OT_Falloff.bl_idname, text="Adjust Falloff")
|
layout.operator(TWEAKLAT_OT_Falloff.bl_idname, text="Adjust Falloff")
|
||||||
|
|
||||||
layout.separator()
|
layout.separator()
|
||||||
layout.operator(TWEAKLAT_OT_Delete.bl_idname, text='Delete Tweak Lattice', icon='TRASH')
|
layout.operator(TWEAKLAT_OT_Delete.bl_idname,
|
||||||
|
text='Delete Tweak Lattice', icon='TRASH')
|
||||||
|
|
||||||
layout.separator()
|
layout.separator()
|
||||||
layout.label(text="Helper Objects")
|
layout.label(text="Helper Objects")
|
||||||
lattice_row = layout.row()
|
lattice_row = layout.row()
|
||||||
lattice_row.prop(hook, '["Lattice"]', text="Lattice")
|
lattice_row.prop(hook, '["Lattice"]', text="Lattice")
|
||||||
lattice_row.prop(hook['Lattice'], 'hide_viewport', text="", emboss=False)
|
lattice_row.prop(hook['Lattice'], 'hide_viewport',
|
||||||
|
text="", emboss=False)
|
||||||
|
|
||||||
root_row = layout.row()
|
root_row = layout.row()
|
||||||
root_row.prop(hook, '["Root"]', text="Root")
|
root_row.prop(hook, '["Root"]', text="Root")
|
||||||
@ -517,7 +429,7 @@ class TWEAKLAT_PT_Main(bpy.types.Panel):
|
|||||||
col = layout.column()
|
col = layout.column()
|
||||||
col.enabled = False
|
col.enabled = False
|
||||||
col.prop(root, 'parent')
|
col.prop(root, 'parent')
|
||||||
if root.parent and root.parent.type=='ARMATURE':
|
if root.parent and root.parent.type == 'ARMATURE':
|
||||||
col.prop(root, 'parent_bone', icon='BONE_DATA')
|
col.prop(root, 'parent_bone', icon='BONE_DATA')
|
||||||
|
|
||||||
layout.separator()
|
layout.separator()
|
||||||
@ -525,56 +437,180 @@ class TWEAKLAT_PT_Main(bpy.types.Panel):
|
|||||||
|
|
||||||
num_to_add = 0
|
num_to_add = 0
|
||||||
for o in context.selected_objects:
|
for o in context.selected_objects:
|
||||||
if o == hook or o.type!='MESH': continue
|
if o == hook or o.type != 'MESH':
|
||||||
if o in hook.values(): continue
|
continue
|
||||||
|
if o in hook.values():
|
||||||
|
continue
|
||||||
num_to_add += 1
|
num_to_add += 1
|
||||||
if num_to_add == 1:
|
if num_to_add == 1:
|
||||||
text = f"Add {o.name}"
|
text = f"Add {o.name}"
|
||||||
if num_to_add:
|
if num_to_add:
|
||||||
if num_to_add > 1:
|
if num_to_add > 1:
|
||||||
text = f"Add {num_to_add} Objects"
|
text = f"Add {num_to_add} Objects"
|
||||||
layout.operator(TWEAKLAT_OT_Add_Objects.bl_idname, icon='ADD', text=text)
|
layout.operator(TWEAKLAT_OT_Add_Objects.bl_idname,
|
||||||
|
icon='ADD', text=text)
|
||||||
|
|
||||||
layout.separator()
|
layout.separator()
|
||||||
num_to_remove = False
|
num_to_remove = False
|
||||||
for o in context.selected_objects:
|
for o in context.selected_objects:
|
||||||
if o == hook or o.type!='MESH': continue
|
if o == hook or o.type != 'MESH':
|
||||||
if o not in hook.values(): continue
|
continue
|
||||||
|
if o not in hook.values():
|
||||||
|
continue
|
||||||
num_to_remove += 1
|
num_to_remove += 1
|
||||||
if num_to_remove == 1:
|
if num_to_remove == 1:
|
||||||
text = f"Remove {o.name}"
|
text = f"Remove {o.name}"
|
||||||
if num_to_remove:
|
if num_to_remove:
|
||||||
if num_to_remove > 1:
|
if num_to_remove > 1:
|
||||||
text = f"Remove {num_to_remove} Objects"
|
text = f"Remove {num_to_remove} Objects"
|
||||||
layout.operator(TWEAKLAT_OT_Remove_Selected_Objects.bl_idname, icon='REMOVE', text=text)
|
layout.operator(
|
||||||
|
TWEAKLAT_OT_Remove_Selected_Objects.bl_idname, icon='REMOVE', text=text)
|
||||||
|
|
||||||
objects_and_keys = [(hook[key], key) for key in hook.keys() if "object_" in key]
|
objects_and_keys = [(hook[key], key)
|
||||||
|
for key in hook.keys() if "object_" in key]
|
||||||
objects_and_keys.sort(key=lambda o_and_k: o_and_k[1])
|
objects_and_keys.sort(key=lambda o_and_k: o_and_k[1])
|
||||||
for ob, key in objects_and_keys:
|
for ob, key in objects_and_keys:
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
row.prop(hook, f'["{key}"]', text="")
|
row.prop(hook, f'["{key}"]', text="")
|
||||||
mod = get_lattice_modifier_of_object(ob, hook['Lattice'])
|
mod = get_lattice_modifier_of_object(ob, hook['Lattice'])
|
||||||
row.prop_search(mod, 'vertex_group', ob, 'vertex_groups', text="", icon='GROUP_VERTEX')
|
row.prop_search(mod, 'vertex_group', ob,
|
||||||
op = row.operator(TWEAKLAT_OT_Remove_Object.bl_idname, text="", icon='X')
|
'vertex_groups', text="", icon='GROUP_VERTEX')
|
||||||
|
op = row.operator(
|
||||||
|
TWEAKLAT_OT_Remove_Object.bl_idname, text="", icon='X')
|
||||||
op.ob_pointer_prop_name = key
|
op.ob_pointer_prop_name = key
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_tweak_lattice_collection(scene: bpy.types.Scene) -> bpy.types.Collection:
|
||||||
|
coll = bpy.data.collections.get(TWEAKLAT_COLL_NAME)
|
||||||
|
if not coll:
|
||||||
|
coll = bpy.data.collections.new(TWEAKLAT_COLL_NAME)
|
||||||
|
scene.collection.children.link(coll)
|
||||||
|
|
||||||
|
return coll
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_falloff_vgroup(
|
||||||
|
lattice_ob: bpy.types.Object,
|
||||||
|
vg_name="Group", multiplier=1, expression="x") -> bpy.types.VertexGroup:
|
||||||
|
lattice = lattice_ob.data
|
||||||
|
res_x, res_y, res_z = lattice.points_u, lattice.points_v, lattice.points_w
|
||||||
|
|
||||||
|
vg = lattice_ob.vertex_groups.get(vg_name)
|
||||||
|
|
||||||
|
center = Vector((res_x-1, res_y-1, res_z-1))/2
|
||||||
|
max_res = max(res_x, res_y, res_z)
|
||||||
|
|
||||||
|
if not vg:
|
||||||
|
vg = lattice_ob.vertex_groups.new(name=vg_name)
|
||||||
|
for x in range(res_x-4):
|
||||||
|
for y in range(res_y-4):
|
||||||
|
for z in range(res_z-4):
|
||||||
|
index = get_lattice_vertex_index(lattice, (x+2, y+2, z+2))
|
||||||
|
|
||||||
|
coord = Vector((x+2, y+2, z+2))
|
||||||
|
distance_from_center = (coord-center).length
|
||||||
|
distance_factor = 1 - (distance_from_center / max_res * 2)
|
||||||
|
try:
|
||||||
|
influence = eval(expression.replace(
|
||||||
|
"x", "distance_factor"))
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
|
vg.add([index], influence * multiplier, 'REPLACE')
|
||||||
|
return vg
|
||||||
|
|
||||||
|
|
||||||
|
def add_radius_constraint(obj, hook, target):
|
||||||
|
trans_con = obj.constraints.new(type='TRANSFORM')
|
||||||
|
trans_con.target = target
|
||||||
|
trans_con.map_to = 'SCALE'
|
||||||
|
trans_con.mix_mode_scale = 'MULTIPLY'
|
||||||
|
for prop in ['to_min_x_scale', 'to_min_y_scale', 'to_min_z_scale']:
|
||||||
|
simple_driver(trans_con, prop, hook, '["Radius"]')
|
||||||
|
return trans_con
|
||||||
|
|
||||||
|
|
||||||
|
def get_objects_of_lattice(hook: bpy.types.Object) -> List[bpy.types.Object]:
|
||||||
|
objs = []
|
||||||
|
ob_count = 0
|
||||||
|
ob_prop_name = "object_"+str(ob_count)
|
||||||
|
while ob_prop_name in hook:
|
||||||
|
objs.append(hook[ob_prop_name])
|
||||||
|
return objs
|
||||||
|
|
||||||
|
|
||||||
|
def get_lattice_modifier_of_object(obj, lattice) -> bpy.types.Modifier:
|
||||||
|
"""Find the lattice modifier on the object that uses this lattice"""
|
||||||
|
for m in obj.modifiers:
|
||||||
|
if m.type == 'LATTICE' and m.object == lattice:
|
||||||
|
return m
|
||||||
|
|
||||||
|
|
||||||
|
def add_objects_to_lattice(
|
||||||
|
hook: bpy.types.Object,
|
||||||
|
objects: List[bpy.types.Object]):
|
||||||
|
lattice_ob = hook['Lattice']
|
||||||
|
|
||||||
|
# Check for existing
|
||||||
|
offset = 0
|
||||||
|
while "object_"+str(offset) in hook:
|
||||||
|
offset += 1
|
||||||
|
|
||||||
|
for i, o in enumerate(objects):
|
||||||
|
o.select_set(False)
|
||||||
|
if o.type != 'MESH' or o in hook.values():
|
||||||
|
offset -= 1
|
||||||
|
continue
|
||||||
|
m = o.modifiers.new(name="Tweak Lattice", type='LATTICE')
|
||||||
|
m.object = lattice_ob
|
||||||
|
hook["object_"+str(i+offset)] = o
|
||||||
|
# Add driver to the modifier influence
|
||||||
|
simple_driver(m, 'strength', hook, '["Tweak Lattice"]')
|
||||||
|
|
||||||
|
|
||||||
|
def remove_all_objects_from_lattice(hook: bpy.types.Object) -> List[bpy.types.Object]:
|
||||||
|
lattice_ob = hook['Lattice']
|
||||||
|
objs = []
|
||||||
|
ob_count = 0
|
||||||
|
ob_prop_name = "object_"+str(ob_count)
|
||||||
|
while ob_prop_name in hook:
|
||||||
|
ob = hook[ob_prop_name]
|
||||||
|
for m in ob.modifiers:
|
||||||
|
if m.type != 'LATTICE':
|
||||||
|
continue
|
||||||
|
if m.object == lattice_ob:
|
||||||
|
m.driver_remove('strength')
|
||||||
|
ob.modifiers.remove(m)
|
||||||
|
break
|
||||||
|
ob_count += 1
|
||||||
|
objs.append(ob)
|
||||||
|
del hook[ob_prop_name]
|
||||||
|
ob_prop_name = "object_"+str(ob_count)
|
||||||
|
return objs
|
||||||
|
|
||||||
|
|
||||||
|
def remove_objects_from_lattice(hook, objects):
|
||||||
|
new_objs = []
|
||||||
|
prev_objs = remove_all_objects_from_lattice(hook)
|
||||||
|
for o in prev_objs:
|
||||||
|
if o not in objects:
|
||||||
|
new_objs.append(o)
|
||||||
|
add_objects_to_lattice(hook, new_objs)
|
||||||
|
|
||||||
|
|
||||||
classes = [
|
classes = [
|
||||||
TWEAKLAT_OT_Create
|
TWEAKLAT_OT_Create, TWEAKLAT_OT_Delete, TWEAKLAT_OT_Falloff, TWEAKLAT_OT_Add_Objects, TWEAKLAT_OT_Remove_Selected_Objects, TWEAKLAT_OT_Remove_Object, TWEAKLAT_PT_Main
|
||||||
,TWEAKLAT_OT_Delete
|
|
||||||
,TWEAKLAT_OT_Falloff
|
|
||||||
,TWEAKLAT_OT_Add_Objects
|
|
||||||
,TWEAKLAT_OT_Remove_Selected_Objects
|
|
||||||
,TWEAKLAT_OT_Remove_Object
|
|
||||||
,TWEAKLAT_PT_Main
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
from bpy.utils import register_class
|
from bpy.utils import register_class
|
||||||
for c in classes:
|
for c in classes:
|
||||||
register_class(c)
|
register_class(c)
|
||||||
|
|
||||||
bpy.types.Scene.tweak_lattice_parent_ob = PointerProperty(type=bpy.types.Object, name="Parent")
|
bpy.types.Scene.tweak_lattice_parent_ob = PointerProperty(
|
||||||
|
type=bpy.types.Object, name="Parent")
|
||||||
|
|
||||||
|
|
||||||
def unregister():
|
def unregister():
|
||||||
from bpy.utils import unregister_class
|
from bpy.utils import unregister_class
|
||||||
|
Loading…
Reference in New Issue
Block a user