Cleanup: strip trailing space, remove BOM
[blender-addons.git] / precision_drawing_tools / pdt_functions.py
blob38879618f713891ab4f780ef569afe78e0b3fc21
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 a3 is normal to screen, or depth
115 "XY": a1 = x, a2 = y, a3 = z
116 "XZ": a1 = x, a2 = z, a3 = y
117 "YZ": a1 = y, a2 = z, a3 = x
119 Args:
120 mode_pl: Plane Selector variable as input
122 Returns:
123 3 Integer indices.
126 order = {
127 "XY": (0, 1, 2),
128 "XZ": (0, 2, 1),
129 "YZ": (1, 2, 0),
130 "LO": (0, 1, 2),
132 return order[mode_pl]
135 def set_axis(mode_pl):
136 """Sets Active Axes for View Orientation.
138 Note:
139 Sets indices for axes from taper vectors
140 Axis order: Rotate Axis, Move Axis, Height Axis
142 Args:
143 mode_pl: Taper Axis Selector variable as input
145 Returns:
146 3 Integer Indicies.
149 order = {
150 "RX-MY": (0, 1, 2),
151 "RX-MZ": (0, 2, 1),
152 "RY-MX": (1, 0, 2),
153 "RY-MZ": (1, 2, 0),
154 "RZ-MX": (2, 0, 1),
155 "RZ-MY": (2, 1, 0),
157 return order[mode_pl]
160 def check_selection(num, bm, obj):
161 """Check that the Object's select_history has sufficient entries.
163 Note:
164 If selection history is not Verts, clears selection and history.
166 Args:
167 num: The number of entries required for each operation
168 bm: The Bmesh from the Object
169 obj: The Object
171 Returns:
172 list of 3D points as Vectors.
175 if len(bm.select_history) < num:
176 return None
177 active_vertex = bm.select_history[-1]
178 if isinstance(active_vertex, bmesh.types.BMVert):
179 vector_a = active_vertex.co
180 if num == 1:
181 return vector_a
182 if num == 2:
183 vector_b = bm.select_history[-2].co
184 return vector_a, vector_b
185 if num == 3:
186 vector_b = bm.select_history[-2].co
187 vector_c = bm.select_history[-3].co
188 return vector_a, vector_b, vector_c
189 if num == 4:
190 vector_b = bm.select_history[-2].co
191 vector_c = bm.select_history[-3].co
192 vector_d = bm.select_history[-4].co
193 return vector_a, vector_b, vector_c, vector_d
194 else:
195 for f in bm.faces:
196 f.select_set(False)
197 for e in bm.edges:
198 e.select_set(False)
199 for v in bm.verts:
200 v.select_set(False)
201 bmesh.update_edit_mesh(obj.data)
202 bm.select_history.clear()
203 return None
206 def update_sel(bm, verts, edges, faces):
207 """Updates Vertex, Edge and Face Selections following a function.
209 Args:
210 bm: Object Bmesh
211 verts: New Selection for Vertices
212 edges: The Edges on which to operate
213 faces: The Faces on which to operate
215 Returns:
216 Nothing.
218 for f in bm.faces:
219 f.select_set(False)
220 for e in bm.edges:
221 e.select_set(False)
222 for v in bm.verts:
223 v.select_set(False)
224 for v in verts:
225 v.select_set(True)
226 for e in edges:
227 e.select_set(True)
228 for f in faces:
229 f.select_set(True)
232 def view_coords(x_loc, y_loc, z_loc):
233 """Converts input Vector values to new Screen Oriented Vector.
235 Args:
236 x_loc: X coordinate from vector
237 y_loc: Y coordinate from vector
238 z_loc: Z coordinate from vector
240 Returns:
241 Vector adjusted to View's Inverted Tranformation Matrix.
244 areas = [a for a in bpy.context.screen.areas if a.type == "VIEW_3D"]
245 if len(areas) > 0:
246 view_matrix = areas[0].spaces.active.region_3d.view_matrix
247 view_matrix = view_matrix.to_3x3().normalized().inverted()
248 view_location = Vector((x_loc, y_loc, z_loc))
249 new_view_location = view_matrix @ view_location
250 return new_view_location
252 return Vector((0, 0, 0))
255 def view_coords_i(x_loc, y_loc, z_loc):
256 """Converts Screen Oriented input Vector values to new World Vector.
258 Note:
259 Converts View tranformation Matrix to Rotational Matrix
261 Args:
262 x_loc: X coordinate from vector
263 y_loc: Y coordinate from vector
264 z_loc: Z coordinate from vector
266 Returns:
267 Vector adjusted to View's Transformation Matrix.
270 areas = [a for a in bpy.context.screen.areas if a.type == "VIEW_3D"]
271 if len(areas) > 0:
272 view_matrix = areas[0].spaces.active.region_3d.view_matrix
273 view_matrix = view_matrix.to_3x3().normalized()
274 view_location = Vector((x_loc, y_loc, z_loc))
275 new_view_location = view_matrix @ view_location
276 return new_view_location
278 return Vector((0, 0, 0))
281 def view_dir(dis_v, ang_v):
282 """Converts Distance and Angle to View Oriented Vector.
284 Note:
285 Converts View Transformation Matrix to Rotational Matrix (3x3)
286 Angles are Converts to Radians from degrees.
288 Args:
289 dis_v: Scene PDT distance
290 ang_v: Scene PDT angle
292 Returns:
293 World Vector.
296 areas = [a for a in bpy.context.screen.areas if a.type == "VIEW_3D"]
297 if len(areas) > 0:
298 view_matrix = areas[0].spaces.active.region_3d.view_matrix
299 view_matrix = view_matrix.to_3x3().normalized().inverted()
300 view_location = Vector((0, 0, 0))
301 view_location.x = dis_v * cos(ang_v * pi / 180)
302 view_location.y = dis_v * sin(ang_v * pi / 180)
303 new_view_location = view_matrix @ view_location
304 return new_view_location
306 return Vector((0, 0, 0))
309 def euler_to_quaternion(roll, pitch, yaw):
310 """Converts Euler Rotation to Quaternion Rotation.
312 Args:
313 roll: Roll in Euler rotation
314 pitch: Pitch in Euler rotation
315 yaw: Yaw in Euler rotation
317 Returns:
318 Quaternion Rotation.
321 # fmt: off
322 quat_x = (np.sin(roll/2) * np.cos(pitch/2) * np.cos(yaw/2)
323 - np.cos(roll/2) * np.sin(pitch/2) * np.sin(yaw/2))
324 quat_y = (np.cos(roll/2) * np.sin(pitch/2) * np.cos(yaw/2)
325 + np.sin(roll/2) * np.cos(pitch/2) * np.sin(yaw/2))
326 quat_z = (np.cos(roll/2) * np.cos(pitch/2) * np.sin(yaw/2)
327 - np.sin(roll/2) * np.sin(pitch/2) * np.cos(yaw/2))
328 quat_w = (np.cos(roll/2) * np.cos(pitch/2) * np.cos(yaw/2)
329 + np.sin(roll/2) * np.sin(pitch/2) * np.sin(yaw/2))
330 # fmt: on
331 return Quaternion((quat_w, quat_x, quat_y, quat_z))
334 def arc_centre(vector_a, vector_b, vector_c):
335 """Calculates Centre of Arc from 3 Vector Locations using standard Numpy routine
337 Args:
338 vector_a: Active vector location
339 vector_b: Second vector location
340 vector_c: Third vector location
342 Returns:
343 Vector representing Arc Centre and Float representing Arc Radius.
346 coord_a = np.array([vector_a.x, vector_a.y, vector_a.z])
347 coord_b = np.array([vector_b.x, vector_b.y, vector_b.z])
348 coord_c = np.array([vector_c.x, vector_c.y, vector_c.z])
349 line_a = np.linalg.norm(coord_c - coord_b)
350 line_b = np.linalg.norm(coord_c - coord_a)
351 line_c = np.linalg.norm(coord_b - coord_a)
352 # fmt: off
353 line_s = (line_a+line_b+line_c) / 2
354 radius = (
355 line_a*line_b*line_c/4
356 / np.sqrt(line_s
357 * (line_s-line_a)
358 * (line_s-line_b)
359 * (line_s-line_c))
361 base_1 = line_a*line_a * (line_b*line_b + line_c*line_c - line_a*line_a)
362 base_2 = line_b*line_b * (line_a*line_a + line_c*line_c - line_b*line_b)
363 base_3 = line_c*line_c * (line_a*line_a + line_b*line_b - line_c*line_c)
364 # fmt: on
365 intersect_coord = np.column_stack((coord_a, coord_b, coord_c))
366 intersect_coord = intersect_coord.dot(np.hstack((base_1, base_2, base_3)))
367 intersect_coord /= base_1 + base_2 + base_3
368 return Vector((intersect_coord[0], intersect_coord[1], intersect_coord[2])), radius
371 def intersection(vertex_a, vertex_b, vertex_c, vertex_d, plane):
372 """Calculates Intersection Point of 2 Imagined Lines from 4 Vectors.
374 Note:
375 Calculates Converging Intersect Location and indication of
376 whether the lines are convergent using standard Numpy Routines
378 Args:
379 vertex_a: Active vector location of first line
380 vertex_b: Second vector location of first line
381 vertex_c: Third vector location of 2nd line
382 vertex_d: Fourth vector location of 2nd line
383 plane: Working Plane 4 Vector Locations representing 2 lines and Working Plane
385 Returns:
386 Intersection Vector and Boolean for convergent state.
389 if plane == "LO":
390 vertex_offset = vertex_b - vertex_a
391 vertex_b = view_coords_i(vertex_offset.x, vertex_offset.y, vertex_offset.z)
392 vertex_offset = vertex_d - vertex_a
393 vertex_d = view_coords_i(vertex_offset.x, vertex_offset.y, vertex_offset.z)
394 vertex_offset = vertex_c - vertex_a
395 vertex_c = view_coords_i(vertex_offset.x, vertex_offset.y, vertex_offset.z)
396 vector_ref = Vector((0, 0, 0))
397 coord_a = (vertex_c.x, vertex_c.y)
398 coord_b = (vertex_d.x, vertex_d.y)
399 coord_c = (vertex_b.x, vertex_b.y)
400 coord_d = (vector_ref.x, vector_ref.y)
401 else:
402 a1, a2, a3 = set_mode(plane)
403 coord_a = (vertex_c[a1], vertex_c[a2])
404 coord_b = (vertex_d[a1], vertex_d[a2])
405 coord_c = (vertex_a[a1], vertex_a[a2])
406 coord_d = (vertex_b[a1], vertex_b[a2])
407 v_stack = np.vstack([coord_a, coord_b, coord_c, coord_d])
408 h_stack = np.hstack((v_stack, np.ones((4, 1))))
409 line_a = np.cross(h_stack[0], h_stack[1])
410 line_b = np.cross(h_stack[2], h_stack[3])
411 x_loc, y_loc, z_loc = np.cross(line_a, line_b)
412 if z_loc == 0:
413 return Vector((0, 0, 0)), False
414 new_x_loc = x_loc / z_loc
415 new_z_loc = y_loc / z_loc
416 if plane == "LO":
417 new_y_loc = 0
418 else:
419 new_y_loc = vertex_a[a3]
420 # Order Vector Delta
421 if plane == "XZ":
422 vector_delta = Vector((new_x_loc, new_y_loc, new_z_loc))
423 elif plane == "XY":
424 vector_delta = Vector((new_x_loc, new_z_loc, new_y_loc))
425 elif plane == "YZ":
426 vector_delta = Vector((new_y_loc, new_x_loc, new_z_loc))
427 else:
428 # Must be Local View Plane
429 vector_delta = view_coords(new_x_loc, new_z_loc, new_y_loc) + vertex_a
430 return vector_delta, True
433 def get_percent(obj, flip_percent, per_v, data, scene):
434 """Calculates a Percentage Distance between 2 Vectors.
436 Note:
437 Calculates a point that lies a set percentage between two given points
438 using standard Numpy Routines.
440 Works for either 2 vertices for an object in Edit mode
441 or 2 selected objects in Object mode.
443 Args:
444 obj: The Object under consideration
445 flip_percent: Setting this to True measures the percentage starting from the second vector
446 per_v: Percentage Input Value
447 data: pg.flip, pg.percent scene variables & Operational Mode
448 scene: Context Scene
450 Returns:
451 World Vector.
454 pg = scene.pdt_pg
456 if obj.mode == "EDIT":
457 bm = bmesh.from_edit_mesh(obj.data)
458 verts = [v for v in bm.verts if v.select]
459 if len(verts) == 2:
460 vector_a = verts[0].co
461 vector_b = verts[1].co
462 if vector_a is None:
463 pg.error = PDT_ERR_VERT_MODE
464 bpy.context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
465 return None
466 else:
467 pg.error = PDT_ERR_SEL_2_V_1_E + str(len(verts)) + " Vertices"
468 bpy.context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
469 return None
470 coord_a = np.array([vector_a.x, vector_a.y, vector_a.z])
471 coord_b = np.array([vector_b.x, vector_b.y, vector_b.z])
472 if obj.mode == "OBJECT":
473 objs = bpy.context.view_layer.objects.selected
474 if len(objs) != 2:
475 pg.error = PDT_ERR_SEL_2_OBJS + str(len(objs)) + ")"
476 bpy.context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
477 return None
478 coord_a = np.array(
480 objs[-1].matrix_world.decompose()[0].x,
481 objs[-1].matrix_world.decompose()[0].y,
482 objs[-1].matrix_world.decompose()[0].z,
485 coord_b = np.array(
487 objs[-2].matrix_world.decompose()[0].x,
488 objs[-2].matrix_world.decompose()[0].y,
489 objs[-2].matrix_world.decompose()[0].z,
492 coord_c = coord_b - coord_a
493 coord_d = np.array([0, 0, 0])
494 _per_v = per_v
495 if (flip_percent and data != "MV") or data == "MV":
496 _per_v = 100 - per_v
497 coord_out = (coord_d+coord_c) * (_per_v / 100) + coord_a
498 return Vector((coord_out[0], coord_out[1], coord_out[2]))
501 def obj_check(obj, scene, operation):
502 """Check Object & Selection Validity.
504 Args:
505 obj: Active Object
506 scene: Active Scene
507 operation: The Operation e.g. Create New Vertex
509 Returns:
510 Object Bmesh
511 Validity Boolean.
514 pg = scene.pdt_pg
515 _operation = operation.upper()
517 if obj is None:
518 pg.error = PDT_ERR_NO_ACT_OBJ
519 bpy.context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
520 return None, False
521 if obj.mode == "EDIT":
522 bm = bmesh.from_edit_mesh(obj.data)
523 if _operation == "S":
524 if len(bm.edges) < 1:
525 pg.error = f"{PDT_ERR_SEL_1_EDGEM} {len(bm.edges)})"
526 bpy.context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
527 return None, False
528 return bm, True
529 if len(bm.select_history) >= 1:
530 vector_a = None
531 if _operation not in {"D", "E", "F", "G", "N", "S"}:
532 vector_a = check_selection(1, bm, obj)
533 else:
534 verts = [v for v in bm.verts if v.select]
535 if len(verts) > 0:
536 vector_a = verts[0]
537 if vector_a is None:
538 pg.error = PDT_ERR_VERT_MODE
539 bpy.context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
540 return None, False
541 return bm, True
542 return None, True
545 def dis_ang(values, flip_angle, plane, scene):
546 """Set Working Axes when using Direction command.
548 Args:
549 values: Input Arguments
550 flip_angle: Whether to flip the angle
551 plane: Working Plane
552 scene: Current Scene
554 Returns:
555 Directional Offset as a Vector.
558 pg = scene.pdt_pg
559 dis_v = float(values[0])
560 ang_v = float(values[1])
561 if flip_angle:
562 if ang_v > 0:
563 ang_v = ang_v - 180
564 else:
565 ang_v = ang_v + 180
566 pg.angle = ang_v
567 if plane == "LO":
568 vector_delta = view_dir(dis_v, ang_v)
569 else:
570 a1, a2, _ = set_mode(plane)
571 vector_delta = Vector((0, 0, 0))
572 # fmt: off
573 vector_delta[a1] = vector_delta[a1] + (dis_v * cos(ang_v * pi/180))
574 vector_delta[a2] = vector_delta[a2] + (dis_v * sin(ang_v * pi/180))
575 # fmt: on
576 return vector_delta
579 # Shader for displaying the Pivot Point as Graphics.
581 SHADER = gpu.shader.from_builtin("3D_UNIFORM_COLOR") if not bpy.app.background else None
584 def draw_3d(coords, gtype, rgba, context):
585 """Draw Pivot Point Graphics.
587 Note:
588 Draws either Lines Points, or Tris using defined shader
590 Args:
591 coords: Input Coordinates List
592 gtype: Graphic Type
593 rgba: Colour in RGBA format
594 context: Blender bpy.context instance.
596 Returns:
597 Nothing.
600 batch = batch_for_shader(SHADER, gtype, {"pos": coords})
602 try:
603 if coords is not None:
604 bgl.glEnable(bgl.GL_BLEND)
605 SHADER.bind()
606 SHADER.uniform_float("color", rgba)
607 batch.draw(SHADER)
608 except:
609 raise PDT_ShaderError
612 def draw_callback_3d(self, context):
613 """Create Coordinate List for Pivot Point Graphic.
615 Note:
616 Creates coordinates for Pivot Point Graphic consisting of 6 Tris
617 and one Point colour coded Red; X axis, Green; Y axis, Blue; Z axis
618 and a yellow point based upon screen scale
620 Args:
621 context: Blender bpy.context instance.
623 Returns:
624 Nothing.
627 scene = context.scene
628 pg = scene.pdt_pg
629 region_width = context.region.width
630 x_loc = pg.pivot_loc.x
631 y_loc = pg.pivot_loc.y
632 z_loc = pg.pivot_loc.z
633 # Scale it from view
634 areas = [a for a in context.screen.areas if a.type == "VIEW_3D"]
635 if len(areas) > 0:
636 scale_factor = abs(areas[0].spaces.active.region_3d.window_matrix.decompose()[2][1])
637 # Check for orhtographic view and resize
638 #if areas[0].spaces.active.region_3d.is_orthographic_side_view:
639 # dim_a = region_width / sf / 60000 * pg.pivot_size
640 #else:
641 # dim_a = region_width / sf / 5000 * pg.pivot_size
642 dim_a = region_width / scale_factor / 50000 * pg.pivot_size
643 dim_b = dim_a * 0.65
644 dim_c = dim_a * 0.05 + (pg.pivot_width * dim_a * 0.02)
645 dim_o = dim_c / 3
647 # fmt: off
648 # X Axis
649 coords = [
650 (x_loc, y_loc, z_loc),
651 (x_loc+dim_b, y_loc-dim_o, z_loc),
652 (x_loc+dim_b, y_loc+dim_o, z_loc),
653 (x_loc+dim_a, y_loc, z_loc),
654 (x_loc+dim_b, y_loc+dim_c, z_loc),
655 (x_loc+dim_b, y_loc-dim_c, z_loc),
657 # fmt: on
658 colour = (1.0, 0.0, 0.0, pg.pivot_alpha)
659 draw_3d(coords, "TRIS", colour, context)
660 coords = [(x_loc, y_loc, z_loc), (x_loc+dim_a, y_loc, z_loc)]
661 draw_3d(coords, "LINES", colour, context)
662 # fmt: off
663 # Y Axis
664 coords = [
665 (x_loc, y_loc, z_loc),
666 (x_loc-dim_o, y_loc+dim_b, z_loc),
667 (x_loc+dim_o, y_loc+dim_b, z_loc),
668 (x_loc, y_loc+dim_a, z_loc),
669 (x_loc+dim_c, y_loc+dim_b, z_loc),
670 (x_loc-dim_c, y_loc+dim_b, z_loc),
672 # fmt: on
673 colour = (0.0, 1.0, 0.0, pg.pivot_alpha)
674 draw_3d(coords, "TRIS", colour, context)
675 coords = [(x_loc, y_loc, z_loc), (x_loc, y_loc + dim_a, z_loc)]
676 draw_3d(coords, "LINES", colour, context)
677 # fmt: off
678 # Z Axis
679 coords = [
680 (x_loc, y_loc, z_loc),
681 (x_loc-dim_o, y_loc, z_loc+dim_b),
682 (x_loc+dim_o, y_loc, z_loc+dim_b),
683 (x_loc, y_loc, z_loc+dim_a),
684 (x_loc+dim_c, y_loc, z_loc+dim_b),
685 (x_loc-dim_c, y_loc, z_loc+dim_b),
687 # fmt: on
688 colour = (0.2, 0.5, 1.0, pg.pivot_alpha)
689 draw_3d(coords, "TRIS", colour, context)
690 coords = [(x_loc, y_loc, z_loc), (x_loc, y_loc, z_loc + dim_a)]
691 draw_3d(coords, "LINES", colour, context)
692 # Centre
693 coords = [(x_loc, y_loc, z_loc)]
694 colour = (1.0, 1.0, 0.0, pg.pivot_alpha)
695 draw_3d(coords, "POINTS", colour, context)
698 def scale_set(self, context):
699 """Sets Scale by dividing Pivot Distance by System Distance.
701 Note:
702 Sets Pivot Point Scale Factors by Measurement
703 Uses pg.pivotdis & pg.distance scene variables
705 Args:
706 context: Blender bpy.context instance.
708 Returns:
709 Status Set.
712 scene = context.scene
713 pg = scene.pdt_pg
714 sys_distance = pg.distance
715 scale_distance = pg.pivot_dis
716 if scale_distance > 0:
717 scale_factor = scale_distance / sys_distance
718 pg.pivot_scale = Vector((scale_factor, scale_factor, scale_factor))