Add Lattice Magic to Addons #48

Merged
Nick Alberelli merged 36 commits from feature/lattice_magic into main 2023-05-17 20:48:52 +02:00
2 changed files with 111 additions and 35 deletions
Showing only changes of commit cfb0f87879 - Show all commits

View File

@ -1,3 +1,9 @@
Camera Lattice lets you easily create a lattice in a camera's view frame and deform a character (or any collection) with said lattice. This addon adds some Lattice-based utilities to Blender.
# Tweak Lattice
Tweak Lattice lets you create a lattice setup at the 3D cursor to make deformation adjustments to the selected objects.
# Camera Lattice
Camera Lattice lets you create a lattice in a camera's view frame and deform a character (or any collection) with the lattice.
Note: If you want to delete a lattice, make sure to do it through the addon's UI, which is in the sidebar. This will make sure to delete all modifiers, drivers and animation datablocks that were created along with the lattice. Note: If you want to delete a lattice, make sure to do it through the addon's UI, which is in the sidebar. This will make sure to delete all modifiers, drivers and animation datablocks that were created along with the lattice.

View File

@ -16,7 +16,7 @@
# Add operators to the UI wherever possible. # Add operators to the UI wherever possible.
import bpy import bpy
from bpy.props import FloatProperty, IntVectorProperty from bpy.props import FloatProperty, IntVectorProperty, FloatVectorProperty, BoolProperty
from typing import List from typing import List
from mathutils import Vector from mathutils import Vector
from rna_prop_ui import rna_idprop_ui_create from rna_prop_ui import rna_idprop_ui_create
@ -33,13 +33,42 @@ def ensure_tweak_lattice_collection(scene):
return coll return coll
def ensure_falloff_vgroup(lattice_ob: bpy.types.Object, vg_name="Group", multiplier=1, power=1) -> 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)
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))
inf = lambda x, res : 1 - abs( 1 - (x / res/2) - 0.5)
x_influence = pow( inf(x, res_x), power)
y_influence = pow( inf(y, res_y), power)
z_influence = pow( inf(z, res_z), power)
min_influence = min(x_influence, y_influence, z_influence)
vg.add([index], min_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
class TWEAKLAT_OT_Create(bpy.types.Operator): class TWEAKLAT_OT_Create(bpy.types.Operator):
"""Create a lattice setup at the 3D cursor to deform selected objects.""" """Create a lattice setup at the 3D cursor to deform selected objects"""
bl_idname = "lattice.create_tweak_lattice" bl_idname = "lattice.create_tweak_lattice"
bl_label = "Create Tweak Lattice" bl_label = "Create Tweak Lattice"
bl_options = {'REGISTER', 'UNDO'} bl_options = {'REGISTER', 'UNDO'}
radius: FloatProperty(name="Radius", default=0.5) radius: FloatProperty(name="Radius", default=0.1, min=0, soft_max=0.2)
resolution: IntVectorProperty(name="Resolution", default=(12, 12, 12), min=6, max=64) resolution: IntVectorProperty(name="Resolution", default=(12, 12, 12), min=6, max=64)
@classmethod @classmethod
@ -58,7 +87,6 @@ class TWEAKLAT_OT_Create(bpy.types.Operator):
lattice = bpy.data.lattices.new(lattice_name) lattice = bpy.data.lattices.new(lattice_name)
lattice_ob = bpy.data.objects.new(lattice_name, lattice) lattice_ob = bpy.data.objects.new(lattice_name, lattice)
coll.objects.link(lattice_ob) coll.objects.link(lattice_ob)
lattice_ob.location = context.scene.cursor.location
lattice_ob.hide_viewport = True lattice_ob.hide_viewport = True
# Set resolution # Set resolution
@ -67,28 +95,20 @@ class TWEAKLAT_OT_Create(bpy.types.Operator):
lattice.points_v = clamp(lattice.points_v, 6, 64) lattice.points_v = clamp(lattice.points_v, 6, 64)
lattice.points_w = clamp(lattice.points_w, 6, 64) lattice.points_w = clamp(lattice.points_w, 6, 64)
# Create a vertex group. # Create a falloff vertex group
vg = lattice_ob.vertex_groups.new(name="Hook") vg = ensure_falloff_vgroup(lattice_ob, vg_name="Hook")
indices = []
for x in range(lattice.points_u-4):
for y in range(lattice.points_v-4):
for z in range(lattice.points_w-4):
indices.append( get_lattice_vertex_index(lattice, (x+2, y+2, z+2)) )
# Assign weights to the group.
vg.add(indices, 1, 'REPLACE')
# Create an Empty at the 3D cursor # Create an Empty at the 3D cursor
hook_name = "Hook_"+lattice_ob.name hook_name = "Hook_"+lattice_ob.name
hook = bpy.data.objects.new(hook_name, None) hook = bpy.data.objects.new(hook_name, None)
hook.empty_display_type = 'SPHERE' hook.empty_display_type = 'SPHERE'
hook.empty_display_size = 0.5 hook.empty_display_size = 0.5
hook.location = context.scene.cursor.location
coll.objects.link(hook) coll.objects.link(hook)
# Create some custom properties # Create some custom properties
hook['Lattice'] = lattice_ob hook['Lattice'] = lattice_ob
hook['Power'] = 1.0
hook['Multiplier'] = 1.0
rna_idprop_ui_create( rna_idprop_ui_create(
hook, "Tweak Lattice", default = 1.0, hook, "Tweak Lattice", default = 1.0,
@ -97,7 +117,7 @@ class TWEAKLAT_OT_Create(bpy.types.Operator):
) )
rna_idprop_ui_create( rna_idprop_ui_create(
hook, "Radius", default = self.radius, hook, "Radius", default = self.radius,
min = 0, soft_max = 1, max=100, min = 0, soft_max = 0.2, max=100,
description = "Size of the influenced area", description = "Size of the influenced area",
) )
@ -107,19 +127,15 @@ class TWEAKLAT_OT_Create(bpy.types.Operator):
root = bpy.data.objects.new(root_name, None) root = bpy.data.objects.new(root_name, None)
root.empty_display_type = 'CUBE' root.empty_display_type = 'CUBE'
root.empty_display_size = 0.5 root.empty_display_size = 0.5
root.location = context.scene.cursor.location root.matrix_world = context.scene.cursor.matrix
coll.objects.link(root) coll.objects.link(root)
root.hide_viewport = True root.hide_viewport = True
hook['Root'] = root hook['Root'] = root
# Parent lattice and hook to root # Parent lattice and hook to root
lattice_ob.parent = root lattice_ob.parent = root
lattice_ob.matrix_parent_inverse = root.matrix_world.inverted()
lattice_ob.location = Vector()
hook.parent = root hook.parent = root
hook.matrix_parent_inverse = root.matrix_world.inverted()
hook.location = Vector()
# Add Hook modifier to the lattice # Add Hook modifier to the lattice
hook_mod = lattice_ob.modifiers.new(name="Hook", type='HOOK') hook_mod = lattice_ob.modifiers.new(name="Hook", type='HOOK')
@ -136,15 +152,8 @@ class TWEAKLAT_OT_Create(bpy.types.Operator):
simple_driver(m, 'strength', hook, '["Tweak Lattice"]') simple_driver(m, 'strength', hook, '["Tweak Lattice"]')
# Set up Radius control. # Set up Radius control.
trans_con = hook.constraints.new(type='TRANSFORM') add_radius_constraint(hook, hook, root)
trans_con.target = root add_radius_constraint(lattice_ob, hook, root)
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"]')
for i in range(3):
simple_driver(lattice_ob, 'scale', hook, '["Radius"]', i)
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'
@ -156,8 +165,67 @@ class TWEAKLAT_OT_Create(bpy.types.Operator):
return {'FINISHED'} return {'FINISHED'}
class TWEAKLAT_OT_Falloff(bpy.types.Operator):
"""Adjust falloff of the hook vertex group of a Tweak Lattice"""
bl_idname = "lattice.tweak_lattice_adjust_falloww"
bl_label = "Adjust Falloff"
bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
def update(self, context):
if self.doing_invoke: return
hook = context.object
lattice_ob = hook['Lattice']
ensure_falloff_vgroup(lattice_ob, 'Hook', multiplier=self.multiplier, power=self.power)
hook['Power'] = self.power
hook['Multiplier'] = self.multiplier
if self.radius!=hook['Radius']:
hook['Radius'] = self.radius
rad_diff = self.radius - self.start_radius
lattice_ob.scale = Vector(self.lattice_start_scale[:]) * Vector([1-rad_diff]*3)
hook.scale = Vector(self.hook_start_scale[:]) * Vector([1-rad_diff]*3)
# Actual parameters
radius: FloatProperty(name="Radius", default=1, update=update, min=0.0001, soft_max=0.2)
power: FloatProperty(name="Power", default=1, update=update, min=0, soft_max=2)
multiplier: FloatProperty(name="Multiplier", default=1, update=update, min=0, soft_max=2)
# Storage to share info between Invoke and Update
lattice_start_scale: FloatVectorProperty()
hook_start_scale: FloatVectorProperty()
start_radius: FloatProperty()
doing_invoke: BoolProperty(default=True)
@classmethod
def poll(cls, context):
ob = context.object
return ob.type=='EMPTY' and 'Tweak Lattice' in ob
def invoke(self, context, event):
hook = context.object
self.start_radius = self.radius = hook['Radius']
self.power = hook['Power']
self.multiplier = hook['Multiplier']
self.hook_start_scale = hook.scale.copy()
lattice_ob = hook['Lattice']
self.lattice_start_scale = lattice_ob.scale.copy()
self.doing_invoke = False
wm = context.window_manager
return wm.invoke_props_dialog(self)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
layout.prop(self, 'radius', text="Radius", slider=True)
layout.prop(self, 'power', text="Smoothness", slider=True)
layout.prop(self, 'multiplier', text="Strength", slider=True)
def execute(self, context):
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"
bl_label = "Delete Tweak Lattice" bl_label = "Delete Tweak Lattice"
bl_options = {'REGISTER', 'UNDO'} bl_options = {'REGISTER', 'UNDO'}
@ -227,6 +295,7 @@ class TWEAKLAT_PT_Main(bpy.types.Panel):
layout.prop(hook, '["Tweak Lattice"]', slider=True, text="Influence") layout.prop(hook, '["Tweak Lattice"]', slider=True, text="Influence")
layout.prop(hook, '["Radius"]', slider=True) layout.prop(hook, '["Radius"]', slider=True)
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')
@ -254,6 +323,7 @@ class TWEAKLAT_PT_Main(bpy.types.Panel):
classes = [ classes = [
TWEAKLAT_OT_Create TWEAKLAT_OT_Create
,TWEAKLAT_OT_Delete ,TWEAKLAT_OT_Delete
,TWEAKLAT_OT_Falloff
,TWEAKLAT_PT_Main ,TWEAKLAT_PT_Main
] ]