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
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 (
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 (
67 def vector_build(context
, pg
, obj
, operation
, values
, num_values
):
68 """Build Movement Vector from Input Fields.
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
79 Vector to position, or offset, items.
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
)
98 pg
.error
= PDT_ERR_BAD3VALS
100 pg
.error
= PDT_ERR_BAD2VALS
102 pg
.error
= PDT_ERR_BAD1VALS
103 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
104 raise PDT_InvalidVector
108 def placement_normal(context
, operation
):
109 """Manipulates Geometry, or Objects by Normal Intersection between 3 points.
112 context: Blender bpy.context instance.
113 operation: The Operation e.g. Create New Vertex
119 scene
= context
.scene
121 extend_all
= pg
.extend
122 obj
= context
.view_layer
.objects
.active
124 if obj
.mode
== "EDIT":
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
)
134 pg
.error
= PDT_ERR_VERT_MODE
135 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
136 raise PDT_FeatureError
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
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]
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":
165 for v
in [v
for v
in bm
.verts
if v
.select
]:
167 bm
.select_history
.clear()
168 bmesh
.ops
.remove_doubles(bm
, verts
=[v
for v
in bm
.verts
if v
.select
], dist
=0.0001)
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
]:
182 vertex_new
.select_set(True)
184 pg
.error
= f
"{PDT_ERR_EDIT_MODE} {obj.mode})"
185 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
187 elif operation
== "V" and obj
.mode
== "EDIT":
188 vector_new
= vector_delta
189 vertex_new
= bm
.verts
.new(vector_new
)
191 for v
in [v
for v
in bm
.verts
if v
.select
]:
192 bm
.edges
.new([v
, vertex_new
])
194 bm
.edges
.new([bm
.select_history
[-1], vertex_new
])
195 for v
in [v
for v
in bm
.verts
if v
.select
]:
197 vertex_new
.select_set(True)
198 bmesh
.update_edit_mesh(obj
.data
)
199 bm
.select_history
.clear()
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.
209 context: Blender bpy.context instance.
210 operation: The Operation e.g. Create New Vertex
216 scene
= context
.scene
218 extend_all
= pg
.extend
219 obj
= context
.view_layer
.objects
.active
221 if obj
.mode
== "EDIT":
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
]
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")
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
]:
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":
258 for v
in [v
for v
in bm
.verts
if v
.select
]:
260 bm
.select_history
.clear()
261 bmesh
.ops
.remove_doubles(bm
, verts
=[v
for v
in bm
.verts
if v
.select
], dist
=0.0001)
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
)
269 for v
in [v
for v
in bm
.verts
if v
.select
]:
270 bm
.edges
.new([v
, vertex_new
])
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
)
277 bm
.edges
.new([bm
.select_history
[-1], vertex_new
])
278 bmesh
.update_edit_mesh(obj
.data
)
279 bm
.select_history
.clear()
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
)
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
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.
308 context: Blender bpy.context instance.
309 operation: The Operation e.g. Create New Vertex
315 scene
= context
.scene
318 obj
= context
.view_layer
.objects
.active
319 if obj
.mode
== "EDIT":
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
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]
335 if len(bm
.select_history
) != 4:
338 + str(len(bm
.select_history
))
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
)
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
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
]:
369 vertex_new
.select_set(True)
370 bmesh
.update_edit_mesh(obj
.data
)
371 bm
.select_history
.clear()
372 elif operation
in {"G", "V"}:
376 if (vertex_a
.co
- vector_delta
).length
< (vertex_b
.co
- vector_delta
).length
:
378 vertex_a
.co
= vector_delta
381 vertex_new
= bm
.verts
.new(vector_delta
)
382 bm
.edges
.new([vertex_a
, vertex_new
])
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
])
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
])
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
])
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
)
422 if vertex_new
is not None:
423 vertex_new
.select_set(True)
424 for v
in bm
.select_history
:
427 bmesh
.update_edit_mesh(obj
.data
)
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
)
441 "Original Object Order (1,2,3,4) was: "
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
)
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
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")
470 pg
.error
= f
"{operation} {PDT_ERR_NON_VALID} {PDT_LAB_INTERSECT}"
471 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
477 def join_two_vertices(context
):
478 """Joins 2 Free Vertices that do not form part of a Face.
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.
486 context: Blender bpy.context instance.
492 scene
= context
.scene
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
]
500 bm
.edges
.new([verts
[-1], verts
[-2]])
501 bmesh
.update_edit_mesh(obj
.data
)
502 bm
.select_history
.clear()
505 pg
.error
= PDT_ERR_CONNECTED
506 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
507 raise PDT_VerticesConnected
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
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.
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.
527 context: Blender bpy.context instance.
533 scene
= context
.scene
536 flip_angle
= pg
.flip_angle
537 obj
= context
.view_layer
.objects
.active
539 pg
.error
= PDT_ERR_NO_ACT_OBJ
540 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
542 if obj
.mode
== "EDIT":
543 bm
= bmesh
.from_edit_mesh(obj
.data
)
544 verts
= [v
for v
in bm
.verts
if v
.select
]
546 if len(bm
.select_history
) == 2:
547 vector_a
, vector_b
= check_selection(2, bm
, obj
)
549 pg
.error
= PDT_ERR_VERT_MODE
550 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
551 raise PDT_FeatureError
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
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
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]
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
])
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
583 pg
.angle
= round(ang
- 180, decimal_places
)
585 pg
.angle
= round(ang
- 180, decimal_places
)
587 pg
.angle
= round(ang
, decimal_places
)
589 pg
.distance
= round(sqrt(
590 (vector_a
.x
- vector_b
.x
) ** 2 +
591 (vector_a
.y
- vector_b
.y
) ** 2), decimal_places
)
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.
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.
608 context: Blender bpy.context instance.
614 pg
= context
.scene
.pdt_pg
615 flip_angle
= pg
.flip_angle
616 obj
= context
.view_layer
.objects
.active
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
]
625 if len(bm
.select_history
) == 3:
626 vector_a
, vector_b
, vector_c
= check_selection(3, bm
, obj
)
628 pg
.error
= PDT_ERR_VERT_MODE
629 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
630 raise PDT_FeatureError
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
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
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
660 pg
.angle
= round(ang
- 180, decimal_places
)
662 pg
.angle
= round(ang
- 180, decimal_places
)
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.
673 Keeps geometry static in World Space whilst moving Object Origin
674 Requires cursor location
675 Works in Edit and Object Modes.
678 context: Blender bpy.context instance.
684 scene
= context
.scene
685 pg
= context
.scene
.pdt_pg
686 obj
= context
.view_layer
.objects
.active
688 pg
.error
= PDT_ERR_NO_ACT_OBJ
689 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
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
)
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
:
704 obj
.location
= cur_loc
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
712 """Taper Geometry along World Axes.
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.
721 context: Blender bpy.context instance.
724 Uses pg.taper & pg.angle scene variables
730 scene
= context
.scene
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
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
)
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
]:
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
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()
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