Skip to content
Snippets Groups Projects
pdt_tangent.py 26.6 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
    
    from math import sqrt, floor, asin, sin, cos, pi
    
    from mathutils import Vector
    from bpy.types import Operator
    
    from .pdt_functions import (
        oops,
        arc_centre,
    
        set_mode,
        view_coords,
        view_coords_i,
    
    )
    
    from .pdt_msg_strings import (
        PDT_OBJ_MODE_ERROR,
        PDT_ERR_NO_ACT_OBJ,
        PDT_ERR_SEL_3_VERTS,
    
        PDT_ERR_SEL_1_VERT,
        PDT_ERR_BADDISTANCE,
        PDT_ERR_MATHSERROR,
        PDT_ERR_SAMERADII,
        PDT_ERR_VERT_MODE,
    
    )
    
    from . import pdt_exception
    
    PDT_ObjectModeError = pdt_exception.ObjectModeError
    PDT_SelectionError = pdt_exception.SelectionError
    
    
    
    def get_tangent_intersect_outer(hloc_0, vloc_0, hloc_1, vloc_1, radius_0, radius_1):
        """Return Location in 2 Dimensions of the Intersect Point for Outer Tangents.
    
        Args:
            hloc_0: Horizontal Coordinate of Centre of First Arc
            vloc_0: Vertical Coordinate of Centre of First Arc
            hloc_1: Horizontal Coordinate of Centre of Second Arc
            vloc_1: Vertical Coordinate of Centre of Second Arc
            radius_0: Radius of First Arc
            radius_1: Radius of Second Arc
    
        Returns:
            hloc_p: Horizontal Coordinate of Centre of Intersection
            vloc_p: Vertical Coordinate of Centre of Intersection.
        """
    
        hloc_p = ((hloc_1 * radius_0) - (hloc_0 * radius_1)) / (radius_0 - radius_1)
        vloc_p = ((vloc_1 * radius_0) - (vloc_0 * radius_1)) / (radius_0 - radius_1)
    
        return hloc_p, vloc_p
    
    def get_tangent_intersect_inner(hloc_0, vloc_0, hloc_1, vloc_1, radius_0, radius_1):
        """Return Location in 2 Dimensions of the Intersect Point for Inner Tangents.
    
        Args:
            hloc_0: Horizontal Coordinate of Centre of First Arc
            vloc_0: Vertical Coordinate of Centre of First Arc
            hloc_1: Horizontal Coordinate of Centre of Second Arc
            vloc_1: Vertical Coordinate of Centre of Second Arc
            radius_0: Radius of First Arc
            radius_1: Radius of Second Arc
    
        Returns:
            hloc_p: Horizontal Coordinate of Centre of Intersection
            vloc_p: Vertical Coordinate of Centre of Intersection.
        """
    
        hloc_p = ((hloc_1 * radius_0) + (hloc_0 * radius_1)) / (radius_0 + radius_1)
        vloc_p = ((vloc_1 * radius_0) + (vloc_0 * radius_1)) / (radius_0 + radius_1)
    
        return hloc_p, vloc_p
    
    
    def get_tangent_points(context, hloc_0, vloc_0, radius_0, hloc_p, vloc_p):
        """Return Location in 2 Dimensions of the Tangent Points.
    
        Args:
            context: Blender bpy.context instance
            hloc_0: Horizontal Coordinate of Centre of First Arc
            vloc_0: Vertical Coordinate of Centre of First Arc
            radius_0: Radius of First Arc
            hloc_p: Horizontal Coordinate of Intersection
            vloc_p: Vertical Coordinate of Intersection
    
        Returns:
            hloc_t1: Horizontal Location of First Tangent Point
            hloc_t2: Horizontal Location of Second Tangent Point
            vloc_t1: Vertical Location of First Tangent Point
            vloc_t2: Vertical Location of Second Tangent Point
        """
    
        numerator = (radius_0 ** 2 * (hloc_p - hloc_0)) + (
    
            * (vloc_p - vloc_0)
            * sqrt((hloc_p - hloc_0) ** 2 + (vloc_p - vloc_0) ** 2 - radius_0 ** 2)
    
        denominator = (hloc_p - hloc_0) ** 2 + (vloc_p - vloc_0) ** 2
        hloc_t1 = round((numerator / denominator) + hloc_0, 5)
    
        numerator = (radius_0 ** 2 * (hloc_p - hloc_0)) - (
    
            * (vloc_p - vloc_0)
            * sqrt((hloc_p - hloc_0) ** 2 + (vloc_p - vloc_0) ** 2 - radius_0 ** 2)
    
        denominator = (hloc_p - hloc_0) ** 2 + (vloc_p - vloc_0) ** 2
        hloc_t2 = round((numerator / denominator) + hloc_0, 5)
    
        numerator = (radius_0 ** 2 * (vloc_p - vloc_0)) - (
    
            * (hloc_p - hloc_0)
            * sqrt((hloc_p - hloc_0) ** 2 + (vloc_p - vloc_0) ** 2 - radius_0 ** 2)
    
        denominator = (hloc_p - hloc_0) ** 2 + (vloc_p - vloc_0) ** 2
        vloc_t1 = round((numerator / denominator) + vloc_0, 5)
    
        numerator = (radius_0 ** 2 * (vloc_p - vloc_0)) + (
    
            * (hloc_p - hloc_0)
            * sqrt((hloc_p - hloc_0) ** 2 + (vloc_p - vloc_0) ** 2 - radius_0 ** 2)
    
        denominator = (hloc_p - hloc_0) ** 2 + (vloc_p - vloc_0) ** 2
        vloc_t2 = round((numerator / denominator) + vloc_0, 5)
    
        return hloc_t1, hloc_t2, vloc_t1, vloc_t2
    
    
    def make_vectors(coords, a1, a2, a3, pg):
        """Return Vectors of the Tangent Points.
    
        Args:
            coords: A List of Coordinates in 2D space of the tangent points
                    & a third dimension for the vectors
            a1: Index of horizontal axis
            a2: Index of vertical axis
            a3: Index of depth axis
            pg: PDT Parameters Group - our variables
    
        Returns:
            tangent_vector_o1: Location of First Tangent Point
            tangent_vector_o2: Location of Second Tangent Point
            tangent_vector_o3: Location of First Tangent Point
            tangent_vector_o4: Location of Second Tangent Point
        """
    
        tangent_vector_o1 = Vector((0, 0, 0))
        tangent_vector_o1[a1] = coords[0]
        tangent_vector_o1[a2] = coords[1]
        tangent_vector_o1[a3] = coords[8]
        tangent_vector_o2 = Vector((0, 0, 0))
        tangent_vector_o2[a1] = coords[2]
        tangent_vector_o2[a2] = coords[3]
        tangent_vector_o2[a3] = coords[8]
        tangent_vector_o3 = Vector((0, 0, 0))
        tangent_vector_o3[a1] = coords[4]
        tangent_vector_o3[a2] = coords[5]
        tangent_vector_o3[a3] = coords[8]
        tangent_vector_o4 = Vector((0, 0, 0))
        tangent_vector_o4[a1] = coords[6]
        tangent_vector_o4[a2] = coords[7]
        tangent_vector_o4[a3] = coords[8]
    
        if pg.plane == "LO":
    
            tangent_vector_o1 = view_coords(
                tangent_vector_o1[a1], tangent_vector_o1[a2], tangent_vector_o1[a3]
            )
            tangent_vector_o2 = view_coords(
                tangent_vector_o2[a1], tangent_vector_o2[a2], tangent_vector_o2[a3]
            )
            tangent_vector_o3 = view_coords(
                tangent_vector_o3[a1], tangent_vector_o3[a2], tangent_vector_o3[a3]
            )
            tangent_vector_o4 = view_coords(
                tangent_vector_o4[a1], tangent_vector_o4[a2], tangent_vector_o4[a3]
            )
    
        return (tangent_vector_o1, tangent_vector_o2, tangent_vector_o3, tangent_vector_o4)
    
    
    
    def tangent_setup(context, pg, plane, obj_data, centre_0, centre_1, centre_2, radius_0, radius_1):
    
    Alan Odom's avatar
    Alan Odom committed
        """This section sets up all the variables required for the tangent functions.
    
        Args:
            context: Blender bpy.context instance
            pg: PDT Parameter Group of variables
            plane: Working plane
            obj_data: All the data of the chosen object
            centre_0: Centre coordinates of the first arc
            centre_1: Centre coordinates of the second arc
            centre_2: Coordinates fo the point
            radius_0: Radius if the first Arc
            radius_1: Radius of the second Arc
    
        Returns:
            Status Set.
        """
    
    
        a1, a2, a3 = set_mode(plane)
    
        mode = pg.tangent_mode
    
        if plane == "LO":
            centre_0 = view_coords_i(centre_0[a1], centre_0[a2], centre_0[a3])
            centre_1 = view_coords_i(centre_1[a1], centre_1[a2], centre_1[a3])
            centre_2 = view_coords_i(centre_2[a1], centre_2[a2], centre_2[a3])
    
        if pg.tangent_mode == "point":
    
            vector_difference = centre_2 - centre_0
            distance = sqrt(vector_difference[a1] ** 2 + vector_difference[a2] ** 2)
        else:
            vector_difference = centre_1 - centre_0
            distance = sqrt(vector_difference[a1] ** 2 + vector_difference[a2] ** 2)
    
    
        if (
            (distance <= radius_0 and mode in {"point"}) or
            (distance <= (radius_0 + radius_1) and mode in {"inner", "both"}) or
            (distance <= radius_0 or distance <= radius_1 and mode in {"outer", "both"})
            ):
    
            # Cannot execute, centres are too close.
            pg.error = f"{PDT_ERR_BADDISTANCE}"
            context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
            return {"FINISHED"}
    
    
    Alan Odom's avatar
    Alan Odom committed
        """This next section will draw Point based Tangents.
    
            These are drawn from a point to an Arc
        """
    
    
        if mode == "point":
            if (
    
                (centre_2[a1] - centre_0[a1]) ** 2 + (centre_2[a2] - centre_0[a2]) ** 2 - radius_0 ** 2
            ) > 0:
                hloc_to1, hloc_to2, vloc_to1, vloc_to2 = get_tangent_points(
                    context, centre_0[a1], centre_0[a2], radius_0, centre_2[a1], centre_2[a2]
    
                )
            else:
                pg.error = PDT_ERR_MATHSERROR
                context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
                return {"FINISHED"}
            # Point Tangents
            tangent_vector_o1 = Vector((0, 0, 0))
            tangent_vector_o1[a1] = hloc_to1
            tangent_vector_o1[a2] = vloc_to1
            tangent_vector_o1[a3] = centre_2[a3]
            tangent_vector_o2 = Vector((0, 0, 0))
            tangent_vector_o2[a1] = hloc_to2
            tangent_vector_o2[a2] = vloc_to2
            tangent_vector_o2[a3] = centre_2[a3]
            if pg.plane == "LO":
                centre_2 = view_coords(centre_2[a1], centre_2[a2], centre_2[a3])
    
                tangent_vector_o1 = view_coords(
                    tangent_vector_o1[a1], tangent_vector_o1[a2], tangent_vector_o1[a3]
                )
                tangent_vector_o2 = view_coords(
                    tangent_vector_o2[a1], tangent_vector_o2[a2], tangent_vector_o2[a3]
                )
            tangent_vectors = (centre_2, tangent_vector_o1, tangent_vector_o2)
    
            draw_tangents(tangent_vectors, obj_data)
    
            return {"FINISHED"}
    
    
    Alan Odom's avatar
    Alan Odom committed
        """This next section will draw Arc based Outer Tangents.
    
            These are drawn from an Arc to another Arc
        """
    
    
        if mode in {"outer", "both"}:
    
            if radius_0 == radius_1:
                # No intersection point for outer tangents
                sin_angle = (centre_1[a2] - centre_0[a2]) / distance
                cos_angle = (centre_1[a1] - centre_0[a1]) / distance
                hloc_to1 = centre_0[a1] + (radius_0 * sin_angle)
                hloc_to2 = centre_0[a1] - (radius_0 * sin_angle)
                hloc_to3 = centre_1[a1] + (radius_0 * sin_angle)
                hloc_to4 = centre_1[a1] - (radius_0 * sin_angle)
                vloc_to1 = centre_0[a2] - (radius_0 * cos_angle)
                vloc_to2 = centre_0[a2] + (radius_0 * cos_angle)
                vloc_to3 = centre_1[a2] - (radius_0 * cos_angle)
                vloc_to4 = centre_1[a2] + (radius_0 * cos_angle)
            else:
                hloc_po, vloc_po = get_tangent_intersect_outer(
                    centre_0[a1], centre_0[a2], centre_1[a1], centre_1[a2], radius_0, radius_1
                )
    
    
                if ((hloc_po - centre_0[a1]) ** 2 + (vloc_po - centre_0[a2]) ** 2 - radius_0 ** 2) > 0:
                    hloc_to1, hloc_to2, vloc_to1, vloc_to2 = get_tangent_points(
                        context, centre_0[a1], centre_0[a2], radius_0, hloc_po, vloc_po
    
                    )
                else:
                    pg.error = PDT_ERR_MATHSERROR
                    context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
                    return {"FINISHED"}
    
                if ((hloc_po - centre_0[a1]) ** 2 + (vloc_po - centre_0[a2]) ** 2 - radius_1 ** 2) > 0:
                    hloc_to3, hloc_to4, vloc_to3, vloc_to4 = get_tangent_points(
                        context, centre_1[a1], centre_1[a2], radius_1, hloc_po, vloc_po
    
                    )
                else:
                    pg.error = PDT_ERR_MATHSERROR
                    context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
                    return {"FINISHED"}
    
            dloc_p = centre_0[a3]
    
            coords_in = (
                hloc_to1,
                vloc_to1,
                hloc_to2,
                vloc_to2,
                hloc_to3,
                vloc_to3,
                hloc_to4,
                vloc_to4,
                dloc_p,
            )
    
            tangent_vectors = make_vectors(coords_in, a1, a2, a3, pg)
            draw_tangents(tangent_vectors, obj_data)
    
    
    Alan Odom's avatar
    Alan Odom committed
        """This next section will draw Arc based Inner Tangents.
    
            These are drawn from an Arc to another Arc
        """
    
    
        if mode in {"inner", "both"}:
    
            hloc_pi, vloc_pi = get_tangent_intersect_inner(
                centre_0[a1], centre_0[a2], centre_1[a1], centre_1[a2], radius_0, radius_1
            )
    
            if ((hloc_pi - centre_0[a1]) ** 2 + (vloc_pi - centre_0[a2]) ** 2 - radius_0 ** 2) > 0:
                hloc_to1, hloc_to2, vloc_to1, vloc_to2 = get_tangent_points(
                    context, centre_0[a1], centre_0[a2], radius_0, hloc_pi, vloc_pi
    
                )
            else:
                pg.error = PDT_ERR_MATHSERROR
                context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
                return {"FINISHED"}
    
            if ((hloc_pi - centre_0[a1]) ** 2 + (vloc_pi - centre_0[a2]) ** 2 - radius_0 ** 2) > 0:
                hloc_to3, hloc_to4, vloc_to3, vloc_to4 = get_tangent_points(
                    context, centre_1[a1], centre_1[a2], radius_1, hloc_pi, vloc_pi
    
                )
            else:
                pg.error = PDT_ERR_MATHSERROR
                context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
                return {"FINISHED"}
    
            dloc_p = centre_0[a3]
    
            coords_in = (
                hloc_to1,
                vloc_to1,
                hloc_to2,
                vloc_to2,
                hloc_to3,
                vloc_to3,
                hloc_to4,
                vloc_to4,
                dloc_p,
            )
    
            tangent_vectors = make_vectors(coords_in, a1, a2, a3, pg)
            draw_tangents(tangent_vectors, obj_data)
    
    
    Alan Odom's avatar
    Alan Odom committed
        return {"FINISHED"}
    
    
    
    def draw_tangents(tangent_vectors, obj_data):
        """Add Edges Representing the Tangents.
    
        Note:
            The length of the tanget_vectors determins whcih tangents will be
            drawn, 3 gives Point Tangents, 4 gives Inner/Outer tangents
    
        Args:
            tangent_vectors: A list of vectores representing the tangents
            obj_data: A list giving Object, Object Location and Object Bmesh
    
        Returns:
            Nothing.
        """
        obj = obj_data[0]
        obj_loc = obj_data[1]
        bm = obj_data[2]
        if len(tangent_vectors) == 3:
            point_vertex_outer = bm.verts.new(tangent_vectors[0] - obj_loc)
            tangent_vertex_o1 = bm.verts.new(tangent_vectors[1] - obj_loc)
            tangent_vertex_o2 = bm.verts.new(tangent_vectors[2] - obj_loc)
            bm.edges.new([tangent_vertex_o1, point_vertex_outer])
            bm.edges.new([tangent_vertex_o2, point_vertex_outer])
        else:
            tangent_vertex_o1 = bm.verts.new(tangent_vectors[0] - obj_loc)
            tangent_vertex_o2 = bm.verts.new(tangent_vectors[2] - obj_loc)
            tangent_vertex_o3 = bm.verts.new(tangent_vectors[1] - obj_loc)
            tangent_vertex_o4 = bm.verts.new(tangent_vectors[3] - obj_loc)
            bm.edges.new([tangent_vertex_o1, tangent_vertex_o2])
            bm.edges.new([tangent_vertex_o3, tangent_vertex_o4])
    
        bmesh.update_edit_mesh(obj.data)
    
    
    def analyse_arc(context, pg):
    
        """Analyses an Arc inferred from Selected Vertices.
    
        Note:
            Will work if more than 3 vertices are selected, taking the
            first, the nearest to the middle and the last.
    
        Args:
            context: Blender bpy.context instance
            pg: PDT Parameters Group - our variables
    
        Returns:
            vector_delta: Location of Arc Centre
            radius: Radius of Arc.
        """
    
        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")
            raise PDT_ObjectModeError
        if obj.mode == "EDIT":
            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")
                raise PDT_SelectionError
            vector_a = verts[0].co
    
            vector_b = verts[int(floor(len(verts) / 2))].co
    
            vector_c = verts[-1].co
    
            vector_delta, radius = arc_centre(vector_a, vector_b, vector_c)
    
            return vector_delta, radius
    
    
    class PDT_OT_TangentOperate(Operator):
    
        """Calculate Tangents from Inputs."""
    
    
        bl_idname = "pdt.tangentoperate"
        bl_label = "Calculate Tangents"
        bl_options = {"REGISTER", "UNDO"}
        bl_description = "Calculate Tangents to Arcs from Points or Other Arcs"
    
        @classmethod
        def poll(cls, context):
            ob = context.object
            if ob is None:
                return False
            return all([bool(ob), ob.type == "MESH", ob.mode == "EDIT"])
    
        def execute(self, context):
    
            """Calculate Tangents from Inputs.
    
    
            Note:
                Uses pg.plane, pg.tangent_point0, pg.tangent_radius0, pg.tangent_point1
                pg.tangent_radius1, pg.tangent_point2 to place tangents.
    
                Analyses distance between arc centres, or arc centre and tangent point
                to determine which mode is possible (Inner, Outer, or Point). If centres are
                both contianed within 1 inferred circle, Inner tangents are not possible.
    
                Arcs of same radius will have no intersection for outer tangents so these
                are calculated differently.
    
            Args:
                context: Blender bpy.context instance.
    
            Returns:
                Nothing.
            """
    
            scene = context.scene
            pg = scene.pdt_pg
    
            plane = pg.plane
            # Get Object
            obj = context.view_layer.objects.active
            if obj is not None:
                if obj.mode not in {"EDIT"} or obj.type != "MESH":
                    pg.error = PDT_OBJ_MODE_ERROR
                    context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
                    return {"FINISHED"}
            else:
                pg.error = PDT_ERR_NO_ACT_OBJ
                context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
                return {"FINISHED"}
            bm = bmesh.from_edit_mesh(obj.data)
            obj_loc = obj.matrix_world.decompose()[0]
    
            obj_data = (obj, obj_loc, bm)
    
            radius_0 = pg.tangent_radius0
            radius_1 = pg.tangent_radius1
    
            centre_0 = pg.tangent_point0
            centre_1 = pg.tangent_point1
    
            centre_2 = pg.tangent_point2
    
    
            tangent_setup(
                context, pg, plane, obj_data, centre_0, centre_1, centre_2, radius_0, radius_1
            )
    
    
            return {"FINISHED"}
    
    
    class PDT_OT_TangentOperateSel(Operator):
    
        """Calculate Tangents from Selection."""
    
    
        bl_idname = "pdt.tangentoperatesel"
        bl_label = "Calculate Tangents"
        bl_options = {"REGISTER", "UNDO"}
        bl_description = "Calculate Tangents to Arcs from 2 Selected Vertices, or 1 & Point"
    
        @classmethod
        def poll(cls, context):
            ob = context.object
            if ob is None:
                return False
            return all([bool(ob), ob.type == "MESH", ob.mode == "EDIT"])
    
        def execute(self, context):
    
            """Calculate Tangents from Selection.
    
    
            Note:
                Uses pg.plane & 2 or more selected Vertices to place tangents.
                One vertex must be on each arc.
    
                Analyses distance between arc centres, or arc centre and tangent point
                to determine which mode is possible (Inner, Outer, or Point). If centres are
                both contianed within 1 inferred circle, Inner tangents are not possible.
    
                Arcs of same radius will have no intersection for outer tangents so these
                are calculated differently.
    
            Args:
                context: Blender bpy.context instance.
    
            Returns:
                Nothing.
            """
    
            scene = context.scene
            pg = scene.pdt_pg
            plane = pg.plane
    
            # Get Object
            obj = context.view_layer.objects.active
            if obj is not None:
                if obj.mode not in {"EDIT"} or obj.type != "MESH":
                    pg.error = PDT_OBJ_MODE_ERROR
                    context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
    
                    return {"FINISHED"}
    
            else:
                pg.error = PDT_ERR_NO_ACT_OBJ
                context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
    
                return {"FINISHED"}
    
            bm = bmesh.from_edit_mesh(obj.data)
            obj_loc = obj.matrix_world.decompose()[0]
    
            obj_data = (obj, obj_loc, bm)
    
            # Get All Values from Selected Vertices
            verts = [v for v in bm.verts if v.select]
    
            if len(verts) <= 0:
                pg.error = f"{PDT_ERR_SEL_1_VERT} 0"
                context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
                return {"FINISHED"}
    
            v1 = verts[0]
            vn = verts[-1]
            for v in bm.verts:
                v.select_set(False)
            for e in bm.edges:
                e.select_set(False)
            v1.select_set(True)
            bpy.ops.mesh.select_linked()
            verts1 = [v for v in bm.verts if v.select].copy()
            if len(verts1) < 3:
    
                pg.error = f"{PDT_ERR_VERT_MODE} or Less than 3 vertices in your Arc(s)"
    
                context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
    
            for v in bm.verts:
                v.select_set(False)
            for e in bm.edges:
                e.select_set(False)
            vn.select_set(True)
            bpy.ops.mesh.select_linked()
            vertsn = [v for v in bm.verts if v.select].copy()
            for v in bm.verts:
                v.select_set(False)
            for e in bm.edges:
                e.select_set(False)
            bmesh.update_edit_mesh(obj.data)
            bm.select_history.clear()
    
            verts1 = [verts1[0].co, verts1[int(floor(len(verts1) / 2))].co, verts1[-1].co]
            vertsn = [vertsn[0].co, vertsn[int(floor(len(vertsn) / 2))].co, vertsn[-1].co]
    
            centre_0, radius_0 = arc_centre(verts1[0], verts1[1], verts1[2])
            centre_1, radius_1 = arc_centre(vertsn[0], vertsn[1], vertsn[2])
            centre_2 = pg.tangent_point2
    
            tangent_setup(
                context, pg, plane, obj_data, centre_0, centre_1, centre_2, radius_0, radius_1
            )
    
    
            return {"FINISHED"}
    
    
    class PDT_OT_TangentSet1(Operator):
        """Calculates Centres & Radii from 3 Vectors."""
    
        bl_idname = "pdt.tangentset1"
        bl_label = "Calculate Centres & Radii"
        bl_options = {"REGISTER", "UNDO"}
        bl_description = "Calculate Centres & Radii from Selected Vertices"
    
        @classmethod
        def poll(cls, context):
            ob = context.object
            if ob is None:
                return False
            return all([bool(ob), ob.type == "MESH", ob.mode == "EDIT"])
    
        def execute(self, context):
    
            """Sets Input Tangent Point 1 to analysis of Arc.
    
            Args:
                context: Blender bpy.context instance.
    
            Returns:
                Nothing.
            """
    
            scene = context.scene
            pg = scene.pdt_pg
            vector_delta, radius = analyse_arc(context, pg)
            pg.tangent_point0 = vector_delta
            pg.tangent_radius0 = radius
            return {"FINISHED"}
    
    
    class PDT_OT_TangentSet2(Operator):
        """Calculates Centres & Radii from 3 Vectors."""
    
        bl_idname = "pdt.tangentset2"
        bl_label = "Calculate Centres & Radii"
        bl_options = {"REGISTER", "UNDO"}
        bl_description = "Calculate Centres & Radii from Selected Vertices"
    
        @classmethod
        def poll(cls, context):
    
            obj = context.object
            if obj is None:
    
            return all([bool(obj), obj.type == "MESH", obj.mode == "EDIT"])
    
    
        def execute(self, context):
    
            """Sets Input Tangent Point 2 to analysis of Arc.
    
            Args:
                context: Blender bpy.context instance.
    
            Returns:
                Nothing.
            """
    
            scene = context.scene
            pg = scene.pdt_pg
            vector_delta, radius = analyse_arc(context, pg)
            pg.tangent_point1 = vector_delta
            pg.tangent_radius1 = radius
            return {"FINISHED"}
    
    
    
    class PDT_OT_TangentSet3(Operator):
        """Set Tangent Origin Point from Cursor."""
    
        bl_idname = "pdt.tangentset3"
        bl_label = "Set Tangent Origin Point from Cursor"
        bl_options = {"REGISTER", "UNDO"}
        bl_description = "Set Tangent Origin Point from Cursor"
    
        @classmethod
        def poll(cls, context):
            obj = context.object
            if obj is None:
                return False
            return all([bool(obj), obj.type == "MESH", obj.mode == "EDIT"])
    
        def execute(self, context):
    
            """Sets Input Tangent Point 3 to analysis of Arc.
    
            Args:
                context: Blender bpy.context instance.
    
            Returns:
                Nothing.
            """
    
            scene = context.scene
            pg = scene.pdt_pg
            pg.tangent_point2 = scene.cursor.location
            return {"FINISHED"}
    
    
    class PDT_OT_TangentSet4(Operator):
        """Set Tangent Origin Point from Cursor."""
    
        bl_idname = "pdt.tangentset4"
        bl_label = "Set Tangent Origin Point from Vertex"
        bl_options = {"REGISTER", "UNDO"}
        bl_description = "Set Tangent Origin Point from Vertex"
    
        @classmethod
        def poll(cls, context):
            obj = context.object
            if obj is None:
                return False
            return all([bool(obj), obj.type == "MESH", obj.mode == "EDIT"])
    
        def execute(self, context):
    
            """Sets Input Tangent Point 2 to selected Vertex.
    
            Args:
                context: Blender bpy.context instance.
    
            Returns:
                Nothing.
            """
    
            scene = context.scene
            pg = scene.pdt_pg
            obj = context.object
            bm = bmesh.from_edit_mesh(obj.data)
            verts = [v for v in bm.verts if v.select]
            if len(verts) != 1:
                pg.error = f"{PDT_ERR_SEL_1_VERT} {len(verts)})"
                context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
                raise PDT_SelectionError
            pg.tangent_point2 = verts[0].co
            return {"FINISHED"}
    
    
    class PDT_OT_TangentExpandMenu(Operator):
        """Expand/Collapse Tangent Menu."""
    
        bl_idname = "pdt.tangentexpandmenu"
        bl_label = "Expand/Collapse Tangent Menu"
        bl_options = {"REGISTER", "UNDO"}
        bl_description = "Expand/Collapse Tangent Menu to Show/Hide Input Options"
    
    
        def execute(self, context):
            """Expand/Collapse Tangent Menu.
    
    
            Args:
                context: Blender bpy.context instance.
    
            Returns:
                Nothing.
            """
            scene = context.scene
            pg = scene.pdt_pg
            if pg.menu_expand:
                pg.menu_expand = False
            else:
                pg.menu_expand = True
            return {"FINISHED"}