Cleanup: minor wording clarification for OBJ import
[blender-addons.git] / precision_drawing_tools / pdt_command_functions.py
blobe499a71a799b0b2a2e0593d84c9f100c25669682
1 # ***** BEGIN GPL LICENSE BLOCK *****
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.
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.
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.
18 # ***** END GPL LICENCE BLOCK *****
20 # -----------------------------------------------------------------------
21 # Author: Alan Odom (Clockmender), Rune Morling (ermo) Copyright (c) 2019
22 # -----------------------------------------------------------------------
24 import bmesh
25 import numpy as np
26 from math import sqrt, tan, pi
27 from mathutils import Vector
28 from mathutils.geometry import intersect_point_line
29 from .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,
43 from . import pdt_exception
44 PDT_SelectionError = pdt_exception.SelectionError
45 PDT_InvalidVector = pdt_exception.InvalidVector
46 PDT_ObjectModeError = pdt_exception.ObjectModeError
47 PDT_InfRadius = pdt_exception.InfRadius
48 PDT_NoObjectError = pdt_exception.NoObjectError
49 PDT_IntersectionError = pdt_exception.IntersectionError
50 PDT_InvalidOperation = pdt_exception.InvalidOperation
51 PDT_VerticesConnected = pdt_exception.VerticesConnected
52 PDT_InvalidAngle = pdt_exception.InvalidAngle
54 from .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,
85 def vector_build(context, pg, obj, operation, values, num_values):
86 """Build Movement Vector from Input Fields.
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
96 Returns:
97 Vector to position, or offset, items.
98 """
100 scene = context.scene
101 plane = pg.plane
102 flip_angle = pg.flip_angle
103 flip_percent= pg.flip_percent
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
126 def placement_normal(context, operation):
127 """Manipulates Geometry, or Objects by Normal Intersection between 3 points.
129 Args:
130 context: Blender bpy.context instance.
131 operation: The Operation e.g. Create New Vertex
133 Returns:
134 Status Set.
137 scene = context.scene
138 pg = scene.pdt_pg
139 extend_all = pg.extend
140 obj = context.view_layer.objects.active
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")
223 def placement_arc_centre(context, operation):
224 """Manipulates Geometry, or Objects to an Arc Centre defined by 3 points on an Imaginary Arc.
226 Args:
227 context: Blender bpy.context instance.
228 operation: The Operation e.g. Create New Vertex
230 Returns:
231 Status Set.
234 scene = context.scene
235 pg = scene.pdt_pg
236 extend_all = pg.extend
237 obj = context.view_layer.objects.active
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")
322 def placement_intersect(context, operation):
323 """Manipulates Geometry, or Objects by Convergance Intersection between 4 points, or 2 Edges.
325 Args:
326 context: Blender bpy.context instance.
327 operation: The Operation e.g. Create New Vertex
329 Returns:
330 Status Set.
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
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)"
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]
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
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
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
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)
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)
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
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
468 context.window_manager.popup_menu(oops, title="Info", icon="INFO")
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
495 def join_two_vertices(context):
496 """Joins 2 Free Vertices that do not form part of a Face.
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.
503 Args:
504 context: Blender bpy.context instance.
506 Returns:
507 Status Set.
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
536 def set_angle_distance_two(context):
537 """Measures Angle and Offsets between 2 Points in View Plane.
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.
544 Args:
545 context: Blender bpy.context instance.
547 Returns:
548 Status Set.
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]))
617 def set_angle_distance_three(context):
618 """Measures Angle and Offsets between 3 Points in World Space, Also sets Deltas.
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.
625 Args:
626 context: Blender bpy.context instance.
628 Returns:
629 Status Set.
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]
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]
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]))
687 def origin_to_cursor(context):
688 """Sets Object Origin in Edit Mode to Cursor Location.
690 Note:
691 Keeps geometry static in World Space whilst moving Object Origin
692 Requires cursor location
693 Works in Edit and Object Modes.
695 Args:
696 context: Blender bpy.context instance.
698 Returns:
699 Status Set.
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
729 def taper(context):
730 """Taper Geometry along World Axes.
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.
738 Args:
739 context: Blender bpy.context instance.
741 Note:
742 Uses pg.taper & pg.angle scene variables
744 Returns:
745 Status Set.
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
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