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