Skip to content
Snippets Groups Projects
dual_mesh.py 9.85 KiB
Newer Older
  • Learn to ignore specific revisions
  • # ##### 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
    import bmesh
    
    bl_info = {
        "name": "Dual Mesh",
        "author": "Alessandro Zomparelli (Co-de-iT)",
        "version": (0, 3),
        "blender": (2, 7, 8),
        "location": "",
        "description": "Convert a generic mesh to its dual",
        "warning": "",
        "wiki_url": "",
        "tracker_url": "",
        "category": "Mesh"}
    
    class dual_mesh(bpy.types.Operator):
        bl_idname = "object.dual_mesh"
        bl_label = "Dual Mesh"
        bl_options = {'REGISTER', 'UNDO'}
    
        quad_method = bpy.props.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 = bpy.props.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 = bpy.props.BoolProperty(
            name="Preserve Borders", default=True,
            description="Preserve original borders")
    
        def execute(self, context):
            act = bpy.context.active_object
            sel = bpy.context.selected_objects
            doneMeshes = []
            for ob0 in sel:
                if ob0.type != 'MESH': continue
                if ob0.data.name in doneMeshes: continue
                ##ob = bpy.data.objects.new("dual_mesh_wip", ob0.data.copy())
                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
                ob.data = ob.data.copy()
                bpy.ops.object.select_all(action='DESELECT')
                ob.select = True
                bpy.context.scene.objects.active = ob0
                bpy.ops.object.mode_set(mode = 'EDIT')
    
                if self.preserve_borders:
                    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),
                        "constraint_axis":(False, False, False),
                        "constraint_orientation":'GLOBAL', "mirror":False,
                        "proportional":'DISABLED',
                        "proportional_edit_falloff":'SMOOTH', "proportional_size":1,
                        "snap":False, "snap_target":'CLOSEST',
                        "snap_point":(0, 0, 0), "snap_align":False,
                        "snap_normal":(0, 0, 0), "gpencil_strokes":False,
                        "texture_space":False, "remove_on_cancel":False,
                        "release_confirm":False})
    
                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"
                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[0].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
                bpy.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:
                    bpy.context.active_object.data.vertices[i].select = False
                for i in bound_e:
                    bpy.context.active_object.data.edges[i].select = False
                for i in bound_p:
                    bpy.context.active_object.data.polygons[i].select = False
    
                bpy.ops.object.mode_set(mode = 'EDIT')
    
                bpy.context.tool_settings.mesh_select_mode = (False, False, True)
                bpy.ops.mesh.edge_face_add()
                bpy.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_set(True)
    
                # dissolve
                bpy.ops.mesh.dissolve_verts()
                bpy.ops.mesh.select_all(action = 'DESELECT')
    
                # 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 = True
            bpy.context.scene.objects.active = act
            return {'FINISHED'}
    
    
    class dual_mesh_panel(bpy.types.Panel):
        bl_label = "Dual Mesh"
        bl_category = "Create"
        bl_space_type = "VIEW_3D"
        bl_region_type = "TOOLS"
        bl_context = (("objectmode"))
    
        bl_options = {'DEFAULT_CLOSED'}
    
    
        def draw(self, context):
            layout = self.layout
            col = layout.column(align=True)
            try:
                if bpy.context.active_object.type == 'MESH':
                    col.operator("object.dual_mesh")
            except:
                pass
    
    
    def register():
        bpy.utils.register_class(dual_mesh)
        bpy.utils.register_class(dual_mesh_panel)
    
    
    def unregister():
        bpy.utils.unregister_class(dual_mesh)
        bpy.utils.unregister_class(dual_mesh_panel)
    
    
    if __name__ == "__main__":
        register()