Cleanup: trailing space
[blender-addons.git] / mesh_tissue / dual_mesh.py
blob5b32d46b658783f951bf0c1e4656a18a70e22f05
1 # SPDX-License-Identifier: GPL-2.0-or-later
3 # --------------------------------- DUAL MESH -------------------------------- #
4 # -------------------------------- version 0.3 ------------------------------- #
5 # #
6 # Convert a generic mesh to its dual. With open meshes it can get some wired #
7 # effect on the borders. #
8 # #
9 # (c) Alessandro Zomparelli #
10 # (2017) #
11 # #
12 # http://www.co-de-it.com/ #
13 # #
14 # ############################################################################ #
17 import bpy
18 from bpy.types import Operator
19 from bpy.props import (
20 BoolProperty,
21 EnumProperty,
23 import bmesh
24 from .utils import *
27 class dual_mesh_tessellated(Operator):
28 bl_idname = "object.dual_mesh_tessellated"
29 bl_label = "Dual Mesh"
30 bl_description = ("Generate a polygonal mesh using Tessellate. (Non-destructive)")
31 bl_options = {'REGISTER', 'UNDO'}
33 apply_modifiers : BoolProperty(
34 name="Apply Modifiers",
35 default=True,
36 description="Apply object's modifiers"
39 source_faces : EnumProperty(
40 items=[
41 ('QUAD', 'Quad Faces', ''),
42 ('TRI', 'Triangles', '')],
43 name="Source Faces",
44 description="Triangles works with any geometry." \
45 "Quad option is faster when the object has only Quads",
46 default="TRI",
47 options={'LIBRARY_EDITABLE'}
50 link_component : BoolProperty(
51 name="Editable Component",
52 default=False,
53 description="Add Component Object to the Scene"
56 def execute(self, context):
57 auto_layer_collection()
58 ob0 = context.object
59 name1 = "DualMesh_{}_Component".format(self.source_faces)
60 # Generate component
61 if self.source_faces == 'QUAD':
62 verts = [(1.0, 0.0, 0.0), (0.5, 0.0, 0.0),
63 (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 (2/3, 1/3, 0.0), (1/3, 2/3, 0.0)]
67 edges = [(0,1), (1,2), (2,3), (3,4), (4,5), (5,6), (6,7),
68 (7,0), (1,8), (8,7), (3,9), (9,5), (8,9)]
69 faces = [(7,8,1,0), (8,9,3,2,1), (9,5,4,3), (9,8,7,6,5)]
70 else:
71 verts = [(0.0, 0.0, 0.0), (1.0, 0.0, 0.0),
72 (0.0, 1.0, 0.0), (1.0, 1.0, 0.0),
73 (0.5, 1/3, 0.0), (0.0, 0.5, 0.0),
74 (1.0, 0.5, 0.0), (0.5, 0.0, 0.0)]
75 edges = [(0,5), (1,7), (3,6), (2,3), (2,5), (1,6), (0,7),
76 (4,5), (4,7), (4,6)]
77 faces = [(5,0,7,4), (7,1,6,4), (3,2,5,4,6)]
79 # check pre-existing component
80 try:
81 _verts = [0]*len(verts)*3
82 __verts = [c for co in verts for c in co]
83 ob1 = bpy.data.objects[name1]
84 ob1.data.vertices.foreach_get("co",_verts)
85 for a, b in zip(_verts, __verts):
86 if abs(a-b) > 0.0001:
87 raise ValueError
88 except:
89 me = bpy.data.meshes.new("Dual-Mesh") # add a new mesh
90 me.from_pydata(verts, edges, faces)
91 me.update(calc_edges=True, calc_edges_loose=True)
92 if self.source_faces == 'QUAD': seams = (0,1,2,3,4,5,6,9)
93 else: seams = (0,1,2,3,4,5,7)
94 for i in seams: me.edges[i].use_seam = True
95 ob1 = bpy.data.objects.new(name1, me)
96 # fix visualization issue
97 if self.link_component:
98 context.collection.objects.link(ob1)
99 context.view_layer.objects.active = ob1
100 ob1.select_set(True)
101 bpy.ops.object.editmode_toggle()
102 bpy.ops.object.editmode_toggle()
103 ob1.select_set(False)
104 ob1.hide_render = True
105 ob = convert_object_to_mesh(ob0,False,False)
106 ob.name = 'DualMesh'
107 ob.tissue.tissue_type = 'TESSELLATE'
108 ob.tissue.bool_lock = 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 = 'TRI'
115 bpy.ops.object.tissue_update_tessellate()
116 ob.tissue.bool_lock = False
117 ob.location = ob0.location
118 ob.matrix_world = ob0.matrix_world
119 return {'FINISHED'}
121 def invoke(self, context, event):
122 return context.window_manager.invoke_props_dialog(self)
124 class dual_mesh(Operator):
125 bl_idname = "object.dual_mesh"
126 bl_label = "Convert to Dual Mesh"
127 bl_description = ("Convert a generic mesh into a polygonal mesh. (Destructive)")
128 bl_options = {'REGISTER', 'UNDO'}
130 quad_method : EnumProperty(
131 items=[('BEAUTY', 'Beauty',
132 'Split the quads in nice triangles, slower method'),
133 ('FIXED', 'Fixed',
134 'Split the quads on the 1st and 3rd vertices'),
135 ('FIXED_ALTERNATE', 'Fixed Alternate',
136 'Split the quads on the 2nd and 4th vertices'),
137 ('SHORTEST_DIAGONAL', 'Shortest Diagonal',
138 'Split the quads based on the distance between the vertices')
140 name="Quad Method",
141 description="Method for splitting the quads into triangles",
142 default="FIXED",
143 options={'LIBRARY_EDITABLE'}
145 polygon_method : EnumProperty(
146 items=[
147 ('BEAUTY', 'Beauty', 'Arrange the new triangles evenly'),
148 ('CLIP', 'Clip',
149 'Split the N-gon with an ear clipping algorithm')],
150 name="N-gon Method",
151 description="Method for splitting the N-gons into triangles",
152 default="BEAUTY",
153 options={'LIBRARY_EDITABLE'}
155 preserve_borders : BoolProperty(
156 name="Preserve Borders",
157 default=True,
158 description="Preserve original borders"
160 apply_modifiers : BoolProperty(
161 name="Apply Modifiers",
162 default=True,
163 description="Apply object's modifiers"
166 def execute(self, context):
167 mode = context.mode
168 if mode == 'EDIT_MESH':
169 mode = 'EDIT'
170 act = context.active_object
171 if mode != 'OBJECT':
172 sel = [act]
173 bpy.ops.object.mode_set(mode='OBJECT')
174 else:
175 sel = context.selected_objects
176 doneMeshes = []
178 for ob0 in sel:
179 if ob0.type != 'MESH':
180 continue
181 if ob0.data.name in doneMeshes:
182 continue
183 ob = ob0
184 mesh_name = ob0.data.name
186 # store linked objects
187 clones = []
188 n_users = ob0.data.users
189 count = 0
190 for o in bpy.data.objects:
191 if o.type != 'MESH':
192 continue
193 if o.data.name == mesh_name:
194 count += 1
195 clones.append(o)
196 if count == n_users:
197 break
199 if self.apply_modifiers:
200 bpy.ops.object.convert(target='MESH')
201 ob.data = ob.data.copy()
202 bpy.ops.object.select_all(action='DESELECT')
203 ob.select_set(True)
204 context.view_layer.objects.active = ob0
205 bpy.ops.object.mode_set(mode='EDIT')
207 # prevent borders erosion
208 bpy.ops.mesh.select_mode(
209 use_extend=False, use_expand=False, type='EDGE'
211 bpy.ops.mesh.select_non_manifold(
212 extend=False, use_wire=False, use_boundary=True,
213 use_multi_face=False, use_non_contiguous=False,
214 use_verts=False
216 bpy.ops.mesh.extrude_region_move(
217 MESH_OT_extrude_region={"mirror": False},
218 TRANSFORM_OT_translate={"value": (0, 0, 0)}
221 bpy.ops.mesh.select_mode(
222 use_extend=False, use_expand=False, type='VERT',
223 action='TOGGLE'
225 bpy.ops.mesh.select_all(action='SELECT')
226 bpy.ops.mesh.quads_convert_to_tris(
227 quad_method=self.quad_method, ngon_method=self.polygon_method
229 bpy.ops.mesh.select_all(action='DESELECT')
230 bpy.ops.object.mode_set(mode='OBJECT')
231 bpy.ops.object.modifier_add(type='SUBSURF')
232 ob.modifiers[-1].name = "dual_mesh_subsurf"
233 while True:
234 bpy.ops.object.modifier_move_up(modifier="dual_mesh_subsurf")
235 if ob.modifiers[0].name == "dual_mesh_subsurf":
236 break
238 bpy.ops.object.modifier_apply(modifier='dual_mesh_subsurf')
240 bpy.ops.object.mode_set(mode='EDIT')
241 bpy.ops.mesh.select_all(action='DESELECT')
243 verts = ob.data.vertices
245 bpy.ops.object.mode_set(mode='OBJECT')
246 verts[-1].select = True
247 bpy.ops.object.mode_set(mode='EDIT')
248 bpy.ops.mesh.select_more(use_face_step=False)
250 bpy.ops.mesh.select_similar(
251 type='EDGE', compare='EQUAL', threshold=0.01)
252 bpy.ops.mesh.select_all(action='INVERT')
254 bpy.ops.mesh.dissolve_verts()
255 bpy.ops.mesh.select_all(action='DESELECT')
257 bpy.ops.mesh.select_non_manifold(
258 extend=False, use_wire=False, use_boundary=True,
259 use_multi_face=False, use_non_contiguous=False, use_verts=False)
260 bpy.ops.mesh.select_more()
262 # find boundaries
263 bpy.ops.object.mode_set(mode='OBJECT')
264 bound_v = [v.index for v in ob.data.vertices if v.select]
265 bound_e = [e.index for e in ob.data.edges if e.select]
266 bound_p = [p.index for p in ob.data.polygons if p.select]
267 bpy.ops.object.mode_set(mode='EDIT')
269 # select quad faces
270 context.tool_settings.mesh_select_mode = (False, False, True)
271 bpy.ops.mesh.select_face_by_sides(number=4, extend=False)
273 # deselect boundaries
274 bpy.ops.object.mode_set(mode='OBJECT')
275 for i in bound_v:
276 context.active_object.data.vertices[i].select = False
277 for i in bound_e:
278 context.active_object.data.edges[i].select = False
279 for i in bound_p:
280 context.active_object.data.polygons[i].select = False
282 bpy.ops.object.mode_set(mode='EDIT')
284 context.tool_settings.mesh_select_mode = (False, False, True)
285 bpy.ops.mesh.edge_face_add()
286 context.tool_settings.mesh_select_mode = (True, False, False)
287 bpy.ops.mesh.select_all(action='DESELECT')
289 # delete boundaries
290 bpy.ops.mesh.select_non_manifold(
291 extend=False, use_wire=True, use_boundary=True,
292 use_multi_face=False, use_non_contiguous=False, use_verts=True
294 bpy.ops.mesh.delete(type='VERT')
296 # remove middle vertices
297 bm = bmesh.from_edit_mesh(ob.data)
298 for v in bm.verts:
299 if len(v.link_edges) == 2 and len(v.link_faces) < 3:
300 v.select = True
302 # dissolve
303 bpy.ops.mesh.dissolve_verts()
304 bpy.ops.mesh.select_all(action='DESELECT')
306 # remove border faces
307 if not self.preserve_borders:
308 bpy.ops.mesh.select_non_manifold(
309 extend=False, use_wire=False, use_boundary=True,
310 use_multi_face=False, use_non_contiguous=False, use_verts=False
312 bpy.ops.mesh.select_more()
313 bpy.ops.mesh.delete(type='FACE')
315 # clean wires
316 bpy.ops.mesh.select_non_manifold(
317 extend=False, use_wire=True, use_boundary=False,
318 use_multi_face=False, use_non_contiguous=False, use_verts=False
320 bpy.ops.mesh.delete(type='EDGE')
322 bpy.ops.object.mode_set(mode='OBJECT')
323 ob0.data.name = mesh_name
324 doneMeshes.append(mesh_name)
326 for o in clones:
327 o.data = ob.data
328 bm.free()
330 for o in sel:
331 o.select_set(True)
333 context.view_layer.objects.active = act
334 bpy.ops.object.mode_set(mode=mode)
336 return {'FINISHED'}