Add Lattice Magic
to Addons
#48
@ -27,20 +27,20 @@ bl_info = {
|
|||||||
|
|
||||||
from . import camera_lattice
|
from . import camera_lattice
|
||||||
from . import tweak_lattice
|
from . import tweak_lattice
|
||||||
|
from . import operators
|
||||||
import importlib
|
import importlib
|
||||||
|
|
||||||
modules = [
|
modules = [
|
||||||
camera_lattice
|
camera_lattice
|
||||||
,tweak_lattice
|
,tweak_lattice
|
||||||
|
,operators
|
||||||
]
|
]
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
for m in modules:
|
for m in modules:
|
||||||
importlib.reload(m)
|
importlib.reload(m)
|
||||||
if hasattr(m, 'register'):
|
m.register()
|
||||||
m.register()
|
|
||||||
|
|
||||||
def unregister():
|
def unregister():
|
||||||
for m in modules:
|
for m in modules:
|
||||||
if hasattr(m, 'unregister'):
|
m.unregister()
|
||||||
m.unregister()
|
|
@ -8,33 +8,12 @@
|
|||||||
# 3D Lattices: Need to have a distance, thickness and Z resolution parameter.
|
# 3D Lattices: Need to have a distance, thickness and Z resolution parameter.
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
from typing import Tuple, List
|
|
||||||
import math
|
import math
|
||||||
from mathutils import Vector
|
from mathutils import Vector
|
||||||
from bpy.props import BoolProperty, PointerProperty, CollectionProperty, IntProperty, EnumProperty, FloatProperty
|
from bpy.props import BoolProperty, PointerProperty, CollectionProperty, IntProperty, EnumProperty, FloatProperty
|
||||||
from mathutils.geometry import intersect_point_line
|
from mathutils.geometry import intersect_point_line
|
||||||
|
|
||||||
def bounding_box(points) -> Tuple[Vector, Vector]:
|
from .utils import bounding_box_center, bounding_box
|
||||||
""" Return two vectors representing the lowest and highest coordinates of
|
|
||||||
a the bounding box of the passed points.
|
|
||||||
"""
|
|
||||||
|
|
||||||
lowest = points[0].copy()
|
|
||||||
highest = points[0].copy()
|
|
||||||
for p in points:
|
|
||||||
for i in range(len(p)):
|
|
||||||
if p[i] < lowest[i]:
|
|
||||||
lowest[i] = p[i]
|
|
||||||
if p[i] > highest[i]:
|
|
||||||
highest[i] = p[i]
|
|
||||||
|
|
||||||
return lowest, highest
|
|
||||||
|
|
||||||
def bounding_box_center(points) -> Vector:
|
|
||||||
"""Find the bounding box center of some points."""
|
|
||||||
bbox_low, bbox_high = bounding_box(points)
|
|
||||||
return bbox_low + (bbox_high-bbox_low)/2
|
|
||||||
|
|
||||||
|
|
||||||
class CAMLAT_UL_lattice_slots(bpy.types.UIList):
|
class CAMLAT_UL_lattice_slots(bpy.types.UIList):
|
||||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||||
@ -102,6 +81,26 @@ class LatticeSlot(bpy.types.PropertyGroup):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CAMLAT_OT_Add(bpy.types.Operator):
|
||||||
|
"""Add a Camera Lattice Slot"""
|
||||||
|
bl_idname = "lattice.add_slot"
|
||||||
|
bl_label = "Add Lattice Slot"
|
||||||
|
bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
scene = context.scene
|
||||||
|
lattice_slots = scene.lattice_slots
|
||||||
|
active_index = scene.active_lattice_index
|
||||||
|
to_index = active_index + 1
|
||||||
|
if len(lattice_slots)==0:
|
||||||
|
to_index = 0
|
||||||
|
|
||||||
|
scene.lattice_slots.add()
|
||||||
|
scene.lattice_slots.move(len(scene.lattice_slots)-1, to_index)
|
||||||
|
scene.active_lattice_index = to_index
|
||||||
|
|
||||||
|
return { 'FINISHED' }
|
||||||
|
|
||||||
class CAMLAT_OT_Remove(bpy.types.Operator):
|
class CAMLAT_OT_Remove(bpy.types.Operator):
|
||||||
"""Remove Lattice Slot along with its Lattice object, animation and modifiers"""
|
"""Remove Lattice Slot along with its Lattice object, animation and modifiers"""
|
||||||
bl_idname = "lattice.remove_slot"
|
bl_idname = "lattice.remove_slot"
|
||||||
@ -132,26 +131,6 @@ class CAMLAT_OT_Remove(bpy.types.Operator):
|
|||||||
|
|
||||||
return { 'FINISHED' }
|
return { 'FINISHED' }
|
||||||
|
|
||||||
class CAMLAT_OT_Add(bpy.types.Operator):
|
|
||||||
"""Add a Camera Lattice Slot"""
|
|
||||||
bl_idname = "lattice.add_slot"
|
|
||||||
bl_label = "Add Lattice Slot"
|
|
||||||
bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
|
|
||||||
|
|
||||||
def execute(self, context):
|
|
||||||
scene = context.scene
|
|
||||||
lattice_slots = scene.lattice_slots
|
|
||||||
active_index = scene.active_lattice_index
|
|
||||||
to_index = active_index + 1
|
|
||||||
if len(lattice_slots)==0:
|
|
||||||
to_index = 0
|
|
||||||
|
|
||||||
scene.lattice_slots.add()
|
|
||||||
scene.lattice_slots.move(len(scene.lattice_slots)-1, to_index)
|
|
||||||
scene.active_lattice_index = to_index
|
|
||||||
|
|
||||||
return { 'FINISHED' }
|
|
||||||
|
|
||||||
class CAMLAT_OT_Move(bpy.types.Operator):
|
class CAMLAT_OT_Move(bpy.types.Operator):
|
||||||
"""Move Lattice Slot"""
|
"""Move Lattice Slot"""
|
||||||
bl_idname = "lattice.move_slot"
|
bl_idname = "lattice.move_slot"
|
||||||
@ -364,41 +343,6 @@ class CAMLAT_OT_Delete(bpy.types.Operator):
|
|||||||
|
|
||||||
return { 'FINISHED' }
|
return { 'FINISHED' }
|
||||||
|
|
||||||
class ShapeKey_OT_Reset(bpy.types.Operator):
|
|
||||||
"""Reset shape of the active shape key of the active object"""
|
|
||||||
bl_idname = "object.reset_shape_key"
|
|
||||||
bl_label = "Reset Shape Key"
|
|
||||||
bl_options = {'REGISTER', 'UNDO'}
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def poll(cls, context):
|
|
||||||
ob = context.object
|
|
||||||
if not ob:
|
|
||||||
return False
|
|
||||||
if not ob.data.shape_keys:
|
|
||||||
return False
|
|
||||||
if len(ob.data.shape_keys.key_blocks)<2:
|
|
||||||
return False
|
|
||||||
if ob.active_shape_key_index==0:
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def execute(self, context):
|
|
||||||
ob = context.object
|
|
||||||
|
|
||||||
active_index = ob.active_shape_key_index
|
|
||||||
key_blocks = ob.data.shape_keys.key_blocks
|
|
||||||
active_block = key_blocks[active_index]
|
|
||||||
basis_block = key_blocks[0]
|
|
||||||
for i, skp in enumerate(active_block.data):
|
|
||||||
skp.co = basis_block.data[i].co
|
|
||||||
|
|
||||||
return { 'FINISHED' }
|
|
||||||
|
|
||||||
def draw_shape_key_reset(self, context):
|
|
||||||
self.layout.operator(ShapeKey_OT_Reset.bl_idname, text="Reset Shape Key", icon='FILE_REFRESH')
|
|
||||||
|
|
||||||
class CAMLAT_OT_ShapeKey_Add(bpy.types.Operator):
|
class CAMLAT_OT_ShapeKey_Add(bpy.types.Operator):
|
||||||
"""Add a shape key to the active Lattice Slot's lattice, named after the current frame number"""
|
"""Add a shape key to the active Lattice Slot's lattice, named after the current frame number"""
|
||||||
|
|
||||||
@ -422,11 +366,12 @@ class CAMLAT_OT_ShapeKey_Add(bpy.types.Operator):
|
|||||||
|
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
class CAMLAT_PT_main(bpy.types.Panel):
|
|
||||||
|
class CAMLAT_PT_Main(bpy.types.Panel):
|
||||||
bl_space_type = 'VIEW_3D'
|
bl_space_type = 'VIEW_3D'
|
||||||
bl_region_type = 'UI'
|
bl_region_type = 'UI'
|
||||||
bl_category = 'Lattice Magic'
|
bl_category = 'Lattice Magic'
|
||||||
bl_label = "Camera Lattice Slots"
|
bl_label = "Camera Lattice"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
@ -523,15 +468,16 @@ class CAMLAT_PT_main(bpy.types.Panel):
|
|||||||
classes = [
|
classes = [
|
||||||
LatticeSlot
|
LatticeSlot
|
||||||
,CAMLAT_UL_lattice_slots
|
,CAMLAT_UL_lattice_slots
|
||||||
,CAMLAT_PT_main
|
|
||||||
|
|
||||||
,CAMLAT_OT_Remove
|
|
||||||
,CAMLAT_OT_Add
|
,CAMLAT_OT_Add
|
||||||
|
,CAMLAT_OT_Remove
|
||||||
,CAMLAT_OT_Move
|
,CAMLAT_OT_Move
|
||||||
|
|
||||||
,CAMLAT_OT_Generate
|
,CAMLAT_OT_Generate
|
||||||
,CAMLAT_OT_Delete
|
,CAMLAT_OT_Delete
|
||||||
,ShapeKey_OT_Reset
|
|
||||||
,CAMLAT_OT_ShapeKey_Add
|
,CAMLAT_OT_ShapeKey_Add
|
||||||
|
|
||||||
|
,CAMLAT_PT_Main
|
||||||
]
|
]
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
@ -541,7 +487,6 @@ def register():
|
|||||||
|
|
||||||
bpy.types.Scene.lattice_slots = CollectionProperty(type=LatticeSlot)
|
bpy.types.Scene.lattice_slots = CollectionProperty(type=LatticeSlot)
|
||||||
bpy.types.Scene.active_lattice_index = IntProperty()
|
bpy.types.Scene.active_lattice_index = IntProperty()
|
||||||
bpy.types.MESH_MT_shape_key_context_menu.append(draw_shape_key_reset)
|
|
||||||
|
|
||||||
def unregister():
|
def unregister():
|
||||||
from bpy.utils import unregister_class
|
from bpy.utils import unregister_class
|
||||||
@ -549,5 +494,4 @@ def unregister():
|
|||||||
unregister_class(c)
|
unregister_class(c)
|
||||||
|
|
||||||
del bpy.types.Scene.lattice_slots
|
del bpy.types.Scene.lattice_slots
|
||||||
del bpy.types.Scene.active_lattice_index
|
del bpy.types.Scene.active_lattice_index
|
||||||
bpy.types.MESH_MT_shape_key_context_menu.remove(draw_shape_key_reset)
|
|
113
operators.py
Normal file
113
operators.py
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
import bpy
|
||||||
|
from bpy.props import FloatProperty
|
||||||
|
from .utils import get_lattice_point_original_position
|
||||||
|
|
||||||
|
class LATTICE_OT_Reset(bpy.types.Operator):
|
||||||
|
"""Reset selected lattice points to their default position."""
|
||||||
|
bl_idname = "lattice.reset_points"
|
||||||
|
bl_label = "Reset Lattice Points"
|
||||||
|
bl_options = {'REGISTER', 'UNDO'}
|
||||||
|
|
||||||
|
factor: FloatProperty(name="Factor", min=0, max=1, default=1)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return len(context.selected_objects)>0 and context.mode=='EDIT_LATTICE'
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
layout.use_property_split = True
|
||||||
|
layout.prop(self, 'factor', slider=True)
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
bpy.ops.object.mode_set(mode='OBJECT')
|
||||||
|
for ob in context.selected_objects:
|
||||||
|
if ob.type!='LATTICE':
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Resetting shape key or Basis shape
|
||||||
|
if ob.data.shape_keys:
|
||||||
|
active_index = ob.active_shape_key_index
|
||||||
|
key_blocks = ob.data.shape_keys.key_blocks
|
||||||
|
active_block = key_blocks[active_index]
|
||||||
|
basis_block = key_blocks[0]
|
||||||
|
if active_index > 0:
|
||||||
|
for i, skp in enumerate(active_block.data):
|
||||||
|
point = ob.data.points[i]
|
||||||
|
if not point.select: continue
|
||||||
|
mix = skp.co.lerp(basis_block.data[i].co, self.factor)
|
||||||
|
skp.co = mix
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
for i, skp in enumerate(active_block.data):
|
||||||
|
base = get_lattice_point_original_position(ob.data, i)
|
||||||
|
# Resetting the Basis shape
|
||||||
|
mix = basis_block.data[i].co.lerp(base, self.factor)
|
||||||
|
basis_block.data[i].co = mix
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Otherwise, reset the actual points.
|
||||||
|
for i in range(len(ob.data.points)):
|
||||||
|
point = ob.data.points[i]
|
||||||
|
if not point.select:
|
||||||
|
continue
|
||||||
|
base = get_lattice_point_original_position(ob.data, i)
|
||||||
|
mix = point.co_deform.lerp(base, self.factor)
|
||||||
|
point.co_deform = base
|
||||||
|
|
||||||
|
bpy.ops.object.mode_set(mode='EDIT')
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
class ShapeKey_OT_Reset(bpy.types.Operator):
|
||||||
|
"""Reset shape of the active shape key of the active object"""
|
||||||
|
bl_idname = "object.reset_shape_key"
|
||||||
|
bl_label = "Reset Shape Key"
|
||||||
|
bl_options = {'REGISTER', 'UNDO'}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
ob = context.object
|
||||||
|
if not ob:
|
||||||
|
return False
|
||||||
|
if not ob.data.shape_keys:
|
||||||
|
return False
|
||||||
|
if len(ob.data.shape_keys.key_blocks)<2:
|
||||||
|
return False
|
||||||
|
if ob.active_shape_key_index==0:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
ob = context.object
|
||||||
|
|
||||||
|
active_index = ob.active_shape_key_index
|
||||||
|
key_blocks = ob.data.shape_keys.key_blocks
|
||||||
|
active_block = key_blocks[active_index]
|
||||||
|
basis_block = key_blocks[0]
|
||||||
|
for i, skp in enumerate(active_block.data):
|
||||||
|
skp.co = basis_block.data[i].co
|
||||||
|
|
||||||
|
return { 'FINISHED' }
|
||||||
|
|
||||||
|
def draw_shape_key_reset(self, context):
|
||||||
|
self.layout.operator(ShapeKey_OT_Reset.bl_idname, text="Reset Shape Key", icon='FILE_REFRESH')
|
||||||
|
|
||||||
|
|
||||||
|
classes = [
|
||||||
|
ShapeKey_OT_Reset
|
||||||
|
,LATTICE_OT_Reset
|
||||||
|
]
|
||||||
|
|
||||||
|
def register():
|
||||||
|
from bpy.utils import register_class
|
||||||
|
for c in classes:
|
||||||
|
register_class(c)
|
||||||
|
bpy.types.MESH_MT_shape_key_context_menu.append(draw_shape_key_reset)
|
||||||
|
|
||||||
|
def unregister():
|
||||||
|
from bpy.utils import unregister_class
|
||||||
|
for c in reversed(classes):
|
||||||
|
unregister_class(c)
|
||||||
|
|
||||||
|
bpy.types.MESH_MT_shape_key_context_menu.remove(draw_shape_key_reset)
|
142
tweak_lattice.py
142
tweak_lattice.py
@ -21,79 +21,10 @@ 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
|
||||||
|
|
||||||
|
from .utils import clamp, get_lattice_vertex_index, simple_driver
|
||||||
|
|
||||||
coll_name = 'Tweak Lattices'
|
coll_name = 'Tweak Lattices'
|
||||||
|
|
||||||
def clamp(val, _min=0, _max=1) -> float or int:
|
|
||||||
if val < _min:
|
|
||||||
return _min
|
|
||||||
if val > _max:
|
|
||||||
return _max
|
|
||||||
return val
|
|
||||||
|
|
||||||
def get_lattice_vertex_index(lattice: bpy.types.Lattice, xyz: List[int], do_clamp=True) -> int:
|
|
||||||
"""Get the index of a lattice vertex based on its position on the XYZ axes."""
|
|
||||||
|
|
||||||
# The lattice vertex indicies start in the -Y, -X, -Z corner,
|
|
||||||
# increase on X+, then moves to the next row on Y+, then moves up on Z+.
|
|
||||||
res_x, res_y, res_z = lattice.points_u, lattice.points_v, lattice.points_w
|
|
||||||
x, y, z = xyz[:]
|
|
||||||
if do_clamp:
|
|
||||||
x = clamp(x, 0, res_x)
|
|
||||||
y = clamp(y, 0, res_y)
|
|
||||||
z = clamp(z, 0, res_z)
|
|
||||||
|
|
||||||
assert x < res_x and y < res_y and z < res_z, "Error: Lattice vertex xyz index out of bounds"
|
|
||||||
|
|
||||||
index = (z * res_y*res_x) + (y * res_x) + x
|
|
||||||
return index
|
|
||||||
|
|
||||||
def get_lattice_vertex_xyz_position(lattice: bpy.types.Lattice, index: int) -> (int, int, int):
|
|
||||||
res_x, res_y, res_z = lattice.points_u, lattice.points_v, lattice.points_w
|
|
||||||
|
|
||||||
x = 0
|
|
||||||
remaining = index
|
|
||||||
z = int(remaining / (res_x*res_y))
|
|
||||||
remaining -= z*(res_x*res_y)
|
|
||||||
y = int(remaining / res_x)
|
|
||||||
remaining -= y*res_x
|
|
||||||
x = remaining # Maybe need to add or subtract 1 here?
|
|
||||||
|
|
||||||
return (x, y, z)
|
|
||||||
|
|
||||||
def get_lattice_point_original_position(lattice: bpy.types.Lattice, index: int) -> Vector:
|
|
||||||
"""Reset a lattice vertex to its original position."""
|
|
||||||
start_vec = Vector((-0.5, -0.5, -0.5))
|
|
||||||
if lattice.points_u == 1:
|
|
||||||
start_vec[0] = 0
|
|
||||||
if lattice.points_v == 1:
|
|
||||||
start_vec[1] = 0
|
|
||||||
if lattice.points_w == 1:
|
|
||||||
start_vec[2] = 0
|
|
||||||
|
|
||||||
unit_u = 1/(lattice.points_u-1)
|
|
||||||
unit_v = 1/(lattice.points_v-1)
|
|
||||||
unit_w = 1/(lattice.points_w-1)
|
|
||||||
|
|
||||||
unit_vec = Vector((unit_u, unit_v, unit_w))
|
|
||||||
xyz_vec = Vector(get_lattice_vertex_xyz_position(lattice, index))
|
|
||||||
|
|
||||||
return start_vec + xyz_vec*unit_vec
|
|
||||||
|
|
||||||
def simple_driver(owner: bpy.types.ID, driver_path: str, target_ob: bpy.types.Object, data_path: str, array_index=-1) -> bpy.types.Driver:
|
|
||||||
if array_index > -1:
|
|
||||||
owner.driver_remove(driver_path, array_index)
|
|
||||||
driver = owner.driver_add(driver_path, array_index).driver
|
|
||||||
else:
|
|
||||||
owner.driver_remove(driver_path)
|
|
||||||
driver = owner.driver_add(driver_path).driver
|
|
||||||
|
|
||||||
driver.expression = 'var'
|
|
||||||
var = driver.variables.new()
|
|
||||||
var.targets[0].id = target_ob
|
|
||||||
var.targets[0].data_path = data_path
|
|
||||||
|
|
||||||
return driver
|
|
||||||
|
|
||||||
def ensure_tweak_lattice_collection(scene):
|
def ensure_tweak_lattice_collection(scene):
|
||||||
coll = bpy.data.collections.get(coll_name)
|
coll = bpy.data.collections.get(coll_name)
|
||||||
if not coll:
|
if not coll:
|
||||||
@ -102,62 +33,6 @@ def ensure_tweak_lattice_collection(scene):
|
|||||||
|
|
||||||
return coll
|
return coll
|
||||||
|
|
||||||
class LATTICE_OT_Reset(bpy.types.Operator):
|
|
||||||
"""Reset selected lattice points to their default position."""
|
|
||||||
bl_idname = "lattice.reset_points"
|
|
||||||
bl_label = "Reset Lattice Points"
|
|
||||||
bl_options = {'REGISTER', 'UNDO'}
|
|
||||||
|
|
||||||
factor: FloatProperty(name="Factor", min=0, max=1, default=1)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def poll(cls, context):
|
|
||||||
return len(context.selected_objects)>0 and context.mode=='EDIT_LATTICE'
|
|
||||||
|
|
||||||
def draw(self, context):
|
|
||||||
layout = self.layout
|
|
||||||
layout.use_property_split = True
|
|
||||||
layout.prop(self, 'factor', slider=True)
|
|
||||||
|
|
||||||
def execute(self, context):
|
|
||||||
bpy.ops.object.mode_set(mode='OBJECT')
|
|
||||||
for ob in context.selected_objects:
|
|
||||||
if ob.type!='LATTICE':
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Resetting shape key or Basis shape
|
|
||||||
if ob.data.shape_keys:
|
|
||||||
active_index = ob.active_shape_key_index
|
|
||||||
key_blocks = ob.data.shape_keys.key_blocks
|
|
||||||
active_block = key_blocks[active_index]
|
|
||||||
basis_block = key_blocks[0]
|
|
||||||
if active_index > 0:
|
|
||||||
for i, skp in enumerate(active_block.data):
|
|
||||||
point = ob.data.points[i]
|
|
||||||
if not point.select: continue
|
|
||||||
mix = skp.co.lerp(basis_block.data[i].co, self.factor)
|
|
||||||
skp.co = mix
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
for i, skp in enumerate(active_block.data):
|
|
||||||
base = get_lattice_point_original_position(ob.data, i)
|
|
||||||
# Resetting the Basis shape
|
|
||||||
mix = basis_block.data[i].co.lerp(base, self.factor)
|
|
||||||
basis_block.data[i].co = mix
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Otherwise, reset the actual points.
|
|
||||||
for i in range(len(ob.data.points)):
|
|
||||||
point = ob.data.points[i]
|
|
||||||
if not point.select:
|
|
||||||
continue
|
|
||||||
base = get_lattice_point_original_position(ob.data, i)
|
|
||||||
mix = point.co_deform.lerp(base, self.factor)
|
|
||||||
point.co_deform = base
|
|
||||||
|
|
||||||
bpy.ops.object.mode_set(mode='EDIT')
|
|
||||||
return {'FINISHED'}
|
|
||||||
|
|
||||||
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"
|
||||||
@ -330,7 +205,7 @@ class TWEAKLAT_OT_Delete(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'
|
||||||
bl_category = 'Lattice Magic'
|
bl_category = 'Lattice Magic'
|
||||||
@ -377,10 +252,9 @@ class TWEAKLAT_PT_main(bpy.types.Panel):
|
|||||||
ob_prop_name = "object_"+str(ob_count)
|
ob_prop_name = "object_"+str(ob_count)
|
||||||
|
|
||||||
classes = [
|
classes = [
|
||||||
LATTICE_OT_Reset
|
TWEAKLAT_OT_Create
|
||||||
,TWEAKLAT_OT_Create
|
|
||||||
,TWEAKLAT_OT_Delete
|
,TWEAKLAT_OT_Delete
|
||||||
,TWEAKLAT_PT_main
|
,TWEAKLAT_PT_Main
|
||||||
]
|
]
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
@ -388,11 +262,7 @@ def register():
|
|||||||
for c in classes:
|
for c in classes:
|
||||||
register_class(c)
|
register_class(c)
|
||||||
|
|
||||||
# bpy.types.MESH_MT_shape_key_context_menu.append(draw_shape_key_reset)
|
|
||||||
|
|
||||||
def unregister():
|
def unregister():
|
||||||
from bpy.utils import unregister_class
|
from bpy.utils import unregister_class
|
||||||
for c in reversed(classes):
|
for c in reversed(classes):
|
||||||
unregister_class(c)
|
unregister_class(c)
|
||||||
|
|
||||||
# bpy.types.MESH_MT_shape_key_context_menu.remove(draw_shape_key_reset)
|
|
96
utils.py
Normal file
96
utils.py
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import bpy
|
||||||
|
from mathutils import Vector
|
||||||
|
from typing import List, Tuple
|
||||||
|
|
||||||
|
def clamp(val, _min=0, _max=1) -> float or int:
|
||||||
|
if val < _min:
|
||||||
|
return _min
|
||||||
|
if val > _max:
|
||||||
|
return _max
|
||||||
|
return val
|
||||||
|
|
||||||
|
def get_lattice_vertex_index(lattice: bpy.types.Lattice, xyz: List[int], do_clamp=True) -> int:
|
||||||
|
"""Get the index of a lattice vertex based on its position on the XYZ axes."""
|
||||||
|
|
||||||
|
# The lattice vertex indicies start in the -Y, -X, -Z corner,
|
||||||
|
# increase on X+, then moves to the next row on Y+, then moves up on Z+.
|
||||||
|
res_x, res_y, res_z = lattice.points_u, lattice.points_v, lattice.points_w
|
||||||
|
x, y, z = xyz[:]
|
||||||
|
if do_clamp:
|
||||||
|
x = clamp(x, 0, res_x)
|
||||||
|
y = clamp(y, 0, res_y)
|
||||||
|
z = clamp(z, 0, res_z)
|
||||||
|
|
||||||
|
assert x < res_x and y < res_y and z < res_z, "Error: Lattice vertex xyz index out of bounds"
|
||||||
|
|
||||||
|
index = (z * res_y*res_x) + (y * res_x) + x
|
||||||
|
return index
|
||||||
|
|
||||||
|
def get_lattice_vertex_xyz_position(lattice: bpy.types.Lattice, index: int) -> (int, int, int):
|
||||||
|
res_x, res_y, res_z = lattice.points_u, lattice.points_v, lattice.points_w
|
||||||
|
|
||||||
|
x = 0
|
||||||
|
remaining = index
|
||||||
|
z = int(remaining / (res_x*res_y))
|
||||||
|
remaining -= z*(res_x*res_y)
|
||||||
|
y = int(remaining / res_x)
|
||||||
|
remaining -= y*res_x
|
||||||
|
x = remaining # Maybe need to add or subtract 1 here?
|
||||||
|
|
||||||
|
return (x, y, z)
|
||||||
|
|
||||||
|
def get_lattice_point_original_position(lattice: bpy.types.Lattice, index: int) -> Vector:
|
||||||
|
"""Reset a lattice vertex to its original position."""
|
||||||
|
start_vec = Vector((-0.5, -0.5, -0.5))
|
||||||
|
if lattice.points_u == 1:
|
||||||
|
start_vec[0] = 0
|
||||||
|
if lattice.points_v == 1:
|
||||||
|
start_vec[1] = 0
|
||||||
|
if lattice.points_w == 1:
|
||||||
|
start_vec[2] = 0
|
||||||
|
|
||||||
|
unit_u = 1/(lattice.points_u-1)
|
||||||
|
unit_v = 1/(lattice.points_v-1)
|
||||||
|
unit_w = 1/(lattice.points_w-1)
|
||||||
|
|
||||||
|
unit_vec = Vector((unit_u, unit_v, unit_w))
|
||||||
|
xyz_vec = Vector(get_lattice_vertex_xyz_position(lattice, index))
|
||||||
|
|
||||||
|
return start_vec + xyz_vec*unit_vec
|
||||||
|
|
||||||
|
def simple_driver(owner: bpy.types.ID, driver_path: str, target_ob: bpy.types.Object, data_path: str, array_index=-1) -> bpy.types.Driver:
|
||||||
|
if array_index > -1:
|
||||||
|
owner.driver_remove(driver_path, array_index)
|
||||||
|
driver = owner.driver_add(driver_path, array_index).driver
|
||||||
|
else:
|
||||||
|
owner.driver_remove(driver_path)
|
||||||
|
driver = owner.driver_add(driver_path).driver
|
||||||
|
|
||||||
|
driver.expression = 'var'
|
||||||
|
var = driver.variables.new()
|
||||||
|
var.targets[0].id = target_ob
|
||||||
|
var.targets[0].data_path = data_path
|
||||||
|
|
||||||
|
return driver
|
||||||
|
|
||||||
|
|
||||||
|
def bounding_box(points) -> Tuple[Vector, Vector]:
|
||||||
|
""" Return two vectors representing the lowest and highest coordinates of
|
||||||
|
a the bounding box of the passed points.
|
||||||
|
"""
|
||||||
|
|
||||||
|
lowest = points[0].copy()
|
||||||
|
highest = points[0].copy()
|
||||||
|
for p in points:
|
||||||
|
for i in range(len(p)):
|
||||||
|
if p[i] < lowest[i]:
|
||||||
|
lowest[i] = p[i]
|
||||||
|
if p[i] > highest[i]:
|
||||||
|
highest[i] = p[i]
|
||||||
|
|
||||||
|
return lowest, highest
|
||||||
|
|
||||||
|
def bounding_box_center(points) -> Vector:
|
||||||
|
"""Find the bounding box center of some points."""
|
||||||
|
bbox_low, bbox_high = bounding_box(points)
|
||||||
|
return bbox_low + (bbox_high-bbox_low)/2
|
Loading…
Reference in New Issue
Block a user