Skip to content
Snippets Groups Projects
pdt_command_functions.py 30.5 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 bpy
    import bmesh
    import math
    from math import sqrt, tan, pi
    import numpy as np
    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_ObjectMode = pdt_exception.ObjectMode
    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.
            PDT parameter group as pg, object, operation,
            command line values, required number of values.
    
        Returns:
            Vector to position, or offset items.
        """
    
        scene = context.scene
        plane = pg.plane
        flip_a = pg.flip_angle
        flip_p = pg.flip_percent
    
    
        if num_values == 3 and len(values) == 3:
            output_vector = Vector((float(values[0]), float(values[1]), float(values[2])))
        elif num_values == 2 and len(values) == 2:
            output_vector = dis_ang(values, flip_a, plane, scene)
        elif num_values == 1 and len(values) == 1:
            output_vector = get_percent(obj, flip_p, 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")
    
    Alan Odom's avatar
    Alan Odom committed
            raise pdt_InvalidVector
        return output_vector
    
    def placement_normal(context, operation):
    
        """Manipulates Geometry, or Objects by Normal Intersection between 3 points.
    
        -- set position of CUrsor       (CU)
        -- set position of Pivot Point  (PP)
        -- MoVe geometry/objects        (MV)
        -- Extrude Vertices             (EV)
        -- Split Edges                  (SE)
        -- add a New Vertex             (NV)
    
        Invalid Options result in "oops" Error.
    
        Local vector variable 'vector_delta' used to reposition features.
    
        Args:
            context: Blender bpy.context instance.
    
        Returns:
            Status Set.
        """
    
        scene = context.scene
        pg = scene.pdt_pg
        ext_a = 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")
    
    Alan Odom's avatar
    Alan Odom committed
                raise PDT_ObjectMode
    
            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":
                if ext_a:
                    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)
    
            if ext_a:
                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")
        return
    
    
    
    def placement_centre(context, operation):
    
        """Manipulates Geometry, or Objects to an Arc Centre defined by 3 points on an Imaginary Arc.
    
        -- set position of CUrsor       (CU)
        -- set position of Pivot Point  (PP)
        -- MoVe geometry/objects        (MV)
        -- Extrude Vertices             (EV)
        -- add a New vertex             (NV)
    
    
        Invalid Options result in "oops" Error.
    
    
        Local vector variable 'vector_delta' used to reposition features.
    
        Args:
            context: Blender bpy.context instance.
    
        Returns:
            Status Set.
        """
    
        scene = context.scene
        pg = scene.pdt_pg
        ext_a = 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")
    
    Alan Odom's avatar
    Alan Odom committed
                raise PDT_ObjectMode
    
            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":
    
                if ext_a:
                    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)
    
                if ext_a:
                    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")
        return
    
    def placement_intersect(context, operation):
    
        """Manipulates Geometry, or Objects by Convergance Intersection between 4 points, or 2 Edges.
    
        - Reads pg.plane scene variable and operates in Working Plane to:
        -- set position of CUrsor       (CU)
        -- set position of Pivot Point  (PP)
        -- MoVe geometry/objects        (MV)
        -- Extrude Vertices             (EV)
        -- add a New vertex             (NV)
    
        Invalid Options result in "oops" Error.
    
        Local vector variable 'vector_delta' used to reposition features.
    
        Args:
            context: Blender bpy.context instance.
    
        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]
    
            ext_a = 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
    
                if (vertex_a.co - vector_delta).length < (vertex_b.co - vector_delta).length:
    
                    if operation == "G":
    
                        vertex_a.co = vector_delta
    
                        proc = True
    
                    else:
                        vertex_new = bm.verts.new(vector_delta)
                        bm.edges.new([vertex_a, vertex_new])
    
                        proc = True
                else:
                    if operation == "G" and ext_a:
    
                        vertex_b.co = vector_delta
    
                    elif operation == "V" and ext_a:
    
                        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 ext_a:
    
                        vertex_c.co = vector_delta
    
                    elif operation == "V" and ext_a:
    
                        bm.edges.new([vertex_c, vertex_new])
                    else:
                        return
    
                else:
                    if operation == "G" and ext_a:
    
                        vertex_d.co = vector_delta
    
                    elif operation == "V" and ext_a:
    
                        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 proc and not ext_a:
                    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")
    
    Alan Odom's avatar
    Alan Odom committed
            raise PDT_ObjectMode
    
    
    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_a = 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)))
    
    Alan Odom's avatar
    Alan Odom committed
        val_round = context.preferences.addons[__package__].preferences.pdt_input_round
    
        if flip_a:
            if ang > 0:
    
    Alan Odom's avatar
    Alan Odom committed
                pg.angle = round(ang - 180, val_round)
    
    Alan Odom's avatar
    Alan Odom committed
                pg.angle = round(ang - 180, val_round)
    
    Alan Odom's avatar
    Alan Odom committed
            pg.angle = round(ang, val_round)
    
        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), val_round
            )
    
    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), val_round
            )
        pg.cartesian_coords = Vector(([round(i, val_round) 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_a = 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")
    
    Alan Odom's avatar
    Alan Odom committed
                        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))
    
    Alan Odom's avatar
    Alan Odom committed
        val_round = context.preferences.addons[__package__].preferences.pdt_input_round
    
        if flip_a:
            if ang > 0:
    
    Alan Odom's avatar
    Alan Odom committed
                pg.angle = round(ang - 180, val_round)
    
    Alan Odom's avatar
    Alan Odom committed
                pg.angle = round(ang - 180, val_round)
    
    Alan Odom's avatar
    Alan Odom committed
            pg.angle = round(ang, val_round)
        pg.distance = round((vector_a - vector_b).length, val_round)
        pg.cartesian_coords = Vector(([round(i, val_round) 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")
    
    Alan Odom's avatar
    Alan Odom committed
            raise PDT_ObjectMode
    
    
    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()
            return
    
        pg.error = f"{PDT_ERR_EDOB_MODE},{obj.mode})"
        context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
    
    Alan Odom's avatar
    Alan Odom committed
        raise PDT_ObjectMode