1 # SPDX-FileCopyrightText: 2017-2023 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 # --------------------------- LATTICE ALONG SURFACE -------------------------- #
6 # -------------------------------- version 0.3 ------------------------------- #
8 # Automatically generate and assign a lattice that follows the active surface. #
10 # (c) Alessandro Zomparelli #
13 # http://www.co-de-it.com/ #
15 # ############################################################################ #
19 from bpy
.types
import Operator
20 from bpy
.props
import (BoolProperty
, StringProperty
, FloatProperty
)
21 from mathutils
import Vector
26 def not_in(element
, grid
):
35 def grid_from_mesh(mesh
, swap_uv
):
50 if len(faces_grid
) == 0:
51 # for first loop check all vertices
52 verts_candidates
= bm
.verts
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
)]
60 for vert
in verts_candidates
:
61 if len(vert
.link_faces
) == 1: # check if corner vertex
63 verts_loop
.append(vert
.index
)
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
72 verts_loop
.append(vert
.index
)
75 running_loop
= len(verts_loop
) > 0
78 bm
.verts
.ensure_lookup_table()
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
:
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
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
:
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
109 for edge
in link_edges
:
110 for vert
in edge
.verts
:
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
:
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
)
128 if verts_loop
[-1] == id or verts_loop
[-1] == verts_loop
[0]:
131 verts_grid
.append(verts_loop
)
132 edges_grid
.append(edges_loop
)
133 faces_grid
.append(faces_loop
)
135 if len(faces_loop
) == 0:
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(
153 description
="Automatically set the Lattice as parent"
155 flipNormals
: BoolProperty(
158 description
="Flip normals direction"
160 swapUV
: BoolProperty(
163 description
="Flip grid's U and V"
165 flipU
: BoolProperty(
168 description
="Flip grid's U")
170 flipV
: BoolProperty(
173 description
="Flip grid's V"
175 flipW
: BoolProperty(
178 description
="Flip grid's W"
180 use_groups
: BoolProperty(
183 description
="Use active Vertex Group for lattice's thickness"
185 high_quality_lattice
: BoolProperty(
188 description
="Increase the the subdivisions in normal direction for a "
189 "more correct result"
191 hide_lattice
: BoolProperty(
194 description
="Automatically hide the Lattice object"
196 scale_x
: FloatProperty(
201 description
="Object scale"
203 scale_y
: FloatProperty(
204 name
="Scale Y", default
=1,
207 description
="Object scale"
209 scale_z
: FloatProperty(
214 description
="Object scale"
216 thickness
: FloatProperty(
221 description
="Lattice thickness"
223 displace
: FloatProperty(
228 description
="Lattice displace"
230 weight_factor
: FloatProperty(
236 description
="Thickness factor to use for zero vertex group influence"
242 def poll(cls
, context
):
243 try: return context
.object.mode
== 'OBJECT'
246 def draw(self
, context
):
248 col
= layout
.column(align
=True)
249 col
.label(text
="Thickness:")
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
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
261 row
.prop(self
, "use_groups")
264 row
.prop(self
, "weight_factor")
266 col
.label(text
="Scale:")
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
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
278 col
.label(text
="Flip:")
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")
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")
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.")
302 for o
in context
.selected_objects
:
303 if o
.name
!= grid_obj
.name
and o
.type in \
304 ('MESH', 'CURVE', 'SURFACE', 'FONT'):
309 obj_dim
= obj
.dimensions
310 obj_me
= simple_to_mesh(obj
)#obj.to_mesh(bpy.context.depsgraph, apply_modifiers=True)
312 self
.report({'ERROR'}, "The object to deform is not valid. Only "
313 "Mesh, Curve, Surface and Font objects are allowed.")
315 self
.grid_object
= grid_obj
.name
316 self
.source_object
= obj
.name
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
335 self
.report({'ERROR'}, "Maximum resolution allowed for Lattice is 64")
339 min = Vector((0, 0, 0))
340 max = Vector((0, 0, 0))
342 for v
in obj_me
.vertices
:
344 vert
= obj
.matrix_world
@ v0
345 if vert
[0] < min[0] or first
:
347 if vert
[1] < min[1] or first
:
349 if vert
[2] < min[2] or first
:
351 if vert
[0] > max[0] or first
:
353 if vert
[1] > max[1] or first
:
355 if vert
[2] > max[2] or first
:
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
))
374 context
.view_layer
.objects
.active
= obj
375 lattice_modifier
= context
.object.modifiers
.new("", 'LATTICE')
376 lattice_modifier
.object = lattice
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(
389 nv
= len(verts_grid
[0])
391 scale_normal
= self
.thickness
394 lattice
.data
.points_u
= nu
395 lattice
.data
.points_v
= nv
396 lattice
.data
.points_w
= nw
398 vg
= temp_grid_obj
.vertex_groups
.active
399 weight_factor
= self
.weight_factor
405 weight_influence
= vg
.weight(verts_grid
[i
][j
])
408 weight_influence
= weight_influence
* (1 - weight_factor
) + weight_factor
409 displace
= weight_influence
* scale_normal
* bb
.z
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
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
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
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")
442 self
.report({'ERROR'}, "The grid mesh is not correct")
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
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
463 lattice
.select_set(False)
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')
473 bpy
.data
.meshes
.remove(grid_mesh
)
474 bpy
.data
.meshes
.remove(obj_me
)