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 # -----------------------------------------------------------------------
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 (
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 (
85 def vector_build(context
, pg
, obj
, operation
, values
, num_values
):
86 """Build Movement Vector from Input Fields.
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
97 Vector to position, or offset, items.
100 scene
= context
.scene
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
)
116 pg
.error
= PDT_ERR_BAD3VALS
117 elif num_values
== 2:
118 pg
.error
= PDT_ERR_BAD2VALS
120 pg
.error
= PDT_ERR_BAD1VALS
121 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
122 raise PDT_InvalidVector
126 def placement_normal(context
, operation
):
127 """Manipulates Geometry, or Objects by Normal Intersection between 3 points.
130 context: Blender bpy.context instance.
131 operation: The Operation e.g. Create New Vertex
137 scene
= context
.scene
139 extend_all
= pg
.extend
140 obj
= context
.view_layer
.objects
.active
142 if obj
.mode
== "EDIT":
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
)
152 pg
.error
= PDT_ERR_VERT_MODE
153 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
154 raise PDT_FeatureError
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
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]
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":
183 for v
in [v
for v
in bm
.verts
if v
.select
]:
185 bm
.select_history
.clear()
186 bmesh
.ops
.remove_doubles(bm
, verts
=[v
for v
in bm
.verts
if v
.select
], dist
=0.0001)
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
]:
200 vertex_new
.select_set(True)
202 pg
.error
= f
"{PDT_ERR_EDIT_MODE} {obj.mode})"
203 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
205 elif operation
== "V" and obj
.mode
== "EDIT":
206 vector_new
= vector_delta
207 vertex_new
= bm
.verts
.new(vector_new
)
209 for v
in [v
for v
in bm
.verts
if v
.select
]:
210 bm
.edges
.new([v
, vertex_new
])
212 bm
.edges
.new([bm
.select_history
[-1], vertex_new
])
213 for v
in [v
for v
in bm
.verts
if v
.select
]:
215 vertex_new
.select_set(True)
216 bmesh
.update_edit_mesh(obj
.data
)
217 bm
.select_history
.clear()
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.
227 context: Blender bpy.context instance.
228 operation: The Operation e.g. Create New Vertex
234 scene
= context
.scene
236 extend_all
= pg
.extend
237 obj
= context
.view_layer
.objects
.active
239 if obj
.mode
== "EDIT":
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
]
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")
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
]:
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":
276 for v
in [v
for v
in bm
.verts
if v
.select
]:
278 bm
.select_history
.clear()
279 bmesh
.ops
.remove_doubles(bm
, verts
=[v
for v
in bm
.verts
if v
.select
], dist
=0.0001)
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
)
287 for v
in [v
for v
in bm
.verts
if v
.select
]:
288 bm
.edges
.new([v
, vertex_new
])
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
)
295 bm
.edges
.new([bm
.select_history
[-1], vertex_new
])
296 bmesh
.update_edit_mesh(obj
.data
)
297 bm
.select_history
.clear()
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
)
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
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.
326 context: Blender bpy.context instance.
327 operation: The Operation e.g. Create New Vertex
333 scene
= context
.scene
336 obj
= context
.view_layer
.objects
.active
337 if obj
.mode
== "EDIT":
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
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]
353 if len(bm
.select_history
) != 4:
356 + str(len(bm
.select_history
))
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
)
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
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
]:
387 vertex_new
.select_set(True)
388 bmesh
.update_edit_mesh(obj
.data
)
389 bm
.select_history
.clear()
390 elif operation
in {"G", "V"}:
394 if (vertex_a
.co
- vector_delta
).length
< (vertex_b
.co
- vector_delta
).length
:
396 vertex_a
.co
= vector_delta
399 vertex_new
= bm
.verts
.new(vector_delta
)
400 bm
.edges
.new([vertex_a
, vertex_new
])
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
])
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
])
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
])
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
)
440 if vertex_new
is not None:
441 vertex_new
.select_set(True)
442 for v
in bm
.select_history
:
445 bmesh
.update_edit_mesh(obj
.data
)
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
)
459 "Original Object Order (1,2,3,4) was: "
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
)
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
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")
488 pg
.error
= f
"{operation} {PDT_ERR_NON_VALID} {PDT_LAB_INTERSECT}"
489 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
495 def join_two_vertices(context
):
496 """Joins 2 Free Vertices that do not form part of a Face.
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.
504 context: Blender bpy.context instance.
510 scene
= context
.scene
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
]
518 bm
.edges
.new([verts
[-1], verts
[-2]])
519 bmesh
.update_edit_mesh(obj
.data
)
520 bm
.select_history
.clear()
523 pg
.error
= PDT_ERR_CONNECTED
524 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
525 raise PDT_VerticesConnected
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
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.
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.
545 context: Blender bpy.context instance.
551 scene
= context
.scene
554 flip_angle
= pg
.flip_angle
555 obj
= context
.view_layer
.objects
.active
557 pg
.error
= PDT_ERR_NO_ACT_OBJ
558 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
560 if obj
.mode
== "EDIT":
561 bm
= bmesh
.from_edit_mesh(obj
.data
)
562 verts
= [v
for v
in bm
.verts
if v
.select
]
564 if len(bm
.select_history
) == 2:
565 vector_a
, vector_b
= check_selection(2, bm
, obj
)
567 pg
.error
= PDT_ERR_VERT_MODE
568 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
569 raise PDT_FeatureError
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
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
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]
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
])
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
601 pg
.angle
= round(ang
- 180, decimal_places
)
603 pg
.angle
= round(ang
- 180, decimal_places
)
605 pg
.angle
= round(ang
, decimal_places
)
607 pg
.distance
= round(sqrt(
608 (vector_a
.x
- vector_b
.x
) ** 2 +
609 (vector_a
.y
- vector_b
.y
) ** 2), decimal_places
)
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.
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.
626 context: Blender bpy.context instance.
632 pg
= context
.scene
.pdt_pg
633 flip_angle
= pg
.flip_angle
634 obj
= context
.view_layer
.objects
.active
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
]
643 if len(bm
.select_history
) == 3:
644 vector_a
, vector_b
, vector_c
= check_selection(3, bm
, obj
)
646 pg
.error
= PDT_ERR_VERT_MODE
647 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
648 raise PDT_FeatureError
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
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
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
678 pg
.angle
= round(ang
- 180, decimal_places
)
680 pg
.angle
= round(ang
- 180, decimal_places
)
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.
691 Keeps geometry static in World Space whilst moving Object Origin
692 Requires cursor location
693 Works in Edit and Object Modes.
696 context: Blender bpy.context instance.
702 scene
= context
.scene
703 pg
= context
.scene
.pdt_pg
704 obj
= context
.view_layer
.objects
.active
706 pg
.error
= PDT_ERR_NO_ACT_OBJ
707 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
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
)
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
:
722 obj
.location
= cur_loc
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
730 """Taper Geometry along World Axes.
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.
739 context: Blender bpy.context instance.
742 Uses pg.taper & pg.angle scene variables
748 scene
= context
.scene
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
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
)
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
]:
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
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()
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