1# ***** BEGIN GPL LICENSE BLOCK ***** 2# 3# 4# This program is free software; you can redistribute it and/or 5# modify it under the terms of the GNU General Public License 6# as published by the Free Software Foundation; either version 2 7# of the License, or (at your option) any later version. 8# 9# This program is distributed in the hope that it will be useful, 10# but WITHOUT ANY WARRANTY; without even the implied warranty of 11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12# GNU General Public License for more details. 13# 14# You should have received a copy of the GNU General Public License 15# along with this program; if not, write to the Free Software Foundation, 16# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17# 18# ***** END GPL LICENCE BLOCK ***** 19# 20# ----------------------------------------------------------------------- 21# Author: Alan Odom (Clockmender), Rune Morling (ermo) Copyright (c) 2019 22# ----------------------------------------------------------------------- 23# 24import bmesh 25import numpy as np 26from math import sqrt, tan, pi 27from mathutils import Vector 28from mathutils.geometry import intersect_point_line 29from .pdt_functions import ( 30 set_mode, 31 oops, 32 get_percent, 33 dis_ang, 34 check_selection, 35 arc_centre, 36 intersection, 37 view_coords_i, 38 view_coords, 39 view_dir, 40 set_axis, 41) 42 43from . import pdt_exception 44PDT_SelectionError = pdt_exception.SelectionError 45PDT_InvalidVector = pdt_exception.InvalidVector 46PDT_ObjectModeError = pdt_exception.ObjectModeError 47PDT_InfRadius = pdt_exception.InfRadius 48PDT_NoObjectError = pdt_exception.NoObjectError 49PDT_IntersectionError = pdt_exception.IntersectionError 50PDT_InvalidOperation = pdt_exception.InvalidOperation 51PDT_VerticesConnected = pdt_exception.VerticesConnected 52PDT_InvalidAngle = pdt_exception.InvalidAngle 53 54from .pdt_msg_strings import ( 55 PDT_ERR_BAD3VALS, 56 PDT_ERR_BAD2VALS, 57 PDT_ERR_BAD1VALS, 58 PDT_ERR_CONNECTED, 59 PDT_ERR_SEL_2_VERTS, 60 PDT_ERR_EDOB_MODE, 61 PDT_ERR_NO_ACT_OBJ, 62 PDT_ERR_VERT_MODE, 63 PDT_ERR_SEL_3_VERTS, 64 PDT_ERR_SEL_3_OBJS, 65 PDT_ERR_EDIT_MODE, 66 PDT_ERR_NON_VALID, 67 PDT_LAB_NOR, 68 PDT_ERR_STRIGHT_LINE, 69 PDT_LAB_ARCCENTRE, 70 PDT_ERR_SEL_4_VERTS, 71 PDT_ERR_INT_NO_ALL, 72 PDT_LAB_INTERSECT, 73 PDT_ERR_SEL_4_OBJS, 74 PDT_INF_OBJ_MOVED, 75 PDT_ERR_SEL_2_VERTIO, 76 PDT_ERR_SEL_2_OBJS, 77 PDT_ERR_SEL_3_VERTIO, 78 PDT_ERR_TAPER_ANG, 79 PDT_ERR_TAPER_SEL, 80 PDT_ERR_INT_LINES, 81 PDT_LAB_PLANE, 82) 83 84 85def vector_build(context, pg, obj, operation, values, num_values): 86 """Build Movement Vector from Input Fields. 87 88 Args: 89 context: Blender bpy.context instance. 90 pg: PDT Parameters Group - our variables 91 obj: The Active Object 92 operation: The Operation e.g. Create New Vertex 93 values: The paramters passed e.g. 1,4,3 for Cartesian Coordinates 94 num_values: The number of values passed - determines the function 95 96 Returns: 97 Vector to position, or offset, items. 98 """ 99 100 scene = context.scene 101 plane = pg.plane 102 flip_angle = pg.flip_angle 103 flip_percent= pg.flip_percent 104 105 # Cartesian 3D coordinates 106 if num_values == 3 and len(values) == 3: 107 output_vector = Vector((float(values[0]), float(values[1]), float(values[2]))) 108 # Polar 2D coordinates 109 elif num_values == 2 and len(values) == 2: 110 output_vector = dis_ang(values, flip_angle, plane, scene) 111 # Percentage of imaginary line between two 3D coordinates 112 elif num_values == 1 and len(values) == 1: 113 output_vector = get_percent(obj, flip_percent, float(values[0]), operation, scene) 114 else: 115 if num_values == 3: 116 pg.error = PDT_ERR_BAD3VALS 117 elif num_values == 2: 118 pg.error = PDT_ERR_BAD2VALS 119 else: 120 pg.error = PDT_ERR_BAD1VALS 121 context.window_manager.popup_menu(oops, title="Error", icon="ERROR") 122 raise PDT_InvalidVector 123 return output_vector 124 125 126def placement_normal(context, operation): 127 """Manipulates Geometry, or Objects by Normal Intersection between 3 points. 128 129 Args: 130 context: Blender bpy.context instance. 131 operation: The Operation e.g. Create New Vertex 132 133 Returns: 134 Status Set. 135 """ 136 137 scene = context.scene 138 pg = scene.pdt_pg 139 extend_all = pg.extend 140 obj = context.view_layer.objects.active 141 142 if obj.mode == "EDIT": 143 if obj is None: 144 pg.error = PDT_ERR_NO_ACT_OBJ 145 context.window_manager.popup_menu(oops, title="Error", icon="ERROR") 146 raise PDT_ObjectModeError 147 obj_loc = obj.matrix_world.decompose()[0] 148 bm = bmesh.from_edit_mesh(obj.data) 149 if len(bm.select_history) == 3: 150 vector_a, vector_b, vector_c = check_selection(3, bm, obj) 151 if vector_a is None: 152 pg.error = PDT_ERR_VERT_MODE 153 context.window_manager.popup_menu(oops, title="Error", icon="ERROR") 154 raise PDT_FeatureError 155 else: 156 pg.error = f"{PDT_ERR_SEL_3_VERTIO} {len(bm.select_history)})" 157 context.window_manager.popup_menu(oops, title="Error", icon="ERROR") 158 raise PDT_SelectionError 159 elif obj.mode == "OBJECT": 160 objs = context.view_layer.objects.selected 161 if len(objs) != 3: 162 pg.error = f"{PDT_ERR_SEL_3_OBJS} {len(objs)})" 163 context.window_manager.popup_menu(oops, title="Error", icon="ERROR") 164 raise PDT_SelectionError 165 objs_s = [ob for ob in objs if ob.name != obj.name] 166 vector_a = obj.matrix_world.decompose()[0] 167 vector_b = objs_s[-1].matrix_world.decompose()[0] 168 vector_c = objs_s[-2].matrix_world.decompose()[0] 169 vector_delta = intersect_point_line(vector_a, vector_b, vector_c)[0] 170 if operation == "C": 171 if obj.mode == "EDIT": 172 scene.cursor.location = obj_loc + vector_delta 173 elif obj.mode == "OBJECT": 174 scene.cursor.location = vector_delta 175 elif operation == "P": 176 if obj.mode == "EDIT": 177 pg.pivot_loc = obj_loc + vector_delta 178 elif obj.mode == "OBJECT": 179 pg.pivot_loc = vector_delta 180 elif operation == "G": 181 if obj.mode == "EDIT": 182 if extend_all: 183 for v in [v for v in bm.verts if v.select]: 184 v.co = vector_delta 185 bm.select_history.clear() 186 bmesh.ops.remove_doubles(bm, verts=[v for v in bm.verts if v.select], dist=0.0001) 187 else: 188 bm.select_history[-1].co = vector_delta 189 bm.select_history.clear() 190 bmesh.update_edit_mesh(obj.data) 191 elif obj.mode == "OBJECT": 192 context.view_layer.objects.active.location = vector_delta 193 elif operation == "N": 194 if obj.mode == "EDIT": 195 vertex_new = bm.verts.new(vector_delta) 196 bmesh.update_edit_mesh(obj.data) 197 bm.select_history.clear() 198 for v in [v for v in bm.verts if v.select]: 199 v.select_set(False) 200 vertex_new.select_set(True) 201 else: 202 pg.error = f"{PDT_ERR_EDIT_MODE} {obj.mode})" 203 context.window_manager.popup_menu(oops, title="Error", icon="ERROR") 204 return 205 elif operation == "V" and obj.mode == "EDIT": 206 vector_new = vector_delta 207 vertex_new = bm.verts.new(vector_new) 208 if extend_all: 209 for v in [v for v in bm.verts if v.select]: 210 bm.edges.new([v, vertex_new]) 211 else: 212 bm.edges.new([bm.select_history[-1], vertex_new]) 213 for v in [v for v in bm.verts if v.select]: 214 v.select_set(False) 215 vertex_new.select_set(True) 216 bmesh.update_edit_mesh(obj.data) 217 bm.select_history.clear() 218 else: 219 pg.error = f"{operation} {PDT_ERR_NON_VALID} {PDT_LAB_NOR}" 220 context.window_manager.popup_menu(oops, title="Error", icon="ERROR") 221 222 223def placement_arc_centre(context, operation): 224 """Manipulates Geometry, or Objects to an Arc Centre defined by 3 points on an Imaginary Arc. 225 226 Args: 227 context: Blender bpy.context instance. 228 operation: The Operation e.g. Create New Vertex 229 230 Returns: 231 Status Set. 232 """ 233 234 scene = context.scene 235 pg = scene.pdt_pg 236 extend_all = pg.extend 237 obj = context.view_layer.objects.active 238 239 if obj.mode == "EDIT": 240 if obj is None: 241 pg.error = PDT_ERR_NO_ACT_OBJ 242 context.window_manager.popup_menu(oops, title="Error", icon="ERROR") 243 raise PDT_ObjectModeError 244 obj = context.view_layer.objects.active 245 obj_loc = obj.matrix_world.decompose()[0] 246 bm = bmesh.from_edit_mesh(obj.data) 247 verts = [v for v in bm.verts if v.select] 248 if len(verts) != 3: 249 pg.error = f"{PDT_ERR_SEL_3_VERTS} {len(verts)})" 250 context.window_manager.popup_menu(oops, title="Error", icon="ERROR") 251 raise PDT_SelectionError 252 vector_a = verts[0].co 253 vector_b = verts[1].co 254 vector_c = verts[2].co 255 vector_delta, radius = arc_centre(vector_a, vector_b, vector_c) 256 if str(radius) == "inf": 257 pg.error = PDT_ERR_STRIGHT_LINE 258 context.window_manager.popup_menu(oops, title="Error", icon="ERROR") 259 raise PDT_InfRadius 260 pg.distance = radius 261 if operation == "C": 262 scene.cursor.location = obj_loc + vector_delta 263 elif operation == "P": 264 pg.pivot_loc = obj_loc + vector_delta 265 elif operation == "N": 266 vector_new = vector_delta 267 vertex_new = bm.verts.new(vector_new) 268 for v in [v for v in bm.verts if v.select]: 269 v.select_set(False) 270 vertex_new.select_set(True) 271 bmesh.update_edit_mesh(obj.data) 272 bm.select_history.clear() 273 vertex_new.select_set(True) 274 elif operation == "G": 275 if extend_all: 276 for v in [v for v in bm.verts if v.select]: 277 v.co = vector_delta 278 bm.select_history.clear() 279 bmesh.ops.remove_doubles(bm, verts=[v for v in bm.verts if v.select], dist=0.0001) 280 else: 281 bm.select_history[-1].co = vector_delta 282 bm.select_history.clear() 283 bmesh.update_edit_mesh(obj.data) 284 elif operation == "V": 285 vertex_new = bm.verts.new(vector_delta) 286 if extend_all: 287 for v in [v for v in bm.verts if v.select]: 288 bm.edges.new([v, vertex_new]) 289 v.select_set(False) 290 vertex_new.select_set(True) 291 bm.select_history.clear() 292 bmesh.ops.remove_doubles(bm, verts=[v for v in bm.verts if v.select], dist=0.0001) 293 bmesh.update_edit_mesh(obj.data) 294 else: 295 bm.edges.new([bm.select_history[-1], vertex_new]) 296 bmesh.update_edit_mesh(obj.data) 297 bm.select_history.clear() 298 else: 299 pg.error = f"{operation} {PDT_ERR_NON_VALID} {PDT_LAB_ARCCENTRE}" 300 context.window_manager.popup_menu(oops, title="Error", icon="ERROR") 301 elif obj.mode == "OBJECT": 302 if len(context.view_layer.objects.selected) != 3: 303 pg.error = f"{PDT_ERR_SEL_3_OBJS} {len(context.view_layer.objects.selected)})" 304 context.window_manager.popup_menu(oops, title="Error", icon="ERROR") 305 raise PDT_SelectionError 306 vector_a = context.view_layer.objects.selected[0].matrix_world.decompose()[0] 307 vector_b = context.view_layer.objects.selected[1].matrix_world.decompose()[0] 308 vector_c = context.view_layer.objects.selected[2].matrix_world.decompose()[0] 309 vector_delta, radius = arc_centre(vector_a, vector_b, vector_c) 310 pg.distance = radius 311 if operation == "C": 312 scene.cursor.location = vector_delta 313 elif operation == "P": 314 pg.pivot_loc = vector_delta 315 elif operation == "G": 316 context.view_layer.objects.active.location = vector_delta 317 else: 318 pg.error = f"{operation} {PDT_ERR_NON_VALID} {PDT_LAB_ARCCENTRE}" 319 context.window_manager.popup_menu(oops, title="Error", icon="ERROR") 320 321 322def placement_intersect(context, operation): 323 """Manipulates Geometry, or Objects by Convergance Intersection between 4 points, or 2 Edges. 324 325 Args: 326 context: Blender bpy.context instance. 327 operation: The Operation e.g. Create New Vertex 328 329 Returns: 330 Status Set. 331 """ 332 333 scene = context.scene 334 pg = scene.pdt_pg 335 plane = pg.plane 336 obj = context.view_layer.objects.active 337 if obj.mode == "EDIT": 338 if obj is None: 339 pg.error = PDT_ERR_NO_ACT_OBJ 340 context.window_manager.popup_menu(oops, title="Error", icon="ERROR") 341 raise PDT_NoObjectError 342 obj_loc = obj.matrix_world.decompose()[0] 343 bm = bmesh.from_edit_mesh(obj.data) 344 edges = [e for e in bm.edges if e.select] 345 extend_all = pg.extend 346 347 if len(edges) == 2: 348 vertex_a = edges[0].verts[0] 349 vertex_b = edges[0].verts[1] 350 vertex_c = edges[1].verts[0] 351 vertex_d = edges[1].verts[1] 352 else: 353 if len(bm.select_history) != 4: 354 pg.error = ( 355 PDT_ERR_SEL_4_VERTS 356 + str(len(bm.select_history)) 357 + " Vertices/" 358 + str(len(edges)) 359 + " Edges)" 360 ) 361 context.window_manager.popup_menu(oops, title="Error", icon="ERROR") 362 raise PDT_SelectionError 363 vertex_a = bm.select_history[-1] 364 vertex_b = bm.select_history[-2] 365 vertex_c = bm.select_history[-3] 366 vertex_d = bm.select_history[-4] 367 368 vector_delta, done = intersection(vertex_a.co, vertex_b.co, vertex_c.co, vertex_d.co, plane) 369 if not done: 370 pg.error = f"{PDT_ERR_INT_LINES} {plane} {PDT_LAB_PLANE}" 371 context.window_manager.popup_menu(oops, title="Error", icon="ERROR") 372 raise PDT_IntersectionError 373 374 if operation == "C": 375 scene.cursor.location = obj_loc + vector_delta 376 elif operation == "P": 377 pg.pivot_loc = obj_loc + vector_delta 378 elif operation == "N": 379 vector_new = vector_delta 380 vertex_new = bm.verts.new(vector_new) 381 for v in [v for v in bm.verts if v.select]: 382 v.select_set(False) 383 for f in bm.faces: 384 f.select_set(False) 385 for e in bm.edges: 386 e.select_set(False) 387 vertex_new.select_set(True) 388 bmesh.update_edit_mesh(obj.data) 389 bm.select_history.clear() 390 elif operation in {"G", "V"}: 391 vertex_new = None 392 process = False 393 394 if (vertex_a.co - vector_delta).length < (vertex_b.co - vector_delta).length: 395 if operation == "G": 396 vertex_a.co = vector_delta 397 process = True 398 else: 399 vertex_new = bm.verts.new(vector_delta) 400 bm.edges.new([vertex_a, vertex_new]) 401 process = True 402 else: 403 if operation == "G" and extend_all: 404 vertex_b.co = vector_delta 405 elif operation == "V" and extend_all: 406 vertex_new = bm.verts.new(vector_delta) 407 bm.edges.new([vertex_b, vertex_new]) 408 else: 409 return 410 411 if (vertex_c.co - vector_delta).length < (vertex_d.co - vector_delta).length: 412 if operation == "G" and extend_all: 413 vertex_c.co = vector_delta 414 elif operation == "V" and extend_all: 415 bm.edges.new([vertex_c, vertex_new]) 416 else: 417 return 418 else: 419 if operation == "G" and extend_all: 420 vertex_d.co = vector_delta 421 elif operation == "V" and extend_all: 422 bm.edges.new([vertex_d, vertex_new]) 423 else: 424 return 425 bm.select_history.clear() 426 bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.0001) 427 428 if not process and not extend_all: 429 pg.error = PDT_ERR_INT_NO_ALL 430 context.window_manager.popup_menu(oops, title="Error", icon="ERROR") 431 bmesh.update_edit_mesh(obj.data) 432 return 433 for v in bm.verts: 434 v.select_set(False) 435 for f in bm.faces: 436 f.select_set(False) 437 for e in bm.edges: 438 e.select_set(False) 439 440 if vertex_new is not None: 441 vertex_new.select_set(True) 442 for v in bm.select_history: 443 if v is not None: 444 v.select_set(True) 445 bmesh.update_edit_mesh(obj.data) 446 else: 447 pg.error = f"{operation} {PDT_ERR_NON_VALID} {PDT_LAB_INTERSECT}" 448 context.window_manager.popup_menu(oops, title="Error", icon="ERROR") 449 raise PDT_InvalidOperation 450 451 elif obj.mode == "OBJECT": 452 if len(context.view_layer.objects.selected) != 4: 453 pg.error = f"{PDT_ERR_SEL_4_OBJS} {len(context.view_layer.objects.selected)})" 454 context.window_manager.popup_menu(oops, title="Error", icon="ERROR") 455 raise PDT_SelectionError 456 order = pg.object_order.split(",") 457 objs = sorted(context.view_layer.objects.selected, key=lambda x: x.name) 458 pg.error = ( 459 "Original Object Order (1,2,3,4) was: " 460 + objs[0].name 461 + ", " 462 + objs[1].name 463 + ", " 464 + objs[2].name 465 + ", " 466 + objs[3].name 467 ) 468 context.window_manager.popup_menu(oops, title="Info", icon="INFO") 469 470 vector_a = objs[int(order[0]) - 1].matrix_world.decompose()[0] 471 vector_b = objs[int(order[1]) - 1].matrix_world.decompose()[0] 472 vector_c = objs[int(order[2]) - 1].matrix_world.decompose()[0] 473 vector_d = objs[int(order[3]) - 1].matrix_world.decompose()[0] 474 vector_delta, done = intersection(vector_a, vector_b, vector_c, vector_d, plane) 475 if not done: 476 pg.error = f"{PDT_ERR_INT_LINES} {plane} {PDT_LAB_PLANE}" 477 context.window_manager.popup_menu(oops, title="Error", icon="ERROR") 478 raise PDT_IntersectionError 479 if operation == "C": 480 scene.cursor.location = vector_delta 481 elif operation == "P": 482 pg.pivot_loc = vector_delta 483 elif operation == "G": 484 context.view_layer.objects.active.location = vector_delta 485 pg.error = f"{PDT_INF_OBJ_MOVED} {context.view_layer.objects.active.name}" 486 context.window_manager.popup_menu(oops, title="Info", icon="INFO") 487 else: 488 pg.error = f"{operation} {PDT_ERR_NON_VALID} {PDT_LAB_INTERSECT}" 489 context.window_manager.popup_menu(oops, title="Error", icon="ERROR") 490 return 491 else: 492 return 493 494 495def join_two_vertices(context): 496 """Joins 2 Free Vertices that do not form part of a Face. 497 498 Note: 499 Joins two vertices that do not form part of a single face 500 It is designed to close open Edge Loops, where a face is not required 501 or to join two disconnected Edges. 502 503 Args: 504 context: Blender bpy.context instance. 505 506 Returns: 507 Status Set. 508 """ 509 510 scene = context.scene 511 pg = scene.pdt_pg 512 obj = context.view_layer.objects.active 513 if all([bool(obj), obj.type == "MESH", obj.mode == "EDIT"]): 514 bm = bmesh.from_edit_mesh(obj.data) 515 verts = [v for v in bm.verts if v.select] 516 if len(verts) == 2: 517 try: 518 bm.edges.new([verts[-1], verts[-2]]) 519 bmesh.update_edit_mesh(obj.data) 520 bm.select_history.clear() 521 return 522 except ValueError: 523 pg.error = PDT_ERR_CONNECTED 524 context.window_manager.popup_menu(oops, title="Error", icon="ERROR") 525 raise PDT_VerticesConnected 526 else: 527 pg.error = f"{PDT_ERR_SEL_2_VERTS} {len(verts)})" 528 context.window_manager.popup_menu(oops, title="Error", icon="ERROR") 529 raise PDT_SelectionError 530 else: 531 pg.error = f"{PDT_ERR_EDOB_MODE},{obj.mode})" 532 context.window_manager.popup_menu(oops, title="Error", icon="ERROR") 533 raise PDT_ObjectModeError 534 535 536def set_angle_distance_two(context): 537 """Measures Angle and Offsets between 2 Points in View Plane. 538 539 Note: 540 Uses 2 Selected Vertices to set pg.angle and pg.distance scene variables 541 also sets delta offset from these 2 points using standard Numpy Routines 542 Works in Edit and Oject Modes. 543 544 Args: 545 context: Blender bpy.context instance. 546 547 Returns: 548 Status Set. 549 """ 550 551 scene = context.scene 552 pg = scene.pdt_pg 553 plane = pg.plane 554 flip_angle = pg.flip_angle 555 obj = context.view_layer.objects.active 556 if obj is None: 557 pg.error = PDT_ERR_NO_ACT_OBJ 558 context.window_manager.popup_menu(oops, title="Error", icon="ERROR") 559 return 560 if obj.mode == "EDIT": 561 bm = bmesh.from_edit_mesh(obj.data) 562 verts = [v for v in bm.verts if v.select] 563 if len(verts) == 2: 564 if len(bm.select_history) == 2: 565 vector_a, vector_b = check_selection(2, bm, obj) 566 if vector_a is None: 567 pg.error = PDT_ERR_VERT_MODE 568 context.window_manager.popup_menu(oops, title="Error", icon="ERROR") 569 raise PDT_FeatureError 570 else: 571 pg.error = f"{PDT_ERR_SEL_2_VERTIO} {len(bm.select_history)})" 572 context.window_manager.popup_menu(oops, title="Error", icon="ERROR") 573 raise PDT_SelectionError 574 else: 575 pg.error = f"{PDT_ERR_SEL_2_VERTIO} {len(verts)})" 576 context.window_manager.popup_menu(oops, title="Error", icon="ERROR") 577 raise PDT_SelectionError 578 elif obj.mode == "OBJECT": 579 objs = context.view_layer.objects.selected 580 if len(objs) < 2: 581 pg.error = f"{PDT_ERR_SEL_2_OBJS} {len(objs)})" 582 context.window_manager.popup_menu(oops, title="Error", icon="ERROR") 583 raise PDT_SelectionError 584 objs_s = [ob for ob in objs if ob.name != obj.name] 585 vector_a = obj.matrix_world.decompose()[0] 586 vector_b = objs_s[-1].matrix_world.decompose()[0] 587 if plane == "LO": 588 vector_difference = vector_b - vector_a 589 vector_b = view_coords_i(vector_difference.x, vector_difference.y, vector_difference.z) 590 vector_a = Vector((0, 0, 0)) 591 v0 = np.array([vector_a.x + 1, vector_a.y]) - np.array([vector_a.x, vector_a.y]) 592 v1 = np.array([vector_b.x, vector_b.y]) - np.array([vector_a.x, vector_a.y]) 593 else: 594 a1, a2, _ = set_mode(plane) 595 v0 = np.array([vector_a[a1] + 1, vector_a[a2]]) - np.array([vector_a[a1], vector_a[a2]]) 596 v1 = np.array([vector_b[a1], vector_b[a2]]) - np.array([vector_a[a1], vector_a[a2]]) 597 ang = np.rad2deg(np.arctan2(np.linalg.det([v0, v1]), np.dot(v0, v1))) 598 decimal_places = context.preferences.addons[__package__].preferences.pdt_input_round 599 if flip_angle: 600 if ang > 0: 601 pg.angle = round(ang - 180, decimal_places) 602 else: 603 pg.angle = round(ang - 180, decimal_places) 604 else: 605 pg.angle = round(ang, decimal_places) 606 if plane == "LO": 607 pg.distance = round(sqrt( 608 (vector_a.x - vector_b.x) ** 2 + 609 (vector_a.y - vector_b.y) ** 2), decimal_places) 610 else: 611 pg.distance = round(sqrt( 612 (vector_a[a1] - vector_b[a1]) ** 2 + 613 (vector_a[a2] - vector_b[a2]) ** 2), decimal_places) 614 pg.cartesian_coords = Vector(([round(i, decimal_places) for i in vector_b - vector_a])) 615 616 617def set_angle_distance_three(context): 618 """Measures Angle and Offsets between 3 Points in World Space, Also sets Deltas. 619 620 Note: 621 Uses 3 Selected Vertices to set pg.angle and pg.distance scene variables 622 also sets delta offset from these 3 points using standard Numpy Routines 623 Works in Edit and Oject Modes. 624 625 Args: 626 context: Blender bpy.context instance. 627 628 Returns: 629 Status Set. 630 """ 631 632 pg = context.scene.pdt_pg 633 flip_angle = pg.flip_angle 634 obj = context.view_layer.objects.active 635 if obj is None: 636 pg.error = PDT_ERR_NO_ACT_OBJ 637 context.window_manager.popup_menu(oops, title="Error", icon="ERROR") 638 raise PDT_NoObjectError 639 if obj.mode == "EDIT": 640 bm = bmesh.from_edit_mesh(obj.data) 641 verts = [v for v in bm.verts if v.select] 642 if len(verts) == 3: 643 if len(bm.select_history) == 3: 644 vector_a, vector_b, vector_c = check_selection(3, bm, obj) 645 if vector_a is None: 646 pg.error = PDT_ERR_VERT_MODE 647 context.window_manager.popup_menu(oops, title="Error", icon="ERROR") 648 raise PDT_FeatureError 649 else: 650 pg.error = f"{PDT_ERR_SEL_3_VERTIO} {len(bm.select_history)})" 651 context.window_manager.popup_menu(oops, title="Error", icon="ERROR") 652 raise PDT_SelectionError 653 else: 654 pg.error = f"{PDT_ERR_SEL_3_VERTIO} {len(verts)})" 655 context.window_manager.popup_menu(oops, title="Error", icon="ERROR") 656 raise PDT_SelectionError 657 elif obj.mode == "OBJECT": 658 objs = context.view_layer.objects.selected 659 if len(objs) < 3: 660 pg.error = PDT_ERR_SEL_3_OBJS + str(len(objs)) 661 context.window_manager.popup_menu(oops, title="Error", icon="ERROR") 662 raise PDT_SelectionError 663 objs_s = [ob for ob in objs if ob.name != obj.name] 664 vector_a = obj.matrix_world.decompose()[0] 665 vector_b = objs_s[-1].matrix_world.decompose()[0] 666 vector_c = objs_s[-2].matrix_world.decompose()[0] 667 ba = np.array([vector_b.x, vector_b.y, vector_b.z]) - np.array( 668 [vector_a.x, vector_a.y, vector_a.z] 669 ) 670 bc = np.array([vector_c.x, vector_c.y, vector_c.z]) - np.array( 671 [vector_a.x, vector_a.y, vector_a.z] 672 ) 673 angle_cosine = np.dot(ba, bc) / (np.linalg.norm(ba) * np.linalg.norm(bc)) 674 ang = np.degrees(np.arccos(angle_cosine)) 675 decimal_places = context.preferences.addons[__package__].preferences.pdt_input_round 676 if flip_angle: 677 if ang > 0: 678 pg.angle = round(ang - 180, decimal_places) 679 else: 680 pg.angle = round(ang - 180, decimal_places) 681 else: 682 pg.angle = round(ang, decimal_places) 683 pg.distance = round((vector_a - vector_b).length, decimal_places) 684 pg.cartesian_coords = Vector(([round(i, decimal_places) for i in vector_b - vector_a])) 685 686 687def origin_to_cursor(context): 688 """Sets Object Origin in Edit Mode to Cursor Location. 689 690 Note: 691 Keeps geometry static in World Space whilst moving Object Origin 692 Requires cursor location 693 Works in Edit and Object Modes. 694 695 Args: 696 context: Blender bpy.context instance. 697 698 Returns: 699 Status Set. 700 """ 701 702 scene = context.scene 703 pg = context.scene.pdt_pg 704 obj = context.view_layer.objects.active 705 if obj is None: 706 pg.error = PDT_ERR_NO_ACT_OBJ 707 context.window_manager.popup_menu(oops, title="Error", icon="ERROR") 708 return 709 obj_loc = obj.matrix_world.decompose()[0] 710 cur_loc = scene.cursor.location 711 diff_v = obj_loc - cur_loc 712 if obj.mode == "EDIT": 713 bm = bmesh.from_edit_mesh(obj.data) 714 for v in bm.verts: 715 v.co = v.co + diff_v 716 obj.location = cur_loc 717 bmesh.update_edit_mesh(obj.data) 718 bm.select_history.clear() 719 elif obj.mode == "OBJECT": 720 for v in obj.data.vertices: 721 v.co = v.co + diff_v 722 obj.location = cur_loc 723 else: 724 pg.error = f"{PDT_ERR_EDOB_MODE} {obj.mode})" 725 context.window_manager.popup_menu(oops, title="Error", icon="ERROR") 726 raise PDT_ObjectModeError 727 728 729def taper(context): 730 """Taper Geometry along World Axes. 731 732 Note: 733 Similar to Shear command except that it shears by angle rather than displacement. 734 Rotates about World Axes and displaces along World Axes, angle must not exceed +-80 degrees. 735 Rotation axis is centred on Active Vertex. 736 Works only in Edit mode. 737 738 Args: 739 context: Blender bpy.context instance. 740 741 Note: 742 Uses pg.taper & pg.angle scene variables 743 744 Returns: 745 Status Set. 746 """ 747 748 scene = context.scene 749 pg = scene.pdt_pg 750 tap_ax = pg.taper 751 ang_v = pg.angle 752 obj = context.view_layer.objects.active 753 if all([bool(obj), obj.type == "MESH", obj.mode == "EDIT"]): 754 if ang_v > 80 or ang_v < -80: 755 pg.error = f"{PDT_ERR_TAPER_ANG} {ang_v})" 756 context.window_manager.popup_menu(oops, title="Error", icon="ERROR") 757 raise PDT_InvalidAngle 758 if obj is None: 759 pg.error = PDT_ERR_NO_ACT_OBJ 760 context.window_manager.popup_menu(oops, title="Error", icon="ERROR") 761 raise PDT_NoObjectError 762 _, a2, a3 = set_axis(tap_ax) 763 bm = bmesh.from_edit_mesh(obj.data) 764 if len(bm.select_history) >= 1: 765 rotate_vertex = bm.select_history[-1] 766 view_vector = view_coords(rotate_vertex.co.x, rotate_vertex.co.y, rotate_vertex.co.z) 767 else: 768 pg.error = f"{PDT_ERR_TAPER_SEL} {len(bm.select_history)})" 769 context.window_manager.popup_menu(oops, title="Error", icon="ERROR") 770 raise PDT_SelectionError 771 for v in [v for v in bm.verts if v.select]: 772 if pg.plane == "LO": 773 v_loc = view_coords(v.co.x, v.co.y, v.co.z) 774 dis_v = sqrt((view_vector.x - v_loc.x) ** 2 + (view_vector.y - v_loc.y) ** 2) 775 x_loc = dis_v * tan(ang_v * pi / 180) 776 view_matrix = view_dir(x_loc, 0) 777 v.co = v.co - view_matrix 778 else: 779 dis_v = sqrt( 780 (rotate_vertex.co[a3] - v.co[a3]) ** 2 + (rotate_vertex.co[a2] - v.co[a2]) ** 2 781 ) 782 v.co[a2] = v.co[a2] - (dis_v * tan(ang_v * pi / 180)) 783 bmesh.update_edit_mesh(obj.data) 784 bm.select_history.clear() 785 else: 786 pg.error = f"{PDT_ERR_EDOB_MODE},{obj.mode})" 787 context.window_manager.popup_menu(oops, title="Error", icon="ERROR") 788 raise PDT_ObjectModeError 789