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