Skip to content
Snippets Groups Projects
mesh_looptools.py 138 KiB
Newer Older
  • Learn to ignore specific revisions
  • Bart Crouch's avatar
    Bart Crouch committed
                pknots = curve_project_knots(mesh_mod, verts_selected, knots,
                    points, loop[1])
                tknots, tpoints = curve_calculate_t(mesh_mod, knots, points,
                    pknots, self.regular, loop[1])
                splines = calculate_splines(self.interpolation, mesh_mod,
                    tknots, knots)
                move.append(curve_calculate_vertices(mesh_mod, knots, tknots,
                    points, tpoints, splines, self.interpolation,
                    self.restriction))
            
            # move vertices to new locations
            move_verts(mesh, mapping, move, self.influence)
            
            # cleaning up 
            if derived:
                bpy.context.blend_data.meshes.remove(mesh_mod)
            
            terminate(global_undo)
            return{'FINISHED'}
    
    
    # flatten operator
    class Flatten(bpy.types.Operator):
        bl_idname = "mesh.looptools_flatten"
        bl_label = "Flatten"
        bl_description = "Flatten vertices on a best-fitting plane"
        bl_options = {'REGISTER', 'UNDO'}
        
        influence = bpy.props.FloatProperty(name = "Influence",
            description = "Force of the tool",
            default = 100.0,
            min = 0.0,
            max = 100.0,
            precision = 1,
            subtype = 'PERCENTAGE')
        plane = bpy.props.EnumProperty(name = "Plane",
            items = (("best_fit", "Best fit", "Calculate a best fitting plane"),
                ("normal", "Normal", "Derive plane from averaging vertex "\
                "normals"),
                ("view", "View", "Flatten on a plane perpendicular to the "\
                "viewing angle")),
            description = "Plane on which vertices are flattened",
            default = 'best_fit')
        restriction = bpy.props.EnumProperty(name = "Restriction",
            items = (("none", "None", "No restrictions on vertex movement"),
                ("bounding_box", "Bounding box", "Vertices are restricted to "\
                "movement inside the bounding box of the selection")),
            description = "Restrictions on how the vertices can be moved",
            default = 'none')
        
        @classmethod
        def poll(cls, context):
            ob = context.active_object
            return(ob and ob.type == 'MESH' and context.mode == 'EDIT_MESH')
        
        def draw(self, context):
            layout = self.layout
            col = layout.column()
            
            col.prop(self, "plane")
            #col.prop(self, "restriction")
            col.separator()
            
            col.prop(self, "influence")
        
        def invoke(self, context, event):
            # load custom settings
            settings_load(self)
            return self.execute(context)
        
        def execute(self, context):
            # initialise
            global_undo, object, mesh = initialise()
            settings_write(self)
            # check cache to see if we can save time
            cached, single_loops, loops, derived, mapping = cache_read("Flatten",
                object, mesh, False, False)
            if not cached:
                # order input into virtual loops
                loops = flatten_get_input(mesh)
                loops = check_loops(loops, mapping, mesh)
            
            # saving cache for faster execution next time
            if not cached:
                cache_write("Flatten", object, mesh, False, False, False, loops,
                    False, False)
            
            move = []
            for loop in loops:
                # calculate plane and position of vertices on them
                com, normal = calculate_plane(mesh, loop, method=self.plane,
                    object=object)
                to_move = flatten_project(mesh, loop, com, normal)
                if self.restriction == 'none':
                    move.append(to_move)
                else:
                    move.append(to_move)
            move_verts(mesh, False, move, self.influence)
            
            terminate(global_undo)
            return{'FINISHED'}
    
    
    # relax operator
    class Relax(bpy.types.Operator):
        bl_idname = "mesh.looptools_relax"
        bl_label = "Relax"
        bl_description = "Relax the loop, so it is smoother"
        bl_options = {'REGISTER', 'UNDO'}
        
        input = bpy.props.EnumProperty(name = "Input",
            items = (("all", "Parallel (all)", "Also use non-selected "\
                    "parallel loops as input"),
                ("selected", "Selection","Only use selected vertices as input")),
            description = "Loops that are relaxed",
            default = 'selected')
        interpolation = bpy.props.EnumProperty(name = "Interpolation",
            items = (("cubic", "Cubic", "Natural cubic spline, smooth results"),
                ("linear", "Linear", "Simple and fast linear algorithm")),
            description = "Algorithm used for interpolation",
            default = 'cubic')
        iterations = bpy.props.EnumProperty(name = "Iterations",
            items = (("1", "1", "One"),
                ("3", "3", "Three"),
                ("5", "5", "Five"),
                ("10", "10", "Ten"),
                ("25", "25", "Twenty-five")),
            description = "Number of times the loop is relaxed",
            default = "1")
        regular = bpy.props.BoolProperty(name = "Regular",
            description = "Distribute vertices at constant distances along the" \
                "loop",
            default = True)
        
        @classmethod
        def poll(cls, context):
            ob = context.active_object
            return(ob and ob.type == 'MESH' and context.mode == 'EDIT_MESH')
        
        def draw(self, context):
            layout = self.layout
            col = layout.column()
            
            col.prop(self, "interpolation")
            col.prop(self, "input")
            col.prop(self, "iterations")
            col.prop(self, "regular")
        
        def invoke(self, context, event):
            # load custom settings
            settings_load(self)
            return self.execute(context)
        
        def execute(self, context):
            # initialise
            global_undo, object, mesh = initialise()
            settings_write(self)
            # check cache to see if we can save time
            cached, single_loops, loops, derived, mapping = cache_read("Relax",
                object, mesh, self.input, False)
            if cached:
                derived, mesh_mod = get_derived_mesh(object, mesh, context.scene)
            else:
                # find loops
                derived, mesh_mod, loops = get_connected_input(object, mesh,
                    context.scene, self.input)
                mapping = get_mapping(derived, mesh, mesh_mod, False, False, loops)
                loops = check_loops(loops, mapping, mesh_mod)
            knots, points = relax_calculate_knots(loops)
            
            # saving cache for faster execution next time
            if not cached:
                cache_write("Relax", object, mesh, self.input, False, False, loops,
                    derived, mapping)
            
            for iteration in range(int(self.iterations)):
                # calculate splines and new positions
                tknots, tpoints = relax_calculate_t(mesh_mod, knots, points,
                    self.regular)
                splines = []
                for i in range(len(knots)):
                    splines.append(calculate_splines(self.interpolation, mesh_mod,
                        tknots[i], knots[i]))
                move = [relax_calculate_verts(mesh_mod, self.interpolation,
                    tknots, knots, tpoints, points, splines)]
                move_verts(mesh, mapping, move, -1)
            
            # cleaning up 
            if derived:
                bpy.context.blend_data.meshes.remove(mesh_mod)
            terminate(global_undo)
            
            return{'FINISHED'}
    
    
    # space operator
    class Space(bpy.types.Operator):
        bl_idname = "mesh.looptools_space"
        bl_label = "Space"
        bl_description = "Space the vertices in a regular distrubtion on the loop"
        bl_options = {'REGISTER', 'UNDO'}
        
        influence = bpy.props.FloatProperty(name = "Influence",
            description = "Force of the tool",
            default = 100.0,
            min = 0.0,
            max = 100.0,
            precision = 1,
            subtype = 'PERCENTAGE')
        input = bpy.props.EnumProperty(name = "Input",
            items = (("all", "Parallel (all)", "Also use non-selected "\
                    "parallel loops as input"),
                ("selected", "Selection","Only use selected vertices as input")),
            description = "Loops that are spaced",
            default = 'selected')
        interpolation = bpy.props.EnumProperty(name = "Interpolation",
            items = (("cubic", "Cubic", "Natural cubic spline, smooth results"),
                ("linear", "Linear", "Vertices are projected on existing edges")),
            description = "Algorithm used for interpolation",
            default = 'cubic')
        
        @classmethod
        def poll(cls, context):
            ob = context.active_object
            return(ob and ob.type == 'MESH' and context.mode == 'EDIT_MESH')
        
        def draw(self, context):
            layout = self.layout
            col = layout.column()
            
            col.prop(self, "interpolation")
            col.prop(self, "input")
            col.separator()
            
            col.prop(self, "influence")
        
        def invoke(self, context, event):
            # load custom settings
            settings_load(self)
            return self.execute(context)
        
        def execute(self, context):
            # initialise
            global_undo, object, mesh = initialise()
            settings_write(self)
            # check cache to see if we can save time
            cached, single_loops, loops, derived, mapping = cache_read("Space",
                object, mesh, self.input, False)
            if cached:
                derived, mesh_mod = get_derived_mesh(object, mesh, context.scene)
            else:
                # find loops
                derived, mesh_mod, loops = get_connected_input(object, mesh,
                    context.scene, self.input)
                mapping = get_mapping(derived, mesh, mesh_mod, False, False, loops)
                loops = check_loops(loops, mapping, mesh_mod)
            
            # saving cache for faster execution next time
            if not cached:
                cache_write("Space", object, mesh, self.input, False, False, loops,
                    derived, mapping)
            
            move = []
            for loop in loops:
                # calculate splines and new positions
                if loop[1]: # circular
                    loop[0].append(loop[0][0])
                tknots, tpoints = space_calculate_t(mesh_mod, loop[0][:])
                splines = calculate_splines(self.interpolation, mesh_mod,
                    tknots, loop[0][:])
                move.append(space_calculate_verts(mesh_mod, self.interpolation,
                    tknots, tpoints, loop[0][:-1], splines))
            
            # move vertices to new locations
            move_verts(mesh, mapping, move, self.influence)
            
            # cleaning up 
            if derived:
                bpy.context.blend_data.meshes.remove(mesh_mod)
            terminate(global_undo)
            
            return{'FINISHED'}
    
    
    ##########################################
    ####### GUI and registration #############
    ##########################################
    
    # menu containing all tools
    class VIEW3D_MT_edit_mesh_looptools(bpy.types.Menu):
        bl_label = "LoopTools"
        
        def draw(self, context):
            layout = self.layout
            
    
    #        layout.operator("mesh.looptools_bridge", text="Bridge").loft = False
    
    Bart Crouch's avatar
    Bart Crouch committed
            layout.operator("mesh.looptools_circle")
            layout.operator("mesh.looptools_curve")
            layout.operator("mesh.looptools_flatten")
    
    #        layout.operator("mesh.looptools_bridge", text="Loft").loft = True
    
    Bart Crouch's avatar
    Bart Crouch committed
            layout.operator("mesh.looptools_relax")
            layout.operator("mesh.looptools_space")
    
    
    # panel containing all tools
    class VIEW3D_PT_tools_looptools(bpy.types.Panel):
        bl_space_type = 'VIEW_3D'
        bl_region_type = 'TOOLS'
        bl_context = "mesh_edit"
        bl_label = "LoopTools"
    
        def draw(self, context):
            layout = self.layout
            col = layout.column(align=True)
            lt = context.window_manager.looptools
            
            # bridge - first line
    
    #        split = col.split(percentage=0.15)
    #        if lt.display_bridge:
    #            split.prop(lt, "display_bridge", text="", icon='DOWNARROW_HLT')
    #        else:
    #            split.prop(lt, "display_bridge", text="", icon='RIGHTARROW')
    #        split.operator("mesh.looptools_bridge", text="Bridge").loft = False
    
    Bart Crouch's avatar
    Bart Crouch committed
            # bridge - settings
    
    #        if lt.display_bridge:
    #            box = col.column(align=True).box().column()
    
    Bart Crouch's avatar
    Bart Crouch committed
                #box.prop(self, "mode")
                
                # top row
    
    #            col_top = box.column(align=True)
    #            row = col_top.row(align=True)
    #            col_left = row.column(align=True)
    #            col_right = row.column(align=True)
    #            col_right.active = lt.bridge_segments != 1
    #            col_left.prop(lt, "bridge_segments")
    #            col_right.prop(lt, "bridge_min_width", text="")
    #            # bottom row
    #            bottom_left = col_left.row()
    #            bottom_left.active = lt.bridge_segments != 1
    #            bottom_left.prop(lt, "bridge_interpolation", text="")
    #            bottom_right = col_right.row()
    #            bottom_right.active = lt.bridge_interpolation == 'cubic'
    #            bottom_right.prop(lt, "bridge_cubic_strength")
    
    Bart Crouch's avatar
    Bart Crouch committed
                # boolean properties
    
    #            col_top.prop(lt, "bridge_remove_faces")
    
    Bart Crouch's avatar
    Bart Crouch committed
                
                # override properties
    
    #            col_top.separator()
    #            row = box.row(align = True)
    #            row.prop(lt, "bridge_twist")
    #            row.prop(lt, "bridge_reverse")
    
    Bart Crouch's avatar
    Bart Crouch committed
            
            # circle - first line
            split = col.split(percentage=0.15)
            if lt.display_circle:
                split.prop(lt, "display_circle", text="", icon='DOWNARROW_HLT')
            else:
                split.prop(lt, "display_circle", text="", icon='RIGHTARROW')
            split.operator("mesh.looptools_circle")
            # circle - settings
            if lt.display_circle:
                box = col.column(align=True).box().column()
                box.prop(lt, "circle_fit")
                box.separator()
                
                box.prop(lt, "circle_flatten")
                row = box.row(align=True)
                row.prop(lt, "circle_custom_radius")
                row_right = row.row(align=True)
                row_right.active = lt.circle_custom_radius
                row_right.prop(lt, "circle_radius", text="")
                box.prop(lt, "circle_regular")
                box.separator()
                
                box.prop(lt, "circle_influence")
            
            # curve - first line
            split = col.split(percentage=0.15)
            if lt.display_curve:
                split.prop(lt, "display_curve", text="", icon='DOWNARROW_HLT')
            else:
                split.prop(lt, "display_curve", text="", icon='RIGHTARROW')
            split.operator("mesh.looptools_curve")
            # curve - settings
            if lt.display_curve:
                box = col.column(align=True).box().column()
                box.prop(lt, "curve_interpolation")
                box.prop(lt, "curve_restriction")
                box.prop(lt, "curve_boundaries")
                box.prop(lt, "curve_regular")
                box.separator()
                
                box.prop(lt, "curve_influence")
            
            # flatten - first line
            split = col.split(percentage=0.15)
            if lt.display_flatten:
                split.prop(lt, "display_flatten", text="", icon='DOWNARROW_HLT')
            else:
                split.prop(lt, "display_flatten", text="", icon='RIGHTARROW')
            split.operator("mesh.looptools_flatten")
            # flatten - settings
            if lt.display_flatten:
                box = col.column(align=True).box().column()
                box.prop(lt, "flatten_plane")
                #box.prop(lt, "flatten_restriction")
                box.separator()
                
                box.prop(lt, "flatten_influence")
            
            # loft - first line
    
    #        split = col.split(percentage=0.15)
    #        if lt.display_loft:
    #            split.prop(lt, "display_loft", text="", icon='DOWNARROW_HLT')
    #        else:
    #            split.prop(lt, "display_loft", text="", icon='RIGHTARROW')
    #        split.operator("mesh.looptools_bridge", text="Loft").loft = True
    #        # loft - settings
    #        if lt.display_loft:
    #            box = col.column(align=True).box().column()
    #            #box.prop(self, "mode")
    #            
    #            # top row
    #            col_top = box.column(align=True)
    #            row = col_top.row(align=True)
    #            col_left = row.column(align=True)
    #            col_right = row.column(align=True)
    #            col_right.active = lt.bridge_segments != 1
    #            col_left.prop(lt, "bridge_segments")
    #            col_right.prop(lt, "bridge_min_width", text="")
    #            # bottom row
    #            bottom_left = col_left.row()
    #            bottom_left.active = lt.bridge_segments != 1
    #            bottom_left.prop(lt, "bridge_interpolation", text="")
    #            bottom_right = col_right.row()
    #            bottom_right.active = lt.bridge_interpolation == 'cubic'
    #            bottom_right.prop(lt, "bridge_cubic_strength")
    #            # boolean properties
    #            col_top.prop(lt, "bridge_remove_faces")
    #            col_top.prop(lt, "bridge_loft_loop")
    #            
    #            # override properties
    #            col_top.separator()
    #            row = box.row(align = True)
    #            row.prop(lt, "bridge_twist")
    #            row.prop(lt, "bridge_reverse")
    
    Bart Crouch's avatar
    Bart Crouch committed
            
            # relax - first line
            split = col.split(percentage=0.15)
            if lt.display_relax:
                split.prop(lt, "display_relax", text="", icon='DOWNARROW_HLT')
            else:
                split.prop(lt, "display_relax", text="", icon='RIGHTARROW')
            split.operator("mesh.looptools_relax")
            # relax - settings
            if lt.display_relax:
                box = col.column(align=True).box().column()
                box.prop(lt, "relax_interpolation")
                box.prop(lt, "relax_input")
                box.prop(lt, "relax_iterations")
                box.prop(lt, "relax_regular")
            
            # space - first line
            split = col.split(percentage=0.15)
            if lt.display_space:
                split.prop(lt, "display_space", text="", icon='DOWNARROW_HLT')
            else:
                split.prop(lt, "display_space", text="", icon='RIGHTARROW')
            split.operator("mesh.looptools_space")
            # space - settings
            if lt.display_space:
                box = col.column(align=True).box().column()
                box.prop(lt, "space_interpolation")
                box.prop(lt, "space_input")
                box.separator()
                
                box.prop(lt, "space_influence")
    
    
    # property group containing all properties for the gui in the panel
    class LoopToolsProps(bpy.types.PropertyGroup):
        """
        Fake module like class
        bpy.context.window_manager.looptools
        """
        
        # general display properties
    
    #    display_bridge = bpy.props.BoolProperty(name = "Bridge settings",
    #        description = "Display settings of the Bridge tool",
    #        default = False)
    
    Bart Crouch's avatar
    Bart Crouch committed
        display_circle = bpy.props.BoolProperty(name = "Circle settings",
            description = "Display settings of the Circle tool",
            default = False)
        display_curve = bpy.props.BoolProperty(name = "Curve settings",
            description = "Display settings of the Curve tool",
            default = False)
        display_flatten = bpy.props.BoolProperty(name = "Flatten settings",
            description = "Display settings of the Flatten tool",
            default = False)
    
    #    display_loft = bpy.props.BoolProperty(name = "Loft settings",
    #        description = "Display settings of the Loft tool",
    #        default = False)
    
    Bart Crouch's avatar
    Bart Crouch committed
        display_relax = bpy.props.BoolProperty(name = "Relax settings",
            description = "Display settings of the Relax tool",
            default = False)
        display_space = bpy.props.BoolProperty(name = "Space settings",
            description = "Display settings of the Space tool",
            default = False)
        
        # bridge properties
        bridge_cubic_strength = bpy.props.FloatProperty(name = "Strength",
            description = "Higher strength results in more fluid curves",
            default = 1.0,
            soft_min = -3.0,
            soft_max = 3.0)
        bridge_interpolation = bpy.props.EnumProperty(name = "Interpolation mode",
            items = (('cubic', "Cubic", "Gives curved results"),
                ('linear', "Linear", "Basic, fast, straight interpolation")),
            description = "Interpolation mode: algorithm used when creating "\
                "segments",
            default = 'cubic')
        bridge_loft = bpy.props.BoolProperty(name = "Loft",
            description = "Loft multiple loops, instead of considering them as "\
                "a multi-input for bridging",
            default = False)
        bridge_loft_loop = bpy.props.BoolProperty(name = "Loop",
            description = "Connect the first and the last loop with each other",
            default = False)
        bridge_min_width = bpy.props.IntProperty(name = "Minimum width",
            description = "Segments with an edge smaller than this are merged "\
                "(compared to base edge)",
            default = 0,
            min = 0,
            max = 100,
            subtype = 'PERCENTAGE')
        bridge_mode = bpy.props.EnumProperty(name = "Mode",
    
            items = (('basic', "Basic", "Fast algorithm"),
                     ('shortest', "Shortest edge", "Slower algorithm with " \
                                                   "better vertex matching")),
    
    Bart Crouch's avatar
    Bart Crouch committed
            description = "Algorithm used for bridging",
            default = 'shortest')
        bridge_remove_faces = bpy.props.BoolProperty(name = "Remove faces",
            description = "Remove faces that are internal after bridging",
            default = True)
        bridge_reverse = bpy.props.BoolProperty(name = "Reverse",
            description = "Manually override the direction in which the loops "\
    
                          "are bridged. Only use if the tool gives the wrong " \
                          "result",
    
    Bart Crouch's avatar
    Bart Crouch committed
            default = False)
        bridge_segments = bpy.props.IntProperty(name = "Segments",
            description = "Number of segments used to bridge the gap "\
                "(0 = automatic)",
            default = 1,
            min = 0,
            soft_max = 20)
        bridge_twist = bpy.props.IntProperty(name = "Twist",
            description = "Twist what vertices are connected to each other",
            default = 0)
        
        # circle properties
        circle_custom_radius = bpy.props.BoolProperty(name = "Radius",
            description = "Force a custom radius",
            default = False)
        circle_fit = bpy.props.EnumProperty(name = "Method",
            items = (("best", "Best fit", "Non-linear least squares"),
                ("inside", "Fit inside","Only move vertices towards the center")),
            description = "Method used for fitting a circle to the vertices",
            default = 'best')
        circle_flatten = bpy.props.BoolProperty(name = "Flatten",
            description = "Flatten the circle, instead of projecting it on the " \
                "mesh",
            default = True)
        circle_influence = bpy.props.FloatProperty(name = "Influence",
            description = "Force of the tool",
            default = 100.0,
            min = 0.0,
            max = 100.0,
            precision = 1,
            subtype = 'PERCENTAGE')
        circle_radius = bpy.props.FloatProperty(name = "Radius",
            description = "Custom radius for circle",
            default = 1.0,
            min = 0.0,
            soft_max = 1000.0)
        circle_regular = bpy.props.BoolProperty(name = "Regular",
            description = "Distribute vertices at constant distances along the " \
                "circle",
            default = True)
        
        # curve properties
        curve_boundaries = bpy.props.BoolProperty(name = "Boundaries",
            description = "Limit the tool to work within the boundaries of the "\
                "selected vertices",
            default = False)
        curve_influence = bpy.props.FloatProperty(name = "Influence",
            description = "Force of the tool",
            default = 100.0,
            min = 0.0,
            max = 100.0,
            precision = 1,
            subtype = 'PERCENTAGE')
        curve_interpolation = bpy.props.EnumProperty(name = "Interpolation",
            items = (("cubic", "Cubic", "Natural cubic spline, smooth results"),
                ("linear", "Linear", "Simple and fast linear algorithm")),
            description = "Algorithm used for interpolation",
            default = 'cubic')
        curve_regular = bpy.props.BoolProperty(name = "Regular",
            description = "Distribute vertices at constant distances along the" \
                "curve",
            default = True)
        curve_restriction = bpy.props.EnumProperty(name = "Restriction",
            items = (("none", "None", "No restrictions on vertex movement"),
                ("extrude", "Extrude only","Only allow extrusions (no "\
                    "indentations)"),
                ("indent", "Indent only", "Only allow indentation (no "\
                    "extrusions)")),
            description = "Restrictions on how the vertices can be moved",
            default = 'none')
        
        # flatten properties
        flatten_influence = bpy.props.FloatProperty(name = "Influence",
            description = "Force of the tool",
            default = 100.0,
            min = 0.0,
            max = 100.0,
            precision = 1,
            subtype = 'PERCENTAGE')
        flatten_plane = bpy.props.EnumProperty(name = "Plane",
            items = (("best_fit", "Best fit", "Calculate a best fitting plane"),
                ("normal", "Normal", "Derive plane from averaging vertex "\
                "normals"),
                ("view", "View", "Flatten on a plane perpendicular to the "\
                "viewing angle")),
            description = "Plane on which vertices are flattened",
            default = 'best_fit')
        flatten_restriction = bpy.props.EnumProperty(name = "Restriction",
            items = (("none", "None", "No restrictions on vertex movement"),
                ("bounding_box", "Bounding box", "Vertices are restricted to "\
                "movement inside the bounding box of the selection")),
            description = "Restrictions on how the vertices can be moved",
            default = 'none')
        
        # relax properties
        relax_input = bpy.props.EnumProperty(name = "Input",
            items = (("all", "Parallel (all)", "Also use non-selected "\
                    "parallel loops as input"),
                ("selected", "Selection","Only use selected vertices as input")),
            description = "Loops that are relaxed",
            default = 'selected')
        relax_interpolation = bpy.props.EnumProperty(name = "Interpolation",
            items = (("cubic", "Cubic", "Natural cubic spline, smooth results"),
                ("linear", "Linear", "Simple and fast linear algorithm")),
            description = "Algorithm used for interpolation",
            default = 'cubic')
        relax_iterations = bpy.props.EnumProperty(name = "Iterations",
            items = (("1", "1", "One"),
                ("3", "3", "Three"),
                ("5", "5", "Five"),
                ("10", "10", "Ten"),
                ("25", "25", "Twenty-five")),
            description = "Number of times the loop is relaxed",
            default = "1")
        relax_regular = bpy.props.BoolProperty(name = "Regular",
            description = "Distribute vertices at constant distances along the" \
                "loop",
            default = True)
        
        # space properties
        space_influence = bpy.props.FloatProperty(name = "Influence",
            description = "Force of the tool",
            default = 100.0,
            min = 0.0,
            max = 100.0,
            precision = 1,
            subtype = 'PERCENTAGE')
        space_input = bpy.props.EnumProperty(name = "Input",
            items = (("all", "Parallel (all)", "Also use non-selected "\
                    "parallel loops as input"),
                ("selected", "Selection","Only use selected vertices as input")),
            description = "Loops that are spaced",
            default = 'selected')
        space_interpolation = bpy.props.EnumProperty(name = "Interpolation",
            items = (("cubic", "Cubic", "Natural cubic spline, smooth results"),
                ("linear", "Linear", "Vertices are projected on existing edges")),
            description = "Algorithm used for interpolation",
            default = 'cubic')
    
    
    # draw function for integration in menus
    def menu_func(self, context):
        self.layout.menu("VIEW3D_MT_edit_mesh_looptools")
        self.layout.separator()
    
    
    # define classes for registration
    classes = [VIEW3D_MT_edit_mesh_looptools,
        VIEW3D_PT_tools_looptools,
        LoopToolsProps,
        Bridge,
        Circle,
        Curve,
        Flatten,
        Relax,
        Space]
    
    
    # registering and menu integration
    def register():
        for c in classes:
            bpy.utils.register_class(c)
        bpy.types.VIEW3D_MT_edit_mesh_specials.prepend(menu_func)
        bpy.types.WindowManager.looptools = bpy.props.PointerProperty(\
            type = LoopToolsProps)
    
    
    # unregistering and removing menus
    def unregister():
        for c in classes:
            bpy.utils.unregister_class(c)
        bpy.types.VIEW3D_MT_edit_mesh_specials.remove(menu_func)
        try:
            del bpy.types.WindowManager.looptools
        except:
            pass
    
    
    if __name__ == "__main__":
        register()