1 # SPDX-License-Identifier: GPL-2.0-or-later
3 # -----------------------------------------------------------------------
4 # Author: Alan Odom (Clockmender), Rune Morling (ermo) Copyright (c) 2019
5 # -----------------------------------------------------------------------
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 (
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 (
68 def vector_build(context
, pg
, obj
, operation
, values
, num_values
):
69 """Build Movement Vector from Input Fields.
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
80 Vector to position, or offset, items.
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
)
99 pg
.error
= PDT_ERR_BAD3VALS
100 elif num_values
== 2:
101 pg
.error
= PDT_ERR_BAD2VALS
103 pg
.error
= PDT_ERR_BAD1VALS
104 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
105 raise PDT_InvalidVector
109 def placement_normal(context
, operation
):
110 """Manipulates Geometry, or Objects by Normal Intersection between 3 points.
113 context: Blender bpy.context instance.
114 operation: The Operation e.g. Create New Vertex
120 scene
= context
.scene
122 extend_all
= pg
.extend
123 obj
= context
.view_layer
.objects
.active
125 if obj
.mode
== "EDIT":
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
)
135 pg
.error
= PDT_ERR_VERT_MODE
136 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
137 raise PDT_FeatureError
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
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]
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":
166 for v
in [v
for v
in bm
.verts
if v
.select
]:
168 bm
.select_history
.clear()
169 bmesh
.ops
.remove_doubles(bm
, verts
=[v
for v
in bm
.verts
if v
.select
], dist
=0.0001)
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
]:
183 vertex_new
.select_set(True)
185 pg
.error
= f
"{PDT_ERR_EDIT_MODE} {obj.mode})"
186 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
188 elif operation
== "V" and obj
.mode
== "EDIT":
189 vector_new
= vector_delta
190 vertex_new
= bm
.verts
.new(vector_new
)
192 for v
in [v
for v
in bm
.verts
if v
.select
]:
193 bm
.edges
.new([v
, vertex_new
])
195 bm
.edges
.new([bm
.select_history
[-1], vertex_new
])
196 for v
in [v
for v
in bm
.verts
if v
.select
]:
198 vertex_new
.select_set(True)
199 bmesh
.update_edit_mesh(obj
.data
)
200 bm
.select_history
.clear()
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.
210 context: Blender bpy.context instance.
211 operation: The Operation e.g. Create New Vertex
217 scene
= context
.scene
219 extend_all
= pg
.extend
220 obj
= context
.view_layer
.objects
.active
222 if obj
.mode
== "EDIT":
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
]
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")
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
]:
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":
259 for v
in [v
for v
in bm
.verts
if v
.select
]:
261 bm
.select_history
.clear()
262 bmesh
.ops
.remove_doubles(bm
, verts
=[v
for v
in bm
.verts
if v
.select
], dist
=0.0001)
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
)
270 for v
in [v
for v
in bm
.verts
if v
.select
]:
271 bm
.edges
.new([v
, vertex_new
])
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
)
278 bm
.edges
.new([bm
.select_history
[-1], vertex_new
])
279 bmesh
.update_edit_mesh(obj
.data
)
280 bm
.select_history
.clear()
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
)
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
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.
309 context: Blender bpy.context instance.
310 operation: The Operation e.g. Create New Vertex
316 scene
= context
.scene
319 obj
= context
.view_layer
.objects
.active
320 if obj
.mode
== "EDIT":
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
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]
336 if len(bm
.select_history
) != 4:
339 + str(len(bm
.select_history
))
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
)
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
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
]:
370 vertex_new
.select_set(True)
371 bmesh
.update_edit_mesh(obj
.data
)
372 bm
.select_history
.clear()
373 elif operation
in {"G", "V"}:
377 if (vertex_a
.co
- vector_delta
).length
< (vertex_b
.co
- vector_delta
).length
:
379 vertex_a
.co
= vector_delta
382 vertex_new
= bm
.verts
.new(vector_delta
)
383 bm
.edges
.new([vertex_a
, vertex_new
])
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
])
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
])
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
])
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
)
423 if vertex_new
is not None:
424 vertex_new
.select_set(True)
425 for v
in bm
.select_history
:
428 bmesh
.update_edit_mesh(obj
.data
)
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
)
442 "Original Object Order (1,2,3,4) was: "
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
)
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
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")
471 pg
.error
= f
"{operation} {PDT_ERR_NON_VALID} {PDT_LAB_INTERSECT}"
472 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
478 def join_two_vertices(context
):
479 """Joins 2 Free Vertices that do not form part of a Face.
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.
487 context: Blender bpy.context instance.
493 scene
= context
.scene
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
]
501 bm
.edges
.new([verts
[-1], verts
[-2]])
502 bmesh
.update_edit_mesh(obj
.data
)
503 bm
.select_history
.clear()
506 pg
.error
= PDT_ERR_CONNECTED
507 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
508 raise PDT_VerticesConnected
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
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.
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.
528 context: Blender bpy.context instance.
534 scene
= context
.scene
537 flip_angle
= pg
.flip_angle
538 obj
= context
.view_layer
.objects
.active
540 pg
.error
= PDT_ERR_NO_ACT_OBJ
541 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
543 if obj
.mode
== "EDIT":
544 bm
= bmesh
.from_edit_mesh(obj
.data
)
545 verts
= [v
for v
in bm
.verts
if v
.select
]
547 if len(bm
.select_history
) == 2:
548 vector_a
, vector_b
= check_selection(2, bm
, obj
)
550 pg
.error
= PDT_ERR_VERT_MODE
551 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
552 raise PDT_FeatureError
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
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
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]
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
])
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
584 pg
.angle
= round(ang
- 180, decimal_places
)
586 pg
.angle
= round(ang
- 180, decimal_places
)
588 pg
.angle
= round(ang
, decimal_places
)
590 pg
.distance
= round(sqrt(
591 (vector_a
.x
- vector_b
.x
) ** 2 +
592 (vector_a
.y
- vector_b
.y
) ** 2), decimal_places
)
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.
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.
609 context: Blender bpy.context instance.
615 pg
= context
.scene
.pdt_pg
616 flip_angle
= pg
.flip_angle
617 obj
= context
.view_layer
.objects
.active
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
]
626 if len(bm
.select_history
) == 3:
627 vector_a
, vector_b
, vector_c
= check_selection(3, bm
, obj
)
629 pg
.error
= PDT_ERR_VERT_MODE
630 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
631 raise PDT_FeatureError
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
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
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
661 pg
.angle
= round(ang
- 180, decimal_places
)
663 pg
.angle
= round(ang
- 180, decimal_places
)
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.
674 Keeps geometry static in World Space whilst moving Object Origin
675 Requires cursor location
676 Works in Edit and Object Modes.
679 context: Blender bpy.context instance.
685 scene
= context
.scene
686 pg
= context
.scene
.pdt_pg
687 obj
= context
.view_layer
.objects
.active
689 pg
.error
= PDT_ERR_NO_ACT_OBJ
690 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
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
)
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
:
705 obj
.location
= cur_loc
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
713 """Taper Geometry along World Axes.
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.
722 context: Blender bpy.context instance.
725 Uses pg.taper & pg.angle scene variables
731 scene
= context
.scene
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
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
)
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
]:
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
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()
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