Merge branch 'blender-v2.92-release'
[blender-addons.git] / mesh_tissue / lattice.py
blob4e3983329fcafc4ed53d2199043739a9eb1fe32c
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 #####
18 # --------------------------- LATTICE ALONG SURFACE -------------------------- #
19 # -------------------------------- version 0.3 ------------------------------- #
20 # #
21 # Automatically generate and assign a lattice that follows the active surface. #
22 # #
23 # (c) Alessandro Zomparelli #
24 # (2017) #
25 # #
26 # http://www.co-de-it.com/ #
27 # #
28 # ############################################################################ #
30 import bpy
31 import bmesh
32 from bpy.types import Operator
33 from bpy.props import (BoolProperty, StringProperty, FloatProperty)
34 from mathutils import Vector
36 from .utils import *
39 def not_in(element, grid):
40 output = True
41 for loop in grid:
42 if element in loop:
43 output = False
44 break
45 return output
48 def grid_from_mesh(mesh, swap_uv):
49 bm = bmesh.new()
50 bm.from_mesh(mesh)
51 verts_grid = []
52 edges_grid = []
53 faces_grid = []
55 running_grid = True
56 while running_grid:
57 verts_loop = []
58 edges_loop = []
59 faces_loop = []
61 # storing first point
62 verts_candidates = []
63 if len(faces_grid) == 0:
64 # for first loop check all vertices
65 verts_candidates = bm.verts
66 else:
67 # for other loops start form the vertices of the first face
68 # the last loop, skipping already used vertices
69 verts_candidates = [v for v in bm.faces[faces_grid[-1][0]].verts if not_in(v.index, verts_grid)]
71 # check for last loop
72 is_last = False
73 for vert in verts_candidates:
74 if len(vert.link_faces) == 1: # check if corner vertex
75 vert.select = True
76 verts_loop.append(vert.index)
77 is_last = True
78 break
80 if not is_last:
81 for vert in verts_candidates:
82 new_link_faces = [f for f in vert.link_faces if not_in(f.index, faces_grid)]
83 if len(new_link_faces) < 2: # check if corner vertex
84 vert.select = True
85 verts_loop.append(vert.index)
86 break
88 running_loop = len(verts_loop) > 0
90 while running_loop:
91 bm.verts.ensure_lookup_table()
92 id = verts_loop[-1]
93 link_edges = bm.verts[id].link_edges
94 # storing second point
95 if len(verts_loop) == 1: # only one vertex stored in the loop
96 if len(faces_grid) == 0: # first loop #
97 edge = link_edges[swap_uv] # chose direction
98 for vert in edge.verts:
99 if vert.index != id:
100 vert.select = True
101 verts_loop.append(vert.index) # new vertex
102 edges_loop.append(edge.index) # chosen edge
103 faces_loop.append(edge.link_faces[0].index) # only one face
104 # edge.link_faces[0].select = True
105 else: # other loops #
106 # start from the edges of the first face of the last loop
107 for edge in bm.faces[faces_grid[-1][0]].edges:
108 # chose an edge starting from the first vertex that is not returning back
109 if bm.verts[verts_loop[0]] in edge.verts and \
110 bm.verts[verts_grid[-1][0]] not in edge.verts:
111 for vert in edge.verts:
112 if vert.index != id:
113 vert.select = True
114 verts_loop.append(vert.index)
115 edges_loop.append(edge.index)
117 for face in edge.link_faces:
118 if not_in(face.index, faces_grid):
119 faces_loop.append(face.index)
120 # continuing the loop
121 else:
122 for edge in link_edges:
123 for vert in edge.verts:
124 store_data = False
125 if not_in(vert.index, verts_grid) and vert.index not in verts_loop:
126 if len(faces_loop) > 0:
127 bm.faces.ensure_lookup_table()
128 if vert not in bm.faces[faces_loop[-1]].verts:
129 store_data = True
130 else:
131 store_data = True
132 if store_data:
133 vert.select = True
134 verts_loop.append(vert.index)
135 edges_loop.append(edge.index)
136 for face in edge.link_faces:
137 if not_in(face.index, faces_grid):
138 faces_loop.append(face.index)
139 break
140 # ending condition
141 if verts_loop[-1] == id or verts_loop[-1] == verts_loop[0]:
142 running_loop = False
144 verts_grid.append(verts_loop)
145 edges_grid.append(edges_loop)
146 faces_grid.append(faces_loop)
148 if len(faces_loop) == 0:
149 running_grid = False
151 return verts_grid, edges_grid, faces_grid
154 class lattice_along_surface(Operator):
155 bl_idname = "object.lattice_along_surface"
156 bl_label = "Lattice along Surface"
157 bl_description = ("Automatically add a Lattice modifier to the selected "
158 "object, adapting it to the active one.\nThe active "
159 "object must be a rectangular grid compatible with the "
160 "Lattice's topology")
161 bl_options = {'REGISTER', 'UNDO'}
163 set_parent : BoolProperty(
164 name="Set Parent",
165 default=True,
166 description="Automatically set the Lattice as parent"
168 flipNormals : BoolProperty(
169 name="Flip Normals",
170 default=False,
171 description="Flip normals direction"
173 swapUV : BoolProperty(
174 name="Swap UV",
175 default=False,
176 description="Flip grid's U and V"
178 flipU : BoolProperty(
179 name="Flip U",
180 default=False,
181 description="Flip grid's U")
183 flipV : BoolProperty(
184 name="Flip V",
185 default=False,
186 description="Flip grid's V"
188 flipW : BoolProperty(
189 name="Flip W",
190 default=False,
191 description="Flip grid's W"
193 use_groups : BoolProperty(
194 name="Vertex Group",
195 default=False,
196 description="Use active Vertex Group for lattice's thickness"
198 high_quality_lattice : BoolProperty(
199 name="High quality",
200 default=True,
201 description="Increase the the subdivisions in normal direction for a "
202 "more correct result"
204 hide_lattice : BoolProperty(
205 name="Hide Lattice",
206 default=True,
207 description="Automatically hide the Lattice object"
209 scale_x : FloatProperty(
210 name="Scale X",
211 default=1,
212 min=0.001,
213 max=1,
214 description="Object scale"
216 scale_y : FloatProperty(
217 name="Scale Y", default=1,
218 min=0.001,
219 max=1,
220 description="Object scale"
222 scale_z : FloatProperty(
223 name="Scale Z",
224 default=1,
225 min=0.001,
226 max=1,
227 description="Object scale"
229 thickness : FloatProperty(
230 name="Thickness",
231 default=1,
232 soft_min=0,
233 soft_max=5,
234 description="Lattice thickness"
236 displace : FloatProperty(
237 name="Displace",
238 default=0,
239 soft_min=-1,
240 soft_max=1,
241 description="Lattice displace"
243 grid_object = ""
244 source_object = ""
246 @classmethod
247 def poll(cls, context):
248 try: return bpy.context.object.mode == 'OBJECT'
249 except: return False
251 def draw(self, context):
252 layout = self.layout
253 col = layout.column(align=True)
254 col.label(text="Thickness:")
255 col.prop(
256 self, "thickness", text="Thickness", icon='NONE', expand=False,
257 slider=True, toggle=False, icon_only=False, event=False,
258 full_event=False, emboss=True, index=-1
260 col.prop(
261 self, "displace", text="Offset", icon='NONE', expand=False,
262 slider=True, toggle=False, icon_only=False, event=False,
263 full_event=False, emboss=True, index=-1
265 row = col.row()
266 row.prop(self, "use_groups")
267 col.separator()
268 col.label(text="Scale:")
269 col.prop(
270 self, "scale_x", text="U", icon='NONE', expand=False,
271 slider=True, toggle=False, icon_only=False, event=False,
272 full_event=False, emboss=True, index=-1
274 col.prop(
275 self, "scale_y", text="V", icon='NONE', expand=False,
276 slider=True, toggle=False, icon_only=False, event=False,
277 full_event=False, emboss=True, index=-1
279 col.separator()
280 col.label(text="Flip:")
281 row = col.row()
282 row.prop(self, "flipU", text="U")
283 row.prop(self, "flipV", text="V")
284 row.prop(self, "flipW", text="W")
285 col.prop(self, "swapUV")
286 col.prop(self, "flipNormals")
287 col.separator()
288 col.label(text="Lattice Options:")
289 col.prop(self, "high_quality_lattice")
290 col.prop(self, "hide_lattice")
291 col.prop(self, "set_parent")
293 def execute(self, context):
294 if self.source_object == self.grid_object == "" or True:
295 if len(bpy.context.selected_objects) != 2:
296 self.report({'ERROR'}, "Please, select two objects")
297 return {'CANCELLED'}
298 grid_obj = bpy.context.object
299 if grid_obj.type not in ('MESH', 'CURVE', 'SURFACE'):
300 self.report({'ERROR'}, "The surface object is not valid. Only Mesh,"
301 "Curve and Surface objects are allowed.")
302 return {'CANCELLED'}
303 obj = None
304 for o in bpy.context.selected_objects:
305 if o.name != grid_obj.name and o.type in \
306 ('MESH', 'CURVE', 'SURFACE', 'FONT'):
307 obj = o
308 o.select_set(False)
309 break
310 try:
311 obj_dim = obj.dimensions
312 obj_me = simple_to_mesh(obj)#obj.to_mesh(bpy.context.depsgraph, apply_modifiers=True)
313 except:
314 self.report({'ERROR'}, "The object to deform is not valid. Only "
315 "Mesh, Curve, Surface and Font objects are allowed.")
316 return {'CANCELLED'}
317 self.grid_object = grid_obj.name
318 self.source_object = obj.name
319 else:
320 grid_obj = bpy.data.objects[self.grid_object]
321 obj = bpy.data.objects[self.source_object]
322 obj_me = simple_to_mesh(obj)# obj.to_mesh(bpy.context.depsgraph, apply_modifiers=True)
323 for o in bpy.context.selected_objects: o.select_set(False)
324 grid_obj.select_set(True)
325 bpy.context.view_layer.objects.active = grid_obj
327 temp_grid_obj = grid_obj.copy()
328 temp_grid_obj.data = simple_to_mesh(grid_obj)
329 grid_mesh = temp_grid_obj.data
330 for v in grid_mesh.vertices:
331 v.co = grid_obj.matrix_world @ v.co
332 grid_mesh.calc_normals()
334 if len(grid_mesh.polygons) > 64 * 64:
335 bpy.data.objects.remove(temp_grid_obj)
336 bpy.context.view_layer.objects.active = obj
337 obj.select_set(True)
338 self.report({'ERROR'}, "Maximum resolution allowed for Lattice is 64")
339 return {'CANCELLED'}
341 # CREATING LATTICE
342 min = Vector((0, 0, 0))
343 max = Vector((0, 0, 0))
344 first = True
345 for v in obj_me.vertices:
346 v0 = v.co.copy()
347 vert = obj.matrix_world @ v0
348 if vert[0] < min[0] or first:
349 min[0] = vert[0]
350 if vert[1] < min[1] or first:
351 min[1] = vert[1]
352 if vert[2] < min[2] or first:
353 min[2] = vert[2]
354 if vert[0] > max[0] or first:
355 max[0] = vert[0]
356 if vert[1] > max[1] or first:
357 max[1] = vert[1]
358 if vert[2] > max[2] or first:
359 max[2] = vert[2]
360 first = False
362 bb = max - min
363 lattice_loc = (max + min) / 2
364 bpy.ops.object.add(type='LATTICE')
365 lattice = bpy.context.active_object
366 lattice.location = lattice_loc
367 lattice.scale = Vector((bb.x / self.scale_x, bb.y / self.scale_y,
368 bb.z / self.scale_z))
370 if bb.x == 0:
371 lattice.scale.x = 1
372 if bb.y == 0:
373 lattice.scale.y = 1
374 if bb.z == 0:
375 lattice.scale.z = 1
377 bpy.context.view_layer.objects.active = obj
378 bpy.ops.object.modifier_add(type='LATTICE')
379 obj.modifiers[-1].object = lattice
381 # set as parent
382 if self.set_parent:
383 obj.select_set(True)
384 lattice.select_set(True)
385 bpy.context.view_layer.objects.active = lattice
386 bpy.ops.object.parent_set(type='LATTICE')
388 # reading grid structure
389 verts_grid, edges_grid, faces_grid = grid_from_mesh(
390 grid_mesh,
391 swap_uv=self.swapUV
393 nu = len(verts_grid)
394 nv = len(verts_grid[0])
395 nw = 2
396 scale_normal = self.thickness
398 try:
399 lattice.data.points_u = nu
400 lattice.data.points_v = nv
401 lattice.data.points_w = nw
402 for i in range(nu):
403 for j in range(nv):
404 for w in range(nw):
405 if self.use_groups:
406 try:
407 displace = temp_grid_obj.vertex_groups.active.weight(
408 verts_grid[i][j]) * scale_normal * bb.z
409 except:
410 displace = 0#scale_normal * bb.z
411 else:
412 displace = scale_normal * bb.z
413 target_point = (grid_mesh.vertices[verts_grid[i][j]].co +
414 grid_mesh.vertices[verts_grid[i][j]].normal *
415 (w + self.displace / 2 - 0.5) * displace) - lattice.location
416 if self.flipW:
417 w = 1 - w
418 if self.flipU:
419 i = nu - i - 1
420 if self.flipV:
421 j = nv - j - 1
423 lattice.data.points[i + j * nu + w * nu * nv].co_deform.x = \
424 target_point.x / bpy.data.objects[lattice.name].scale.x
425 lattice.data.points[i + j * nu + w * nu * nv].co_deform.y = \
426 target_point.y / bpy.data.objects[lattice.name].scale.y
427 lattice.data.points[i + j * nu + w * nu * nv].co_deform.z = \
428 target_point.z / bpy.data.objects[lattice.name].scale.z
430 except:
431 bpy.ops.object.mode_set(mode='OBJECT')
432 temp_grid_obj.select_set(True)
433 lattice.select_set(True)
434 obj.select_set(False)
435 bpy.ops.object.delete(use_global=False)
436 bpy.context.view_layer.objects.active = obj
437 obj.select_set(True)
438 bpy.ops.object.modifier_remove(modifier=obj.modifiers[-1].name)
439 if nu > 64 or nv > 64:
440 self.report({'ERROR'}, "Maximum resolution allowed for Lattice is 64")
441 return {'CANCELLED'}
442 else:
443 self.report({'ERROR'}, "The grid mesh is not correct")
444 return {'CANCELLED'}
446 bpy.ops.object.mode_set(mode='OBJECT')
447 #grid_obj.select_set(True)
448 #lattice.select_set(False)
449 obj.select_set(False)
450 #bpy.ops.object.delete(use_global=False)
451 bpy.context.view_layer.objects.active = lattice
452 lattice.select_set(True)
454 if self.high_quality_lattice:
455 bpy.context.object.data.points_w = 8
456 else:
457 bpy.context.object.data.use_outside = True
459 if self.hide_lattice:
460 bpy.ops.object.hide_view_set(unselected=False)
462 bpy.context.view_layer.objects.active = obj
463 obj.select_set(True)
464 lattice.select_set(False)
466 if self.flipNormals:
467 try:
468 bpy.ops.object.mode_set(mode='EDIT')
469 bpy.ops.mesh.select_all(action='SELECT')
470 bpy.ops.mesh.flip_normals()
471 bpy.ops.object.mode_set(mode='OBJECT')
472 except:
473 pass
474 bpy.data.meshes.remove(grid_mesh)
475 bpy.data.meshes.remove(obj_me)
477 return {'FINISHED'}