1 # SPDX-FileCopyrightText: 2017 Alessandro Zomparelli
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 # --------------------------------- DUAL MESH -------------------------------- #
6 # -------------------------------- version 0.3 ------------------------------- #
8 # Convert a generic mesh to its dual. With open meshes it can get some wired #
9 # effect on the borders. #
11 # (c) Alessandro Zomparelli #
14 # http://www.co-de-it.com/ #
16 # ############################################################################ #
20 from bpy
.types
import Operator
21 from bpy
.props
import (
29 class dual_mesh_tessellated(Operator
):
30 bl_idname
= "object.dual_mesh_tessellated"
31 bl_label
= "Dual Mesh"
32 bl_description
= ("Generate a polygonal mesh using Tessellate. (Non-destructive)")
33 bl_options
= {'REGISTER', 'UNDO'}
35 apply_modifiers
: BoolProperty(
36 name
="Apply Modifiers",
38 description
="Apply object's modifiers"
41 source_faces
: EnumProperty(
43 ('QUAD', 'Quad Faces', ''),
44 ('TRI', 'Triangles', '')],
46 description
="Triangles works with any geometry." \
47 "Quad option is faster when the object has only Quads",
49 options
={'LIBRARY_EDITABLE'}
52 link_component
: BoolProperty(
53 name
="Editable Component",
55 description
="Add Component Object to the Scene"
58 def execute(self
, context
):
59 auto_layer_collection()
61 name1
= "DualMesh_{}_Component".format(self
.source_faces
)
63 if self
.source_faces
== 'QUAD':
64 verts
= [(1.0, 0.0, 0.0), (0.5, 0.0, 0.0),
65 (0.0, 0.0, 0.0), (0.0, 0.5, 0.0),
66 (0.0, 1.0, 0.0), (0.5, 1.0, 0.0),
67 (1.0, 1.0, 0.0), (1.0, 0.5, 0.0),
68 (2/3, 1/3, 0.0), (1/3, 2/3, 0.0)]
69 edges
= [(0,1), (1,2), (2,3), (3,4), (4,5), (5,6), (6,7),
70 (7,0), (1,8), (8,7), (3,9), (9,5), (8,9)]
71 faces
= [(7,8,1,0), (8,9,3,2,1), (9,5,4,3), (9,8,7,6,5)]
73 verts
= [(0.0, 0.0, 0.0), (1.0, 0.0, 0.0),
74 (0.0, 1.0, 0.0), (1.0, 1.0, 0.0),
75 (0.5, 1/3, 0.0), (0.0, 0.5, 0.0),
76 (1.0, 0.5, 0.0), (0.5, 0.0, 0.0)]
77 edges
= [(0,5), (1,7), (3,6), (2,3), (2,5), (1,6), (0,7),
79 faces
= [(5,0,7,4), (7,1,6,4), (3,2,5,4,6)]
81 # check pre-existing component
83 _verts
= [0]*len(verts
)*3
84 __verts
= [c
for co
in verts
for c
in co
]
85 ob1
= bpy
.data
.objects
[name1
]
86 ob1
.data
.vertices
.foreach_get("co",_verts
)
87 for a
, b
in zip(_verts
, __verts
):
91 me
= bpy
.data
.meshes
.new("Dual-Mesh") # add a new mesh
92 me
.from_pydata(verts
, edges
, faces
)
93 me
.update(calc_edges
=True, calc_edges_loose
=True)
94 if self
.source_faces
== 'QUAD': seams
= (0,1,2,3,4,5,6,9)
95 else: seams
= (0,1,2,3,4,5,7)
96 for i
in seams
: me
.edges
[i
].use_seam
= True
97 ob1
= bpy
.data
.objects
.new(name1
, me
)
98 # fix visualization issue
99 if self
.link_component
:
100 context
.collection
.objects
.link(ob1
)
101 context
.view_layer
.objects
.active
= ob1
103 bpy
.ops
.object.editmode_toggle()
104 bpy
.ops
.object.editmode_toggle()
105 ob1
.select_set(False)
106 ob1
.hide_render
= True
107 ob
= convert_object_to_mesh(ob0
,False,False)
109 ob
.tissue
.tissue_type
= 'TESSELLATE'
110 ob
.tissue
.bool_lock
= True
111 ob
.tissue_tessellate
.component
= ob1
112 ob
.tissue_tessellate
.generator
= ob0
113 ob
.tissue_tessellate
.gen_modifiers
= self
.apply_modifiers
114 ob
.tissue_tessellate
.merge
= True
115 ob
.tissue_tessellate
.bool_dissolve_seams
= True
116 if self
.source_faces
== 'TRI': ob
.tissue_tessellate
.fill_mode
= 'TRI'
117 bpy
.ops
.object.tissue_update_tessellate()
118 ob
.tissue
.bool_lock
= False
119 ob
.location
= ob0
.location
120 ob
.matrix_world
= ob0
.matrix_world
123 def invoke(self
, context
, event
):
124 return context
.window_manager
.invoke_props_dialog(self
)
126 class dual_mesh(Operator
):
127 bl_idname
= "object.dual_mesh"
128 bl_label
= "Convert to Dual Mesh"
129 bl_description
= ("Convert a generic mesh into a polygonal mesh. (Destructive)")
130 bl_options
= {'REGISTER', 'UNDO'}
132 quad_method
: EnumProperty(
133 items
=[('BEAUTY', 'Beauty',
134 'Split the quads in nice triangles, slower method'),
136 'Split the quads on the 1st and 3rd vertices'),
137 ('FIXED_ALTERNATE', 'Fixed Alternate',
138 'Split the quads on the 2nd and 4th vertices'),
139 ('SHORTEST_DIAGONAL', 'Shortest Diagonal',
140 'Split the quads based on the distance between the vertices')
143 description
="Method for splitting the quads into triangles",
145 options
={'LIBRARY_EDITABLE'}
147 polygon_method
: EnumProperty(
149 ('BEAUTY', 'Beauty', 'Arrange the new triangles evenly'),
151 'Split the N-gon with an ear clipping algorithm')],
153 description
="Method for splitting the N-gons into triangles",
155 options
={'LIBRARY_EDITABLE'}
157 preserve_borders
: BoolProperty(
158 name
="Preserve Borders",
160 description
="Preserve original borders"
162 apply_modifiers
: BoolProperty(
163 name
="Apply Modifiers",
165 description
="Apply object's modifiers"
168 def execute(self
, context
):
170 if mode
== 'EDIT_MESH':
172 act
= context
.active_object
175 bpy
.ops
.object.mode_set(mode
='OBJECT')
177 sel
= context
.selected_objects
181 if ob0
.type != 'MESH':
183 if ob0
.data
.name
in doneMeshes
:
186 mesh_name
= ob0
.data
.name
188 # store linked objects
190 n_users
= ob0
.data
.users
192 for o
in bpy
.data
.objects
:
195 if o
.data
.name
== mesh_name
:
201 if self
.apply_modifiers
:
202 bpy
.ops
.object.convert(target
='MESH')
203 ob
.data
= ob
.data
.copy()
204 bpy
.ops
.object.select_all(action
='DESELECT')
206 context
.view_layer
.objects
.active
= ob0
207 bpy
.ops
.object.mode_set(mode
='EDIT')
209 # prevent borders erosion
210 bpy
.ops
.mesh
.select_mode(
211 use_extend
=False, use_expand
=False, type='EDGE'
213 bpy
.ops
.mesh
.select_non_manifold(
214 extend
=False, use_wire
=False, use_boundary
=True,
215 use_multi_face
=False, use_non_contiguous
=False,
218 bpy
.ops
.mesh
.extrude_region_move(
219 MESH_OT_extrude_region
={"mirror": False},
220 TRANSFORM_OT_translate
={"value": (0, 0, 0)}
223 bpy
.ops
.mesh
.select_mode(
224 use_extend
=False, use_expand
=False, type='VERT',
227 bpy
.ops
.mesh
.select_all(action
='SELECT')
228 bpy
.ops
.mesh
.quads_convert_to_tris(
229 quad_method
=self
.quad_method
, ngon_method
=self
.polygon_method
231 bpy
.ops
.mesh
.select_all(action
='DESELECT')
232 bpy
.ops
.object.mode_set(mode
='OBJECT')
233 bpy
.ops
.object.modifier_add(type='SUBSURF')
234 ob
.modifiers
[-1].name
= "dual_mesh_subsurf"
236 bpy
.ops
.object.modifier_move_up(modifier
="dual_mesh_subsurf")
237 if ob
.modifiers
[0].name
== "dual_mesh_subsurf":
240 bpy
.ops
.object.modifier_apply(modifier
='dual_mesh_subsurf')
242 bpy
.ops
.object.mode_set(mode
='EDIT')
243 bpy
.ops
.mesh
.select_all(action
='DESELECT')
245 verts
= ob
.data
.vertices
247 bpy
.ops
.object.mode_set(mode
='OBJECT')
248 verts
[-1].select
= True
249 bpy
.ops
.object.mode_set(mode
='EDIT')
250 bpy
.ops
.mesh
.select_more(use_face_step
=False)
252 bpy
.ops
.mesh
.select_similar(
253 type='VERT_EDGES', compare
='EQUAL', threshold
=0.01)
254 bpy
.ops
.mesh
.select_all(action
='INVERT')
256 bpy
.ops
.mesh
.dissolve_verts()
257 bpy
.ops
.mesh
.select_all(action
='DESELECT')
259 bpy
.ops
.mesh
.select_non_manifold(
260 extend
=False, use_wire
=False, use_boundary
=True,
261 use_multi_face
=False, use_non_contiguous
=False, use_verts
=False)
262 bpy
.ops
.mesh
.select_more()
265 bpy
.ops
.object.mode_set(mode
='OBJECT')
266 bound_v
= [v
.index
for v
in ob
.data
.vertices
if v
.select
]
267 bound_e
= [e
.index
for e
in ob
.data
.edges
if e
.select
]
268 bound_p
= [p
.index
for p
in ob
.data
.polygons
if p
.select
]
269 bpy
.ops
.object.mode_set(mode
='EDIT')
272 context
.tool_settings
.mesh_select_mode
= (False, False, True)
273 bpy
.ops
.mesh
.select_face_by_sides(number
=4, extend
=False)
275 # deselect boundaries
276 bpy
.ops
.object.mode_set(mode
='OBJECT')
278 context
.active_object
.data
.vertices
[i
].select
= False
280 context
.active_object
.data
.edges
[i
].select
= False
282 context
.active_object
.data
.polygons
[i
].select
= False
284 bpy
.ops
.object.mode_set(mode
='EDIT')
286 context
.tool_settings
.mesh_select_mode
= (False, False, True)
287 bpy
.ops
.mesh
.edge_face_add()
288 context
.tool_settings
.mesh_select_mode
= (True, False, False)
289 bpy
.ops
.mesh
.select_all(action
='DESELECT')
292 bpy
.ops
.mesh
.select_non_manifold(
293 extend
=False, use_wire
=True, use_boundary
=True,
294 use_multi_face
=False, use_non_contiguous
=False, use_verts
=True
296 bpy
.ops
.mesh
.delete(type='VERT')
298 # remove middle vertices
299 bm
= bmesh
.from_edit_mesh(ob
.data
)
301 if len(v
.link_edges
) == 2 and len(v
.link_faces
) < 3:
305 bpy
.ops
.mesh
.dissolve_verts()
306 bpy
.ops
.mesh
.select_all(action
='DESELECT')
308 # remove border faces
309 if not self
.preserve_borders
:
310 bpy
.ops
.mesh
.select_non_manifold(
311 extend
=False, use_wire
=False, use_boundary
=True,
312 use_multi_face
=False, use_non_contiguous
=False, use_verts
=False
314 bpy
.ops
.mesh
.select_more()
315 bpy
.ops
.mesh
.delete(type='FACE')
318 bpy
.ops
.mesh
.select_non_manifold(
319 extend
=False, use_wire
=True, use_boundary
=False,
320 use_multi_face
=False, use_non_contiguous
=False, use_verts
=False
322 bpy
.ops
.mesh
.delete(type='EDGE')
324 bpy
.ops
.object.mode_set(mode
='OBJECT')
325 ob0
.data
.name
= mesh_name
326 doneMeshes
.append(mesh_name
)
335 context
.view_layer
.objects
.active
= act
336 bpy
.ops
.object.mode_set(mode
=mode
)