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