Skip to content
Snippets Groups Projects
pdt_command_functions.py 30 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 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 LICENCE BLOCK *****
    #
    # -----------------------------------------------------------------------
    # Author: Alan Odom (Clockmender), Rune Morling (ermo) Copyright (c) 2019
    # -----------------------------------------------------------------------
    #
    import bmesh
    import numpy as np
    
    from math import sqrt, tan, pi
    
    from mathutils import Vector
    from mathutils.geometry import intersect_point_line
    from .pdt_functions import (
        set_mode,
        oops,
        get_percent,
        dis_ang,
        check_selection,
        arc_centre,
        intersection,
        view_coords_i,
        view_coords,
        view_dir,
        set_axis,
    )
    
    Alan Odom's avatar
    Alan Odom committed
    
    from . import pdt_exception
    PDT_SelectionError = pdt_exception.SelectionError
    PDT_InvalidVector = pdt_exception.InvalidVector
    
    PDT_ObjectModeError = pdt_exception.ObjectModeError
    
    Alan Odom's avatar
    Alan Odom committed
    PDT_InfRadius = pdt_exception.InfRadius
    PDT_NoObjectError = pdt_exception.NoObjectError
    PDT_IntersectionError = pdt_exception.IntersectionError
    PDT_InvalidOperation = pdt_exception.InvalidOperation
    PDT_VerticesConnected = pdt_exception.VerticesConnected
    PDT_InvalidAngle = pdt_exception.InvalidAngle
    
    
    from .pdt_msg_strings import (
        PDT_ERR_BAD3VALS,
        PDT_ERR_BAD2VALS,
        PDT_ERR_BAD1VALS,
        PDT_ERR_CONNECTED,
        PDT_ERR_SEL_2_VERTS,
        PDT_ERR_EDOB_MODE,
        PDT_ERR_NO_ACT_OBJ,
        PDT_ERR_VERT_MODE,
        PDT_ERR_SEL_3_VERTS,
        PDT_ERR_SEL_3_OBJS,
        PDT_ERR_EDIT_MODE,
        PDT_ERR_NON_VALID,
        PDT_LAB_NOR,
        PDT_ERR_STRIGHT_LINE,
        PDT_LAB_ARCCENTRE,
        PDT_ERR_SEL_4_VERTS,
        PDT_ERR_INT_NO_ALL,
        PDT_LAB_INTERSECT,
        PDT_ERR_SEL_4_OBJS,
        PDT_INF_OBJ_MOVED,
        PDT_ERR_SEL_2_VERTIO,
        PDT_ERR_SEL_2_OBJS,
        PDT_ERR_SEL_3_VERTIO,
        PDT_ERR_TAPER_ANG,
        PDT_ERR_TAPER_SEL,
    
        PDT_ERR_INT_LINES,
        PDT_LAB_PLANE,
    
    def vector_build(context, pg, obj, operation, values, num_values):
    
        """Build Movement Vector from input Fields.
    
        Args:
            context: Blender bpy.context instance.
    
            pg: PDT Parameters Group - our variables
            obj: The Active Object
            operation: The Operation e.g. Create New Vertex
            values: The paramters passed e.g. 1,4,3 for Catrtesan Coordinates
            num_values: The number of values passed - determines the function
    
    
        Returns:
            Vector to position, or offset items.
        """
    
        scene = context.scene
        plane = pg.plane
    
        flip_angle = pg.flip_angle
        flip_percent= pg.flip_percent
    
        # Cartesian 3D coordinates
    
        if num_values == 3 and len(values) == 3:
            output_vector = Vector((float(values[0]), float(values[1]), float(values[2])))
    
        # Polar 2D coordinates
    
        elif num_values == 2 and len(values) == 2:
    
            output_vector = dis_ang(values, flip_angle, plane, scene)
    
        # Percentage of imaginary line between two 3D coordinates
    
        elif num_values == 1 and len(values) == 1:
    
            output_vector = get_percent(obj, flip_percent, float(values[0]), operation, scene)
    
        else:
            if num_values == 3:
    
                pg.error = PDT_ERR_BAD3VALS
    
            elif num_values == 2:
    
                pg.error = PDT_ERR_BAD2VALS
    
                pg.error = PDT_ERR_BAD1VALS
    
            context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
    
            raise PDT_InvalidVector
    
    Alan Odom's avatar
    Alan Odom committed
        return output_vector
    
    def placement_normal(context, operation):
    
        """Manipulates Geometry, or Objects by Normal Intersection between 3 points.
    
        Args:
            context: Blender bpy.context instance.
    
            operation: The Operation e.g. Create New Vertex
    
    
        Returns:
            Status Set.
        """
    
        scene = context.scene
        pg = scene.pdt_pg
    
        extend_all = pg.extend
    
        obj = context.view_layer.objects.active
    
        if obj.mode == "EDIT":
    
            if obj is None:
                pg.error = PDT_ERR_NO_ACT_OBJ
                context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
    
                raise PDT_ObjectModeError
    
            obj_loc = obj.matrix_world.decompose()[0]
    
            bm = bmesh.from_edit_mesh(obj.data)
            if len(bm.select_history) == 3:
    
                vector_a, vector_b, vector_c = check_selection(3, bm, obj)
                if vector_a is None:
    
                    pg.error = PDT_ERR_VERT_MODE
                    context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
    
    Alan Odom's avatar
    Alan Odom committed
                    raise PDT_InvalidVector
    
    Alan Odom's avatar
    Alan Odom committed
                pg.error = f"{PDT_ERR_SEL_3_VERTIO} {len(bm.select_history)})"
    
                context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
    
    Alan Odom's avatar
    Alan Odom committed
                raise PDT_SelectionError
    
        elif obj.mode == "OBJECT":
            objs = context.view_layer.objects.selected
            if len(objs) != 3:
                pg.error = f"{PDT_ERR_SEL_3_OBJS} {len(objs)})"
    
                context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
    
    Alan Odom's avatar
    Alan Odom committed
                raise PDT_SelectionError
    
            objs_s = [ob for ob in objs if ob.name != obj.name]
            vector_a = obj.matrix_world.decompose()[0]
            vector_b = objs_s[-1].matrix_world.decompose()[0]
            vector_c = objs_s[-2].matrix_world.decompose()[0]
        vector_delta = intersect_point_line(vector_a, vector_b, vector_c)[0]
    
        if operation == "C":
            if obj.mode == "EDIT":
                scene.cursor.location = obj_loc + vector_delta
            elif obj.mode == "OBJECT":
                scene.cursor.location = vector_delta
    
        elif operation == "P":
    
            if obj.mode == "EDIT":
                pg.pivot_loc = obj_loc + vector_delta
            elif obj.mode == "OBJECT":
                pg.pivot_loc = vector_delta
        elif operation == "G":
            if obj.mode == "EDIT":
    
                    for v in [v for v in bm.verts if v.select]:
                        v.co = vector_delta
                    bm.select_history.clear()
    
                    bmesh.ops.remove_doubles(bm, verts=[v for v in bm.verts if v.select], dist=0.0001)
    
                else:
                    bm.select_history[-1].co = vector_delta
                    bm.select_history.clear()
                bmesh.update_edit_mesh(obj.data)
            elif obj.mode == "OBJECT":
                context.view_layer.objects.active.location = vector_delta
        elif operation == "N":
            if obj.mode == "EDIT":
    
                vertex_new = bm.verts.new(vector_delta)
    
                bmesh.update_edit_mesh(obj.data)
                bm.select_history.clear()
                for v in [v for v in bm.verts if v.select]:
                    v.select_set(False)
    
                vertex_new.select_set(True)
    
            else:
                pg.error = f"{PDT_ERR_EDIT_MODE} {obj.mode})"
                context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
                return
        elif operation == "V" and obj.mode == "EDIT":
    
            vector_new = vector_delta
            vertex_new = bm.verts.new(vector_new)
    
                for v in [v for v in bm.verts if v.select]:
    
                    bm.edges.new([v, vertex_new])
    
                bm.edges.new([bm.select_history[-1], vertex_new])
    
            for v in [v for v in bm.verts if v.select]:
                v.select_set(False)
    
            vertex_new.select_set(True)
    
            bmesh.update_edit_mesh(obj.data)
            bm.select_history.clear()
        else:
            pg.error = f"{operation} {PDT_ERR_NON_VALID} {PDT_LAB_NOR}"
            context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
    
    
    def placement_arc_centre(context, operation):
    
        """Manipulates Geometry, or Objects to an Arc Centre defined by 3 points on an Imaginary Arc.
    
        Args:
            context: Blender bpy.context instance.
    
            operation: The Operation e.g. Create New Vertex
    
    
        Returns:
            Status Set.
        """
    
        scene = context.scene
        pg = scene.pdt_pg
    
        extend_all = pg.extend
    
        obj = context.view_layer.objects.active
    
        if obj.mode == "EDIT":
    
            if obj is None:
                pg.error = PDT_ERR_NO_ACT_OBJ
                context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
    
                raise PDT_ObjectModeError
    
            obj = context.view_layer.objects.active
            obj_loc = obj.matrix_world.decompose()[0]
            bm = bmesh.from_edit_mesh(obj.data)
            verts = [v for v in bm.verts if v.select]
    
            if len(verts) != 3:
    
                pg.error = f"{PDT_ERR_SEL_3_VERTS} {len(verts)})"
                context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
    
    Alan Odom's avatar
    Alan Odom committed
                raise PDT_SelectionError
    
            vector_a = verts[0].co
            vector_b = verts[1].co
            vector_c = verts[2].co
            vector_delta, radius = arc_centre(vector_a, vector_b, vector_c)
    
            if str(radius) == "inf":
                pg.error = PDT_ERR_STRIGHT_LINE
                context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
    
    Alan Odom's avatar
    Alan Odom committed
                raise PDT_InfRadius
    
            pg.distance = radius
            if operation == "C":
                scene.cursor.location = obj_loc + vector_delta
            elif operation == "P":
                pg.pivot_loc = obj_loc + vector_delta
            elif operation == "N":
    
                vector_new = vector_delta
                vertex_new = bm.verts.new(vector_new)
    
                for v in [v for v in bm.verts if v.select]:
                    v.select_set(False)
    
                vertex_new.select_set(True)
    
                bmesh.update_edit_mesh(obj.data)
                bm.select_history.clear()
    
                vertex_new.select_set(True)
    
            elif operation == "G":
    
                    for v in [v for v in bm.verts if v.select]:
                        v.co = vector_delta
                    bm.select_history.clear()
                    bmesh.ops.remove_doubles(bm, verts=[v for v in bm.verts if v.select], dist=0.0001)
                else:
                    bm.select_history[-1].co = vector_delta
                    bm.select_history.clear()
                bmesh.update_edit_mesh(obj.data)
    
            elif operation == "V":
    
                vertex_new = bm.verts.new(vector_delta)
    
                    for v in [v for v in bm.verts if v.select]:
    
                        bm.edges.new([v, vertex_new])
    
                        v.select_set(False)
    
                    vertex_new.select_set(True)
    
                    bm.select_history.clear()
    
                    bmesh.ops.remove_doubles(bm, verts=[v for v in bm.verts if v.select], dist=0.0001)
    
                    bmesh.update_edit_mesh(obj.data)
                else:
    
                    bm.edges.new([bm.select_history[-1], vertex_new])
    
                    bmesh.update_edit_mesh(obj.data)
                    bm.select_history.clear()
            else:
                pg.error = f"{operation} {PDT_ERR_NON_VALID} {PDT_LAB_ARCCENTRE}"
                context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
        elif obj.mode == "OBJECT":
            if len(context.view_layer.objects.selected) != 3:
                pg.error = f"{PDT_ERR_SEL_3_OBJS} {len(context.view_layer.objects.selected)})"
                context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
    
    Alan Odom's avatar
    Alan Odom committed
                raise PDT_SelectionError
    
            vector_a = context.view_layer.objects.selected[0].matrix_world.decompose()[0]
            vector_b = context.view_layer.objects.selected[1].matrix_world.decompose()[0]
            vector_c = context.view_layer.objects.selected[2].matrix_world.decompose()[0]
            vector_delta, radius = arc_centre(vector_a, vector_b, vector_c)
            pg.distance = radius
            if operation == "C":
                scene.cursor.location = vector_delta
            elif operation == "P":
                pg.pivot_loc = vector_delta
            elif operation == "G":
                context.view_layer.objects.active.location = vector_delta
    
                pg.error = f"{operation} {PDT_ERR_NON_VALID} {PDT_LAB_ARCCENTRE}"
                context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
    
    def placement_intersect(context, operation):
    
        """Manipulates Geometry, or Objects by Convergance Intersection between 4 points, or 2 Edges.
    
        Args:
            context: Blender bpy.context instance.
    
            operation: The Operation e.g. Create New Vertex
    
    
        Returns:
            Status Set.
        """
    
        scene = context.scene
        pg = scene.pdt_pg
        plane = pg.plane
        obj = context.view_layer.objects.active
        if obj.mode == "EDIT":
    
            if obj is None:
                pg.error = PDT_ERR_NO_ACT_OBJ
                context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
    
    Alan Odom's avatar
    Alan Odom committed
                raise PDT_NoObjectError
    
            obj_loc = obj.matrix_world.decompose()[0]
            bm = bmesh.from_edit_mesh(obj.data)
            edges = [e for e in bm.edges if e.select]
    
            extend_all = pg.extend
    
    
            if len(edges) == 2:
                vertex_a = edges[0].verts[0]
                vertex_b = edges[0].verts[1]
                vertex_c = edges[1].verts[0]
                vertex_d = edges[1].verts[1]
            else:
                if len(bm.select_history) != 4:
                    pg.error = (
                        PDT_ERR_SEL_4_VERTS
                        + str(len(bm.select_history))
                        + " Vertices/"
                        + str(len(edges))
                        + " Edges)"
                    )
    
                    context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
    
    Alan Odom's avatar
    Alan Odom committed
                    raise PDT_SelectionError
    
                vertex_a = bm.select_history[-1]
                vertex_b = bm.select_history[-2]
                vertex_c = bm.select_history[-3]
                vertex_d = bm.select_history[-4]
    
            vector_delta, done = intersection(vertex_a.co, vertex_b.co, vertex_c.co, vertex_d.co, plane)
    
            if not done:
                pg.error = f"{PDT_ERR_INT_LINES} {plane}  {PDT_LAB_PLANE}"
                context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
    
    Alan Odom's avatar
    Alan Odom committed
                raise PDT_IntersectionError
    
    
            if operation == "C":
                scene.cursor.location = obj_loc + vector_delta
            elif operation == "P":
                pg.pivot_loc = obj_loc + vector_delta
            elif operation == "N":
    
                vector_new = vector_delta
                vertex_new = bm.verts.new(vector_new)
    
                for v in [v for v in bm.verts if v.select]:
                    v.select_set(False)
                for f in bm.faces:
                    f.select_set(False)
                for e in bm.edges:
                    e.select_set(False)
    
                vertex_new.select_set(True)
    
                bmesh.update_edit_mesh(obj.data)
                bm.select_history.clear()
            elif operation in {"G", "V"}:
    
                vertex_new = None
    
                process = False
    
                if (vertex_a.co - vector_delta).length < (vertex_b.co - vector_delta).length:
    
                    if operation == "G":
    
                        vertex_a.co = vector_delta
    
                        process = True
    
                    else:
                        vertex_new = bm.verts.new(vector_delta)
                        bm.edges.new([vertex_a, vertex_new])
    
                        process = True
    
                    if operation == "G" and extend_all:
    
                        vertex_b.co = vector_delta
    
                    elif operation == "V" and extend_all:
    
                        vertex_new = bm.verts.new(vector_delta)
                        bm.edges.new([vertex_b, vertex_new])
                    else:
                        return
    
                if (vertex_c.co - vector_delta).length < (vertex_d.co - vector_delta).length:
    
                    if operation == "G" and extend_all:
    
                        vertex_c.co = vector_delta
    
                    elif operation == "V" and extend_all:
    
                        bm.edges.new([vertex_c, vertex_new])
                    else:
                        return
    
                    if operation == "G" and extend_all:
    
                        vertex_d.co = vector_delta
    
                    elif operation == "V" and extend_all:
    
                        bm.edges.new([vertex_d, vertex_new])
                    else:
                        return
    
                bm.select_history.clear()
                bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.0001)
    
    
                if not process and not extend_all:
    
                    pg.error = PDT_ERR_INT_NO_ALL
                    context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
                    bmesh.update_edit_mesh(obj.data)
                    return
    
                for v in bm.verts:
                    v.select_set(False)
                for f in bm.faces:
                    f.select_set(False)
                for e in bm.edges:
                    e.select_set(False)
    
                if vertex_new is not None:
                    vertex_new.select_set(True)
                for v in bm.select_history:
                    if v is not None:
                        v.select_set(True)
                bmesh.update_edit_mesh(obj.data)
    
            else:
                pg.error = f"{operation} {PDT_ERR_NON_VALID} {PDT_LAB_INTERSECT}"
                context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
    
    Alan Odom's avatar
    Alan Odom committed
                raise PDT_InvalidOperation
    
        elif obj.mode == "OBJECT":
            if len(context.view_layer.objects.selected) != 4:
                pg.error = f"{PDT_ERR_SEL_4_OBJS} {len(context.view_layer.objects.selected)})"
                context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
    
    Alan Odom's avatar
    Alan Odom committed
                raise PDT_SelectionError
    
            order = pg.object_order.split(",")
            objs = sorted(context.view_layer.objects.selected, key=lambda x: x.name)
            pg.error = (
    
    Alan Odom's avatar
    Alan Odom committed
                "Original Object Order (1,2,3,4) was: "
    
                + objs[0].name
                + ", "
                + objs[1].name
                + ", "
                + objs[2].name
                + ", "
                + objs[3].name
            )
            context.window_manager.popup_menu(oops, title="Info", icon="INFO")
    
            vector_a = objs[int(order[0]) - 1].matrix_world.decompose()[0]
            vector_b = objs[int(order[1]) - 1].matrix_world.decompose()[0]
            vector_c = objs[int(order[2]) - 1].matrix_world.decompose()[0]
            vector_d = objs[int(order[3]) - 1].matrix_world.decompose()[0]
            vector_delta, done = intersection(vector_a, vector_b, vector_c, vector_d, plane)
    
            if not done:
                pg.error = f"{PDT_ERR_INT_LINES} {plane}  {PDT_LAB_PLANE}"
                context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
    
    Alan Odom's avatar
    Alan Odom committed
                raise PDT_IntersectionError
    
            if operation == "C":
    
                scene.cursor.location = vector_delta
    
            elif operation == "P":
    
                pg.pivot_loc = vector_delta
    
            elif operation == "G":
    
                context.view_layer.objects.active.location = vector_delta
    
                pg.error = f"{PDT_INF_OBJ_MOVED} {context.view_layer.objects.active.name}"
                context.window_manager.popup_menu(oops, title="Info", icon="INFO")
    
            else:
                pg.error = f"{operation} {PDT_ERR_NON_VALID} {PDT_LAB_INTERSECT}"
                context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
            return
    
    def join_two_vertices(context):
    
        """Joins 2 Free Vertices that do not form part of a Face.
    
        Joins two vertices that do not form part of a single face
        It is designed to close open Edge Loops, where a face is not required
        or to join two disconnected Edges.
    
        Args:
            context: Blender bpy.context instance.
    
        Returns:
            Status Set.
        """
    
        scene = context.scene
        pg = scene.pdt_pg
        obj = context.view_layer.objects.active
        if all([bool(obj), obj.type == "MESH", obj.mode == "EDIT"]):
            bm = bmesh.from_edit_mesh(obj.data)
            verts = [v for v in bm.verts if v.select]
            if len(verts) == 2:
                try:
                    bm.edges.new([verts[-1], verts[-2]])
                    bmesh.update_edit_mesh(obj.data)
                    bm.select_history.clear()
                    return
                except ValueError:
                    pg.error = PDT_ERR_CONNECTED
                    context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
    
    Alan Odom's avatar
    Alan Odom committed
                    raise PDT_VerticesConnected
    
            else:
                pg.error = f"{PDT_ERR_SEL_2_VERTS} {len(verts)})"
                context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
    
    Alan Odom's avatar
    Alan Odom committed
                raise PDT_SelectionError
    
        else:
            pg.error = f"{PDT_ERR_EDOB_MODE},{obj.mode})"
            context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
    
            raise PDT_ObjectModeError
    
    
    def set_angle_distance_two(context):
    
        """Measures Angle and Offsets between 2 Points in View Plane.
    
        Uses 2 Selected Vertices to set pg.angle and pg.distance scene variables
        also sets delta offset from these 2 points using standard Numpy Routines
        Works in Edit and Oject Modes.
    
        Args:
            context: Blender bpy.context instance.
    
        Returns:
            Status Set.
        """
    
        scene = context.scene
        pg = scene.pdt_pg
        plane = pg.plane
    
        flip_angle = pg.flip_angle
    
        obj = context.view_layer.objects.active
        if obj is None:
            pg.error = PDT_ERR_NO_ACT_OBJ
            context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
            return
        if obj.mode == "EDIT":
            bm = bmesh.from_edit_mesh(obj.data)
            verts = [v for v in bm.verts if v.select]
            if len(verts) == 2:
                if len(bm.select_history) == 2:
    
                    vector_a, vector_b = check_selection(2, bm, obj)
                    if vector_a is None:
                        pg.error = PDT_ERR_VERT_MODE
                        context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
    
    Alan Odom's avatar
    Alan Odom committed
                        raise PDT_InvalidVector
    
                else:
                    pg.error = f"{PDT_ERR_SEL_2_VERTIO} {len(bm.select_history)})"
                    context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
    
    Alan Odom's avatar
    Alan Odom committed
                    raise PDT_SelectionError
    
            else:
                pg.error = f"{PDT_ERR_SEL_2_VERTIO} {len(verts)})"
                context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
    
    Alan Odom's avatar
    Alan Odom committed
                raise PDT_SelectionError
    
        elif obj.mode == "OBJECT":
            objs = context.view_layer.objects.selected
            if len(objs) < 2:
                pg.error = f"{PDT_ERR_SEL_2_OBJS} {len(objs)})"
                context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
    
    Alan Odom's avatar
    Alan Odom committed
                raise PDT_SelectionError
    
            objs_s = [ob for ob in objs if ob.name != obj.name]
    
            vector_a = obj.matrix_world.decompose()[0]
            vector_b = objs_s[-1].matrix_world.decompose()[0]
    
        if plane == "LO":
    
            vector_difference = vector_b - vector_a
            vector_b = view_coords_i(vector_difference.x, vector_difference.y, vector_difference.z)
            vector_a = Vector((0, 0, 0))
            v0 = np.array([vector_a.x + 1, vector_a.y]) - np.array([vector_a.x, vector_a.y])
            v1 = np.array([vector_b.x, vector_b.y]) - np.array([vector_a.x, vector_a.y])
    
        else:
            a1, a2, _ = set_mode(plane)
    
            v0 = np.array([vector_a[a1] + 1, vector_a[a2]]) - np.array([vector_a[a1], vector_a[a2]])
            v1 = np.array([vector_b[a1], vector_b[a2]]) - np.array([vector_a[a1], vector_a[a2]])
    
        ang = np.rad2deg(np.arctan2(np.linalg.det([v0, v1]), np.dot(v0, v1)))
    
        decimal_places = context.preferences.addons[__package__].preferences.pdt_input_round
        if flip_angle:
    
            if ang > 0:
    
                pg.angle = round(ang - 180, decimal_places)
    
                pg.angle = round(ang - 180, decimal_places)
    
            pg.angle = round(ang, decimal_places)
    
        if plane == "LO":
    
    Alan Odom's avatar
    Alan Odom committed
            pg.distance = round(sqrt(
                (vector_a.x - vector_b.x) ** 2 +
    
                (vector_a.y - vector_b.y) ** 2), decimal_places)
    
    Alan Odom's avatar
    Alan Odom committed
            pg.distance = round(sqrt(
                (vector_a[a1] - vector_b[a1]) ** 2 +
    
                (vector_a[a2] - vector_b[a2]) ** 2), decimal_places)
        pg.cartesian_coords = Vector(([round(i, decimal_places) for i in vector_b - vector_a]))
    
    def set_angle_distance_three(context):
    
        """Measures Angle and Offsets between 3 Points in World Space, Also sets Deltas.
    
        Uses 3 Selected Vertices to set pg.angle and pg.distance scene variables
        also sets delta offset from these 3 points using standard Numpy Routines
        Works in Edit and Oject Modes.
    
        Args:
            context: Blender bpy.context instance.
    
        Returns:
            Status Set.
        """
    
        pg = context.scene.pdt_pg
    
        flip_angle = pg.flip_angle
    
        obj = context.view_layer.objects.active
        if obj is None:
            pg.error = PDT_ERR_NO_ACT_OBJ
            context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
    
    Alan Odom's avatar
    Alan Odom committed
            raise PDT_NoObjectError
    
        if obj.mode == "EDIT":
            bm = bmesh.from_edit_mesh(obj.data)
            verts = [v for v in bm.verts if v.select]
            if len(verts) == 3:
                if len(bm.select_history) == 3:
    
                    vector_a, vector_b, vector_c = check_selection(3, bm, obj)
                    if vector_a is None:
    
                        pg.error = PDT_ERR_VERT_MODE
                        context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
    
                        raise PDT_InvalidVector
    
                else:
                    pg.error = f"{PDT_ERR_SEL_3_VERTIO} {len(bm.select_history)})"
                    context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
    
    Alan Odom's avatar
    Alan Odom committed
                    raise PDT_SelectionError
    
            else:
                pg.error = f"{PDT_ERR_SEL_3_VERTIO} {len(verts)})"
                context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
    
    Alan Odom's avatar
    Alan Odom committed
                raise PDT_SelectionError
    
        elif obj.mode == "OBJECT":
            objs = context.view_layer.objects.selected
            if len(objs) < 3:
                pg.error = PDT_ERR_SEL_3_OBJS + str(len(objs))
                context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
    
    Alan Odom's avatar
    Alan Odom committed
                raise PDT_SelectionError
    
            objs_s = [ob for ob in objs if ob.name != obj.name]
    
            vector_a = obj.matrix_world.decompose()[0]
            vector_b = objs_s[-1].matrix_world.decompose()[0]
            vector_c = objs_s[-2].matrix_world.decompose()[0]
        ba = np.array([vector_b.x, vector_b.y, vector_b.z]) - np.array(
            [vector_a.x, vector_a.y, vector_a.z]
        )
        bc = np.array([vector_c.x, vector_c.y, vector_c.z]) - np.array(
            [vector_a.x, vector_a.y, vector_a.z]
        )
        angle_cosine = np.dot(ba, bc) / (np.linalg.norm(ba) * np.linalg.norm(bc))
        ang = np.degrees(np.arccos(angle_cosine))
    
        decimal_places = context.preferences.addons[__package__].preferences.pdt_input_round
        if flip_angle:
    
            if ang > 0:
    
                pg.angle = round(ang - 180, decimal_places)
    
                pg.angle = round(ang - 180, decimal_places)
    
            pg.angle = round(ang, decimal_places)
        pg.distance = round((vector_a - vector_b).length, decimal_places)
        pg.cartesian_coords = Vector(([round(i, decimal_places) for i in vector_b - vector_a]))
    
    def origin_to_cursor(context):
    
        """Sets Object Origin in Edit Mode to Cursor Location.
    
        Keeps geometry static in World Space whilst moving Object Origin
        Requires cursor location
        Works in Edit and Object Modes.
    
        Args:
            context: Blender bpy.context instance.
    
        Returns:
            Status Set.
        """
    
        scene = context.scene
    
        pg = context.scene.pdt_pg
    
        obj = context.view_layer.objects.active
        if obj is None:
            pg.error = PDT_ERR_NO_ACT_OBJ
            context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
            return
        obj_loc = obj.matrix_world.decompose()[0]
        cur_loc = scene.cursor.location
        diff_v = obj_loc - cur_loc
        if obj.mode == "EDIT":
            bm = bmesh.from_edit_mesh(obj.data)
            for v in bm.verts:
                v.co = v.co + diff_v
            obj.location = cur_loc
            bmesh.update_edit_mesh(obj.data)
            bm.select_history.clear()
        elif obj.mode == "OBJECT":
            for v in obj.data.vertices:
                v.co = v.co + diff_v
            obj.location = cur_loc
        else:
            pg.error = f"{PDT_ERR_EDOB_MODE} {obj.mode})"
            context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
    
            raise PDT_ObjectModeError
    
    
    def taper(context):
    
        """Taper Geometry along World Axes.
    
        Similar to Shear command except that it shears by angle rather than displacement.
        Rotates about World Axes and displaces along World Axes, angle must not exceed +-80 degrees.
        Rotation axis is centred on Active Vertex.
        Works only in Edit mode.
    
        Args:
            context: Blender bpy.context instance.
    
        Note:
            Uses pg.taper & pg.angle scene variables
    
        Returns:
            Status Set.
        """
    
        scene = context.scene
        pg = scene.pdt_pg
        tap_ax = pg.taper
        ang_v = pg.angle
        obj = context.view_layer.objects.active
        if all([bool(obj), obj.type == "MESH", obj.mode == "EDIT"]):
            if ang_v > 80 or ang_v < -80:
    
                pg.error = f"{PDT_ERR_TAPER_ANG} {ang_v})"
                context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
    
    Alan Odom's avatar
    Alan Odom committed
                raise PDT_InvalidAngle
    
            if obj is None:
                pg.error = PDT_ERR_NO_ACT_OBJ
                context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
    
    Alan Odom's avatar
    Alan Odom committed
                raise PDT_NoObjectError
    
            _, a2, a3 = set_axis(tap_ax)
            bm = bmesh.from_edit_mesh(obj.data)
            if len(bm.select_history) >= 1:
    
                rotate_vertex = bm.select_history[-1]
                view_vector = view_coords(rotate_vertex.co.x, rotate_vertex.co.y, rotate_vertex.co.z)
    
            else:
                pg.error = f"{PDT_ERR_TAPER_SEL} {len(bm.select_history)})"
                context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
    
    Alan Odom's avatar
    Alan Odom committed
                raise PDT_SelectionError
    
            for v in [v for v in bm.verts if v.select]:
                if pg.plane == "LO":
                    v_loc = view_coords(v.co.x, v.co.y, v.co.z)
    
                    dis_v = sqrt((view_vector.x - v_loc.x) ** 2 + (view_vector.y - v_loc.y) ** 2)
    
                    x_loc = dis_v * tan(ang_v * pi / 180)
    
                    view_matrix = view_dir(x_loc, 0)
                    v.co = v.co - view_matrix
    
                    dis_v = sqrt(
                        (rotate_vertex.co[a3] - v.co[a3]) ** 2 + (rotate_vertex.co[a2] - v.co[a2]) ** 2
                    )
    
                    v.co[a2] = v.co[a2] - (dis_v * tan(ang_v * pi / 180))
            bmesh.update_edit_mesh(obj.data)
            bm.select_history.clear()
    
        else:
            pg.error = f"{PDT_ERR_EDOB_MODE},{obj.mode})"
            context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
            raise PDT_ObjectModeError