UI: Move Extensions repositories popover to header
[blender-addons-contrib.git] / mesh_easy_lattice.py
blobcbe63f26755706cd8a2b7511c05e8bb789bca1c4
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 #####
20 bl_info = {
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",
27 "warning": "",
28 "doc_url": "",
29 "tracker_url": "",
30 "category": "Mesh"}
32 import bpy
33 import copy
35 import bmesh
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
42 import mathutils
43 import numpy as np
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",
53 default="")
55 bpy.types.Scene.ezlattice_objects = StringProperty(name="Objects2Operate",
56 description="Objects to be operated on",
57 default="")
59 bpy.types.Scene.ezlattice_mode = StringProperty(name="CurrentMode",
60 default="")
62 bpy.types.Scene.ezlattice_lattice = StringProperty(name="LatticeObName",
63 default="")
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="")
75 def objectMode():
77 if isEditMode():
78 bpy.ops.object.mode_set(mode="OBJECT")
80 else:
81 return True
83 return
85 def isEditMode():
86 """Check to see we are in edit mode
87 """
89 try:
90 if bpy.context.object.mode == "EDIT":
91 return True
93 else:
94 return False
96 except:
97 print("No active mesh object")
101 def isObjectMode():
103 if bpy.context.object.mode == "OBJECT":
104 return True
106 else:
107 return False
110 def setMode(mode=None):
112 if mode:
113 bpy.ops.object.mode_set(mode=mode)
115 def curMode():
117 return bpy.context.object.mode
119 def editMode():
121 if not isEditMode():
122 bpy.ops.object.mode_set(mode="EDIT")
124 else:
125 return True
127 return
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
136 obj.select_set(True)
139 def getObject(name=None):
140 try:
141 ob=[o for o in bpy.data.objects if o.name == name][0]
142 return ob
144 except:
145 return None
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
161 return mat_final
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
176 m=obj.matrix_world
178 count=len(obj.data.vertices)
179 shape = (count, 3)
182 if obj.type=='MESH':
183 me = obj.data
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
194 if obj.type=='MESH':
196 obj.update_from_editmode()
198 #BMESH
199 me = obj.data
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)
205 shape = (count, 3)
207 co = np.empty(count*3, dtype=np.float32)
209 obj.data.vertices.foreach_get("co",co)
212 co.shape=shape
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,
236 (y_min+y_max)/2,
237 (z_min+z_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),
243 np.array(middle)
246 return bbox
249 def addSelected2VertGrp():
251 C=bpy.context
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():
259 C=bpy.context
261 grp=[g for g in C.active_object.vertex_groups if ".templatticegrp" in g.name]
263 if grp:
264 for g in grp:
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
273 try:
274 lats=[l for l in bpy.data.objects if ".latticetemp" in l.name]
276 if lats:
277 for l in lats:
278 setSelectActiveObject(context, l)
279 bpy.data.objects.remove(l)
281 bpy.ops.ed.undo_push()
283 setSelectActiveObject(context, cur_obj)
285 except:
286 print("no cleanup")
291 def cleanupLatticeModifier(context):
293 scn=context.scene
295 obj_operated_name=scn.ezlattice_object
296 obj_operated=getObject(obj_operated_name)
298 curmode=curMode()
300 temp_mod=None
302 obj=None
304 if obj_operated:
306 if context.active_object.type=='LATTICE':
307 setMode('OBJECT')
308 setSelectActiveObject(context, obj_operated )
311 temp_mod=[m for m in obj_operated.modifiers if ".LatticeModTemp" in m.name]
313 obj=obj_operated
315 else:
316 temp_mod=[m for m in context.object.modifiers if ".LatticeModTemp" in m.name]
317 obj=context.object
319 if temp_mod:
320 for m in temp_mod:
321 bpy.ops.object.modifier_remove(modifier=m.name)
323 setMode(curmode)
325 return True
327 return False
329 def cleanupApplyPre(context):
331 scn=context.scene
333 obj_operated_name=scn.ezlattice_object
335 obj_operated=getObject(obj_operated_name)
337 cur_mode=curMode()
340 if obj_operated:
342 if context.active_object.type=='LATTICE':
343 setMode('OBJECT')
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
354 curmode=curMode()
357 if isEditMode():
358 objectMode()
360 if temp_mod:
361 for m in temp_mod:
362 if m.object:
363 bpy.ops.object.modifier_apply(modifier=m.name)
365 else:
366 bpy.ops.object.modifier_remove(modifier=m.name)
368 if lats:
369 for l in lats:
371 bpy.data.objects.remove(l)
373 bpy.ops.ed.undo_push()
375 setSelectActiveObject(context, cur_obj)
377 setMode(curmode)
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"):
385 C=bpy.context
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)
401 ob.location=loc
402 ob.scale = scale
404 return ob
407 def applyLatticeModifier():
409 try:
410 temp_mod=[m for m in bpy.context.object.modifiers if ".LatticeModTemp" in m.name]
412 if temp_mod:
413 for m in temp_mod:
414 bpy.ops.object.modifier_apply(modifier=m.name)
416 except:
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):
436 scn=context.scene
439 if scn.ezlattice_flag:
441 applyLatticeOp(obj, context)
443 scn.ezlattice_flag=False
444 scn.ezlattice_mode=""
446 return
448 cur_obj=obj
449 scn.ezlattice_object=cur_obj.name
451 scn.ezlattice_mode=curMode()
453 cleanupApplyPre(context)
455 removetempVertGrp()
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")
475 objectMode()
476 setSelectActiveObject(context, lat)
478 editMode()
480 scn.ezlattice_flag=True
485 return
487 def resetHelperAttrbs(context):
489 scn=context.scene
491 scn.ezlattice_mode=""
492 scn.ezlattice_object=""
493 scn.ezlattice_objects=""
494 scn.ezlattice_lattice=""
497 def applyLatticeOp(obj, context):
499 scn=context.scene
501 if scn.ezlattice_mode=="EDIT":
504 obj_operated_name=scn.ezlattice_object
506 obj_operated=getObject(obj_operated_name)
508 cur_mode=curMode()
510 if obj_operated:
512 if context.active_object.type=='LATTICE':
513 setMode('OBJECT')
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
523 curmode=curMode()
526 if isEditMode():
527 objectMode()
529 if temp_mod:
530 for m in temp_mod:
531 if m.object:
532 bpy.ops.object.modifier_apply(modifier=m.name)
534 else:
535 bpy.ops.object.modifier_remove(modifier=m.name)
537 if lats:
538 for l in lats:
540 bpy.data.objects.remove(l)
542 bpy.ops.ed.undo_push()
544 setSelectActiveObject(context, cur_obj)
546 setMode(curmode)
547 bpy.ops.object.mode_set(mode=curmode)
550 removetempVertGrp()
553 resetHelperAttrbs(context)
555 return
557 def clearLatticeOps(obj, context):
559 scn=context.scene
561 if scn.ezlattice_mode=="EDIT":
562 cleanupLatticeModifier(context)
566 cleanupLatticeObjects(context)
568 resetHelperAttrbs(context)
569 return
572 def objectCheck(context):
574 if not [bool(o) for o in context.selected_objects if o.type!="MESH"]:
575 return True
577 else:
578 return False
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):
587 scn=context.scene
588 layout = self.layout
590 layout.operator_context = 'INVOKE_REGION_WIN'
592 if scn.ezlattice_flag:
593 op="Apply"
594 layout.operator("object.ezlattice_new", text=op)
596 else:
597 op="New"
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'}
609 @classmethod
610 def poll(cls, context):
611 return bool(context.active_object)
613 def execute(self, context):
614 return {'FINISHED'}
616 class OBJECT_OT_EZLatticeOperatorNew(Operator):
618 """Tooltip"""
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)
637 @classmethod
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
645 scn=context.scene
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)
654 self.isnew=False
656 else:
657 newLatticeOp(cur_obj,context,self)
659 return {'FINISHED'}
661 def invoke( self, context, event ):
662 wm = context.window_manager
664 cur_obj=context.active_object
666 objs=context.selected_objects
668 scn=context.scene
670 if isEditMode() and cur_obj.type=='MESH':
672 self.isnew=True
673 if not scn.ezlattice_flag:
674 return wm.invoke_props_dialog( self )
676 else:
677 newLatticeOp(cur_obj,context,self)
679 return {'FINISHED'}
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):
690 layout = self.layout
691 layout.menu(OBJECT_MT_EZLatticeOperator.bl_idname)
693 classes = (
694 OBJECT_OT_EZLatticeOperatorNew,
695 OBJECT_MT_EZLatticeOperator,
699 @persistent
700 def resetProps(dummy):
702 context=bpy.context
704 resetHelperAttrbs(context)
706 return
708 def register():
710 defineSceneProps()
712 bpy.app.handlers.load_post.append(resetProps)
714 for cls in classes:
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)
723 def unregister():
726 for cls in classes:
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__":
735 register()