Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRune Morling <ermo.blender.org@spammesenseless.net>2020-02-03 13:33:42 +0300
committerRune Morling <ermo.blender.org@spammesenseless.net>2020-02-03 13:33:42 +0300
commitd4c1e6cdc121908c4a5b1af6b7fc23def5c94a17 (patch)
treea5d1bf2b13c452a1c641cfe5cd00b50796ee9735 /precision_drawing_tools
parent42d713b0a63a5c0502f861381dbf07c61731e2ae (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.py783
-rw-r--r--precision_drawing_tools/pdt_exception.py83
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