Import Images: fix crash in Animate Sequence when no image selected
[blender-addons.git] / precision_drawing_tools / pdt_pivot_point.py
blob299a7da9e7a6f3b52614e751ad10f868e3c25998
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 bpy.types import Operator, SpaceView3D
9 from mathutils import Vector, Matrix
10 from math import pi
11 from .pdt_functions import view_coords, draw_callback_3d
12 from .pdt_msg_strings import (
13 PDT_CON_AREYOURSURE,
14 PDT_ERR_EDIT_MODE,
15 PDT_ERR_NO3DVIEW,
16 PDT_ERR_NOPPLOC,
17 PDT_ERR_NO_ACT_OBJ,
18 PDT_ERR_NO_SEL_GEOM
22 class PDT_OT_ModalDrawOperator(bpy.types.Operator):
23 """Show/Hide Pivot Point"""
25 bl_idname = "pdt.modaldraw"
26 bl_label = "PDT Modal Draw"
27 bl_options = {"REGISTER", "UNDO"}
29 _handle = None # keep function handler
31 @staticmethod
32 def handle_add(self, context):
33 """Draw Pivot Point Graphic if not displayed.
35 Note:
36 Draws 7 element Pivot Point Graphic
38 Args:
39 context: Blender bpy.context instance.
41 Returns:
42 Nothing.
43 """
45 if PDT_OT_ModalDrawOperator._handle is None:
46 PDT_OT_ModalDrawOperator._handle = SpaceView3D.draw_handler_add(
47 draw_callback_3d, (self, context), "WINDOW", "POST_VIEW"
49 context.window_manager.pdt_run_opengl = True
51 @staticmethod
52 def handle_remove(self, context):
53 """Remove Pivot Point Graphic if displayed.
55 Note:
56 Removes 7 element Pivot Point Graphic
58 Args:
59 context: Blender bpy.context instance.
61 Returns:
62 Nothing.
63 """
65 if PDT_OT_ModalDrawOperator._handle is not None:
66 SpaceView3D.draw_handler_remove(PDT_OT_ModalDrawOperator._handle, "WINDOW")
67 PDT_OT_ModalDrawOperator._handle = None
68 context.window_manager.pdt_run_opengl = False
70 def execute(self, context):
71 """Pivot Point Show/Hide Button Function.
73 Note:
74 Operational execute function for Show/Hide Pivot Point function
76 Args:
77 context: Blender bpy.context instance.
79 Returns:
80 Status Set.
81 """
83 if context.area.type == "VIEW_3D":
84 if context.window_manager.pdt_run_opengl is False:
85 self.handle_add(self, context)
86 context.area.tag_redraw()
87 else:
88 self.handle_remove(self, context)
89 context.area.tag_redraw()
91 return {"FINISHED"}
93 self.report({"ERROR"}, PDT_ERR_NO3DVIEW)
94 return {"CANCELLED"}
97 class PDT_OT_ViewPlaneRotate(Operator):
98 """Rotate Selected Vertices about Pivot Point in View Plane"""
100 bl_idname = "pdt.viewplanerot"
101 bl_label = "PDT View Rotate"
102 bl_options = {"REGISTER", "UNDO"}
104 @classmethod
105 def poll(cls, context):
106 """Check Object Status.
108 Args:
109 context: Blender bpy.context instance.
111 Returns:
112 Nothing.
115 obj = context.object
116 if obj is None:
117 return False
118 return all([bool(obj), obj.type == "MESH", obj.mode == "EDIT"])
121 def execute(self, context):
122 """Rotate Selected Vertices about Pivot Point.
124 Note:
125 Rotates any selected vertices about the Pivot Point
126 in View Oriented coordinates, works in any view orientation.
128 Args:
129 context: Blender bpy.context instance.
131 Note:
132 Uses pg.pivot_loc, pg.pivot_ang scene variables
134 Returns:
135 Status Set.
138 scene = context.scene
139 pg = scene.pdt_pg
140 obj = bpy.context.view_layer.objects.active
141 if obj is None:
142 self.report({"ERROR"}, PDT_ERR_NO_ACT_OBJ)
143 return {"FINISHED"}
144 if obj.mode != "EDIT":
145 error_message = f"{PDT_ERR_EDIT_MODE} {obj.mode})"
146 self.report({"ERROR"}, error_message)
147 return {"FINISHED"}
148 bm = bmesh.from_edit_mesh(obj.data)
149 v1 = Vector((0, 0, 0))
150 v2 = view_coords(0, 0, 1)
151 axis = (v2 - v1).normalized()
152 rot = Matrix.Rotation((pg.pivot_ang * pi / 180), 4, axis)
153 verts = verts = [v for v in bm.verts if v.select]
154 bmesh.ops.rotate(
155 bm, cent=pg.pivot_loc - obj.matrix_world.decompose()[0], matrix=rot, verts=verts
157 bmesh.update_edit_mesh(obj.data)
158 return {"FINISHED"}
161 class PDT_OT_ViewPlaneScale(Operator):
162 """Scale Selected Vertices about Pivot Point"""
164 bl_idname = "pdt.viewscale"
165 bl_label = "PDT View Scale"
166 bl_options = {"REGISTER", "UNDO"}
168 @classmethod
169 def poll(cls, context):
170 """Check Object Status.
172 Args:
173 context: Blender bpy.context instance.
175 Returns:
176 Nothing.
179 obj = context.object
180 if obj is None:
181 return False
182 return all([bool(obj), obj.type == "MESH", obj.mode == "EDIT"])
185 def execute(self, context):
186 """Scales Selected Vertices about Pivot Point.
188 Note:
189 Scales any selected vertices about the Pivot Point
190 in View Oriented coordinates, works in any view orientation
192 Args:
193 context: Blender bpy.context instance.
195 Note:
196 Uses pg.pivot_loc, pg.pivot_scale scene variables
198 Returns:
199 Status Set.
202 scene = context.scene
203 pg = scene.pdt_pg
204 obj = bpy.context.view_layer.objects.active
205 if obj is None:
206 self.report({"ERROR"}, PDT_ERR_NO_ACT_OBJ)
207 return {"FINISHED"}
208 if obj.mode != "EDIT":
209 error_message = f"{PDT_ERR_EDIT_MODE} {obj.mode})"
210 self.report({"ERROR"}, error_message)
211 return {"FINISHED"}
212 bm = bmesh.from_edit_mesh(obj.data)
213 verts = verts = [v for v in bm.verts if v.select]
214 for v in verts:
215 delta_x = (pg.pivot_loc.x - obj.matrix_world.decompose()[0].x - v.co.x) * (
216 1 - pg.pivot_scale.x
218 delta_y = (pg.pivot_loc.y - obj.matrix_world.decompose()[0].y - v.co.y) * (
219 1 - pg.pivot_scale.y
221 delta_z = (pg.pivot_loc.z - obj.matrix_world.decompose()[0].z - v.co.z) * (
222 1 - pg.pivot_scale.z
224 delta_v = Vector((delta_x, delta_y, delta_z))
225 v.co = v.co + delta_v
226 bmesh.update_edit_mesh(obj.data)
227 return {"FINISHED"}
230 class PDT_OT_PivotToCursor(Operator):
231 """Set The Pivot Point to Cursor Location"""
233 bl_idname = "pdt.pivotcursor"
234 bl_label = "PDT Pivot To Cursor"
235 bl_options = {"REGISTER", "UNDO"}
237 def execute(self, context):
238 """Moves Pivot Point to Cursor Location.
240 Note:
241 Moves Pivot Point to Cursor Location in active scene
243 Args:
244 context: Blender bpy.context instance.
246 Returns:
247 Status Set.
250 scene = context.scene
251 pg = scene.pdt_pg
252 old_cursor_loc = scene.cursor.location.copy()
253 pg.pivot_loc = scene.cursor.location
254 scene.cursor.location = old_cursor_loc
255 return {"FINISHED"}
258 class PDT_OT_CursorToPivot(Operator):
259 """Set The Cursor Location to Pivot Point"""
261 bl_idname = "pdt.cursorpivot"
262 bl_label = "PDT Cursor To Pivot"
263 bl_options = {"REGISTER", "UNDO"}
265 def execute(self, context):
266 """Moves Cursor to Pivot Point Location.
268 Note:
269 Moves Cursor to Pivot Point Location in active scene
271 Args:
272 context: Blender bpy.context instance.
274 Returns:
275 Status Set.
278 scene = context.scene
279 pg = scene.pdt_pg
280 scene.cursor.location = pg.pivot_loc
281 return {"FINISHED"}
284 class PDT_OT_PivotSelected(Operator):
285 """Set Pivot Point to Selected Geometry"""
287 bl_idname = "pdt.pivotselected"
288 bl_label = "PDT Pivot to Selected"
289 bl_options = {"REGISTER", "UNDO"}
291 @classmethod
292 def poll(cls, context):
293 """Check Object Status.
295 Args:
296 context: Blender bpy.context instance.
298 Returns:
299 Nothing.
302 obj = context.object
303 if obj is None:
304 return False
305 return all([bool(obj), obj.type == "MESH", obj.mode == "EDIT"])
308 def execute(self, context):
309 """Moves Pivot Point centroid of Selected Geometry.
311 Note:
312 Moves Pivot Point centroid of Selected Geometry in active scene
313 using Snap_Cursor_To_Selected, then puts cursor back to original location.
315 Args:
316 context: Blender bpy.context instance.
318 Returns:
319 Status Set.
322 scene = context.scene
323 pg = scene.pdt_pg
324 obj = bpy.context.view_layer.objects.active
325 if obj is None:
326 self.report({"ERROR"}, PDT_ERR_NO_ACT_OBJ)
327 return {"FINISHED"}
328 if obj.mode != "EDIT":
329 error_message = f"{PDT_ERR_EDIT_MODE} {obj.mode})"
330 self.report({"ERROR"}, error_message)
331 return {"FINISHED"}
332 bm = bmesh.from_edit_mesh(obj.data)
333 verts = verts = [v for v in bm.verts if v.select]
334 if len(verts) > 0:
335 old_cursor_loc = scene.cursor.location.copy()
336 bpy.ops.view3d.snap_cursor_to_selected()
337 pg.pivot_loc = scene.cursor.location
338 scene.cursor.location = old_cursor_loc
339 return {"FINISHED"}
341 self.report({"ERROR"}, PDT_ERR_NO_SEL_GEOM)
342 return {"FINISHED"}
345 class PDT_OT_PivotOrigin(Operator):
346 """Set Pivot Point at Object Origin"""
348 bl_idname = "pdt.pivotorigin"
349 bl_label = "PDT Pivot to Object Origin"
350 bl_options = {"REGISTER", "UNDO"}
352 @classmethod
353 def poll(cls, context):
354 """Check Object Status.
356 Args:
357 context: Blender bpy.context instance.
359 Returns:
360 Nothing.
363 obj = context.object
364 if obj is None:
365 return False
366 return all([bool(obj), obj.type == "MESH"])
368 def execute(self, context):
369 """Moves Pivot Point to Object Origin.
371 Note:
372 Moves Pivot Point to Object Origin in active scene
374 Args:
375 context: Blender bpy.context instance.
377 Returns:
378 Status Set.
381 scene = context.scene
382 pg = scene.pdt_pg
383 obj = bpy.context.view_layer.objects.active
384 if obj is None:
385 self.report({"ERROR"}, PDT_ERR_NO_ACT_OBJ)
386 return {"FINISHED"}
387 old_cursor_loc = scene.cursor.location.copy()
388 obj_loc = obj.matrix_world.decompose()[0]
389 pg.pivot_loc = obj_loc
390 scene.cursor.location = old_cursor_loc
391 return {"FINISHED"}
394 class PDT_OT_PivotWrite(Operator):
395 """Write Pivot Point Location to Object"""
397 bl_idname = "pdt.pivotwrite"
398 bl_label = "PDT Write PP to Object?"
399 bl_options = {"REGISTER", "UNDO"}
401 @classmethod
402 def poll(cls, context):
403 """Check Object Status.
405 Args:
406 context: Blender bpy.context instance.
408 Returns:
409 Nothing.
412 obj = context.object
413 if obj is None:
414 return False
415 return all([bool(obj), obj.type == "MESH"])
417 def execute(self, context):
418 """Writes Pivot Point Location to Object's Custom Properties.
420 Note:
421 Writes Pivot Point Location to Object's Custom Properties
422 as Vector to 'PDT_PP_LOC' - Requires Confirmation through dialogue
424 Args:
425 context: Blender bpy.context instance.
427 Note:
428 Uses pg.pivot_loc scene variable
430 Returns:
431 Status Set.
434 scene = context.scene
435 pg = scene.pdt_pg
436 obj = bpy.context.view_layer.objects.active
437 if obj is None:
438 self.report({"ERROR"}, PDT_ERR_NO_ACT_OBJ)
439 return {"FINISHED"}
440 obj["PDT_PP_LOC"] = pg.pivot_loc
441 return {"FINISHED"}
443 def invoke(self, context, event):
444 return context.window_manager.invoke_props_dialog(self)
446 def draw(self, context):
447 row = self.layout
448 row.label(text=PDT_CON_AREYOURSURE)
451 class PDT_OT_PivotRead(Operator):
452 """Read Pivot Point Location from Object"""
454 bl_idname = "pdt.pivotread"
455 bl_label = "PDT Read PP"
456 bl_options = {"REGISTER", "UNDO"}
458 @classmethod
459 def poll(cls, context):
460 """Check Object Status.
462 Args:
463 context: Blender bpy.context instance.
465 Returns:
466 Nothing.
469 obj = context.object
470 if obj is None:
471 return False
472 return all([bool(obj), obj.type == "MESH"])
474 def execute(self, context):
475 """Reads Pivot Point Location from Object's Custom Properties.
477 Note:
478 Sets Pivot Point Location from Object's Custom Properties
479 using 'PDT_PP_LOC'
481 Args:
482 context: Blender bpy.context instance.
484 Note:
485 Uses pg.pivot_loc scene variable
487 Returns:
488 Status Set.
491 scene = context.scene
492 pg = scene.pdt_pg
493 obj = bpy.context.view_layer.objects.active
494 if obj is None:
495 self.report({"ERROR"}, PDT_ERR_NO_ACT_OBJ)
496 return {"FINISHED"}
497 if "PDT_PP_LOC" in obj:
498 pg.pivot_loc = obj["PDT_PP_LOC"]
499 return {"FINISHED"}
501 self.report({"ERROR"}, PDT_ERR_NOPPLOC)
502 return {"FINISHED"}