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
Showing only changes of commit d32576cc2e - Show all commits

View File

@ -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