Skip to content
Snippets Groups Projects
space_view3d_enhanced_3d_cursor.py 185 KiB
Newer Older
  • Learn to ignore specific revisions
  •         prepare_grid_mesh(bm, nx=subdiv, ny=subdiv,
                z=side[0], xyz_indices=side[1])
    
    
    # ===== DRAWING UTILITIES ===== #
    class GfxCell:
        def __init__(self, w, h, color=None, alpha=None, draw=None):
            self.w = w
            self.h = h
    
            self.color = (0, 0, 0, 1)
            self.set_color(color, alpha)
    
            if draw:
                self.draw = draw
    
        def set_color(self, color=None, alpha=None):
            if color is None:
                color = self.color
            if alpha is None:
                alpha = (color[3] if len(color) > 3 else self.color[3])
            self.color = Vector((color[0], color[1], color[2], alpha))
    
        def prepare_draw(self, x, y, align=(0, 0)):
            if self.color[3] <= 0.0:
                return None
    
            if (align[0] != 0) or (align[1] != 0):
                x -= self.w * align[0]
                y -= self.h * align[1]
    
            x = int(math.floor(x + 0.5))
            y = int(math.floor(y + 0.5))
    
            bgl.glColor4f(*self.color)
    
            return x, y
    
        def draw(self, x, y, align=(0, 0)):
            xy = self.prepare_draw(x, y, align)
            if not xy:
                return
    
            draw_rect(xy[0], xy[1], w, h)
    
    class TextCell(GfxCell):
        font_id = 0
    
        def __init__(self, text="", color=None, alpha=None, font_id=None):
            if font_id is None:
                font_id = TextCell.font_id
            self.font_id = font_id
    
            self.set_text(text)
    
            self.color = (0, 0, 0, 1)
            self.set_color(color, alpha)
    
        def set_text(self, text):
            self.text = str(text)
            dims = blf.dimensions(self.font_id, self.text)
            self.w = dims[0]
            dims = blf.dimensions(self.font_id, "dp") # fontheight
            self.h = dims[1]
    
        def draw(self, x, y, align=(0, 0)):
            xy = self.prepare_draw(x, y, align)
            if not xy:
                return
    
            blf.position(self.font_id, xy[0], xy[1], 0)
            blf.draw(self.font_id, self.text)
    
    
    def draw_text(x, y, value, font_id=0, align=(0, 0), font_height=None):
        value = str(value)
    
        if (align[0] != 0) or (align[1] != 0):
            dims = blf.dimensions(font_id, value)
            if font_height is not None:
                dims = (dims[0], font_height)
            x -= dims[0] * align[0]
            y -= dims[1] * align[1]
    
        x = int(math.floor(x + 0.5))
        y = int(math.floor(y + 0.5))
    
        blf.position(font_id, x, y, 0)
        blf.draw(font_id, value)
    
    def draw_rect(x, y, w, h, margin=0, outline=False):
        if w < 0:
            x += w
            w = abs(w)
    
        if h < 0:
            y += h
            h = abs(h)
    
        x = int(x)
        y = int(y)
        w = int(w)
        h = int(h)
        margin = int(margin)
    
        if outline:
            bgl.glBegin(bgl.GL_LINE_LOOP)
        else:
            bgl.glBegin(bgl.GL_TRIANGLE_FAN)
        bgl.glVertex2i(x - margin, y - margin)
        bgl.glVertex2i(x + w + margin, y - margin)
        bgl.glVertex2i(x + w + margin, y + h + margin)
        bgl.glVertex2i(x - margin, y + h + margin)
        bgl.glEnd()
    
    def append_round_rect(verts, x, y, w, h, rw, rh=None):
        if rh is None:
            rh = rw
    
        if w < 0:
            x += w
            w = abs(w)
    
        if h < 0:
            y += h
            h = abs(h)
    
        if rw < 0:
            rw = min(abs(rw), w * 0.5)
            x += rw
            w -= rw * 2
    
        if rh < 0:
            rh = min(abs(rh), h * 0.5)
            y += rh
            h -= rh * 2
    
        n = int(max(rw, rh) * math.pi / 2.0)
    
        a0 = 0.0
        a1 = math.pi / 2.0
        append_oval_segment(verts, x + w, y + h, rw, rh, a0, a1, n)
    
        a0 = math.pi / 2.0
        a1 = math.pi
        append_oval_segment(verts, x + w, y, rw, rh, a0, a1, n)
    
        a0 = math.pi
        a1 = 3.0 * math.pi / 2.0
        append_oval_segment(verts, x, y, rw, rh, a0, a1, n)
    
        a0 = 3.0 * math.pi / 2.0
        a1 = math.pi * 2.0
        append_oval_segment(verts, x, y + h, rw, rh, a0, a1, n)
    
    def append_oval_segment(verts, x, y, rw, rh, a0, a1, n, skip_last=False):
        nmax = n - 1
        da = a1 - a0
        for i in range(n - int(skip_last)):
            a = a0 + da * (i / nmax)
            dx = math.sin(a) * rw
            dy = math.cos(a) * rh
            verts.append((x + int(dx), y + int(dy)))
    
    def draw_line(p0, p1, c=None):
        if c is not None:
            bgl.glColor4f(c[0], c[1], c[2], \
                (c[3] if len(c) > 3 else 1.0))
        bgl.glBegin(bgl.GL_LINE_STRIP)
        bgl.glVertex3f(p0[0], p0[1], p0[2])
        bgl.glVertex3f(p1[0], p1[1], p1[2])
        bgl.glEnd()
    
    def draw_line_2d(p0, p1, c=None):
        if c is not None:
            bgl.glColor4f(c[0], c[1], c[2], \
                (c[3] if len(c) > 3 else 1.0))
        bgl.glBegin(bgl.GL_LINE_STRIP)
        bgl.glVertex2f(p0[0], p0[1])
        bgl.glVertex2f(p1[0], p1[1])
        bgl.glEnd()
    
    def draw_line_hidden_depth(p0, p1, c, a0=1.0, a1=0.5, s0=None, s1=None):
        bgl.glEnable(bgl.GL_DEPTH_TEST)
        bgl.glColor4f(c[0], c[1], c[2], a0)
        if s0 is not None:
            gl_enable(bgl.GL_LINE_STIPPLE, int(bool(s0)))
        draw_line(p0, p1)
        bgl.glDisable(bgl.GL_DEPTH_TEST)
        if (a1 == a0) and (s1 == s0):
            return
        bgl.glColor4f(c[0], c[1], c[2], a1)
        if s1 is not None:
            gl_enable(bgl.GL_LINE_STIPPLE, int(bool(s1)))
        draw_line(p0, p1)
    
    def draw_arrow(p0, x, y, z, n_scl=0.2, ort_scl=0.035):
        p1 = p0 + z
    
        bgl.glBegin(bgl.GL_LINE_STRIP)
        bgl.glVertex3f(p0[0], p0[1], p0[2])
        bgl.glVertex3f(p1[0], p1[1], p1[2])
        bgl.glEnd()
    
        p2 = p1 - z * n_scl
        bgl.glBegin(bgl.GL_TRIANGLE_FAN)
        bgl.glVertex3f(p1[0], p1[1], p1[2])
        p3 = p2 + (x + y) * ort_scl
        bgl.glVertex3f(p3[0], p3[1], p3[2])
        p3 = p2 + (-x + y) * ort_scl
        bgl.glVertex3f(p3[0], p3[1], p3[2])
        p3 = p2 + (-x - y) * ort_scl
        bgl.glVertex3f(p3[0], p3[1], p3[2])
        p3 = p2 + (x - y) * ort_scl
        bgl.glVertex3f(p3[0], p3[1], p3[2])
        p3 = p2 + (x + y) * ort_scl
        bgl.glVertex3f(p3[0], p3[1], p3[2])
        bgl.glEnd()
    
    def draw_arrow_2d(p0, n, L, arrow_len, arrow_width):
        p1 = p0 + n * L
        t = Vector((-n[1], n[0]))
        pA = p1 - n * arrow_len + t * arrow_width
        pB = p1 - n * arrow_len - t * arrow_width
    
        bgl.glBegin(bgl.GL_LINES)
    
        bgl.glVertex2f(p0[0], p0[1])
        bgl.glVertex2f(p1[0], p1[1])
    
        bgl.glVertex2f(p1[0], p1[1])
        bgl.glVertex2f(pA[0], pA[1])
    
        bgl.glVertex2f(p1[0], p1[1])
        bgl.glVertex2f(pB[0], pB[1])
    
        bgl.glEnd()
    
    # Store/restore OpenGL settings and working with
    # projection matrices -- inspired by space_view3d_panel_measure
    # of Buerbaum Martin (Pontiac).
    
    # OpenGl helper functions/data
    gl_state_info = {
        bgl.GL_MATRIX_MODE:(bgl.GL_INT, 1),
        bgl.GL_PROJECTION_MATRIX:(bgl.GL_DOUBLE, 16),
        bgl.GL_LINE_WIDTH:(bgl.GL_FLOAT, 1),
        bgl.GL_BLEND:(bgl.GL_BYTE, 1),
        bgl.GL_LINE_STIPPLE:(bgl.GL_BYTE, 1),
        bgl.GL_COLOR:(bgl.GL_FLOAT, 4),
        bgl.GL_SMOOTH:(bgl.GL_BYTE, 1),
        bgl.GL_DEPTH_TEST:(bgl.GL_BYTE, 1),
        bgl.GL_DEPTH_WRITEMASK:(bgl.GL_BYTE, 1),
    }
    gl_type_getters = {
        bgl.GL_INT:bgl.glGetIntegerv,
        bgl.GL_DOUBLE:bgl.glGetFloatv, # ?
        bgl.GL_FLOAT:bgl.glGetFloatv,
        #bgl.GL_BYTE:bgl.glGetFloatv, # Why GetFloat for getting byte???
        bgl.GL_BYTE:bgl.glGetBooleanv, # maybe like that?
    }
    
    def gl_get(state_id):
        type, size = gl_state_info[state_id]
        buf = bgl.Buffer(type, [size])
        gl_type_getters[type](state_id, buf)
        return (buf if (len(buf) != 1) else buf[0])
    
    def gl_enable(state_id, enable):
        if enable:
            bgl.glEnable(state_id)
        else:
            bgl.glDisable(state_id)
    
    def gl_matrix_to_buffer(m):
        tempMat = [m[i][j] for i in range(4) for j in range(4)]
        return bgl.Buffer(bgl.GL_FLOAT, 16, tempMat)
    
    
    # ===== DRAWING CALLBACKS ===== #
    
    cursor_save_location = Vector()
    
    
    def draw_callback_view(self, context):
    
        global cursor_save_location
    
        settings = find_settings()
        if settings is None:
            return
    
        update_stick_to_obj(context)
    
            # It's nice to have bookmark position update interactively
            # However, this still can be slow if there are many
            # selected objects
    
            # ATTENTION!!!
            # This eats a lot of processor time!
            #CursorDynamicSettings.recalc_csu(context, 'PRESS')
            pass
    
        history = settings.history
    
        tfm_operator = CursorDynamicSettings.active_transform_operator
    
        is_drawing = history.show_trace or tfm_operator
    
        if is_drawing:
            # Store previous OpenGL settings
            MatrixMode_prev = gl_get(bgl.GL_MATRIX_MODE)
            ProjMatrix_prev = gl_get(bgl.GL_PROJECTION_MATRIX)
            lineWidth_prev = gl_get(bgl.GL_LINE_WIDTH)
            blend_prev = gl_get(bgl.GL_BLEND)
            line_stipple_prev = gl_get(bgl.GL_LINE_STIPPLE)
            color_prev = gl_get(bgl.GL_COLOR)
            smooth_prev = gl_get(bgl.GL_SMOOTH)
            depth_test_prev = gl_get(bgl.GL_DEPTH_TEST)
            depth_mask_prev = gl_get(bgl.GL_DEPTH_WRITEMASK)
    
        if history.show_trace:
            bgl.glDepthRange(0.0, 0.9999)
    
            history.draw_trace(context)
    
            library = settings.libraries.get_item()
            if library and library.offset:
                history.draw_offset(context)
    
            bgl.glDepthRange(0.0, 1.0)
    
        if tfm_operator:
    
            tfm_operator.draw_3d(context)
    
        if is_drawing:
            # Restore previous OpenGL settings
            bgl.glLineWidth(lineWidth_prev)
            gl_enable(bgl.GL_BLEND, blend_prev)
            gl_enable(bgl.GL_LINE_STIPPLE, line_stipple_prev)
            gl_enable(bgl.GL_SMOOTH, smooth_prev)
            gl_enable(bgl.GL_DEPTH_TEST, depth_test_prev)
            bgl.glDepthMask(depth_mask_prev)
            bgl.glColor4f(color_prev[0],
                color_prev[1],
                color_prev[2],
                color_prev[3])
    
        cursor_save_location = Vector(context.space_data.cursor_location)
    
        if not settings.cursor_visible:
            # This is causing problems! See <https://developer.blender.org/T33197>
            #bpy.context.space_data.cursor_location = Vector([float('nan')] * 3)
    
            region = context.region
            v3d = context.space_data
            rv3d = context.region_data
    
            pixelsize = 1
    
            dpi = context.preferences.system.dpi
    
            widget_unit = (pixelsize * dpi * 20.0 + 36.0) / 72.0
    
            cursor_w = widget_unit*2
            cursor_h = widget_unit*2
    
            viewinv = rv3d.view_matrix.inverted()
            persinv = rv3d.perspective_matrix.inverted()
    
            origin_start = viewinv.translation
            view_direction = viewinv.col[2].xyz#.normalized()
            depth_location = origin_start - view_direction
    
            coord = (-cursor_w, -cursor_h)
            dx = (2.0 * coord[0] / region.width) - 1.0
            dy = (2.0 * coord[1] / region.height) - 1.0
            p = ((persinv.col[0].xyz * dx) +
                 (persinv.col[1].xyz * dy) +
                 depth_location)
    
            context.space_data.cursor_location = p
    
    
    def draw_callback_header_px(self, context):
        r = context.region
    
        tfm_operator = CursorDynamicSettings.active_transform_operator
        if not tfm_operator:
            return
    
        smooth_prev = gl_get(bgl.GL_SMOOTH)
    
        tfm_operator.draw_axes_coords(context, (r.width, r.height))
    
        gl_enable(bgl.GL_SMOOTH, smooth_prev)
    
        bgl.glDisable(bgl.GL_BLEND)
        bgl.glColor4f(0.0, 0.0, 0.0, 1.0)
    
    def draw_callback_px(self, context):
    
        global cursor_save_location
    
        settings = find_settings()
        if settings is None:
            return
        library = settings.libraries.get_item()
    
        if not settings.cursor_visible:
            context.space_data.cursor_location = cursor_save_location
    
    
        tfm_operator = CursorDynamicSettings.active_transform_operator
    
        if settings.show_bookmarks and library:
            library.draw_bookmark(context)
    
        if tfm_operator:
            tfm_operator.draw_2d(context)
    
        # restore opengl defaults
        bgl.glLineWidth(1)
        bgl.glDisable(bgl.GL_BLEND)
        bgl.glColor4f(0.0, 0.0, 0.0, 1.0)
    
    
    # ===== UTILITY FUNCTIONS ===== #
    
    def update_stick_to_obj(context):
    
        settings = find_settings()
    
        if not settings.stick_to_obj:
    
            return
    
        scene = context.scene
    
        settings_scene = scene.cursor_3d_tools_settings
    
        name = settings_scene.stick_obj_name
    
        if (not name) or (name not in scene.objects):
            cursor_stick_pos_cache = None
            return
    
        obj = scene.objects[name]
        pos = settings_scene.stick_obj_pos
        pos = obj.matrix_world * pos
    
        if pos != cursor_stick_pos_cache:
            cursor_stick_pos_cache = pos
    
            # THIS IS AN EXPENSIVE OPERATION!
            # (eats 50% of my CPU if called each frame)
    
            context.space_data.cursor_location = pos
    
    
    def get_cursor_location(v3d=None, scene=None):
        if v3d:
            pos = v3d.cursor_location
        elif scene:
            pos = scene.cursor_location
    
    set_cursor_location__reset_stick = True
    
    def set_cursor_location(pos, v3d=None, scene=None):
    
        pos = pos.to_3d().copy()
    
        if v3d:
            scene = bpy.context.scene
    
            # Accessing scene.cursor_location is SLOW
            # (well, at least assigning to it).
            # Accessing v3d.cursor_location is fast.
            v3d.cursor_location = pos
    
        elif scene:
    
            scene.cursor_location = pos
    
        if set_cursor_location__reset_stick:
            set_stick_obj(scene, None)
    
    
    def set_stick_obj(scene, name=None, pos=None):
        settings_scene = scene.cursor_3d_tools_settings
    
        if name:
            settings_scene.stick_obj_name = name
        else:
            settings_scene.stick_obj_name = ""
    
        if pos is not None:
            settings_scene.stick_obj_pos = Vector(pos).to_3d()
    
    # WHERE TO STORE SETTINGS:
    # Currently there are two types of ID blocks
    # which properties don't change on Undo/Redo.
    # - WindowManager seems to be unique (at least
    #   for majority of situations). However, the
    #   properties stored in it are not saved
    #   with the blend.
    # - Screen. Properties are saved with blend,
    #   but there is some probability that any of
    #   the pre-chosen screen names may not exist
    #   in the user's blend.
    
    
    def propagate_settings_to_all_screens(settings):
        # At least the most vital "user preferences" stuff
        for screen in bpy.data.screens:
            _settings = screen.cursor_3d_tools_settings
            _settings.auto_register_keymaps = settings.auto_register_keymaps
            _settings.free_coord_precision = settings.free_coord_precision
    
    
    def find_settings():
        #wm = bpy.data.window_managers[0]
        #settings = wm.cursor_3d_tools_settings
    
        try:
            screen = bpy.data.screens.get("Default", bpy.data.screens[0])
        except:
            # find_settings() was called from register()/unregister()
            screen = bpy.context.window_manager.windows[0].screen
    
    
        try:
            settings = screen.cursor_3d_tools_settings
        except:
            # addon was unregistered
            settings = None
    
        return settings
    
    def find_runtime_settings():
        wm = bpy.data.window_managers[0]
        try:
            runtime_settings = wm.cursor_3d_runtime_settings
        except:
            # addon was unregistered
            runtime_settings = None
    
        return runtime_settings
    
    
    def KeyMapItemSearch(idname, place=None):
        if isinstance(place, bpy.types.KeyMap):
            for kmi in place.keymap_items:
                if kmi.idname == idname:
                    yield kmi
        elif isinstance(place, bpy.types.KeyConfig):
            for keymap in place.keymaps:
                for kmi in KeyMapItemSearch(idname, keymap):
                    yield kmi
        else:
            wm = bpy.context.window_manager
            for keyconfig in wm.keyconfigs:
                for kmi in KeyMapItemSearch(idname, keyconfig):
                    yield kmi
    
    def IsKeyMapItemEvent(kmi, event):
        event_any = (event.shift or event.ctrl or event.alt or event.oskey)
        event_key_modifier = 'NONE' # no such info in event
        return ((kmi.type == event.type) and
                (kmi.value == event.value) and
                (kmi.shift == event.shift) and
                (kmi.ctrl == event.ctrl) and
                (kmi.alt == event.alt) and
                (kmi.oskey == event.oskey) and
                (kmi.any == event_any) and
                (kmi.key_modifier == event_key_modifier))
    
    # ===== REGISTRATION ===== #
    
    def update_keymap(activate):
    
        enh_idname = EnhancedSetCursor.bl_idname
        cur_idname = 'view3d.cursor3d'
    
        wm = bpy.context.window_manager
    
        userprefs = bpy.context.preferences
    
        auto_register_keymaps = settings.auto_register_keymaps
    
        # add a check for Templates switching introduced in 2.78.x/2.79
        if __name__ in userprefs.addons.keys():
            addon_prefs = userprefs.addons[__name__].preferences
    
            wm.cursor_3d_runtime_settings.use_cursor_monitor = \
                addon_prefs.use_cursor_monitor
            auto_register_keymaps &= addon_prefs.auto_register_keymaps
    
        try:
            km = wm.keyconfigs.user.keymaps['3D View']
        except:
            # wm.keyconfigs.user is empty on Blender startup!
            return
    
        # We need for the enhanced operator to take precedence over
        # the default cursor3d, but not over the manipulator.
        # If we add the operator to "addon" keymaps, it will
        # take precedence over both. If we add it to "user"
        # keymaps, the default will take precedence.
        # However, we may just simply turn it off or remove
        # (depending on what saves with blend).
    
        items = list(KeyMapItemSearch(enh_idname, km))
    
        if activate and (len(items) == 0):
    
            kmi = km.keymap_items.new(enh_idname, 'ACTIONMOUSE', 'PRESS')
    
            for key in EnhancedSetCursor.key_map["free_mouse"]:
    
                kmi = km.keymap_items.new(enh_idname, key, 'PRESS')
    
        else:
    
            for kmi in items:
                if activate:
    
                    kmi.active = activate
    
                    km.keymap_items.remove(kmi)
    
        for kmi in KeyMapItemSearch(cur_idname):
    
            kmi.active = not activate
    
    @bpy.app.handlers.persistent
    def scene_update_post_kmreg(scene):
        bpy.app.handlers.scene_update_post.remove(scene_update_post_kmreg)
        update_keymap(True)
    
    @bpy.app.handlers.persistent
    def scene_load_post(*args):
        wm = bpy.context.window_manager
    
        userprefs = bpy.context.preferences
    
        addon_prefs = userprefs.addons[__name__].preferences
        wm.cursor_3d_runtime_settings.use_cursor_monitor = \
            addon_prefs.use_cursor_monitor
    
    
    class ThisAddonPreferences(bpy.types.AddonPreferences):
        # this must match the addon name, use '__package__'
        # when defining this in a submodule of a python package.
        bl_idname = __name__
    
        auto_register_keymaps: bpy.props.BoolProperty(
    
            name="Auto Register Keymaps",
            default=True)
    
        use_cursor_monitor: bpy.props.BoolProperty(
    
    dairin0d's avatar
    dairin0d committed
            name="Enable Cursor Monitor",
            description="Cursor monitor is a background modal operator "\
                "that records 3D cursor history",
            default=True)
    
    
        def draw(self, context):
            layout = self.layout
    
            settings = find_settings()
            row = layout.row()
    
            row.prop(self, "auto_register_keymaps", text="")
    
            row.prop(settings, "auto_register_keymaps")
            row.prop(settings, "free_coord_precision")
    
    dairin0d's avatar
    dairin0d committed
            row.prop(self, "use_cursor_monitor")
    
    def extra_snap_menu_draw(self, context):
        layout = self.layout
        layout.operator("view3d.snap_cursor_to_circumscribed")
        layout.operator("view3d.snap_cursor_to_inscribed")
    
    
    
    def register():
        bpy.utils.register_module(__name__)
    
        bpy.types.Scene.cursor_3d_tools_settings = \
            bpy.props.PointerProperty(type=Cursor3DToolsSceneSettings)
    
        bpy.types.Screen.cursor_3d_tools_settings = \
            bpy.props.PointerProperty(type=Cursor3DToolsSettings)
    
        bpy.types.WindowManager.align_orientation_properties = \
            bpy.props.PointerProperty(type=AlignOrientationProperties)
    
        bpy.types.WindowManager.cursor_3d_runtime_settings = \
            bpy.props.PointerProperty(type=CursorRuntimeSettings)
    
        bpy.types.VIEW3D_PT_transform_orientations.append(
            transform_orientations_panel_extension)
    
        # View properties panel is already long. Appending something
        # to it would make it too inconvenient
        #bpy.types.VIEW3D_PT_view3d_properties.append(draw_cursor_tools)
    
    
        bpy.types.VIEW3D_MT_snap.append(extra_snap_menu_draw)
    
    
        bpy.app.handlers.scene_update_post.append(scene_update_post_kmreg)
    
        bpy.app.handlers.load_post.append(scene_load_post)
    
    
    
    def unregister():
        # In case they are enabled/active
        CursorMonitor.handle_remove(bpy.context)
    
        # Manually set this to False on unregister
        CursorMonitor.is_running = False
    
        if hasattr(bpy.types.Scene, "cursor_3d_tools_settings"):
            del bpy.types.Scene.cursor_3d_tools_settings
    
        if hasattr(bpy.types.Screen, "cursor_3d_tools_settings"):
            del bpy.types.Screen.cursor_3d_tools_settings
    
        if hasattr(bpy.types.WindowManager, "align_orientation_properties"):
            del bpy.types.WindowManager.align_orientation_properties
    
        if hasattr(bpy.types.WindowManager, "cursor_3d_runtime_settings"):
            del bpy.types.WindowManager.cursor_3d_runtime_settings
    
        bpy.types.VIEW3D_PT_transform_orientations.remove(
            transform_orientations_panel_extension)
    
        #bpy.types.VIEW3D_PT_view3d_properties.remove(draw_cursor_tools)
    
    
        bpy.types.VIEW3D_MT_snap.remove(extra_snap_menu_draw)
    
        bpy.app.handlers.load_post.remove(scene_load_post)
    
    if __name__ == "__main__":
        # launched from the Blender text editor
        try:
            register()
        except Exception as e:
    
            raise