1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
21 "name": " Easy Lattice",
22 "author": "Kursad Karatas / Mechanical Mustache Labs",
23 "version": ( 1, 0, 1 ),
24 "blender": ( 2, 80,0 ),
25 "location": "View3D > EZ Lattice",
26 "description": "Create a lattice for shape editing",
36 from bpy
.app
.handlers
import persistent
37 from bpy
.props
import (EnumProperty
, FloatProperty
, FloatVectorProperty
,
38 IntProperty
, StringProperty
, BoolProperty
)
39 from bpy
.types
import Operator
40 from bpy_extras
.object_utils
import AddObjectHelper
, object_data_add
41 from mathutils
import Matrix
, Vector
46 LAT_TYPES
= ( ( 'KEY_LINEAR', 'KEY_LINEAR', '' ), ( 'KEY_CARDINAL', 'KEY_CARDINAL', '' ), ( 'KEY_BSPLINE', 'KEY_BSPLINE', '' ) )
48 OP_TYPES
= ( ( 'NEW', 'NEW', '' ), ( 'APPLY', 'APPLY', '' ), ( 'CLEAR', 'CLEAR', '' ) )
50 def defineSceneProps():
51 bpy
.types
.Scene
.ezlattice_object
= StringProperty(name
="Object2Operate",
52 description
="Object to be operated on",
55 bpy
.types
.Scene
.ezlattice_objects
= StringProperty(name
="Objects2Operate",
56 description
="Objects to be operated on",
59 bpy
.types
.Scene
.ezlattice_mode
= StringProperty(name
="CurrentMode",
62 bpy
.types
.Scene
.ezlattice_lattice
= StringProperty(name
="LatticeObName",
65 bpy
.types
.Scene
.ezlattice_flag
= BoolProperty(name
="LatticeFlag", default
=False)
67 def defineObjectProps():
69 bpy
.types
.Object
.ezlattice_flag
= BoolProperty(name
="LatticeFlag", default
=False)
71 bpy
.types
.Object
.ezlattice_controller
= StringProperty(name
="LatticeController", default
="")
73 bpy
.types
.Object
.ezlattice_modifier
= StringProperty(name
="latticeModifier", default
="")
78 bpy
.ops
.object.mode_set(mode
="OBJECT")
86 """Check to see we are in edit mode
90 if bpy
.context
.object.mode
== "EDIT":
97 print("No active mesh object")
103 if bpy
.context
.object.mode
== "OBJECT":
110 def setMode(mode
=None):
113 bpy
.ops
.object.mode_set(mode
=mode
)
117 return bpy
.context
.object.mode
122 bpy
.ops
.object.mode_set(mode
="EDIT")
130 def setSelectActiveObject(context
, obj
):
132 if context
.mode
== 'OBJECT':
134 bpy
.ops
.object.select_all(action
='DESELECT')
135 context
.view_layer
.objects
.active
= obj
139 def getObject(name
=None):
141 ob
=[o
for o
in bpy
.data
.objects
if o
.name
== name
][0]
149 def buildTrnScl_WorldMat( obj
):
150 # This function builds a real world matrix that encodes translation and scale and it leaves out the rotation matrix
151 # The rotation is applied at obejct level if there is any
152 loc
,rot
,scl
=obj
.matrix_world
.decompose()
153 mat_trans
= mathutils
.Matrix
.Translation( loc
)
155 mat_scale
= mathutils
.Matrix
.Scale( scl
[0], 4, ( 1, 0, 0 ) )
156 mat_scale
@= mathutils
.Matrix
.Scale( scl
[1], 4, ( 0, 1, 0 ) )
157 mat_scale
@= mathutils
.Matrix
.Scale( scl
[2], 4, ( 0, 0, 1 ) )
159 mat_final
= mat_trans
@ mat_scale
163 def getSelectedVerts(context
):
165 https://devtalk.blender.org/t/foreach-get-for-selected-vertex-indices/7712/6
167 v_sel = np.empty(len(me.vertices), dtype=bool)
168 me.vertices.foreach_get('select', v_sel)
170 sel_idx, = np.where(v_sel)
171 unsel_idx, = np.where(np.invert(v_sel))
175 obj
= context
.active_object
178 count
=len(obj
.data
.vertices
)
184 bm
= bmesh
.from_edit_mesh(me
)
186 verts
=[m
@v.co
for v
in bm
.verts
if v
.select
]
188 return np
.array(verts
, dtype
=np
.float32
)
191 def getSelectedVertsNumPy(context
):
192 obj
= context
.active_object
196 obj
.update_from_editmode()
200 bm
= bmesh
.from_edit_mesh(me
)
202 verts_selected
=np
.array([v
.select
for v
in bm
.verts
])
204 count
=len(obj
.data
.vertices
)
207 co
= np
.empty(count
*3, dtype
=np
.float32
)
209 obj
.data
.vertices
.foreach_get("co",co
)
214 return co
[verts_selected
]
217 def findSelectedVertsBBoxNumPy(context
):
220 verts
=getSelectedVerts(context
)
222 x_min
= verts
[:,0].min()
223 y_min
= verts
[:,1].min()
224 z_min
= verts
[:,2].min()
226 x_max
= verts
[:,0].max()
227 y_max
= verts
[:,1].max()
228 z_max
= verts
[:,2].max()
231 x_avg
= verts
[:,0].mean()
232 y_avg
= verts
[:,1].mean()
233 z_avg
= verts
[:,2].mean()
235 middle
=Vector( ( (x_min
+x_max
)/2,
240 bbox
= [ np
.array([x_max
,y_max
,z_max
], dtype
=np
.float32
),
241 np
.array([x_min
, y_min
, z_min
], dtype
=np
.float32
),
242 np
.array([x_avg
, y_avg
, z_avg
], dtype
=np
.float32
),
249 def addSelected2VertGrp():
253 grp
=C
.active_object
.vertex_groups
.new(name
=".templatticegrp")
254 bpy
.ops
.object.vertex_group_assign()
255 bpy
.ops
.object.vertex_group_set_active( group
= grp
.name
)
257 def removetempVertGrp():
261 grp
=[g
for g
in C
.active_object
.vertex_groups
if ".templatticegrp" in g
.name
]
265 bpy
.context
.object.vertex_groups
.active_index
= g
.index
266 bpy
.ops
.object.vertex_group_remove(all
=False, all_unlocked
=False)
269 def cleanupLatticeObjects(context
):
271 cur_obj
=context
.active_object
274 lats
=[l
for l
in bpy
.data
.objects
if ".latticetemp" in l
.name
]
278 setSelectActiveObject(context
, l
)
279 bpy
.data
.objects
.remove(l
)
281 bpy
.ops
.ed
.undo_push()
283 setSelectActiveObject(context
, cur_obj
)
291 def cleanupLatticeModifier(context
):
295 obj_operated_name
=scn
.ezlattice_object
296 obj_operated
=getObject(obj_operated_name
)
306 if context
.active_object
.type=='LATTICE':
308 setSelectActiveObject(context
, obj_operated
)
311 temp_mod
=[m
for m
in obj_operated
.modifiers
if ".LatticeModTemp" in m
.name
]
316 temp_mod
=[m
for m
in context
.object.modifiers
if ".LatticeModTemp" in m
.name
]
321 bpy
.ops
.object.modifier_remove(modifier
=m
.name
)
329 def cleanupApplyPre(context
):
333 obj_operated_name
=scn
.ezlattice_object
335 obj_operated
=getObject(obj_operated_name
)
342 if context
.active_object
.type=='LATTICE':
344 setSelectActiveObject(context
, obj_operated
)
347 temp_mod
=[m
for m
in obj_operated
.modifiers
if ".LatticeModTemp" in m
.name
]
350 lats
=[l
for l
in bpy
.data
.objects
if ".latticetemp" in l
.name
]
352 cur_obj
=context
.active_object
363 bpy
.ops
.object.modifier_apply(modifier
=m
.name
)
366 bpy
.ops
.object.modifier_remove(modifier
=m
.name
)
371 bpy
.data
.objects
.remove(l
)
373 bpy
.ops
.ed
.undo_push()
375 setSelectActiveObject(context
, cur_obj
)
378 bpy
.ops
.object.mode_set(mode
=curmode
)
382 def createLatticeObject(context
, loc
=Vector((0,0,0)), scale
=Vector((1,1,1)),
383 name
=".latticetemp", divisions
=[], interp
="KEY_BSPLINE"):
386 lat_name
=name
+"_"+C
.object.name
388 lat
= bpy
.data
.lattices
.new( lat_name
)
389 ob
= bpy
.data
.objects
.new( lat_name
, lat
)
390 ob
.data
.use_outside
=True
391 ob
.data
.points_u
=divisions
[0]
392 ob
.data
.points_v
=divisions
[1]
393 ob
.data
.points_w
=divisions
[2]
395 ob
.data
.interpolation_type_u
= interp
396 ob
.data
.interpolation_type_v
= interp
397 ob
.data
.interpolation_type_w
= interp
399 scene
= context
.scene
400 scene
.collection
.objects
.link(ob
)
407 def applyLatticeModifier():
410 temp_mod
=[m
for m
in bpy
.context
.object.modifiers
if ".LatticeModTemp" in m
.name
]
414 bpy
.ops
.object.modifier_apply(modifier
=m
.name
)
417 print("no modifiers")
420 def addLatticeModifier(context
, lat
,vrtgrp
=""):
422 bpy
.ops
.object.modifier_add(type='LATTICE')
424 bpy
.context
.object.modifiers
['Lattice'].name
=".LatticeModTemp"
426 bpy
.context
.object.modifiers
[".LatticeModTemp"].object=lat
427 bpy
.context
.object.modifiers
[".LatticeModTemp"].vertex_group
=vrtgrp
429 bpy
.context
.object.modifiers
[".LatticeModTemp"].show_in_editmode
= True
430 bpy
.context
.object.modifiers
[".LatticeModTemp"].show_on_cage
= True
434 def newLatticeOp(obj
, context
,self
):
439 if scn
.ezlattice_flag
:
441 applyLatticeOp(obj
, context
)
443 scn
.ezlattice_flag
=False
444 scn
.ezlattice_mode
=""
449 scn
.ezlattice_object
=cur_obj
.name
451 scn
.ezlattice_mode
=curMode()
453 cleanupApplyPre(context
)
457 addSelected2VertGrp()
459 bbox
=findSelectedVertsBBoxNumPy(context
)
460 scale
=bbox
[0]-bbox
[1]
462 loc_crs
=Vector((bbox
[3][0],bbox
[3][1],bbox
[3][2]))
464 lat
=createLatticeObject(context
, loc
=loc_crs
, scale
=scale
,
465 divisions
=[self
.lat_u
,self
.lat_w
,self
.lat_m
],
466 interp
=self
.lat_type
)
468 scn
.ezlattice_lattice
=lat
.name
471 setSelectActiveObject(context
, cur_obj
)
473 addLatticeModifier(context
, lat
=lat
, vrtgrp
=".templatticegrp")
476 setSelectActiveObject(context
, lat
)
480 scn
.ezlattice_flag
=True
487 def resetHelperAttrbs(context
):
491 scn
.ezlattice_mode
=""
492 scn
.ezlattice_object
=""
493 scn
.ezlattice_objects
=""
494 scn
.ezlattice_lattice
=""
497 def applyLatticeOp(obj
, context
):
501 if scn
.ezlattice_mode
=="EDIT":
504 obj_operated_name
=scn
.ezlattice_object
506 obj_operated
=getObject(obj_operated_name
)
512 if context
.active_object
.type=='LATTICE':
514 setSelectActiveObject(context
, obj_operated
)
516 temp_mod
=[m
for m
in obj_operated
.modifiers
if ".LatticeModTemp" in m
.name
]
519 lats
=[l
for l
in bpy
.data
.objects
if ".latticetemp" in l
.name
]
521 cur_obj
=context
.active_object
532 bpy
.ops
.object.modifier_apply(modifier
=m
.name
)
535 bpy
.ops
.object.modifier_remove(modifier
=m
.name
)
540 bpy
.data
.objects
.remove(l
)
542 bpy
.ops
.ed
.undo_push()
544 setSelectActiveObject(context
, cur_obj
)
547 bpy
.ops
.object.mode_set(mode
=curmode
)
553 resetHelperAttrbs(context
)
557 def clearLatticeOps(obj
, context
):
561 if scn
.ezlattice_mode
=="EDIT":
562 cleanupLatticeModifier(context
)
566 cleanupLatticeObjects(context
)
568 resetHelperAttrbs(context
)
572 def objectCheck(context
):
574 if not [bool(o
) for o
in context
.selected_objects
if o
.type!="MESH"]:
581 class OBJECT_MT_EZLatticeOperator(bpy
.types
.Menu
):
582 bl_label
= "Easy Lattice Menu"
583 bl_idname
= "LAT_MT_ezlattice"
585 def draw(self
, context
):
590 layout
.operator_context
= 'INVOKE_REGION_WIN'
592 if scn
.ezlattice_flag
:
594 layout
.operator("object.ezlattice_new", text
=op
)
598 layout
.operator("object.ezlattice_new", text
=op
)
602 class OBJECT_OT_EZLatticeCall(Operator
):
603 """UV Operator description"""
604 bl_idname
= "object.ezlatticecall"
605 bl_label
= "Easy Lattice"
606 bl_options
= {'REGISTER', 'UNDO'}
610 def poll(cls
, context
):
611 return bool(context
.active_object
)
613 def execute(self
, context
):
616 class OBJECT_OT_EZLatticeOperatorNew(Operator
):
619 bl_idname
= "object.ezlattice_new"
620 bl_label
= "Easy Lattice Creator"
621 bl_space_type
= "VIEW_3D"
622 bl_region_type
= "TOOLS"
624 operation
: StringProperty(options
={'HIDDEN'})
625 isnew
: BoolProperty(default
=False, options
={'HIDDEN'})
626 isobjectmode
: BoolProperty(default
=False, options
={'HIDDEN'})
628 op_type
: EnumProperty( name
= "Operation Type", items
= OP_TYPES
)
630 lat_u
: IntProperty( name
= "Lattice u", default
= 3 )
631 lat_w
: IntProperty( name
= "Lattice w", default
= 3 )
632 lat_m
: IntProperty( name
= "Lattice m", default
= 3 )
635 lat_type
: EnumProperty( name
= "Lattice Type", items
= LAT_TYPES
)
638 def poll(cls
, context
):
639 return (context
.mode
== 'EDIT_MESH') or (context
.mode
== 'EDIT_LATTICE') or (context
.object.type=='LATTICE')
641 def execute(self
, context
):
643 cur_obj
=context
.active_object
644 objs
=context
.selected_objects
647 if self
.isnew
and isEditMode():
649 self
.isobjectmode
=False
650 scn
.ezlattice_objects
=""
651 scn
.ezlattice_mode
="EDIT"
653 newLatticeOp(cur_obj
,context
,self
)
657 newLatticeOp(cur_obj
,context
,self
)
661 def invoke( self
, context
, event
):
662 wm
= context
.window_manager
664 cur_obj
=context
.active_object
666 objs
=context
.selected_objects
670 if isEditMode() and cur_obj
.type=='MESH':
673 if not scn
.ezlattice_flag
:
674 return wm
.invoke_props_dialog( self
)
677 newLatticeOp(cur_obj
,context
,self
)
682 def menu_draw(self
, context
):
683 self
.layout
.separator()
684 self
.layout
.operator_context
= 'INVOKE_REGION_WIN'
686 self
.layout
.menu(OBJECT_MT_EZLatticeOperator
.bl_idname
)
689 def draw_item(self
, context
):
691 layout
.menu(OBJECT_MT_EZLatticeOperator
.bl_idname
)
694 OBJECT_OT_EZLatticeOperatorNew
,
695 OBJECT_MT_EZLatticeOperator
,
700 def resetProps(dummy
):
704 resetHelperAttrbs(context
)
712 bpy
.app
.handlers
.load_post
.append(resetProps
)
715 bpy
.utils
.register_class(cls
)
717 bpy
.types
.VIEW3D_MT_edit_mesh_context_menu
.prepend(menu_draw
)
719 bpy
.types
.VIEW3D_MT_edit_lattice
.prepend(menu_draw
)
720 bpy
.types
.VIEW3D_MT_edit_lattice_context_menu
.prepend(menu_draw
)
727 bpy
.utils
.unregister_class(cls
)
729 bpy
.types
.VIEW3D_MT_edit_mesh_context_menu
.remove(menu_draw
)
731 bpy
.types
.VIEW3D_MT_edit_lattice
.remove(menu_draw
)
732 bpy
.types
.VIEW3D_MT_edit_lattice_context_menu
.remove(menu_draw
)
734 if __name__
== "__main__":