Skip to content
Snippets Groups Projects
mesh_looptools.py 186 KiB
Newer Older
  • Learn to ignore specific revisions
  •         items=(("project", "Project", "Project vertices onto the stroke, "
                     "using vertex normals and connected edges"),
                    ("irregular", "Spread", "Distribute vertices along the full "
                    "stroke, retaining relative distances between the vertices"),
                    ("regular", "Spread evenly", "Distribute vertices at regular "
                    "distances along the full stroke")),
    
            description="Method of distributing the vertices over the "
    
            default='regular'
            )
    
    Bart Crouch's avatar
    Bart Crouch committed
        @classmethod
        def poll(cls, context):
            ob = context.active_object
    
            return(ob and ob.type == 'MESH' and context.mode == 'EDIT_MESH')
    
    Bart Crouch's avatar
    Bart Crouch committed
        def draw(self, context):
    
            looptools =  context.window_manager.looptools
    
    Bart Crouch's avatar
    Bart Crouch committed
            layout = self.layout
            col = layout.column()
    
    Bart Crouch's avatar
    Bart Crouch committed
            col.prop(self, "method")
    
            col_conv = col.column(align=True)
            col_conv.prop(self, "conversion", text="")
            if self.conversion == 'distance':
                col_conv.prop(self, "conversion_distance")
            elif self.conversion == 'limit_vertices':
                row = col_conv.row(align=True)
                row.prop(self, "conversion_min", text="Min")
                row.prop(self, "conversion_max", text="Max")
            elif self.conversion == 'vertices':
                col_conv.prop(self, "conversion_vertices")
    
    Bart Crouch's avatar
    Bart Crouch committed
            col.separator()
    
            col_move = col.column(align=True)
            row = col_move.row(align=True)
            if self.lock_x:
    
                row.prop(self, "lock_x", text="X", icon='LOCKED')
    
                row.prop(self, "lock_x", text="X", icon='UNLOCKED')
    
            if self.lock_y:
    
                row.prop(self, "lock_y", text="Y", icon='LOCKED')
    
                row.prop(self, "lock_y", text="Y", icon='UNLOCKED')
    
            if self.lock_z:
    
                row.prop(self, "lock_z", text="Z", icon='LOCKED')
    
                row.prop(self, "lock_z", text="Z", icon='UNLOCKED')
    
            col_move.prop(self, "influence")
    
    meta-androcto's avatar
    meta-androcto committed
            col.separator()
    
            if looptools.gstretch_use_guide == "Annotation":
                col.operator("remove.annotation", text="Delete annotation strokes")
            if looptools.gstretch_use_guide == "GPencil":
                col.operator("remove.gp", text="Delete GPencil strokes")
    
    Bart Crouch's avatar
    Bart Crouch committed
        def invoke(self, context, event):
    
            # flush cached strokes
            if 'Gstretch' in looptools_cache:
                looptools_cache['Gstretch']['single_loops'] = []
    
    Bart Crouch's avatar
    Bart Crouch committed
            # load custom settings
            settings_load(self)
            return self.execute(context)
    
    Bart Crouch's avatar
    Bart Crouch committed
        def execute(self, context):
            # initialise
    
    Bart Crouch's avatar
    Bart Crouch committed
            settings_write(self)
    
    Bart Crouch's avatar
    Bart Crouch committed
            # check cache to see if we can save time
    
            cached, safe_strokes, loops, derived, mapping = cache_read("Gstretch",
    
                object, bm, False, False)
    
    Bart Crouch's avatar
    Bart Crouch committed
            if cached:
    
                straightening = False
    
                if safe_strokes:
                    strokes = gstretch_safe_to_true_strokes(safe_strokes)
                # cached strokes were flushed (see operator's invoke function)
    
                    strokes = gstretch_get_strokes(self, context)
    
                    # straightening function (no GP) -> loops ignore modifiers
                    straightening = True
                    derived = False
                    bm_mod = bm.copy()
    
                    bm_mod.verts.ensure_lookup_table()
                    bm_mod.edges.ensure_lookup_table()
                    bm_mod.faces.ensure_lookup_table()
    
                    strokes = gstretch_get_fake_strokes(object, bm_mod, loops)
    
                if not straightening:
    
                    derived, bm_mod = get_derived_bmesh(object, bm, False)
    
    Bart Crouch's avatar
    Bart Crouch committed
            else:
    
                # get loops and strokes
    
                    # find loops
    
                    derived, bm_mod, loops = get_connected_input(object, bm, False, input='selected')
    
                    mapping = get_mapping(derived, bm, bm_mod, False, False, loops)
                    loops = check_loops(loops, mapping, bm_mod)
                    # get strokes
    
                    strokes = gstretch_get_strokes(self, context)
    
                    # straightening function (no GP) -> loops ignore modifiers
                    derived = False
                    mapping = False
                    bm_mod = bm.copy()
    
                    bm_mod.verts.ensure_lookup_table()
                    bm_mod.edges.ensure_lookup_table()
                    bm_mod.faces.ensure_lookup_table()
    
                    edge_keys = [
                        edgekey(edge) for edge in bm_mod.edges if
                        edge.select and not edge.hide
                        ]
    
                    loops = get_connected_selections(edge_keys)
                    loops = check_loops(loops, mapping, bm_mod)
                    # create fake strokes
    
                    strokes = gstretch_get_fake_strokes(object, bm_mod, loops)
    
    Bart Crouch's avatar
    Bart Crouch committed
            # saving cache for faster execution next time
            if not cached:
    
                if strokes:
                    safe_strokes = gstretch_true_to_safe_strokes(strokes)
                else:
                    safe_strokes = []
    
                cache_write("Gstretch", object, bm, False, False,
    
                    safe_strokes, loops, derived, mapping)
    
    
    Bart Crouch's avatar
    Bart Crouch committed
            # pair loops and strokes
            ls_pairs = gstretch_match_loops_strokes(loops, strokes, object, bm_mod)
            ls_pairs = gstretch_align_pairs(ls_pairs, object, bm_mod, self.method)
    
    Bart Crouch's avatar
    Bart Crouch committed
            move = []
    
            if not loops:
                # no selected geometry, convert GP to verts
                if strokes:
                    move.append(gstretch_create_verts(object, bm, strokes,
                        self.method, self.conversion, self.conversion_distance,
                        self.conversion_max, self.conversion_min,
                        self.conversion_vertices))
                    for stroke in strokes:
                        gstretch_erase_stroke(stroke, context)
            elif ls_pairs:
    
    Bart Crouch's avatar
    Bart Crouch committed
                for (loop, stroke) in ls_pairs:
                    move.append(gstretch_calculate_verts(loop, stroke, object,
                        bm_mod, self.method))
                    if self.delete_strokes:
    
                        if type(stroke) != bpy.types.GPencilStroke:
                            # in case of cached fake stroke, get the real one
    
                                strokes = gstretch_get_strokes(self, context)
    
                                if loops and strokes:
                                    ls_pairs = gstretch_match_loops_strokes(loops,
                                        strokes, object, bm_mod)
                                    ls_pairs = gstretch_align_pairs(ls_pairs,
                                        object, bm_mod, self.method)
                                    for (l, s) in ls_pairs:
                                        if l == loop:
                                            stroke = s
                                            break
    
    Bart Crouch's avatar
    Bart Crouch committed
                        gstretch_erase_stroke(stroke, context)
    
    Bart Crouch's avatar
    Bart Crouch committed
            # move vertices to new locations
    
            if self.lock_x or self.lock_y or self.lock_z:
                lock = [self.lock_x, self.lock_y, self.lock_z]
            else:
                lock = False
    
            bmesh.update_edit_mesh(object.data, loop_triangles=True, destructive=True)
    
            move_verts(object, bm, mapping, move, lock, self.influence)
    
    Bart Crouch's avatar
    Bart Crouch committed
            if derived:
                bm_mod.free()
    
    Bart Crouch's avatar
    Bart Crouch committed
            return{'FINISHED'}
    
    
    
    class Relax(Operator):
    
        bl_idname = "mesh.looptools_relax"
        bl_label = "Relax"
        bl_description = "Relax the loop, so it is smoother"
        bl_options = {'REGISTER', 'UNDO'}
    
    florianfelix's avatar
    florianfelix committed
        input: 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'
            )
    
    florianfelix's avatar
    florianfelix committed
        interpolation: 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'
            )
    
    florianfelix's avatar
    florianfelix committed
        iterations: 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"
            )
    
    florianfelix's avatar
    florianfelix committed
        regular: 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
    
            settings_write(self)
            # check cache to see if we can save time
            cached, single_loops, loops, derived, mapping = cache_read("Relax",
                object, bm, self.input, False)
            if cached:
    
                derived, bm_mod = get_derived_bmesh(object, bm, False)
    
                derived, bm_mod, loops = get_connected_input(object, bm, False, self.input)
    
                mapping = get_mapping(derived, bm, bm_mod, False, False, loops)
                loops = check_loops(loops, mapping, bm_mod)
            knots, points = relax_calculate_knots(loops)
    
            # saving cache for faster execution next time
            if not cached:
                cache_write("Relax", object, bm, self.input, False, False, loops,
                    derived, mapping)
    
            for iteration in range(int(self.iterations)):
                # calculate splines and new positions
                tknots, tpoints = relax_calculate_t(bm_mod, knots, points,
                    self.regular)
                splines = []
                for i in range(len(knots)):
                    splines.append(calculate_splines(self.interpolation, bm_mod,
                        tknots[i], knots[i]))
                move = [relax_calculate_verts(bm_mod, self.interpolation,
                    tknots, knots, tpoints, points, splines)]
    
                move_verts(object, bm, mapping, move, False, -1)
    
            # cleaning up
            if derived:
                bm_mod.free()
    
    class Space(Operator):
    
        bl_idname = "mesh.looptools_space"
        bl_label = "Space"
    
        bl_description = "Space the vertices in a regular distribution on the loop"
    
        bl_options = {'REGISTER', 'UNDO'}
    
    florianfelix's avatar
    florianfelix committed
        influence: FloatProperty(
    
            name="Influence",
            description="Force of the tool",
            default=100.0,
            min=0.0,
            max=100.0,
            precision=1,
            subtype='PERCENTAGE'
            )
    
    florianfelix's avatar
    florianfelix committed
        input: 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'
            )
    
    florianfelix's avatar
    florianfelix committed
        interpolation: 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'
            )
    
    florianfelix's avatar
    florianfelix committed
        lock_x: BoolProperty(
    
            name="Lock X",
            description="Lock editing of the x-coordinate",
            default=False
            )
    
    florianfelix's avatar
    florianfelix committed
        lock_y: BoolProperty(
    
            name="Lock Y",
            description="Lock editing of the y-coordinate",
            default=False
            )
    
    florianfelix's avatar
    florianfelix committed
        lock_z: BoolProperty(
    
            name="Lock Z",
            description="Lock editing of the z-coordinate",
            default=False
            )
    
        @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_move = col.column(align=True)
            row = col_move.row(align=True)
            if self.lock_x:
    
                row.prop(self, "lock_x", text="X", icon='LOCKED')
    
                row.prop(self, "lock_x", text="X", icon='UNLOCKED')
    
            if self.lock_y:
    
                row.prop(self, "lock_y", text="Y", icon='LOCKED')
    
                row.prop(self, "lock_y", text="Y", icon='UNLOCKED')
    
            if self.lock_z:
    
                row.prop(self, "lock_z", text="Z", icon='LOCKED')
    
                row.prop(self, "lock_z", text="Z", icon='UNLOCKED')
    
            col_move.prop(self, "influence")
    
        def invoke(self, context, event):
            # load custom settings
            settings_load(self)
            return self.execute(context)
    
        def execute(self, context):
            # initialise
    
            settings_write(self)
            # check cache to see if we can save time
            cached, single_loops, loops, derived, mapping = cache_read("Space",
                object, bm, self.input, False)
            if cached:
    
                derived, bm_mod = get_derived_bmesh(object, bm, True)
    
                derived, bm_mod, loops = get_connected_input(object, bm, True, self.input)
    
                mapping = get_mapping(derived, bm, bm_mod, False, False, loops)
                loops = check_loops(loops, mapping, bm_mod)
    
            # saving cache for faster execution next time
            if not cached:
                cache_write("Space", object, bm, 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(bm_mod, loop[0][:])
                splines = calculate_splines(self.interpolation, bm_mod,
                    tknots, loop[0][:])
                move.append(space_calculate_verts(bm_mod, self.interpolation,
                    tknots, tpoints, loop[0][:-1], splines))
            # move vertices to new locations
    
            if self.lock_x or self.lock_y or self.lock_z:
                lock = [self.lock_x, self.lock_y, self.lock_z]
            else:
                lock = False
            move_verts(object, bm, mapping, move, lock, self.influence)
    
            # cleaning up
            if derived:
                bm_mod.free()
    
            cache_delete("Space")
    
    # ########################################
    # ##### GUI and registration #############
    # ########################################
    
    
    # menu containing all tools
    
    class VIEW3D_MT_edit_mesh_looptools(Menu):
    
        bl_label = "LoopTools"
    
        def draw(self, context):
            layout = self.layout
    
    Campbell Barton's avatar
    Campbell Barton committed
            layout.operator("mesh.looptools_bridge", text="Bridge").loft = False
    
            layout.operator("mesh.looptools_circle")
            layout.operator("mesh.looptools_curve")
            layout.operator("mesh.looptools_flatten")
    
    Bart Crouch's avatar
    Bart Crouch committed
            layout.operator("mesh.looptools_gstretch")
    
    Campbell Barton's avatar
    Campbell Barton committed
            layout.operator("mesh.looptools_bridge", text="Loft").loft = True
    
            layout.operator("mesh.looptools_relax")
            layout.operator("mesh.looptools_space")
    
    
    # panel containing all tools
    
    class VIEW3D_PT_tools_looptools(Panel):
    
        bl_space_type = 'VIEW_3D'
    
    florianfelix's avatar
    florianfelix committed
        bl_region_type = 'UI'
    
        bl_context = "mesh_edit"
        bl_label = "LoopTools"
    
    Brendon Murphy's avatar
    Brendon Murphy committed
        bl_options = {'DEFAULT_CLOSED'}
    
    
        def draw(self, context):
            layout = self.layout
            col = layout.column(align=True)
            lt = context.window_manager.looptools
    
    florianfelix's avatar
    florianfelix committed
            split = col.split(factor=0.15, align=True)
    
            if lt.display_bridge:
                split.prop(lt, "display_bridge", text="", icon='DOWNARROW_HLT')
            else:
                split.prop(lt, "display_bridge", text="", icon='RIGHTARROW')
    
    Campbell Barton's avatar
    Campbell Barton committed
            split.operator("mesh.looptools_bridge", text="Bridge").loft = False
    
            # bridge - settings
            if lt.display_bridge:
                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")
    
                # override properties
                col_top.separator()
    
                row = box.row(align=True)
    
                row.prop(lt, "bridge_twist")
                row.prop(lt, "bridge_reverse")
    
    florianfelix's avatar
    florianfelix committed
            split = col.split(factor=0.15, align=True)
    
            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()
    
                col_move = box.column(align=True)
                row = col_move.row(align=True)
                if lt.circle_lock_x:
    
                    row.prop(lt, "circle_lock_x", text="X", icon='LOCKED')
    
                    row.prop(lt, "circle_lock_x", text="X", icon='UNLOCKED')
    
                if lt.circle_lock_y:
    
                    row.prop(lt, "circle_lock_y", text="Y", icon='LOCKED')
    
                    row.prop(lt, "circle_lock_y", text="Y", icon='UNLOCKED')
    
                if lt.circle_lock_z:
    
                    row.prop(lt, "circle_lock_z", text="Z", icon='LOCKED')
    
                    row.prop(lt, "circle_lock_z", text="Z", icon='UNLOCKED')
    
                col_move.prop(lt, "circle_influence")
    
    florianfelix's avatar
    florianfelix committed
            split = col.split(factor=0.15, align=True)
    
            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()
    
                col_move = box.column(align=True)
                row = col_move.row(align=True)
                if lt.curve_lock_x:
    
                    row.prop(lt, "curve_lock_x", text="X", icon='LOCKED')
    
                    row.prop(lt, "curve_lock_x", text="X", icon='UNLOCKED')
    
                if lt.curve_lock_y:
    
                    row.prop(lt, "curve_lock_y", text="Y", icon='LOCKED')
    
                    row.prop(lt, "curve_lock_y", text="Y", icon='UNLOCKED')
    
                if lt.curve_lock_z:
    
                    row.prop(lt, "curve_lock_z", text="Z", icon='LOCKED')
    
                    row.prop(lt, "curve_lock_z", text="Z", icon='UNLOCKED')
    
                col_move.prop(lt, "curve_influence")
    
            # flatten - first line
    
    florianfelix's avatar
    florianfelix committed
            split = col.split(factor=0.15, align=True)
    
            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")
    
                col_move = box.column(align=True)
                row = col_move.row(align=True)
                if lt.flatten_lock_x:
    
                    row.prop(lt, "flatten_lock_x", text="X", icon='LOCKED')
    
                    row.prop(lt, "flatten_lock_x", text="X", icon='UNLOCKED')
    
                if lt.flatten_lock_y:
    
                    row.prop(lt, "flatten_lock_y", text="Y", icon='LOCKED')
    
                    row.prop(lt, "flatten_lock_y", text="Y", icon='UNLOCKED')
    
                if lt.flatten_lock_z:
    
                    row.prop(lt, "flatten_lock_z", text="Z", icon='LOCKED')
    
                    row.prop(lt, "flatten_lock_z", text="Z", icon='UNLOCKED')
    
                col_move.prop(lt, "flatten_influence")
    
    Bart Crouch's avatar
    Bart Crouch committed
            # gstretch - first line
    
    florianfelix's avatar
    florianfelix committed
            split = col.split(factor=0.15, align=True)
    
    Bart Crouch's avatar
    Bart Crouch committed
            if lt.display_gstretch:
                split.prop(lt, "display_gstretch", text="", icon='DOWNARROW_HLT')
            else:
                split.prop(lt, "display_gstretch", text="", icon='RIGHTARROW')
            split.operator("mesh.looptools_gstretch")
            # gstretch settings
            if lt.display_gstretch:
                box = col.column(align=True).box().column()
    
                box.prop(lt, "gstretch_use_guide")
                if lt.gstretch_use_guide == "GPencil":
                    box.prop(lt, "gstretch_guide")
    
    Bart Crouch's avatar
    Bart Crouch committed
                box.prop(lt, "gstretch_method")
    
                col_conv = box.column(align=True)
                col_conv.prop(lt, "gstretch_conversion", text="")
                if lt.gstretch_conversion == 'distance':
                    col_conv.prop(lt, "gstretch_conversion_distance")
                elif lt.gstretch_conversion == 'limit_vertices':
                    row = col_conv.row(align=True)
                    row.prop(lt, "gstretch_conversion_min", text="Min")
                    row.prop(lt, "gstretch_conversion_max", text="Max")
                elif lt.gstretch_conversion == 'vertices':
                    col_conv.prop(lt, "gstretch_conversion_vertices")
                box.separator()
    
                col_move = box.column(align=True)
                row = col_move.row(align=True)
                if lt.gstretch_lock_x:
    
                    row.prop(lt, "gstretch_lock_x", text="X", icon='LOCKED')
    
                    row.prop(lt, "gstretch_lock_x", text="X", icon='UNLOCKED')
    
                if lt.gstretch_lock_y:
    
                    row.prop(lt, "gstretch_lock_y", text="Y", icon='LOCKED')
    
                    row.prop(lt, "gstretch_lock_y", text="Y", icon='UNLOCKED')
    
                if lt.gstretch_lock_z:
    
                    row.prop(lt, "gstretch_lock_z", text="Z", icon='LOCKED')
    
                    row.prop(lt, "gstretch_lock_z", text="Z", icon='UNLOCKED')
    
                col_move.prop(lt, "gstretch_influence")
    
                if lt.gstretch_use_guide == "Annotation":
                    box.operator("remove.annotation", text="Delete Annotation Strokes")
                if lt.gstretch_use_guide == "GPencil":
                    box.operator("remove.gp", text="Delete GPencil Strokes")
    
    florianfelix's avatar
    florianfelix committed
            split = col.split(factor=0.15, align=True)
    
            if lt.display_loft:
                split.prop(lt, "display_loft", text="", icon='DOWNARROW_HLT')
            else:
                split.prop(lt, "display_loft", text="", icon='RIGHTARROW')
    
    Campbell Barton's avatar
    Campbell Barton committed
            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")
    
    florianfelix's avatar
    florianfelix committed
            split = col.split(factor=0.15, align=True)
    
            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")
    
    florianfelix's avatar
    florianfelix committed
            split = col.split(factor=0.15, align=True)
    
            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()
    
                col_move = box.column(align=True)
                row = col_move.row(align=True)
                if lt.space_lock_x:
    
                    row.prop(lt, "space_lock_x", text="X", icon='LOCKED')
    
                    row.prop(lt, "space_lock_x", text="X", icon='UNLOCKED')
    
                if lt.space_lock_y:
    
                    row.prop(lt, "space_lock_y", text="Y", icon='LOCKED')
    
                    row.prop(lt, "space_lock_y", text="Y", icon='UNLOCKED')
    
                if lt.space_lock_z:
    
                    row.prop(lt, "space_lock_z", text="Z", icon='LOCKED')
    
                    row.prop(lt, "space_lock_z", text="Z", icon='UNLOCKED')
    
                col_move.prop(lt, "space_influence")
    
    
    
    # property group containing all properties for the gui in the panel
    
    class LoopToolsProps(PropertyGroup):
    
        """
        Fake module like class
        bpy.context.window_manager.looptools
        """
        # general display properties
    
    florianfelix's avatar
    florianfelix committed
        display_bridge: BoolProperty(
    
            name="Bridge settings",
            description="Display settings of the Bridge tool",
            default=False
            )
    
    florianfelix's avatar
    florianfelix committed
        display_circle: BoolProperty(
    
            name="Circle settings",
            description="Display settings of the Circle tool",
            default=False
            )
    
    florianfelix's avatar
    florianfelix committed
        display_curve: BoolProperty(
    
            name="Curve settings",
            description="Display settings of the Curve tool",
            default=False
            )
    
    florianfelix's avatar
    florianfelix committed
        display_flatten: BoolProperty(
    
            name="Flatten settings",
            description="Display settings of the Flatten tool",
            default=False
            )
    
    florianfelix's avatar
    florianfelix committed
        display_gstretch: BoolProperty(
    
            name="Gstretch settings",
            description="Display settings of the Gstretch tool",
            default=False
            )
    
    florianfelix's avatar
    florianfelix committed
        display_loft: BoolProperty(
    
            name="Loft settings",
            description="Display settings of the Loft tool",
            default=False
            )
    
    florianfelix's avatar
    florianfelix committed
        display_relax: BoolProperty(
    
            name="Relax settings",
            description="Display settings of the Relax tool",
            default=False
            )
    
    florianfelix's avatar
    florianfelix committed
        display_space: BoolProperty(
    
            name="Space settings",
            description="Display settings of the Space tool",
            default=False
            )
    
    florianfelix's avatar
    florianfelix committed
        bridge_cubic_strength: FloatProperty(
    
            name="Strength",
            description="Higher strength results in more fluid curves",
            default=1.0,
            soft_min=-3.0,
            soft_max=3.0
            )
    
    florianfelix's avatar
    florianfelix committed
        bridge_interpolation: 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'
            )
    
    florianfelix's avatar
    florianfelix committed
        bridge_loft: BoolProperty(
    
            name="Loft",
            description="Loft multiple loops, instead of considering them as "
                        "a multi-input for bridging",
            default=False
            )
    
    florianfelix's avatar
    florianfelix committed
        bridge_loft_loop: BoolProperty(
    
            name="Loop",
            description="Connect the first and the last loop with each other",
            default=False
            )
    
    florianfelix's avatar
    florianfelix committed
        bridge_min_width: 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'
            )
    
    florianfelix's avatar
    florianfelix committed
        bridge_mode: EnumProperty(
    
            name="Mode",
            items=(('basic', "Basic", "Fast algorithm"),
                     ('shortest', "Shortest edge", "Slower algorithm with "
    
                                                   "better vertex matching")),
    
            description="Algorithm used for bridging",
            default='shortest'
            )
    
    florianfelix's avatar
    florianfelix committed
        bridge_remove_faces: BoolProperty(
    
            name="Remove faces",
            description="Remove faces that are internal after bridging",
            default=True
            )
    
    florianfelix's avatar
    florianfelix committed
        bridge_reverse: BoolProperty(
    
            name="Reverse",
            description="Manually override the direction in which the loops "
                        "are bridged. Only use if the tool gives the wrong result",
            default=False
            )
    
    florianfelix's avatar
    florianfelix committed
        bridge_segments: IntProperty(
    
            name="Segments",
            description="Number of segments used to bridge the gap (0=automatic)",
            default=1,
            min=0,
            soft_max=20
            )
    
    florianfelix's avatar
    florianfelix committed
        bridge_twist: IntProperty(
    
            name="Twist",
            description="Twist what vertices are connected to each other",
            default=0
            )
    
    florianfelix's avatar
    florianfelix committed
        circle_custom_radius: BoolProperty(
    
            name="Radius",
            description="Force a custom radius",
            default=False
            )
    
    florianfelix's avatar
    florianfelix committed
        circle_fit: 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'
            )
    
    florianfelix's avatar
    florianfelix committed
        circle_flatten: BoolProperty(
    
            name="Flatten",
            description="Flatten the circle, instead of projecting it on the mesh",
            default=True
            )
    
    florianfelix's avatar
    florianfelix committed
        circle_influence: FloatProperty(
    
            name="Influence",
            description="Force of the tool",
            default=100.0,
            min=0.0,
            max=100.0,
            precision=1,
            subtype='PERCENTAGE'
            )
    
    florianfelix's avatar
    florianfelix committed
        circle_lock_x: BoolProperty(
    
            name="Lock X",
            description="Lock editing of the x-coordinate",
            default=False
            )
    
    florianfelix's avatar
    florianfelix committed
        circle_lock_y: BoolProperty(
    
            name="Lock Y",
            description="Lock editing of the y-coordinate",
            default=False
            )
    
    florianfelix's avatar
    florianfelix committed
        circle_lock_z: BoolProperty(
    
            name="Lock Z",
            description="Lock editing of the z-coordinate",
            default=False
            )
    
    florianfelix's avatar
    florianfelix committed
        circle_radius: FloatProperty(
    
            name="Radius",
            description="Custom radius for circle",
            default=1.0,
            min=0.0,
            soft_max=1000.0
            )
    
    florianfelix's avatar
    florianfelix committed
        circle_regular: BoolProperty(
    
            name="Regular",
            description="Distribute vertices at constant distances along the circle",
            default=True
            )
    
        circle_angle: FloatProperty(
            name="Angle",
            description="Rotate a circle by an angle",
            unit='ROTATION',
            default=math.radians(0.0),
            soft_min=math.radians(-360.0),
            soft_max=math.radians(360.0)
            )
    
    florianfelix's avatar
    florianfelix committed
        curve_boundaries: BoolProperty(
    
            name="Boundaries",
            description="Limit the tool to work within the boundaries of the "
                        "selected vertices",
            default=False
            )
    
    florianfelix's avatar
    florianfelix committed
        curve_influence: FloatProperty(
    
            name="Influence",
            description="Force of the tool",
            default=100.0,
            min=0.0,
            max=100.0,
            precision=1,
            subtype='PERCENTAGE'
            )
    
    florianfelix's avatar
    florianfelix committed
        curve_interpolation: 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'
            )
    
    florianfelix's avatar
    florianfelix committed
        curve_lock_x: BoolProperty(
    
            name="Lock X",
            description="Lock editing of the x-coordinate",
            default=False
            )
    
    florianfelix's avatar
    florianfelix committed
        curve_lock_y: BoolProperty(
    
            name="Lock Y",
            description="Lock editing of the y-coordinate",
            default=False
            )
    
    florianfelix's avatar
    florianfelix committed
        curve_lock_z: BoolProperty(
    
            name="Lock Z",
            description="Lock editing of the z-coordinate",
            default=False
            )
    
    florianfelix's avatar
    florianfelix committed
        curve_regular: BoolProperty(
    
            name="Regular",
            description="Distribute vertices at constant distances along the curve",
            default=True
            )
    
    florianfelix's avatar
    florianfelix committed
        curve_restriction: 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'
            )
    
    florianfelix's avatar
    florianfelix committed
        flatten_influence: FloatProperty(
    
            name="Influence",
            description="Force of the tool",
            default=100.0,
            min=0.0,
            max=100.0,
            precision=1,
            subtype='PERCENTAGE'
            )
    
    florianfelix's avatar
    florianfelix committed
        flatten_lock_x: BoolProperty(
    
            name="Lock X",
            description="Lock editing of the x-coordinate",
            default=False)
    
    florianfelix's avatar
    florianfelix committed
        flatten_lock_y: BoolProperty(name="Lock Y",
    
            description="Lock editing of the y-coordinate",
            default=False
            )
    
    florianfelix's avatar
    florianfelix committed
        flatten_lock_z: BoolProperty(
    
            name="Lock Z",
            description="Lock editing of the z-coordinate",
            default=False
            )
    
    florianfelix's avatar
    florianfelix committed
        flatten_plane: EnumProperty(
    
            name="Plane",
            items=(("best_fit", "Best fit", "Calculate a best fitting plane"),
                ("normal", "Normal", "Derive plane from averaging vertex "
    
                ("view", "View", "Flatten on a plane perpendicular to the "
    
            description="Plane on which vertices are flattened",
            default='best_fit'
            )
    
    florianfelix's avatar
    florianfelix committed
        flatten_restriction: 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'
            )
    
    Bart Crouch's avatar
    Bart Crouch committed
        # gstretch properties
    
    florianfelix's avatar
    florianfelix committed
        gstretch_conversion: EnumProperty(
    
            name="Conversion",
            items=(("distance", "Distance", "Set the distance between vertices "
    
                ("limit_vertices", "Limit vertices", "Set the minimum and maximum "
    
                "number of vertices that converted GP strokes will have"),
    
                ("vertices", "Exact vertices", "Set the exact number of vertices "
    
                "that converted strokes will have. Short strokes "
    
                "with few points may contain less vertices than this number."),
    
                ("none", "No simplification", "Convert each point "
    
            description="If strokes are converted to geometry, "
    
                        "use this simplification method",
            default='limit_vertices'
            )
    
    florianfelix's avatar
    florianfelix committed
        gstretch_conversion_distance: FloatProperty(
    
            name="Distance",
            description="Absolute distance between vertices along the converted "
    
            default=0.1,
            min=0.000001,
            soft_min=0.01,
            soft_max=100
            )
    
    florianfelix's avatar
    florianfelix committed
        gstretch_conversion_max: IntProperty(