Skip to content
Snippets Groups Projects
common_classes.py 26.8 KiB
Newer Older
  • Learn to ignore specific revisions
  • ### BEGIN GPL LICENSE BLOCK #####
    #
    #  This program is free software; you can redistribute it and/or
    #  modify it under the terms of the GNU General Public License
    #  as published by the Free Software Foundation; either version 3
    #  of the License, or (at your option) any later version.
    #
    #  This program is distributed in the hope that it will be useful,
    #  but WITHOUT ANY WARRANTY; without even the implied warranty of
    #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    #  GNU General Public License for more details.
    #
    #  You should have received a copy of the GNU General Public License
    #  along with this program.  If not, see <http://www.gnu.org/licenses/>.
    #
    # ##### END GPL LICENSE BLOCK #####
    
    import bpy
    import bgl
    
    
    from mathutils import Vector
    
    from .common_utilities import (
        convert_distance,
        get_units_info,
        snap_utilities,
        )
    
    
    class SnapDrawn():
        def __init__(self, out_color, face_color,
                     edge_color, vert_color, center_color,
                     perpendicular_color, constrain_shift_color,
                     axis_x_color, axis_y_color, axis_z_color):
    
    
            self.out_color = out_color
            self.face_color = face_color
            self.edge_color = edge_color
            self.vert_color = vert_color
            self.center_color = center_color
            self.perpendicular_color = perpendicular_color
            self.constrain_shift_color = constrain_shift_color
    
            self.axis_x_color = axis_x_color
            self.axis_y_color = axis_y_color
            self.axis_z_color = axis_z_color
    
            self._format_pos = gpu.types.GPUVertFormat()
            self._format_pos.attr_add(id="pos", comp_type='F32', len=3, fetch_mode='FLOAT')
    
            self._format_pos_and_color = gpu.types.GPUVertFormat()
            self._format_pos_and_color.attr_add(id="pos", comp_type='F32', len=3, fetch_mode='FLOAT')
            self._format_pos_and_color.attr_add(id="color", comp_type='F32', len=4, fetch_mode='FLOAT')
    
            self._program_unif_col = gpu.shader.from_builtin("3D_UNIFORM_COLOR")
            self._program_smooth_col = gpu.shader.from_builtin("3D_SMOOTH_COLOR")
    
            self._batch_point = None
    
    
        def batch_line_strip_create(self, coords):
    
            from gpu.types import (
                GPUVertBuf,
                GPUBatch,
            )
    
            vbo = GPUVertBuf(self._format_pos, len = len(coords))
    
            vbo.attr_fill(0, data = coords)
    
            batch_lines = GPUBatch(type = "LINE_STRIP", buf = vbo)
    
            return batch_lines
    
        def batch_lines_smooth_color_create(self, coords, colors):
    
            from gpu.types import (
                GPUVertBuf,
                GPUBatch,
            )
    
            vbo = GPUVertBuf(self._format_pos_and_color, len = len(coords))
    
            vbo.attr_fill(0, data = coords)
            vbo.attr_fill(1, data = colors)
    
            batch_lines = GPUBatch(type = "LINES", buf = vbo)
    
            return batch_lines
    
        def batch_triangles_create(self, coords):
    
            from gpu.types import (
                GPUVertBuf,
                GPUBatch,
            )
    
            vbo = GPUVertBuf(self._format_pos, len = len(coords))
    
            vbo.attr_fill(0, data = coords)
    
            batch_tris = GPUBatch(type = "TRIS", buf = vbo)
    
            return batch_tris
    
        def batch_point_get(self):
            if self._batch_point is None:
    
                from gpu.types import (
                    GPUVertBuf,
                    GPUBatch,
                )
                vbo = GPUVertBuf(self._format_pos, len = 1)
    
                vbo.attr_fill(0, ((0.0, 0.0, 0.0),))
    
                self._batch_point = GPUBatch(type = "POINTS", buf = vbo)
    
            return self._batch_point
    
        def draw(self, type, location, list_verts_co, vector_constrain, prevloc):
    
            # draw 3d point OpenGL in the 3D View
            bgl.glEnable(bgl.GL_BLEND)
            gpu.matrix.push()
            self._program_unif_col.bind()
    
            if list_verts_co:
                # draw 3d line OpenGL in the 3D View
                bgl.glDepthRange(0, 0.9999)
                bgl.glLineWidth(3.0)
    
                batch = self.batch_line_strip_create([v.to_tuple() for v in list_verts_co] + [location.to_tuple()])
    
                self._program_unif_col.uniform_float("color", (1.0, 0.8, 0.0, 0.5))
                batch.draw(self._program_unif_col)
                del batch
    
            bgl.glDisable(bgl.GL_DEPTH_TEST)
    
            point_batch = self.batch_point_get()
            if vector_constrain:
                if prevloc:
                    bgl.glPointSize(5)
                    gpu.matrix.translate(prevloc)
                    self._program_unif_col.uniform_float("color", (1.0, 1.0, 1.0, 0.5))
                    point_batch.draw(self._program_unif_col)
                    gpu.matrix.translate(-prevloc)
    
                if vector_constrain[2] == 'X':
                    Color4f = self.axis_x_color
                elif vector_constrain[2] == 'Y':
                    Color4f = self.axis_y_color
                elif vector_constrain[2] == 'Z':
                    Color4f = self.axis_z_color
                else:
                    Color4f = self.constrain_shift_color
            else:
                if type == 'OUT':
                    Color4f = self.out_color
                elif type == 'FACE':
                    Color4f = self.face_color
                elif type == 'EDGE':
                    Color4f = self.edge_color
                elif type == 'VERT':
                    Color4f = self.vert_color
                elif type == 'CENTER':
                    Color4f = self.center_color
                elif type == 'PERPENDICULAR':
                    Color4f = self.perpendicular_color
                else: # type == None
                    Color4f = self.out_color
    
            bgl.glPointSize(10)
    
            gpu.matrix.translate(location)
            self._program_unif_col.uniform_float("color", Color4f)
            point_batch.draw(self._program_unif_col)
    
            # restore opengl defaults
            bgl.glDepthRange(0.0, 1.0)
            bgl.glPointSize(1.0)
            bgl.glLineWidth(1.0)
            bgl.glEnable(bgl.GL_DEPTH_TEST)
            bgl.glDisable(bgl.GL_BLEND)
    
            gpu.matrix.pop()
    
        def draw_elem(self, snap_obj, bm, elem):
    
            #TODO: Cache coords (because antialiasing)
    
            from bmesh.types import(
                BMVert,
                BMEdge,
                BMFace,
            )
            # draw 3d point OpenGL in the 3D View
            bgl.glEnable(bgl.GL_BLEND)
            bgl.glDisable(bgl.GL_DEPTH_TEST)
    
    
            with gpu.matrix.push_pop():
                gpu.matrix.multiply_matrix(snap_obj.mat)
    
                if isinstance(elem, BMVert):
                    if elem.link_edges:
    
                        color = self.vert_color
                        edges = np.empty((len(elem.link_edges), 2), [("pos", "f4", 3), ("color", "f4", 4)])
                        edges["pos"][:, 0] = elem.co
                        edges["pos"][:, 1] = [e.other_vert(elem).co for e in elem.link_edges]
                        edges["color"][:, 0] = color
                        edges["color"][:, 1] = (color[0], color[1], color[2], 0.0)
                        edges.shape = -1
    
                        self._program_smooth_col.bind()
                        bgl.glLineWidth(3.0)
                        batch = self.batch_lines_smooth_color_create(edges["pos"], edges["color"])
                        batch.draw(self._program_smooth_col)
                        bgl.glLineWidth(1.0)
                else:
                    self._program_unif_col.bind()
    
                    if isinstance(elem, BMEdge):
                        self._program_unif_col.uniform_float("color", self.edge_color)
    
                        bgl.glLineWidth(3.0)
                        batch = self.batch_line_strip_create([v.co for v in elem.verts])
    
                        batch.draw(self._program_unif_col)
    
                    elif isinstance(elem, BMFace):
                        if len(snap_obj.data) == 2:
                            face_color = self.face_color[0], self.face_color[1], self.face_color[2], self.face_color[3] * 0.2
                            self._program_unif_col.uniform_float("color", face_color)
    
                            tris = snap_obj.data[1].get_loop_tri_co_by_bmface(bm, elem)
                            tris.shape = (-1, 3)
                            batch = self.batch_triangles_create(tris)
                            batch.draw(self._program_unif_col)
    
                # restore opengl defaults
                bgl.glEnable(bgl.GL_DEPTH_TEST)
                bgl.glDisable(bgl.GL_BLEND)
    
    
    
    class SnapNavigation():
        @staticmethod
        def debug_key(key):
            for member in dir(key):
                print(member, getattr(key, member))
    
        @staticmethod
        def convert_to_flag(shift, ctrl, alt):
            return (shift << 0) | (ctrl << 1) | (alt << 2)
    
        def __init__(self, context, use_ndof):
            # TO DO:
            # 'View Orbit', 'View Pan', 'NDOF Orbit View', 'NDOF Pan View'
    
            self.use_ndof = use_ndof and context.preferences.inputs.use_ndof
    
    
            self._rotate = set()
            self._move = set()
            self._zoom = set()
    
            if self.use_ndof:
                self._ndof_all = set()
                self._ndof_orbit = set()
                self._ndof_orbit_zoom = set()
                self._ndof_pan = set()
    
            for key in context.window_manager.keyconfigs.user.keymaps['3D View'].keymap_items:
                if key.idname == 'view3d.rotate':
                    self._rotate.add((self.convert_to_flag(key.shift, key.ctrl, key.alt), key.type, key.value))
                elif key.idname == 'view3d.move':
                    self._move.add((self.convert_to_flag(key.shift, key.ctrl, key.alt), key.type, key.value))
                elif key.idname == 'view3d.zoom':
                    if key.type == 'WHEELINMOUSE':
                        self._zoom.add((self.convert_to_flag(key.shift, key.ctrl, key.alt), 'WHEELUPMOUSE', key.value, key.properties.delta))
                    elif key.type == 'WHEELOUTMOUSE':
                        self._zoom.add((self.convert_to_flag(key.shift, key.ctrl, key.alt), 'WHEELDOWNMOUSE', key.value, key.properties.delta))
                    else:
                        self._zoom.add((self.convert_to_flag(key.shift, key.ctrl, key.alt), key.type, key.value, key.properties.delta))
    
                elif self.use_ndof:
                    if key.idname == 'view3d.ndof_all':
                        self._ndof_all.add((self.convert_to_flag(key.shift, key.ctrl, key.alt), key.type))
                    elif key.idname == 'view3d.ndof_orbit':
                        self._ndof_orbit.add((self.convert_to_flag(key.shift, key.ctrl, key.alt), key.type))
                    elif key.idname == 'view3d.ndof_orbit_zoom':
                        self._ndof_orbit_zoom.add((self.convert_to_flag(key.shift, key.ctrl, key.alt), key.type))
                    elif key.idname == 'view3d.ndof_pan':
                        self._ndof_pan.add((self.convert_to_flag(key.shift, key.ctrl, key.alt), key.type))
    
    
        def run(self, context, event, snap_location):
            evkey = (self.convert_to_flag(event.shift, event.ctrl, event.alt), event.type, event.value)
    
            if evkey in self._rotate:
    
                if snap_location:
                    bpy.ops.view3d.rotate_custom_pivot('INVOKE_DEFAULT', pivot=snap_location)
                else:
                    bpy.ops.view3d.rotate('INVOKE_DEFAULT', use_mouse_init=True)
    
                return True
    
            if evkey in self._move:
                #if event.shift and self.vector_constrain and \
                #    self.vector_constrain[2] in {'RIGHT_SHIFT', 'LEFT_SHIFT', 'shift'}:
                #    self.vector_constrain = None
                bpy.ops.view3d.move('INVOKE_DEFAULT')
                return True
    
            for key in self._zoom:
                if evkey == key[0:3]:
    
                    if snap_location and key[3]:
                        bpy.ops.view3d.zoom_custom_target('INVOKE_DEFAULT', delta=key[3], target=snap_location)
    
                        bpy.ops.view3d.zoom('INVOKE_DEFAULT', delta=key[3])
    
                    return True
    
            if self.use_ndof:
                ndofkey = evkey[:2]
    
                if ndofkey in self._ndof_all:
    
                    bpy.ops.view3d.ndof_all('INVOKE_DEFAULT')
                    return True
    
                if ndofkey in self._ndof_orbit:
    
                    bpy.ops.view3d.ndof_orbit('INVOKE_DEFAULT')
                    return True
    
                if ndofkey in self._ndof_orbit_zoom:
    
                    bpy.ops.view3d.ndof_orbit_zoom('INVOKE_DEFAULT')
                    return True
    
                if ndofkey in self._ndof_pan:
    
                    bpy.ops.view3d.ndof_pan('INVOKE_DEFAULT')
                    return True
    
            return False
    
    
    class CharMap:
        ascii = {
            ".", ",", "-", "+", "1", "2", "3",
            "4", "5", "6", "7", "8", "9", "0",
            "c", "m", "d", "k", "h", "a",
            " ", "/", "*", "'", "\""
            # "="
            }
        type = {
            'BACK_SPACE', 'DEL',
            'LEFT_ARROW', 'RIGHT_ARROW'
            }
    
    
        def __init__(self, context):
            scale = context.scene.unit_settings.scale_length
            separate_units = context.scene.unit_settings.use_separate
            self.unit_system = context.scene.unit_settings.system
            self.uinfo = get_units_info(scale, self.unit_system, separate_units)
    
            self.clear()
    
        def modal_(self, context, event):
            if event.value == 'PRESS':
                type = event.type
                ascii = event.ascii
                if (type in self.type) or (ascii in self.ascii):
                    if ascii:
                        pos = self.line_pos
                        if ascii == ",":
                            ascii = "."
                        self.length_entered = self.length_entered[:pos] + ascii + self.length_entered[pos:]
                        self.line_pos += 1
    
                    if self.length_entered:
                        pos = self.line_pos
                        if type == 'BACK_SPACE':
                            self.length_entered = self.length_entered[:pos - 1] + self.length_entered[pos:]
                            self.line_pos -= 1
    
                        elif type == 'DEL':
                            self.length_entered = self.length_entered[:pos] + self.length_entered[pos + 1:]
    
                        elif type == 'LEFT_ARROW':
                            self.line_pos = (pos - 1) % (len(self.length_entered) + 1)
    
                        elif type == 'RIGHT_ARROW':
                            self.line_pos = (pos + 1) % (len(self.length_entered) + 1)
    
                        try:
                            self.length_entered_value = bpy.utils.units.to_value(
                                    self.unit_system, 'LENGTH', self.length_entered)
                        except:  # ValueError:
                            self.length_entered_value = 0.0 #invalid
                            #self.report({'INFO'}, "Operation not supported yet")
                    else:
                        self.length_entered_value = 0.0
    
        def get_converted_length_str(self, length):
            if self.length_entered:
                pos = self.line_pos
                ret = self.length_entered[:pos] + '|' + self.length_entered[pos:]
            else:
                ret = convert_distance(length, self.uinfo)
    
        def clear(self):
            self.length_entered = ''
            self.length_entered_value = 0.0
            self.line_pos = 0
    
    
    
    class VIEW3D_OT_rotate_custom_pivot(bpy.types.Operator):
        bl_idname = "view3d.rotate_custom_pivot"
        bl_label = "Rotate the view"
        bl_options = {'BLOCKING', 'GRAB_CURSOR'}
    
    
        pivot: bpy.props.FloatVectorProperty("Pivot", subtype='XYZ')
        g_up_axis: bpy.props.FloatVectorProperty("up_axis", default=(0.0, 0.0, 1.0), subtype='XYZ')
        sensitivity: bpy.props.FloatProperty("sensitivity", default=0.007)
    
    
        def modal(self, context, event):
            from mathutils import Matrix
            if event.value == 'PRESS' and event.type in {'MOUSEMOVE', 'INBETWEEN_MOUSEMOVE'}:
                dx = self.init_coord[0] - event.mouse_region_x
                dy = self.init_coord[1] - event.mouse_region_y
    
                rot_ver = Matrix.Rotation(-dx * self.sensitivity, 3, self.g_up_axis)
    
                rot_hor = Matrix.Rotation(dy * self.sensitivity, 3, self.view_rot[0])
                rot_mat =  rot_hor @ rot_ver
                view_matrix = self.view_rot @ rot_mat
    
                pos = self.pos1 @ rot_mat + self.pivot
                qua = view_matrix.to_quaternion()
                qua.invert()
    
                self.rv3d.view_location = pos
                self.rv3d.view_rotation = qua
    
                context.area.tag_redraw()
                return {'RUNNING_MODAL'}
    
            return {'FINISHED'}
    
        def invoke(self, context, event):
            self.rv3d = context.region_data
            self.init_coord = event.mouse_region_x, event.mouse_region_y
            self.pos1 = self.rv3d.view_location - self.pivot
            self.view_rot = self.rv3d.view_matrix.to_3x3()
    
            context.window_manager.modal_handler_add(self)
            return {'RUNNING_MODAL'}
    
    
    
    class VIEW3D_OT_zoom_custom_target(bpy.types.Operator):
        bl_idname = "view3d.zoom_custom_target"
        bl_label = "Zoom the view"
        bl_options = {'BLOCKING', 'GRAB_CURSOR'}
    
        target: bpy.props.FloatVectorProperty("target", subtype='XYZ')
        delta: bpy.props.IntProperty("delta", default=0)
        step_factor = 0.333
    
        def modal(self, context, event):
            if event.value == 'PRESS' and event.type in {'MOUSEMOVE', 'INBETWEEN_MOUSEMOVE'}:
                if not hasattr(self, "init_mouse_region_y"):
                    self.init_mouse_region_y = event.mouse_region_y
                    self.heigt_up = context.area.height - self.init_mouse_region_y
                    self.rv3d.view_location = self.target
    
                fac = (event.mouse_region_y - self.init_mouse_region_y) / self.heigt_up
                ret = 'RUNNING_MODAL'
            else:
                fac = self.step_factor * self.delta
                ret = 'FINISHED'
    
            self.rv3d.view_location = self.init_loc + (self.target - self.init_loc) * fac
            self.rv3d.view_distance = self.init_dist - self.init_dist * fac
    
            context.area.tag_redraw()
            return {ret}
    
        def invoke(self, context, event):
            v3d = context.space_data
            dist_range = (v3d.clip_start, v3d.clip_end)
            self.rv3d = context.region_data
            self.init_dist = self.rv3d.view_distance
            if ((self.delta <= 0 and self.init_dist < dist_range[1]) or
                (self.delta >  0 and self.init_dist > dist_range[0])):
                    self.init_loc = self.rv3d.view_location.copy()
    
                    context.window_manager.modal_handler_add(self)
                    return {'RUNNING_MODAL'}
    
            return {'FINISHED'}
    
    
    
    class SnapUtilities:
    #    __slots__ = (
    #        "sctx",
    #        "draw_cache",
    #        "outer_verts",
    #        "snap_face",
    #        "snap_to_grid",
    #        "unit_system",
    #        "rd",
    #        "incremental",
    #    )
    
        constrain_keys = {
            'X': Vector((1,0,0)),
            'Y': Vector((0,1,0)),
            'Z': Vector((0,0,1)),
            'RIGHT_SHIFT': 'shift',
            'LEFT_SHIFT': 'shift',
            }
    
        snap_widget = None
        snap_widget_refcnt = 0
        constrain = None
    
        @staticmethod
        def set_contrain(context, key):
            widget = SnapUtilities.snap_widget
            if SnapUtilities.constrain == key:
                SnapUtilities.constrain = None
                return
    
            SnapUtilities.constrain = key
    
    
        def visible_objects_and_duplis(self, context):
            if self.preferences.outer_verts:
                for obj in context.visible_objects:
                    yield (obj, obj.matrix_world)
    
                    if obj.instance_type == 'COLLECTION':
                        mat = obj.matrix_world.copy()
                        for ob in obj.instance_collection.objects:
                            yield (ob, mat @ ob.matrix_world)
            else:
                for obj in context.objects_in_mode_unique_data:
                    yield (obj, obj.matrix_world)
    
    
        def snap_context_init(self, context, snap_edge_and_vert = True):
            from .snap_context_l import global_snap_context_get
    
            #Create Snap Context
            self.sctx = global_snap_context_get(context.region, context.space_data)
            self.sctx.set_pixel_dist(12)
            self.sctx.use_clip_planes(True)
    
            widget = self.snap_widget
    
            if widget is not None:
                self.preferences = widget.preferences
                self.draw_cache = widget.draw_cache
            else:
                preferences = context.preferences.addons[__package__].preferences
                self.preferences = preferences
                #Init DrawCache
                self.draw_cache = SnapDrawn(
                    preferences.out_color,
                    preferences.face_color,
                    preferences.edge_color,
                    preferences.vert_color,
                    preferences.center_color,
                    preferences.perpendicular_color,
                    preferences.constrain_shift_color,
                    tuple(context.preferences.themes[0].user_interface.axis_x) + (1.0,),
                    tuple(context.preferences.themes[0].user_interface.axis_y) + (1.0,),
                    tuple(context.preferences.themes[0].user_interface.axis_z) + (1.0,)
                )
    
            self.snap_vert = self.snap_edge = snap_edge_and_vert
    
            shading = context.space_data.shading
            self.snap_face = not (snap_edge_and_vert and
                                 (shading.show_xray or shading.type == 'WIREFRAME'))
    
            self.snap_context_update(context)
    
            #Configure the unit of measure
            unit_system = context.scene.unit_settings.system
            scale = context.scene.unit_settings.scale_length
            scale /= context.space_data.overlay.grid_scale
            self.rd = bpy.utils.units.to_value(unit_system, 'LENGTH', str(1 / scale))
    
            self.incremental = bpy.utils.units.to_value(
                    unit_system, 'LENGTH', str(self.preferences.incremental))
    
        def snap_context_update(self, context):
            self.sctx.set_snap_mode(
                     self.snap_vert, self.snap_edge, self.snap_face)
    
            self.sctx.clear_snap_objects()
    
            for obj, matrix in self.visible_objects_and_duplis(context):
                self.sctx.add_obj(obj, matrix)
    
            widget = self.snap_widget
    
            if widget:
                self.snap_obj = widget.snap_obj
                self.bm = widget.bm
                self.geom = widget.geom
                self.type = widget.type
                self.location = widget.location
            else:
                #init these variables to avoid errors
                self.snap_obj = None
                self.bm = None
                self.geom = None
                self.type = 'OUT'
                self.location = Vector()
    
        def snap_to_grid(self):
            if self.type == 'OUT' and self.preferences.increments_grid:
                loc = self.location / self.rd
                self.location = Vector((round(loc.x),
                                        round(loc.y),
                                        round(loc.z))) * self.rd
    
        def snap_context_free(self):
            del self.sctx
    
            del self.bm
            del self.draw_cache
            del self.geom
            del self.location
            del self.rd
            del self.snap_face
            del self.snap_obj
            del self.type
    
            del self.preferences
    
            SnapUtilities.constrain = None
    
    
    #def mesh_runtime_batchcache_isdirty(me):
    #    import ctypes
    #    batch_cache = ctypes.c_void_p.from_address(me.as_pointer() + 1440)
    #    if batch_cache:
    #        return ctypes.c_bool.from_address(batch_cache.value + 549).value
    #    return False
    
    
    class SnapWidgetCommon:
        def draw_point_and_elem(self):
            if self.bm:
                if self.bm.is_valid and self.geom.is_valid:
                    self.draw_cache.draw_elem(self.snap_obj, self.bm, self.geom)
                else:
                    self.bm = None
                    self.geom = None
                    self.sctx.update_all()
    
            self.draw_cache.draw(self.type, self.location, None, None, None)
    
        def init_snap_widget(self, context, snap_edge_and_vert = True):
            self.snap_context_init(context, snap_edge_and_vert)
            self.mode = context.mode
            self.wm_operators = context.window_manager.operators
            self.last_operator = self.wm_operators[-1] if self.wm_operators else None
            self.last_mval = None
    
        def update_snap(self, context, mval):
            if self.last_mval == mval:
                return -1
            else:
                self.last_mval = mval
    
            last_operator = self.wm_operators[-1] if self.wm_operators else None
            if last_operator != self.last_operator:
                if (not last_operator or
                    last_operator.name not in {'Select', 'Loop Select', '(De)select All'}):
                        ## Something has changed since the last time.
                        # Has the mesh been changed?
                        # In the doubt lets clear the snap context.
                        self.sctx.update_all()
    
                self.last_operator = last_operator
    
            #print('test_select', mval)
            space = context.space_data
            self.sctx.update_viewport_context(context.region, space)
    
            shading = space.shading
            snap_face = not ((self.snap_vert or self.snap_edge) and
                            (shading.show_xray or shading.type == 'WIREFRAME'))
    
            if snap_face != self.snap_face:
                self.snap_face = snap_face
                self.sctx.set_snap_mode(
                         self.snap_vert, self.snap_edge, self.snap_face)
    
            self.snap_obj, prev_loc, self.location, self.type, self.bm, self.geom, len = snap_utilities(
                    self.sctx,
                    None,
                    mval,
                    increment=self.incremental
            )
    
        def __del__(self):
            from .snap_context_l import global_snap_context_get
            sctx = global_snap_context_get(None, None)
            if sctx:
                sctx.clear_snap_objects()
    
    
    class SnapPointWidget(SnapUtilities, SnapWidgetCommon, bpy.types.Gizmo):
        bl_idname = "VIEW3D_GT_snap_point"
    
        __slots__ = (
            "bm",
            "draw_cache",
            "geom",
            "incremental",
            "preferences",
            "last_operator",
            "location",
            "mode",
            "snap_edge",
            "snap_face",
            "snap_vert",
            "snap_obj",
            "type",
            "wm_operators",
        )
    
        def test_select(self, context, mval):
            self.update_snap(context, mval)
            self.snap_to_grid()
    
            context.area.tag_redraw()
            return -1
    
        def draw(self, context):
            self.draw_point_and_elem()
    
        def setup(self):
            self.init_snap_widget(bpy.context)
            SnapUtilities.snap_widget = self
    
    
    def context_mode_check(context, widget_group):
        workspace = context.workspace
        mode = workspace.tools_mode
        for tool in workspace.tools:
            if (tool.widget == widget_group) and (tool.mode == mode):
                break
        else:
            return False
        return True
    
    class SnapWidgetCommon:
        bl_space_type = 'VIEW_3D'
        bl_region_type = 'WINDOW'
        bl_options = {'3D'}
    
        @classmethod
        def poll(cls, context):
            return context_mode_check(context, cls.bl_idname)
    #        return context_mode_change(
    #                context, SnapUtilities.snap_widget, cls.bl_idname)
    
        def init_tool(self, context, gizmo_name):
            self.gizmos.new(gizmo_name)
            SnapUtilities.snap_widget_refcnt += 1
    
        def __del__(self):
            SnapUtilities.snap_widget_refcnt -= 1
            if SnapUtilities.snap_widget_refcnt == 0:
                SnapUtilities.snap_widget = None
    
    
    class SnapPointWidgetGroup(SnapWidgetCommon, bpy.types.GizmoGroup):
        bl_idname = "MESH_GGT_snap_point"
        bl_label = "Draw Snap Point"
    
        def setup(self, context):
            self.init_tool(context, SnapPointWidget.bl_idname)