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