OverviewFilesCommitsIssuesDocsReleasesPeers

hardops / operators / meshtools / face_solver.py

174 lines · python ref:
import bpy, math, bmesh, mathutils
from mathutils import Vector, Matrix, Quaternion
from bpy.props import EnumProperty, IntProperty
from ... utility import addon
from ...utility import math as hops_math


solvers = [
    ("CORNER", "Corner", "", 1),
]


class HOPS_OT_Face_Solver(bpy.types.Operator):
    bl_idname = "hops.face_solver"
    bl_label = "Face Solver"
    bl_description = """Face Solver
    Solve faces for various retopo redirects
    Press H for help
    """
    bl_options = {"REGISTER", "UNDO"}

    solver : EnumProperty(items=solvers, name='Solvers', description='Algos', default='CORNER')\

    corner : IntProperty(name='Corner', description='Offset starting corner', default=1, min=1, max=6)

    @classmethod
    def poll(cls, context):
        if context.mode == 'EDIT_MESH':
            if context.active_object and context.active_object.type == 'MESH':
                return True
        return False


    def draw(self, layout):

        layout = self.layout
        layout.use_property_split = True

        col = layout.column()

        col.prop(self, 'solver')

        col.separator_spacer()

        if self.solver == 'CORNER':
            col.prop(self, 'corner')


    def execute(self, context):

        obj = context.active_object
        obj.update_from_editmode()
        mesh = obj.data
        bm = bmesh.from_edit_mesh(mesh)

        valid = True

        if self.solver == 'CORNER':
            valid = corner_solver(bm, self.corner - 1)

        bmesh.update_edit_mesh(mesh)
        if valid:
            return {'FINISHED'}
        return {'CANCELLED'}

# --- SOLVER : CORNER --- #

def corner_solver(bm, corner=0):

    # Face        
    face = bm.faces.active
    if not face:
        bpy.ops.hops.display_notification(info="No Active Face")
        return False

    # Clear Tags
    remove_tags(face)

    # Edges
    loops = connected_loops(face.loops[0])
    if len(loops) == 4:
        if corner > 3:
            corner = 3
        quad_corner(bm, face, loops, corner)

    elif len(loops) == 6:
        hexagon_corner(bm, face, loops, corner)

    else:
        bpy.ops.hops.display_notification(info="Supported : 4 Edges / 6 Edges")
        return False

    return True


def hexagon_corner(bm, face, loops, corner=0):
    
    # Center
    points = [v.co for l in loops for v in l.edge.verts ]
    bb = hops_math.coords_to_bounds(points)
    coords = (hops_math.coords_to_center(bb),)

    cv1 = loops[corner].link_loop_next.link_loop_next.link_loop_next.vert

    v1 = loops[corner].link_loop_next.vert
    v2 = loops[corner].link_loop_prev.vert

    split_face, split_loop = bmesh.utils.face_split(face, v1, v2, coords)
    cv2 = split_loop.link_loop_next.vert

    # Join
    verts = [cv1, cv2]
    bmesh.ops.connect_verts(bm, verts=verts)

    bpy.ops.mesh.select_all(action='DESELECT')
    split_face.select = True
    cv1.select = True


def quad_corner(bm, face, loops, corner=0):

    # Center
    coords = (face.calc_center_median(),)

    # Split
    split_verts = []
    i1 = corner
    i2 = corner + 1 if corner + 1 <= len(loops) - 1 else 0
    
    if i2 == 0:
        v1 = loops[i2].link_loop_prev.link_loop_prev.vert
    else:
        v1 = loops[i2].link_loop_next.link_loop_next.vert

    for i, loop in enumerate(loops):
        if i == i1 or i == i2:
            edge, vert = bmesh.utils.edge_split(loop.edge, loop.vert, .5)
            split_verts.append(vert)

    # Create Face
    split_face, split_loop = bmesh.utils.face_split(face, split_verts[0], split_verts[1], coords)
    v2 = split_loop.link_loop_next.vert

    # Join
    verts = [v1, v2]
    bmesh.ops.connect_verts(bm, verts=verts)

    bpy.ops.mesh.select_all(action='DESELECT')

    loop = v2.link_loops[0].link_loop_radial_next if i2 == 0 else v2.link_loops[0].link_loop_next
    loop.face.select = True
    bm.faces.active = loop.face

# --- UTILS --- #

def connected_loops(loop):
    loops = []
    while True:
        if loop in loops:
            break
        else:
            loops.append(loop)
            loop = loop.link_loop_next
            if not loop: break
    return loops


def remove_tags(face):
    for e in face.edges:
        e.tag = False
    for v in face.verts:
        v.tag = False