FBX IO: Add support for armature data custom properties
[blender-addons.git] / precision_drawing_tools / pdt_tangent.py
blobe12c60caf24fc2f8a2f3b96ddda5f644116ca38f
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 bpy
7 import bmesh
8 from math import sqrt, floor, asin, sin, cos, pi
9 from mathutils import Vector
10 from bpy.types import Operator
12 from .pdt_functions import (
13 oops,
14 arc_centre,
15 set_mode,
16 view_coords,
17 view_coords_i,
20 from .pdt_msg_strings import (
21 PDT_OBJ_MODE_ERROR,
22 PDT_ERR_NO_ACT_OBJ,
23 PDT_ERR_SEL_3_VERTS,
24 PDT_ERR_SEL_1_VERT,
25 PDT_ERR_BADDISTANCE,
26 PDT_ERR_MATHSERROR,
27 PDT_ERR_SAMERADII,
28 PDT_ERR_VERT_MODE,
31 from . import pdt_exception
33 PDT_ObjectModeError = pdt_exception.ObjectModeError
34 PDT_SelectionError = pdt_exception.SelectionError
37 def get_tangent_intersect_outer(hloc_0, vloc_0, hloc_1, vloc_1, radius_0, radius_1):
38 """Return Location in 2 Dimensions of the Intersect Point for Outer Tangents.
40 Args:
41 hloc_0: Horizontal Coordinate of Centre of First Arc
42 vloc_0: Vertical Coordinate of Centre of First Arc
43 hloc_1: Horizontal Coordinate of Centre of Second Arc
44 vloc_1: Vertical Coordinate of Centre of Second Arc
45 radius_0: Radius of First Arc
46 radius_1: Radius of Second Arc
48 Returns:
49 hloc_p: Horizontal Coordinate of Centre of Intersection
50 vloc_p: Vertical Coordinate of Centre of Intersection.
51 """
53 hloc_p = ((hloc_1 * radius_0) - (hloc_0 * radius_1)) / (radius_0 - radius_1)
54 vloc_p = ((vloc_1 * radius_0) - (vloc_0 * radius_1)) / (radius_0 - radius_1)
56 return hloc_p, vloc_p
59 def get_tangent_intersect_inner(hloc_0, vloc_0, hloc_1, vloc_1, radius_0, radius_1):
60 """Return Location in 2 Dimensions of the Intersect Point for Inner Tangents.
62 Args:
63 hloc_0: Horizontal Coordinate of Centre of First Arc
64 vloc_0: Vertical Coordinate of Centre of First Arc
65 hloc_1: Horizontal Coordinate of Centre of Second Arc
66 vloc_1: Vertical Coordinate of Centre of Second Arc
67 radius_0: Radius of First Arc
68 radius_1: Radius of Second Arc
70 Returns:
71 hloc_p: Horizontal Coordinate of Centre of Intersection
72 vloc_p: Vertical Coordinate of Centre of Intersection.
73 """
75 hloc_p = ((hloc_1 * radius_0) + (hloc_0 * radius_1)) / (radius_0 + radius_1)
76 vloc_p = ((vloc_1 * radius_0) + (vloc_0 * radius_1)) / (radius_0 + radius_1)
78 return hloc_p, vloc_p
81 def get_tangent_points(context, hloc_0, vloc_0, radius_0, hloc_p, vloc_p):
82 """Return Location in 2 Dimensions of the Tangent Points.
84 Args:
85 context: Blender bpy.context instance
86 hloc_0: Horizontal Coordinate of Centre of First Arc
87 vloc_0: Vertical Coordinate of Centre of First Arc
88 radius_0: Radius of First Arc
89 hloc_p: Horizontal Coordinate of Intersection
90 vloc_p: Vertical Coordinate of Intersection
92 Returns:
93 hloc_t1: Horizontal Location of First Tangent Point
94 hloc_t2: Horizontal Location of Second Tangent Point
95 vloc_t1: Vertical Location of First Tangent Point
96 vloc_t2: Vertical Location of Second Tangent Point
97 """
99 # Uses basic Pythagorus' theorem to compute locations
101 numerator = (radius_0 ** 2 * (hloc_p - hloc_0)) + (
102 radius_0
103 * (vloc_p - vloc_0)
104 * sqrt((hloc_p - hloc_0) ** 2 + (vloc_p - vloc_0) ** 2 - radius_0 ** 2)
106 denominator = (hloc_p - hloc_0) ** 2 + (vloc_p - vloc_0) ** 2
107 hloc_t1 = round((numerator / denominator) + hloc_0, 5)
109 numerator = (radius_0 ** 2 * (hloc_p - hloc_0)) - (
110 radius_0
111 * (vloc_p - vloc_0)
112 * sqrt((hloc_p - hloc_0) ** 2 + (vloc_p - vloc_0) ** 2 - radius_0 ** 2)
114 denominator = (hloc_p - hloc_0) ** 2 + (vloc_p - vloc_0) ** 2
115 hloc_t2 = round((numerator / denominator) + hloc_0, 5)
117 # Get Y values
118 numerator = (radius_0 ** 2 * (vloc_p - vloc_0)) - (
119 radius_0
120 * (hloc_p - hloc_0)
121 * sqrt((hloc_p - hloc_0) ** 2 + (vloc_p - vloc_0) ** 2 - radius_0 ** 2)
123 denominator = (hloc_p - hloc_0) ** 2 + (vloc_p - vloc_0) ** 2
124 vloc_t1 = round((numerator / denominator) + vloc_0, 5)
126 numerator = (radius_0 ** 2 * (vloc_p - vloc_0)) + (
127 radius_0
128 * (hloc_p - hloc_0)
129 * sqrt((hloc_p - hloc_0) ** 2 + (vloc_p - vloc_0) ** 2 - radius_0 ** 2)
131 denominator = (hloc_p - hloc_0) ** 2 + (vloc_p - vloc_0) ** 2
132 vloc_t2 = round((numerator / denominator) + vloc_0, 5)
134 return hloc_t1, hloc_t2, vloc_t1, vloc_t2
137 def make_vectors(coords, a1, a2, a3, pg):
138 """Return Vectors of the Tangent Points.
140 Args:
141 coords: A List of Coordinates in 2D space of the tangent points
142 & a third dimension for the vectors
143 a1: Index of horizontal axis
144 a2: Index of vertical axis
145 a3: Index of depth axis
146 pg: PDT Parameters Group - our variables
148 Returns:
149 tangent_vector_o1: Location of First Tangent Point
150 tangent_vector_o2: Location of Second Tangent Point
151 tangent_vector_o3: Location of First Tangent Point
152 tangent_vector_o4: Location of Second Tangent Point
155 tangent_vector_o1 = Vector((0, 0, 0))
156 tangent_vector_o1[a1] = coords[0]
157 tangent_vector_o1[a2] = coords[1]
158 tangent_vector_o1[a3] = coords[8]
159 tangent_vector_o2 = Vector((0, 0, 0))
160 tangent_vector_o2[a1] = coords[2]
161 tangent_vector_o2[a2] = coords[3]
162 tangent_vector_o2[a3] = coords[8]
163 tangent_vector_o3 = Vector((0, 0, 0))
164 tangent_vector_o3[a1] = coords[4]
165 tangent_vector_o3[a2] = coords[5]
166 tangent_vector_o3[a3] = coords[8]
167 tangent_vector_o4 = Vector((0, 0, 0))
168 tangent_vector_o4[a1] = coords[6]
169 tangent_vector_o4[a2] = coords[7]
170 tangent_vector_o4[a3] = coords[8]
172 if pg.plane == "LO":
173 # Reset coordinates from view local (Horiz, Vert, depth) to World XYZ.
175 tangent_vector_o1 = view_coords(
176 tangent_vector_o1[a1], tangent_vector_o1[a2], tangent_vector_o1[a3]
178 tangent_vector_o2 = view_coords(
179 tangent_vector_o2[a1], tangent_vector_o2[a2], tangent_vector_o2[a3]
181 tangent_vector_o3 = view_coords(
182 tangent_vector_o3[a1], tangent_vector_o3[a2], tangent_vector_o3[a3]
184 tangent_vector_o4 = view_coords(
185 tangent_vector_o4[a1], tangent_vector_o4[a2], tangent_vector_o4[a3]
188 return (tangent_vector_o1, tangent_vector_o2, tangent_vector_o3, tangent_vector_o4)
191 def tangent_setup(context, pg, plane, obj_data, centre_0, centre_1, centre_2, radius_0, radius_1):
192 """This section sets up all the variables required for the tangent functions.
194 Args:
195 context: Blender bpy.context instance
196 pg: PDT Parameter Group of variables
197 plane: Working plane
198 obj_data: All the data of the chosen object
199 centre_0: Centre coordinates of the first arc
200 centre_1: Centre coordinates of the second arc
201 centre_2: Coordinates of the point
202 radius_0: Radius of the first Arc
203 radius_1: Radius of the second Arc
205 Returns:
206 Status Set.
209 a1, a2, a3 = set_mode(plane)
210 mode = pg.tangent_mode
211 if plane == "LO":
212 # Translate world coordinates into view local (horiz, vert, depth)
214 centre_0 = view_coords_i(centre_0[a1], centre_0[a2], centre_0[a3])
215 centre_1 = view_coords_i(centre_1[a1], centre_1[a2], centre_1[a3])
216 centre_2 = view_coords_i(centre_2[a1], centre_2[a2], centre_2[a3])
217 if pg.tangent_mode == "point":
218 vector_difference = centre_2 - centre_0
219 distance = sqrt(vector_difference[a1] ** 2 + vector_difference[a2] ** 2)
220 else:
221 vector_difference = centre_1 - centre_0
222 distance = sqrt(vector_difference[a1] ** 2 + vector_difference[a2] ** 2)
224 if (
225 (distance <= radius_0 and mode in {"point"}) or
226 (distance <= (radius_0 + radius_1) and mode in {"inner", "both"}) or
227 (distance <= radius_0 or distance <= radius_1 and mode in {"outer", "both"})
229 # Cannot execute, centres are too close.
231 pg.error = f"{PDT_ERR_BADDISTANCE}"
232 context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
233 return {"FINISHED"}
235 """This next section will draw Point based Tangents.
237 These are drawn from a point to an Arc
240 if mode == "point":
241 if (
242 (centre_2[a1] - centre_0[a1]) ** 2 + (centre_2[a2] - centre_0[a2]) ** 2 - radius_0 ** 2
243 ) > 0:
244 hloc_to1, hloc_to2, vloc_to1, vloc_to2 = get_tangent_points(
245 context, centre_0[a1], centre_0[a2], radius_0, centre_2[a1], centre_2[a2]
247 else:
248 pg.error = PDT_ERR_MATHSERROR
249 context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
250 return {"FINISHED"}
251 # Point Tangents
253 tangent_vector_o1 = Vector((0, 0, 0))
254 tangent_vector_o1[a1] = hloc_to1
255 tangent_vector_o1[a2] = vloc_to1
256 tangent_vector_o1[a3] = centre_2[a3]
257 tangent_vector_o2 = Vector((0, 0, 0))
258 tangent_vector_o2[a1] = hloc_to2
259 tangent_vector_o2[a2] = vloc_to2
260 tangent_vector_o2[a3] = centre_2[a3]
261 if pg.plane == "LO":
262 # Translate view local coordinates (horiz, vert, depth) into World XYZ
264 centre_2 = view_coords(centre_2[a1], centre_2[a2], centre_2[a3])
265 tangent_vector_o1 = view_coords(
266 tangent_vector_o1[a1], tangent_vector_o1[a2], tangent_vector_o1[a3]
268 tangent_vector_o2 = view_coords(
269 tangent_vector_o2[a1], tangent_vector_o2[a2], tangent_vector_o2[a3]
271 tangent_vectors = (centre_2, tangent_vector_o1, tangent_vector_o2)
272 draw_tangents(tangent_vectors, obj_data)
274 return {"FINISHED"}
276 """This next section will draw Arc based Outer Tangents.
278 These are drawn from an Arc to another Arc
281 if mode in {"outer", "both"}:
282 # Uses basic trigonometry and Pythagorus' theorem to compute locations
284 if radius_0 == radius_1:
285 # No intersection point for outer tangents
287 sin_angle = (centre_1[a2] - centre_0[a2]) / distance
288 cos_angle = (centre_1[a1] - centre_0[a1]) / distance
289 hloc_to1 = centre_0[a1] + (radius_0 * sin_angle)
290 hloc_to2 = centre_0[a1] - (radius_0 * sin_angle)
291 hloc_to3 = centre_1[a1] + (radius_0 * sin_angle)
292 hloc_to4 = centre_1[a1] - (radius_0 * sin_angle)
293 vloc_to1 = centre_0[a2] - (radius_0 * cos_angle)
294 vloc_to2 = centre_0[a2] + (radius_0 * cos_angle)
295 vloc_to3 = centre_1[a2] - (radius_0 * cos_angle)
296 vloc_to4 = centre_1[a2] + (radius_0 * cos_angle)
297 else:
298 hloc_po, vloc_po = get_tangent_intersect_outer(
299 centre_0[a1], centre_0[a2], centre_1[a1], centre_1[a2], radius_0, radius_1
302 if ((hloc_po - centre_0[a1]) ** 2 + (vloc_po - centre_0[a2]) ** 2 - radius_0 ** 2) > 0:
303 hloc_to1, hloc_to2, vloc_to1, vloc_to2 = get_tangent_points(
304 context, centre_0[a1], centre_0[a2], radius_0, hloc_po, vloc_po
306 else:
307 pg.error = PDT_ERR_MATHSERROR
308 context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
309 return {"FINISHED"}
310 if ((hloc_po - centre_0[a1]) ** 2 + (vloc_po - centre_0[a2]) ** 2 - radius_1 ** 2) > 0:
311 hloc_to3, hloc_to4, vloc_to3, vloc_to4 = get_tangent_points(
312 context, centre_1[a1], centre_1[a2], radius_1, hloc_po, vloc_po
314 else:
315 pg.error = PDT_ERR_MATHSERROR
316 context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
317 return {"FINISHED"}
319 dloc_p = centre_0[a3]
320 coords_in = (
321 hloc_to1,
322 vloc_to1,
323 hloc_to2,
324 vloc_to2,
325 hloc_to3,
326 vloc_to3,
327 hloc_to4,
328 vloc_to4,
329 dloc_p,
331 tangent_vectors = make_vectors(coords_in, a1, a2, a3, pg)
332 draw_tangents(tangent_vectors, obj_data)
334 """This next section will draw Arc based Inner Tangents.
336 These are drawn from an Arc to another Arc
339 if mode in {"inner", "both"}:
340 # Uses basic trigonometry and Pythagorus' theorem to compute locations
342 hloc_pi, vloc_pi = get_tangent_intersect_inner(
343 centre_0[a1], centre_0[a2], centre_1[a1], centre_1[a2], radius_0, radius_1
345 if ((hloc_pi - centre_0[a1]) ** 2 + (vloc_pi - centre_0[a2]) ** 2 - radius_0 ** 2) > 0:
346 hloc_to1, hloc_to2, vloc_to1, vloc_to2 = get_tangent_points(
347 context, centre_0[a1], centre_0[a2], radius_0, hloc_pi, vloc_pi
349 else:
350 pg.error = PDT_ERR_MATHSERROR
351 context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
352 return {"FINISHED"}
353 if ((hloc_pi - centre_0[a1]) ** 2 + (vloc_pi - centre_0[a2]) ** 2 - radius_0 ** 2) > 0:
354 hloc_to3, hloc_to4, vloc_to3, vloc_to4 = get_tangent_points(
355 context, centre_1[a1], centre_1[a2], radius_1, hloc_pi, vloc_pi
357 else:
358 pg.error = PDT_ERR_MATHSERROR
359 context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
360 return {"FINISHED"}
362 dloc_p = centre_0[a3]
363 coords_in = (
364 hloc_to1,
365 vloc_to1,
366 hloc_to2,
367 vloc_to2,
368 hloc_to3,
369 vloc_to3,
370 hloc_to4,
371 vloc_to4,
372 dloc_p,
374 tangent_vectors = make_vectors(coords_in, a1, a2, a3, pg)
375 draw_tangents(tangent_vectors, obj_data)
377 return {"FINISHED"}
380 def draw_tangents(tangent_vectors, obj_data):
381 """Add Edges Representing the Tangents.
383 Note:
384 The length of the tanget_vectors determines which tangents will be
385 drawn, 3 gives Point Tangents, 4 gives Inner/Outer tangents
387 Args:
388 tangent_vectors: A list of vectors representing the tangents
389 obj_data: A list giving Object, Object Location and Object Bmesh
391 Returns:
392 Nothing.
394 obj = obj_data[0]
395 obj_loc = obj_data[1]
396 bm = obj_data[2]
397 if len(tangent_vectors) == 3:
398 point_vertex_outer = bm.verts.new(tangent_vectors[0] - obj_loc)
399 tangent_vertex_o1 = bm.verts.new(tangent_vectors[1] - obj_loc)
400 tangent_vertex_o2 = bm.verts.new(tangent_vectors[2] - obj_loc)
401 bm.edges.new([tangent_vertex_o1, point_vertex_outer])
402 bm.edges.new([tangent_vertex_o2, point_vertex_outer])
403 else:
404 tangent_vertex_o1 = bm.verts.new(tangent_vectors[0] - obj_loc)
405 tangent_vertex_o2 = bm.verts.new(tangent_vectors[2] - obj_loc)
406 tangent_vertex_o3 = bm.verts.new(tangent_vectors[1] - obj_loc)
407 tangent_vertex_o4 = bm.verts.new(tangent_vectors[3] - obj_loc)
408 bm.edges.new([tangent_vertex_o1, tangent_vertex_o2])
409 bm.edges.new([tangent_vertex_o3, tangent_vertex_o4])
410 bmesh.update_edit_mesh(obj.data)
413 def analyse_arc(context, pg):
414 """Analyses an Arc inferred from Selected Vertices.
416 Note:
417 Will work if more than 3 vertices are selected, taking the
418 first, the nearest to the middle and the last.
420 Args:
421 context: Blender bpy.context instance
422 pg: PDT Parameters Group - our variables
424 Returns:
425 vector_delta: Location of Arc Centre
426 radius: Radius of Arc.
428 obj = context.view_layer.objects.active
429 if obj is None:
430 pg.error = PDT_ERR_NO_ACT_OBJ
431 context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
432 raise PDT_ObjectModeError
433 if obj.mode == "EDIT":
434 obj_loc = obj.matrix_world.decompose()[0]
435 bm = bmesh.from_edit_mesh(obj.data)
436 verts = [v for v in bm.verts if v.select]
437 if len(verts) < 3:
438 pg.error = f"{PDT_ERR_SEL_3_VERTS} {len(verts)})"
439 context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
440 raise PDT_SelectionError
441 vector_a = verts[0].co
442 # Get the nearest to middle vertex of the arc
444 vector_b = verts[int(floor(len(verts) / 2))].co
445 vector_c = verts[-1].co
446 vector_delta, radius = arc_centre(vector_a, vector_b, vector_c)
448 return vector_delta, radius
451 class PDT_OT_TangentOperate(Operator):
452 """Calculate Tangents from Inputs."""
454 bl_idname = "pdt.tangentoperate"
455 bl_label = "Calculate Tangents"
456 bl_options = {"REGISTER", "UNDO"}
457 bl_description = "Calculate Tangents to Arcs from Points or Other Arcs"
459 @classmethod
460 def poll(cls, context):
461 ob = context.object
462 if ob is None:
463 return False
464 return all([bool(ob), ob.type == "MESH", ob.mode == "EDIT"])
466 def execute(self, context):
467 """Calculate Tangents from Inputs.
469 Note:
470 Uses pg.plane, pg.tangent_point0, pg.tangent_radius0, pg.tangent_point1
471 pg.tangent_radius1, pg.tangent_point2 to place tangents.
473 Analyses distance between arc centres, or arc centre and tangent point
474 to determine which mode is possible (Inner, Outer, or Point). If centres are
475 both contained within 1 inferred circle, Inner tangents are not possible.
477 Arcs of same radius will have no intersection for outer tangents so these
478 are calculated differently.
480 Args:
481 context: Blender bpy.context instance.
483 Returns:
484 Nothing.
487 scene = context.scene
488 pg = scene.pdt_pg
489 plane = pg.plane
490 # Get Object
491 obj = context.view_layer.objects.active
492 if obj is not None:
493 if obj.mode not in {"EDIT"} or obj.type != "MESH":
494 pg.error = PDT_OBJ_MODE_ERROR
495 context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
496 return {"FINISHED"}
497 else:
498 pg.error = PDT_ERR_NO_ACT_OBJ
499 context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
500 return {"FINISHED"}
501 bm = bmesh.from_edit_mesh(obj.data)
502 obj_loc = obj.matrix_world.decompose()[0]
503 obj_data = (obj, obj_loc, bm)
505 radius_0 = pg.tangent_radius0
506 radius_1 = pg.tangent_radius1
507 centre_0 = pg.tangent_point0
508 centre_1 = pg.tangent_point1
509 centre_2 = pg.tangent_point2
511 tangent_setup(
512 context, pg, plane, obj_data, centre_0, centre_1, centre_2, radius_0, radius_1
515 return {"FINISHED"}
518 class PDT_OT_TangentOperateSel(Operator):
519 """Calculate Tangents from Selection."""
521 bl_idname = "pdt.tangentoperatesel"
522 bl_label = "Calculate Tangents"
523 bl_options = {"REGISTER", "UNDO"}
524 bl_description = "Calculate Tangents to Arcs from 2 Selected Vertices, or 1 & Point in Menu"
526 @classmethod
527 def poll(cls, context):
528 ob = context.object
529 if ob is None:
530 return False
531 return all([bool(ob), ob.type == "MESH", ob.mode == "EDIT"])
533 def execute(self, context):
534 """Calculate Tangents from Selection.
536 Note:
537 Uses pg.plane & 2 or more selected Vertices to place tangents.
538 One vertex must be on each arc.
540 Analyses distance between arc centres, or arc centre and tangent point
541 to determine which mode is possible (Inner, Outer, or Point). If centres are
542 both contained within 1 inferred circle, Inner tangents are not possible.
544 Arcs of same radius will have no intersection for outer tangents so these
545 are calculated differently.
547 Args:
548 context: Blender bpy.context instance.
550 Returns:
551 Nothing.
554 scene = context.scene
555 pg = scene.pdt_pg
556 plane = pg.plane
557 # Get Object
558 obj = context.view_layer.objects.active
559 if obj is not None:
560 if obj.mode not in {"EDIT"} or obj.type != "MESH":
561 pg.error = PDT_OBJ_MODE_ERROR
562 context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
563 return {"FINISHED"}
564 else:
565 pg.error = PDT_ERR_NO_ACT_OBJ
566 context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
567 return {"FINISHED"}
568 bm = bmesh.from_edit_mesh(obj.data)
569 obj_loc = obj.matrix_world.decompose()[0]
570 obj_data = (obj, obj_loc, bm)
572 # Get All Values from Selected Vertices
573 verts = [v for v in bm.verts if v.select]
574 if len(verts) <= 0:
575 pg.error = f"{PDT_ERR_SEL_1_VERT} 0"
576 context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
577 return {"FINISHED"}
578 v1 = verts[0]
579 vn = verts[-1]
580 for v in bm.verts:
581 v.select_set(False)
582 for e in bm.edges:
583 e.select_set(False)
584 v1.select_set(True)
585 bpy.ops.mesh.select_linked()
586 verts1 = [v for v in bm.verts if v.select].copy()
587 if len(verts1) < 3:
588 pg.error = f"{PDT_ERR_VERT_MODE} or Less than 3 vertices in your Arc(s)"
589 context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
590 return {"FINISHED"}
591 for v in bm.verts:
592 v.select_set(False)
593 for e in bm.edges:
594 e.select_set(False)
595 vn.select_set(True)
596 bpy.ops.mesh.select_linked()
597 vertsn = [v for v in bm.verts if v.select].copy()
598 for v in bm.verts:
599 v.select_set(False)
600 for e in bm.edges:
601 e.select_set(False)
602 bmesh.update_edit_mesh(obj.data)
603 bm.select_history.clear()
604 # Select the nearest to middle vertex in the arc
606 verts1 = [verts1[0].co, verts1[int(floor(len(verts1) / 2))].co, verts1[-1].co]
607 vertsn = [vertsn[0].co, vertsn[int(floor(len(vertsn) / 2))].co, vertsn[-1].co]
608 centre_0, radius_0 = arc_centre(verts1[0], verts1[1], verts1[2])
609 centre_1, radius_1 = arc_centre(vertsn[0], vertsn[1], vertsn[2])
610 centre_2 = pg.tangent_point2
612 tangent_setup(
613 context, pg, plane, obj_data, centre_0, centre_1, centre_2, radius_0, radius_1
616 return {"FINISHED"}
619 class PDT_OT_TangentSet1(Operator):
620 """Calculates Centres & Radii from 3 Vectors."""
622 bl_idname = "pdt.tangentset1"
623 bl_label = "Calculate Centres & Radii"
624 bl_options = {"REGISTER", "UNDO"}
625 bl_description = "Calculate Centres & Radii from Selected Vertices"
627 @classmethod
628 def poll(cls, context):
629 ob = context.object
630 if ob is None:
631 return False
632 return all([bool(ob), ob.type == "MESH", ob.mode == "EDIT"])
634 def execute(self, context):
635 """Sets Input Tangent Point 1 to analysis of Arc.
637 Args:
638 context: Blender bpy.context instance.
640 Returns:
641 Nothing.
643 scene = context.scene
644 pg = scene.pdt_pg
645 vector_delta, radius = analyse_arc(context, pg)
646 pg.tangent_point0 = vector_delta
647 pg.tangent_radius0 = radius
648 return {"FINISHED"}
651 class PDT_OT_TangentSet2(Operator):
652 """Calculates Centres & Radii from 3 Vectors."""
654 bl_idname = "pdt.tangentset2"
655 bl_label = "Calculate Centres & Radii"
656 bl_options = {"REGISTER", "UNDO"}
657 bl_description = "Calculate Centres & Radii from Selected Vertices"
659 @classmethod
660 def poll(cls, context):
661 obj = context.object
662 if obj is None:
663 return False
664 return all([bool(obj), obj.type == "MESH", obj.mode == "EDIT"])
666 def execute(self, context):
667 """Sets Input Tangent Point 2 to analysis of Arc.
669 Args:
670 context: Blender bpy.context instance.
672 Returns:
673 Nothing.
675 scene = context.scene
676 pg = scene.pdt_pg
677 vector_delta, radius = analyse_arc(context, pg)
678 pg.tangent_point1 = vector_delta
679 pg.tangent_radius1 = radius
680 return {"FINISHED"}
683 class PDT_OT_TangentSet3(Operator):
684 """Set Tangent Origin Point from Cursor."""
686 bl_idname = "pdt.tangentset3"
687 bl_label = "Set Tangent Origin Point from Cursor"
688 bl_options = {"REGISTER", "UNDO"}
689 bl_description = "Set Tangent Origin Point from Cursor"
691 @classmethod
692 def poll(cls, context):
693 obj = context.object
694 if obj is None:
695 return False
696 return all([bool(obj), obj.type == "MESH", obj.mode == "EDIT"])
698 def execute(self, context):
699 """Sets Input Tangent Point 3 to analysis of Arc.
701 Args:
702 context: Blender bpy.context instance.
704 Returns:
705 Nothing.
707 scene = context.scene
708 pg = scene.pdt_pg
709 pg.tangent_point2 = scene.cursor.location
710 return {"FINISHED"}
713 class PDT_OT_TangentSet4(Operator):
714 """Set Tangent Origin Point from Cursor."""
716 bl_idname = "pdt.tangentset4"
717 bl_label = "Set Tangent Origin Point from Vertex"
718 bl_options = {"REGISTER", "UNDO"}
719 bl_description = "Set Tangent Origin Point from Vertex"
721 @classmethod
722 def poll(cls, context):
723 obj = context.object
724 if obj is None:
725 return False
726 return all([bool(obj), obj.type == "MESH", obj.mode == "EDIT"])
728 def execute(self, context):
729 """Sets Input Tangent Point 2 to selected Vertex.
731 Args:
732 context: Blender bpy.context instance.
734 Returns:
735 Nothing.
737 scene = context.scene
738 pg = scene.pdt_pg
739 obj = context.object
740 bm = bmesh.from_edit_mesh(obj.data)
741 verts = [v for v in bm.verts if v.select]
742 if len(verts) != 1:
743 pg.error = f"{PDT_ERR_SEL_1_VERT} {len(verts)})"
744 context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
745 raise PDT_SelectionError
746 pg.tangent_point2 = verts[0].co
747 return {"FINISHED"}
750 class PDT_OT_TangentExpandMenu(Operator):
751 """Expand/Collapse Tangent Menu."""
753 bl_idname = "pdt.tangentexpandmenu"
754 bl_label = "Expand/Collapse Tangent Menu"
755 bl_options = {"REGISTER", "UNDO"}
756 bl_description = "Expand/Collapse Tangent Menu to Show/Hide Input Options"
758 def execute(self, context):
759 """Expand/Collapse Tangent Menu.
761 Note:
762 This is used to add further options to the menu.
764 Args:
765 context: Blender bpy.context instance.
767 Returns:
768 Nothing.
770 scene = context.scene
771 pg = scene.pdt_pg
772 if pg.menu_expand:
773 pg.menu_expand = False
774 else:
775 pg.menu_expand = True
776 return {"FINISHED"}