1#====================== BEGIN GPL LICENSE BLOCK ====================== 2# 3# This program is free software; you can redistribute it and/or 4# modify it under the terms of the GNU General Public License 5# as published by the Free Software Foundation; either version 2 6# of the License, or (at your option) any later version. 7# 8# This program is distributed in the hope that it will be useful, 9# but WITHOUT ANY WARRANTY; without even the implied warranty of 10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11# GNU General Public License for more details. 12# 13# You should have received a copy of the GNU General Public License 14# along with this program; if not, write to the Free Software Foundation, 15# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 16# 17#======================= END GPL LICENSE BLOCK ======================== 18 19# <pep8 compliant> 20 21import bpy 22import math 23import collections 24 25from itertools import tee, chain, islice, repeat 26from mathutils import Vector, Matrix, Color 27from rna_prop_ui import rna_idprop_value_to_python 28 29 30#============================================= 31# Math 32#============================================= 33 34 35def angle_on_plane(plane, vec1, vec2): 36 """ Return the angle between two vectors projected onto a plane. 37 """ 38 plane.normalize() 39 vec1 = vec1 - (plane * (vec1.dot(plane))) 40 vec2 = vec2 - (plane * (vec2.dot(plane))) 41 vec1.normalize() 42 vec2.normalize() 43 44 # Determine the angle 45 angle = math.acos(max(-1.0, min(1.0, vec1.dot(vec2)))) 46 47 if angle < 0.00001: # close enough to zero that sign doesn't matter 48 return angle 49 50 # Determine the sign of the angle 51 vec3 = vec2.cross(vec1) 52 vec3.normalize() 53 sign = vec3.dot(plane) 54 if sign >= 0: 55 sign = 1 56 else: 57 sign = -1 58 59 return angle * sign 60 61 62# Convert between a matrix and axis+roll representations. 63# Re-export the C implementation internally used by bones. 64matrix_from_axis_roll = bpy.types.Bone.MatrixFromAxisRoll 65axis_roll_from_matrix = bpy.types.Bone.AxisRollFromMatrix 66 67 68def matrix_from_axis_pair(y_axis, other_axis, axis_name): 69 assert axis_name in 'xz' 70 71 y_axis = Vector(y_axis).normalized() 72 73 if axis_name == 'x': 74 z_axis = Vector(other_axis).cross(y_axis).normalized() 75 x_axis = y_axis.cross(z_axis) 76 else: 77 x_axis = y_axis.cross(other_axis).normalized() 78 z_axis = x_axis.cross(y_axis) 79 80 return Matrix((x_axis, y_axis, z_axis)).transposed() 81 82 83#============================================= 84# Color correction functions 85#============================================= 86 87 88def linsrgb_to_srgb (linsrgb): 89 """Convert physically linear RGB values into sRGB ones. The transform is 90 uniform in the components, so *linsrgb* can be of any shape. 91 92 *linsrgb* values should range between 0 and 1, inclusively. 93 94 """ 95 # From Wikipedia, but easy analogue to the above. 96 gamma = 1.055 * linsrgb**(1./2.4) - 0.055 97 scale = linsrgb * 12.92 98 # return np.where (linsrgb > 0.0031308, gamma, scale) 99 if linsrgb > 0.0031308: 100 return gamma 101 return scale 102 103 104def gamma_correct(color): 105 106 corrected_color = Color() 107 for i, component in enumerate(color): 108 corrected_color[i] = linsrgb_to_srgb(color[i]) 109 return corrected_color 110 111 112#============================================= 113# Iterators 114#============================================= 115 116 117def padnone(iterable, pad=None): 118 return chain(iterable, repeat(pad)) 119 120 121def pairwise_nozip(iterable): 122 "s -> (s0,s1), (s1,s2), (s2,s3), ..." 123 a, b = tee(iterable) 124 next(b, None) 125 return a, b 126 127 128def pairwise(iterable): 129 "s -> (s0,s1), (s1,s2), (s2,s3), ..." 130 a, b = tee(iterable) 131 next(b, None) 132 return zip(a, b) 133 134 135def map_list(func, *inputs): 136 "[func(a0,b0...), func(a1,b1...), ...]" 137 return list(map(func, *inputs)) 138 139 140def skip(n, iterable): 141 "Returns an iterator skipping first n elements of an iterable." 142 iterator = iter(iterable) 143 if n == 1: 144 next(iterator, None) 145 else: 146 next(islice(iterator, n, n), None) 147 return iterator 148 149 150def map_apply(func, *inputs): 151 "Apply the function to inputs like map for side effects, discarding results." 152 collections.deque(map(func, *inputs), maxlen=0) 153 154 155#============================================= 156# Misc 157#============================================= 158 159 160def force_lazy(value): 161 if callable(value): 162 return value() 163 else: 164 return value 165 166 167def copy_attributes(a, b): 168 keys = dir(a) 169 for key in keys: 170 if not key.startswith("_") \ 171 and not key.startswith("error_") \ 172 and key != "group" \ 173 and key != "is_valid" \ 174 and key != "rna_type" \ 175 and key != "bl_rna": 176 try: 177 setattr(b, key, getattr(a, key)) 178 except AttributeError: 179 pass 180 181 182def property_to_python(value): 183 value = rna_idprop_value_to_python(value) 184 185 if isinstance(value, dict): 186 return { k: property_to_python(v) for k, v in value.items() } 187 elif isinstance(value, list): 188 return map_list(property_to_python, value) 189 else: 190 return value 191 192 193def clone_parameters(target): 194 return property_to_python(dict(target)) 195 196 197def assign_parameters(target, val_dict=None, **params): 198 if val_dict is not None: 199 for key in list(target.keys()): 200 del target[key] 201 202 data = { **val_dict, **params } 203 else: 204 data = params 205 206 for key, value in data.items(): 207 try: 208 target[key] = value 209 except Exception as e: 210 raise Exception("Couldn't set {} to {}: {}".format(key,value,e)) 211 212 213def select_object(context, object, deselect_all=False): 214 view_layer = context.view_layer 215 216 if deselect_all: 217 for objt in view_layer.objects: 218 objt.select_set(False) # deselect all objects 219 220 object.select_set(True) 221 view_layer.objects.active = object 222