346 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
		
		
			
		
	
	
			346 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
|   | # ##### BEGIN GPL LICENSE BLOCK ##### | ||
|  | # | ||
|  | #  This program is free software; you can redistribute it and/or | ||
|  | #  modify it under the terms of the GNU General Public License | ||
|  | #  as published by the Free Software Foundation; either version 2 | ||
|  | #  of the License, or (at your option) any later version. | ||
|  | # | ||
|  | #  This program is distributed in the hope that it will be useful, | ||
|  | #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
|  | #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||
|  | #  GNU General Public License for more details. | ||
|  | # | ||
|  | #  You should have received a copy of the GNU General Public License | ||
|  | #  along with this program; if not, write to the Free Software Foundation, | ||
|  | #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
|  | # | ||
|  | # ##### END GPL LICENSE BLOCK ##### | ||
|  | 
 | ||
|  | # --------------------------------- DUAL MESH -------------------------------- # | ||
|  | # -------------------------------- version 0.3 ------------------------------- # | ||
|  | #                                                                              # | ||
|  | # Convert a generic mesh to its dual. With open meshes it can get some wired   # | ||
|  | # effect on the borders.                                                       # | ||
|  | #                                                                              # | ||
|  | #                        (c)   Alessandro Zomparelli                           # | ||
|  | #                                    (2017)                                    # | ||
|  | #                                                                              # | ||
|  | # http://www.co-de-it.com/                                                     # | ||
|  | #                                                                              # | ||
|  | # ############################################################################ # | ||
|  | 
 | ||
|  | 
 | ||
|  | import bpy | ||
|  | from bpy.types import Operator | ||
|  | from bpy.props import ( | ||
|  |         BoolProperty, | ||
|  |         EnumProperty, | ||
|  |         ) | ||
|  | import bmesh | ||
|  | from .utils import * | ||
|  | 
 | ||
|  | 
 | ||
|  | class dual_mesh_tessellated(Operator): | ||
|  |     bl_idname = "object.dual_mesh_tessellated" | ||
|  |     bl_label = "Dual Mesh" | ||
|  |     bl_description = ("Generate a polygonal mesh using Tessellate. (Non-destructive)") | ||
|  |     bl_options = {'REGISTER', 'UNDO'} | ||
|  | 
 | ||
|  |     apply_modifiers : BoolProperty( | ||
|  |         name="Apply Modifiers", | ||
|  |         default=True, | ||
|  |         description="Apply object's modifiers" | ||
|  |         ) | ||
|  | 
 | ||
|  |     source_faces : EnumProperty( | ||
|  |             items=[ | ||
|  |                 ('QUAD', 'Quad Faces', ''), | ||
|  |                 ('TRI', 'Triangles', '')], | ||
|  |             name="Source Faces", | ||
|  |             description="Source polygons", | ||
|  |             default="QUAD", | ||
|  |             options={'LIBRARY_EDITABLE'} | ||
|  |             ) | ||
|  | 
 | ||
|  |     def execute(self, context): | ||
|  |         auto_layer_collection() | ||
|  |         ob0 = context.object | ||
|  |         name1 = "DualMesh_{}_Component".format(self.source_faces) | ||
|  |         # Generate component | ||
|  |         if self.source_faces == 'QUAD': | ||
|  |             verts = [(0.0, 0.0, 0.0), (0.0, 0.5, 0.0), | ||
|  |                     (0.0, 1.0, 0.0), (0.5, 1.0, 0.0), | ||
|  |                     (1.0, 1.0, 0.0), (1.0, 0.5, 0.0), | ||
|  |                     (1.0, 0.0, 0.0), (0.5, 0.0, 0.0), | ||
|  |                     (1/3, 1/3, 0.0), (2/3, 2/3, 0.0)] | ||
|  |             edges = [(0,1), (1,2), (2,3), (3,4), (4,5), (5,6), (6,7), | ||
|  |                         (7,0), (1,8), (8,7), (3,9), (9,5), (8,9)] | ||
|  |             faces = [(7,8,1,0), (8,9,3,2,1), (9,5,4,3), (9,8,7,6,5)] | ||
|  |         else: | ||
|  |             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)] | ||
|  |             edges = [(0,1), (1,2), (2,5), (5,4), (4,3), (3,0), (1,4)] | ||
|  |             faces = [(0,1,4,3), (1,2,5,4)] | ||
|  | 
 | ||
|  |         # check pre-existing component | ||
|  |         try: | ||
|  |             _verts = [0]*len(verts)*3 | ||
|  |             __verts = [c for co in verts for c in co] | ||
|  |             ob1 = bpy.data.objects[name1] | ||
|  |             ob1.data.vertices.foreach_get("co",_verts) | ||
|  |             for a, b in zip(_verts, __verts): | ||
|  |                 if abs(a-b) > 0.0001: | ||
|  |                     raise ValueError | ||
|  |         except: | ||
|  |             me = bpy.data.meshes.new("Dual-Mesh")  # add a new mesh | ||
|  |             me.from_pydata(verts, edges, faces) | ||
|  |             me.update(calc_edges=True, calc_edges_loose=True) | ||
|  |             if self.source_faces == 'QUAD': n_seams = 8 | ||
|  |             else: n_seams = 6 | ||
|  |             for i in range(n_seams): me.edges[i].use_seam = True | ||
|  |             ob1 = bpy.data.objects.new(name1, me) | ||
|  |             context.collection.objects.link(ob1) | ||
|  |             # fix visualization issue | ||
|  |             context.view_layer.objects.active = ob1 | ||
|  |             ob1.select_set(True) | ||
|  |             bpy.ops.object.editmode_toggle() | ||
|  |             bpy.ops.object.editmode_toggle() | ||
|  |             ob1.select_set(False) | ||
|  |             # hide component | ||
|  |             ob1.hide_select = True | ||
|  |             ob1.hide_render = True | ||
|  |             ob1.hide_viewport = True | ||
|  |         ob = convert_object_to_mesh(ob0,False,False) | ||
|  |         ob.name = 'DualMesh' | ||
|  |         #ob = bpy.data.objects.new("DualMesh", convert_object_to_mesh(ob0,False,False)) | ||
|  |         #context.collection.objects.link(ob) | ||
|  |         #context.view_layer.objects.active = ob | ||
|  |         #ob.select_set(True) | ||
|  |         ob.tissue_tessellate.component = ob1 | ||
|  |         ob.tissue_tessellate.generator = ob0 | ||
|  |         ob.tissue_tessellate.gen_modifiers = self.apply_modifiers | ||
|  |         ob.tissue_tessellate.merge = True | ||
|  |         ob.tissue_tessellate.bool_dissolve_seams = True | ||
|  |         if self.source_faces == 'TRI': ob.tissue_tessellate.fill_mode = 'FAN' | ||
|  |         bpy.ops.object.update_tessellate() | ||
|  |         ob.location = ob0.location | ||
|  |         ob.matrix_world = ob0.matrix_world | ||
|  |         return {'FINISHED'} | ||
|  | 
 | ||
|  |     def invoke(self, context, event): | ||
|  |         return context.window_manager.invoke_props_dialog(self) | ||
|  | 
 | ||
|  | class dual_mesh(Operator): | ||
|  |     bl_idname = "object.dual_mesh" | ||
|  |     bl_label = "Convert to Dual Mesh" | ||
|  |     bl_description = ("Convert a generic mesh into a polygonal mesh. (Destructive)") | ||
|  |     bl_options = {'REGISTER', 'UNDO'} | ||
|  | 
 | ||
|  |     quad_method : EnumProperty( | ||
|  |             items=[('BEAUTY', 'Beauty', | ||
|  |                     'Split the quads in nice triangles, slower method'), | ||
|  |                     ('FIXED', 'Fixed', | ||
|  |                     'Split the quads on the 1st and 3rd vertices'), | ||
|  |                     ('FIXED_ALTERNATE', 'Fixed Alternate', | ||
|  |                     'Split the quads on the 2nd and 4th vertices'), | ||
|  |                     ('SHORTEST_DIAGONAL', 'Shortest Diagonal', | ||
|  |                     'Split the quads based on the distance between the vertices') | ||
|  |                     ], | ||
|  |             name="Quad Method", | ||
|  |             description="Method for splitting the quads into triangles", | ||
|  |             default="FIXED", | ||
|  |             options={'LIBRARY_EDITABLE'} | ||
|  |             ) | ||
|  |     polygon_method : EnumProperty( | ||
|  |             items=[ | ||
|  |                 ('BEAUTY', 'Beauty', 'Arrange the new triangles evenly'), | ||
|  |                 ('CLIP', 'Clip', | ||
|  |                  'Split the polygons with an ear clipping algorithm')], | ||
|  |             name="Polygon Method", | ||
|  |             description="Method for splitting the polygons into triangles", | ||
|  |             default="BEAUTY", | ||
|  |             options={'LIBRARY_EDITABLE'} | ||
|  |             ) | ||
|  |     preserve_borders : BoolProperty( | ||
|  |             name="Preserve Borders", | ||
|  |             default=True, | ||
|  |             description="Preserve original borders" | ||
|  |             ) | ||
|  |     apply_modifiers : BoolProperty( | ||
|  |             name="Apply Modifiers", | ||
|  |             default=True, | ||
|  |             description="Apply object's modifiers" | ||
|  |             ) | ||
|  | 
 | ||
|  |     def execute(self, context): | ||
|  |         mode = context.mode | ||
|  |         if mode == 'EDIT_MESH': | ||
|  |             mode = 'EDIT' | ||
|  |         act = context.active_object | ||
|  |         if mode != 'OBJECT': | ||
|  |             sel = [act] | ||
|  |             bpy.ops.object.mode_set(mode='OBJECT') | ||
|  |         else: | ||
|  |             sel = context.selected_objects | ||
|  |         doneMeshes = [] | ||
|  | 
 | ||
|  |         for ob0 in sel: | ||
|  |             if ob0.type != 'MESH': | ||
|  |                 continue | ||
|  |             if ob0.data.name in doneMeshes: | ||
|  |                 continue | ||
|  |             ob = ob0 | ||
|  |             mesh_name = ob0.data.name | ||
|  | 
 | ||
|  |             # store linked objects | ||
|  |             clones = [] | ||
|  |             n_users = ob0.data.users | ||
|  |             count = 0 | ||
|  |             for o in bpy.data.objects: | ||
|  |                 if o.type != 'MESH': | ||
|  |                     continue | ||
|  |                 if o.data.name == mesh_name: | ||
|  |                     count += 1 | ||
|  |                     clones.append(o) | ||
|  |                 if count == n_users: | ||
|  |                     break | ||
|  | 
 | ||
|  |             if self.apply_modifiers: | ||
|  |                 bpy.ops.object.convert(target='MESH') | ||
|  |             ob.data = ob.data.copy() | ||
|  |             bpy.ops.object.select_all(action='DESELECT') | ||
|  |             ob.select_set(True) | ||
|  |             context.view_layer.objects.active = ob0 | ||
|  |             bpy.ops.object.mode_set(mode='EDIT') | ||
|  | 
 | ||
|  |             # prevent borders erosion | ||
|  |             bpy.ops.mesh.select_mode( | ||
|  |                     use_extend=False, use_expand=False, type='EDGE' | ||
|  |                     ) | ||
|  |             bpy.ops.mesh.select_non_manifold( | ||
|  |                     extend=False, use_wire=False, use_boundary=True, | ||
|  |                     use_multi_face=False, use_non_contiguous=False, | ||
|  |                     use_verts=False | ||
|  |                     ) | ||
|  |             bpy.ops.mesh.extrude_region_move( | ||
|  |                     MESH_OT_extrude_region={"mirror": False}, | ||
|  |                     TRANSFORM_OT_translate={"value": (0, 0, 0)} | ||
|  |                     ) | ||
|  | 
 | ||
|  |             bpy.ops.mesh.select_mode( | ||
|  |                     use_extend=False, use_expand=False, type='VERT', | ||
|  |                     action='TOGGLE' | ||
|  |                     ) | ||
|  |             bpy.ops.mesh.select_all(action='SELECT') | ||
|  |             bpy.ops.mesh.quads_convert_to_tris( | ||
|  |                     quad_method=self.quad_method, ngon_method=self.polygon_method | ||
|  |                     ) | ||
|  |             bpy.ops.mesh.select_all(action='DESELECT') | ||
|  |             bpy.ops.object.mode_set(mode='OBJECT') | ||
|  |             bpy.ops.object.modifier_add(type='SUBSURF') | ||
|  |             ob.modifiers[-1].name = "dual_mesh_subsurf" | ||
|  |             while True: | ||
|  |                 bpy.ops.object.modifier_move_up(modifier="dual_mesh_subsurf") | ||
|  |                 if ob.modifiers[0].name == "dual_mesh_subsurf": | ||
|  |                     break | ||
|  | 
 | ||
|  |             bpy.ops.object.modifier_apply( | ||
|  |                     apply_as='DATA', modifier='dual_mesh_subsurf' | ||
|  |                     ) | ||
|  | 
 | ||
|  |             bpy.ops.object.mode_set(mode='EDIT') | ||
|  |             bpy.ops.mesh.select_all(action='DESELECT') | ||
|  | 
 | ||
|  |             verts = ob.data.vertices | ||
|  | 
 | ||
|  |             bpy.ops.object.mode_set(mode='OBJECT') | ||
|  |             verts[-1].select = True | ||
|  |             bpy.ops.object.mode_set(mode='EDIT') | ||
|  |             bpy.ops.mesh.select_more(use_face_step=False) | ||
|  | 
 | ||
|  |             bpy.ops.mesh.select_similar( | ||
|  |                 type='EDGE', compare='EQUAL', threshold=0.01) | ||
|  |             bpy.ops.mesh.select_all(action='INVERT') | ||
|  | 
 | ||
|  |             bpy.ops.mesh.dissolve_verts() | ||
|  |             bpy.ops.mesh.select_all(action='DESELECT') | ||
|  | 
 | ||
|  |             bpy.ops.mesh.select_non_manifold( | ||
|  |                 extend=False, use_wire=False, use_boundary=True, | ||
|  |                 use_multi_face=False, use_non_contiguous=False, use_verts=False) | ||
|  |             bpy.ops.mesh.select_more() | ||
|  | 
 | ||
|  |             # find boundaries | ||
|  |             bpy.ops.object.mode_set(mode='OBJECT') | ||
|  |             bound_v = [v.index for v in ob.data.vertices if v.select] | ||
|  |             bound_e = [e.index for e in ob.data.edges if e.select] | ||
|  |             bound_p = [p.index for p in ob.data.polygons if p.select] | ||
|  |             bpy.ops.object.mode_set(mode='EDIT') | ||
|  | 
 | ||
|  |             # select quad faces | ||
|  |             context.tool_settings.mesh_select_mode = (False, False, True) | ||
|  |             bpy.ops.mesh.select_face_by_sides(number=4, extend=False) | ||
|  | 
 | ||
|  |             # deselect boundaries | ||
|  |             bpy.ops.object.mode_set(mode='OBJECT') | ||
|  |             for i in bound_v: | ||
|  |                 context.active_object.data.vertices[i].select = False | ||
|  |             for i in bound_e: | ||
|  |                 context.active_object.data.edges[i].select = False | ||
|  |             for i in bound_p: | ||
|  |                 context.active_object.data.polygons[i].select = False | ||
|  | 
 | ||
|  |             bpy.ops.object.mode_set(mode='EDIT') | ||
|  | 
 | ||
|  |             context.tool_settings.mesh_select_mode = (False, False, True) | ||
|  |             bpy.ops.mesh.edge_face_add() | ||
|  |             context.tool_settings.mesh_select_mode = (True, False, False) | ||
|  |             bpy.ops.mesh.select_all(action='DESELECT') | ||
|  | 
 | ||
|  |             # delete boundaries | ||
|  |             bpy.ops.mesh.select_non_manifold( | ||
|  |                     extend=False, use_wire=True, use_boundary=True, | ||
|  |                     use_multi_face=False, use_non_contiguous=False, use_verts=True | ||
|  |                     ) | ||
|  |             bpy.ops.mesh.delete(type='VERT') | ||
|  | 
 | ||
|  |             # remove middle vertices | ||
|  |             bm = bmesh.from_edit_mesh(ob.data) | ||
|  |             for v in bm.verts: | ||
|  |                 if len(v.link_edges) == 2 and len(v.link_faces) < 3: | ||
|  |                     v.select = True | ||
|  | 
 | ||
|  |             # dissolve | ||
|  |             bpy.ops.mesh.dissolve_verts() | ||
|  |             bpy.ops.mesh.select_all(action='DESELECT') | ||
|  | 
 | ||
|  |             # remove border faces | ||
|  |             if not self.preserve_borders: | ||
|  |                 bpy.ops.mesh.select_non_manifold( | ||
|  |                     extend=False, use_wire=False, use_boundary=True, | ||
|  |                     use_multi_face=False, use_non_contiguous=False, use_verts=False | ||
|  |                     ) | ||
|  |                 bpy.ops.mesh.select_more() | ||
|  |                 bpy.ops.mesh.delete(type='FACE') | ||
|  | 
 | ||
|  |             # clean wires | ||
|  |             bpy.ops.mesh.select_non_manifold( | ||
|  |                     extend=False, use_wire=True, use_boundary=False, | ||
|  |                     use_multi_face=False, use_non_contiguous=False, use_verts=False | ||
|  |                     ) | ||
|  |             bpy.ops.mesh.delete(type='EDGE') | ||
|  | 
 | ||
|  |             bpy.ops.object.mode_set(mode='OBJECT') | ||
|  |             ob0.data.name = mesh_name | ||
|  |             doneMeshes.append(mesh_name) | ||
|  | 
 | ||
|  |             for o in clones: | ||
|  |                 o.data = ob.data | ||
|  | 
 | ||
|  |         for o in sel: | ||
|  |             o.select_set(True) | ||
|  | 
 | ||
|  |         context.view_layer.objects.active = act | ||
|  |         bpy.ops.object.mode_set(mode=mode) | ||
|  | 
 | ||
|  |         return {'FINISHED'} |