1 # SPDX-License-Identifier: GPL-2.0-or-later
3 # --------------------------------- DUAL MESH -------------------------------- #
4 # -------------------------------- version 0.3 ------------------------------- #
6 # Convert a generic mesh to its dual. With open meshes it can get some wired #
7 # effect on the borders. #
9 # (c) Alessandro Zomparelli #
12 # http://www.co-de-it.com/ #
14 # ############################################################################ #
18 from bpy
.types
import Operator
19 from bpy
.props
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",
36 description
="Apply object's modifiers"
39 source_faces
: EnumProperty(
41 ('QUAD', 'Quad Faces', ''),
42 ('TRI', 'Triangles', '')],
44 description
="Triangles works with any geometry." \
45 "Quad option is faster when the object has only Quads",
47 options
={'LIBRARY_EDITABLE'}
50 link_component
: BoolProperty(
51 name
="Editable Component",
53 description
="Add Component Object to the Scene"
56 def execute(self
, context
):
57 auto_layer_collection()
59 name1
= "DualMesh_{}_Component".format(self
.source_faces
)
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)]
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),
77 faces
= [(5,0,7,4), (7,1,6,4), (3,2,5,4,6)]
79 # check pre-existing component
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
):
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
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)
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
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'),
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')
141 description
="Method for splitting the quads into triangles",
143 options
={'LIBRARY_EDITABLE'}
145 polygon_method
: EnumProperty(
147 ('BEAUTY', 'Beauty', 'Arrange the new triangles evenly'),
149 'Split the N-gon with an ear clipping algorithm')],
151 description
="Method for splitting the N-gons into triangles",
153 options
={'LIBRARY_EDITABLE'}
155 preserve_borders
: BoolProperty(
156 name
="Preserve Borders",
158 description
="Preserve original borders"
160 apply_modifiers
: BoolProperty(
161 name
="Apply Modifiers",
163 description
="Apply object's modifiers"
166 def execute(self
, context
):
168 if mode
== 'EDIT_MESH':
170 act
= context
.active_object
173 bpy
.ops
.object.mode_set(mode
='OBJECT')
175 sel
= context
.selected_objects
179 if ob0
.type != 'MESH':
181 if ob0
.data
.name
in doneMeshes
:
184 mesh_name
= ob0
.data
.name
186 # store linked objects
188 n_users
= ob0
.data
.users
190 for o
in bpy
.data
.objects
:
193 if o
.data
.name
== mesh_name
:
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')
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,
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',
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"
234 bpy
.ops
.object.modifier_move_up(modifier
="dual_mesh_subsurf")
235 if ob
.modifiers
[0].name
== "dual_mesh_subsurf":
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()
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')
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')
276 context
.active_object
.data
.vertices
[i
].select
= False
278 context
.active_object
.data
.edges
[i
].select
= False
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')
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
)
299 if len(v
.link_edges
) == 2 and len(v
.link_faces
) < 3:
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')
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
)
333 context
.view_layer
.objects
.active
= act
334 bpy
.ops
.object.mode_set(mode
=mode
)