Add Lattice Magic
to Addons
#48
@ -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.
|
134
tweak_lattice.py
134
tweak_lattice.py
@ -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
|
||||||
]
|
]
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user