diff --git a/pdt_tangent.py b/pdt_tangent.py index 1f2a7ab93611193971cd6952b5b4e8371b283e53..a00be31381a0c9928acb605e7659ebbb19ca2686 100644 --- a/pdt_tangent.py +++ b/pdt_tangent.py @@ -23,96 +23,371 @@ # import bpy import bmesh -from math import sqrt +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_NoObjectError = pdt_exception.NoObjectError PDT_SelectionError = pdt_exception.SelectionError -def get_tangent_intersect_outer(xloc_0, yloc_0, xloc_1, yloc_1, radius_0, radius_1): - xloc_p = ((xloc_1 * radius_0) - (xloc_0 * radius_1)) / (radius_0 - radius_1) - yloc_p = ((yloc_1 * radius_0) - (yloc_0 * radius_1)) / (radius_0 - radius_1) +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. - return xloc_p, yloc_p + 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. + """ -def get_tangent_intersect_inner(xloc_0, yloc_0, xloc_1, yloc_1, radius_0, radius_1): - xloc_p = ((xloc_1 * radius_0) + (xloc_0 * radius_1)) / (radius_0 + radius_1) - yloc_p = ((yloc_1 * radius_0) + (yloc_0 * radius_1)) / (radius_0 + radius_1) + 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 xloc_p, yloc_p + return hloc_p, vloc_p -def get_tangent_points(xloc_0, yloc_0, radius_0, xloc_p, yloc_p): - numerator = (radius_0 ** 2 * (xloc_p - xloc_0)) + ( +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)) + ( radius_0 - * (yloc_p - yloc_0) - * sqrt((xloc_p - xloc_0) ** 2 + (yloc_p - yloc_0) ** 2 - radius_0 ** 2) + * (vloc_p - vloc_0) + * sqrt((hloc_p - hloc_0) ** 2 + (vloc_p - vloc_0) ** 2 - radius_0 ** 2) ) - denominator = (xloc_p - xloc_0) ** 2 + (yloc_p - yloc_0) ** 2 - xloc_t1 = round((numerator / denominator) + xloc_0, 5) + denominator = (hloc_p - hloc_0) ** 2 + (vloc_p - vloc_0) ** 2 + hloc_t1 = round((numerator / denominator) + hloc_0, 5) - numerator = (radius_0 ** 2 * (xloc_p - xloc_0)) - ( + numerator = (radius_0 ** 2 * (hloc_p - hloc_0)) - ( radius_0 - * (yloc_p - yloc_0) - * sqrt((xloc_p - xloc_0) ** 2 + (yloc_p - yloc_0) ** 2 - radius_0 ** 2) + * (vloc_p - vloc_0) + * sqrt((hloc_p - hloc_0) ** 2 + (vloc_p - vloc_0) ** 2 - radius_0 ** 2) ) - denominator = (xloc_p - xloc_0) ** 2 + (yloc_p - yloc_0) ** 2 - xloc_t2 = round((numerator / denominator) + xloc_0, 5) + denominator = (hloc_p - hloc_0) ** 2 + (vloc_p - vloc_0) ** 2 + hloc_t2 = round((numerator / denominator) + hloc_0, 5) # Get Y values - numerator = (radius_0 ** 2 * (yloc_p - yloc_0)) - ( + numerator = (radius_0 ** 2 * (vloc_p - vloc_0)) - ( radius_0 - * (xloc_p - xloc_0) - * sqrt((xloc_p - xloc_0) ** 2 + (yloc_p - yloc_0) ** 2 - radius_0 ** 2) + * (hloc_p - hloc_0) + * sqrt((hloc_p - hloc_0) ** 2 + (vloc_p - vloc_0) ** 2 - radius_0 ** 2) ) - denominator = (xloc_p - xloc_0) ** 2 + (yloc_p - yloc_0) ** 2 - yloc_t1 = round((numerator / denominator) + yloc_0, 5) + denominator = (hloc_p - hloc_0) ** 2 + (vloc_p - vloc_0) ** 2 + vloc_t1 = round((numerator / denominator) + vloc_0, 5) - numerator = (radius_0 ** 2 * (yloc_p - yloc_0)) + ( + numerator = (radius_0 ** 2 * (vloc_p - vloc_0)) + ( radius_0 - * (xloc_p - xloc_0) - * sqrt((xloc_p - xloc_0) ** 2 + (yloc_p - yloc_0) ** 2 - radius_0 ** 2) + * (hloc_p - hloc_0) + * sqrt((hloc_p - hloc_0) ** 2 + (vloc_p - vloc_0) ** 2 - radius_0 ** 2) ) - denominator = (xloc_p - xloc_0) ** 2 + (yloc_p - yloc_0) ** 2 - yloc_t2 = round((numerator / denominator) + yloc_0, 5) - - return xloc_t1, xloc_t2, yloc_t1, yloc_t2 - - -def draw_tangents( - xloc_to1, xloc_to2, yloc_to1, yloc_to2, xloc_to3, xloc_to4, yloc_to3, yloc_to4, bm, obj, obj_loc -): - tangent_vector_o1 = Vector((xloc_to1, 0, yloc_to1)) - tangent_vertex_o1 = bm.verts.new(tangent_vector_o1 - obj_loc) - tangent_vector_o2 = Vector((xloc_to2, 0, yloc_to2)) - tangent_vertex_o2 = bm.verts.new(tangent_vector_o2 - obj_loc) - tangent_vector_o3 = Vector((xloc_to3, 0, yloc_to3)) - tangent_vertex_o3 = bm.verts.new(tangent_vector_o3 - obj_loc) - tangent_vector_o4 = Vector((xloc_to4, 0, yloc_to4)) - tangent_vertex_o4 = bm.verts.new(tangent_vector_o4 - obj_loc) - # Add Edges - bm.edges.new([tangent_vertex_o1, tangent_vertex_o3]) - bm.edges.new([tangent_vertex_o2, tangent_vertex_o4]) + 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): + # Depth is a3 + a1, a2, a3 = set_mode(plane) + 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_from_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 + radius_1 and not pg.tangent_from_point: + mode = "inner" + elif distance > radius_0 and distance > radius_1 and not pg.tangent_from_point: + mode = "outer" + elif distance > radius_1 and pg.tangent_from_point: + mode = "point" + else: + # Cannot execute, centres are too close. + pg.error = f"{PDT_ERR_BADDISTANCE}" + context.window_manager.popup_menu(oops, title="Error", icon="ERROR") + return {"FINISHED"} + + 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"} + + if mode in {"outer", "inner"}: + # Outer Tangents + 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) + + if mode == "inner": + # Inner Tangents + 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) + + +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 @@ -122,17 +397,17 @@ def analyse_arc(context, pg): 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: + 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[1].co - vector_c = verts[2].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.""" @@ -150,108 +425,146 @@ class PDT_OT_TangentOperate(Operator): return all([bool(ob), ob.type == "MESH", ob.mode == "EDIT"]) def execute(self, context): + """Repeat Current Command Line Input. + + 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 - centre_0 = pg.tangent_point0 + 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 - centre_1 = pg.tangent_point1 radius_1 = pg.tangent_radius1 + centre_0 = pg.tangent_point0 + centre_1 = pg.tangent_point1 centre_2 = pg.tangent_point2 - distance = (centre_0 - centre_1).length - if distance > radius_0 + radius_1: - mode = "inner" - elif distance > radius_0 and distance > radius_1: - mode = "outer" - else: - # Cannot execute, centres are too close. - print("Execution Error") - return {"FINISHED"} + 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.""" + + 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): + """Repeat Current Command Line Input. + + 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") - raise PDT_ObjectModeError + return {"FINISHED"} else: pg.error = PDT_ERR_NO_ACT_OBJ context.window_manager.popup_menu(oops, title="Error", icon="ERROR") - raise PDT_NoObjectError + return {"FINISHED"} bm = bmesh.from_edit_mesh(obj.data) obj_loc = obj.matrix_world.decompose()[0] + obj_data = ((obj, obj_loc, bm)) - if pg.tangent_from_point: - # FIXME Get Proper Axes Values - xloc_to1, xloc_to2, yloc_to1, yloc_to2 = get_tangent_points( - centre_0.x, centre_0.z, radius_0, centre_2.x, centre_2.z - ) - # Point Tangents - point_vector_outer = Vector((centre_2.x, 0, centre_2.z)) - point_vertex_outer = bm.verts.new(point_vector_outer - obj_loc) - tangent_vector_o1 = Vector((xloc_to1, 0, yloc_to1)) - tangent_vertex_o1 = bm.verts.new(tangent_vector_o1 - obj_loc) - tangent_vector_o2 = Vector((xloc_to2, 0, yloc_to2)) - tangent_vertex_o2 = bm.verts.new(tangent_vector_o2 - obj_loc) - bm.edges.new([tangent_vertex_o1, point_vertex_outer]) - bm.edges.new([tangent_vertex_o2, point_vertex_outer]) - bmesh.update_edit_mesh(obj.data) + # Get All Values from Selected Vertices + verts = [v for v in bm.verts if v.select] + 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}" + context.window_manager.popup_menu(oops, title="Error", icon="ERROR") return {"FINISHED"} + 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 - if mode in {"outer", "inner"}: - # FIXME Get Proper Axes Values - xloc_po, yloc_po = get_tangent_intersect_outer( - centre_0.x, centre_0.z, centre_1.x, centre_1.z, radius_0, radius_1 - ) - # Outer Tangents - xloc_to1, xloc_to2, yloc_to1, yloc_to2 = get_tangent_points( - centre_0.x, centre_0.z, radius_0, xloc_po, yloc_po - ) - xloc_to3, xloc_to4, yloc_to3, yloc_to4 = get_tangent_points( - centre_1.x, centre_1.z, radius_1, xloc_po, yloc_po - ) - - # Add Outer Tangent Vertices - draw_tangents( - xloc_to1, - xloc_to2, - yloc_to1, - yloc_to2, - xloc_to3, - xloc_to4, - yloc_to3, - yloc_to4, - bm, - obj, - obj_loc, - ) - - if mode == "inner": - # FIXME Get Proper Axes Values - xloc_pi, yloc_pi = get_tangent_intersect_inner( - centre_0.x, centre_0.z, centre_1.x, centre_1.z, radius_0, radius_1 - ) - # Inner Tangents - xloc_to1, xloc_to2, yloc_to1, yloc_to2 = get_tangent_points( - centre_0.x, centre_0.z, radius_0, xloc_pi, yloc_pi - ) - xloc_to3, xloc_to4, yloc_to3, yloc_to4 = get_tangent_points( - centre_1.x, centre_1.z, radius_1, xloc_pi, yloc_pi - ) - # Add Inner Tangent Vertices - draw_tangents( - xloc_to1, - xloc_to2, - yloc_to1, - yloc_to2, - xloc_to3, - xloc_to4, - yloc_to3, - yloc_to4, - bm, - obj, - obj_loc, - ) + tangent_setup(context, pg, plane, obj_data, centre_0, centre_1, centre_2, + radius_0, radius_1) return {"FINISHED"} @@ -290,10 +603,10 @@ class PDT_OT_TangentSet2(Operator): @classmethod def poll(cls, context): - ob = context.object - if ob is None: + obj = context.object + if obj is None: return False - return all([bool(ob), ob.type == "MESH", ob.mode == "EDIT"]) + return all([bool(obj), obj.type == "MESH", obj.mode == "EDIT"]) def execute(self, context): scene = context.scene @@ -302,3 +615,80 @@ class PDT_OT_TangentSet2(Operator): 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): + 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): + 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 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"} diff --git a/precision_drawing_tools/__init__.py b/precision_drawing_tools/__init__.py index 298b2d9ce0e12eeb05c758b379d74b2c6c5d23f8..4aeecd26090c3a7ead235efb9e05d080e7315fa3 100644 --- a/precision_drawing_tools/__init__.py +++ b/precision_drawing_tools/__init__.py @@ -413,6 +413,9 @@ class PDTSceneProperties(PropertyGroup): name="Coordst3", default=(0.0, 0.0, 0.0), subtype="XYZ", description=PDT_DES_TANCEN3 ) tangent_from_point: BoolProperty(name="From Point", default=False, description=PDT_DES_TPOINT) + menu_expand: BoolProperty( + name="Expand", default=False, description="Expand/Collapse Menu", + ) class PDTPreferences(AddonPreferences): @@ -502,8 +505,12 @@ classes = ( pdt_pivot_point.PDT_OT_PivotWrite, pdt_pivot_point.PDT_OT_PivotRead, pdt_tangent.PDT_OT_TangentOperate, + pdt_tangent.PDT_OT_TangentOperateSel, pdt_tangent.PDT_OT_TangentSet1, pdt_tangent.PDT_OT_TangentSet2, + pdt_tangent.PDT_OT_TangentSet3, + pdt_tangent.PDT_OT_TangentSet4, + pdt_tangent.PDT_OT_TangentExpandMenu, pdt_view.PDT_OT_ViewRot, pdt_view.PDT_OT_ViewRotL, pdt_view.PDT_OT_ViewRotR, diff --git a/precision_drawing_tools/pdt_exception.py b/precision_drawing_tools/pdt_exception.py index d0dc157a93a7a22322dbcc61edcca8e65e7d7d4f..601f51413eedef1895b2f2b389c7ed967841625e 100644 --- a/precision_drawing_tools/pdt_exception.py +++ b/precision_drawing_tools/pdt_exception.py @@ -85,3 +85,7 @@ class ShaderError(Exception): class FeatureError(Exception): """Wrong Feature Type Error Exception.""" pass + +class DistanceError(Exception): + """Invalid Distance (Separation) Error.""" + pass diff --git a/precision_drawing_tools/pdt_functions.py b/precision_drawing_tools/pdt_functions.py index 3d12c4f99b6581cd92274597f239b89bce7d0339..38879618f713891ab4f780ef569afe78e0b3fc21 100644 --- a/precision_drawing_tools/pdt_functions.py +++ b/precision_drawing_tools/pdt_functions.py @@ -111,6 +111,7 @@ def set_mode(mode_pl): Note: Sets indices of axes for locational vectors: + a3 is normal to screen, or depth "XY": a1 = x, a2 = y, a3 = z "XZ": a1 = x, a2 = z, a3 = y "YZ": a1 = y, a2 = z, a3 = x @@ -126,6 +127,7 @@ def set_mode(mode_pl): "XY": (0, 1, 2), "XZ": (0, 2, 1), "YZ": (1, 2, 0), + "LO": (0, 1, 2), } return order[mode_pl] diff --git a/precision_drawing_tools/pdt_menus.py b/precision_drawing_tools/pdt_menus.py index 1632730c16315d1b00e7f29b7012f215d5f0a48d..649b2b962af9da336d4137cf5b55676145ff8283 100644 --- a/precision_drawing_tools/pdt_menus.py +++ b/precision_drawing_tools/pdt_menus.py @@ -426,30 +426,50 @@ class PDT_PT_PanelTangent(Panel): def draw(self,context): layout = self.layout pdt_pg = context.scene.pdt_pg - # First Centre & Radius - row = layout.row() - split = row.split(factor=0.35, align=True) - split.label(text="Centre 1") - split.prop(pdt_pg, "tangent_point0", text="") - row = layout.row() - split = row.split(factor=0.45, align=False) - split.operator("pdt.tangentset1", text="Set From Arc") - split.prop(pdt_pg, "tangent_radius0", text="") - # Second Centre & Radius + if pdt_pg.menu_expand: + icon_e = "EVENT_C" + else: + icon_e = "EVENT_E" row = layout.row() - split = row.split(factor=0.35, align=True) - split.label(text="Centre 2") - split.prop(pdt_pg, "tangent_point1", text="") + row.label(text=f"Working {PDT_LAB_PLANE}:") + row.prop(pdt_pg, "plane", text="") row = layout.row() - split = row.split(factor=0.45, align=False) - split.operator("pdt.tangentset2", text="Set From Arc") - split.prop(pdt_pg, "tangent_radius1", text="") - + row.operator("pdt.tangentoperatesel", text="Tangents from Selection", icon="NONE") row = layout.row() - row.operator("pdt.tangentoperate", text="Execute", icon="NONE") + row.label(text="Or Use Tangents From Inputs") + row.operator("pdt.tangentexpandmenu", text="", icon=icon_e) + + box = layout.box() + row = box.row() row.prop(pdt_pg, "tangent_from_point", text="From Point") - row = layout.row() + row = box.row() split = row.split(factor=0.35, align=True) split.label(text="Tan Point") split.prop(pdt_pg, "tangent_point2", text="") + row = box.row() + row.operator("pdt.tangentset3", text="from Cursor") + row.operator("pdt.tangentset4", text="from Vertex") + + if pdt_pg.menu_expand: + box = layout.box() + row = box.row() + split = row.split(factor=0.35, align=True) + split.label(text="Centre 1") + split.prop(pdt_pg, "tangent_point0", text="") + row = box.row() + split = row.split(factor=0.45, align=False) + split.operator("pdt.tangentset1", text="Set From Arc") + split.prop(pdt_pg, "tangent_radius0", text="") + + # Second Centre & Radius + row = box.row() + split = row.split(factor=0.35, align=True) + split.label(text="Centre 2") + split.prop(pdt_pg, "tangent_point1", text="") + row = box.row() + split = row.split(factor=0.45, align=False) + split.operator("pdt.tangentset2", text="Set From Arc") + split.prop(pdt_pg, "tangent_radius1", text="") + row = box.row() + row.operator("pdt.tangentoperate", text="Tangents From Inputs", icon="NONE") diff --git a/precision_drawing_tools/pdt_msg_strings.py b/precision_drawing_tools/pdt_msg_strings.py index 9eeb08f17b125b6abd18f01216ac2abeece8fb28..0233a8c1fdb93e222d59f5f5271b5f8d850c8034 100644 --- a/precision_drawing_tools/pdt_msg_strings.py +++ b/precision_drawing_tools/pdt_msg_strings.py @@ -151,6 +151,9 @@ PDT_ERR_2CPNPE = "Select 2 Co-Planar Non-Parallel Edges" PDT_ERR_NCEDGES = "Edges must be Co-Planar Non-Parallel Edges, Selected Edges aren't" PDT_ERR_1EDGE1FACE = "Select 1 face and 1 Detached Edge" PDT_ERR_NOINT = "No Intersection Found" +PDT_ERR_BADDISTANCE = "Invalid Distance (Separtion) Error; Chosen Points too Close" +PDT_ERR_MATHSERROR = "Maths Error - Check Working Plane" +PDT_ERR_SAMERADII = "Circles have the same radius - Just offset the Edge between centres" # Info messages #