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 ------------------------------- #
21 # Automatically generate and assign a lattice that follows the active surface. #
23 # (c) Alessandro Zomparelli #
26 # http://www.co-de-it.com/ #
28 # ############################################################################ #
32 from bpy
.types
import Operator
33 from bpy
.props
import (BoolProperty
, StringProperty
, FloatProperty
)
34 from mathutils
import Vector
39 def not_in(element
, grid
):
48 def grid_from_mesh(mesh
, swap_uv
):
63 if len(faces_grid
) == 0:
64 # for first loop check all vertices
65 verts_candidates
= bm
.verts
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
)]
73 for vert
in verts_candidates
:
74 if len(vert
.link_faces
) == 1: # check if corner vertex
76 verts_loop
.append(vert
.index
)
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
85 verts_loop
.append(vert
.index
)
88 running_loop
= len(verts_loop
) > 0
91 bm
.verts
.ensure_lookup_table()
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
:
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
:
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
122 for edge
in link_edges
:
123 for vert
in edge
.verts
:
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
:
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
)
141 if verts_loop
[-1] == id or verts_loop
[-1] == verts_loop
[0]:
144 verts_grid
.append(verts_loop
)
145 edges_grid
.append(edges_loop
)
146 faces_grid
.append(faces_loop
)
148 if len(faces_loop
) == 0:
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(
166 description
="Automatically set the Lattice as parent"
168 flipNormals
: BoolProperty(
171 description
="Flip normals direction"
173 swapUV
: BoolProperty(
176 description
="Flip grid's U and V"
178 flipU
: BoolProperty(
181 description
="Flip grid's U")
183 flipV
: BoolProperty(
186 description
="Flip grid's V"
188 flipW
: BoolProperty(
191 description
="Flip grid's W"
193 use_groups
: BoolProperty(
196 description
="Use active Vertex Group for lattice's thickness"
198 high_quality_lattice
: BoolProperty(
201 description
="Increase the the subdivisions in normal direction for a "
202 "more correct result"
204 hide_lattice
: BoolProperty(
207 description
="Automatically hide the Lattice object"
209 scale_x
: FloatProperty(
214 description
="Object scale"
216 scale_y
: FloatProperty(
217 name
="Scale Y", default
=1,
220 description
="Object scale"
222 scale_z
: FloatProperty(
227 description
="Object scale"
229 thickness
: FloatProperty(
234 description
="Lattice thickness"
236 displace
: FloatProperty(
241 description
="Lattice displace"
247 def poll(cls
, context
):
248 try: return bpy
.context
.object.mode
== 'OBJECT'
251 def draw(self
, context
):
253 col
= layout
.column(align
=True)
254 col
.label(text
="Thickness:")
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
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
266 row
.prop(self
, "use_groups")
268 col
.label(text
="Scale:")
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
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
280 col
.label(text
="Flip:")
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")
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")
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.")
304 for o
in bpy
.context
.selected_objects
:
305 if o
.name
!= grid_obj
.name
and o
.type in \
306 ('MESH', 'CURVE', 'SURFACE', 'FONT'):
311 obj_dim
= obj
.dimensions
312 obj_me
= simple_to_mesh(obj
)#obj.to_mesh(bpy.context.depsgraph, apply_modifiers=True)
314 self
.report({'ERROR'}, "The object to deform is not valid. Only "
315 "Mesh, Curve, Surface and Font objects are allowed.")
317 self
.grid_object
= grid_obj
.name
318 self
.source_object
= obj
.name
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
338 self
.report({'ERROR'}, "Maximum resolution allowed for Lattice is 64")
342 min = Vector((0, 0, 0))
343 max = Vector((0, 0, 0))
345 for v
in obj_me
.vertices
:
347 vert
= obj
.matrix_world
@ v0
348 if vert
[0] < min[0] or first
:
350 if vert
[1] < min[1] or first
:
352 if vert
[2] < min[2] or first
:
354 if vert
[0] > max[0] or first
:
356 if vert
[1] > max[1] or first
:
358 if vert
[2] > max[2] or first
:
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
))
377 bpy
.context
.view_layer
.objects
.active
= obj
378 bpy
.ops
.object.modifier_add(type='LATTICE')
379 obj
.modifiers
[-1].object = lattice
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(
394 nv
= len(verts_grid
[0])
396 scale_normal
= self
.thickness
399 lattice
.data
.points_u
= nu
400 lattice
.data
.points_v
= nv
401 lattice
.data
.points_w
= nw
407 displace
= temp_grid_obj
.vertex_groups
.active
.weight(
408 verts_grid
[i
][j
]) * scale_normal
* bb
.z
410 displace
= 0#scale_normal * bb.z
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
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
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
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")
443 self
.report({'ERROR'}, "The grid mesh is not correct")
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
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
464 lattice
.select_set(False)
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')
474 bpy
.data
.meshes
.remove(grid_mesh
)
475 bpy
.data
.meshes
.remove(obj_me
)