OverviewFilesCommitsIssuesDocsReleasesPeers

hardops / operators / meshtools / mesh_clean.py

160 lines · python ref:
import bpy
import bmesh
from math import degrees
import bpy.utils.previews
from ... utility import addon
from ...ui_framework.operator_ui import Master


class HOPS_OT_CleanMeshOperator(bpy.types.Operator):
    bl_idname      = "view3d.clean_mesh"
    bl_label       = "Limited Dissolve"
    bl_options     = {'REGISTER', 'UNDO'}
    bl_description = """Clean Mesh

Cleans mesh of Coplanar / Colinear / Degenerate / Duplicate FACES, EDGES and VERTS
Advanced selection options in F6"""

    text      = "Limited Dissolve Removed"
    op_tag    = "Limited Dissolve / Remove Doubles"
    op_detail = "Angled Doubles Dissolved"

    called_ui = False

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

        HOPS_OT_CleanMeshOperator.called_ui = False


    @classmethod
    def poll(cls, context):
        return context.active_object and context.active_object.mode in {'OBJECT', 'EDIT'}


    def draw(self, context):

        layout = self.layout
        box = layout.box()
        row = box.row()

        row.prop(addon.preference().property, 'meshclean_mode', expand=True)
        box.prop(addon.preference().property, 'meshclean_dissolve_angle', text="Limited Dissolve Angle")
        box.prop(addon.preference().property, 'meshclean_remove_threshold', text="Remove Threshold")
        box.prop(addon.preference().property, 'meshclean_degenerate_iter', text="Degenerate Pass")
        box.prop(addon.preference().property, 'meshclean_unhide_behavior', text="Unhide Mesh")
        box.prop(addon.preference().property, 'meshclean_delete_interior', text="Delete Interior Faces")


    def execute(self, context):

        self.object_mode = context.active_object.mode
        self.selection_mode = context.tool_settings.mesh_select_mode[:]

        if addon.preference().property.meshclean_mode == 'SELECTED':
            if self.object_mode == 'OBJECT':
                original_active_object = context.view_layer.objects.active

                for object in context.selected_objects:
                    if object.type == 'MESH':
                        self.clean_mesh(context, object)

                context.view_layer.objects.active = original_active_object

            else:
                if context.active_object.type == 'MESH':
                    self.clean_mesh(context, context.active_object)

        elif addon.preference().property.meshclean_mode == 'VISIBLE':
            if self.object_mode == 'OBJECT':
                original_active_object = context.view_layer.objects.active

                for object in context.visible_objects:
                    if object.type == 'MESH':
                        self.clean_mesh(context, object)

                context.view_layer.objects.active = original_active_object

            else:
                if context.active_object.type == 'MESH':
                    mesh = bmesh.from_edit_mesh(context.active_object.data)

                    original_selected_geometry = {'verts': [vert for vert in mesh.verts if vert.select],
                                                  'edges': [edge for edge in mesh.edges if edge.select],
                                                  'faces': [face for face in mesh.faces if face.select]}

                    visible_geometry = {'verts': [vert for vert in mesh.verts if not vert.hide],
                                        'edges': [edge for edge in mesh.edges if not edge.hide],
                                        'faces': [face for face in mesh.faces if not face.hide]}

                    # unselect geometry
                    for type in original_selected_geometry:
                        for geo in original_selected_geometry[type]:
                            geo.select_set(False)

                    # select visible
                    for type in visible_geometry:
                        for geo in visible_geometry[type]:
                            geo.select_set(True)

                    self.clean_mesh(context, context.active_object)

        else:
            if context.active_object.type == 'MESH':
                self.clean_mesh(context, context.active_object)

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

            bpy.ops.hops.draw_wire_mesh_launcher('INVOKE_DEFAULT', target='SELECTED')

            ui = Master()

            draw_data = [
                ["Clean Mesh"],
                ["Remove Threshold", "%.1f" % addon.preference().property.meshclean_remove_threshold],
                ["Dissolve Angle", "%.1f" % degrees(addon.preference().property.meshclean_dissolve_angle)+ "°"],
                ["Degenerate Pass", addon.preference().property.meshclean_degenerate_iter],
                [f"Modifiers Applied / Remain", f"{0} / {len(context.active_object.modifiers[:])}"],
                ["MeshClean Mode ", addon.preference().property.meshclean_mode]
            ]

            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)

        bpy.ops.object.mode_set(mode=self.object_mode)
        context.tool_settings.mesh_select_mode = self.selection_mode

        return {'FINISHED'}


    def clean_mesh(self, context, object):

        context.view_layer.objects.active = object

        bpy.ops.object.mode_set(mode='EDIT')

        if addon.preference().property.meshclean_unhide_behavior:
            bpy.ops.mesh.reveal()

        context.tool_settings.mesh_select_mode = (False, True, False)

        if addon.preference().property.meshclean_mode == 'ACTIVE' or self.object_mode == 'OBJECT':
            bpy.ops.mesh.select_all(action='DESELECT')
            bpy.ops.mesh.select_all(action='TOGGLE')

        bpy.ops.mesh.remove_doubles(threshold=addon.preference().property.meshclean_remove_threshold)

        bpy.ops.mesh.dissolve_limited(angle_limit=addon.preference().property.meshclean_dissolve_angle)

        for _ in range(addon.preference().property.meshclean_degenerate_iter):
            bpy.ops.mesh.dissolve_degenerate(threshold=0.000001)

        if addon.preference().property.meshclean_delete_interior:
            bpy.ops.mesh.select_all(action='DESELECT')
            bpy.ops.mesh.select_interior_faces()
            bpy.ops.mesh.delete(type='FACE')

        bpy.ops.object.mode_set(mode='OBJECT')