1"""
2.. module:: _operations
3    :platform: Unix, Windows
4    :synopsis: Helper functions for operations module
5
6.. moduleauthor:: Onur Rauf Bingol <orbingol@gmail.com>
7
8"""
9
10from . import linalg, helpers
11from .exceptions import GeomdlException
12
13
14# Initialize an empty __all__ for controlling imports
15__all__ = []
16
17
18def tangent_curve_single(obj, u, normalize):
19    """ Evaluates the curve tangent vector at the given parameter value.
20
21    The output returns a list containing the starting point (i.e. origin) of the vector and the vector itself.
22
23    :param obj: input curve
24    :type obj: abstract.Curve
25    :param u: parameter
26    :type u: float
27    :param normalize: if True, the returned vector is converted to a unit vector
28    :type normalize: bool
29    :return: a list containing "point" and "vector" pairs
30    :rtype: tuple
31    """
32    # 1st derivative of the curve gives the tangent
33    ders = obj.derivatives(u, 1)
34
35    point = ders[0]
36    vector = linalg.vector_normalize(ders[1]) if normalize else ders[1]
37
38    return tuple(point), tuple(vector)
39
40
41def tangent_curve_single_list(obj, param_list, normalize):
42    """ Evaluates the curve tangent vectors at the given list of parameter values.
43
44    :param obj: input curve
45    :type obj: abstract.Curve
46    :param param_list: parameter list
47    :type param_list: list or tuple
48    :param normalize: if True, the returned vector is converted to a unit vector
49    :type normalize: bool
50    :return: a list containing "point" and "vector" pairs
51    :rtype: tuple
52    """
53    ret_vector = []
54    for param in param_list:
55        temp = tangent_curve_single(obj, param, normalize)
56        ret_vector.append(temp)
57    return tuple(ret_vector)
58
59
60def normal_curve_single(obj, u, normalize):
61    """ Evaluates the vector normal to the tangent vector at the input parameter, u.
62
63    The output returns a list containing the starting point (i.e. origin) of the vector and the vector itself.
64
65    :param obj: input curve
66    :type obj: abstract.Curve
67    :param u: parameter
68    :type u: float
69    :param normalize: if True, the returned vector is converted to a unit vector
70    :type normalize: bool
71    :return: a list containing "point" and "vector" pairs
72    :rtype: tuple
73    """
74    # Find 1st derivative
75    ders = obj.derivatives(u, 1)
76
77    # Apply fix on https://github.com/orbingol/NURBS-Python/pull/50#issuecomment-499354073
78    t = ders[1]
79    tn = [0.0, 0.0, 1.0]
80    n = linalg.vector_cross(t, tn)
81
82    # Normalize the vector component
83    vector = linalg.vector_normalize(n) if normalize else n
84    point = ders[0]
85
86    return tuple(point), tuple(vector)
87
88
89def normal_curve_single_list(obj, param_list, normalize):
90    """ Evaluates the curve normal vectors at the given list of parameter values.
91
92    :param obj: input curve
93    :type obj: abstract.Curve
94    :param param_list: parameter list
95    :type param_list: list or tuple
96    :param normalize: if True, the returned vector is converted to a unit vector
97    :type normalize: bool
98    :return: a list containing "point" and "vector" pairs
99    :rtype: tuple
100    """
101    ret_vector = []
102    for param in param_list:
103        temp = normal_curve_single(obj, param, normalize)
104        ret_vector.append(temp)
105    return tuple(ret_vector)
106
107
108def binormal_curve_single(obj, u, normalize):
109    """ Evaluates the curve binormal vector at the given u parameter.
110
111    Curve binormal is the cross product of the normal and the tangent vectors.
112    The output returns a list containing the starting point (i.e. origin) of the vector and the vector itself.
113
114    :param obj: input curve
115    :type obj: abstract.Curve
116    :param u: parameter
117    :type u: float
118    :param normalize: if True, the returned vector is converted to a unit vector
119    :type normalize: bool
120    :return: a list containing "point" and "vector" pairs
121    :rtype: tuple
122    """
123    # Cross product of tangent and normal vectors gives binormal vector
124    tan_vector = tangent_curve_single(obj, u, normalize)
125    norm_vector = normal_curve_single(obj, u, normalize)
126
127    point = tan_vector[0]
128    vector = linalg.vector_cross(tan_vector[1], norm_vector[1])
129    vector = linalg.vector_normalize(vector) if normalize else vector
130
131    return tuple(point), tuple(vector)
132
133
134def binormal_curve_single_list(obj, param_list, normalize):
135    """ Evaluates the curve binormal vectors at the given list of parameter values.
136
137    :param obj: input curve
138    :type obj: abstract.Curve
139    :param param_list: parameter list
140    :type param_list: list or tuple
141    :param normalize: if True, the returned vector is converted to a unit vector
142    :type normalize: bool
143    :return: a list containing "point" and "vector" pairs
144    :rtype: tuple
145    """
146    ret_vector = []
147    for param in param_list:
148        temp = binormal_curve_single(obj, param, normalize)
149        ret_vector.append(temp)
150    return tuple(ret_vector)
151
152
153def tangent_surface_single(obj, uv, normalize):
154    """ Evaluates the surface tangent vector at the given (u,v) parameter pair.
155
156    The output returns a list containing the starting point (i.e., origin) of the vector and the vectors themselves.
157
158    :param obj: input surface
159    :type obj: abstract.Surface
160    :param uv: (u,v) parameter pair
161    :type uv: list or tuple
162    :param normalize: if True, the returned tangent vector is converted to a unit vector
163    :type normalize: bool
164    :return: A list in the order of "surface point", "derivative w.r.t. u" and "derivative w.r.t. v"
165    :rtype: list
166    """
167    # Tangent is the 1st derivative of the surface
168    skl = obj.derivatives(uv[0], uv[1], 1)
169
170    point = skl[0][0]
171    vector_u = linalg.vector_normalize(skl[1][0]) if normalize else skl[1][0]
172    vector_v = linalg.vector_normalize(skl[0][1]) if normalize else skl[0][1]
173
174    return tuple(point), tuple(vector_u), tuple(vector_v)
175
176
177def tangent_surface_single_list(obj, param_list, normalize):
178    """ Evaluates the surface tangent vectors at the given list of parameter values.
179
180    :param obj: input surface
181    :type obj: abstract.Surface
182    :param param_list: parameter list
183    :type param_list: list or tuple
184    :param normalize: if True, the returned vector is converted to a unit vector
185    :type normalize: bool
186    :return: a list containing "point" and "vector" pairs
187    :rtype: tuple
188    """
189    ret_vector = []
190    for param in param_list:
191        temp = tangent_surface_single(obj, param, normalize)
192        ret_vector.append(temp)
193    return tuple(ret_vector)
194
195
196def normal_surface_single(obj, uv, normalize):
197    """ Evaluates the surface normal vector at the given (u, v) parameter pair.
198
199    The output returns a list containing the starting point (i.e. origin) of the vector and the vector itself.
200
201    :param obj: input surface
202    :type obj: abstract.Surface
203    :param uv: (u,v) parameter pair
204    :type uv: list or tuple
205    :param normalize: if True, the returned normal vector is converted to a unit vector
206    :type normalize: bool
207    :return: a list in the order of "surface point" and "normal vector"
208    :rtype: list
209    """
210    # Take the 1st derivative of the surface
211    skl = obj.derivatives(uv[0], uv[1], 1)
212
213    point = skl[0][0]
214    vector = linalg.vector_cross(skl[1][0], skl[0][1])
215    vector = linalg.vector_normalize(vector) if normalize else vector
216
217    return tuple(point), tuple(vector)
218
219
220def normal_surface_single_list(obj, param_list, normalize):
221    """ Evaluates the surface normal vectors at the given list of parameter values.
222
223    :param obj: input surface
224    :type obj: abstract.Surface
225    :param param_list: parameter list
226    :type param_list: list or tuple
227    :param normalize: if True, the returned vector is converted to a unit vector
228    :type normalize: bool
229    :return: a list containing "point" and "vector" pairs
230    :rtype: tuple
231    """
232    ret_vector = []
233    for param in param_list:
234        temp = normal_surface_single(obj, param, normalize)
235        ret_vector.append(temp)
236    return tuple(ret_vector)
237
238
239def find_ctrlpts_curve(t, curve, **kwargs):
240    """ Finds the control points involved in the evaluation of the curve point defined by the input parameter.
241
242    This function uses a modified version of the algorithm *A3.1 CurvePoint* from The NURBS Book by Piegl & Tiller.
243
244    :param t: parameter
245    :type t: float
246    :param curve: input curve object
247    :type curve: abstract.Curve
248    :return: 1-dimensional control points array
249    :rtype: list
250    """
251    # Get keyword arguments
252    span_func = kwargs.get('find_span_func', helpers.find_span_linear)
253
254    # Find spans and the constant index
255    span = span_func(curve.degree, curve.knotvector, len(curve.ctrlpts), t)
256    idx = span - curve.degree
257
258    # Find control points involved in evaluation of the curve point at the input parameter
259    curve_ctrlpts = [() for _ in range(curve.degree + 1)]
260    for i in range(0, curve.degree + 1):
261        curve_ctrlpts[i] = curve.ctrlpts[idx + i]
262
263    # Return control points array
264    return curve_ctrlpts
265
266
267def find_ctrlpts_surface(t_u, t_v, surf, **kwargs):
268    """ Finds the control points involved in the evaluation of the surface point defined by the input parameter pair.
269
270    This function uses a modified version of the algorithm *A3.5 SurfacePoint* from The NURBS Book by Piegl & Tiller.
271
272    :param t_u: parameter on the u-direction
273    :type t_u: float
274    :param t_v: parameter on the v-direction
275    :type t_v: float
276    :param surf: input surface
277    :type surf: abstract.Surface
278    :return: 2-dimensional control points array
279    :rtype: list
280    """
281    # Get keyword arguments
282    span_func = kwargs.get('find_span_func', helpers.find_span_linear)
283
284    # Find spans
285    span_u = span_func(surf.degree_u, surf.knotvector_u, surf.ctrlpts_size_u, t_u)
286    span_v = span_func(surf.degree_v, surf.knotvector_v, surf.ctrlpts_size_v, t_v)
287
288    # Constant indices
289    idx_u = span_u - surf.degree_u
290    idx_v = span_v - surf.degree_v
291
292    # Find control points involved in evaluation of the surface point at the input parameter pair (u, v)
293    surf_ctrlpts = [[] for _ in range(surf.degree_u + 1)]
294    for k in range(surf.degree_u + 1):
295        temp = [() for _ in range(surf.degree_v + 1)]
296        for l in range(surf.degree_v + 1):
297            temp[l] = surf.ctrlpts2d[idx_u + k][idx_v + l]
298        surf_ctrlpts[k] = temp
299
300    # Return 2-dimensional control points array
301    return surf_ctrlpts
302
303
304def link_curves(*args, **kwargs):
305    """ Links the input curves together.
306
307    The end control point of the curve k has to be the same with the start control point of the curve k + 1.
308
309    Keyword Arguments:
310        * ``tol``: tolerance value for checking equality. *Default: 10e-8*
311        * ``validate``: flag to enable input validation. *Default: False*
312
313    :return: a tuple containing knot vector, control points, weights vector and knots
314    """
315    # Get keyword arguments
316    tol = kwargs.get('tol', 10e-8)
317    validate = kwargs.get('validate', False)
318
319    # Validate input
320    if validate:
321        for idx in range(len(args) - 1):
322            if linalg.point_distance(args[idx].ctrlpts[-1], args[idx + 1].ctrlpts[0]) > tol:
323                raise GeomdlException("Curve #" + str(idx) + " and Curve #" + str(idx + 1) + " don't touch each other")
324
325    kv = []  # new knot vector
326    cpts = []  # new control points array
327    wgts = []  # new weights array
328    kv_connected = []  # superfluous knots to be removed
329    pdomain_end = 0
330
331    # Loop though the curves
332    for arg in args:
333        # Process knot vectors
334        if not kv:
335            kv += list(arg.knotvector[:-(arg.degree + 1)])  # get rid of the last superfluous knot to maintain split curve notation
336            cpts += list(arg.ctrlpts)
337            # Process control points
338            if arg.rational:
339                wgts += list(arg.weights)
340            else:
341                tmp_w = [1.0 for _ in range(arg.ctrlpts_size)]
342                wgts += tmp_w
343        else:
344            tmp_kv = [pdomain_end + k for k in arg.knotvector[1:-(arg.degree + 1)]]
345            kv += tmp_kv
346            cpts += list(arg.ctrlpts[1:])
347            # Process control points
348            if arg.rational:
349                wgts += list(arg.weights[1:])
350            else:
351                tmp_w = [1.0 for _ in range(arg.ctrlpts_size - 1)]
352                wgts += tmp_w
353
354        pdomain_end += arg.knotvector[-1]
355        kv_connected.append(pdomain_end)
356
357    # Fix curve by appending the last knot to the end
358    kv += [pdomain_end for _ in range(arg.degree + 1)]
359    # Remove the last knot from knot insertion list
360    kv_connected.pop()
361
362    return kv, cpts, wgts, kv_connected
363