
# ##### 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 2
#  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, write to the Free Software Foundation,
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####


bl_info = {
    'name': 'NP 020 Point Align',
    'author': 'Okavango & the Blenderartists community',
    'version': (0, 2, 0),
    'blender': (2, 75, 0),
    'location': 'View3D',
    'warning': '',
    'description': 'Aligns selected objects using 1, 2 or 3 pairs of snap points',
    'doc_url': '',
    'category': '3D View'}

import bpy
import copy
import bgl
import blf
import mathutils
from mathutils import *
from math import *
#from math import sin, cos, tan, atan, degrees, radians, asin, acos
from bpy_extras import view3d_utils
from bpy.app.handlers import persistent

from .utils_geometry import *
from .utils_graphics import *
from .utils_function import *

# Defining the main class - the macro:

class NP020PointAlign(bpy.types.Macro):
    bl_idname = 'object.np_020_point_align'
    bl_label = 'NP 020 Point Align'
    bl_options = {'UNDO'}


# Defining the storage class that will serve as a variable bank for exchange among the classes. Later, this bank will receive more variables with their values for safe keeping, as the program goes on:

class NP020PL:

    flag = 'RUNTRANSF0'

# Defining the scene update algorithm that will track the state of the objects during modal transforms, which is otherwise impossible:
'''
@persistent
def NPPL_scene_update(context):

    if bpy.data.objects.is_updated:
        region = bpy.context.region
        rv3d = bpy.context.region_data
        helper = NP020PL.helper
        co = helper.location
'''

# Defining the first of the classes from the macro, that will gather the current system settings set by the user. Some of the system settings will be changed during the process, and will be restored when macro has completed.

class NPPLGetContext(bpy.types.Operator):
    bl_idname = 'object.np_pl_get_context'
    bl_label = 'NP PL Get Context'
    bl_options = {'INTERNAL'}

    def execute(self, context):
        if bpy.context.selected_objects == []:
            self.report({'WARNING'}, "Please select objects first")
            return {'CANCELLED'}
        NP020PL.use_snap = copy.deepcopy(bpy.context.tool_settings.use_snap)
        NP020PL.snap_element = copy.deepcopy(bpy.context.tool_settings.snap_element)
        NP020PL.snap_target = copy.deepcopy(bpy.context.tool_settings.snap_target)
        NP020PL.pivot_point = copy.deepcopy(bpy.context.space_data.pivot_point)
        NP020PL.trans_orient = copy.deepcopy(bpy.context.space_data.transform_orientation)
        NP020PL.curloc = copy.deepcopy(bpy.context.scene.cursor.location)
        NP020PL.acob = bpy.context.active_object
        if bpy.context.mode == 'OBJECT':
            NP020PL.edit_mode = 'OBJECT'
        elif bpy.context.mode in ('EDIT_MESH', 'EDIT_CURVE', 'EDIT_SURFACE', 'EDIT_TEXT', 'EDIT_ARMATURE', 'EDIT_METABALL', 'EDIT_LATTICE'):
            NP020PL.edit_mode = 'EDIT'
        elif bpy.context.mode == 'POSE':
            NP020PL.edit_mode = 'POSE'
        elif bpy.context.mode == 'SCULPT':
            NP020PL.edit_mode = 'SCULPT'
        elif bpy.context.mode == 'PAINT_WEIGHT':
            NP020PL.edit_mode = 'WEIGHT_PAINT'
        elif bpy.context.mode == 'PAINT_TEXTURE':
            NP020PL.edit_mode = 'TEXTURE_PAINT'
        elif bpy.context.mode == 'PAINT_VERTEX':
            NP020PL.edit_mode = 'VERTEX_PAINT'
        elif bpy.context.mode == 'PARTICLE':
            NP020PL.edit_mode = 'PARTICLE_EDIT'
        return {'FINISHED'}


# Defining the operator for aquiring the list of selected objects and storing them for later re-calls:

class NPPLGetSelection(bpy.types.Operator):
    bl_idname = 'object.np_pl_get_selection'
    bl_label = 'NP PL Get Selection'
    bl_options = {'INTERNAL'}

    def execute(self, context):
        # Reading and storing the selection:
        NP020PL.selob = bpy.context.selected_objects
        return {'FINISHED'}


# Defining the operator that will read the mouse position in 3D when the command is activated and store it as a location for placing the helper object under the mouse:

class NPPLGetMouseloc(bpy.types.Operator):
    bl_idname = 'object.np_pl_get_mouseloc'
    bl_label = 'NP PL Get Mouseloc'
    bl_options = {'INTERNAL'}

    def modal(self, context, event):
        region = context.region
        rv3d = context.region_data
        co2d = ((event.mouse_region_x, event.mouse_region_y))
        view_vector = view3d_utils.region_2d_to_vector_3d(region, rv3d, co2d)
        enterloc = view3d_utils.region_2d_to_origin_3d(region, rv3d, co2d) + view_vector*100
        NP020PL.enterloc = copy.deepcopy(enterloc)
        np_print('02_GetMouseloc_FINISHED', ';', 'NP020PL.flag = ', NP020PL.flag)
        return{'FINISHED'}

    def invoke(self,context,event):
        args = (self, context)
        context.window_manager.modal_handler_add(self)
        np_print('02_GetMouseloc_INVOKED_FINISHED', ';', 'NP020PL.flag = ', NP020PL.flag)
        return {'RUNNING_MODAL'}


# Defining the operator that will generate the helper object at the spot marked by mouse, preparing for translation:

class NPPLAddHelper(bpy.types.Operator):
    bl_idname = 'object.np_pl_add_helper'
    bl_label = 'NP PL Add Helper'
    bl_options = {'INTERNAL'}

    def execute(self, context):
        np_print('03_AddHelper_START', ';', 'NP020PL.flag = ', NP020PL.flag)
        enterloc = NP020PL.enterloc
        bpy.ops.object.add(type = 'MESH',location = enterloc)
        helper = bpy.context.active_object
        helper.name = 'NP_PA_helper'
        NP020PL.helper = helper
        np_print('03_AddHelper_FINISHED', ';', 'NP020PL.flag = ', NP020PL.flag)
        return{'FINISHED'}


# Defining the operator that will change some of the system settings and prepare objects for the operation:

class NPPLPrepareContext(bpy.types.Operator):
    bl_idname = 'object.np_pl_prepare_context'
    bl_label = 'NP PL Prepare Context'
    bl_options = {'INTERNAL'}

    def execute(self, context):

        flag = NP020PL.flag
        helper = NP020PL.helper
        NP020PL.mode = '1P'
        NP020PL.snap = 'VERTEX'
        np_print('prepare, NP020PL.flag = ', flag)

        bpy.ops.object.select_all(action = 'DESELECT')
        helper.select_set(True)
        bpy.context.view_layer.objects.active = helper
        bpy.context.tool_settings.use_snap = False
        bpy.context.tool_settings.snap_element = 'VERTEX'
        bpy.context.tool_settings.snap_target = 'ACTIVE'
        bpy.context.space_data.pivot_point = 'ACTIVE_ELEMENT'
        bpy.context.space_data.transform_orientation = 'GLOBAL'

        NP020PL.frompoints = [None, None, None]
        NP020PL.topoints = [None, None, None]

        return{'FINISHED'}


# Defining the operator that will let the user translate the helper to the desired point. It also uses some listening operators that clean up the leftovers should the user interrupt the command. Many thanks to CoDEmanX and lukas_t:

class NPPLRunTranslate(bpy.types.Operator):
    bl_idname = 'object.np_pl_run_translate'
    bl_label = 'NP PL Run Translate'
    bl_options = {'INTERNAL'}

    np_print('RunTrans_START',';','NP020PL.flag = ', NP020PL.flag)

    def modal(self, context, event):
        context.area.tag_redraw()
        flag = NP020PL.flag
        helper = NP020PL.helper

        if event.type in ('LEFTMOUSE', 'NUMPAD_ENTER', 'SPACE') and event.value == 'RELEASE':
            bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
            self.co2d = ((event.mouse_region_x, event.mouse_region_y))
            if flag == 'RUNTRANSF0':
                NP020PL.frompoints[0] = copy.deepcopy(NP020PL.helper.location)
                NP020PL.flag = 'RUNTRANST0'
            elif flag == 'RUNTRANST0':
                NP020PL.topoints[0] = copy.deepcopy(NP020PL.helper.location)
                NP020PL.flag = 'RUNTRANSF1'
            elif flag == 'RUNTRANSF1':
                NP020PL.frompoints[1] = copy.deepcopy(NP020PL.helper.location)
                NP020PL.flag = 'RUNTRANST1'
            elif flag == 'RUNTRANST1':
                NP020PL.topoints[1] = copy.deepcopy(NP020PL.helper.location)
                NP020PL.mode = '2P'
                NP020PL.flag = 'RUNTRANSF2'
            elif flag == 'RUNTRANSF2':
                NP020PL.frompoints[2] = copy.deepcopy(NP020PL.helper.location)
                NP020PL.flag = 'RUNTRANST2'
            elif flag == 'RUNTRANST2':
                NP020PL.topoints[2] = copy.deepcopy(NP020PL.helper.location)
                NP020PL.mode = '3P'
                NP020PL.flag = 'ALIGN'
            np_print('RunTrans_left_enter_FINISHED',';','NP020PL.flag = ', NP020PL.flag)
            return{'FINISHED'}

        elif event.type == 'RET' and event.value == 'RELEASE':
            bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
            if bpy.context.tool_settings.snap_element == 'VERTEX':
                bpy.context.tool_settings.snap_element = 'EDGE'
                NP020PL.snap = 'EDGE'
            elif bpy.context.tool_settings.snap_element == 'EDGE':
                bpy.context.tool_settings.snap_element = 'FACE'
                NP020PL.snap = 'FACE'
            elif bpy.context.tool_settings.snap_element == 'FACE':
                bpy.context.tool_settings.snap_element = 'VERTEX'
                NP020PL.snap = 'VERTEX'
            np_print('RunTrans_enter_PASS')
            return{'FINISHED'}

        elif event.type == 'RIGHTMOUSE':
            bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
            if NP020PL.topoints[0] != None:
                NP020PL.flag = 'ALIGN'
                np_print('04_RunTrans_right_ALIGN',';','NP020PL.flag = ', NP020PL.flag)
            else:
                NP020PL.flag = 'EXIT'
                np_print('04_RunTrans_right_EXIT',';','NP020PL.flag = ', NP020PL.flag)
            return{'FINISHED'}

        elif event.type == 'ESC':
            bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
            NP020PL.flag = 'EXIT'
            np_print('04_RunTrans_esc_EXIT',';','NP020PL.flag = ', NP020PL.flag)
            return{'FINISHED'}

        np_print('04_RunTrans_count_PASS_THROUGH',';','NP020PL.flag = ', NP020PL.flag)
        return{'PASS_THROUGH'}

    def invoke(self, context, event):
        np_print('RunTrans_INVOKE_START')
        helper = NP020PL.helper
        flag = NP020PL.flag

        if context.area.type == 'VIEW_3D':
            if flag in ('RUNTRANSF0', 'RUNTRANST0', 'RUNTRANSF1', 'RUNTRANST1', 'RUNTRANSF2', 'RUNTRANST2'):
                self.co2d = ((event.mouse_region_x, event.mouse_region_y))
                args = (self, context)
                self._handle = bpy.types.SpaceView3D.draw_handler_add(DRAW_Overlay, args, 'WINDOW', 'POST_PIXEL')
                context.window_manager.modal_handler_add(self)
                bpy.ops.transform.translate('INVOKE_DEFAULT')
                np_print('RunTrans_INVOKED_RUNNING_MODAL',';','NP020PL.flag = ', NP020PL.flag)
                return {'RUNNING_MODAL'}
            else:
                np_print('RunTrans_INVOKE_DECLINED_wrong_flag_FINISHED',';','NP020PL.flag = ', NP020PL.flag)
                return {'FINISHED'}
        else:
            self.report({'WARNING'}, "View3D not found, cannot run operator")
            NP020PL.flag = 'EXIT'
            np_print('RunTrans_INVOKE_DECLINED_no_VIEW_3D_FINISHED',';','NP020PL.flag = ', NP020PL.flag)
            return {'FINISHED'}


# Defining the set of instructions that will draw the OpenGL elements on the screen during the execution of RunTranslate operator:

def DRAW_Overlay(self, context):

    np_print('DRAW_Overlay_START',';','NP020PL.flag = ', NP020PL.flag)

    flag = NP020PL.flag
    helper = NP020PL.helper
    region = bpy.context.region
    rv3d = bpy.context.region_data
    rw = region.width
    rh = region.height
    co2d = view3d_utils.location_3d_to_region_2d(region, rv3d, helper.location)
    frompoints = NP020PL.frompoints
    topoints = NP020PL.topoints


    col_line_main = (1.0, 1.0, 1.0, 1.0)
    col_line_shadow = (0.1, 0.1, 0.1, 0.25)

    #greys:
    mark_col_A = (0.25, 0.25, 0.25, 1.0)
    mark_col_B = (0.5, 0.5, 0.5, 1.0)
    mark_col_C = (0.75, 0.75, 0.75, 1.0)

    #marins
    mark_col_A = (0.25, 0.35, 0.4, 1.0)
    mark_col_B = (0.5, 0.6, 0.65, 1.0)
    mark_col_C = (0.67, 0.77, 0.82, 1.0)

    # writing the dots for recwidget widget at center of scene:

    r = 12
    angstep = 20
    psize = 25
    pofsetx = 15
    pofsety = 15
    fsize = 20
    fofsetx = -8
    fofsety = -7



    widget_circle = construct_circle_2d(r, angstep)


    if flag == 'RUNTRANSF0':
        instruct = 'place start for point a'
        keys_aff = 'LMB - select, CTRL - snap, ENT - change snap'
        keys_neg = 'RMB, ESC - quit'
        frompoints[0] = helper.location

    elif flag == 'RUNTRANST0':
        instruct = 'place target for point a'
        keys_aff = 'LMB - select, CTRL - snap, ENT - change snap, MMB - lock axis, NUMPAD - value'
        keys_neg = 'RMB, ESC - quit'
        topoints[0] = helper.location

    elif flag == 'RUNTRANSF1':
        instruct = 'place start for point b'
        keys_aff = 'LMB - select, CTRL - snap, ENT - change snap, MMB - lock axis, NUMPAD - value, RMB - align A (translate)'
        keys_neg = 'ESC - quit'
        frompoints[1] = helper.location

    elif flag == 'RUNTRANST1':
        instruct = 'place target for point b'
        keys_aff = 'LMB - select, CTRL - snap, ENT - change snap, MMB - lock axis, NUMPAD - value, RMB - align A (translate)'
        keys_neg = 'ESC - quit'
        topoints[1] = helper.location

    elif flag == 'RUNTRANSF2':
        instruct = 'place start for point c'
        keys_aff = 'LMB - select, CTRL - snap, ENT - change snap, MMB - lock axis, NUMPAD - value, RMB - align AB (translate, rotate)'
        keys_neg = 'ESC - quit'
        frompoints[2] = helper.location

    elif flag == 'RUNTRANST2':
        instruct = 'place target for point c'
        keys_aff = 'LMB - select, CTRL - snap, ENT - change snap, MMB - lock axis, NUMPAD - value, RMB - align AB (translate, rotate)'
        keys_neg = 'ESC - quit'
        topoints[2] = helper.location


    # ON-SCREEN INSTRUCTIONS:


    keys_nav = ''

    display_instructions(region, rv3d, instruct, keys_aff, keys_nav, keys_neg)


    # LINE:

    for i, frompoint in enumerate(frompoints):
        topoint = topoints[i]
        if frompoint != None and topoint != None:
            bgl.glColor4f(*col_line_shadow)
            bgl.glLineWidth(1.4)
            bgl.glBegin(bgl.GL_LINE_STRIP)
            frompoint = view3d_utils.location_3d_to_region_2d(region, rv3d, frompoint)
            topoint = view3d_utils.location_3d_to_region_2d(region, rv3d, topoint)
            bgl.glVertex2f((frompoint[0] - 1), (frompoint[1] - 1))
            bgl.glVertex2f((topoint[0] - 1), (topoint[1] - 1))
            bgl.glEnd()

            bgl.glColor4f(*col_line_main)
            bgl.glLineWidth(1.4)
            bgl.glBegin(bgl.GL_LINE_STRIP)
            bgl.glVertex2f(*frompoint)
            bgl.glVertex2f(*topoint)
            bgl.glEnd()


    # drawing of markers:
    i = 0
    for point in frompoints:
        if point != None:
            point = view3d_utils.location_3d_to_region_2d(region, rv3d, point)
            point[0] = point[0] + pofsetx
            point[1] = point[1] + pofsety
            widget = []
            for co in widget_circle:
                c = [0.0, 0.0]
                c[0] = co[0] + point[0] + pofsetx
                c[1] = co[1] + point[1] + pofsety
                widget.append(c)
            bgl.glEnable(bgl.GL_BLEND)
            if i == 0:
                bgl.glColor4f(*mark_col_A)
                mark = "A"
            elif i == 1:
                bgl.glColor4f(*mark_col_B)
                mark = "B"
            elif i == 2:
                bgl.glColor4f(*mark_col_C)
                mark = "C"

            '''
            bgl.glLineWidth(1)
            bgl.glBegin(bgl.GL_LINE_STRIP)
            for co in widget:
                bgl.glVertex2f(*co)
            bgl.glVertex2f(*widget[len(widget) - 1])
            bgl.glEnd()
            bgl.glBegin(bgl.GL_TRIANGLE_FAN)
            for co in widget:
                bgl.glVertex2f(*co)
            bgl.glEnd()
            '''
            font_id = 0
            bgl.glEnable(bgl.GL_POINT_SMOOTH)
            bgl.glPointSize(psize)
            bgl.glBegin(bgl.GL_POINTS)
            bgl.glVertex2f(*point)
            bgl.glEnd()
            bgl.glDisable(bgl.GL_POINT_SMOOTH)

            bgl.glColor4f(1.0, 1.0, 1.0, 0.75)
            blf.size(font_id, fsize, 72)
            blf.position(font_id, point[0] + fofsetx, point[1] + fofsety, 0)
            blf.draw(font_id, mark)
            i = i + 1

    i = 0
    for point in topoints:
        if point != None:
            point = view3d_utils.location_3d_to_region_2d(region, rv3d, point)
            point[0] = point[0] + pofsetx
            point[1] = point[1] + pofsety
            widget = []
            for co in widget_circle:
                c = [0.0, 0.0]
                c[0] = co[0] + point[0] + pofsetx
                c[1] = co[1] + point[1] + pofsety
                widget.append(c)
            bgl.glEnable(bgl.GL_BLEND)
            if i == 0:
                bgl.glColor4f(*mark_col_A)
                mark = "A'"
            elif i == 1:
                bgl.glColor4f(*mark_col_B)
                mark = "B'"
            elif i == 2:
                bgl.glColor4f(*mark_col_C)
                mark = "C'"
            '''
            bgl.glLineWidth(1)
            bgl.glBegin(bgl.GL_LINE_STRIP)
            for co in widget:
                bgl.glVertex2f(*co)
            bgl.glVertex2f(*widget[len(widget) - 1])
            bgl.glEnd()
            bgl.glBegin(bgl.GL_TRIANGLE_FAN)
            for co in widget:
                bgl.glVertex2f(*co)
            bgl.glEnd()
            '''
            font_id = 0
            bgl.glEnable(bgl.GL_POINT_SMOOTH)
            bgl.glPointSize(psize)
            bgl.glBegin(bgl.GL_POINTS)
            bgl.glVertex2f(*point)
            bgl.glEnd()
            bgl.glDisable(bgl.GL_POINT_SMOOTH)

            bgl.glColor4f(1.0, 1.0, 1.0, 0.75)
            blf.size(font_id, fsize, 72)
            blf.position(font_id, point[0] + fofsetx, point[1] + fofsety, 0)
            blf.draw(font_id, mark)
            i = i + 1


    #ENDING:

    bgl.glLineWidth(1)
    bgl.glDisable(bgl.GL_BLEND)
    bgl.glColor4f(0.0, 0.0, 0.0, 1.0)


# Defining the operator that will align the selected objects according to the collected points:

class NPPLAlignSelected(bpy.types.Operator):
    bl_idname = 'object.np_pl_align_selected'
    bl_label = 'NP PL Align Selected'
    bl_options = {'INTERNAL'}

    def execute(self, context):

        flag = NP020PL.flag
        mode = NP020PL.mode
        helper = NP020PL.helper
        selob = NP020PL.selob
        frompoints = NP020PL.frompoints
        topoints = NP020PL.topoints
        scale = False
        copy = False

        bpy.ops.object.select_all(action = 'DESELECT')
        helper.select_set(True)
        bpy.ops.object.delete('EXEC_DEFAULT')


        fromverts = []
        for point in frompoints:
            if point != None:
                vert = point.to_tuple()
                fromverts.append(vert)
        fromedges = []
        fromfaces = []

        toverts = []
        for point in topoints:
            if point != None:
                vert = point.to_tuple()
                toverts.append(vert)
        toedges = []
        tofaces = []

        fromme = bpy.data.meshes.new('NP020PL_from')
        fromme.from_pydata(fromverts, fromedges, fromfaces)
        fromob = bpy.data.objects.new('NP020PL_from',fromme)
        fromob.location = mathutils.Vector((0,0,0))


        tome = bpy.data.meshes.new('NP020PL_to')
        tome.from_pydata(toverts, toedges, tofaces)
        toob = bpy.data.objects.new('NP020PL_to',tome)
        toob.location = mathutils.Vector((0,0,0))

        scn = bpy.context.scene
        scn.objects.link(fromob)
        scn.objects.link(toob)
        scn.update()

        bpy.ops.object.select_all(action = 'DESELECT')
        fromob.select_set(True)
        bpy.ops.object.origin_set(type = 'ORIGIN_GEOMETRY')


        for ob in selob:
            ob.select_set(True)

        if flag == 'ALIGN':
            value = (tome.vertices[0].co) - (fromme.vertices[0].co + fromob.location)
            bpy.ops.transform.translate(value = value)
            scn.update()
            if mode in ('2P', '3P'):

                rot_axis = geometry.normal(tome.vertices[0].co, tome.vertices[1].co, (fromme.vertices[1].co + fromob.location))
                rot_ang = (((fromme.vertices[1].co + fromob.location) - tome.vertices[0].co)).angle((tome.vertices[1].co - tome.vertices[0].co))
                np_print('rot_axis, rot_ang =', rot_axis, rot_ang)
                bpy.context.scene.cursor.location = tome.vertices[0].co
                bpy.context.space_data.pivot_point = 'CURSOR'
                bpy.ops.transform.rotate(value = -rot_ang, axis = rot_axis)
                np_print('fromme.vertices = ', fromme.vertices)
                bpy.ops.object.select_all(action = 'DESELECT')
                fromob.select_set(True)
                bpy.ops.object.transform_apply(location=False,rotation=True,scale=True)
                for ob in selob:
                    ob.select_set(True)
                scn.update()
                #if scale:
                    #bpy.ops.transform_resize(value=(1.0, 1.0, 1.0))
                if mode == '3P':
                    proj_t = geometry.intersect_point_line(tome.vertices[2].co, tome.vertices[1].co, tome.vertices[0].co)[0]
                    proj_f = geometry.intersect_point_line((fromme.vertices[2].co + fromob.location), tome.vertices[1].co, tome.vertices[0].co)[0]
                    np_print('proj_t, proj_f, tome[1], tome[0]', proj_t, proj_f, tome.vertices[1].co, tome.vertices[0].co)
                    #rot_axis = geometry.normal(tome.vertices[2].co, proj, (fromme.vertices[2].co + fromob.location))
                    rot_axis = tome.vertices[1].co - tome.vertices[0].co
                    rot_ang = ((tome.vertices[2].co - proj_t)).angle(((fromme.vertices[2].co + fromob.location) - proj_f))
                    np_print('rot_axis, rot_ang =', rot_axis, rot_ang)
                    bpy.context.scene.cursor.location = tome.vertices[1].co
                    bpy.ops.transform.rotate(value = -rot_ang, axis = rot_axis)
                    bpy.ops.object.select_all(action = 'DESELECT')
                    fromob.select_set(True)
                    bpy.ops.object.transform_apply(location=False,rotation=True,scale=True)
                    for ob in selob:
                        ob.select_set(True)
                    if ((tome.vertices[2].co - proj_t)).angle(((fromme.vertices[2].co + fromob.location) - proj_f)) != 0: bpy.ops.transform.rotate(value = 2*rot_ang, axis = rot_axis)

        bpy.ops.object.select_all(action = 'DESELECT')
        fromob.select_set(True)
        toob.select_set(True)
        bpy.ops.object.delete('EXEC_DEFAULT')

        return{'FINISHED'}

# Restoring the object selection and system settings from before the operator activation:

class NPPLRestoreContext(bpy.types.Operator):
    bl_idname = "object.np_pl_restore_context"
    bl_label = "NP PL Restore Context"
    bl_options = {'INTERNAL'}

    def execute(self, context):
        bpy.ops.object.select_all(action = 'DESELECT')
        selob = NP020PL.selob
        for ob in selob:
            ob.select_set(True)
        if NP020PL.acob is not None:
            bpy.context.view_layer.objects.active = NP020PL.acob
            bpy.ops.object.mode_set(mode = NP020PL.edit_mode)
        bpy.context.tool_settings.use_snap = NP020PL.use_snap
        bpy.context.tool_settings.snap_element = NP020PL.snap_element
        bpy.context.tool_settings.snap_target = NP020PL.snap_target
        bpy.context.space_data.pivot_point = NP020PL.pivot_point
        bpy.context.space_data.transform_orientation = NP020PL.trans_orient
        bpy.context.scene.cursor.location = NP020PL.curloc

        NP020PL.flag = 'RUNTRANSF0'
        return {'FINISHED'}


# This is the actual addon process, the algorithm that defines the order of operator activation inside the main macro:

def register():

    #bpy.app.handlers.scene_update_post.append(NPPL_scene_update)

    NP020PointAlign.define('OBJECT_OT_np_pl_get_context')
    NP020PointAlign.define('OBJECT_OT_np_pl_get_selection')
    NP020PointAlign.define('OBJECT_OT_np_pl_get_mouseloc')
    NP020PointAlign.define('OBJECT_OT_np_pl_add_helper')
    NP020PointAlign.define('OBJECT_OT_np_pl_prepare_context')
    for i in range(1, 50):
        NP020PointAlign.define('OBJECT_OT_np_pl_run_translate')
    NP020PointAlign.define('OBJECT_OT_np_pl_align_selected')
    NP020PointAlign.define('OBJECT_OT_np_pl_restore_context')

def unregister():
    #bpy.app.handlers.scene_update_post.remove(NPPL_scene_update)
    pass
