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