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