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 26add92e7f - Show all commits

View File

@ -5,7 +5,9 @@
import bpy
from bpy.props import FloatProperty, IntVectorProperty, FloatVectorProperty, BoolProperty, PointerProperty, StringProperty, EnumProperty
from typing import List
from bpy.types import Operator, Object, VertexGroup, Scene, Collection, Modifier, Panel
from typing import List, Tuple
from mathutils import Vector
from rna_prop_ui import rna_idprop_ui_create
@ -14,7 +16,7 @@ from .utils import clamp, get_lattice_vertex_index, simple_driver, bounding_box_
TWEAKLAT_COLL_NAME = 'Tweak Lattices'
class TWEAKLAT_OT_Create(bpy.types.Operator):
class TWEAKLAT_OT_Create(Operator):
"""Create a lattice setup to deform selected objects"""
bl_idname = "lattice.create_tweak_lattice"
bl_label = "Create Tweak Lattice"
@ -112,6 +114,7 @@ class TWEAKLAT_OT_Create(bpy.types.Operator):
# Create some custom properties
hook['Lattice'] = lattice_ob
lattice_ob['Hook'] = hook
hook['Multiplier'] = 1.0
hook['Expression'] = 'x'
@ -130,6 +133,7 @@ class TWEAKLAT_OT_Create(bpy.types.Operator):
# This will allow pressing Ctrl+G/R/S on the hook to reset its transforms.
root_name = "Root_" + hook.name
root = bpy.data.objects.new(root_name, None)
root['Hook'] = hook
root.empty_display_type = 'CUBE'
root.empty_display_size = 0.5
if self.location == 'CENTER':
@ -189,7 +193,7 @@ class TWEAKLAT_OT_Create(bpy.types.Operator):
return {'FINISHED'}
class TWEAKLAT_OT_Falloff(bpy.types.Operator):
class TWEAKLAT_OT_Falloff(Operator):
"""Adjust falloff of the hook vertex group of a Tweak Lattice"""
bl_idname = "lattice.tweak_lattice_adjust_falloww"
bl_label = "Adjust Falloff"
@ -198,10 +202,9 @@ class TWEAKLAT_OT_Falloff(bpy.types.Operator):
def update_falloff(self, context):
if self.doing_invoke:
return
hook = context.object
lattice_ob = hook['Lattice']
hook, lattice, _root = get_tweak_setup(context.object)
ret = ensure_falloff_vgroup(
lattice_ob, 'Hook', multiplier=self.multiplier, expression=self.expression)
lattice, 'Hook', multiplier=self.multiplier, expression=self.expression)
self.is_expression_valid = ret != None
if ret:
hook['Expression'] = self.expression
@ -235,11 +238,11 @@ class TWEAKLAT_OT_Falloff(bpy.types.Operator):
@classmethod
def poll(cls, context):
ob = context.object
return ob.type == 'EMPTY' and 'Tweak Lattice' in ob
hook, lattice, root = get_tweak_setup(context.object)
return hook and lattice and root
def invoke(self, context, event):
hook = context.object
hook, _lattice, _root = get_tweak_setup(context.object)
self.multiplier = hook['Multiplier']
self.hook_start_scale = hook.scale.copy()
lattice_ob = hook['Lattice']
@ -269,7 +272,7 @@ class TWEAKLAT_OT_Falloff(bpy.types.Operator):
return {'FINISHED'}
class TWEAKLAT_OT_Delete(bpy.types.Operator):
class TWEAKLAT_OT_Delete(Operator):
"""Delete a tweak lattice setup with all its helper objects, drivers, etc"""
bl_idname = "lattice.delete_tweak_lattice"
bl_label = "Delete Tweak Lattice"
@ -277,13 +280,11 @@ class TWEAKLAT_OT_Delete(bpy.types.Operator):
@classmethod
def poll(cls, context):
ob = context.object
return ob and ob.type == 'EMPTY' and 'Tweak Lattice' in ob
hook, lattice, root = get_tweak_setup(context.object)
return hook and lattice and root
def execute(self, context):
hook = context.object
lattice = hook['Lattice']
root = hook['Root']
hook, lattice, root = get_tweak_setup(context.object)
# Remove Lattice modifiers and their drivers.
remove_all_objects_from_lattice(hook)
@ -307,7 +308,7 @@ class TWEAKLAT_OT_Delete(bpy.types.Operator):
return {'FINISHED'}
class TWEAKLAT_OT_Add_Objects(bpy.types.Operator):
class TWEAKLAT_OT_Add_Objects(Operator):
"""Add selected objects to this tweak lattice"""
bl_idname = "lattice.add_selected_objects"
bl_label = "Add Selected Objects"
@ -315,19 +316,20 @@ class TWEAKLAT_OT_Add_Objects(bpy.types.Operator):
@classmethod
def poll(cls, context):
ob = context.object
if ob.type != 'EMPTY' or 'Tweak Lattice' not in ob:
hook, _lattice, _root = get_tweak_setup(context.object)
if not hook:
return False
values = ob.values()
values = hook.values()
for sel_o in context.selected_objects:
if sel_o == ob or sel_o.type != 'MESH':
if sel_o == hook or sel_o.type != 'MESH':
continue
if sel_o not in values:
return True
return False
def execute(self, context):
hook = context.object
hook, _lattice, _root = get_tweak_setup(context.object)
# Add Lattice modifier to the selected objects
add_objects_to_lattice(hook, context.selected_objects)
@ -335,7 +337,7 @@ class TWEAKLAT_OT_Add_Objects(bpy.types.Operator):
return {'FINISHED'}
class TWEAKLAT_OT_Remove_Selected_Objects(bpy.types.Operator):
class TWEAKLAT_OT_Remove_Selected_Objects(Operator):
"""Remove selected objects from this tweak lattice"""
bl_idname = "lattice.remove_selected_objects"
bl_label = "Remove Selected Objects"
@ -343,19 +345,20 @@ class TWEAKLAT_OT_Remove_Selected_Objects(bpy.types.Operator):
@classmethod
def poll(cls, context):
ob = context.object
if ob.type != 'EMPTY' or 'Tweak Lattice' not in ob:
hook, _lattice, _root = get_tweak_setup(context.object)
if not hook:
return False
values = ob.values()
values = hook.values()
for sel_o in context.selected_objects:
if sel_o == ob or sel_o.type != 'MESH':
if sel_o == hook or sel_o.type != 'MESH':
continue
if sel_o in values:
return True
return False
def execute(self, context):
hook = context.object
hook, _lattice, _root = get_tweak_setup(context.object)
# Add Lattice modifier to the selected objects
remove_objects_from_lattice(hook, context.selected_objects)
@ -363,7 +366,7 @@ class TWEAKLAT_OT_Remove_Selected_Objects(bpy.types.Operator):
return {'FINISHED'}
class TWEAKLAT_OT_Remove_Object(bpy.types.Operator):
class TWEAKLAT_OT_Remove_Object(Operator):
"""Remove this object from the tweak lattice"""
bl_idname = "lattice.remove_object"
bl_label = "Remove Object"
@ -373,7 +376,7 @@ class TWEAKLAT_OT_Remove_Object(bpy.types.Operator):
description="Name of the custom property that references the object that should be removed")
def execute(self, context):
hook = context.object
hook, _lattice, _root = get_tweak_setup(context.object)
target = hook[self.ob_pointer_prop_name]
# Add Lattice modifier to the selected objects
@ -382,7 +385,17 @@ class TWEAKLAT_OT_Remove_Object(bpy.types.Operator):
return {'FINISHED'}
class TWEAKLAT_PT_Main(bpy.types.Panel):
class TWEAKLAT_OT_Duplicate(Operator):
"""Duplicate this Tweak Lattice set-up"""
bl_idname = "lattice.duplicate_tweak_setup"
bl_label = "Duplicate Tweak Lattice"
bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
def execute(self, context):
pass
class TWEAKLAT_PT_Main(Panel):
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'Lattice Magic'
@ -390,16 +403,19 @@ class TWEAKLAT_PT_Main(bpy.types.Panel):
@classmethod
def poll(cls, context):
return context.object
hook, _lattice, _root = get_tweak_setup(context.object)
return context.object.type == 'MESH' or hook
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
hook = context.object
hook, lattice, root = get_tweak_setup(context.object)
layout = layout.column()
if hook.type != 'EMPTY' or 'Tweak Lattice' not in hook:
if not hook:
layout.operator(TWEAKLAT_OT_Create.bl_idname,
icon='OUTLINER_OB_LATTICE')
return
@ -416,16 +432,15 @@ class TWEAKLAT_PT_Main(bpy.types.Panel):
layout.label(text="Helper Objects")
lattice_row = layout.row()
lattice_row.prop(hook, '["Lattice"]', text="Lattice")
lattice_row.prop(hook['Lattice'], 'hide_viewport',
lattice_row.prop(lattice, 'hide_viewport',
text="", emboss=False)
root_row = layout.row()
root_row.prop(hook, '["Root"]', text="Root")
root_row.prop(hook['Root'], 'hide_viewport', text="", emboss=False)
root_row.prop(root, 'hide_viewport', text="", emboss=False)
layout.separator()
layout.label(text="Parenting")
root = hook['Root']
col = layout.column()
col.enabled = False
col.prop(root, 'parent')
@ -472,7 +487,7 @@ class TWEAKLAT_PT_Main(bpy.types.Panel):
for ob, key in objects_and_keys:
row = layout.row(align=True)
row.prop(hook, f'["{key}"]', text="")
mod = get_lattice_modifier_of_object(ob, hook['Lattice'])
mod = get_lattice_modifier_of_object(ob, lattice)
row.prop_search(mod, 'vertex_group', ob,
'vertex_groups', text="", icon='GROUP_VERTEX')
op = row.operator(
@ -480,7 +495,20 @@ class TWEAKLAT_PT_Main(bpy.types.Panel):
op.ob_pointer_prop_name = key
def ensure_tweak_lattice_collection(scene: bpy.types.Scene) -> bpy.types.Collection:
def get_tweak_setup(obj: Object) -> Tuple[Object, Object, Object]:
"""Based on either the hook, lattice or root, return all three."""
if obj.type == 'EMPTY':
if 'Root' and 'Lattice' in obj:
return obj, obj['Lattice'], obj['Root']
elif 'Hook' in obj:
return obj['Hook'], obj['Hook']['Lattice'], obj
elif obj.type == 'LATTICE' and 'Hook' in obj:
return obj['Hook'], obj, obj['Hook']['Root']
return [None, None, None]
def ensure_tweak_lattice_collection(scene: Scene) -> Collection:
coll = bpy.data.collections.get(TWEAKLAT_COLL_NAME)
if not coll:
coll = bpy.data.collections.new(TWEAKLAT_COLL_NAME)
@ -490,8 +518,8 @@ def ensure_tweak_lattice_collection(scene: bpy.types.Scene) -> bpy.types.Collect
def ensure_falloff_vgroup(
lattice_ob: bpy.types.Object,
vg_name="Group", multiplier=1, expression="x") -> bpy.types.VertexGroup:
lattice_ob: Object,
vg_name="Group", multiplier=1, expression="x") -> VertexGroup:
lattice = lattice_ob.data
res_x, res_y, res_z = lattice.points_u, lattice.points_v, lattice.points_w
@ -530,7 +558,7 @@ def add_radius_constraint(obj, hook, target):
return trans_con
def get_objects_of_lattice(hook: bpy.types.Object) -> List[bpy.types.Object]:
def get_objects_of_lattice(hook: Object) -> List[Object]:
objs = []
ob_count = 0
ob_prop_name = "object_"+str(ob_count)
@ -539,7 +567,7 @@ def get_objects_of_lattice(hook: bpy.types.Object) -> List[bpy.types.Object]:
return objs
def get_lattice_modifier_of_object(obj, lattice) -> bpy.types.Modifier:
def get_lattice_modifier_of_object(obj, lattice) -> 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:
@ -547,8 +575,8 @@ def get_lattice_modifier_of_object(obj, lattice) -> bpy.types.Modifier:
def add_objects_to_lattice(
hook: bpy.types.Object,
objects: List[bpy.types.Object]):
hook: Object,
objects: List[Object]):
lattice_ob = hook['Lattice']
# Check for existing
@ -568,7 +596,7 @@ def add_objects_to_lattice(
simple_driver(m, 'strength', hook, '["Tweak Lattice"]')
def remove_all_objects_from_lattice(hook: bpy.types.Object) -> List[bpy.types.Object]:
def remove_all_objects_from_lattice(hook: Object) -> List[Object]:
lattice_ob = hook['Lattice']
objs = []
ob_count = 0
@ -599,7 +627,13 @@ def remove_objects_from_lattice(hook, objects):
classes = [
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_Create,
TWEAKLAT_OT_Delete,
TWEAKLAT_OT_Falloff,
TWEAKLAT_OT_Add_Objects,
TWEAKLAT_OT_Remove_Selected_Objects,
TWEAKLAT_OT_Remove_Object,
TWEAKLAT_PT_Main
]
@ -608,8 +642,8 @@ def register():
for c in classes:
register_class(c)
bpy.types.Scene.tweak_lattice_parent_ob = PointerProperty(
type=bpy.types.Object, name="Parent")
Scene.tweak_lattice_parent_ob = PointerProperty(
type=Object, name="Parent")
def unregister():
@ -617,4 +651,4 @@ def unregister():
for c in reversed(classes):
unregister_class(c)
del bpy.types.Scene.tweak_lattice_parent_ob
del Scene.tweak_lattice_parent_ob