diff options
author | Rune Morling <ermo.blender.org@spammesenseless.net> | 2020-02-03 13:33:42 +0300 |
---|---|---|
committer | Rune Morling <ermo.blender.org@spammesenseless.net> | 2020-02-03 13:33:42 +0300 |
commit | d4c1e6cdc121908c4a5b1af6b7fc23def5c94a17 (patch) | |
tree | a5d1bf2b13c452a1c641cfe5cd00b50796ee9735 /precision_drawing_tools | |
parent | 42d713b0a63a5c0502f861381dbf07c61731e2ae (diff) |
PDT: Move stray files from add-on root folder
The error likely came about because we first commit files to @clockmender's
upstream github repo and then use git format-patch to create patches which
are then applied to the blender add-on repo with:
`git-am -3 --rerere-autoupdate foo.patch`.
Here, the issue seems to be that we've renamed files in the upstream github
repo, but that I then failed to first create and commit empty files with
the same name in the precision_drawing_tools/ repo, which meant that git
did the best it could and added them to the root of the blender add-ons
repo since the files didn't exist in the add-on repo index when they were
added.
Diffstat (limited to 'precision_drawing_tools')
-rw-r--r-- | precision_drawing_tools/pdt_command_functions.py | 783 | ||||
-rw-r--r-- | precision_drawing_tools/pdt_exception.py | 83 |
2 files changed, 866 insertions, 0 deletions
diff --git a/precision_drawing_tools/pdt_command_functions.py b/precision_drawing_tools/pdt_command_functions.py new file mode 100644 index 00000000..0e891544 --- /dev/null +++ b/precision_drawing_tools/pdt_command_functions.py @@ -0,0 +1,783 @@ +# ***** BEGIN GPL LICENSE BLOCK ***** +# +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ***** END GPL LICENCE BLOCK ***** +# +# ----------------------------------------------------------------------- +# Author: Alan Odom (Clockmender), Rune Morling (ermo) Copyright (c) 2019 +# ----------------------------------------------------------------------- +# +import bmesh +import numpy as np +from math import sqrt, tan, pi +from mathutils import Vector +from mathutils.geometry import intersect_point_line +from .pdt_functions import ( + set_mode, + oops, + get_percent, + dis_ang, + check_selection, + arc_centre, + intersection, + view_coords_i, + view_coords, + view_dir, + set_axis, +) + +from . import pdt_exception +PDT_SelectionError = pdt_exception.SelectionError +PDT_InvalidVector = pdt_exception.InvalidVector +PDT_ObjectModeError = pdt_exception.ObjectModeError +PDT_InfRadius = pdt_exception.InfRadius +PDT_NoObjectError = pdt_exception.NoObjectError +PDT_IntersectionError = pdt_exception.IntersectionError +PDT_InvalidOperation = pdt_exception.InvalidOperation +PDT_VerticesConnected = pdt_exception.VerticesConnected +PDT_InvalidAngle = pdt_exception.InvalidAngle + +from .pdt_msg_strings import ( + PDT_ERR_BAD3VALS, + PDT_ERR_BAD2VALS, + PDT_ERR_BAD1VALS, + PDT_ERR_CONNECTED, + PDT_ERR_SEL_2_VERTS, + PDT_ERR_EDOB_MODE, + PDT_ERR_NO_ACT_OBJ, + PDT_ERR_VERT_MODE, + PDT_ERR_SEL_3_VERTS, + PDT_ERR_SEL_3_OBJS, + PDT_ERR_EDIT_MODE, + PDT_ERR_NON_VALID, + PDT_LAB_NOR, + PDT_ERR_STRIGHT_LINE, + PDT_LAB_ARCCENTRE, + PDT_ERR_SEL_4_VERTS, + PDT_ERR_INT_NO_ALL, + PDT_LAB_INTERSECT, + PDT_ERR_SEL_4_OBJS, + PDT_INF_OBJ_MOVED, + PDT_ERR_SEL_2_VERTIO, + PDT_ERR_SEL_2_OBJS, + PDT_ERR_SEL_3_VERTIO, + PDT_ERR_TAPER_ANG, + PDT_ERR_TAPER_SEL, + PDT_ERR_INT_LINES, + PDT_LAB_PLANE, +) + + +def vector_build(context, pg, obj, operation, values, num_values): + """Build Movement Vector from input Fields. + + Args: + context: Blender bpy.context instance. + pg: PDT Parameters Group - our variables + obj: The Active Object + operation: The Operation e.g. Create New Vertex + values: The paramters passed e.g. 1,4,3 for Catrtesan Coordinates + num_values: The number of values passed - determines the function + + Returns: + Vector to position, or offset items. + """ + + scene = context.scene + plane = pg.plane + flip_angle = pg.flip_angle + flip_percent= pg.flip_percent + + # Cartesian 3D coordinates + if num_values == 3 and len(values) == 3: + output_vector = Vector((float(values[0]), float(values[1]), float(values[2]))) + # Polar 2D coordinates + elif num_values == 2 and len(values) == 2: + output_vector = dis_ang(values, flip_angle, plane, scene) + # Percentage of imaginary line between two 3D coordinates + elif num_values == 1 and len(values) == 1: + output_vector = get_percent(obj, flip_percent, float(values[0]), operation, scene) + else: + if num_values == 3: + pg.error = PDT_ERR_BAD3VALS + elif num_values == 2: + pg.error = PDT_ERR_BAD2VALS + else: + pg.error = PDT_ERR_BAD1VALS + context.window_manager.popup_menu(oops, title="Error", icon="ERROR") + raise PDT_InvalidVector + return output_vector + + +def placement_normal(context, operation): + """Manipulates Geometry, or Objects by Normal Intersection between 3 points. + + Args: + context: Blender bpy.context instance. + operation: The Operation e.g. Create New Vertex + + Returns: + Status Set. + """ + + scene = context.scene + pg = scene.pdt_pg + extend_all = pg.extend + obj = context.view_layer.objects.active + + if obj.mode == "EDIT": + if obj is None: + pg.error = PDT_ERR_NO_ACT_OBJ + context.window_manager.popup_menu(oops, title="Error", icon="ERROR") + raise PDT_ObjectModeError + obj_loc = obj.matrix_world.decompose()[0] + bm = bmesh.from_edit_mesh(obj.data) + if len(bm.select_history) == 3: + vector_a, vector_b, vector_c = check_selection(3, bm, obj) + if vector_a is None: + pg.error = PDT_ERR_VERT_MODE + context.window_manager.popup_menu(oops, title="Error", icon="ERROR") + 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") + 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") + 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 extend_all: + 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 extend_all: + for v in [v for v in bm.verts if v.select]: + bm.edges.new([v, vertex_new]) + else: + bm.edges.new([bm.select_history[-1], vertex_new]) + for v in [v for v in bm.verts if v.select]: + v.select_set(False) + vertex_new.select_set(True) + bmesh.update_edit_mesh(obj.data) + bm.select_history.clear() + else: + pg.error = f"{operation} {PDT_ERR_NON_VALID} {PDT_LAB_NOR}" + context.window_manager.popup_menu(oops, title="Error", icon="ERROR") + + +def placement_arc_centre(context, operation): + """Manipulates Geometry, or Objects to an Arc Centre defined by 3 points on an Imaginary Arc. + + Args: + context: Blender bpy.context instance. + operation: The Operation e.g. Create New Vertex + + Returns: + Status Set. + """ + + scene = context.scene + pg = scene.pdt_pg + extend_all = pg.extend + obj = context.view_layer.objects.active + + if obj.mode == "EDIT": + if obj is None: + pg.error = PDT_ERR_NO_ACT_OBJ + context.window_manager.popup_menu(oops, title="Error", icon="ERROR") + raise PDT_ObjectModeError + obj = context.view_layer.objects.active + obj_loc = obj.matrix_world.decompose()[0] + bm = bmesh.from_edit_mesh(obj.data) + verts = [v for v in bm.verts if v.select] + if len(verts) != 3: + pg.error = f"{PDT_ERR_SEL_3_VERTS} {len(verts)})" + context.window_manager.popup_menu(oops, title="Error", icon="ERROR") + 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") + 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 extend_all: + 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 extend_all: + 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") + 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 + else: + pg.error = f"{operation} {PDT_ERR_NON_VALID} {PDT_LAB_ARCCENTRE}" + context.window_manager.popup_menu(oops, title="Error", icon="ERROR") + + +def placement_intersect(context, operation): + """Manipulates Geometry, or Objects by Convergance Intersection between 4 points, or 2 Edges. + + Args: + context: Blender bpy.context instance. + operation: The Operation e.g. Create New Vertex + + Returns: + Status Set. + """ + + scene = context.scene + pg = scene.pdt_pg + plane = pg.plane + obj = context.view_layer.objects.active + if obj.mode == "EDIT": + if obj is None: + pg.error = PDT_ERR_NO_ACT_OBJ + context.window_manager.popup_menu(oops, title="Error", icon="ERROR") + raise PDT_NoObjectError + obj_loc = obj.matrix_world.decompose()[0] + bm = bmesh.from_edit_mesh(obj.data) + edges = [e for e in bm.edges if e.select] + extend_all = pg.extend + + if len(edges) == 2: + vertex_a = edges[0].verts[0] + vertex_b = edges[0].verts[1] + vertex_c = edges[1].verts[0] + vertex_d = edges[1].verts[1] + else: + if len(bm.select_history) != 4: + pg.error = ( + PDT_ERR_SEL_4_VERTS + + str(len(bm.select_history)) + + " Vertices/" + + str(len(edges)) + + " Edges)" + ) + context.window_manager.popup_menu(oops, title="Error", icon="ERROR") + 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") + raise PDT_IntersectionError + + if operation == "C": + scene.cursor.location = obj_loc + vector_delta + elif operation == "P": + pg.pivot_loc = obj_loc + vector_delta + elif operation == "N": + vector_new = vector_delta + vertex_new = bm.verts.new(vector_new) + for v in [v for v in bm.verts if v.select]: + v.select_set(False) + for f in bm.faces: + f.select_set(False) + for e in bm.edges: + e.select_set(False) + vertex_new.select_set(True) + bmesh.update_edit_mesh(obj.data) + bm.select_history.clear() + elif operation in {"G", "V"}: + vertex_new = None + process = False + + if (vertex_a.co - vector_delta).length < (vertex_b.co - vector_delta).length: + if operation == "G": + vertex_a.co = vector_delta + process = True + else: + vertex_new = bm.verts.new(vector_delta) + bm.edges.new([vertex_a, vertex_new]) + process = True + else: + if operation == "G" and extend_all: + vertex_b.co = vector_delta + elif operation == "V" and extend_all: + vertex_new = bm.verts.new(vector_delta) + bm.edges.new([vertex_b, vertex_new]) + else: + return + + if (vertex_c.co - vector_delta).length < (vertex_d.co - vector_delta).length: + if operation == "G" and extend_all: + vertex_c.co = vector_delta + elif operation == "V" and extend_all: + bm.edges.new([vertex_c, vertex_new]) + else: + return + else: + if operation == "G" and extend_all: + vertex_d.co = vector_delta + elif operation == "V" and extend_all: + bm.edges.new([vertex_d, vertex_new]) + else: + return + bm.select_history.clear() + bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.0001) + + if not process and not extend_all: + pg.error = PDT_ERR_INT_NO_ALL + context.window_manager.popup_menu(oops, title="Error", icon="ERROR") + bmesh.update_edit_mesh(obj.data) + return + for v in bm.verts: + v.select_set(False) + for f in bm.faces: + f.select_set(False) + for e in bm.edges: + e.select_set(False) + + if vertex_new is not None: + vertex_new.select_set(True) + for v in bm.select_history: + if v is not None: + v.select_set(True) + bmesh.update_edit_mesh(obj.data) + else: + pg.error = f"{operation} {PDT_ERR_NON_VALID} {PDT_LAB_INTERSECT}" + context.window_manager.popup_menu(oops, title="Error", icon="ERROR") + 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") + raise PDT_SelectionError + order = pg.object_order.split(",") + objs = sorted(context.view_layer.objects.selected, key=lambda x: x.name) + pg.error = ( + "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") + 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 + else: + 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") + raise PDT_VerticesConnected + else: + pg.error = f"{PDT_ERR_SEL_2_VERTS} {len(verts)})" + context.window_manager.popup_menu(oops, title="Error", icon="ERROR") + raise PDT_SelectionError + else: + pg.error = f"{PDT_ERR_EDOB_MODE},{obj.mode})" + context.window_manager.popup_menu(oops, title="Error", icon="ERROR") + raise PDT_ObjectModeError + + +def set_angle_distance_two(context): + """Measures Angle and Offsets between 2 Points in View Plane. + + Uses 2 Selected Vertices to set pg.angle and pg.distance scene variables + also sets delta offset from these 2 points using standard Numpy Routines + Works in Edit and Oject Modes. + + Args: + context: Blender bpy.context instance. + + Returns: + Status Set. + """ + + scene = context.scene + pg = scene.pdt_pg + plane = pg.plane + flip_angle = pg.flip_angle + obj = context.view_layer.objects.active + if obj is None: + pg.error = PDT_ERR_NO_ACT_OBJ + context.window_manager.popup_menu(oops, title="Error", icon="ERROR") + return + if obj.mode == "EDIT": + bm = bmesh.from_edit_mesh(obj.data) + verts = [v for v in bm.verts if v.select] + if len(verts) == 2: + if len(bm.select_history) == 2: + vector_a, vector_b = check_selection(2, bm, obj) + if vector_a is None: + pg.error = PDT_ERR_VERT_MODE + context.window_manager.popup_menu(oops, title="Error", icon="ERROR") + 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") + raise PDT_SelectionError + else: + pg.error = f"{PDT_ERR_SEL_2_VERTIO} {len(verts)})" + context.window_manager.popup_menu(oops, title="Error", icon="ERROR") + 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") + raise PDT_SelectionError + objs_s = [ob for ob in objs if ob.name != obj.name] + vector_a = obj.matrix_world.decompose()[0] + vector_b = objs_s[-1].matrix_world.decompose()[0] + if plane == "LO": + vector_difference = vector_b - vector_a + vector_b = view_coords_i(vector_difference.x, vector_difference.y, vector_difference.z) + vector_a = Vector((0, 0, 0)) + v0 = np.array([vector_a.x + 1, vector_a.y]) - np.array([vector_a.x, vector_a.y]) + v1 = np.array([vector_b.x, vector_b.y]) - np.array([vector_a.x, vector_a.y]) + else: + a1, a2, _ = set_mode(plane) + v0 = np.array([vector_a[a1] + 1, vector_a[a2]]) - np.array([vector_a[a1], vector_a[a2]]) + v1 = np.array([vector_b[a1], vector_b[a2]]) - np.array([vector_a[a1], vector_a[a2]]) + ang = np.rad2deg(np.arctan2(np.linalg.det([v0, v1]), np.dot(v0, v1))) + decimal_places = context.preferences.addons[__package__].preferences.pdt_input_round + if flip_angle: + if ang > 0: + pg.angle = round(ang - 180, decimal_places) + else: + pg.angle = round(ang - 180, decimal_places) + else: + pg.angle = round(ang, decimal_places) + if plane == "LO": + pg.distance = round(sqrt( + (vector_a.x - vector_b.x) ** 2 + + (vector_a.y - vector_b.y) ** 2), decimal_places) + else: + pg.distance = round(sqrt( + (vector_a[a1] - vector_b[a1]) ** 2 + + (vector_a[a2] - vector_b[a2]) ** 2), decimal_places) + pg.cartesian_coords = Vector(([round(i, decimal_places) for i in vector_b - vector_a])) + + +def set_angle_distance_three(context): + """Measures Angle and Offsets between 3 Points in World Space, Also sets Deltas. + + Uses 3 Selected Vertices to set pg.angle and pg.distance scene variables + also sets delta offset from these 3 points using standard Numpy Routines + Works in Edit and Oject Modes. + + Args: + context: Blender bpy.context instance. + + Returns: + Status Set. + """ + + pg = context.scene.pdt_pg + flip_angle = pg.flip_angle + obj = context.view_layer.objects.active + if obj is None: + pg.error = PDT_ERR_NO_ACT_OBJ + context.window_manager.popup_menu(oops, title="Error", icon="ERROR") + raise PDT_NoObjectError + if obj.mode == "EDIT": + bm = bmesh.from_edit_mesh(obj.data) + verts = [v for v in bm.verts if v.select] + if len(verts) == 3: + if len(bm.select_history) == 3: + vector_a, vector_b, vector_c = check_selection(3, bm, obj) + if vector_a is None: + pg.error = PDT_ERR_VERT_MODE + context.window_manager.popup_menu(oops, title="Error", icon="ERROR") + raise PDT_InvalidVector + else: + pg.error = f"{PDT_ERR_SEL_3_VERTIO} {len(bm.select_history)})" + context.window_manager.popup_menu(oops, title="Error", icon="ERROR") + raise PDT_SelectionError + else: + pg.error = f"{PDT_ERR_SEL_3_VERTIO} {len(verts)})" + context.window_manager.popup_menu(oops, title="Error", icon="ERROR") + 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") + raise PDT_SelectionError + objs_s = [ob for ob in objs if ob.name != obj.name] + vector_a = obj.matrix_world.decompose()[0] + vector_b = objs_s[-1].matrix_world.decompose()[0] + vector_c = objs_s[-2].matrix_world.decompose()[0] + ba = np.array([vector_b.x, vector_b.y, vector_b.z]) - np.array( + [vector_a.x, vector_a.y, vector_a.z] + ) + bc = np.array([vector_c.x, vector_c.y, vector_c.z]) - np.array( + [vector_a.x, vector_a.y, vector_a.z] + ) + angle_cosine = np.dot(ba, bc) / (np.linalg.norm(ba) * np.linalg.norm(bc)) + ang = np.degrees(np.arccos(angle_cosine)) + decimal_places = context.preferences.addons[__package__].preferences.pdt_input_round + if flip_angle: + if ang > 0: + pg.angle = round(ang - 180, decimal_places) + else: + pg.angle = round(ang - 180, decimal_places) + else: + pg.angle = round(ang, decimal_places) + pg.distance = round((vector_a - vector_b).length, decimal_places) + pg.cartesian_coords = Vector(([round(i, decimal_places) for i in vector_b - vector_a])) + + +def origin_to_cursor(context): + """Sets Object Origin in Edit Mode to Cursor Location. + + Keeps geometry static in World Space whilst moving Object Origin + Requires cursor location + Works in Edit and Object Modes. + + Args: + context: Blender bpy.context instance. + + Returns: + Status Set. + """ + + scene = context.scene + pg = context.scene.pdt_pg + obj = context.view_layer.objects.active + if obj is None: + pg.error = PDT_ERR_NO_ACT_OBJ + context.window_manager.popup_menu(oops, title="Error", icon="ERROR") + return + obj_loc = obj.matrix_world.decompose()[0] + cur_loc = scene.cursor.location + diff_v = obj_loc - cur_loc + if obj.mode == "EDIT": + bm = bmesh.from_edit_mesh(obj.data) + for v in bm.verts: + v.co = v.co + diff_v + obj.location = cur_loc + bmesh.update_edit_mesh(obj.data) + bm.select_history.clear() + elif obj.mode == "OBJECT": + for v in obj.data.vertices: + v.co = v.co + diff_v + obj.location = cur_loc + else: + pg.error = f"{PDT_ERR_EDOB_MODE} {obj.mode})" + context.window_manager.popup_menu(oops, title="Error", icon="ERROR") + raise PDT_ObjectModeError + + +def taper(context): + """Taper Geometry along World Axes. + + Similar to Shear command except that it shears by angle rather than displacement. + Rotates about World Axes and displaces along World Axes, angle must not exceed +-80 degrees. + Rotation axis is centred on Active Vertex. + Works only in Edit mode. + + Args: + context: Blender bpy.context instance. + + Note: + Uses pg.taper & pg.angle scene variables + + Returns: + Status Set. + """ + + scene = context.scene + pg = scene.pdt_pg + tap_ax = pg.taper + ang_v = pg.angle + obj = context.view_layer.objects.active + if all([bool(obj), obj.type == "MESH", obj.mode == "EDIT"]): + if ang_v > 80 or ang_v < -80: + pg.error = f"{PDT_ERR_TAPER_ANG} {ang_v})" + context.window_manager.popup_menu(oops, title="Error", icon="ERROR") + raise PDT_InvalidAngle + if obj is None: + pg.error = PDT_ERR_NO_ACT_OBJ + context.window_manager.popup_menu(oops, title="Error", icon="ERROR") + 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") + 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 + else: + dis_v = sqrt( + (rotate_vertex.co[a3] - v.co[a3]) ** 2 + (rotate_vertex.co[a2] - v.co[a2]) ** 2 + ) + v.co[a2] = v.co[a2] - (dis_v * tan(ang_v * pi / 180)) + bmesh.update_edit_mesh(obj.data) + bm.select_history.clear() + else: + pg.error = f"{PDT_ERR_EDOB_MODE},{obj.mode})" + context.window_manager.popup_menu(oops, title="Error", icon="ERROR") + raise PDT_ObjectModeError diff --git a/precision_drawing_tools/pdt_exception.py b/precision_drawing_tools/pdt_exception.py new file mode 100644 index 00000000..dd2aea4d --- /dev/null +++ b/precision_drawing_tools/pdt_exception.py @@ -0,0 +1,83 @@ +# ***** 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 +# ----------------------------------------------------------------------- +# +# Exceptions are used in the absence of nullable types in python + + +class SelectionError(Exception): + """Selection Error Exception.""" + pass + + +class InvalidVector(Exception): + """Invalid Vector Exception.""" + pass + + +class CommandFailure(Exception): + """Command Failure Exception.""" + pass + + +class ObjectModeError(Exception): + """Object Mode Error Exception.""" + pass + + +class MathsError(Exception): + """Mathematical Expression Error Exception.""" + pass + + +class InfRadius(Exception): + """Infinite Radius Error Exception.""" + pass + + +class NoObjectError(Exception): + """No Active Object Exception.""" + pass + + +class IntersectionError(Exception): + """Failure to Find Intersect Exception.""" + pass + + +class InvalidOperation(Exception): + """Invalid Operation Error Exception.""" + pass + + +class VerticesConnected(Exception): + """Vertices Already Connected Exception.""" + pass + + +class InvalidAngle(Exception): + """Angle Given was Outside Parameters Exception.""" + pass + +class ShaderError(Exception): + """GL Shader Error Exception.""" + pass |