OverviewFilesCommitsIssuesDocsReleasesPeers

hardops / operators / booleans / editmode_inset.py

169 lines · python ref:
import bpy
import bmesh
from ... utility import addon
from ...ui_framework.operator_ui import Master


def edit_bool_inset(context, keep_cutters, outset, thickness, use_swap, use_self, threshold, solver):
    def select( geom):
        for g in geom:
            g.select = True

    def deselect(geom):
        for g in geom:
            g.select = False

    def reveal(geom):
        for g in geom:
            g.hide = False

    def hide(geom):
        for g in geom:
            g.hide = True

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

    geometry = bm.verts[:] + bm.edges[:] + bm.faces[:]
    visible = [g for g in geometry if not g.hide]

    target = [g for g in visible if not g.select]
    cutter = [g for g in visible if g.select]

    if keep_cutters:
        duplicate = bmesh.ops.duplicate(bm, geom=cutter)["geom"]
        hide(duplicate)

    inset = bmesh.ops.duplicate(bm, geom=target)["geom"]
    faces = [g for g in inset if type(g) == bmesh.types.BMFace]

    if outset:
        bmesh.ops.reverse_faces(bm, faces=faces)

    bmesh.ops.solidify(bm, geom=inset, thickness=float(thickness))
    bmesh.ops.inset_region(bm, faces=faces, thickness=0.00, depth=0.01, use_even_offset=True)

    hide(target)
    bmesh.update_edit_mesh(mesh)
    if bpy.app.version > (2, 83, 0):
        bpy.ops.mesh.intersect_boolean(operation='INTERSECT', use_swap=use_swap, use_self=use_self, threshold=threshold, solver=solver)
    else:
        bpy.ops.mesh.intersect_boolean(operation='INTERSECT', use_swap=use_swap, threshold=threshold)

    geometry = bm.verts[:] + bm.edges[:] + bm.faces[:]
    result = [g for g in geometry if not g.hide]

    reveal(target)
    select(result)
    operation = 'UNION' if outset else 'DIFFERENCE'
    if bpy.app.version > (2, 83, 0):
        bpy.ops.mesh.intersect_boolean(operation=operation, use_swap=use_swap, use_self=use_self, threshold=threshold, solver=solver)
    else:
        bpy.ops.mesh.intersect_boolean(operation=operation, use_swap=use_swap, threshold=threshold)

    if keep_cutters:
        geometry = bm.verts[:] + bm.edges[:] + bm.faces[:]
        deselect(geometry)
        reveal(duplicate)
        select(duplicate)

    bmesh.update_edit_mesh(mesh)
    return {'FINISHED'}


class HOPS_OT_EditBoolInset(bpy.types.Operator):
    bl_idname = "hops.edit_bool_inset"
    bl_label = "Hops Inset Boolean Edit Mode"
    bl_options = {'REGISTER', 'UNDO'}
    bl_description = """Inset Boolean in Edit Mode
LMB - Inset and remove cutters after use (DEFAULT)
LMB + Ctrl - Keep cutters after use
LMB + Shift - Outset"""

    keep_cutters: bpy.props.BoolProperty(
        name="Keep Cutters",
        description="Keep cutters after use",
        default=False)

    outset: bpy.props.BoolProperty(
        name="Outset",
        description="Use union instead of difference",
        default=False)

    thickness: bpy.props.FloatProperty(
        name="Thickness",
        description="How deep the inset should cut",
        default=0.10,
        min=0.00,
        soft_max=10.0,
        step=1,
        precision=3,)

    use_swap: bpy.props.BoolProperty(
        name="Swap",
        description="Swaps selection after boolean",
        default=False)

    use_self: bpy.props.BoolProperty(
        name="Self",
        description="Use on self",
        default=False)

    threshold: bpy.props.FloatProperty(
        name="Threshold",
        description="Threshold",
        default=0.001)

    called_ui = False

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        HOPS_OT_EditBoolInset.called_ui = False

    @classmethod
    def poll(cls, context):
        obj = context.active_object
        return obj and obj.mode == 'EDIT' and obj.type == 'MESH'

    def draw(self, context):
        layout = self.layout
        layout.use_property_split = True

        row = layout.row()
        row.prop(self, "keep_cutters")
        row.prop(self, "outset")
        layout.prop(self, "thickness")
        layout.separator()

        if bpy.app.version > (2, 83, 0):
            row = self.layout.row()
            row.prop(addon.preference().property, "boolean_solver", text='Solver', expand=True)
        layout.separator()
        layout.prop(self, 'use_swap')
        if bpy.app.version > (2, 83, 0):
            layout.prop(self, 'use_self')
        layout.prop(self, "keep_cutters")
        layout.prop(self, 'threshold')

    def invoke(self, context, event):
        self.keep_cutters = event.ctrl
        self.outset = event.shift
        return self.execute(context)

    def execute(self, context):

        # Operator UI
        if not HOPS_OT_EditBoolInset.called_ui:
            HOPS_OT_EditBoolInset.called_ui = True

            ui = Master()

            draw_data = [
                ["Inset Boolean"]]

            ui.receive_draw_data(draw_data=draw_data)
            ui.draw(draw_bg=addon.preference().ui.Hops_operator_draw_bg, draw_border=addon.preference().ui.Hops_operator_draw_border)

        return edit_bool_inset(context, self.keep_cutters, self.outset, self.thickness, self.use_swap, self.use_self, self.threshold, addon.preference().property.boolean_solver)