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 ------------------------------- #
22 # Convert a generic mesh to its dual. With open meshes it can get some wired #
23 # effect on the borders. #
25 # (c) Alessandro Zomparelli #
28 # http://www.co-de-it.com/ #
30 # ############################################################################ #
34 from bpy
.types
import Operator
35 from bpy
.props
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",
52 description
="Apply object's modifiers"
55 source_faces
: EnumProperty(
57 ('QUAD', 'Quad Faces', ''),
58 ('TRI', 'Triangles', '')],
60 description
="Source polygons",
62 options
={'LIBRARY_EDITABLE'}
65 def execute(self
, context
):
66 auto_layer_collection()
68 name1
= "DualMesh_{}_Component".format(self
.source_faces
)
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)]
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
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
):
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, calc_loop_triangles
=True)
97 if self
.source_faces
== 'QUAD': n_seams
= 8
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
105 bpy
.ops
.object.editmode_toggle()
106 bpy
.ops
.object.editmode_toggle()
107 ob1
.select_set(False)
109 ob1
.hide_select
= True
110 ob1
.hide_render
= True
111 ob1
.hide_viewport
= True
112 ob
= convert_object_to_mesh(ob0
,False,False)
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
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
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'),
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')
146 description
="Method for splitting the quads into triangles",
148 options
={'LIBRARY_EDITABLE'}
150 polygon_method
: EnumProperty(
152 ('BEAUTY', 'Beauty', 'Arrange the new triangles evenly'),
154 'Split the polygons with an ear clipping algorithm')],
155 name
="Polygon Method",
156 description
="Method for splitting the polygons into triangles",
158 options
={'LIBRARY_EDITABLE'}
160 preserve_borders
: BoolProperty(
161 name
="Preserve Borders",
163 description
="Preserve original borders"
165 apply_modifiers
: BoolProperty(
166 name
="Apply Modifiers",
168 description
="Apply object's modifiers"
171 def execute(self
, context
):
173 if mode
== 'EDIT_MESH':
175 act
= bpy
.context
.active_object
178 bpy
.ops
.object.mode_set(mode
='OBJECT')
180 sel
= bpy
.context
.selected_objects
184 if ob0
.type != 'MESH':
186 if ob0
.data
.name
in doneMeshes
:
189 mesh_name
= ob0
.data
.name
191 # store linked objects
193 n_users
= ob0
.data
.users
195 for o
in bpy
.data
.objects
:
198 if o
.data
.name
== mesh_name
:
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')
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,
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',
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"
239 bpy
.ops
.object.modifier_move_up(modifier
="dual_mesh_subsurf")
240 if ob
.modifiers
[0].name
== "dual_mesh_subsurf":
243 bpy
.ops
.object.modifier_apply(
244 apply_as
='DATA', modifier
='dual_mesh_subsurf'
247 bpy
.ops
.object.mode_set(mode
='EDIT')
248 bpy
.ops
.mesh
.select_all(action
='DESELECT')
250 verts
= ob
.data
.vertices
252 bpy
.ops
.object.mode_set(mode
='OBJECT')
253 verts
[-1].select
= True
254 bpy
.ops
.object.mode_set(mode
='EDIT')
255 bpy
.ops
.mesh
.select_more(use_face_step
=False)
257 bpy
.ops
.mesh
.select_similar(
258 type='EDGE', compare
='EQUAL', threshold
=0.01)
259 bpy
.ops
.mesh
.select_all(action
='INVERT')
261 bpy
.ops
.mesh
.dissolve_verts()
262 bpy
.ops
.mesh
.select_all(action
='DESELECT')
264 bpy
.ops
.mesh
.select_non_manifold(
265 extend
=False, use_wire
=False, use_boundary
=True,
266 use_multi_face
=False, use_non_contiguous
=False, use_verts
=False)
267 bpy
.ops
.mesh
.select_more()
270 bpy
.ops
.object.mode_set(mode
='OBJECT')
271 bound_v
= [v
.index
for v
in ob
.data
.vertices
if v
.select
]
272 bound_e
= [e
.index
for e
in ob
.data
.edges
if e
.select
]
273 bound_p
= [p
.index
for p
in ob
.data
.polygons
if p
.select
]
274 bpy
.ops
.object.mode_set(mode
='EDIT')
277 bpy
.context
.tool_settings
.mesh_select_mode
= (False, False, True)
278 bpy
.ops
.mesh
.select_face_by_sides(number
=4, extend
=False)
280 # deselect boundaries
281 bpy
.ops
.object.mode_set(mode
='OBJECT')
283 bpy
.context
.active_object
.data
.vertices
[i
].select
= False
285 bpy
.context
.active_object
.data
.edges
[i
].select
= False
287 bpy
.context
.active_object
.data
.polygons
[i
].select
= False
289 bpy
.ops
.object.mode_set(mode
='EDIT')
291 bpy
.context
.tool_settings
.mesh_select_mode
= (False, False, True)
292 bpy
.ops
.mesh
.edge_face_add()
293 bpy
.context
.tool_settings
.mesh_select_mode
= (True, False, False)
294 bpy
.ops
.mesh
.select_all(action
='DESELECT')
297 bpy
.ops
.mesh
.select_non_manifold(
298 extend
=False, use_wire
=True, use_boundary
=True,
299 use_multi_face
=False, use_non_contiguous
=False, use_verts
=True
301 bpy
.ops
.mesh
.delete(type='VERT')
303 # remove middle vertices
304 bm
= bmesh
.from_edit_mesh(ob
.data
)
306 if len(v
.link_edges
) == 2 and len(v
.link_faces
) < 3:
310 bpy
.ops
.mesh
.dissolve_verts()
311 bpy
.ops
.mesh
.select_all(action
='DESELECT')
313 # remove border faces
314 if not self
.preserve_borders
:
315 bpy
.ops
.mesh
.select_non_manifold(
316 extend
=False, use_wire
=False, use_boundary
=True,
317 use_multi_face
=False, use_non_contiguous
=False, use_verts
=False
319 bpy
.ops
.mesh
.select_more()
320 bpy
.ops
.mesh
.delete(type='FACE')
323 bpy
.ops
.mesh
.select_non_manifold(
324 extend
=False, use_wire
=True, use_boundary
=False,
325 use_multi_face
=False, use_non_contiguous
=False, use_verts
=False
327 bpy
.ops
.mesh
.delete(type='EDGE')
329 bpy
.ops
.object.mode_set(mode
='OBJECT')
330 ob0
.data
.name
= mesh_name
331 doneMeshes
.append(mesh_name
)
339 bpy
.context
.view_layer
.objects
.active
= act
340 bpy
.ops
.object.mode_set(mode
=mode
)