DUAL_MESH.PY 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  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. # --------------------------------- DUAL MESH -------------------------------- #
  19. # -------------------------------- version 0.3 ------------------------------- #
  20. # #
  21. # Convert a generic mesh to its dual. With open meshes it can get some wired #
  22. # effect on the borders. #
  23. # #
  24. # (c) Alessandro Zomparelli #
  25. # (2017) #
  26. # #
  27. # http://www.co-de-it.com/ #
  28. # #
  29. # ############################################################################ #
  30. import bpy
  31. from bpy.types import Operator
  32. from bpy.props import (
  33. BoolProperty,
  34. EnumProperty,
  35. )
  36. import bmesh
  37. from .utils import *
  38. class dual_mesh_tessellated(Operator):
  39. bl_idname = "object.dual_mesh_tessellated"
  40. bl_label = "Dual Mesh"
  41. bl_description = ("Generate a polygonal mesh using Tessellate. (Non-destructive)")
  42. bl_options = {'REGISTER', 'UNDO'}
  43. apply_modifiers : BoolProperty(
  44. name="Apply Modifiers",
  45. default=True,
  46. description="Apply object's modifiers"
  47. )
  48. source_faces : EnumProperty(
  49. items=[
  50. ('QUAD', 'Quad Faces', ''),
  51. ('TRI', 'Triangles', '')],
  52. name="Source Faces",
  53. description="Source polygons",
  54. default="QUAD",
  55. options={'LIBRARY_EDITABLE'}
  56. )
  57. def execute(self, context):
  58. auto_layer_collection()
  59. ob0 = context.object
  60. name1 = "DualMesh_{}_Component".format(self.source_faces)
  61. # Generate component
  62. if self.source_faces == 'QUAD':
  63. verts = [(0.0, 0.0, 0.0), (0.0, 0.5, 0.0),
  64. (0.0, 1.0, 0.0), (0.5, 1.0, 0.0),
  65. (1.0, 1.0, 0.0), (1.0, 0.5, 0.0),
  66. (1.0, 0.0, 0.0), (0.5, 0.0, 0.0),
  67. (1/3, 1/3, 0.0), (2/3, 2/3, 0.0)]
  68. edges = [(0,1), (1,2), (2,3), (3,4), (4,5), (5,6), (6,7),
  69. (7,0), (1,8), (8,7), (3,9), (9,5), (8,9)]
  70. faces = [(7,8,1,0), (8,9,3,2,1), (9,5,4,3), (9,8,7,6,5)]
  71. else:
  72. verts = [(0.0,0.0,0.0), (0.5,0.0,0.0), (1.0,0.0,0.0), (0.0,1.0,0.0), (0.5,1.0,0.0), (1.0,1.0,0.0)]
  73. edges = [(0,1), (1,2), (2,5), (5,4), (4,3), (3,0), (1,4)]
  74. faces = [(0,1,4,3), (1,2,5,4)]
  75. # check pre-existing component
  76. try:
  77. _verts = [0]*len(verts)*3
  78. __verts = [c for co in verts for c in co]
  79. ob1 = bpy.data.objects[name1]
  80. ob1.data.vertices.foreach_get("co",_verts)
  81. for a, b in zip(_verts, __verts):
  82. if abs(a-b) > 0.0001:
  83. raise ValueError
  84. except:
  85. me = bpy.data.meshes.new("Dual-Mesh") # add a new mesh
  86. me.from_pydata(verts, edges, faces)
  87. me.update(calc_edges=True, calc_edges_loose=True)
  88. if self.source_faces == 'QUAD': n_seams = 8
  89. else: n_seams = 6
  90. for i in range(n_seams): me.edges[i].use_seam = True
  91. ob1 = bpy.data.objects.new(name1, me)
  92. context.collection.objects.link(ob1)
  93. # fix visualization issue
  94. context.view_layer.objects.active = ob1
  95. ob1.select_set(True)
  96. bpy.ops.object.editmode_toggle()
  97. bpy.ops.object.editmode_toggle()
  98. ob1.select_set(False)
  99. # hide component
  100. ob1.hide_select = True
  101. ob1.hide_render = True
  102. ob1.hide_viewport = True
  103. ob = convert_object_to_mesh(ob0,False,False)
  104. ob.name = 'DualMesh'
  105. #ob = bpy.data.objects.new("DualMesh", convert_object_to_mesh(ob0,False,False))
  106. #context.collection.objects.link(ob)
  107. #context.view_layer.objects.active = ob
  108. #ob.select_set(True)
  109. ob.tissue_tessellate.component = ob1
  110. ob.tissue_tessellate.generator = ob0
  111. ob.tissue_tessellate.gen_modifiers = self.apply_modifiers
  112. ob.tissue_tessellate.merge = True
  113. ob.tissue_tessellate.bool_dissolve_seams = True
  114. if self.source_faces == 'TRI': ob.tissue_tessellate.fill_mode = 'FAN'
  115. bpy.ops.object.update_tessellate()
  116. ob.location = ob0.location
  117. ob.matrix_world = ob0.matrix_world
  118. return {'FINISHED'}
  119. def invoke(self, context, event):
  120. return context.window_manager.invoke_props_dialog(self)
  121. class dual_mesh(Operator):
  122. bl_idname = "object.dual_mesh"
  123. bl_label = "Convert to Dual Mesh"
  124. bl_description = ("Convert a generic mesh into a polygonal mesh. (Destructive)")
  125. bl_options = {'REGISTER', 'UNDO'}
  126. quad_method : EnumProperty(
  127. items=[('BEAUTY', 'Beauty',
  128. 'Split the quads in nice triangles, slower method'),
  129. ('FIXED', 'Fixed',
  130. 'Split the quads on the 1st and 3rd vertices'),
  131. ('FIXED_ALTERNATE', 'Fixed Alternate',
  132. 'Split the quads on the 2nd and 4th vertices'),
  133. ('SHORTEST_DIAGONAL', 'Shortest Diagonal',
  134. 'Split the quads based on the distance between the vertices')
  135. ],
  136. name="Quad Method",
  137. description="Method for splitting the quads into triangles",
  138. default="FIXED",
  139. options={'LIBRARY_EDITABLE'}
  140. )
  141. polygon_method : EnumProperty(
  142. items=[
  143. ('BEAUTY', 'Beauty', 'Arrange the new triangles evenly'),
  144. ('CLIP', 'Clip',
  145. 'Split the polygons with an ear clipping algorithm')],
  146. name="Polygon Method",
  147. description="Method for splitting the polygons into triangles",
  148. default="BEAUTY",
  149. options={'LIBRARY_EDITABLE'}
  150. )
  151. preserve_borders : BoolProperty(
  152. name="Preserve Borders",
  153. default=True,
  154. description="Preserve original borders"
  155. )
  156. apply_modifiers : BoolProperty(
  157. name="Apply Modifiers",
  158. default=True,
  159. description="Apply object's modifiers"
  160. )
  161. def execute(self, context):
  162. mode = context.mode
  163. if mode == 'EDIT_MESH':
  164. mode = 'EDIT'
  165. act = context.active_object
  166. if mode != 'OBJECT':
  167. sel = [act]
  168. bpy.ops.object.mode_set(mode='OBJECT')
  169. else:
  170. sel = context.selected_objects
  171. doneMeshes = []
  172. for ob0 in sel:
  173. if ob0.type != 'MESH':
  174. continue
  175. if ob0.data.name in doneMeshes:
  176. continue
  177. ob = ob0
  178. mesh_name = ob0.data.name
  179. # store linked objects
  180. clones = []
  181. n_users = ob0.data.users
  182. count = 0
  183. for o in bpy.data.objects:
  184. if o.type != 'MESH':
  185. continue
  186. if o.data.name == mesh_name:
  187. count += 1
  188. clones.append(o)
  189. if count == n_users:
  190. break
  191. if self.apply_modifiers:
  192. bpy.ops.object.convert(target='MESH')
  193. ob.data = ob.data.copy()
  194. bpy.ops.object.select_all(action='DESELECT')
  195. ob.select_set(True)
  196. context.view_layer.objects.active = ob0
  197. bpy.ops.object.mode_set(mode='EDIT')
  198. # prevent borders erosion
  199. bpy.ops.mesh.select_mode(
  200. use_extend=False, use_expand=False, type='EDGE'
  201. )
  202. bpy.ops.mesh.select_non_manifold(
  203. extend=False, use_wire=False, use_boundary=True,
  204. use_multi_face=False, use_non_contiguous=False,
  205. use_verts=False
  206. )
  207. bpy.ops.mesh.extrude_region_move(
  208. MESH_OT_extrude_region={"mirror": False},
  209. TRANSFORM_OT_translate={"value": (0, 0, 0)}
  210. )
  211. bpy.ops.mesh.select_mode(
  212. use_extend=False, use_expand=False, type='VERT',
  213. action='TOGGLE'
  214. )
  215. bpy.ops.mesh.select_all(action='SELECT')
  216. bpy.ops.mesh.quads_convert_to_tris(
  217. quad_method=self.quad_method, ngon_method=self.polygon_method
  218. )
  219. bpy.ops.mesh.select_all(action='DESELECT')
  220. bpy.ops.object.mode_set(mode='OBJECT')
  221. bpy.ops.object.modifier_add(type='SUBSURF')
  222. ob.modifiers[-1].name = "dual_mesh_subsurf"
  223. while True:
  224. bpy.ops.object.modifier_move_up(modifier="dual_mesh_subsurf")
  225. if ob.modifiers[0].name == "dual_mesh_subsurf":
  226. break
  227. bpy.ops.object.modifier_apply(
  228. apply_as='DATA', modifier='dual_mesh_subsurf'
  229. )
  230. bpy.ops.object.mode_set(mode='EDIT')
  231. bpy.ops.mesh.select_all(action='DESELECT')
  232. verts = ob.data.vertices
  233. bpy.ops.object.mode_set(mode='OBJECT')
  234. verts[-1].select = True
  235. bpy.ops.object.mode_set(mode='EDIT')
  236. bpy.ops.mesh.select_more(use_face_step=False)
  237. bpy.ops.mesh.select_similar(
  238. type='EDGE', compare='EQUAL', threshold=0.01)
  239. bpy.ops.mesh.select_all(action='INVERT')
  240. bpy.ops.mesh.dissolve_verts()
  241. bpy.ops.mesh.select_all(action='DESELECT')
  242. bpy.ops.mesh.select_non_manifold(
  243. extend=False, use_wire=False, use_boundary=True,
  244. use_multi_face=False, use_non_contiguous=False, use_verts=False)
  245. bpy.ops.mesh.select_more()
  246. # find boundaries
  247. bpy.ops.object.mode_set(mode='OBJECT')
  248. bound_v = [v.index for v in ob.data.vertices if v.select]
  249. bound_e = [e.index for e in ob.data.edges if e.select]
  250. bound_p = [p.index for p in ob.data.polygons if p.select]
  251. bpy.ops.object.mode_set(mode='EDIT')
  252. # select quad faces
  253. context.tool_settings.mesh_select_mode = (False, False, True)
  254. bpy.ops.mesh.select_face_by_sides(number=4, extend=False)
  255. # deselect boundaries
  256. bpy.ops.object.mode_set(mode='OBJECT')
  257. for i in bound_v:
  258. context.active_object.data.vertices[i].select = False
  259. for i in bound_e:
  260. context.active_object.data.edges[i].select = False
  261. for i in bound_p:
  262. context.active_object.data.polygons[i].select = False
  263. bpy.ops.object.mode_set(mode='EDIT')
  264. context.tool_settings.mesh_select_mode = (False, False, True)
  265. bpy.ops.mesh.edge_face_add()
  266. context.tool_settings.mesh_select_mode = (True, False, False)
  267. bpy.ops.mesh.select_all(action='DESELECT')
  268. # delete boundaries
  269. bpy.ops.mesh.select_non_manifold(
  270. extend=False, use_wire=True, use_boundary=True,
  271. use_multi_face=False, use_non_contiguous=False, use_verts=True
  272. )
  273. bpy.ops.mesh.delete(type='VERT')
  274. # remove middle vertices
  275. bm = bmesh.from_edit_mesh(ob.data)
  276. for v in bm.verts:
  277. if len(v.link_edges) == 2 and len(v.link_faces) < 3:
  278. v.select = True
  279. # dissolve
  280. bpy.ops.mesh.dissolve_verts()
  281. bpy.ops.mesh.select_all(action='DESELECT')
  282. # remove border faces
  283. if not self.preserve_borders:
  284. bpy.ops.mesh.select_non_manifold(
  285. extend=False, use_wire=False, use_boundary=True,
  286. use_multi_face=False, use_non_contiguous=False, use_verts=False
  287. )
  288. bpy.ops.mesh.select_more()
  289. bpy.ops.mesh.delete(type='FACE')
  290. # clean wires
  291. bpy.ops.mesh.select_non_manifold(
  292. extend=False, use_wire=True, use_boundary=False,
  293. use_multi_face=False, use_non_contiguous=False, use_verts=False
  294. )
  295. bpy.ops.mesh.delete(type='EDGE')
  296. bpy.ops.object.mode_set(mode='OBJECT')
  297. ob0.data.name = mesh_name
  298. doneMeshes.append(mesh_name)
  299. for o in clones:
  300. o.data = ob.data
  301. for o in sel:
  302. o.select_set(True)
  303. context.view_layer.objects.active = act
  304. bpy.ops.object.mode_set(mode=mode)
  305. return {'FINISHED'}