736 lines
17 KiB
Python
736 lines
17 KiB
Python
# ##### BEGIN GPL LICENSE BLOCK #####
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License
|
|
# as published by the Free Software Foundation; either version 2
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software Foundation,
|
|
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
#
|
|
# ##### END GPL LICENSE BLOCK #####
|
|
|
|
|
|
bl_info = {
|
|
"name": " Easy Lattice",
|
|
"author": "Kursad Karatas / Mechanical Mustache Labs",
|
|
"version": ( 1, 0, 1 ),
|
|
"blender": ( 2, 80,0 ),
|
|
"location": "View3D > EZ Lattice",
|
|
"description": "Create a lattice for shape editing",
|
|
"warning": "",
|
|
"doc_url": "",
|
|
"tracker_url": "",
|
|
"category": "Mesh"}
|
|
|
|
import bpy
|
|
import copy
|
|
|
|
import bmesh
|
|
from bpy.app.handlers import persistent
|
|
from bpy.props import (EnumProperty, FloatProperty, FloatVectorProperty,
|
|
IntProperty, StringProperty, BoolProperty)
|
|
from bpy.types import Operator
|
|
from bpy_extras.object_utils import AddObjectHelper, object_data_add
|
|
from mathutils import Matrix, Vector
|
|
import mathutils
|
|
import numpy as np
|
|
|
|
|
|
LAT_TYPES= ( ( 'KEY_LINEAR', 'KEY_LINEAR', '' ), ( 'KEY_CARDINAL', 'KEY_CARDINAL', '' ), ( 'KEY_BSPLINE', 'KEY_BSPLINE', '' ) )
|
|
|
|
OP_TYPES= ( ( 'NEW', 'NEW', '' ), ( 'APPLY', 'APPLY', '' ), ( 'CLEAR', 'CLEAR', '' ) )
|
|
|
|
def defineSceneProps():
|
|
bpy.types.Scene.ezlattice_object = StringProperty(name="Object2Operate",
|
|
description="Object to be operated on",
|
|
default="")
|
|
|
|
bpy.types.Scene.ezlattice_objects = StringProperty(name="Objects2Operate",
|
|
description="Objects to be operated on",
|
|
default="")
|
|
|
|
bpy.types.Scene.ezlattice_mode = StringProperty(name="CurrentMode",
|
|
default="")
|
|
|
|
bpy.types.Scene.ezlattice_lattice = StringProperty(name="LatticeObName",
|
|
default="")
|
|
|
|
bpy.types.Scene.ezlattice_flag = BoolProperty(name="LatticeFlag", default=False)
|
|
|
|
def defineObjectProps():
|
|
|
|
bpy.types.Object.ezlattice_flag = BoolProperty(name="LatticeFlag", default=False)
|
|
|
|
bpy.types.Object.ezlattice_controller = StringProperty(name="LatticeController", default="")
|
|
|
|
bpy.types.Object.ezlattice_modifier = StringProperty(name="latticeModifier", default="")
|
|
|
|
def objectMode():
|
|
|
|
if isEditMode():
|
|
bpy.ops.object.mode_set(mode="OBJECT")
|
|
|
|
else:
|
|
return True
|
|
|
|
return
|
|
|
|
def isEditMode():
|
|
"""Check to see we are in edit mode
|
|
"""
|
|
|
|
try:
|
|
if bpy.context.object.mode == "EDIT":
|
|
return True
|
|
|
|
else:
|
|
return False
|
|
|
|
except:
|
|
print("No active mesh object")
|
|
|
|
|
|
|
|
def isObjectMode():
|
|
|
|
if bpy.context.object.mode == "OBJECT":
|
|
return True
|
|
|
|
else:
|
|
return False
|
|
|
|
|
|
def setMode(mode=None):
|
|
|
|
if mode:
|
|
bpy.ops.object.mode_set(mode=mode)
|
|
|
|
def curMode():
|
|
|
|
return bpy.context.object.mode
|
|
|
|
def editMode():
|
|
|
|
if not isEditMode():
|
|
bpy.ops.object.mode_set(mode="EDIT")
|
|
|
|
else:
|
|
return True
|
|
|
|
return
|
|
|
|
|
|
def setSelectActiveObject(context, obj):
|
|
|
|
if context.mode == 'OBJECT':
|
|
|
|
bpy.ops.object.select_all(action='DESELECT')
|
|
context.view_layer.objects.active = obj
|
|
obj.select_set(True)
|
|
|
|
|
|
def getObject(name=None):
|
|
try:
|
|
ob=[o for o in bpy.data.objects if o.name == name][0]
|
|
return ob
|
|
|
|
except:
|
|
return None
|
|
|
|
|
|
|
|
def buildTrnScl_WorldMat( obj ):
|
|
# This function builds a real world matrix that encodes translation and scale and it leaves out the rotation matrix
|
|
# The rotation is applied at obejct level if there is any
|
|
loc,rot,scl=obj.matrix_world.decompose()
|
|
mat_trans = mathutils.Matrix.Translation( loc)
|
|
|
|
mat_scale = mathutils.Matrix.Scale( scl[0], 4, ( 1, 0, 0 ) )
|
|
mat_scale @= mathutils.Matrix.Scale( scl[1], 4, ( 0, 1, 0 ) )
|
|
mat_scale @= mathutils.Matrix.Scale( scl[2], 4, ( 0, 0, 1 ) )
|
|
|
|
mat_final = mat_trans @ mat_scale
|
|
|
|
return mat_final
|
|
|
|
def getSelectedVerts(context):
|
|
"""
|
|
https://devtalk.blender.org/t/foreach-get-for-selected-vertex-indices/7712/6
|
|
|
|
v_sel = np.empty(len(me.vertices), dtype=bool)
|
|
me.vertices.foreach_get('select', v_sel)
|
|
|
|
sel_idx, = np.where(v_sel)
|
|
unsel_idx, = np.where(np.invert(v_sel))
|
|
|
|
"""
|
|
|
|
obj = context.active_object
|
|
m=obj.matrix_world
|
|
|
|
count=len(obj.data.vertices)
|
|
shape = (count, 3)
|
|
|
|
|
|
if obj.type=='MESH':
|
|
me = obj.data
|
|
bm = bmesh.from_edit_mesh(me)
|
|
|
|
verts=[m@v.co for v in bm.verts if v.select]
|
|
|
|
return np.array(verts, dtype=np.float32)
|
|
|
|
|
|
def getSelectedVertsNumPy(context):
|
|
obj = context.active_object
|
|
|
|
if obj.type=='MESH':
|
|
|
|
obj.update_from_editmode()
|
|
|
|
#BMESH
|
|
me = obj.data
|
|
bm = bmesh.from_edit_mesh(me)
|
|
|
|
verts_selected=np.array([v.select for v in bm.verts])
|
|
|
|
count=len(obj.data.vertices)
|
|
shape = (count, 3)
|
|
|
|
co = np.empty(count*3, dtype=np.float32)
|
|
|
|
obj.data.vertices.foreach_get("co",co)
|
|
|
|
|
|
co.shape=shape
|
|
|
|
return co[verts_selected]
|
|
|
|
|
|
def findSelectedVertsBBoxNumPy(context):
|
|
|
|
|
|
verts=getSelectedVerts(context)
|
|
|
|
x_min = verts[:,0].min()
|
|
y_min = verts[:,1].min()
|
|
z_min = verts[:,2].min()
|
|
|
|
x_max = verts[:,0].max()
|
|
y_max = verts[:,1].max()
|
|
z_max = verts[:,2].max()
|
|
|
|
|
|
x_avg = verts[:,0].mean()
|
|
y_avg = verts[:,1].mean()
|
|
z_avg = verts[:,2].mean()
|
|
|
|
middle=Vector( ( (x_min+x_max)/2,
|
|
(y_min+y_max)/2,
|
|
(z_min+z_max)/2 )
|
|
)
|
|
|
|
bbox= [ np.array([x_max,y_max,z_max], dtype=np.float32),
|
|
np.array([x_min, y_min, z_min], dtype=np.float32),
|
|
np.array([x_avg, y_avg, z_avg], dtype=np.float32),
|
|
np.array(middle)
|
|
]
|
|
|
|
return bbox
|
|
|
|
|
|
def addSelected2VertGrp():
|
|
|
|
C=bpy.context
|
|
|
|
grp=C.active_object.vertex_groups.new(name=".templatticegrp")
|
|
bpy.ops.object.vertex_group_assign()
|
|
bpy.ops.object.vertex_group_set_active( group = grp.name )
|
|
|
|
def removetempVertGrp():
|
|
|
|
C=bpy.context
|
|
|
|
grp=[g for g in C.active_object.vertex_groups if ".templatticegrp" in g.name]
|
|
|
|
if grp:
|
|
for g in grp:
|
|
bpy.context.object.vertex_groups.active_index = g.index
|
|
bpy.ops.object.vertex_group_remove(all=False, all_unlocked=False)
|
|
|
|
|
|
def cleanupLatticeObjects(context):
|
|
|
|
cur_obj=context.active_object
|
|
|
|
try:
|
|
lats=[l for l in bpy.data.objects if ".latticetemp" in l.name]
|
|
|
|
if lats:
|
|
for l in lats:
|
|
setSelectActiveObject(context, l)
|
|
bpy.data.objects.remove(l)
|
|
|
|
bpy.ops.ed.undo_push()
|
|
|
|
setSelectActiveObject(context, cur_obj)
|
|
|
|
except:
|
|
print("no cleanup")
|
|
|
|
|
|
|
|
|
|
def cleanupLatticeModifier(context):
|
|
|
|
scn=context.scene
|
|
|
|
obj_operated_name=scn.ezlattice_object
|
|
obj_operated=getObject(obj_operated_name)
|
|
|
|
curmode=curMode()
|
|
|
|
temp_mod=None
|
|
|
|
obj=None
|
|
|
|
if obj_operated:
|
|
|
|
if context.active_object.type=='LATTICE':
|
|
setMode('OBJECT')
|
|
setSelectActiveObject(context, obj_operated )
|
|
|
|
|
|
temp_mod=[m for m in obj_operated.modifiers if ".LatticeModTemp" in m.name]
|
|
|
|
obj=obj_operated
|
|
|
|
else:
|
|
temp_mod=[m for m in context.object.modifiers if ".LatticeModTemp" in m.name]
|
|
obj=context.object
|
|
|
|
if temp_mod:
|
|
for m in temp_mod:
|
|
bpy.ops.object.modifier_remove(modifier=m.name)
|
|
|
|
setMode(curmode)
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
def cleanupApplyPre(context):
|
|
|
|
scn=context.scene
|
|
|
|
obj_operated_name=scn.ezlattice_object
|
|
|
|
obj_operated=getObject(obj_operated_name)
|
|
|
|
cur_mode=curMode()
|
|
|
|
|
|
if obj_operated:
|
|
|
|
if context.active_object.type=='LATTICE':
|
|
setMode('OBJECT')
|
|
setSelectActiveObject(context, obj_operated )
|
|
|
|
|
|
temp_mod=[m for m in obj_operated.modifiers if ".LatticeModTemp" in m.name]
|
|
|
|
|
|
lats=[l for l in bpy.data.objects if ".latticetemp" in l.name]
|
|
|
|
cur_obj=context.active_object
|
|
|
|
curmode=curMode()
|
|
|
|
|
|
if isEditMode():
|
|
objectMode()
|
|
|
|
if temp_mod:
|
|
for m in temp_mod:
|
|
if m.object:
|
|
bpy.ops.object.modifier_apply(modifier=m.name)
|
|
|
|
else:
|
|
bpy.ops.object.modifier_remove(modifier=m.name)
|
|
|
|
if lats:
|
|
for l in lats:
|
|
|
|
bpy.data.objects.remove(l)
|
|
|
|
bpy.ops.ed.undo_push()
|
|
|
|
setSelectActiveObject(context, cur_obj)
|
|
|
|
setMode(curmode)
|
|
bpy.ops.object.mode_set(mode=curmode)
|
|
|
|
|
|
|
|
def createLatticeObject(context, loc=Vector((0,0,0)), scale=Vector((1,1,1)),
|
|
name=".latticetemp", divisions=[], interp="KEY_BSPLINE"):
|
|
|
|
C=bpy.context
|
|
lat_name=name+"_"+C.object.name
|
|
|
|
lat = bpy.data.lattices.new( lat_name )
|
|
ob = bpy.data.objects.new( lat_name, lat )
|
|
ob.data.use_outside=True
|
|
ob.data.points_u=divisions[0]
|
|
ob.data.points_v=divisions[1]
|
|
ob.data.points_w=divisions[2]
|
|
|
|
ob.data.interpolation_type_u = interp
|
|
ob.data.interpolation_type_v = interp
|
|
ob.data.interpolation_type_w = interp
|
|
|
|
scene = context.scene
|
|
scene.collection.objects.link(ob)
|
|
ob.location=loc
|
|
ob.scale = scale
|
|
|
|
return ob
|
|
|
|
|
|
def applyLatticeModifier():
|
|
|
|
try:
|
|
temp_mod=[m for m in bpy.context.object.modifiers if ".LatticeModTemp" in m.name]
|
|
|
|
if temp_mod:
|
|
for m in temp_mod:
|
|
bpy.ops.object.modifier_apply(modifier=m.name)
|
|
|
|
except:
|
|
print("no modifiers")
|
|
|
|
|
|
def addLatticeModifier(context, lat,vrtgrp=""):
|
|
|
|
bpy.ops.object.modifier_add(type='LATTICE')
|
|
|
|
bpy.context.object.modifiers['Lattice'].name=".LatticeModTemp"
|
|
|
|
bpy.context.object.modifiers[".LatticeModTemp"].object=lat
|
|
bpy.context.object.modifiers[".LatticeModTemp"].vertex_group=vrtgrp
|
|
|
|
bpy.context.object.modifiers[".LatticeModTemp"].show_in_editmode = True
|
|
bpy.context.object.modifiers[".LatticeModTemp"].show_on_cage = True
|
|
|
|
|
|
|
|
def newLatticeOp(obj, context,self):
|
|
|
|
scn=context.scene
|
|
|
|
|
|
if scn.ezlattice_flag:
|
|
|
|
applyLatticeOp(obj, context)
|
|
|
|
scn.ezlattice_flag=False
|
|
scn.ezlattice_mode=""
|
|
|
|
return
|
|
|
|
cur_obj=obj
|
|
scn.ezlattice_object=cur_obj.name
|
|
|
|
scn.ezlattice_mode=curMode()
|
|
|
|
cleanupApplyPre(context)
|
|
|
|
removetempVertGrp()
|
|
|
|
addSelected2VertGrp()
|
|
|
|
bbox=findSelectedVertsBBoxNumPy(context)
|
|
scale=bbox[0]-bbox[1]
|
|
|
|
loc_crs=Vector((bbox[3][0],bbox[3][1],bbox[3][2]))
|
|
|
|
lat=createLatticeObject(context, loc=loc_crs, scale=scale,
|
|
divisions=[self.lat_u,self.lat_w,self.lat_m],
|
|
interp=self.lat_type )
|
|
|
|
scn.ezlattice_lattice=lat.name
|
|
|
|
|
|
setSelectActiveObject(context, cur_obj)
|
|
|
|
addLatticeModifier(context, lat=lat, vrtgrp=".templatticegrp")
|
|
|
|
objectMode()
|
|
setSelectActiveObject(context, lat)
|
|
|
|
editMode()
|
|
|
|
scn.ezlattice_flag=True
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
def resetHelperAttrbs(context):
|
|
|
|
scn=context.scene
|
|
|
|
scn.ezlattice_mode=""
|
|
scn.ezlattice_object=""
|
|
scn.ezlattice_objects=""
|
|
scn.ezlattice_lattice=""
|
|
|
|
|
|
def applyLatticeOp(obj, context):
|
|
|
|
scn=context.scene
|
|
|
|
if scn.ezlattice_mode=="EDIT":
|
|
|
|
|
|
obj_operated_name=scn.ezlattice_object
|
|
|
|
obj_operated=getObject(obj_operated_name)
|
|
|
|
cur_mode=curMode()
|
|
|
|
if obj_operated:
|
|
|
|
if context.active_object.type=='LATTICE':
|
|
setMode('OBJECT')
|
|
setSelectActiveObject(context, obj_operated )
|
|
|
|
temp_mod=[m for m in obj_operated.modifiers if ".LatticeModTemp" in m.name]
|
|
|
|
|
|
lats=[l for l in bpy.data.objects if ".latticetemp" in l.name]
|
|
|
|
cur_obj=context.active_object
|
|
|
|
curmode=curMode()
|
|
|
|
|
|
if isEditMode():
|
|
objectMode()
|
|
|
|
if temp_mod:
|
|
for m in temp_mod:
|
|
if m.object:
|
|
bpy.ops.object.modifier_apply(modifier=m.name)
|
|
|
|
else:
|
|
bpy.ops.object.modifier_remove(modifier=m.name)
|
|
|
|
if lats:
|
|
for l in lats:
|
|
|
|
bpy.data.objects.remove(l)
|
|
|
|
bpy.ops.ed.undo_push()
|
|
|
|
setSelectActiveObject(context, cur_obj)
|
|
|
|
setMode(curmode)
|
|
bpy.ops.object.mode_set(mode=curmode)
|
|
|
|
|
|
removetempVertGrp()
|
|
|
|
|
|
resetHelperAttrbs(context)
|
|
|
|
return
|
|
|
|
def clearLatticeOps(obj, context):
|
|
|
|
scn=context.scene
|
|
|
|
if scn.ezlattice_mode=="EDIT":
|
|
cleanupLatticeModifier(context)
|
|
|
|
|
|
|
|
cleanupLatticeObjects(context)
|
|
|
|
resetHelperAttrbs(context)
|
|
return
|
|
|
|
|
|
def objectCheck(context):
|
|
|
|
if not [bool(o) for o in context.selected_objects if o.type!="MESH"]:
|
|
return True
|
|
|
|
else:
|
|
return False
|
|
|
|
|
|
class OBJECT_MT_EZLatticeOperator(bpy.types.Menu):
|
|
bl_label = "Easy Lattice Menu"
|
|
bl_idname = "LAT_MT_ezlattice"
|
|
|
|
def draw(self, context):
|
|
|
|
scn=context.scene
|
|
layout = self.layout
|
|
|
|
layout.operator_context = 'INVOKE_REGION_WIN'
|
|
|
|
if scn.ezlattice_flag:
|
|
op="Apply"
|
|
layout.operator("object.ezlattice_new", text=op)
|
|
|
|
else:
|
|
op="New"
|
|
layout.operator("object.ezlattice_new", text=op)
|
|
|
|
|
|
|
|
class OBJECT_OT_EZLatticeCall(Operator):
|
|
"""UV Operator description"""
|
|
bl_idname = "object.ezlatticecall"
|
|
bl_label = "Easy Lattice"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return bool(context.active_object)
|
|
|
|
def execute(self, context):
|
|
return {'FINISHED'}
|
|
|
|
class OBJECT_OT_EZLatticeOperatorNew(Operator):
|
|
|
|
"""Tooltip"""
|
|
bl_idname = "object.ezlattice_new"
|
|
bl_label = "Easy Lattice Creator"
|
|
bl_space_type = "VIEW_3D"
|
|
bl_region_type = "TOOLS"
|
|
|
|
operation: StringProperty(options={'HIDDEN'})
|
|
isnew: BoolProperty(default=False, options={'HIDDEN'})
|
|
isobjectmode: BoolProperty(default=False, options={'HIDDEN'})
|
|
|
|
op_type: EnumProperty( name = "Operation Type", items = OP_TYPES)
|
|
|
|
lat_u: IntProperty( name = "Lattice u", default = 3 )
|
|
lat_w: IntProperty( name = "Lattice w", default = 3 )
|
|
lat_m: IntProperty( name = "Lattice m", default = 3 )
|
|
|
|
|
|
lat_type: EnumProperty( name = "Lattice Type", items = LAT_TYPES)
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return (context.mode == 'EDIT_MESH') or (context.mode == 'EDIT_LATTICE') or (context.object.type=='LATTICE')
|
|
|
|
def execute(self, context):
|
|
|
|
cur_obj=context.active_object
|
|
objs=context.selected_objects
|
|
scn=context.scene
|
|
|
|
if self.isnew and isEditMode():
|
|
|
|
self.isobjectmode=False
|
|
scn.ezlattice_objects=""
|
|
scn.ezlattice_mode="EDIT"
|
|
|
|
newLatticeOp(cur_obj,context,self)
|
|
self.isnew=False
|
|
|
|
else:
|
|
newLatticeOp(cur_obj,context,self)
|
|
|
|
return {'FINISHED'}
|
|
|
|
def invoke( self, context, event ):
|
|
wm = context.window_manager
|
|
|
|
cur_obj=context.active_object
|
|
|
|
objs=context.selected_objects
|
|
|
|
scn=context.scene
|
|
|
|
if isEditMode() and cur_obj.type=='MESH':
|
|
|
|
self.isnew=True
|
|
if not scn.ezlattice_flag:
|
|
return wm.invoke_props_dialog( self )
|
|
|
|
else:
|
|
newLatticeOp(cur_obj,context,self)
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
def menu_draw(self, context):
|
|
self.layout.separator()
|
|
self.layout.operator_context = 'INVOKE_REGION_WIN'
|
|
|
|
self.layout.menu(OBJECT_MT_EZLatticeOperator.bl_idname)
|
|
|
|
|
|
def draw_item(self, context):
|
|
layout = self.layout
|
|
layout.menu(OBJECT_MT_EZLatticeOperator.bl_idname)
|
|
|
|
classes = (
|
|
OBJECT_OT_EZLatticeOperatorNew,
|
|
OBJECT_MT_EZLatticeOperator,
|
|
)
|
|
|
|
|
|
@persistent
|
|
def resetProps(dummy):
|
|
|
|
context=bpy.context
|
|
|
|
resetHelperAttrbs(context)
|
|
|
|
return
|
|
|
|
def register():
|
|
|
|
defineSceneProps()
|
|
|
|
bpy.app.handlers.load_post.append(resetProps)
|
|
|
|
for cls in classes:
|
|
bpy.utils.register_class(cls)
|
|
|
|
bpy.types.VIEW3D_MT_edit_mesh_context_menu.prepend(menu_draw)
|
|
|
|
bpy.types.VIEW3D_MT_edit_lattice.prepend(menu_draw)
|
|
bpy.types.VIEW3D_MT_edit_lattice_context_menu.prepend(menu_draw)
|
|
|
|
|
|
def unregister():
|
|
|
|
|
|
for cls in classes:
|
|
bpy.utils.unregister_class(cls)
|
|
|
|
bpy.types.VIEW3D_MT_edit_mesh_context_menu.remove(menu_draw)
|
|
|
|
bpy.types.VIEW3D_MT_edit_lattice.remove(menu_draw)
|
|
bpy.types.VIEW3D_MT_edit_lattice_context_menu.remove(menu_draw)
|
|
|
|
if __name__ == "__main__":
|
|
register()
|