sun_position: fix warning from deleted prop in User Preferences
[blender-addons.git] / precision_drawing_tools / pdt_functions.py
blob3d12c4f99b6581cd92274597f239b89bce7d0339
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 # -----------------------------------------------------------------------
24 # Common Functions used in more than one place in PDT Operations
26 import bpy
27 import bmesh
28 import bgl
29 import gpu
30 import numpy as np
31 from mathutils import Vector, Quaternion
32 from gpu_extras.batch import batch_for_shader
33 from math import cos, sin, pi
34 from .pdt_msg_strings import (
35 PDT_ERR_VERT_MODE,
36 PDT_ERR_SEL_2_V_1_E,
37 PDT_ERR_SEL_2_OBJS,
38 PDT_ERR_NO_ACT_OBJ,
39 PDT_ERR_SEL_1_EDGEM,
41 from . import pdt_exception
42 PDT_ShaderError = pdt_exception.ShaderError
45 def debug(msg, prefix=""):
46 """Print a debug message to the console if PDT's or Blender's debug flags are set.
48 Note:
49 The printed message will be of the form:
51 {prefix}{caller file name:line number}| {msg}
53 Args:
54 msg: Incomming message to display
55 prefix: Always Blank
57 Returns:
58 Nothing.
59 """
61 pdt_debug = bpy.context.preferences.addons[__package__].preferences.debug
62 if bpy.app.debug or bpy.app.debug_python or pdt_debug:
63 import traceback
65 def extract_filename(fullpath):
66 """Return only the filename part of fullpath (excluding its path).
68 Args:
69 fullpath: Filename's full path
71 Returns:
72 filename.
73 """
74 # Expected to end up being a string containing only the filename
75 # (i.e. excluding its preceding '/' separated path)
76 filename = fullpath.split('/')[-1]
77 #print(filename)
78 # something went wrong
79 if len(filename) < 1:
80 return fullpath
81 # since this is a string, just return it
82 return filename
84 # stack frame corresponding to the line where debug(msg) was called
85 #print(traceback.extract_stack()[-2])
86 laststack = traceback.extract_stack()[-2]
87 #print(laststack[0])
88 # laststack[0] is the caller's full file name, laststack[1] is the line number
89 print(f"{prefix}{extract_filename(laststack[0])}:{laststack[1]}| {msg}")
91 def oops(self, context):
92 """Error Routine.
94 Note:
95 Displays error message in a popup.
97 Args:
98 context: Blender bpy.context instance.
100 Returns:
101 Nothing.
104 scene = context.scene
105 pg = scene.pdt_pg
106 self.layout.label(text=pg.error)
109 def set_mode(mode_pl):
110 """Sets Active Axes for View Orientation.
112 Note:
113 Sets indices of axes for locational vectors:
114 "XY": a1 = x, a2 = y, a3 = z
115 "XZ": a1 = x, a2 = z, a3 = y
116 "YZ": a1 = y, a2 = z, a3 = x
118 Args:
119 mode_pl: Plane Selector variable as input
121 Returns:
122 3 Integer indices.
125 order = {
126 "XY": (0, 1, 2),
127 "XZ": (0, 2, 1),
128 "YZ": (1, 2, 0),
130 return order[mode_pl]
133 def set_axis(mode_pl):
134 """Sets Active Axes for View Orientation.
136 Note:
137 Sets indices for axes from taper vectors
138 Axis order: Rotate Axis, Move Axis, Height Axis
140 Args:
141 mode_pl: Taper Axis Selector variable as input
143 Returns:
144 3 Integer Indicies.
147 order = {
148 "RX-MY": (0, 1, 2),
149 "RX-MZ": (0, 2, 1),
150 "RY-MX": (1, 0, 2),
151 "RY-MZ": (1, 2, 0),
152 "RZ-MX": (2, 0, 1),
153 "RZ-MY": (2, 1, 0),
155 return order[mode_pl]
158 def check_selection(num, bm, obj):
159 """Check that the Object's select_history has sufficient entries.
161 Note:
162 If selection history is not Verts, clears selection and history.
164 Args:
165 num: The number of entries required for each operation
166 bm: The Bmesh from the Object
167 obj: The Object
169 Returns:
170 list of 3D points as Vectors.
173 if len(bm.select_history) < num:
174 return None
175 active_vertex = bm.select_history[-1]
176 if isinstance(active_vertex, bmesh.types.BMVert):
177 vector_a = active_vertex.co
178 if num == 1:
179 return vector_a
180 if num == 2:
181 vector_b = bm.select_history[-2].co
182 return vector_a, vector_b
183 if num == 3:
184 vector_b = bm.select_history[-2].co
185 vector_c = bm.select_history[-3].co
186 return vector_a, vector_b, vector_c
187 if num == 4:
188 vector_b = bm.select_history[-2].co
189 vector_c = bm.select_history[-3].co
190 vector_d = bm.select_history[-4].co
191 return vector_a, vector_b, vector_c, vector_d
192 else:
193 for f in bm.faces:
194 f.select_set(False)
195 for e in bm.edges:
196 e.select_set(False)
197 for v in bm.verts:
198 v.select_set(False)
199 bmesh.update_edit_mesh(obj.data)
200 bm.select_history.clear()
201 return None
204 def update_sel(bm, verts, edges, faces):
205 """Updates Vertex, Edge and Face Selections following a function.
207 Args:
208 bm: Object Bmesh
209 verts: New Selection for Vertices
210 edges: The Edges on which to operate
211 faces: The Faces on which to operate
213 Returns:
214 Nothing.
216 for f in bm.faces:
217 f.select_set(False)
218 for e in bm.edges:
219 e.select_set(False)
220 for v in bm.verts:
221 v.select_set(False)
222 for v in verts:
223 v.select_set(True)
224 for e in edges:
225 e.select_set(True)
226 for f in faces:
227 f.select_set(True)
230 def view_coords(x_loc, y_loc, z_loc):
231 """Converts input Vector values to new Screen Oriented Vector.
233 Args:
234 x_loc: X coordinate from vector
235 y_loc: Y coordinate from vector
236 z_loc: Z coordinate from vector
238 Returns:
239 Vector adjusted to View's Inverted Tranformation Matrix.
242 areas = [a for a in bpy.context.screen.areas if a.type == "VIEW_3D"]
243 if len(areas) > 0:
244 view_matrix = areas[0].spaces.active.region_3d.view_matrix
245 view_matrix = view_matrix.to_3x3().normalized().inverted()
246 view_location = Vector((x_loc, y_loc, z_loc))
247 new_view_location = view_matrix @ view_location
248 return new_view_location
250 return Vector((0, 0, 0))
253 def view_coords_i(x_loc, y_loc, z_loc):
254 """Converts Screen Oriented input Vector values to new World Vector.
256 Note:
257 Converts View tranformation Matrix to Rotational Matrix
259 Args:
260 x_loc: X coordinate from vector
261 y_loc: Y coordinate from vector
262 z_loc: Z coordinate from vector
264 Returns:
265 Vector adjusted to View's Transformation Matrix.
268 areas = [a for a in bpy.context.screen.areas if a.type == "VIEW_3D"]
269 if len(areas) > 0:
270 view_matrix = areas[0].spaces.active.region_3d.view_matrix
271 view_matrix = view_matrix.to_3x3().normalized()
272 view_location = Vector((x_loc, y_loc, z_loc))
273 new_view_location = view_matrix @ view_location
274 return new_view_location
276 return Vector((0, 0, 0))
279 def view_dir(dis_v, ang_v):
280 """Converts Distance and Angle to View Oriented Vector.
282 Note:
283 Converts View Transformation Matrix to Rotational Matrix (3x3)
284 Angles are Converts to Radians from degrees.
286 Args:
287 dis_v: Scene PDT distance
288 ang_v: Scene PDT angle
290 Returns:
291 World Vector.
294 areas = [a for a in bpy.context.screen.areas if a.type == "VIEW_3D"]
295 if len(areas) > 0:
296 view_matrix = areas[0].spaces.active.region_3d.view_matrix
297 view_matrix = view_matrix.to_3x3().normalized().inverted()
298 view_location = Vector((0, 0, 0))
299 view_location.x = dis_v * cos(ang_v * pi / 180)
300 view_location.y = dis_v * sin(ang_v * pi / 180)
301 new_view_location = view_matrix @ view_location
302 return new_view_location
304 return Vector((0, 0, 0))
307 def euler_to_quaternion(roll, pitch, yaw):
308 """Converts Euler Rotation to Quaternion Rotation.
310 Args:
311 roll: Roll in Euler rotation
312 pitch: Pitch in Euler rotation
313 yaw: Yaw in Euler rotation
315 Returns:
316 Quaternion Rotation.
319 # fmt: off
320 quat_x = (np.sin(roll/2) * np.cos(pitch/2) * np.cos(yaw/2)
321 - np.cos(roll/2) * np.sin(pitch/2) * np.sin(yaw/2))
322 quat_y = (np.cos(roll/2) * np.sin(pitch/2) * np.cos(yaw/2)
323 + np.sin(roll/2) * np.cos(pitch/2) * np.sin(yaw/2))
324 quat_z = (np.cos(roll/2) * np.cos(pitch/2) * np.sin(yaw/2)
325 - np.sin(roll/2) * np.sin(pitch/2) * np.cos(yaw/2))
326 quat_w = (np.cos(roll/2) * np.cos(pitch/2) * np.cos(yaw/2)
327 + np.sin(roll/2) * np.sin(pitch/2) * np.sin(yaw/2))
328 # fmt: on
329 return Quaternion((quat_w, quat_x, quat_y, quat_z))
332 def arc_centre(vector_a, vector_b, vector_c):
333 """Calculates Centre of Arc from 3 Vector Locations using standard Numpy routine
335 Args:
336 vector_a: Active vector location
337 vector_b: Second vector location
338 vector_c: Third vector location
340 Returns:
341 Vector representing Arc Centre and Float representing Arc Radius.
344 coord_a = np.array([vector_a.x, vector_a.y, vector_a.z])
345 coord_b = np.array([vector_b.x, vector_b.y, vector_b.z])
346 coord_c = np.array([vector_c.x, vector_c.y, vector_c.z])
347 line_a = np.linalg.norm(coord_c - coord_b)
348 line_b = np.linalg.norm(coord_c - coord_a)
349 line_c = np.linalg.norm(coord_b - coord_a)
350 # fmt: off
351 line_s = (line_a+line_b+line_c) / 2
352 radius = (
353 line_a*line_b*line_c/4
354 / np.sqrt(line_s
355 * (line_s-line_a)
356 * (line_s-line_b)
357 * (line_s-line_c))
359 base_1 = line_a*line_a * (line_b*line_b + line_c*line_c - line_a*line_a)
360 base_2 = line_b*line_b * (line_a*line_a + line_c*line_c - line_b*line_b)
361 base_3 = line_c*line_c * (line_a*line_a + line_b*line_b - line_c*line_c)
362 # fmt: on
363 intersect_coord = np.column_stack((coord_a, coord_b, coord_c))
364 intersect_coord = intersect_coord.dot(np.hstack((base_1, base_2, base_3)))
365 intersect_coord /= base_1 + base_2 + base_3
366 return Vector((intersect_coord[0], intersect_coord[1], intersect_coord[2])), radius
369 def intersection(vertex_a, vertex_b, vertex_c, vertex_d, plane):
370 """Calculates Intersection Point of 2 Imagined Lines from 4 Vectors.
372 Note:
373 Calculates Converging Intersect Location and indication of
374 whether the lines are convergent using standard Numpy Routines
376 Args:
377 vertex_a: Active vector location of first line
378 vertex_b: Second vector location of first line
379 vertex_c: Third vector location of 2nd line
380 vertex_d: Fourth vector location of 2nd line
381 plane: Working Plane 4 Vector Locations representing 2 lines and Working Plane
383 Returns:
384 Intersection Vector and Boolean for convergent state.
387 if plane == "LO":
388 vertex_offset = vertex_b - vertex_a
389 vertex_b = view_coords_i(vertex_offset.x, vertex_offset.y, vertex_offset.z)
390 vertex_offset = vertex_d - vertex_a
391 vertex_d = view_coords_i(vertex_offset.x, vertex_offset.y, vertex_offset.z)
392 vertex_offset = vertex_c - vertex_a
393 vertex_c = view_coords_i(vertex_offset.x, vertex_offset.y, vertex_offset.z)
394 vector_ref = Vector((0, 0, 0))
395 coord_a = (vertex_c.x, vertex_c.y)
396 coord_b = (vertex_d.x, vertex_d.y)
397 coord_c = (vertex_b.x, vertex_b.y)
398 coord_d = (vector_ref.x, vector_ref.y)
399 else:
400 a1, a2, a3 = set_mode(plane)
401 coord_a = (vertex_c[a1], vertex_c[a2])
402 coord_b = (vertex_d[a1], vertex_d[a2])
403 coord_c = (vertex_a[a1], vertex_a[a2])
404 coord_d = (vertex_b[a1], vertex_b[a2])
405 v_stack = np.vstack([coord_a, coord_b, coord_c, coord_d])
406 h_stack = np.hstack((v_stack, np.ones((4, 1))))
407 line_a = np.cross(h_stack[0], h_stack[1])
408 line_b = np.cross(h_stack[2], h_stack[3])
409 x_loc, y_loc, z_loc = np.cross(line_a, line_b)
410 if z_loc == 0:
411 return Vector((0, 0, 0)), False
412 new_x_loc = x_loc / z_loc
413 new_z_loc = y_loc / z_loc
414 if plane == "LO":
415 new_y_loc = 0
416 else:
417 new_y_loc = vertex_a[a3]
418 # Order Vector Delta
419 if plane == "XZ":
420 vector_delta = Vector((new_x_loc, new_y_loc, new_z_loc))
421 elif plane == "XY":
422 vector_delta = Vector((new_x_loc, new_z_loc, new_y_loc))
423 elif plane == "YZ":
424 vector_delta = Vector((new_y_loc, new_x_loc, new_z_loc))
425 else:
426 # Must be Local View Plane
427 vector_delta = view_coords(new_x_loc, new_z_loc, new_y_loc) + vertex_a
428 return vector_delta, True
431 def get_percent(obj, flip_percent, per_v, data, scene):
432 """Calculates a Percentage Distance between 2 Vectors.
434 Note:
435 Calculates a point that lies a set percentage between two given points
436 using standard Numpy Routines.
438 Works for either 2 vertices for an object in Edit mode
439 or 2 selected objects in Object mode.
441 Args:
442 obj: The Object under consideration
443 flip_percent: Setting this to True measures the percentage starting from the second vector
444 per_v: Percentage Input Value
445 data: pg.flip, pg.percent scene variables & Operational Mode
446 scene: Context Scene
448 Returns:
449 World Vector.
452 pg = scene.pdt_pg
454 if obj.mode == "EDIT":
455 bm = bmesh.from_edit_mesh(obj.data)
456 verts = [v for v in bm.verts if v.select]
457 if len(verts) == 2:
458 vector_a = verts[0].co
459 vector_b = verts[1].co
460 if vector_a is None:
461 pg.error = PDT_ERR_VERT_MODE
462 bpy.context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
463 return None
464 else:
465 pg.error = PDT_ERR_SEL_2_V_1_E + str(len(verts)) + " Vertices"
466 bpy.context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
467 return None
468 coord_a = np.array([vector_a.x, vector_a.y, vector_a.z])
469 coord_b = np.array([vector_b.x, vector_b.y, vector_b.z])
470 if obj.mode == "OBJECT":
471 objs = bpy.context.view_layer.objects.selected
472 if len(objs) != 2:
473 pg.error = PDT_ERR_SEL_2_OBJS + str(len(objs)) + ")"
474 bpy.context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
475 return None
476 coord_a = np.array(
478 objs[-1].matrix_world.decompose()[0].x,
479 objs[-1].matrix_world.decompose()[0].y,
480 objs[-1].matrix_world.decompose()[0].z,
483 coord_b = np.array(
485 objs[-2].matrix_world.decompose()[0].x,
486 objs[-2].matrix_world.decompose()[0].y,
487 objs[-2].matrix_world.decompose()[0].z,
490 coord_c = coord_b - coord_a
491 coord_d = np.array([0, 0, 0])
492 _per_v = per_v
493 if (flip_percent and data != "MV") or data == "MV":
494 _per_v = 100 - per_v
495 coord_out = (coord_d+coord_c) * (_per_v / 100) + coord_a
496 return Vector((coord_out[0], coord_out[1], coord_out[2]))
499 def obj_check(obj, scene, operation):
500 """Check Object & Selection Validity.
502 Args:
503 obj: Active Object
504 scene: Active Scene
505 operation: The Operation e.g. Create New Vertex
507 Returns:
508 Object Bmesh
509 Validity Boolean.
512 pg = scene.pdt_pg
513 _operation = operation.upper()
515 if obj is None:
516 pg.error = PDT_ERR_NO_ACT_OBJ
517 bpy.context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
518 return None, False
519 if obj.mode == "EDIT":
520 bm = bmesh.from_edit_mesh(obj.data)
521 if _operation == "S":
522 if len(bm.edges) < 1:
523 pg.error = f"{PDT_ERR_SEL_1_EDGEM} {len(bm.edges)})"
524 bpy.context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
525 return None, False
526 return bm, True
527 if len(bm.select_history) >= 1:
528 vector_a = None
529 if _operation not in {"D", "E", "F", "G", "N", "S"}:
530 vector_a = check_selection(1, bm, obj)
531 else:
532 verts = [v for v in bm.verts if v.select]
533 if len(verts) > 0:
534 vector_a = verts[0]
535 if vector_a is None:
536 pg.error = PDT_ERR_VERT_MODE
537 bpy.context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
538 return None, False
539 return bm, True
540 return None, True
543 def dis_ang(values, flip_angle, plane, scene):
544 """Set Working Axes when using Direction command.
546 Args:
547 values: Input Arguments
548 flip_angle: Whether to flip the angle
549 plane: Working Plane
550 scene: Current Scene
552 Returns:
553 Directional Offset as a Vector.
556 pg = scene.pdt_pg
557 dis_v = float(values[0])
558 ang_v = float(values[1])
559 if flip_angle:
560 if ang_v > 0:
561 ang_v = ang_v - 180
562 else:
563 ang_v = ang_v + 180
564 pg.angle = ang_v
565 if plane == "LO":
566 vector_delta = view_dir(dis_v, ang_v)
567 else:
568 a1, a2, _ = set_mode(plane)
569 vector_delta = Vector((0, 0, 0))
570 # fmt: off
571 vector_delta[a1] = vector_delta[a1] + (dis_v * cos(ang_v * pi/180))
572 vector_delta[a2] = vector_delta[a2] + (dis_v * sin(ang_v * pi/180))
573 # fmt: on
574 return vector_delta
577 # Shader for displaying the Pivot Point as Graphics.
579 SHADER = gpu.shader.from_builtin("3D_UNIFORM_COLOR") if not bpy.app.background else None
582 def draw_3d(coords, gtype, rgba, context):
583 """Draw Pivot Point Graphics.
585 Note:
586 Draws either Lines Points, or Tris using defined shader
588 Args:
589 coords: Input Coordinates List
590 gtype: Graphic Type
591 rgba: Colour in RGBA format
592 context: Blender bpy.context instance.
594 Returns:
595 Nothing.
598 batch = batch_for_shader(SHADER, gtype, {"pos": coords})
600 try:
601 if coords is not None:
602 bgl.glEnable(bgl.GL_BLEND)
603 SHADER.bind()
604 SHADER.uniform_float("color", rgba)
605 batch.draw(SHADER)
606 except:
607 raise PDT_ShaderError
610 def draw_callback_3d(self, context):
611 """Create Coordinate List for Pivot Point Graphic.
613 Note:
614 Creates coordinates for Pivot Point Graphic consisting of 6 Tris
615 and one Point colour coded Red; X axis, Green; Y axis, Blue; Z axis
616 and a yellow point based upon screen scale
618 Args:
619 context: Blender bpy.context instance.
621 Returns:
622 Nothing.
625 scene = context.scene
626 pg = scene.pdt_pg
627 region_width = context.region.width
628 x_loc = pg.pivot_loc.x
629 y_loc = pg.pivot_loc.y
630 z_loc = pg.pivot_loc.z
631 # Scale it from view
632 areas = [a for a in context.screen.areas if a.type == "VIEW_3D"]
633 if len(areas) > 0:
634 scale_factor = abs(areas[0].spaces.active.region_3d.window_matrix.decompose()[2][1])
635 # Check for orhtographic view and resize
636 #if areas[0].spaces.active.region_3d.is_orthographic_side_view:
637 # dim_a = region_width / sf / 60000 * pg.pivot_size
638 #else:
639 # dim_a = region_width / sf / 5000 * pg.pivot_size
640 dim_a = region_width / scale_factor / 50000 * pg.pivot_size
641 dim_b = dim_a * 0.65
642 dim_c = dim_a * 0.05 + (pg.pivot_width * dim_a * 0.02)
643 dim_o = dim_c / 3
645 # fmt: off
646 # X Axis
647 coords = [
648 (x_loc, y_loc, z_loc),
649 (x_loc+dim_b, y_loc-dim_o, z_loc),
650 (x_loc+dim_b, y_loc+dim_o, z_loc),
651 (x_loc+dim_a, y_loc, z_loc),
652 (x_loc+dim_b, y_loc+dim_c, z_loc),
653 (x_loc+dim_b, y_loc-dim_c, z_loc),
655 # fmt: on
656 colour = (1.0, 0.0, 0.0, pg.pivot_alpha)
657 draw_3d(coords, "TRIS", colour, context)
658 coords = [(x_loc, y_loc, z_loc), (x_loc+dim_a, y_loc, z_loc)]
659 draw_3d(coords, "LINES", colour, context)
660 # fmt: off
661 # Y Axis
662 coords = [
663 (x_loc, y_loc, z_loc),
664 (x_loc-dim_o, y_loc+dim_b, z_loc),
665 (x_loc+dim_o, y_loc+dim_b, z_loc),
666 (x_loc, y_loc+dim_a, z_loc),
667 (x_loc+dim_c, y_loc+dim_b, z_loc),
668 (x_loc-dim_c, y_loc+dim_b, z_loc),
670 # fmt: on
671 colour = (0.0, 1.0, 0.0, pg.pivot_alpha)
672 draw_3d(coords, "TRIS", colour, context)
673 coords = [(x_loc, y_loc, z_loc), (x_loc, y_loc + dim_a, z_loc)]
674 draw_3d(coords, "LINES", colour, context)
675 # fmt: off
676 # Z Axis
677 coords = [
678 (x_loc, y_loc, z_loc),
679 (x_loc-dim_o, y_loc, z_loc+dim_b),
680 (x_loc+dim_o, y_loc, z_loc+dim_b),
681 (x_loc, y_loc, z_loc+dim_a),
682 (x_loc+dim_c, y_loc, z_loc+dim_b),
683 (x_loc-dim_c, y_loc, z_loc+dim_b),
685 # fmt: on
686 colour = (0.2, 0.5, 1.0, pg.pivot_alpha)
687 draw_3d(coords, "TRIS", colour, context)
688 coords = [(x_loc, y_loc, z_loc), (x_loc, y_loc, z_loc + dim_a)]
689 draw_3d(coords, "LINES", colour, context)
690 # Centre
691 coords = [(x_loc, y_loc, z_loc)]
692 colour = (1.0, 1.0, 0.0, pg.pivot_alpha)
693 draw_3d(coords, "POINTS", colour, context)
696 def scale_set(self, context):
697 """Sets Scale by dividing Pivot Distance by System Distance.
699 Note:
700 Sets Pivot Point Scale Factors by Measurement
701 Uses pg.pivotdis & pg.distance scene variables
703 Args:
704 context: Blender bpy.context instance.
706 Returns:
707 Status Set.
710 scene = context.scene
711 pg = scene.pdt_pg
712 sys_distance = pg.distance
713 scale_distance = pg.pivot_dis
714 if scale_distance > 0:
715 scale_factor = scale_distance / sys_distance
716 pg.pivot_scale = Vector((scale_factor, scale_factor, scale_factor))